From: MirceaKitsune Date: Thu, 14 Jul 2011 20:13:25 +0000 (+0300) Subject: Include the source of the Darkplaces engine too X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=e39f59ac14770b3339553eb55723eef0e038298d;p=voretournament%2Fvoretournament.git Include the source of the Darkplaces engine too --- diff --git a/misc/source/darkplaces-src/.gitattributes b/misc/source/darkplaces-src/.gitattributes new file mode 100644 index 00000000..ec3bfe3d --- /dev/null +++ b/misc/source/darkplaces-src/.gitattributes @@ -0,0 +1,222 @@ +* -crlf + +*.0 -diff -crlf +*.1 crlf=input +*.3 crlf=input +*.7z -diff -crlf +*.ac crlf=input +*.a -diff -crlf +*.afm crlf=input +*.aft crlf=input +*.ai -diff -crlf +*.aliases crlf=input +all crlf=input +*.am crlf=input +*.animinfo crlf=input +*.aps -diff -crlf +*.asc -diff -crlf +*.ase -crlf +*.bat -crlf +*.bgs crlf=input +*.blend1 -diff -crlf +*.blend -diff -crlf +blind_id -diff -crlf +*.bmp -diff -crlf +branch-manager crlf=input +*.brand crlf=input +BSDmakefile crlf=input +bsp2ent crlf=input +*.bsp -diff -crlf +*.cache -diff -crlf +*.cbp -crlf +*.cbp -diff -crlf +*.c crlf=input +*.cfg crlf=input +*.cg crlf=input +ChangeLog crlf=input +CHANGES crlf=input +cjpeg -diff -crlf +COMPILING crlf=input +compress-texture crlf=input +*.conf crlf=input +CONTRIBUTORS crlf=input +COPYING crlf=input +*.cpp crlf=input +create crlf=input +*.cron crlf=input +*.css crlf=input +*.cvswrappers crlf=input +*.d0pk -diff -crlf +*.db -diff -crlf +*.default crlf=input +*.def crlf=input +*.dem -diff -crlf +*.dev -crlf +dir -diff -crlf +djpeg -diff -crlf +*.dll -diff -crlf +DOCS -diff -crlf +*.dot crlf=input +DoxyConfig crlf=input +doxyfile crlf=input +Doxyfile crlf=input +*.doxygen crlf=input +*.dpm -diff -crlf +*.dsp -crlf +*.dsw -crlf +*.dtd crlf=input +*.dylib -diff -crlf +empty -diff -crlf +*.EncoderPlugin crlf=input +*.flac -diff -crlf +*.form crlf=input +*.framegroups crlf=input +*.game crlf=input +*.gdb crlf=input +gendox crlf=input +gendoxfunctions crlf=input +genDoxyfile crlf=input +*.gif -diff -crlf +*.gitattributes crlf=input +git-branch-manager crlf=input +git-filter-index crlf=input +git-filter-repository crlf=input +*.gitignore crlf=input +git-pk3-import crlf=input +git-pk3-merge crlf=input +git-pullall crlf=input +git-recurse crlf=input +git-split-repository crlf=input +git-svn-checkout crlf=input +git-svn-update crlf=input +git-update-octopus crlf=input +*.glp crlf=input +*.glsl crlf=input +GPL crlf=input +*.hardwired crlf=input +*.h crlf=input +*.hs crlf=input +*.html crlf=input +*.html-part crlf=input +*.icns -diff -crlf +*.ico -diff -crlf +*.idl crlf=input +*.idsoftware crlf=input +*.inc crlf=input +*.in crlf=input +*.info-1 -diff -crlf +*.info-2 -diff -crlf +*.info -diff -crlf +*.inl crlf=input +*.instantaction crlf=input +*.iqm -diff -crlf +*.java crlf=input +*.jhm crlf=input +*.jnlp crlf=input +jpegtran -diff -crlf +*.jpg -diff -crlf +*.jsmooth crlf=input +*.la crlf=input +LGPL crlf=input +LICENSE crlf=input +*.lmp -diff -crlf +*.loaders crlf=input +*.lso -diff -crlf +*.m4 crlf=input +makefile crlf=input +Makefile crlf=input +makespr32 crlf=input +*.map -crlf filter=mapclean +*.mapinfo crlf=input +*.m crlf=input +*.md3 -diff -crlf +*.md5anim -crlf +*.md5mesh -crlf +*.mdl -diff -crlf +*.med crlf=input +*.mf crlf=input +*.mid -diff -crlf +*.mk crlf=input +*.mkdir -diff -crlf +*.mmpz -diff -crlf +*.modules crlf=input +*.nib -crlf +*.obj -crlf +OFFSETS -diff -crlf +*.ogg -diff -crlf +*.options crlf=input +pangorc crlf=input +*.patch crlf=input +*.patchsets crlf=input +*.pc crlf=input +*.pcx -diff -crlf +*.pfb -diff -crlf +*.pfm -diff -crlf +*.pk3 -diff -crlf +PkgInfo crlf=input +*.pl crlf=input +*.plist crlf=input +*.pm crlf=input +*.png -diff -crlf +POSITIONS -diff -crlf +*.proj -crlf +*.properties crlf=input +*.psd -diff -crlf +*.py crlf=input +*.q3map1 crlf=input +*.qc crlf=input +*.qdt crlf=input +*.qh crlf=input +*.rb crlf=input +*.rc2 crlf=input +*.rc -crlf +rdjpgcom -diff -crlf +*.readme crlf=input +README crlf=input +*.rtlights -diff -crlf +SCHEMA crlf=input +*.scm crlf=input +sdl-config crlf=input +SDL -diff -crlf +*.shader crlf=input +*.sh crlf=input +*.skin crlf=input +*.sln -crlf +*.sounds crlf=input +*.sp2 -diff -crlf +*.spr32 -diff -crlf +*.spr -diff -crlf +*.src crlf=input +*.strings crlf=input +strip crlf=input +*.svg -diff -crlf +*.TAB -diff -crlf +*.tga -diff -crlf +TMAP -diff -crlf +todo crlf=input +TODO crlf=input +*.ttf -diff -crlf +*.TTF -diff -crlf +*.txt crlf=input +update-shaderlists crlf=input +*.vbs -crlf +*.vcproj -crlf +versionbuilder crlf=input +*.wav -diff -crlf +*.waypoints -diff -crlf +w crlf=input +*.width crlf=input +*.workspace -crlf +wrjpgcom -diff -crlf +*.xcf -diff -crlf +*.xlink crlf=input +*.xml crlf=input +xonotic-map-compiler-autobuild crlf=input +xonotic-map-compiler crlf=input +xonotic-map-screenshot crlf=input +xonotic-osx-agl crlf=input +xonotic-osx-sdl crlf=input +*.xpm crlf=input +*.zip -diff -crlf +zipdiff crlf=input +*.zym -diff -crlf diff --git a/misc/source/darkplaces-src/.gitignore b/misc/source/darkplaces-src/.gitignore new file mode 100644 index 00000000..de9a5adf --- /dev/null +++ b/misc/source/darkplaces-src/.gitignore @@ -0,0 +1,20 @@ +*.d +*.o +*.i +*.s +ChangeLog +darkplaces-agl +darkplaces-glx +darkplaces-sdl +darkplaces-dedicated +gmon.out +*.ncb +*.opt +*.plg +*.exe +darkplaces_private.h +darkplaces_private.rc +Makefile.win +*.dll +*.dylib +*.dSYM diff --git a/misc/source/darkplaces-src/BSDmakefile b/misc/source/darkplaces-src/BSDmakefile new file mode 100644 index 00000000..ac9793a0 --- /dev/null +++ b/misc/source/darkplaces-src/BSDmakefile @@ -0,0 +1,111 @@ +##### DP_MAKE_TARGET autodetection and arch specific variables ##### + +.ifndef DP_MAKE_TARGET + +DP_MAKE_TARGET=bsd + +.endif +DP_ARCH != uname + +# Command used to delete files +CMD_RM=$(CMD_UNIXRM) + +# default targets +TARGETS_DEBUG=sv-debug cl-debug sdl-debug +TARGETS_PROFILE=sv-profile cl-profile sdl-profile +TARGETS_RELEASE=sv-release cl-release sdl-release +TARGETS_RELEASE_PROFILE=sv-release-profile cl-release-profile sdl-release-profile +TARGETS_NEXUIZ=sv-nexuiz cl-nexuiz sdl-nexuiz + +# X11 libs +UNIX_X11LIBPATH=/usr/X11R6/lib + +# BSD configuration +.if $(DP_MAKE_TARGET) == "bsd" + +# FreeBSD uses OSS +.if $(DP_ARCH) == "FreeBSD" +DEFAULT_SNDAPI=OSS +.else +DEFAULT_SNDAPI=BSD +.endif +OBJ_CD=$(OBJ_BSDCD) + +OBJ_CL=$(OBJ_GLX) +OBJ_ICON= +OBJ_ICON_NEXUIZ= + +LDFLAGS_CL=$(LDFLAGS_BSDCL) +LDFLAGS_SV=$(LDFLAGS_BSDSV) +LDFLAGS_SDL=$(LDFLAGS_BSDSDL) + +SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11) +SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11) +SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11) + +EXE_CL=$(EXE_UNIXCL) +EXE_SV=$(EXE_UNIXSV) +EXE_SDL=$(EXE_UNIXSDL) +EXE_CLNEXUIZ=$(EXE_UNIXCLNEXUIZ) +EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) +EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) + +# libjpeg dependency (set these to "" if you want to use dynamic loading instead) +CFLAGS_LIBJPEG=-DLINK_TO_LIBJPEG +LIB_JPEG=-ljpeg + +.endif + + +##### Sound configuration ##### + +.ifndef DP_SOUND_API +DP_SOUND_API=$(DEFAULT_SNDAPI) +.endif + +# NULL: no sound +.if $(DP_SOUND_API) == "NULL" +OBJ_SOUND=$(OBJ_SND_NULL) +LIB_SOUND=$(LIB_SND_NULL) +.endif + +# OSS: Open Sound System +.if $(DP_SOUND_API) == "OSS" +OBJ_SOUND=$(OBJ_SND_OSS) +LIB_SOUND=$(LIB_SND_OSS) +.endif + +# BSD: BSD / Sun audio API +.if $(DP_SOUND_API) == "BSD" +OBJ_SOUND=$(OBJ_SND_BSD) +LIB_SOUND=$(LIB_SND_BSD) +.endif + + +##### Extra CFLAGS ##### + +CFLAGS_MAKEDEP=-MD +.ifdef DP_FS_BASEDIR +CFLAGS_FS=-DDP_FS_BASEDIR='\"$(DP_FS_BASEDIR)\"' +.else +CFLAGS_FS= +.endif + +CFLAGS_PRELOAD= +.ifdef DP_PRELOAD_DEPENDENCIES +LDFLAGS_CL+=$(LDFLAGS_UNIXCL_PRELOAD) +LDFLAGS_SV+=$(LDFLAGS_UNIXSV_PRELOAD) +LDFLAGS_SDL+=$(LDFLAGS_UNIXSDL_PRELOAD) +CFLAGS_PRELOAD=$(CFLAGS_UNIX_PRELOAD) +.endif + + +##### BSD Make specific definitions ##### + +MAKE:=$(MAKE) -f BSDmakefile + +DO_LD=$(CC) -o $@ $> $(LDFLAGS) + + +##### Definitions shared by all makefiles ##### +.include "makefile.inc" diff --git a/misc/source/darkplaces-src/COPYING b/misc/source/darkplaces-src/COPYING new file mode 100644 index 00000000..d60c31a9 --- /dev/null +++ b/misc/source/darkplaces-src/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/misc/source/darkplaces-src/DPiOS.xcodeproj/project.pbxproj b/misc/source/darkplaces-src/DPiOS.xcodeproj/project.pbxproj new file mode 100644 index 00000000..e73b85ca --- /dev/null +++ b/misc/source/darkplaces-src/DPiOS.xcodeproj/project.pbxproj @@ -0,0 +1,845 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 28FD15000DC6FC520079059D /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD14FF0DC6FC520079059D /* OpenGLES.framework */; }; + 28FD15080DC6FC5B0079059D /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD15070DC6FC5B0079059D /* QuartzCore.framework */; }; + 7463B77812F9CE6B00983F6A /* bih.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C012F9CE6B00983F6A /* bih.c */; }; + 7463B77912F9CE6B00983F6A /* cap_avi.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C312F9CE6B00983F6A /* cap_avi.c */; }; + 7463B77A12F9CE6B00983F6A /* cap_ogg.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C512F9CE6B00983F6A /* cap_ogg.c */; }; + 7463B77B12F9CE6B00983F6A /* cd_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C712F9CE6B00983F6A /* cd_sdl.c */; }; + 7463B77C12F9CE6B00983F6A /* cd_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C812F9CE6B00983F6A /* cd_shared.c */; }; + 7463B77D12F9CE6B00983F6A /* cl_collision.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6CA12F9CE6B00983F6A /* cl_collision.c */; }; + 7463B77E12F9CE6B00983F6A /* cl_demo.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6CC12F9CE6B00983F6A /* cl_demo.c */; }; + 7463B77F12F9CE6B00983F6A /* cl_dyntexture.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6CD12F9CE6B00983F6A /* cl_dyntexture.c */; }; + 7463B78012F9CE6B00983F6A /* cl_gecko.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6CF12F9CE6B00983F6A /* cl_gecko.c */; }; + 7463B78112F9CE6B00983F6A /* cl_input.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D112F9CE6B00983F6A /* cl_input.c */; }; + 7463B78212F9CE6B00983F6A /* cl_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D212F9CE6B00983F6A /* cl_main.c */; }; + 7463B78312F9CE6B00983F6A /* cl_parse.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D312F9CE6B00983F6A /* cl_parse.c */; }; + 7463B78412F9CE6B00983F6A /* cl_particles.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D412F9CE6B00983F6A /* cl_particles.c */; }; + 7463B78512F9CE6B00983F6A /* cl_screen.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D512F9CE6B00983F6A /* cl_screen.c */; }; + 7463B78612F9CE6B00983F6A /* cl_video.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D712F9CE6B00983F6A /* cl_video.c */; }; + 7463B78712F9CE6B00983F6A /* cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6DC12F9CE6B00983F6A /* cmd.c */; }; + 7463B78812F9CE6B00983F6A /* collision.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6DE12F9CE6B00983F6A /* collision.c */; }; + 7463B78912F9CE6B00983F6A /* common.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E012F9CE6B00983F6A /* common.c */; }; + 7463B78A12F9CE6B00983F6A /* console.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E212F9CE6B00983F6A /* console.c */; }; + 7463B78B12F9CE6B00983F6A /* crypto.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E412F9CE6B00983F6A /* crypto.c */; }; + 7463B78C12F9CE6B00983F6A /* csprogs.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E612F9CE6B00983F6A /* csprogs.c */; }; + 7463B78D12F9CE6B00983F6A /* curves.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E812F9CE6B00983F6A /* curves.c */; }; + 7463B78E12F9CE6B00983F6A /* cvar.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6EA12F9CE6B00983F6A /* cvar.c */; }; + 7463B78F12F9CE6B00983F6A /* dpsoftrast.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6EC12F9CE6B00983F6A /* dpsoftrast.c */; }; + 7463B79012F9CE6B00983F6A /* dpvsimpledecode.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6EE12F9CE6B00983F6A /* dpvsimpledecode.c */; }; + 7463B79112F9CE6B00983F6A /* filematch.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F112F9CE6B00983F6A /* filematch.c */; }; + 7463B79212F9CE6B00983F6A /* fractalnoise.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F212F9CE6B00983F6A /* fractalnoise.c */; }; + 7463B79312F9CE6B00983F6A /* fs.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F312F9CE6B00983F6A /* fs.c */; }; + 7463B79412F9CE6B00983F6A /* ft2.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F712F9CE6B00983F6A /* ft2.c */; }; + 7463B79512F9CE6B00983F6A /* gl_backend.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F912F9CE6B00983F6A /* gl_backend.c */; }; + 7463B79612F9CE6B00983F6A /* gl_draw.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6FB12F9CE6B00983F6A /* gl_draw.c */; }; + 7463B79712F9CE6B00983F6A /* gl_rmain.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6FC12F9CE6B00983F6A /* gl_rmain.c */; }; + 7463B79812F9CE6B00983F6A /* gl_rsurf.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6FD12F9CE6B00983F6A /* gl_rsurf.c */; }; + 7463B79912F9CE6B00983F6A /* gl_textures.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6FE12F9CE6B00983F6A /* gl_textures.c */; }; + 7463B79A12F9CE6B00983F6A /* hmac.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70012F9CE6B00983F6A /* hmac.c */; }; + 7463B79B12F9CE6B00983F6A /* host_cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70212F9CE6B00983F6A /* host_cmd.c */; }; + 7463B79C12F9CE6B00983F6A /* host.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70312F9CE6B00983F6A /* host.c */; }; + 7463B79D12F9CE6B00983F6A /* image_png.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70412F9CE6B00983F6A /* image_png.c */; }; + 7463B79E12F9CE6B00983F6A /* image.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70612F9CE6B00983F6A /* image.c */; }; + 7463B79F12F9CE6B00983F6A /* jpeg.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70A12F9CE6B00983F6A /* jpeg.c */; }; + 7463B7A012F9CE6B00983F6A /* keys.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70C12F9CE6B00983F6A /* keys.c */; }; + 7463B7A112F9CE6B00983F6A /* lhnet.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70F12F9CE6B00983F6A /* lhnet.c */; }; + 7463B7A212F9CE6B00983F6A /* libcurl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71112F9CE6B00983F6A /* libcurl.c */; }; + 7463B7A312F9CE6B00983F6A /* mathlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71312F9CE6B00983F6A /* mathlib.c */; }; + 7463B7A412F9CE6B00983F6A /* matrixlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71512F9CE6B00983F6A /* matrixlib.c */; }; + 7463B7A512F9CE6B00983F6A /* mdfour.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71712F9CE6B00983F6A /* mdfour.c */; }; + 7463B7A612F9CE6B00983F6A /* menu.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71912F9CE6B00983F6A /* menu.c */; }; + 7463B7A712F9CE6B00983F6A /* meshqueue.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71B12F9CE6B00983F6A /* meshqueue.c */; }; + 7463B7A812F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71D12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c */; }; + 7463B7A912F9CE6B00983F6A /* model_alias.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71F12F9CE6B00983F6A /* model_alias.c */; }; + 7463B7AA12F9CE6B00983F6A /* model_brush.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72112F9CE6B00983F6A /* model_brush.c */; }; + 7463B7AB12F9CE6B00983F6A /* model_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72612F9CE6B00983F6A /* model_shared.c */; }; + 7463B7AC12F9CE6B00983F6A /* model_sprite.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72812F9CE6B00983F6A /* model_sprite.c */; }; + 7463B7AD12F9CE6B00983F6A /* mvm_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72D12F9CE6B00983F6A /* mvm_cmds.c */; }; + 7463B7AE12F9CE6B00983F6A /* netconn.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72E12F9CE6B00983F6A /* netconn.c */; }; + 7463B7AF12F9CE6B00983F6A /* palette.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73012F9CE6B00983F6A /* palette.c */; }; + 7463B7B012F9CE6B00983F6A /* polygon.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73212F9CE6B00983F6A /* polygon.c */; }; + 7463B7B112F9CE6B00983F6A /* portals.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73412F9CE6B00983F6A /* portals.c */; }; + 7463B7B212F9CE6B00983F6A /* protocol.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73A12F9CE6B00983F6A /* protocol.c */; }; + 7463B7B312F9CE6B00983F6A /* prvm_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73C12F9CE6B00983F6A /* prvm_cmds.c */; }; + 7463B7B412F9CE6B00983F6A /* prvm_edict.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73E12F9CE6B00983F6A /* prvm_edict.c */; }; + 7463B7B512F9CE6B00983F6A /* prvm_exec.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73F12F9CE6B00983F6A /* prvm_exec.c */; }; + 7463B7B612F9CE6B00983F6A /* r_explosion.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74312F9CE6B00983F6A /* r_explosion.c */; }; + 7463B7B712F9CE6B00983F6A /* r_lightning.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74412F9CE6B00983F6A /* r_lightning.c */; }; + 7463B7B812F9CE6B00983F6A /* r_modules.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74512F9CE6B00983F6A /* r_modules.c */; }; + 7463B7B912F9CE6B00983F6A /* r_shadow.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74712F9CE6B00983F6A /* r_shadow.c */; }; + 7463B7BA12F9CE6B00983F6A /* r_sky.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74912F9CE6B00983F6A /* r_sky.c */; }; + 7463B7BB12F9CE6B00983F6A /* r_sprites.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74A12F9CE6B00983F6A /* r_sprites.c */; }; + 7463B7BC12F9CE6B00983F6A /* sbar.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74E12F9CE6B00983F6A /* sbar.c */; }; + 7463B7BD12F9CE6B00983F6A /* snd_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75212F9CE6B00983F6A /* snd_main.c */; }; + 7463B7BE12F9CE6B00983F6A /* snd_mem.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75412F9CE6B00983F6A /* snd_mem.c */; }; + 7463B7BF12F9CE6B00983F6A /* snd_mix.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75512F9CE6B00983F6A /* snd_mix.c */; }; + 7463B7C012F9CE6B00983F6A /* snd_modplug.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75612F9CE6B00983F6A /* snd_modplug.c */; }; + 7463B7C112F9CE6B00983F6A /* snd_ogg.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75812F9CE6B00983F6A /* snd_ogg.c */; }; + 7463B7C212F9CE6B00983F6A /* snd_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75A12F9CE6B00983F6A /* snd_sdl.c */; }; + 7463B7C312F9CE6B00983F6A /* snd_wav.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75B12F9CE6B00983F6A /* snd_wav.c */; }; + 7463B7C412F9CE6B00983F6A /* sv_demo.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75F12F9CE6B00983F6A /* sv_demo.c */; }; + 7463B7C512F9CE6B00983F6A /* sv_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76112F9CE6B00983F6A /* sv_main.c */; }; + 7463B7C612F9CE6B00983F6A /* sv_move.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76212F9CE6B00983F6A /* sv_move.c */; }; + 7463B7C712F9CE6B00983F6A /* sv_phys.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76312F9CE6B00983F6A /* sv_phys.c */; }; + 7463B7C812F9CE6B00983F6A /* sv_user.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76412F9CE6B00983F6A /* sv_user.c */; }; + 7463B7C912F9CE6B00983F6A /* svbsp.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76512F9CE6B00983F6A /* svbsp.c */; }; + 7463B7CA12F9CE6B00983F6A /* svvm_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76712F9CE6B00983F6A /* svvm_cmds.c */; }; + 7463B7CB12F9CE6B00983F6A /* sys_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76812F9CE6B00983F6A /* sys_sdl.c */; }; + 7463B7CC12F9CE6B00983F6A /* sys_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76912F9CE6B00983F6A /* sys_shared.c */; }; + 7463B7CD12F9CE6B00983F6A /* utf8lib.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76C12F9CE6B00983F6A /* utf8lib.c */; }; + 7463B7CE12F9CE6B00983F6A /* vid_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76E12F9CE6B00983F6A /* vid_sdl.c */; }; + 7463B7CF12F9CE6B00983F6A /* vid_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76F12F9CE6B00983F6A /* vid_shared.c */; }; + 7463B7D012F9CE6B00983F6A /* view.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B77112F9CE6B00983F6A /* view.c */; }; + 7463B7D112F9CE6B00983F6A /* wad.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B77212F9CE6B00983F6A /* wad.c */; }; + 7463B7D212F9CE6B00983F6A /* world.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B77412F9CE6B00983F6A /* world.c */; }; + 7463B7D312F9CE6B00983F6A /* zone.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B77612F9CE6B00983F6A /* zone.c */; }; + 7463B7D912F9CF8F00983F6A /* darkplaces64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = 7463B7D812F9CF8F00983F6A /* darkplaces64x64.png */; }; + 7463B7EA12F9D11E00983F6A /* mod_skeletal_animatevertices_sse.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B7E812F9D11E00983F6A /* mod_skeletal_animatevertices_sse.c */; }; + 7463B7EF12F9D17D00983F6A /* builddate.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B7ED12F9D17D00983F6A /* builddate.c */; }; + 7463B7F012F9D17D00983F6A /* clvm_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B7EE12F9D17D00983F6A /* clvm_cmds.c */; }; + 7487D481130102AA00AEE909 /* thread_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7487D47F130102AA00AEE909 /* thread_sdl.c */; }; + FD779ED20E26B9B000F39101 /* libSDLSimulator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD779ED00E26B9B000F39101 /* libSDLSimulator.a */; }; + FD779ED30E26B9B000F39101 /* libSDLiPhoneOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD779ED10E26B9B000F39101 /* libSDLiPhoneOS.a */; }; + FD779EDE0E26BA1200F39101 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD779EDD0E26BA1200F39101 /* CoreAudio.framework */; }; + FD77A0850E26BDB800F39101 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD77A0840E26BDB800F39101 /* AudioToolbox.framework */; }; + FDB8BFC60E5A0F6A00980157 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDB8BFC50E5A0F6A00980157 /* CoreGraphics.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D6058910D05DD3D006BFB54 /* DPiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DPiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 28FD14FF0DC6FC520079059D /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; + 28FD15070DC6FC5B0079059D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 7463B6C012F9CE6B00983F6A /* bih.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bih.c; sourceTree = ""; }; + 7463B6C112F9CE6B00983F6A /* bih.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bih.h; sourceTree = ""; }; + 7463B6C212F9CE6B00983F6A /* bspfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bspfile.h; sourceTree = ""; }; + 7463B6C312F9CE6B00983F6A /* cap_avi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cap_avi.c; sourceTree = ""; }; + 7463B6C412F9CE6B00983F6A /* cap_avi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cap_avi.h; sourceTree = ""; }; + 7463B6C512F9CE6B00983F6A /* cap_ogg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cap_ogg.c; sourceTree = ""; }; + 7463B6C612F9CE6B00983F6A /* cap_ogg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cap_ogg.h; sourceTree = ""; }; + 7463B6C712F9CE6B00983F6A /* cd_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cd_sdl.c; sourceTree = ""; }; + 7463B6C812F9CE6B00983F6A /* cd_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cd_shared.c; sourceTree = ""; }; + 7463B6C912F9CE6B00983F6A /* cdaudio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cdaudio.h; sourceTree = ""; }; + 7463B6CA12F9CE6B00983F6A /* cl_collision.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_collision.c; sourceTree = ""; }; + 7463B6CB12F9CE6B00983F6A /* cl_collision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_collision.h; sourceTree = ""; }; + 7463B6CC12F9CE6B00983F6A /* cl_demo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_demo.c; sourceTree = ""; }; + 7463B6CD12F9CE6B00983F6A /* cl_dyntexture.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_dyntexture.c; sourceTree = ""; }; + 7463B6CE12F9CE6B00983F6A /* cl_dyntexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_dyntexture.h; sourceTree = ""; }; + 7463B6CF12F9CE6B00983F6A /* cl_gecko.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_gecko.c; sourceTree = ""; }; + 7463B6D012F9CE6B00983F6A /* cl_gecko.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_gecko.h; sourceTree = ""; }; + 7463B6D112F9CE6B00983F6A /* cl_input.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_input.c; sourceTree = ""; }; + 7463B6D212F9CE6B00983F6A /* cl_main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_main.c; sourceTree = ""; }; + 7463B6D312F9CE6B00983F6A /* cl_parse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_parse.c; sourceTree = ""; }; + 7463B6D412F9CE6B00983F6A /* cl_particles.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_particles.c; sourceTree = ""; }; + 7463B6D512F9CE6B00983F6A /* cl_screen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_screen.c; sourceTree = ""; }; + 7463B6D612F9CE6B00983F6A /* cl_screen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_screen.h; sourceTree = ""; }; + 7463B6D712F9CE6B00983F6A /* cl_video.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_video.c; sourceTree = ""; }; + 7463B6D812F9CE6B00983F6A /* cl_video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_video.h; sourceTree = ""; }; + 7463B6D912F9CE6B00983F6A /* client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = client.h; sourceTree = ""; }; + 7463B6DA12F9CE6B00983F6A /* clprogdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clprogdefs.h; sourceTree = ""; }; + 7463B6DB12F9CE6B00983F6A /* clvm_cmds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clvm_cmds.h; sourceTree = ""; }; + 7463B6DC12F9CE6B00983F6A /* cmd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cmd.c; sourceTree = ""; }; + 7463B6DD12F9CE6B00983F6A /* cmd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cmd.h; sourceTree = ""; }; + 7463B6DE12F9CE6B00983F6A /* collision.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = collision.c; sourceTree = ""; }; + 7463B6DF12F9CE6B00983F6A /* collision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = collision.h; sourceTree = ""; }; + 7463B6E012F9CE6B00983F6A /* common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = common.c; sourceTree = ""; }; + 7463B6E112F9CE6B00983F6A /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; }; + 7463B6E212F9CE6B00983F6A /* console.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = console.c; sourceTree = ""; }; + 7463B6E312F9CE6B00983F6A /* console.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = console.h; sourceTree = ""; }; + 7463B6E412F9CE6B00983F6A /* crypto.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = crypto.c; sourceTree = ""; }; + 7463B6E512F9CE6B00983F6A /* crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto.h; sourceTree = ""; }; + 7463B6E612F9CE6B00983F6A /* csprogs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = csprogs.c; sourceTree = ""; }; + 7463B6E712F9CE6B00983F6A /* csprogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = csprogs.h; sourceTree = ""; }; + 7463B6E812F9CE6B00983F6A /* curves.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = curves.c; sourceTree = ""; }; + 7463B6E912F9CE6B00983F6A /* curves.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = curves.h; sourceTree = ""; }; + 7463B6EA12F9CE6B00983F6A /* cvar.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cvar.c; sourceTree = ""; }; + 7463B6EB12F9CE6B00983F6A /* cvar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cvar.h; sourceTree = ""; }; + 7463B6EC12F9CE6B00983F6A /* dpsoftrast.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dpsoftrast.c; sourceTree = ""; }; + 7463B6ED12F9CE6B00983F6A /* dpsoftrast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dpsoftrast.h; sourceTree = ""; }; + 7463B6EE12F9CE6B00983F6A /* dpvsimpledecode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dpvsimpledecode.c; sourceTree = ""; }; + 7463B6EF12F9CE6B00983F6A /* dpvsimpledecode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dpvsimpledecode.h; sourceTree = ""; }; + 7463B6F012F9CE6B00983F6A /* draw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = draw.h; sourceTree = ""; }; + 7463B6F112F9CE6B00983F6A /* filematch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = filematch.c; sourceTree = ""; }; + 7463B6F212F9CE6B00983F6A /* fractalnoise.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fractalnoise.c; sourceTree = ""; }; + 7463B6F312F9CE6B00983F6A /* fs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fs.c; sourceTree = ""; }; + 7463B6F412F9CE6B00983F6A /* fs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fs.h; sourceTree = ""; }; + 7463B6F512F9CE6B00983F6A /* ft2_defs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ft2_defs.h; sourceTree = ""; }; + 7463B6F612F9CE6B00983F6A /* ft2_fontdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ft2_fontdefs.h; sourceTree = ""; }; + 7463B6F712F9CE6B00983F6A /* ft2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ft2.c; sourceTree = ""; }; + 7463B6F812F9CE6B00983F6A /* ft2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ft2.h; sourceTree = ""; }; + 7463B6F912F9CE6B00983F6A /* gl_backend.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_backend.c; sourceTree = ""; }; + 7463B6FA12F9CE6B00983F6A /* gl_backend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gl_backend.h; sourceTree = ""; }; + 7463B6FB12F9CE6B00983F6A /* gl_draw.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_draw.c; sourceTree = ""; }; + 7463B6FC12F9CE6B00983F6A /* gl_rmain.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_rmain.c; sourceTree = ""; }; + 7463B6FD12F9CE6B00983F6A /* gl_rsurf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_rsurf.c; sourceTree = ""; }; + 7463B6FE12F9CE6B00983F6A /* gl_textures.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_textures.c; sourceTree = ""; }; + 7463B6FF12F9CE6B00983F6A /* glquake.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = glquake.h; sourceTree = ""; }; + 7463B70012F9CE6B00983F6A /* hmac.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hmac.c; sourceTree = ""; }; + 7463B70112F9CE6B00983F6A /* hmac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hmac.h; sourceTree = ""; }; + 7463B70212F9CE6B00983F6A /* host_cmd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = host_cmd.c; sourceTree = ""; }; + 7463B70312F9CE6B00983F6A /* host.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = host.c; sourceTree = ""; }; + 7463B70412F9CE6B00983F6A /* image_png.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = image_png.c; sourceTree = ""; }; + 7463B70512F9CE6B00983F6A /* image_png.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = image_png.h; sourceTree = ""; }; + 7463B70612F9CE6B00983F6A /* image.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = image.c; sourceTree = ""; }; + 7463B70712F9CE6B00983F6A /* image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = image.h; sourceTree = ""; }; + 7463B70812F9CE6B00983F6A /* input.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input.h; sourceTree = ""; }; + 7463B70912F9CE6B00983F6A /* intoverflow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = intoverflow.h; sourceTree = ""; }; + 7463B70A12F9CE6B00983F6A /* jpeg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = jpeg.c; sourceTree = ""; }; + 7463B70B12F9CE6B00983F6A /* jpeg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jpeg.h; sourceTree = ""; }; + 7463B70C12F9CE6B00983F6A /* keys.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = keys.c; sourceTree = ""; }; + 7463B70D12F9CE6B00983F6A /* keys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = keys.h; sourceTree = ""; }; + 7463B70E12F9CE6B00983F6A /* lhfont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lhfont.h; sourceTree = ""; }; + 7463B70F12F9CE6B00983F6A /* lhnet.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lhnet.c; sourceTree = ""; }; + 7463B71012F9CE6B00983F6A /* lhnet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lhnet.h; sourceTree = ""; }; + 7463B71112F9CE6B00983F6A /* libcurl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = libcurl.c; sourceTree = ""; }; + 7463B71212F9CE6B00983F6A /* libcurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libcurl.h; sourceTree = ""; }; + 7463B71312F9CE6B00983F6A /* mathlib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mathlib.c; sourceTree = ""; }; + 7463B71412F9CE6B00983F6A /* mathlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mathlib.h; sourceTree = ""; }; + 7463B71512F9CE6B00983F6A /* matrixlib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = matrixlib.c; sourceTree = ""; }; + 7463B71612F9CE6B00983F6A /* matrixlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = matrixlib.h; sourceTree = ""; }; + 7463B71712F9CE6B00983F6A /* mdfour.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdfour.c; sourceTree = ""; }; + 7463B71812F9CE6B00983F6A /* mdfour.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdfour.h; sourceTree = ""; }; + 7463B71912F9CE6B00983F6A /* menu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = menu.c; sourceTree = ""; }; + 7463B71A12F9CE6B00983F6A /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = ""; }; + 7463B71B12F9CE6B00983F6A /* meshqueue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = meshqueue.c; sourceTree = ""; }; + 7463B71C12F9CE6B00983F6A /* meshqueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = meshqueue.h; sourceTree = ""; }; + 7463B71D12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mod_skeletal_animatevertices_generic.c; sourceTree = ""; }; + 7463B71E12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mod_skeletal_animatevertices_generic.h; sourceTree = ""; }; + 7463B71F12F9CE6B00983F6A /* model_alias.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = model_alias.c; sourceTree = ""; }; + 7463B72012F9CE6B00983F6A /* model_alias.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_alias.h; sourceTree = ""; }; + 7463B72112F9CE6B00983F6A /* model_brush.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = model_brush.c; sourceTree = ""; }; + 7463B72212F9CE6B00983F6A /* model_brush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_brush.h; sourceTree = ""; }; + 7463B72312F9CE6B00983F6A /* model_dpmodel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_dpmodel.h; sourceTree = ""; }; + 7463B72412F9CE6B00983F6A /* model_iqm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_iqm.h; sourceTree = ""; }; + 7463B72512F9CE6B00983F6A /* model_psk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_psk.h; sourceTree = ""; }; + 7463B72612F9CE6B00983F6A /* model_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = model_shared.c; sourceTree = ""; }; + 7463B72712F9CE6B00983F6A /* model_shared.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_shared.h; sourceTree = ""; }; + 7463B72812F9CE6B00983F6A /* model_sprite.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = model_sprite.c; sourceTree = ""; }; + 7463B72912F9CE6B00983F6A /* model_sprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_sprite.h; sourceTree = ""; }; + 7463B72A12F9CE6B00983F6A /* model_zymotic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_zymotic.h; sourceTree = ""; }; + 7463B72B12F9CE6B00983F6A /* modelgen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modelgen.h; sourceTree = ""; }; + 7463B72C12F9CE6B00983F6A /* mprogdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mprogdefs.h; sourceTree = ""; }; + 7463B72D12F9CE6B00983F6A /* mvm_cmds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mvm_cmds.c; sourceTree = ""; }; + 7463B72E12F9CE6B00983F6A /* netconn.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = netconn.c; sourceTree = ""; }; + 7463B72F12F9CE6B00983F6A /* netconn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = netconn.h; sourceTree = ""; }; + 7463B73012F9CE6B00983F6A /* palette.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = palette.c; sourceTree = ""; }; + 7463B73112F9CE6B00983F6A /* palette.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = palette.h; sourceTree = ""; }; + 7463B73212F9CE6B00983F6A /* polygon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = polygon.c; sourceTree = ""; }; + 7463B73312F9CE6B00983F6A /* polygon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = polygon.h; sourceTree = ""; }; + 7463B73412F9CE6B00983F6A /* portals.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = portals.c; sourceTree = ""; }; + 7463B73512F9CE6B00983F6A /* portals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = portals.h; sourceTree = ""; }; + 7463B73612F9CE6B00983F6A /* pr_comp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pr_comp.h; sourceTree = ""; }; + 7463B73712F9CE6B00983F6A /* progdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = progdefs.h; sourceTree = ""; }; + 7463B73812F9CE6B00983F6A /* progs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = progs.h; sourceTree = ""; }; + 7463B73912F9CE6B00983F6A /* progsvm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = progsvm.h; sourceTree = ""; }; + 7463B73A12F9CE6B00983F6A /* protocol.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = protocol.c; sourceTree = ""; }; + 7463B73B12F9CE6B00983F6A /* protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = protocol.h; sourceTree = ""; }; + 7463B73C12F9CE6B00983F6A /* prvm_cmds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = prvm_cmds.c; sourceTree = ""; }; + 7463B73D12F9CE6B00983F6A /* prvm_cmds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = prvm_cmds.h; sourceTree = ""; }; + 7463B73E12F9CE6B00983F6A /* prvm_edict.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = prvm_edict.c; sourceTree = ""; }; + 7463B73F12F9CE6B00983F6A /* prvm_exec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = prvm_exec.c; sourceTree = ""; }; + 7463B74012F9CE6B00983F6A /* prvm_execprogram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = prvm_execprogram.h; sourceTree = ""; }; + 7463B74112F9CE6B00983F6A /* qtypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = qtypes.h; sourceTree = ""; }; + 7463B74212F9CE6B00983F6A /* quakedef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = quakedef.h; sourceTree = ""; }; + 7463B74312F9CE6B00983F6A /* r_explosion.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_explosion.c; sourceTree = ""; }; + 7463B74412F9CE6B00983F6A /* r_lightning.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_lightning.c; sourceTree = ""; }; + 7463B74512F9CE6B00983F6A /* r_modules.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_modules.c; sourceTree = ""; }; + 7463B74612F9CE6B00983F6A /* r_modules.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r_modules.h; sourceTree = ""; }; + 7463B74712F9CE6B00983F6A /* r_shadow.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_shadow.c; sourceTree = ""; }; + 7463B74812F9CE6B00983F6A /* r_shadow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r_shadow.h; sourceTree = ""; }; + 7463B74912F9CE6B00983F6A /* r_sky.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_sky.c; sourceTree = ""; }; + 7463B74A12F9CE6B00983F6A /* r_sprites.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_sprites.c; sourceTree = ""; }; + 7463B74B12F9CE6B00983F6A /* r_textures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r_textures.h; sourceTree = ""; }; + 7463B74C12F9CE6B00983F6A /* render.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = render.h; sourceTree = ""; }; + 7463B74D12F9CE6B00983F6A /* resource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = resource.h; sourceTree = ""; }; + 7463B74E12F9CE6B00983F6A /* sbar.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sbar.c; sourceTree = ""; }; + 7463B74F12F9CE6B00983F6A /* sbar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sbar.h; sourceTree = ""; }; + 7463B75012F9CE6B00983F6A /* screen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = screen.h; sourceTree = ""; }; + 7463B75112F9CE6B00983F6A /* server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = server.h; sourceTree = ""; }; + 7463B75212F9CE6B00983F6A /* snd_main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_main.c; sourceTree = ""; }; + 7463B75312F9CE6B00983F6A /* snd_main.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = snd_main.h; sourceTree = ""; }; + 7463B75412F9CE6B00983F6A /* snd_mem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_mem.c; sourceTree = ""; }; + 7463B75512F9CE6B00983F6A /* snd_mix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_mix.c; sourceTree = ""; }; + 7463B75612F9CE6B00983F6A /* snd_modplug.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_modplug.c; sourceTree = ""; }; + 7463B75712F9CE6B00983F6A /* snd_modplug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = snd_modplug.h; sourceTree = ""; }; + 7463B75812F9CE6B00983F6A /* snd_ogg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_ogg.c; sourceTree = ""; }; + 7463B75912F9CE6B00983F6A /* snd_ogg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = snd_ogg.h; sourceTree = ""; }; + 7463B75A12F9CE6B00983F6A /* snd_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_sdl.c; sourceTree = ""; }; + 7463B75B12F9CE6B00983F6A /* snd_wav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_wav.c; sourceTree = ""; }; + 7463B75C12F9CE6B00983F6A /* snd_wav.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = snd_wav.h; sourceTree = ""; }; + 7463B75D12F9CE6B00983F6A /* sound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sound.h; sourceTree = ""; }; + 7463B75E12F9CE6B00983F6A /* spritegn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = spritegn.h; sourceTree = ""; }; + 7463B75F12F9CE6B00983F6A /* sv_demo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_demo.c; sourceTree = ""; }; + 7463B76012F9CE6B00983F6A /* sv_demo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sv_demo.h; sourceTree = ""; }; + 7463B76112F9CE6B00983F6A /* sv_main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_main.c; sourceTree = ""; }; + 7463B76212F9CE6B00983F6A /* sv_move.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_move.c; sourceTree = ""; }; + 7463B76312F9CE6B00983F6A /* sv_phys.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_phys.c; sourceTree = ""; }; + 7463B76412F9CE6B00983F6A /* sv_user.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_user.c; sourceTree = ""; }; + 7463B76512F9CE6B00983F6A /* svbsp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = svbsp.c; sourceTree = ""; }; + 7463B76612F9CE6B00983F6A /* svbsp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = svbsp.h; sourceTree = ""; }; + 7463B76712F9CE6B00983F6A /* svvm_cmds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = svvm_cmds.c; sourceTree = ""; }; + 7463B76812F9CE6B00983F6A /* sys_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sys_sdl.c; sourceTree = ""; }; + 7463B76912F9CE6B00983F6A /* sys_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sys_shared.c; sourceTree = ""; }; + 7463B76A12F9CE6B00983F6A /* sys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sys.h; sourceTree = ""; }; + 7463B76B12F9CE6B00983F6A /* timing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timing.h; sourceTree = ""; }; + 7463B76C12F9CE6B00983F6A /* utf8lib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = utf8lib.c; sourceTree = ""; }; + 7463B76D12F9CE6B00983F6A /* utf8lib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utf8lib.h; sourceTree = ""; }; + 7463B76E12F9CE6B00983F6A /* vid_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vid_sdl.c; sourceTree = ""; }; + 7463B76F12F9CE6B00983F6A /* vid_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vid_shared.c; sourceTree = ""; }; + 7463B77012F9CE6B00983F6A /* vid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vid.h; sourceTree = ""; }; + 7463B77112F9CE6B00983F6A /* view.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = view.c; sourceTree = ""; }; + 7463B77212F9CE6B00983F6A /* wad.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = wad.c; sourceTree = ""; }; + 7463B77312F9CE6B00983F6A /* wad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wad.h; sourceTree = ""; }; + 7463B77412F9CE6B00983F6A /* world.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = world.c; sourceTree = ""; }; + 7463B77512F9CE6B00983F6A /* world.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = world.h; sourceTree = ""; }; + 7463B77612F9CE6B00983F6A /* zone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zone.c; sourceTree = ""; }; + 7463B77712F9CE6B00983F6A /* zone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zone.h; sourceTree = ""; }; + 7463B7D812F9CF8F00983F6A /* darkplaces64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = darkplaces64x64.png; sourceTree = ""; }; + 7463B7E812F9D11E00983F6A /* mod_skeletal_animatevertices_sse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mod_skeletal_animatevertices_sse.c; sourceTree = ""; }; + 7463B7E912F9D11E00983F6A /* mod_skeletal_animatevertices_sse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mod_skeletal_animatevertices_sse.h; sourceTree = ""; }; + 7463B7ED12F9D17D00983F6A /* builddate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = builddate.c; sourceTree = ""; }; + 7463B7EE12F9D17D00983F6A /* clvm_cmds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = clvm_cmds.c; sourceTree = ""; }; + 7487D47F130102AA00AEE909 /* thread_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = thread_sdl.c; sourceTree = ""; }; + 7487D480130102AA00AEE909 /* thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread.h; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FD779ED00E26B9B000F39101 /* libSDLSimulator.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libSDLSimulator.a; path = lib/libSDLSimulator.a; sourceTree = ""; }; + FD779ED10E26B9B000F39101 /* libSDLiPhoneOS.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libSDLiPhoneOS.a; path = lib/libSDLiPhoneOS.a; sourceTree = ""; }; + FD779EDD0E26BA1200F39101 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; + FD77A0840E26BDB800F39101 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + FDB8BFC50E5A0F6A00980157 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FD779ED20E26B9B000F39101 /* libSDLSimulator.a in Frameworks */, + FD779ED30E26B9B000F39101 /* libSDLiPhoneOS.a in Frameworks */, + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + 28FD15000DC6FC520079059D /* OpenGLES.framework in Frameworks */, + 28FD15080DC6FC5B0079059D /* QuartzCore.framework in Frameworks */, + FD779EDE0E26BA1200F39101 /* CoreAudio.framework in Frameworks */, + FD77A0850E26BDB800F39101 /* AudioToolbox.framework in Frameworks */, + FDB8BFC60E5A0F6A00980157 /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* DPiOS.app */, + ); + name = Products; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 29B97315FDCFA39411CA2CEA /* Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + FD779EC50E26B99E00F39101 /* Libraries */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Sources */ = { + isa = PBXGroup; + children = ( + 7487D47F130102AA00AEE909 /* thread_sdl.c */, + 7487D480130102AA00AEE909 /* thread.h */, + 7463B7ED12F9D17D00983F6A /* builddate.c */, + 7463B7EE12F9D17D00983F6A /* clvm_cmds.c */, + 7463B7E812F9D11E00983F6A /* mod_skeletal_animatevertices_sse.c */, + 7463B7E912F9D11E00983F6A /* mod_skeletal_animatevertices_sse.h */, + 7463B6C012F9CE6B00983F6A /* bih.c */, + 7463B6C112F9CE6B00983F6A /* bih.h */, + 7463B6C212F9CE6B00983F6A /* bspfile.h */, + 7463B6C312F9CE6B00983F6A /* cap_avi.c */, + 7463B6C412F9CE6B00983F6A /* cap_avi.h */, + 7463B6C512F9CE6B00983F6A /* cap_ogg.c */, + 7463B6C612F9CE6B00983F6A /* cap_ogg.h */, + 7463B6C712F9CE6B00983F6A /* cd_sdl.c */, + 7463B6C812F9CE6B00983F6A /* cd_shared.c */, + 7463B6C912F9CE6B00983F6A /* cdaudio.h */, + 7463B6CA12F9CE6B00983F6A /* cl_collision.c */, + 7463B6CB12F9CE6B00983F6A /* cl_collision.h */, + 7463B6CC12F9CE6B00983F6A /* cl_demo.c */, + 7463B6CD12F9CE6B00983F6A /* cl_dyntexture.c */, + 7463B6CE12F9CE6B00983F6A /* cl_dyntexture.h */, + 7463B6CF12F9CE6B00983F6A /* cl_gecko.c */, + 7463B6D012F9CE6B00983F6A /* cl_gecko.h */, + 7463B6D112F9CE6B00983F6A /* cl_input.c */, + 7463B6D212F9CE6B00983F6A /* cl_main.c */, + 7463B6D312F9CE6B00983F6A /* cl_parse.c */, + 7463B6D412F9CE6B00983F6A /* cl_particles.c */, + 7463B6D512F9CE6B00983F6A /* cl_screen.c */, + 7463B6D612F9CE6B00983F6A /* cl_screen.h */, + 7463B6D712F9CE6B00983F6A /* cl_video.c */, + 7463B6D812F9CE6B00983F6A /* cl_video.h */, + 7463B6D912F9CE6B00983F6A /* client.h */, + 7463B6DA12F9CE6B00983F6A /* clprogdefs.h */, + 7463B6DB12F9CE6B00983F6A /* clvm_cmds.h */, + 7463B6DC12F9CE6B00983F6A /* cmd.c */, + 7463B6DD12F9CE6B00983F6A /* cmd.h */, + 7463B6DE12F9CE6B00983F6A /* collision.c */, + 7463B6DF12F9CE6B00983F6A /* collision.h */, + 7463B6E012F9CE6B00983F6A /* common.c */, + 7463B6E112F9CE6B00983F6A /* common.h */, + 7463B6E212F9CE6B00983F6A /* console.c */, + 7463B6E312F9CE6B00983F6A /* console.h */, + 7463B6E412F9CE6B00983F6A /* crypto.c */, + 7463B6E512F9CE6B00983F6A /* crypto.h */, + 7463B6E612F9CE6B00983F6A /* csprogs.c */, + 7463B6E712F9CE6B00983F6A /* csprogs.h */, + 7463B6E812F9CE6B00983F6A /* curves.c */, + 7463B6E912F9CE6B00983F6A /* curves.h */, + 7463B6EA12F9CE6B00983F6A /* cvar.c */, + 7463B6EB12F9CE6B00983F6A /* cvar.h */, + 7463B6EC12F9CE6B00983F6A /* dpsoftrast.c */, + 7463B6ED12F9CE6B00983F6A /* dpsoftrast.h */, + 7463B6EE12F9CE6B00983F6A /* dpvsimpledecode.c */, + 7463B6EF12F9CE6B00983F6A /* dpvsimpledecode.h */, + 7463B6F012F9CE6B00983F6A /* draw.h */, + 7463B6F112F9CE6B00983F6A /* filematch.c */, + 7463B6F212F9CE6B00983F6A /* fractalnoise.c */, + 7463B6F312F9CE6B00983F6A /* fs.c */, + 7463B6F412F9CE6B00983F6A /* fs.h */, + 7463B6F512F9CE6B00983F6A /* ft2_defs.h */, + 7463B6F612F9CE6B00983F6A /* ft2_fontdefs.h */, + 7463B6F712F9CE6B00983F6A /* ft2.c */, + 7463B6F812F9CE6B00983F6A /* ft2.h */, + 7463B6F912F9CE6B00983F6A /* gl_backend.c */, + 7463B6FA12F9CE6B00983F6A /* gl_backend.h */, + 7463B6FB12F9CE6B00983F6A /* gl_draw.c */, + 7463B6FC12F9CE6B00983F6A /* gl_rmain.c */, + 7463B6FD12F9CE6B00983F6A /* gl_rsurf.c */, + 7463B6FE12F9CE6B00983F6A /* gl_textures.c */, + 7463B6FF12F9CE6B00983F6A /* glquake.h */, + 7463B70012F9CE6B00983F6A /* hmac.c */, + 7463B70112F9CE6B00983F6A /* hmac.h */, + 7463B70212F9CE6B00983F6A /* host_cmd.c */, + 7463B70312F9CE6B00983F6A /* host.c */, + 7463B70412F9CE6B00983F6A /* image_png.c */, + 7463B70512F9CE6B00983F6A /* image_png.h */, + 7463B70612F9CE6B00983F6A /* image.c */, + 7463B70712F9CE6B00983F6A /* image.h */, + 7463B70812F9CE6B00983F6A /* input.h */, + 7463B70912F9CE6B00983F6A /* intoverflow.h */, + 7463B70A12F9CE6B00983F6A /* jpeg.c */, + 7463B70B12F9CE6B00983F6A /* jpeg.h */, + 7463B70C12F9CE6B00983F6A /* keys.c */, + 7463B70D12F9CE6B00983F6A /* keys.h */, + 7463B70E12F9CE6B00983F6A /* lhfont.h */, + 7463B70F12F9CE6B00983F6A /* lhnet.c */, + 7463B71012F9CE6B00983F6A /* lhnet.h */, + 7463B71112F9CE6B00983F6A /* libcurl.c */, + 7463B71212F9CE6B00983F6A /* libcurl.h */, + 7463B71312F9CE6B00983F6A /* mathlib.c */, + 7463B71412F9CE6B00983F6A /* mathlib.h */, + 7463B71512F9CE6B00983F6A /* matrixlib.c */, + 7463B71612F9CE6B00983F6A /* matrixlib.h */, + 7463B71712F9CE6B00983F6A /* mdfour.c */, + 7463B71812F9CE6B00983F6A /* mdfour.h */, + 7463B71912F9CE6B00983F6A /* menu.c */, + 7463B71A12F9CE6B00983F6A /* menu.h */, + 7463B71B12F9CE6B00983F6A /* meshqueue.c */, + 7463B71C12F9CE6B00983F6A /* meshqueue.h */, + 7463B71D12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c */, + 7463B71E12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.h */, + 7463B71F12F9CE6B00983F6A /* model_alias.c */, + 7463B72012F9CE6B00983F6A /* model_alias.h */, + 7463B72112F9CE6B00983F6A /* model_brush.c */, + 7463B72212F9CE6B00983F6A /* model_brush.h */, + 7463B72312F9CE6B00983F6A /* model_dpmodel.h */, + 7463B72412F9CE6B00983F6A /* model_iqm.h */, + 7463B72512F9CE6B00983F6A /* model_psk.h */, + 7463B72612F9CE6B00983F6A /* model_shared.c */, + 7463B72712F9CE6B00983F6A /* model_shared.h */, + 7463B72812F9CE6B00983F6A /* model_sprite.c */, + 7463B72912F9CE6B00983F6A /* model_sprite.h */, + 7463B72A12F9CE6B00983F6A /* model_zymotic.h */, + 7463B72B12F9CE6B00983F6A /* modelgen.h */, + 7463B72C12F9CE6B00983F6A /* mprogdefs.h */, + 7463B72D12F9CE6B00983F6A /* mvm_cmds.c */, + 7463B72E12F9CE6B00983F6A /* netconn.c */, + 7463B72F12F9CE6B00983F6A /* netconn.h */, + 7463B73012F9CE6B00983F6A /* palette.c */, + 7463B73112F9CE6B00983F6A /* palette.h */, + 7463B73212F9CE6B00983F6A /* polygon.c */, + 7463B73312F9CE6B00983F6A /* polygon.h */, + 7463B73412F9CE6B00983F6A /* portals.c */, + 7463B73512F9CE6B00983F6A /* portals.h */, + 7463B73612F9CE6B00983F6A /* pr_comp.h */, + 7463B73712F9CE6B00983F6A /* progdefs.h */, + 7463B73812F9CE6B00983F6A /* progs.h */, + 7463B73912F9CE6B00983F6A /* progsvm.h */, + 7463B73A12F9CE6B00983F6A /* protocol.c */, + 7463B73B12F9CE6B00983F6A /* protocol.h */, + 7463B73C12F9CE6B00983F6A /* prvm_cmds.c */, + 7463B73D12F9CE6B00983F6A /* prvm_cmds.h */, + 7463B73E12F9CE6B00983F6A /* prvm_edict.c */, + 7463B73F12F9CE6B00983F6A /* prvm_exec.c */, + 7463B74012F9CE6B00983F6A /* prvm_execprogram.h */, + 7463B74112F9CE6B00983F6A /* qtypes.h */, + 7463B74212F9CE6B00983F6A /* quakedef.h */, + 7463B74312F9CE6B00983F6A /* r_explosion.c */, + 7463B74412F9CE6B00983F6A /* r_lightning.c */, + 7463B74512F9CE6B00983F6A /* r_modules.c */, + 7463B74612F9CE6B00983F6A /* r_modules.h */, + 7463B74712F9CE6B00983F6A /* r_shadow.c */, + 7463B74812F9CE6B00983F6A /* r_shadow.h */, + 7463B74912F9CE6B00983F6A /* r_sky.c */, + 7463B74A12F9CE6B00983F6A /* r_sprites.c */, + 7463B74B12F9CE6B00983F6A /* r_textures.h */, + 7463B74C12F9CE6B00983F6A /* render.h */, + 7463B74D12F9CE6B00983F6A /* resource.h */, + 7463B74E12F9CE6B00983F6A /* sbar.c */, + 7463B74F12F9CE6B00983F6A /* sbar.h */, + 7463B75012F9CE6B00983F6A /* screen.h */, + 7463B75112F9CE6B00983F6A /* server.h */, + 7463B75212F9CE6B00983F6A /* snd_main.c */, + 7463B75312F9CE6B00983F6A /* snd_main.h */, + 7463B75412F9CE6B00983F6A /* snd_mem.c */, + 7463B75512F9CE6B00983F6A /* snd_mix.c */, + 7463B75612F9CE6B00983F6A /* snd_modplug.c */, + 7463B75712F9CE6B00983F6A /* snd_modplug.h */, + 7463B75812F9CE6B00983F6A /* snd_ogg.c */, + 7463B75912F9CE6B00983F6A /* snd_ogg.h */, + 7463B75A12F9CE6B00983F6A /* snd_sdl.c */, + 7463B75B12F9CE6B00983F6A /* snd_wav.c */, + 7463B75C12F9CE6B00983F6A /* snd_wav.h */, + 7463B75D12F9CE6B00983F6A /* sound.h */, + 7463B75E12F9CE6B00983F6A /* spritegn.h */, + 7463B75F12F9CE6B00983F6A /* sv_demo.c */, + 7463B76012F9CE6B00983F6A /* sv_demo.h */, + 7463B76112F9CE6B00983F6A /* sv_main.c */, + 7463B76212F9CE6B00983F6A /* sv_move.c */, + 7463B76312F9CE6B00983F6A /* sv_phys.c */, + 7463B76412F9CE6B00983F6A /* sv_user.c */, + 7463B76512F9CE6B00983F6A /* svbsp.c */, + 7463B76612F9CE6B00983F6A /* svbsp.h */, + 7463B76712F9CE6B00983F6A /* svvm_cmds.c */, + 7463B76812F9CE6B00983F6A /* sys_sdl.c */, + 7463B76912F9CE6B00983F6A /* sys_shared.c */, + 7463B76A12F9CE6B00983F6A /* sys.h */, + 7463B76B12F9CE6B00983F6A /* timing.h */, + 7463B76C12F9CE6B00983F6A /* utf8lib.c */, + 7463B76D12F9CE6B00983F6A /* utf8lib.h */, + 7463B76E12F9CE6B00983F6A /* vid_sdl.c */, + 7463B76F12F9CE6B00983F6A /* vid_shared.c */, + 7463B77012F9CE6B00983F6A /* vid.h */, + 7463B77112F9CE6B00983F6A /* view.c */, + 7463B77212F9CE6B00983F6A /* wad.c */, + 7463B77312F9CE6B00983F6A /* wad.h */, + 7463B77412F9CE6B00983F6A /* world.c */, + 7463B77512F9CE6B00983F6A /* world.h */, + 7463B77612F9CE6B00983F6A /* zone.c */, + 7463B77712F9CE6B00983F6A /* zone.h */, + ); + name = Sources; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 7463B7D812F9CF8F00983F6A /* darkplaces64x64.png */, + 8D1107310486CEB800E47090 /* Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + FDB8BFC50E5A0F6A00980157 /* CoreGraphics.framework */, + FD77A0840E26BDB800F39101 /* AudioToolbox.framework */, + FD779EDD0E26BA1200F39101 /* CoreAudio.framework */, + 28FD15070DC6FC5B0079059D /* QuartzCore.framework */, + 28FD14FF0DC6FC520079059D /* OpenGLES.framework */, + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + FD779EC50E26B99E00F39101 /* Libraries */ = { + isa = PBXGroup; + children = ( + FD779ED00E26B9B000F39101 /* libSDLSimulator.a */, + FD779ED10E26B9B000F39101 /* libSDLiPhoneOS.a */, + ); + name = Libraries; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* DPiOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "DPiOS" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DPiOS; + productName = DPiOS; + productReference = 1D6058910D05DD3D006BFB54 /* DPiOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "DPiOS" */; + compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* DPiOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7463B7D912F9CF8F00983F6A /* darkplaces64x64.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7463B77812F9CE6B00983F6A /* bih.c in Sources */, + 7463B77912F9CE6B00983F6A /* cap_avi.c in Sources */, + 7463B77A12F9CE6B00983F6A /* cap_ogg.c in Sources */, + 7463B77B12F9CE6B00983F6A /* cd_sdl.c in Sources */, + 7463B77C12F9CE6B00983F6A /* cd_shared.c in Sources */, + 7463B77D12F9CE6B00983F6A /* cl_collision.c in Sources */, + 7463B77E12F9CE6B00983F6A /* cl_demo.c in Sources */, + 7463B77F12F9CE6B00983F6A /* cl_dyntexture.c in Sources */, + 7463B78012F9CE6B00983F6A /* cl_gecko.c in Sources */, + 7463B78112F9CE6B00983F6A /* cl_input.c in Sources */, + 7463B78212F9CE6B00983F6A /* cl_main.c in Sources */, + 7463B78312F9CE6B00983F6A /* cl_parse.c in Sources */, + 7463B78412F9CE6B00983F6A /* cl_particles.c in Sources */, + 7463B78512F9CE6B00983F6A /* cl_screen.c in Sources */, + 7463B78612F9CE6B00983F6A /* cl_video.c in Sources */, + 7463B78712F9CE6B00983F6A /* cmd.c in Sources */, + 7463B78812F9CE6B00983F6A /* collision.c in Sources */, + 7463B78912F9CE6B00983F6A /* common.c in Sources */, + 7463B78A12F9CE6B00983F6A /* console.c in Sources */, + 7463B78B12F9CE6B00983F6A /* crypto.c in Sources */, + 7463B78C12F9CE6B00983F6A /* csprogs.c in Sources */, + 7463B78D12F9CE6B00983F6A /* curves.c in Sources */, + 7463B78E12F9CE6B00983F6A /* cvar.c in Sources */, + 7463B78F12F9CE6B00983F6A /* dpsoftrast.c in Sources */, + 7463B79012F9CE6B00983F6A /* dpvsimpledecode.c in Sources */, + 7463B79112F9CE6B00983F6A /* filematch.c in Sources */, + 7463B79212F9CE6B00983F6A /* fractalnoise.c in Sources */, + 7463B79312F9CE6B00983F6A /* fs.c in Sources */, + 7463B79412F9CE6B00983F6A /* ft2.c in Sources */, + 7463B79512F9CE6B00983F6A /* gl_backend.c in Sources */, + 7463B79612F9CE6B00983F6A /* gl_draw.c in Sources */, + 7463B79712F9CE6B00983F6A /* gl_rmain.c in Sources */, + 7463B79812F9CE6B00983F6A /* gl_rsurf.c in Sources */, + 7463B79912F9CE6B00983F6A /* gl_textures.c in Sources */, + 7463B79A12F9CE6B00983F6A /* hmac.c in Sources */, + 7463B79B12F9CE6B00983F6A /* host_cmd.c in Sources */, + 7463B79C12F9CE6B00983F6A /* host.c in Sources */, + 7463B79D12F9CE6B00983F6A /* image_png.c in Sources */, + 7463B79E12F9CE6B00983F6A /* image.c in Sources */, + 7463B79F12F9CE6B00983F6A /* jpeg.c in Sources */, + 7463B7A012F9CE6B00983F6A /* keys.c in Sources */, + 7463B7A112F9CE6B00983F6A /* lhnet.c in Sources */, + 7463B7A212F9CE6B00983F6A /* libcurl.c in Sources */, + 7463B7A312F9CE6B00983F6A /* mathlib.c in Sources */, + 7463B7A412F9CE6B00983F6A /* matrixlib.c in Sources */, + 7463B7A512F9CE6B00983F6A /* mdfour.c in Sources */, + 7463B7A612F9CE6B00983F6A /* menu.c in Sources */, + 7463B7A712F9CE6B00983F6A /* meshqueue.c in Sources */, + 7463B7A812F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c in Sources */, + 7463B7A912F9CE6B00983F6A /* model_alias.c in Sources */, + 7463B7AA12F9CE6B00983F6A /* model_brush.c in Sources */, + 7463B7AB12F9CE6B00983F6A /* model_shared.c in Sources */, + 7463B7AC12F9CE6B00983F6A /* model_sprite.c in Sources */, + 7463B7AD12F9CE6B00983F6A /* mvm_cmds.c in Sources */, + 7463B7AE12F9CE6B00983F6A /* netconn.c in Sources */, + 7463B7AF12F9CE6B00983F6A /* palette.c in Sources */, + 7463B7B012F9CE6B00983F6A /* polygon.c in Sources */, + 7463B7B112F9CE6B00983F6A /* portals.c in Sources */, + 7463B7B212F9CE6B00983F6A /* protocol.c in Sources */, + 7463B7B312F9CE6B00983F6A /* prvm_cmds.c in Sources */, + 7463B7B412F9CE6B00983F6A /* prvm_edict.c in Sources */, + 7463B7B512F9CE6B00983F6A /* prvm_exec.c in Sources */, + 7463B7B612F9CE6B00983F6A /* r_explosion.c in Sources */, + 7463B7B712F9CE6B00983F6A /* r_lightning.c in Sources */, + 7463B7B812F9CE6B00983F6A /* r_modules.c in Sources */, + 7463B7B912F9CE6B00983F6A /* r_shadow.c in Sources */, + 7463B7BA12F9CE6B00983F6A /* r_sky.c in Sources */, + 7463B7BB12F9CE6B00983F6A /* r_sprites.c in Sources */, + 7463B7BC12F9CE6B00983F6A /* sbar.c in Sources */, + 7463B7BD12F9CE6B00983F6A /* snd_main.c in Sources */, + 7463B7BE12F9CE6B00983F6A /* snd_mem.c in Sources */, + 7463B7BF12F9CE6B00983F6A /* snd_mix.c in Sources */, + 7463B7C012F9CE6B00983F6A /* snd_modplug.c in Sources */, + 7463B7C112F9CE6B00983F6A /* snd_ogg.c in Sources */, + 7463B7C212F9CE6B00983F6A /* snd_sdl.c in Sources */, + 7463B7C312F9CE6B00983F6A /* snd_wav.c in Sources */, + 7463B7C412F9CE6B00983F6A /* sv_demo.c in Sources */, + 7463B7C512F9CE6B00983F6A /* sv_main.c in Sources */, + 7463B7C612F9CE6B00983F6A /* sv_move.c in Sources */, + 7463B7C712F9CE6B00983F6A /* sv_phys.c in Sources */, + 7463B7C812F9CE6B00983F6A /* sv_user.c in Sources */, + 7463B7C912F9CE6B00983F6A /* svbsp.c in Sources */, + 7463B7CA12F9CE6B00983F6A /* svvm_cmds.c in Sources */, + 7463B7CB12F9CE6B00983F6A /* sys_sdl.c in Sources */, + 7463B7CC12F9CE6B00983F6A /* sys_shared.c in Sources */, + 7463B7CD12F9CE6B00983F6A /* utf8lib.c in Sources */, + 7463B7CE12F9CE6B00983F6A /* vid_sdl.c in Sources */, + 7463B7CF12F9CE6B00983F6A /* vid_shared.c in Sources */, + 7463B7D012F9CE6B00983F6A /* view.c in Sources */, + 7463B7D112F9CE6B00983F6A /* wad.c in Sources */, + 7463B7D212F9CE6B00983F6A /* world.c in Sources */, + 7463B7D312F9CE6B00983F6A /* zone.c in Sources */, + 7463B7EA12F9D11E00983F6A /* mod_skeletal_animatevertices_sse.c in Sources */, + 7463B7EF12F9D17D00983F6A /* builddate.c in Sources */, + 7463B7F012F9D17D00983F6A /* clvm_cmds.c in Sources */, + 7487D481130102AA00AEE909 /* thread_sdl.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = ""; + INFOPLIST_FILE = Info.plist; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/lib\"", + ); + PRODUCT_NAME = DPiOS; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = ""; + INFOPLIST_FILE = Info.plist; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/lib\"", + ); + PRODUCT_NAME = DPiOS; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ./include; + LIBRARY_SEARCH_PATHS = ./lib; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ""; + PREBINDING = NO; + SDKROOT = iphoneos; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ./include; + LIBRARY_SEARCH_PATHS = ./lib; + OTHER_CFLAGS = ""; + PREBINDING = NO; + SDKROOT = iphoneos; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "DPiOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "DPiOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/misc/source/darkplaces-src/Darkplaces.app/Contents/Info.plist b/misc/source/darkplaces-src/Darkplaces.app/Contents/Info.plist new file mode 100644 index 00000000..68abd985 --- /dev/null +++ b/misc/source/darkplaces-src/Darkplaces.app/Contents/Info.plist @@ -0,0 +1,18 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + darkplaces-sdl + CFBundleIconFile + Darkplaces + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/misc/source/darkplaces-src/Darkplaces.app/Contents/PkgInfo b/misc/source/darkplaces-src/Darkplaces.app/Contents/PkgInfo new file mode 100644 index 00000000..bd04210f --- /dev/null +++ b/misc/source/darkplaces-src/Darkplaces.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/._Darkplaces.icns b/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/._Darkplaces.icns new file mode 100644 index 00000000..0ddac7cf Binary files /dev/null and b/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/._Darkplaces.icns differ diff --git a/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/Darkplaces.icns b/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/Darkplaces.icns new file mode 100644 index 00000000..262e82c1 Binary files /dev/null and b/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/Darkplaces.icns differ diff --git a/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/English.lproj/InfoPlist.strings b/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/English.lproj/InfoPlist.strings new file mode 100644 index 00000000..5d6a57ef Binary files /dev/null and b/misc/source/darkplaces-src/Darkplaces.app/Contents/Resources/English.lproj/InfoPlist.strings differ diff --git a/misc/source/darkplaces-src/Doxyfile b/misc/source/darkplaces-src/Doxyfile new file mode 100644 index 00000000..322052e0 --- /dev/null +++ b/misc/source/darkplaces-src/Doxyfile @@ -0,0 +1,1503 @@ +# Doxyfile 1.5.9 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = darkplaces + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set +# FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/misc/source/darkplaces-src/Info.plist b/misc/source/darkplaces-src/Info.plist new file mode 100644 index 00000000..3873788b --- /dev/null +++ b/misc/source/darkplaces-src/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + darkplaces64x64 + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + + diff --git a/misc/source/darkplaces-src/README.iOS b/misc/source/darkplaces-src/README.iOS new file mode 100644 index 00000000..58007ed0 --- /dev/null +++ b/misc/source/darkplaces-src/README.iOS @@ -0,0 +1,6 @@ +To build DarkPlaces for iOS, you need to extract this zip into the source folder: +http://ghdigital.com/~havoc/SDLiOS20110208.zip + +This is built from the in-development version of SDL 1.3, to make an updated include folder and libSDL*.a files, you need to get the SDL 1.3 source (from hg or a nightly build or whatever), then simply open Xcode-iPhoneOS/SDL/SDLiPhoneOS.xcodeproj and build libSDL for both simulator and device and then the SDL Application xcode template. + +Then copy the include folder and two libSDL*.a files from the Xcode-iPhoneOS/SDL/build/Debug-template (or Release-template) into the darkplaces source folder, and it will build with your updated files. diff --git a/misc/source/darkplaces-src/SDLMain.h b/misc/source/darkplaces-src/SDLMain.h new file mode 100644 index 00000000..4683df57 --- /dev/null +++ b/misc/source/darkplaces-src/SDLMain.h @@ -0,0 +1,11 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import + +@interface SDLMain : NSObject +@end diff --git a/misc/source/darkplaces-src/SDLMain.m b/misc/source/darkplaces-src/SDLMain.m new file mode 100644 index 00000000..2434f81a --- /dev/null +++ b/misc/source/darkplaces-src/SDLMain.m @@ -0,0 +1,381 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#include "SDL.h" +#include "SDLMain.h" +#include /* for MAXPATHLEN */ +#include + +/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, + but the method still is there and works. To avoid warnings, we declare + it ourselves here. */ +@interface NSApplication(SDL_Missing_Methods) +- (void)setAppleMenu:(NSMenu *)menu; +@end + +/* Use this flag to determine whether we use SDLMain.nib or not */ +#define SDL_USE_NIB_FILE 0 + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +static BOOL gCalledAppMainline = FALSE; + +static NSString *getApplicationName(void) +{ + const NSDictionary *dict; + NSString *appName = 0; + + /* Determine the application name */ + dict = (const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); + if (dict) + appName = [dict objectForKey: @"CFBundleName"]; + + if (![appName length]) + appName = [[NSProcessInfo processInfo] processName]; + + return appName; +} + +#if SDL_USE_NIB_FILE +/* A helper category for NSString */ +@interface NSString (ReplaceSubString) +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; +@end +#endif + +@interface NSApplication (SDLApplication) +@end + +@implementation NSApplication (SDLApplication) +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { + chdir(parentdir); /* chdir to the binary app's parent */ + } + CFRelease(url); + CFRelease(url2); + } +} + +#if SDL_USE_NIB_FILE + +/* Fix menu to contain the real app name instead of "SDL App" */ +- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName +{ + NSRange aRange; + NSEnumerator *enumerator; + NSMenuItem *menuItem; + + aRange = [[aMenu title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; + + enumerator = [[aMenu itemArray] objectEnumerator]; + while ((menuItem = [enumerator nextObject])) + { + aRange = [[menuItem title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; + if ([menuItem hasSubmenu]) + [self fixMenu:[menuItem submenu] withAppName:appName]; + } +} + +#else + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenuItem *menuItem; + NSString *title; + NSString *appName; + + appName = getApplicationName(); + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (int argc, char **argv) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [NSApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [NSApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; + + [sdlMain release]; + [pool release]; +} + +#endif + + +/* + * Catch document open requests...this lets us notice files when the app + * was launched by double-clicking a document, or when a document was + * dragged/dropped on the app's icon. You need to have a + * CFBundleDocumentsType section in your Info.plist to get this message, + * apparently. + * + * Files are added to gArgv, so to the app, they'll look like command line + * arguments. Previously, apps launched from the finder had nothing but + * an argv[0]. + * + * This message may be received multiple times to open several docs on launch. + * + * This message is ignored once the app's mainline has been called. + */ +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + const char *temparg; + size_t arglen; + char *arg; + char **newargv; + + if (!gFinderLaunch) /* MacOS is passing command line args. */ + return FALSE; + + if (gCalledAppMainline) /* app has started, ignore this document. */ + return FALSE; + + temparg = [filename UTF8String]; + arglen = SDL_strlen(temparg) + 1; + arg = (char *) SDL_malloc(arglen); + if (arg == NULL) + return FALSE; + + newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); + if (newargv == NULL) + { + SDL_free(arg); + return FALSE; + } + gArgv = newargv; + + SDL_strlcpy(arg, temparg, arglen); + gArgv[gArgc++] = arg; + gArgv[gArgc] = NULL; + return TRUE; +} + + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + +#if SDL_USE_NIB_FILE + /* Set the main menu to contain the real app name instead of "SDL App" */ + [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; +#endif + + /* Hand off to main application code */ + gCalledAppMainline = TRUE; + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + + +#ifdef main +# undef main +#endif + + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgv = (char **) SDL_malloc(sizeof (char *) * 2); + gArgv[0] = argv[0]; + gArgv[1] = NULL; + gArgc = 1; + gFinderLaunch = YES; + } else { + int i; + gArgc = argc; + gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); + for (i = 0; i <= argc; i++) + gArgv[i] = argv[i]; + gFinderLaunch = NO; + } + +#if SDL_USE_NIB_FILE + NSApplicationMain (argc, argv); +#else + CustomApplicationMain (argc, argv); +#endif + return 0; +} + diff --git a/misc/source/darkplaces-src/bih.c b/misc/source/darkplaces-src/bih.c new file mode 100644 index 00000000..7a25c668 --- /dev/null +++ b/misc/source/darkplaces-src/bih.c @@ -0,0 +1,216 @@ + +// This code written in 2010 by Forest Hale (lordhavoc ghdigital com), and placed into public domain. + +#include +#include +#include "bih.h" + +static int BIH_BuildNode(bih_t *bih, int numchildren, int *leaflist, float *totalmins, float *totalmaxs) +{ + int i; + int j; + int longestaxis; + int axis = 0; + int nodenum; + int front = 0; + int back = 0; + bih_node_t *node; + bih_leaf_t *child; + float splitdist; + float d; + float mins[3]; + float maxs[3]; + float size[3]; + float frontmins[3]; + float frontmaxs[3]; + float backmins[3]; + float backmaxs[3]; + // calculate bounds of children + child = bih->leafs + leaflist[0]; + mins[0] = child->mins[0]; + mins[1] = child->mins[1]; + mins[2] = child->mins[2]; + maxs[0] = child->maxs[0]; + maxs[1] = child->maxs[1]; + maxs[2] = child->maxs[2]; + for (i = 1;i < numchildren;i++) + { + child = bih->leafs + leaflist[i]; + if (mins[0] > child->mins[0]) mins[0] = child->mins[0]; + if (mins[1] > child->mins[1]) mins[1] = child->mins[1]; + if (mins[2] > child->mins[2]) mins[2] = child->mins[2]; + if (maxs[0] < child->maxs[0]) maxs[0] = child->maxs[0]; + if (maxs[1] < child->maxs[1]) maxs[1] = child->maxs[1]; + if (maxs[2] < child->maxs[2]) maxs[2] = child->maxs[2]; + } + size[0] = maxs[0] - mins[0]; + size[1] = maxs[1] - mins[1]; + size[2] = maxs[2] - mins[2]; + // provide bounds to caller + totalmins[0] = mins[0]; + totalmins[1] = mins[1]; + totalmins[2] = mins[2]; + totalmaxs[0] = maxs[0]; + totalmaxs[1] = maxs[1]; + totalmaxs[2] = maxs[2]; + // if we run out of nodes it's the caller's fault, but don't crash + if (bih->numnodes == bih->maxnodes) + { + if (!bih->error) + bih->error = BIHERROR_OUT_OF_NODES; + return 0; + } + nodenum = bih->numnodes++; + node = bih->nodes + nodenum; + // store bounds for node + node->mins[0] = mins[0]; + node->mins[1] = mins[1]; + node->mins[2] = mins[2]; + node->maxs[0] = maxs[0]; + node->maxs[1] = maxs[1]; + node->maxs[2] = maxs[2]; + node->front = 0; + node->back = 0; + node->frontmin = 0; + node->backmax = 0; + memset(node->children, -1, sizeof(node->children)); + // check if there are few enough children to store an unordered node + if (numchildren <= BIH_MAXUNORDEREDCHILDREN) + { + node->type = BIH_UNORDERED; + for (j = 0;j < numchildren;j++) + node->children[j] = leaflist[j]; + return nodenum; + } + // pick longest axis + longestaxis = 0; + if (size[0] < size[1]) longestaxis = 1; + if (size[longestaxis] < size[2]) longestaxis = 2; + // iterate possible split axis choices, starting with the longest axis, if + // all fail it means all children have the same bounds and we simply split + // the list in half because each node can only have two children. + for (j = 0;j < 3;j++) + { + // pick an axis + axis = (longestaxis + j) % 3; + // sort children into front and back lists + splitdist = (node->mins[axis] + node->maxs[axis]) * 0.5f; + front = 0; + back = 0; + for (i = 0;i < numchildren;i++) + { + child = bih->leafs + leaflist[i]; + d = (child->mins[axis] + child->maxs[axis]) * 0.5f; + if (d < splitdist) + bih->leafsortscratch[back++] = leaflist[i]; + else + leaflist[front++] = leaflist[i]; + } + // now copy the back ones into the space made in the leaflist for them + if (back) + memcpy(leaflist + front, bih->leafsortscratch, back*sizeof(leaflist[0])); + // if both sides have some children, it's good enough for us. + if (front && back) + break; + } + if (j == 3) + { + // somewhat common case: no good choice, divide children arbitrarily + axis = 0; + back = numchildren >> 1; + front = numchildren - back; + } + + // we now have front and back children divided in leaflist... + node->type = (bih_nodetype_t)((int)BIH_SPLITX + axis); + node->front = BIH_BuildNode(bih, front, leaflist, frontmins, frontmaxs); + node->frontmin = frontmins[axis]; + node->back = BIH_BuildNode(bih, back, leaflist + front, backmins, backmaxs); + node->backmax = backmaxs[axis]; + return nodenum; +} + +int BIH_Build(bih_t *bih, int numleafs, bih_leaf_t *leafs, int maxnodes, bih_node_t *nodes, int *temp_leafsort, int *temp_leafsortscratch) +{ + int i; + + memset(bih, 0, sizeof(*bih)); + bih->numleafs = numleafs; + bih->leafs = leafs; + bih->leafsort = temp_leafsort; + bih->leafsortscratch = temp_leafsortscratch; + bih->numnodes = 0; + bih->maxnodes = maxnodes; + bih->nodes = nodes; + + // clear things we intend to rebuild + memset(bih->nodes, 0, sizeof(bih->nodes[0]) * bih->maxnodes); + for (i = 0;i < bih->numleafs;i++) + bih->leafsort[i] = i; + + bih->rootnode = BIH_BuildNode(bih, bih->numleafs, bih->leafsort, bih->mins, bih->maxs); + return bih->error; +} + +static void BIH_GetTriangleListForBox_Node(const bih_t *bih, int nodenum, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, int *numtrianglespointer, const float *mins, const float *maxs) +{ + int axis; + bih_node_t *node; + bih_leaf_t *leaf; + for(;;) + { + node = bih->nodes + nodenum; + // check if this is an unordered node (which holds an array of leaf numbers) + if (node->type == BIH_UNORDERED) + { + for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) + { + leaf = bih->leafs + node->children[axis]; + if (mins[0] > leaf->maxs[0] || maxs[0] < leaf->mins[0] + || mins[1] > leaf->maxs[1] || maxs[1] < leaf->mins[1] + || mins[2] > leaf->maxs[2] || maxs[2] < leaf->mins[2]) + continue; + switch(leaf->type) + { + case BIH_RENDERTRIANGLE: + if (*numtrianglespointer >= maxtriangles) + { + ++*numtrianglespointer; // so the caller can detect overflow + break; + } + if(trianglelist_surf) + trianglelist_surf[*numtrianglespointer] = leaf->surfaceindex; + trianglelist_idx[*numtrianglespointer] = leaf->itemindex; + ++*numtrianglespointer; + break; + default: + break; + } + } + return; + } + // splitting node + axis = node->type - BIH_SPLITX; + if (mins[axis] < node->backmax) + { + if (maxs[axis] > node->frontmin) + BIH_GetTriangleListForBox_Node(bih, node->front, maxtriangles, trianglelist_idx, trianglelist_surf, numtrianglespointer, mins, maxs); + nodenum = node->back; + continue; + } + if (maxs[axis] > node->frontmin) + { + nodenum = node->front; + continue; + } + // fell between the child groups, nothing here + return; + } +} + +int BIH_GetTriangleListForBox(const bih_t *bih, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, const float *mins, const float *maxs) +{ + int numtriangles = 0; + BIH_GetTriangleListForBox_Node(bih, bih->rootnode, maxtriangles, trianglelist_idx, trianglelist_surf, &numtriangles, mins, maxs); + return numtriangles; +} diff --git a/misc/source/darkplaces-src/bih.h b/misc/source/darkplaces-src/bih.h new file mode 100644 index 00000000..43b659e9 --- /dev/null +++ b/misc/source/darkplaces-src/bih.h @@ -0,0 +1,91 @@ + +// This code written in 2010 by Forest Hale (lordhavoc ghdigital com), and placed into public domain. + +// Based on information in http://zach.in.tu-clausthal.de/papers/vrst02.html (in particular vrst02_boxtree.pdf) + +#ifndef BIH_H +#define BIH_H + +#define BIH_MAXUNORDEREDCHILDREN 8 + +typedef enum biherror_e +{ + BIHERROR_OK, // no error, be happy + BIHERROR_OUT_OF_NODES // could not produce complete hierarchy, maxnodes too low (should be roughly half of numleafs) +} +biherror_t; + +typedef enum bih_nodetype_e +{ + BIH_SPLITX = 0, + BIH_SPLITY = 1, + BIH_SPLITZ = 2, + BIH_UNORDERED = 3, +} +bih_nodetype_t; + +typedef enum bih_leaftype_e +{ + BIH_BRUSH = 4, + BIH_COLLISIONTRIANGLE = 5, + BIH_RENDERTRIANGLE = 6 +} +bih_leaftype_t; + +typedef struct bih_node_s +{ + bih_nodetype_t type; // = BIH_SPLITX and similar values + // TODO: store just one float for distance, and have BIH_SPLITMINX and BIH_SPLITMAXX distinctions, to reduce memory footprint and traversal time, as described in the paper (vrst02_boxtree.pdf) + // TODO: move bounds data to parent node and remove it from leafs? + float mins[3]; + float maxs[3]; + // node indexes of children (always > this node's index) + int front; + int back; + // interval of children + float frontmin; // children[0] + float backmax; // children[1] + // BIH_UNORDERED uses this for a list of leafindex (all >= 0), -1 = end of list + int children[BIH_MAXUNORDEREDCHILDREN]; +} +bih_node_t; + +typedef struct bih_leaf_s +{ + bih_leaftype_t type; // = BIH_BRUSH And similar values + float mins[3]; + float maxs[3]; + // data past this point is generic and entirely up to the caller... + int textureindex; + int surfaceindex; + int itemindex; // triangle or brush index +} +bih_leaf_t; + +typedef struct bih_s +{ + // permanent fields + // leafs are constructed by caller before calling BIH_Build + int numleafs; + bih_leaf_t *leafs; + // nodes are constructed by BIH_Build + int numnodes; + bih_node_t *nodes; + int rootnode; // 0 if numnodes > 0, -1 otherwise + // bounds calculated by BIH_Build + float mins[3]; + float maxs[3]; + + // fields used only during BIH_Build: + int maxnodes; + int error; // set to a value if an error occurs in building (such as numnodes == maxnodes) + int *leafsort; + int *leafsortscratch; +} +bih_t; + +int BIH_Build(bih_t *bih, int numleafs, bih_leaf_t *leafs, int maxnodes, bih_node_t *nodes, int *temp_leafsort, int *temp_leafsortscratch); + +int BIH_GetTriangleListForBox(const bih_t *bih, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, const float *mins, const float *maxs); + +#endif diff --git a/misc/source/darkplaces-src/bspfile.h b/misc/source/darkplaces-src/bspfile.h new file mode 100644 index 00000000..648b294d --- /dev/null +++ b/misc/source/darkplaces-src/bspfile.h @@ -0,0 +1,299 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +#define MAX_MAP_HULLS 16 // Q1BSP has 4, Hexen2 Q1BSP has 8, MCBSP has 16 + +//============================================================================= + + +#define BSPVERSION 29 + +typedef struct lump_s +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 +#define HEADER_LUMPS 15 + +typedef struct hullinfo_s +{ + int filehulls; + float hullsizes[MAX_MAP_HULLS][2][3]; +} hullinfo_t; + +// WARNING: this struct does NOT match q1bsp's disk format because MAX_MAP_HULLS has been changed by Sajt's MCBSP code, this struct is only being used in memory as a result +typedef struct dmodel_s +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +typedef struct dheader_s +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct dmiptexlump_s +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} miptex_t; + + +typedef struct dvertex_s +{ + float point[3]; +} dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +typedef struct dplane_s +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +// contents values in Q1 maps +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +// these were #ifdef QUAKE2 in the quake source +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +//contents flags in Q2 maps +#define CONTENTSQ2_SOLID 0x00000001 // an eye is never valid in a solid +#define CONTENTSQ2_WINDOW 0x00000002 // translucent, but not watery +#define CONTENTSQ2_AUX 0x00000004 +#define CONTENTSQ2_LAVA 0x00000008 +#define CONTENTSQ2_SLIME 0x00000010 +#define CONTENTSQ2_WATER 0x00000020 +#define CONTENTSQ2_MIST 0x00000040 +#define CONTENTSQ2_AREAPORTAL 0x00008000 +#define CONTENTSQ2_PLAYERCLIP 0x00010000 +#define CONTENTSQ2_MONSTERCLIP 0x00020000 +#define CONTENTSQ2_CURRENT_0 0x00040000 +#define CONTENTSQ2_CURRENT_90 0x00080000 +#define CONTENTSQ2_CURRENT_180 0x00100000 +#define CONTENTSQ2_CURRENT_270 0x00200000 +#define CONTENTSQ2_CURRENT_UP 0x00400000 +#define CONTENTSQ2_CURRENT_DOWN 0x00800000 +#define CONTENTSQ2_ORIGIN 0x01000000 // removed before bsping an entity +#define CONTENTSQ2_MONSTER 0x02000000 // should never be on a brush, only in game +#define CONTENTSQ2_DEADMONSTER 0x04000000 +#define CONTENTSQ2_DETAIL 0x08000000 // brushes to be added after vis leafs +#define CONTENTSQ2_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTSQ2_LADDER 0x20000000 + +//contents flags in Q3 maps +#define CONTENTSQ3_SOLID 0x00000001 // solid (opaque and transparent) +#define CONTENTSQ3_LAVA 0x00000008 // lava +#define CONTENTSQ3_SLIME 0x00000010 // slime +#define CONTENTSQ3_WATER 0x00000020 // water +#define CONTENTSQ3_FOG 0x00000040 // unused? +#define CONTENTSQ3_AREAPORTAL 0x00008000 // areaportal (separates areas) +#define CONTENTSQ3_PLAYERCLIP 0x00010000 // block players +#define CONTENTSQ3_MONSTERCLIP 0x00020000 // block monsters +#define CONTENTSQ3_TELEPORTER 0x00040000 // hint for Q3's bots +#define CONTENTSQ3_JUMPPAD 0x00080000 // hint for Q3's bots +#define CONTENTSQ3_CLUSTERPORTAL 0x00100000 // hint for Q3's bots +#define CONTENTSQ3_DONOTENTER 0x00200000 // hint for Q3's bots +#define CONTENTSQ3_BOTCLIP 0x00400000 // hint for Q3's bots +#define CONTENTSQ3_ORIGIN 0x01000000 // used by origin brushes to indicate origin of bmodel (removed by map compiler) +#define CONTENTSQ3_BODY 0x02000000 // used by bbox entities (should never be on a brush) +#define CONTENTSQ3_CORPSE 0x04000000 // used by dead bodies (SOLID_CORPSE in darkplaces) +#define CONTENTSQ3_DETAIL 0x08000000 // brushes that do not split the bsp tree (decorations) +#define CONTENTSQ3_STRUCTURAL 0x10000000 // brushes that split the bsp tree +#define CONTENTSQ3_TRANSLUCENT 0x20000000 // leaves surfaces that are inside for rendering +#define CONTENTSQ3_TRIGGER 0x40000000 // used by trigger entities +#define CONTENTSQ3_NODROP 0x80000000 // remove items that fall into this brush + +#define SUPERCONTENTS_SOLID 0x00000001 +#define SUPERCONTENTS_WATER 0x00000002 +#define SUPERCONTENTS_SLIME 0x00000004 +#define SUPERCONTENTS_LAVA 0x00000008 +#define SUPERCONTENTS_SKY 0x00000010 +#define SUPERCONTENTS_BODY 0x00000020 +#define SUPERCONTENTS_CORPSE 0x00000040 +#define SUPERCONTENTS_NODROP 0x00000080 +#define SUPERCONTENTS_PLAYERCLIP 0x00000100 +#define SUPERCONTENTS_MONSTERCLIP 0x00000200 +#define SUPERCONTENTS_DONOTENTER 0x00000400 +#define SUPERCONTENTS_BOTCLIP 0x00000800 +#define SUPERCONTENTS_OPAQUE 0x00001000 +// TODO: is there any reason to define: +// fog? +// areaportal? +// teleporter? +// jumppad? +// clusterportal? +// detail? (div0) no, game code should not be allowed to differentiate between structural and detail +// structural? (div0) no, game code should not be allowed to differentiate between structural and detail +// trigger? (div0) no, as these are always solid anyway, and that's all that matters for trigger brushes +#define SUPERCONTENTS_LIQUIDSMASK (SUPERCONTENTS_LAVA | SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER) +#define SUPERCONTENTS_VISBLOCKERMASK SUPERCONTENTS_OPAQUE + +/* +#define SUPERCONTENTS_DEADMONSTER 0x00000000 +#define SUPERCONTENTS_CURRENT_0 0x00000000 +#define SUPERCONTENTS_CURRENT_90 0x00000000 +#define SUPERCONTENTS_CURRENT_180 0x00000000 +#define SUPERCONTENTS_CURRENT_270 0x00000000 +#define SUPERCONTENTS_CURRENT_DOWN 0x00000000 +#define SUPERCONTENTS_CURRENT_UP 0x00000000 +#define SUPERCONTENTS_AREAPORTAL 0x00000000 +#define SUPERCONTENTS_AUX 0x00000000 +#define SUPERCONTENTS_CLUSTERPORTAL 0x00000000 +#define SUPERCONTENTS_DETAIL 0x00000000 +#define SUPERCONTENTS_STRUCTURAL 0x00000000 +#define SUPERCONTENTS_DONOTENTER 0x00000000 +#define SUPERCONTENTS_JUMPPAD 0x00000000 +#define SUPERCONTENTS_LADDER 0x00000000 +#define SUPERCONTENTS_MONSTER 0x00000000 +#define SUPERCONTENTS_MONSTERCLIP 0x00000000 +#define SUPERCONTENTS_PLAYERCLIP 0x00000000 +#define SUPERCONTENTS_TELEPORTER 0x00000000 +#define SUPERCONTENTS_TRANSLUCENT 0x00000000 +#define SUPERCONTENTS_TRIGGER 0x00000000 +#define SUPERCONTENTS_WINDOW 0x00000000 +*/ + + +typedef struct dnode_s +{ + int planenum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + +typedef struct dclipnode_s +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} texinfo_t; +#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct dedge_s +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct dface_s +{ + // LordHavoc: changed from short to unsigned short for q2 support + unsigned short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + unsigned char styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + + + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct dleaf_s +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + unsigned char ambient_level[NUM_AMBIENTS]; +} dleaf_t; + diff --git a/misc/source/darkplaces-src/builddate.c b/misc/source/darkplaces-src/builddate.c new file mode 100644 index 00000000..061173ef --- /dev/null +++ b/misc/source/darkplaces-src/builddate.c @@ -0,0 +1,12 @@ +#define STRINGIFY2(arg) #arg +#define STRINGIFY(arg) STRINGIFY2(arg) + +extern const char *buildstring; +const char *buildstring = __TIME__ " " __DATE__ +#ifdef SVNREVISION +" " STRINGIFY(SVNREVISION) +#endif +#ifdef BUILDTYPE +" " STRINGIFY(BUILDTYPE) +#endif +; diff --git a/misc/source/darkplaces-src/cap_avi.c b/misc/source/darkplaces-src/cap_avi.c new file mode 100644 index 00000000..e7cf88d6 --- /dev/null +++ b/misc/source/darkplaces-src/cap_avi.c @@ -0,0 +1,719 @@ +#include "quakedef.h" +#include "cap_avi.h" + +#define AVI_MASTER_INDEX_SIZE 640 // GB ought to be enough for anyone + +typedef struct capturevideostate_avi_formatspecific_s +{ + // AVI stuff + fs_offset_t videofile_firstchunkframes_offset; + fs_offset_t videofile_totalframes_offset1; + fs_offset_t videofile_totalframes_offset2; + fs_offset_t videofile_totalsampleframes_offset; + int videofile_ix_master_audio_inuse; + fs_offset_t videofile_ix_master_audio_inuse_offset; + fs_offset_t videofile_ix_master_audio_start_offset; + int videofile_ix_master_video_inuse; + fs_offset_t videofile_ix_master_video_inuse_offset; + fs_offset_t videofile_ix_master_video_start_offset; + fs_offset_t videofile_ix_movistart; + fs_offset_t position; + qboolean canseek; + sizebuf_t riffbuffer; + unsigned char riffbufferdata[128]; + sizebuf_t riffindexbuffer; + int riffstacklevel; + fs_offset_t riffstackstartoffset[4]; + fs_offset_t riffstacksizehint[4]; + const char *riffstackfourcc[4]; +} +capturevideostate_avi_formatspecific_t; +#define LOAD_FORMATSPECIFIC_AVI() capturevideostate_avi_formatspecific_t *format = (capturevideostate_avi_formatspecific_t *) cls.capturevideo.formatspecific + +static void SCR_CaptureVideo_RIFF_Start(void) +{ + LOAD_FORMATSPECIFIC_AVI(); + memset(&format->riffbuffer, 0, sizeof(sizebuf_t)); + format->riffbuffer.maxsize = sizeof(format->riffbufferdata); + format->riffbuffer.data = format->riffbufferdata; + format->position = 0; +} + +static void SCR_CaptureVideo_RIFF_Flush(void) +{ + LOAD_FORMATSPECIFIC_AVI(); + if (format->riffbuffer.cursize > 0) + { + if (!FS_Write(cls.capturevideo.videofile, format->riffbuffer.data, format->riffbuffer.cursize)) + cls.capturevideo.error = true; + format->position += format->riffbuffer.cursize; + format->riffbuffer.cursize = 0; + format->riffbuffer.overflowed = false; + } +} + +static void SCR_CaptureVideo_RIFF_FlushNoIncrease(void) +{ + LOAD_FORMATSPECIFIC_AVI(); + if (format->riffbuffer.cursize > 0) + { + if (!FS_Write(cls.capturevideo.videofile, format->riffbuffer.data, format->riffbuffer.cursize)) + cls.capturevideo.error = true; + format->riffbuffer.cursize = 0; + format->riffbuffer.overflowed = false; + } +} + +static void SCR_CaptureVideo_RIFF_WriteBytes(const unsigned char *data, size_t size) +{ + LOAD_FORMATSPECIFIC_AVI(); + SCR_CaptureVideo_RIFF_Flush(); + if (!FS_Write(cls.capturevideo.videofile, data, size)) + cls.capturevideo.error = true; + format->position += size; +} + +static void SCR_CaptureVideo_RIFF_Write32(int n) +{ + LOAD_FORMATSPECIFIC_AVI(); + if (format->riffbuffer.cursize + 4 > format->riffbuffer.maxsize) + SCR_CaptureVideo_RIFF_Flush(); + MSG_WriteLong(&format->riffbuffer, n); +} + +static void SCR_CaptureVideo_RIFF_Write16(int n) +{ + LOAD_FORMATSPECIFIC_AVI(); + if (format->riffbuffer.cursize + 2 > format->riffbuffer.maxsize) + SCR_CaptureVideo_RIFF_Flush(); + MSG_WriteShort(&format->riffbuffer, n); +} + +static void SCR_CaptureVideo_RIFF_WriteFourCC(const char *chunkfourcc) +{ + LOAD_FORMATSPECIFIC_AVI(); + if (format->riffbuffer.cursize + (int)strlen(chunkfourcc) > format->riffbuffer.maxsize) + SCR_CaptureVideo_RIFF_Flush(); + MSG_WriteUnterminatedString(&format->riffbuffer, chunkfourcc); +} + +static void SCR_CaptureVideo_RIFF_WriteTerminatedString(const char *string) +{ + LOAD_FORMATSPECIFIC_AVI(); + if (format->riffbuffer.cursize + (int)strlen(string) > format->riffbuffer.maxsize) + SCR_CaptureVideo_RIFF_Flush(); + MSG_WriteString(&format->riffbuffer, string); +} + +static fs_offset_t SCR_CaptureVideo_RIFF_GetPosition(void) +{ + LOAD_FORMATSPECIFIC_AVI(); + SCR_CaptureVideo_RIFF_Flush(); + //return FS_Tell(cls.capturevideo.videofile); + return format->position; +} + +static void SCR_CaptureVideo_RIFF_Push(const char *chunkfourcc, const char *listtypefourcc, fs_offset_t sizeHint) +{ + LOAD_FORMATSPECIFIC_AVI(); + if (listtypefourcc && sizeHint >= 0) + sizeHint += 4; // size hint is for INNER size + SCR_CaptureVideo_RIFF_WriteFourCC(chunkfourcc); + SCR_CaptureVideo_RIFF_Write32(sizeHint); + SCR_CaptureVideo_RIFF_Flush(); + format->riffstacksizehint[format->riffstacklevel] = sizeHint; + format->riffstackstartoffset[format->riffstacklevel] = SCR_CaptureVideo_RIFF_GetPosition(); + format->riffstackfourcc[format->riffstacklevel] = chunkfourcc; + ++format->riffstacklevel; + if (listtypefourcc) + SCR_CaptureVideo_RIFF_WriteFourCC(listtypefourcc); +} + +static void SCR_CaptureVideo_RIFF_Pop(void) +{ + LOAD_FORMATSPECIFIC_AVI(); + fs_offset_t offset, sizehint; + int x; + unsigned char sizebytes[4]; + // write out the chunk size and then return to the current file position + format->riffstacklevel--; + offset = SCR_CaptureVideo_RIFF_GetPosition(); + + sizehint = format->riffstacksizehint[format->riffstacklevel]; + x = (int)(offset - (format->riffstackstartoffset[format->riffstacklevel])); + + if(x != sizehint) + { + if(sizehint != -1) + { + int i; + Con_Printf("WARNING: invalid size hint %d when writing video data (actual size: %d)\n", (int) sizehint, x); + for(i = 0; i <= format->riffstacklevel; ++i) + { + Con_Printf(" RIFF level %d = %s\n", i, format->riffstackfourcc[i]); + } + } + sizebytes[0] = (x) & 0xff;sizebytes[1] = (x >> 8) & 0xff;sizebytes[2] = (x >> 16) & 0xff;sizebytes[3] = (x >> 24) & 0xff; + if(FS_Seek(cls.capturevideo.videofile, -(x + 4), SEEK_END) >= 0) + { + FS_Write(cls.capturevideo.videofile, sizebytes, 4); + } + FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); + } + + if (offset & 1) + { + SCR_CaptureVideo_RIFF_WriteBytes((unsigned char *) "\0", 1); + } +} + +static void GrowBuf(sizebuf_t *buf, int extralen) +{ + if(buf->cursize + extralen > buf->maxsize) + { + int oldsize = buf->maxsize; + unsigned char *olddata; + olddata = buf->data; + buf->maxsize = max(buf->maxsize * 2, 4096); + buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize); + if(olddata) + { + memcpy(buf->data, olddata, oldsize); + Mem_Free(olddata); + } + } +} + +static void SCR_CaptureVideo_RIFF_IndexEntry(const char *chunkfourcc, int chunksize, int flags) +{ + LOAD_FORMATSPECIFIC_AVI(); + if(!format->canseek) + Host_Error("SCR_CaptureVideo_RIFF_IndexEntry called on non-seekable AVI"); + + if (format->riffstacklevel != 2) + Sys_Error("SCR_Capturevideo_RIFF_IndexEntry: RIFF stack level is %i (should be 2)\n", format->riffstacklevel); + GrowBuf(&format->riffindexbuffer, 16); + SCR_CaptureVideo_RIFF_Flush(); + MSG_WriteUnterminatedString(&format->riffindexbuffer, chunkfourcc); + MSG_WriteLong(&format->riffindexbuffer, flags); + MSG_WriteLong(&format->riffindexbuffer, (int)FS_Tell(cls.capturevideo.videofile) - format->riffstackstartoffset[1]); + MSG_WriteLong(&format->riffindexbuffer, chunksize); +} + +static void SCR_CaptureVideo_RIFF_MakeIxChunk(const char *fcc, const char *dwChunkId, fs_offset_t masteridx_counter, int *masteridx_count, fs_offset_t masteridx_start) +{ + LOAD_FORMATSPECIFIC_AVI(); + int nMatching; + int i; + fs_offset_t ix = SCR_CaptureVideo_RIFF_GetPosition(); + fs_offset_t pos, sz; + + if(!format->canseek) + Host_Error("SCR_CaptureVideo_RIFF_MakeIxChunk called on non-seekable AVI"); + + if(*masteridx_count >= AVI_MASTER_INDEX_SIZE) + return; + + nMatching = 0; // go through index and enumerate them + for(i = 0; i < format->riffindexbuffer.cursize; i += 16) + if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4)) + ++nMatching; + + sz = 2+2+4+4+4+4+4; + for(i = 0; i < format->riffindexbuffer.cursize; i += 16) + if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4)) + sz += 8; + + SCR_CaptureVideo_RIFF_Push(fcc, NULL, sz); + SCR_CaptureVideo_RIFF_Write16(2); // wLongsPerEntry + SCR_CaptureVideo_RIFF_Write16(0x0100); // bIndexType=1, bIndexSubType=0 + SCR_CaptureVideo_RIFF_Write32(nMatching); // nEntriesInUse + SCR_CaptureVideo_RIFF_WriteFourCC(dwChunkId); // dwChunkId + SCR_CaptureVideo_RIFF_Write32(format->videofile_ix_movistart & (fs_offset_t) 0xFFFFFFFFu); + SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) format->videofile_ix_movistart) >> 32); + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved + + for(i = 0; i < format->riffindexbuffer.cursize; i += 16) + if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4)) + { + unsigned int *p = (unsigned int *) (format->riffindexbuffer.data + i); + unsigned int flags = p[1]; + unsigned int rpos = p[2]; + unsigned int size = p[3]; + size &= ~0x80000000; + if(!(flags & 0x10)) // no keyframe? + size |= 0x80000000; + SCR_CaptureVideo_RIFF_Write32(rpos + 8); + SCR_CaptureVideo_RIFF_Write32(size); + } + + SCR_CaptureVideo_RIFF_Flush(); + SCR_CaptureVideo_RIFF_Pop(); + pos = SCR_CaptureVideo_RIFF_GetPosition(); + + if(FS_Seek(cls.capturevideo.videofile, masteridx_start + 16 * *masteridx_count, SEEK_SET) >= 0) + { + SCR_CaptureVideo_RIFF_Write32(ix & (fs_offset_t) 0xFFFFFFFFu); + SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) ix) >> 32); + SCR_CaptureVideo_RIFF_Write32(pos - ix); + SCR_CaptureVideo_RIFF_Write32(nMatching); + SCR_CaptureVideo_RIFF_FlushNoIncrease(); + } + + if(FS_Seek(cls.capturevideo.videofile, masteridx_counter, SEEK_SET) >= 0) + { + SCR_CaptureVideo_RIFF_Write32(++*masteridx_count); + SCR_CaptureVideo_RIFF_FlushNoIncrease(); + } + + FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); // return value doesn't matter here +} + +static void SCR_CaptureVideo_RIFF_Finish(qboolean final) +{ + LOAD_FORMATSPECIFIC_AVI(); + // close the "movi" list + SCR_CaptureVideo_RIFF_Pop(); + if(format->videofile_ix_master_video_inuse_offset) + SCR_CaptureVideo_RIFF_MakeIxChunk("ix00", "00dc", format->videofile_ix_master_video_inuse_offset, &format->videofile_ix_master_video_inuse, format->videofile_ix_master_video_start_offset); + if(format->videofile_ix_master_audio_inuse_offset) + SCR_CaptureVideo_RIFF_MakeIxChunk("ix01", "01wb", format->videofile_ix_master_audio_inuse_offset, &format->videofile_ix_master_audio_inuse, format->videofile_ix_master_audio_start_offset); + // write the idx1 chunk that we've been building while saving the frames (for old style players) + if(final && format->videofile_firstchunkframes_offset) + // TODO replace index creating by OpenDML ix##/##ix/indx chunk so it works for more than one AVI part too + { + SCR_CaptureVideo_RIFF_Push("idx1", NULL, format->riffindexbuffer.cursize); + SCR_CaptureVideo_RIFF_WriteBytes(format->riffindexbuffer.data, format->riffindexbuffer.cursize); + SCR_CaptureVideo_RIFF_Pop(); + } + format->riffindexbuffer.cursize = 0; + // pop the RIFF chunk itself + while (format->riffstacklevel > 0) + SCR_CaptureVideo_RIFF_Pop(); + SCR_CaptureVideo_RIFF_Flush(); + if(format->videofile_firstchunkframes_offset) + { + Con_DPrintf("Finishing first chunk (%d frames)\n", cls.capturevideo.frame); + if(FS_Seek(cls.capturevideo.videofile, format->videofile_firstchunkframes_offset, SEEK_SET) >= 0) + { + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame); + SCR_CaptureVideo_RIFF_FlushNoIncrease(); + } + FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); + format->videofile_firstchunkframes_offset = 0; + } + else + Con_DPrintf("Finishing another chunk (%d frames)\n", cls.capturevideo.frame); +} + +static void SCR_CaptureVideo_RIFF_OverflowCheck(int framesize) +{ + LOAD_FORMATSPECIFIC_AVI(); + fs_offset_t cursize; + //fs_offset_t curfilesize; + if (format->riffstacklevel != 2) + Sys_Error("SCR_CaptureVideo_RIFF_OverflowCheck: chunk stack leakage!\n"); + + if(!format->canseek) + return; + + // check where we are in the file + SCR_CaptureVideo_RIFF_Flush(); + cursize = SCR_CaptureVideo_RIFF_GetPosition() - format->riffstackstartoffset[0]; + //curfilesize = SCR_CaptureVideo_RIFF_GetPosition(); + + // if this would overflow the windows limit of 1GB per RIFF chunk, we need + // to close the current RIFF chunk and open another for future frames + if (8 + cursize + framesize + format->riffindexbuffer.cursize + 8 + format->riffindexbuffer.cursize + 64 > 1<<30) // note that the Ix buffer takes less space... I just don't dare to / 2 here now... sorry, maybe later + { + SCR_CaptureVideo_RIFF_Finish(false); + // begin a new 1GB extended section of the AVI + SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", -1); + SCR_CaptureVideo_RIFF_Push("LIST", "movi", -1); + format->videofile_ix_movistart = format->riffstackstartoffset[1]; + } +} + +// converts from BGRA32 to I420 colorspace (identical to YV12 except chroma plane order is reversed), this colorspace is handled by the Intel(r) 4:2:0 codec on Windows +static void SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(int width, int height, unsigned char *instart, unsigned char *outstart) +{ + int x, y; + int blockr, blockg, blockb; + int outoffset = (width/2)*(height/2); + unsigned char *b, *out; + // process one line at a time, and CbCr every other line at 2 pixel intervals + for (y = 0;y < height;y++) + { + // 1x1 Y + for (b = instart + (height-1-y)*width*4, out = outstart + y*width, x = 0;x < width;x++, b += 4, out++) + { + blockr = b[2]; + blockg = b[1]; + blockb = b[0]; + *out = cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]]; + } + if ((y & 1) == 0) + { + // 2x2 Cr and Cb planes + int inpitch = width*4; + for (b = instart + (height-2-y)*width*4, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 8, out++) + { + blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2; + blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2; + blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2; + // Cr + out[0 ] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128]; + // Cb + out[outoffset] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128]; + } + } + } +} + +static void SCR_CaptureVideo_Avi_VideoFrames(int num) +{ + LOAD_FORMATSPECIFIC_AVI(); + int x = 0, width = cls.capturevideo.width, height = cls.capturevideo.height; + unsigned char *in, *out; + // FIXME: width/height must be multiple of 2, enforce this? + in = cls.capturevideo.outbuffer; + out = cls.capturevideo.outbuffer + width*height*4; + SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(width, height, in, out); + x = width*height+(width/2)*(height/2)*2; + while(num-- > 0) + { + if(format->canseek) + { + SCR_CaptureVideo_RIFF_OverflowCheck(8 + x); + SCR_CaptureVideo_RIFF_IndexEntry("00dc", x, 0x10); // AVIIF_KEYFRAME + } + + if(!format->canseek) + { + SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", 12+8+x); + SCR_CaptureVideo_RIFF_Push("LIST", "movi", 8+x); + } + SCR_CaptureVideo_RIFF_Push("00dc", NULL, x); + SCR_CaptureVideo_RIFF_WriteBytes(out, x); + SCR_CaptureVideo_RIFF_Pop(); + if(!format->canseek) + { + SCR_CaptureVideo_RIFF_Pop(); + SCR_CaptureVideo_RIFF_Pop(); + } + } +} + +void SCR_CaptureVideo_Avi_EndVideo(void) +{ + LOAD_FORMATSPECIFIC_AVI(); + + if(format->canseek) + { + // close any open chunks + SCR_CaptureVideo_RIFF_Finish(true); + + // go back and fix the video frames and audio samples fields + if(format->videofile_totalframes_offset1) + if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalframes_offset1, SEEK_SET) >= 0) + { + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame); + SCR_CaptureVideo_RIFF_FlushNoIncrease(); + } + if(format->videofile_totalframes_offset2) + if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalframes_offset2, SEEK_SET) >= 0) + { + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame); + SCR_CaptureVideo_RIFF_FlushNoIncrease(); + } + if (cls.capturevideo.soundrate) + { + if(format->videofile_totalsampleframes_offset) + if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalsampleframes_offset, SEEK_SET) >= 0) + { + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundsampleframe); + SCR_CaptureVideo_RIFF_FlushNoIncrease(); + } + } + } + + if (format->riffindexbuffer.data) + { + Mem_Free(format->riffindexbuffer.data); + format->riffindexbuffer.data = NULL; + } + + FS_Close(cls.capturevideo.videofile); + cls.capturevideo.videofile = NULL; + + Mem_Free(format); +} + +void SCR_CaptureVideo_Avi_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length) +{ + LOAD_FORMATSPECIFIC_AVI(); + int x; + unsigned char bufstereo16le[PAINTBUFFER_SIZE * 4]; + unsigned char* out_ptr; + size_t i; + + // write the sound buffer as little endian 16bit interleaved stereo + for(i = 0, out_ptr = bufstereo16le; i < length; i++, out_ptr += 4) + { + int n0, n1; + + n0 = paintbuffer[i].sample[0]; + n0 = bound(-32768, n0, 32767); + out_ptr[0] = (unsigned char)n0; + out_ptr[1] = (unsigned char)(n0 >> 8); + + n1 = paintbuffer[i].sample[1]; + n1 = bound(-32768, n1, 32767); + out_ptr[2] = (unsigned char)n1; + out_ptr[3] = (unsigned char)(n1 >> 8); + } + + x = length*4; + if(format->canseek) + { + SCR_CaptureVideo_RIFF_OverflowCheck(8 + x); + SCR_CaptureVideo_RIFF_IndexEntry("01wb", x, 0x10); // AVIIF_KEYFRAME + } + + if(!format->canseek) + { + SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", 12+8+x); + SCR_CaptureVideo_RIFF_Push("LIST", "movi", 8+x); + } + SCR_CaptureVideo_RIFF_Push("01wb", NULL, x); + SCR_CaptureVideo_RIFF_WriteBytes(bufstereo16le, x); + SCR_CaptureVideo_RIFF_Pop(); + if(!format->canseek) + { + SCR_CaptureVideo_RIFF_Pop(); + SCR_CaptureVideo_RIFF_Pop(); + } +} + +void SCR_CaptureVideo_Avi_BeginVideo(void) +{ + int width = cls.capturevideo.width; + int height = cls.capturevideo.height; + int n, d; + unsigned int i; + double aspect; + + aspect = vid.width / (vid.height * vid_pixelheight.value); + + cls.capturevideo.format = CAPTUREVIDEOFORMAT_AVI_I420; + cls.capturevideo.formatextension = "avi"; + cls.capturevideo.videofile = FS_OpenRealFile(va("%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false); + cls.capturevideo.endvideo = SCR_CaptureVideo_Avi_EndVideo; + cls.capturevideo.videoframes = SCR_CaptureVideo_Avi_VideoFrames; + cls.capturevideo.soundframe = SCR_CaptureVideo_Avi_SoundFrame; + cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_avi_formatspecific_t)); + { + LOAD_FORMATSPECIFIC_AVI(); + format->canseek = (FS_Seek(cls.capturevideo.videofile, 0, SEEK_SET) == 0); + SCR_CaptureVideo_RIFF_Start(); + // enclosing RIFF chunk (there can be multiple of these in >1GB files, the later ones are "AVIX" instead of "AVI " and have no header/stream info) + SCR_CaptureVideo_RIFF_Push("RIFF", "AVI ", format->canseek ? -1 : 12+(8+56+12+(12+52+8+40+8+68)+(cls.capturevideo.soundrate?(12+12+52+8+18):0)+12+(8+4))+12+(8+(((int) strlen(engineversion) | 1) + 1))+12); + // AVI main header + SCR_CaptureVideo_RIFF_Push("LIST", "hdrl", format->canseek ? -1 : 8+56+12+(12+52+8+40+8+68)+(cls.capturevideo.soundrate?(12+12+52+8+18):0)+12+(8+4)); + SCR_CaptureVideo_RIFF_Push("avih", NULL, 56); + SCR_CaptureVideo_RIFF_Write32((int)(1000000.0 / (cls.capturevideo.framerate / cls.capturevideo.framestep))); // microseconds per frame + SCR_CaptureVideo_RIFF_Write32(0); // max bytes per second + SCR_CaptureVideo_RIFF_Write32(0); // padding granularity + SCR_CaptureVideo_RIFF_Write32(0x910); // flags (AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE) + format->videofile_firstchunkframes_offset = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0); // total frames + SCR_CaptureVideo_RIFF_Write32(0); // initial frames + if (cls.capturevideo.soundrate) + SCR_CaptureVideo_RIFF_Write32(2); // number of streams + else + SCR_CaptureVideo_RIFF_Write32(1); // number of streams + SCR_CaptureVideo_RIFF_Write32(0); // suggested buffer size + SCR_CaptureVideo_RIFF_Write32(width); // width + SCR_CaptureVideo_RIFF_Write32(height); // height + SCR_CaptureVideo_RIFF_Write32(0); // reserved[0] + SCR_CaptureVideo_RIFF_Write32(0); // reserved[1] + SCR_CaptureVideo_RIFF_Write32(0); // reserved[2] + SCR_CaptureVideo_RIFF_Write32(0); // reserved[3] + SCR_CaptureVideo_RIFF_Pop(); + // video stream info + SCR_CaptureVideo_RIFF_Push("LIST", "strl", format->canseek ? -1 : 12+52+8+40+8+68); + SCR_CaptureVideo_RIFF_Push("strh", "vids", 52); + SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // stream fourcc (I420 colorspace, uncompressed) + SCR_CaptureVideo_RIFF_Write32(0); // flags + SCR_CaptureVideo_RIFF_Write16(0); // priority + SCR_CaptureVideo_RIFF_Write16(0); // language + SCR_CaptureVideo_RIFF_Write32(0); // initial frames + // find an ideal divisor for the framerate + FindFraction(cls.capturevideo.framerate / cls.capturevideo.framestep, &n, &d, 1000); + SCR_CaptureVideo_RIFF_Write32(d); // samples/second divisor + SCR_CaptureVideo_RIFF_Write32(n); // samples/second multiplied by divisor + SCR_CaptureVideo_RIFF_Write32(0); // start + format->videofile_totalframes_offset1 = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); // length + SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // suggested buffer size + SCR_CaptureVideo_RIFF_Write32(0); // quality + SCR_CaptureVideo_RIFF_Write32(0); // sample size + SCR_CaptureVideo_RIFF_Write16(0); // frame left + SCR_CaptureVideo_RIFF_Write16(0); // frame top + SCR_CaptureVideo_RIFF_Write16(width); // frame right + SCR_CaptureVideo_RIFF_Write16(height); // frame bottom + SCR_CaptureVideo_RIFF_Pop(); + // video stream format + SCR_CaptureVideo_RIFF_Push("strf", NULL, 40); + SCR_CaptureVideo_RIFF_Write32(40); // BITMAPINFO struct size + SCR_CaptureVideo_RIFF_Write32(width); // width + SCR_CaptureVideo_RIFF_Write32(height); // height + SCR_CaptureVideo_RIFF_Write16(3); // planes + SCR_CaptureVideo_RIFF_Write16(12); // bitcount + SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // compression + SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // size of image + SCR_CaptureVideo_RIFF_Write32(0); // x pixels per meter + SCR_CaptureVideo_RIFF_Write32(0); // y pixels per meter + SCR_CaptureVideo_RIFF_Write32(0); // color used + SCR_CaptureVideo_RIFF_Write32(0); // color important + SCR_CaptureVideo_RIFF_Pop(); + // master index + if(format->canseek) + { + SCR_CaptureVideo_RIFF_Push("indx", NULL, -1); + SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry + SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0 + format->videofile_ix_master_video_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse + SCR_CaptureVideo_RIFF_WriteFourCC("00dc"); // dwChunkId + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1 + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2 + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3 + format->videofile_ix_master_video_start_offset = SCR_CaptureVideo_RIFF_GetPosition(); + for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i) + SCR_CaptureVideo_RIFF_Write32(0); // fill up later + SCR_CaptureVideo_RIFF_Pop(); + } + // extended format (aspect!) + SCR_CaptureVideo_RIFF_Push("vprp", NULL, 68); + SCR_CaptureVideo_RIFF_Write32(0); // VideoFormatToken + SCR_CaptureVideo_RIFF_Write32(0); // VideoStandard + SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.framerate / cls.capturevideo.framestep)); // dwVerticalRefreshRate (bogus) + SCR_CaptureVideo_RIFF_Write32(width); // dwHTotalInT + SCR_CaptureVideo_RIFF_Write32(height); // dwVTotalInLines + FindFraction(aspect, &n, &d, 1000); + SCR_CaptureVideo_RIFF_Write32((n << 16) | d); // dwFrameAspectRatio // TODO a word + SCR_CaptureVideo_RIFF_Write32(width); // dwFrameWidthInPixels + SCR_CaptureVideo_RIFF_Write32(height); // dwFrameHeightInLines + SCR_CaptureVideo_RIFF_Write32(1); // nFieldPerFrame + SCR_CaptureVideo_RIFF_Write32(width); // CompressedBMWidth + SCR_CaptureVideo_RIFF_Write32(height); // CompressedBMHeight + SCR_CaptureVideo_RIFF_Write32(width); // ValidBMHeight + SCR_CaptureVideo_RIFF_Write32(height); // ValidBMWidth + SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffset + SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYOffset + SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffsetInT + SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYValidStartLine + SCR_CaptureVideo_RIFF_Pop(); + SCR_CaptureVideo_RIFF_Pop(); + if (cls.capturevideo.soundrate) + { + // audio stream info + SCR_CaptureVideo_RIFF_Push("LIST", "strl", format->canseek ? -1 : 12+52+8+18); + SCR_CaptureVideo_RIFF_Push("strh", "auds", 52); + SCR_CaptureVideo_RIFF_Write32(1); // stream fourcc (PCM audio, uncompressed) + SCR_CaptureVideo_RIFF_Write32(0); // flags + SCR_CaptureVideo_RIFF_Write16(0); // priority + SCR_CaptureVideo_RIFF_Write16(0); // language + SCR_CaptureVideo_RIFF_Write32(0); // initial frames + SCR_CaptureVideo_RIFF_Write32(1); // samples/second divisor + SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.soundrate)); // samples/second multiplied by divisor + SCR_CaptureVideo_RIFF_Write32(0); // start + format->videofile_totalsampleframes_offset = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); // length + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 2); // suggested buffer size (this is a half second) + SCR_CaptureVideo_RIFF_Write32(0); // quality + SCR_CaptureVideo_RIFF_Write32(4); // sample size + SCR_CaptureVideo_RIFF_Write16(0); // frame left + SCR_CaptureVideo_RIFF_Write16(0); // frame top + SCR_CaptureVideo_RIFF_Write16(0); // frame right + SCR_CaptureVideo_RIFF_Write16(0); // frame bottom + SCR_CaptureVideo_RIFF_Pop(); + // audio stream format + SCR_CaptureVideo_RIFF_Push("strf", NULL, 18); + SCR_CaptureVideo_RIFF_Write16(1); // format (uncompressed PCM?) + SCR_CaptureVideo_RIFF_Write16(2); // channels (stereo) + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate); // sampleframes per second + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 4); // average bytes per second + SCR_CaptureVideo_RIFF_Write16(4); // block align + SCR_CaptureVideo_RIFF_Write16(16); // bits per sample + SCR_CaptureVideo_RIFF_Write16(0); // size + SCR_CaptureVideo_RIFF_Pop(); + // master index + if(format->canseek) + { + SCR_CaptureVideo_RIFF_Push("indx", NULL, -1); + SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry + SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0 + format->videofile_ix_master_audio_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse + SCR_CaptureVideo_RIFF_WriteFourCC("01wb"); // dwChunkId + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1 + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2 + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3 + format->videofile_ix_master_audio_start_offset = SCR_CaptureVideo_RIFF_GetPosition(); + for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i) + SCR_CaptureVideo_RIFF_Write32(0); // fill up later + SCR_CaptureVideo_RIFF_Pop(); + } + SCR_CaptureVideo_RIFF_Pop(); + } + + format->videofile_ix_master_audio_inuse = format->videofile_ix_master_video_inuse = 0; + + // extended header (for total #frames) + SCR_CaptureVideo_RIFF_Push("LIST", "odml", 8+4); + SCR_CaptureVideo_RIFF_Push("dmlh", NULL, 4); + format->videofile_totalframes_offset2 = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); + SCR_CaptureVideo_RIFF_Pop(); + SCR_CaptureVideo_RIFF_Pop(); + + // close the AVI header list + SCR_CaptureVideo_RIFF_Pop(); + // software that produced this AVI video file + SCR_CaptureVideo_RIFF_Push("LIST", "INFO", 8+((strlen(engineversion) | 1) + 1)); + SCR_CaptureVideo_RIFF_Push("ISFT", NULL, strlen(engineversion) + 1); + SCR_CaptureVideo_RIFF_WriteTerminatedString(engineversion); + SCR_CaptureVideo_RIFF_Pop(); + // enable this junk filler if you like the LIST movi to always begin at 4KB in the file (why?) +#if 0 + SCR_CaptureVideo_RIFF_Push("JUNK", NULL); + x = 4096 - SCR_CaptureVideo_RIFF_GetPosition(); + while (x > 0) + { + const char *junkfiller = "[ DarkPlaces junk data ]"; + int i = min(x, (int)strlen(junkfiller)); + SCR_CaptureVideo_RIFF_WriteBytes((const unsigned char *)junkfiller, i); + x -= i; + } + SCR_CaptureVideo_RIFF_Pop(); +#endif + SCR_CaptureVideo_RIFF_Pop(); + // begin the actual video section now + SCR_CaptureVideo_RIFF_Push("LIST", "movi", format->canseek ? -1 : 0); + format->videofile_ix_movistart = format->riffstackstartoffset[1]; + // we're done with the headers now... + SCR_CaptureVideo_RIFF_Flush(); + if (format->riffstacklevel != 2) + Sys_Error("SCR_CaptureVideo_BeginVideo: broken AVI writing code (stack level is %i (should be 2) at end of headers)\n", format->riffstacklevel); + + if(!format->canseek) + { + // close the movi immediately + SCR_CaptureVideo_RIFF_Pop(); + // close the AVI immediately (we'll put all frames into AVIX) + SCR_CaptureVideo_RIFF_Pop(); + } + } +} diff --git a/misc/source/darkplaces-src/cap_avi.h b/misc/source/darkplaces-src/cap_avi.h new file mode 100644 index 00000000..2cf15d6a --- /dev/null +++ b/misc/source/darkplaces-src/cap_avi.h @@ -0,0 +1 @@ +void SCR_CaptureVideo_Avi_BeginVideo(void); diff --git a/misc/source/darkplaces-src/cap_ogg.c b/misc/source/darkplaces-src/cap_ogg.c new file mode 100644 index 00000000..eef3f90d --- /dev/null +++ b/misc/source/darkplaces-src/cap_ogg.c @@ -0,0 +1,1120 @@ +#ifndef _MSC_VER +#include +#endif +#include + +#include "quakedef.h" +#include "client.h" +#include "cap_ogg.h" + +// video capture cvars +static cvar_t cl_capturevideo_ogg_theora_vp3compat = {CVAR_SAVE, "cl_capturevideo_ogg_theora_vp3compat", "1", "make VP3 compatible theora streams"}; +static cvar_t cl_capturevideo_ogg_theora_quality = {CVAR_SAVE, "cl_capturevideo_ogg_theora_quality", "48", "video quality factor (0 to 63), or -1 to use bitrate only; higher is better; setting both to -1 achieves unlimited quality"}; +static cvar_t cl_capturevideo_ogg_theora_bitrate = {CVAR_SAVE, "cl_capturevideo_ogg_theora_bitrate", "-1", "video bitrate (45 to 2000 kbps), or -1 to use quality only; higher is better; setting both to -1 achieves unlimited quality"}; +static cvar_t cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier", "1.5", "how much more bit rate to use for keyframes, specified as a factor of at least 1"}; +static cvar_t cl_capturevideo_ogg_theora_keyframe_maxinterval = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_maxinterval", "64", "maximum keyframe interval (1 to 1000)"}; +static cvar_t cl_capturevideo_ogg_theora_keyframe_mininterval = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_mininterval", "8", "minimum keyframe interval (1 to 1000)"}; +static cvar_t cl_capturevideo_ogg_theora_keyframe_auto_threshold = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_auto_threshold", "80", "threshold for key frame decision (0 to 100)"}; +static cvar_t cl_capturevideo_ogg_theora_noise_sensitivity = {CVAR_SAVE, "cl_capturevideo_ogg_theora_noise_sensitivity", "1", "video noise sensitivity (0 to 6); lower is better"}; +static cvar_t cl_capturevideo_ogg_theora_sharpness = {CVAR_SAVE, "cl_capturevideo_ogg_theora_sharpness", "0", "sharpness (0 to 2); lower is sharper"}; +static cvar_t cl_capturevideo_ogg_vorbis_quality = {CVAR_SAVE, "cl_capturevideo_ogg_vorbis_quality", "3", "audio quality (-1 to 10); higher is better"}; + +// ogg.h stuff +#ifdef _MSC_VER +typedef __int16 ogg_int16_t; +typedef unsigned __int16 ogg_uint16_t; +typedef __int32 ogg_int32_t; +typedef unsigned __int32 ogg_uint32_t; +typedef __int64 ogg_int64_t; +#else +typedef int16_t ogg_int16_t; +typedef uint16_t ogg_uint16_t; +typedef int32_t ogg_int32_t; +typedef uint32_t ogg_uint32_t; +typedef int64_t ogg_int64_t; +#endif + +typedef struct { + long endbyte; + int endbit; + + unsigned char *buffer; + unsigned char *ptr; + long storage; +} oggpack_buffer; + +/* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ + +typedef struct { + unsigned char *header; + long header_len; + unsigned char *body; + long body_len; +} ogg_page; + +/* ogg_stream_state contains the current encode/decode state of a logical + Ogg bitstream **********************************************************/ + +typedef struct { + unsigned char *body_data; /* bytes from packet bodies */ + long body_storage; /* storage elements allocated */ + long body_fill; /* elements stored; fill mark */ + long body_returned; /* elements of fill returned */ + + + int *lacing_vals; /* The values that will go to the segment table */ + ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + long lacing_storage; + long lacing_fill; + long lacing_packet; + long lacing_returned; + + unsigned char header[282]; /* working space for header encode */ + int header_fill; + + int e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + int b_o_s; /* set after we've written the initial page + of a logical bitstream */ + long serialno; + long pageno; + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a seperate abstraction + layer) also knows about the gap */ + ogg_int64_t granulepos; + +} ogg_stream_state; + +/* ogg_packet is used to encapsulate the data and metadata belonging + to a single raw Ogg/Vorbis packet *************************************/ + +typedef struct { + unsigned char *packet; + long bytes; + long b_o_s; + long e_o_s; + + ogg_int64_t granulepos; + + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a seperate abstraction + layer) also knows about the gap */ +} ogg_packet; + +typedef struct { + unsigned char *data; + int storage; + int fill; + int returned; + + int unsynced; + int headerbytes; + int bodybytes; +} ogg_sync_state; + +/* Ogg BITSTREAM PRIMITIVES: encoding **************************/ + +static int (*qogg_stream_packetin) (ogg_stream_state *os, ogg_packet *op); +static int (*qogg_stream_pageout) (ogg_stream_state *os, ogg_page *og); +static int (*qogg_stream_flush) (ogg_stream_state *os, ogg_page *og); + +/* Ogg BITSTREAM PRIMITIVES: general ***************************/ + +static int (*qogg_stream_init) (ogg_stream_state *os,int serialno); +static int (*qogg_stream_clear) (ogg_stream_state *os); +static ogg_int64_t (*qogg_page_granulepos) (ogg_page *og); + +// end of ogg.h stuff + +// vorbis/codec.h stuff +typedef struct vorbis_info{ + int version; + int channels; + long rate; + + /* The below bitrate declarations are *hints*. + Combinations of the three values carry the following implications: + + all three set to the same value: + implies a fixed rate bitstream + only nominal set: + implies a VBR stream that averages the nominal bitrate. No hard + upper/lower limit + upper and or lower set: + implies a VBR bitstream that obeys the bitrate limits. nominal + may also be set to give a nominal rate. + none set: + the coder does not care to speculate. + */ + + long bitrate_upper; + long bitrate_nominal; + long bitrate_lower; + long bitrate_window; + + void *codec_setup; +} vorbis_info; + +/* vorbis_dsp_state buffers the current vorbis audio + analysis/synthesis state. The DSP state belongs to a specific + logical bitstream ****************************************************/ +typedef struct vorbis_dsp_state{ + int analysisp; + vorbis_info *vi; + + float **pcm; + float **pcmret; + int pcm_storage; + int pcm_current; + int pcm_returned; + + int preextrapolate; + int eofflag; + + long lW; + long W; + long nW; + long centerW; + + ogg_int64_t granulepos; + ogg_int64_t sequence; + + ogg_int64_t glue_bits; + ogg_int64_t time_bits; + ogg_int64_t floor_bits; + ogg_int64_t res_bits; + + void *backend_state; +} vorbis_dsp_state; + +typedef struct vorbis_block{ + /* necessary stream state for linking to the framing abstraction */ + float **pcm; /* this is a pointer into local storage */ + oggpack_buffer opb; + + long lW; + long W; + long nW; + int pcmend; + int mode; + + int eofflag; + ogg_int64_t granulepos; + ogg_int64_t sequence; + vorbis_dsp_state *vd; /* For read-only access of configuration */ + + /* local storage to avoid remallocing; it's up to the mapping to + structure it */ + void *localstore; + long localtop; + long localalloc; + long totaluse; + struct alloc_chain *reap; + + /* bitmetrics for the frame */ + long glue_bits; + long time_bits; + long floor_bits; + long res_bits; + + void *internal; + +} vorbis_block; + +/* vorbis_block is a single block of data to be processed as part of +the analysis/synthesis stream; it belongs to a specific logical +bitstream, but is independant from other vorbis_blocks belonging to +that logical bitstream. *************************************************/ + +struct alloc_chain{ + void *ptr; + struct alloc_chain *next; +}; + +/* vorbis_info contains all the setup information specific to the + specific compression/decompression mode in progress (eg, + psychoacoustic settings, channel setup, options, codebook + etc). vorbis_info and substructures are in backends.h. +*********************************************************************/ + +/* the comments are not part of vorbis_info so that vorbis_info can be + static storage */ +typedef struct vorbis_comment{ + /* unlimited user comment fields. libvorbis writes 'libvorbis' + whatever vendor is set to in encode */ + char **user_comments; + int *comment_lengths; + int comments; + char *vendor; + +} vorbis_comment; + + +/* libvorbis encodes in two abstraction layers; first we perform DSP + and produce a packet (see docs/analysis.txt). The packet is then + coded into a framed OggSquish bitstream by the second layer (see + docs/framing.txt). Decode is the reverse process; we sync/frame + the bitstream and extract individual packets, then decode the + packet back into PCM audio. + + The extra framing/packetizing is used in streaming formats, such as + files. Over the net (such as with UDP), the framing and + packetization aren't necessary as they're provided by the transport + and the streaming layer is not used */ + +/* Vorbis PRIMITIVES: general ***************************************/ + +static void (*qvorbis_info_init) (vorbis_info *vi); +static void (*qvorbis_info_clear) (vorbis_info *vi); +static void (*qvorbis_comment_init) (vorbis_comment *vc); +static void (*qvorbis_comment_clear) (vorbis_comment *vc); + +static int (*qvorbis_block_init) (vorbis_dsp_state *v, vorbis_block *vb); +static int (*qvorbis_block_clear) (vorbis_block *vb); +static void (*qvorbis_dsp_clear) (vorbis_dsp_state *v); +static double (*qvorbis_granule_time) (vorbis_dsp_state *v, + ogg_int64_t granulepos); + +/* Vorbis PRIMITIVES: analysis/DSP layer ****************************/ + +static int (*qvorbis_analysis_init) (vorbis_dsp_state *v,vorbis_info *vi); +static int (*qvorbis_commentheader_out) (vorbis_comment *vc, ogg_packet *op); +static int (*qvorbis_analysis_headerout) (vorbis_dsp_state *v, + vorbis_comment *vc, + ogg_packet *op, + ogg_packet *op_comm, + ogg_packet *op_code); +static float ** (*qvorbis_analysis_buffer) (vorbis_dsp_state *v,int vals); +static int (*qvorbis_analysis_wrote) (vorbis_dsp_state *v,int vals); +static int (*qvorbis_analysis_blockout) (vorbis_dsp_state *v,vorbis_block *vb); +static int (*qvorbis_analysis) (vorbis_block *vb,ogg_packet *op); + +static int (*qvorbis_bitrate_addblock) (vorbis_block *vb); +static int (*qvorbis_bitrate_flushpacket) (vorbis_dsp_state *vd, + ogg_packet *op); + +// end of vorbis/codec.h stuff + +// vorbisenc.h stuff +static int (*qvorbis_encode_init_vbr) (vorbis_info *vi, + long channels, + long rate, + + float base_quality /* quality level from 0. (lo) to 1. (hi) */ + ); +// end of vorbisenc.h stuff + +// theora.h stuff + +#define TH_ENCCTL_SET_VP3_COMPATIBLE (10) + +typedef struct { + int y_width; /**< Width of the Y' luminance plane */ + int y_height; /**< Height of the luminance plane */ + int y_stride; /**< Offset in bytes between successive rows */ + + int uv_width; /**< Width of the Cb and Cr chroma planes */ + int uv_height; /**< Height of the chroma planes */ + int uv_stride; /**< Offset between successive chroma rows */ + unsigned char *y; /**< Pointer to start of luminance data */ + unsigned char *u; /**< Pointer to start of Cb data */ + unsigned char *v; /**< Pointer to start of Cr data */ + +} yuv_buffer; + +/** + * A Colorspace. + */ +typedef enum { + OC_CS_UNSPECIFIED, /**< The colorspace is unknown or unspecified */ + OC_CS_ITU_REC_470M, /**< This is the best option for 'NTSC' content */ + OC_CS_ITU_REC_470BG, /**< This is the best option for 'PAL' content */ + OC_CS_NSPACES /**< This marks the end of the defined colorspaces */ +} theora_colorspace; + +/** + * A Chroma subsampling + * + * These enumerate the available chroma subsampling options supported + * by the theora format. See Section 4.4 of the specification for + * exact definitions. + */ +typedef enum { + OC_PF_420, /**< Chroma subsampling by 2 in each direction (4:2:0) */ + OC_PF_RSVD, /**< Reserved value */ + OC_PF_422, /**< Horizonatal chroma subsampling by 2 (4:2:2) */ + OC_PF_444 /**< No chroma subsampling at all (4:4:4) */ +} theora_pixelformat; +/** + * Theora bitstream info. + * Contains the basic playback parameters for a stream, + * corresponding to the initial 'info' header packet. + * + * Encoded theora frames must be a multiple of 16 in width and height. + * To handle other frame sizes, a crop rectangle is specified in + * frame_height and frame_width, offset_x and * offset_y. The offset + * and size should still be a multiple of 2 to avoid chroma sampling + * shifts. Offset values in this structure are measured from the + * upper left of the image. + * + * Frame rate, in frames per second, is stored as a rational + * fraction. Aspect ratio is also stored as a rational fraction, and + * refers to the aspect ratio of the frame pixels, not of the + * overall frame itself. + * + * See + * examples/encoder_example.c for usage examples of the + * other paramters and good default settings for the encoder parameters. + */ +typedef struct { + ogg_uint32_t width; /**< encoded frame width */ + ogg_uint32_t height; /**< encoded frame height */ + ogg_uint32_t frame_width; /**< display frame width */ + ogg_uint32_t frame_height; /**< display frame height */ + ogg_uint32_t offset_x; /**< horizontal offset of the displayed frame */ + ogg_uint32_t offset_y; /**< vertical offset of the displayed frame */ + ogg_uint32_t fps_numerator; /**< frame rate numerator **/ + ogg_uint32_t fps_denominator; /**< frame rate denominator **/ + ogg_uint32_t aspect_numerator; /**< pixel aspect ratio numerator */ + ogg_uint32_t aspect_denominator; /**< pixel aspect ratio denominator */ + theora_colorspace colorspace; /**< colorspace */ + int target_bitrate; /**< nominal bitrate in bits per second */ + int quality; /**< Nominal quality setting, 0-63 */ + int quick_p; /**< Quick encode/decode */ + + /* decode only */ + unsigned char version_major; + unsigned char version_minor; + unsigned char version_subminor; + + void *codec_setup; + + /* encode only */ + int dropframes_p; + int keyframe_auto_p; + ogg_uint32_t keyframe_frequency; + ogg_uint32_t keyframe_frequency_force; /* also used for decode init to + get granpos shift correct */ + ogg_uint32_t keyframe_data_target_bitrate; + ogg_int32_t keyframe_auto_threshold; + ogg_uint32_t keyframe_mindistance; + ogg_int32_t noise_sensitivity; + ogg_int32_t sharpness; + + theora_pixelformat pixelformat; /**< chroma subsampling mode to expect */ + +} theora_info; + +/** Codec internal state and context. + */ +typedef struct{ + theora_info *i; + ogg_int64_t granulepos; + + void *internal_encode; + void *internal_decode; + +} theora_state; + +/** + * Comment header metadata. + * + * This structure holds the in-stream metadata corresponding to + * the 'comment' header packet. + * + * Meta data is stored as a series of (tag, value) pairs, in + * length-encoded string vectors. The first occurence of the + * '=' character delimits the tag and value. A particular tag + * may occur more than once. The character set encoding for + * the strings is always UTF-8, but the tag names are limited + * to case-insensitive ASCII. See the spec for details. + * + * In filling in this structure, qtheora_decode_header() will + * null-terminate the user_comment strings for safety. However, + * the bitstream format itself treats them as 8-bit clean, + * and so the length array should be treated as authoritative + * for their length. + */ +typedef struct theora_comment{ + char **user_comments; /**< An array of comment string vectors */ + int *comment_lengths; /**< An array of corresponding string vector lengths in bytes */ + int comments; /**< The total number of comment string vectors */ + char *vendor; /**< The vendor string identifying the encoder, null terminated */ + +} theora_comment; +static int (*qtheora_encode_init) (theora_state *th, theora_info *ti); +static int (*qtheora_encode_YUVin) (theora_state *t, yuv_buffer *yuv); +static int (*qtheora_encode_packetout) ( theora_state *t, int last_p, + ogg_packet *op); +static int (*qtheora_encode_header) (theora_state *t, ogg_packet *op); +static int (*qtheora_encode_comment) (theora_comment *tc, ogg_packet *op); +static int (*qtheora_encode_tables) (theora_state *t, ogg_packet *op); +static void (*qtheora_info_init) (theora_info *c); +static void (*qtheora_info_clear) (theora_info *c); +static void (*qtheora_clear) (theora_state *t); +static void (*qtheora_comment_init) (theora_comment *tc); +static void (*qtheora_comment_clear) (theora_comment *tc); +static double (*qtheora_granule_time) (theora_state *th,ogg_int64_t granulepos); +static int (*qtheora_control) (theora_state *th,int req,void *buf,size_t buf_sz); +// end of theora.h stuff + +static dllfunction_t oggfuncs[] = +{ + {"ogg_stream_packetin", (void **) &qogg_stream_packetin}, + {"ogg_stream_pageout", (void **) &qogg_stream_pageout}, + {"ogg_stream_flush", (void **) &qogg_stream_flush}, + {"ogg_stream_init", (void **) &qogg_stream_init}, + {"ogg_stream_clear", (void **) &qogg_stream_clear}, + {"ogg_page_granulepos", (void **) &qogg_page_granulepos}, + {NULL, NULL} +}; + +static dllfunction_t vorbisencfuncs[] = +{ + {"vorbis_encode_init_vbr", (void **) &qvorbis_encode_init_vbr}, + {NULL, NULL} +}; + +static dllfunction_t vorbisfuncs[] = +{ + {"vorbis_info_init", (void **) &qvorbis_info_init}, + {"vorbis_info_clear", (void **) &qvorbis_info_clear}, + {"vorbis_comment_init", (void **) &qvorbis_comment_init}, + {"vorbis_comment_clear", (void **) &qvorbis_comment_clear}, + {"vorbis_block_init", (void **) &qvorbis_block_init}, + {"vorbis_block_clear", (void **) &qvorbis_block_clear}, + {"vorbis_dsp_clear", (void **) &qvorbis_dsp_clear}, + {"vorbis_analysis_init", (void **) &qvorbis_analysis_init}, + {"vorbis_commentheader_out", (void **) &qvorbis_commentheader_out}, + {"vorbis_analysis_headerout", (void **) &qvorbis_analysis_headerout}, + {"vorbis_analysis_buffer", (void **) &qvorbis_analysis_buffer}, + {"vorbis_analysis_wrote", (void **) &qvorbis_analysis_wrote}, + {"vorbis_analysis_blockout", (void **) &qvorbis_analysis_blockout}, + {"vorbis_analysis", (void **) &qvorbis_analysis}, + {"vorbis_bitrate_addblock", (void **) &qvorbis_bitrate_addblock}, + {"vorbis_bitrate_flushpacket", (void **) &qvorbis_bitrate_flushpacket}, + {"vorbis_granule_time", (void **) &qvorbis_granule_time}, + {NULL, NULL} +}; + +static dllfunction_t theorafuncs[] = +{ + {"theora_info_init", (void **) &qtheora_info_init}, + {"theora_info_clear", (void **) &qtheora_info_clear}, + {"theora_comment_init", (void **) &qtheora_comment_init}, + {"theora_comment_clear", (void **) &qtheora_comment_clear}, + {"theora_encode_init", (void **) &qtheora_encode_init}, + {"theora_encode_YUVin", (void **) &qtheora_encode_YUVin}, + {"theora_encode_packetout", (void **) &qtheora_encode_packetout}, + {"theora_encode_header", (void **) &qtheora_encode_header}, + {"theora_encode_comment", (void **) &qtheora_encode_comment}, + {"theora_encode_tables", (void **) &qtheora_encode_tables}, + {"theora_clear", (void **) &qtheora_clear}, + {"theora_granule_time", (void **) &qtheora_granule_time}, + {"theora_control", (void **) &qtheora_control}, + {NULL, NULL} +}; + +static dllhandle_t og_dll = NULL, vo_dll = NULL, ve_dll = NULL, th_dll = NULL; + +qboolean SCR_CaptureVideo_Ogg_OpenLibrary(void) +{ + const char* dllnames_og [] = + { +#if defined(WIN32) + "libogg-0.dll", + "libogg.dll", + "ogg.dll", +#elif defined(MACOSX) + "libogg.dylib", +#else + "libogg.so.0", + "libogg.so", +#endif + NULL + }; + const char* dllnames_vo [] = + { +#if defined(WIN32) + "libvorbis-0.dll", + "libvorbis.dll", + "vorbis.dll", +#elif defined(MACOSX) + "libvorbis.dylib", +#else + "libvorbis.so.0", + "libvorbis.so", +#endif + NULL + }; + const char* dllnames_ve [] = + { +#if defined(WIN32) + "libvorbisenc-2.dll", + "libvorbisenc.dll", + "vorbisenc.dll", +#elif defined(MACOSX) + "libvorbisenc.dylib", +#else + "libvorbisenc.so.2", + "libvorbisenc.so", +#endif + NULL + }; + const char* dllnames_th [] = + { +#if defined(WIN32) + "libtheora-0.dll", + "libtheora.dll", + "theora.dll", +#elif defined(MACOSX) + "libtheora.dylib", +#else + "libtheora.so.0", + "libtheora.so", +#endif + NULL + }; + + return + Sys_LoadLibrary (dllnames_og, &og_dll, oggfuncs) + && + Sys_LoadLibrary (dllnames_th, &th_dll, theorafuncs) + && + Sys_LoadLibrary (dllnames_vo, &vo_dll, vorbisfuncs) + && + Sys_LoadLibrary (dllnames_ve, &ve_dll, vorbisencfuncs); +} + +void SCR_CaptureVideo_Ogg_Init(void) +{ + SCR_CaptureVideo_Ogg_OpenLibrary(); + + Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_vp3compat); + Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_quality); + Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_bitrate); + Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier); + Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_maxinterval); + Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_mininterval); + Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_auto_threshold); + Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_noise_sensitivity); + Cvar_RegisterVariable(&cl_capturevideo_ogg_vorbis_quality); +} + +qboolean SCR_CaptureVideo_Ogg_Available(void) +{ + return og_dll && th_dll && vo_dll && ve_dll; +} + +void SCR_CaptureVideo_Ogg_CloseDLL(void) +{ + Sys_UnloadLibrary (&ve_dll); + Sys_UnloadLibrary (&vo_dll); + Sys_UnloadLibrary (&th_dll); + Sys_UnloadLibrary (&og_dll); +} + +// this struct should not be needed +// however, libogg appears to pull the ogg_page's data element away from our +// feet before we get to write the data due to interleaving +// so this struct is used to keep the page data around until it actually gets +// written +typedef struct allocatedoggpage_s +{ + size_t len; + double time; + unsigned char data[65307]; + // this number is from RFC 3533. In case libogg writes more, we'll have to increase this + // but we'll get a Host_Error in this case so we can track it down +} +allocatedoggpage_t; + +typedef struct capturevideostate_ogg_formatspecific_s +{ + ogg_stream_state to, vo; + int serial1, serial2; + theora_state ts; + vorbis_dsp_state vd; + vorbis_block vb; + vorbis_info vi; + yuv_buffer yuv[2]; + int yuvi; + int lastnum; + int channels; + + allocatedoggpage_t videopage, audiopage; +} +capturevideostate_ogg_formatspecific_t; +#define LOAD_FORMATSPECIFIC_OGG() capturevideostate_ogg_formatspecific_t *format = (capturevideostate_ogg_formatspecific_t *) cls.capturevideo.formatspecific + +static void SCR_CaptureVideo_Ogg_Interleave(void) +{ + LOAD_FORMATSPECIFIC_OGG(); + ogg_page pg; + + if(!cls.capturevideo.soundrate) + { + while(qogg_stream_pageout(&format->to, &pg) > 0) + { + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + } + return; + } + + for(;;) + { + // first: make sure we have a page of both types + if(!format->videopage.len) + if(qogg_stream_pageout(&format->to, &pg) > 0) + { + format->videopage.len = pg.header_len + pg.body_len; + format->videopage.time = qtheora_granule_time(&format->ts, qogg_page_granulepos(&pg)); + if(format->videopage.len > sizeof(format->videopage.data)) + Host_Error("video page too long"); + memcpy(format->videopage.data, pg.header, pg.header_len); + memcpy(format->videopage.data + pg.header_len, pg.body, pg.body_len); + } + if(!format->audiopage.len) + if(qogg_stream_pageout(&format->vo, &pg) > 0) + { + format->audiopage.len = pg.header_len + pg.body_len; + format->audiopage.time = qvorbis_granule_time(&format->vd, qogg_page_granulepos(&pg)); + if(format->audiopage.len > sizeof(format->audiopage.data)) + Host_Error("audio page too long"); + memcpy(format->audiopage.data, pg.header, pg.header_len); + memcpy(format->audiopage.data + pg.header_len, pg.body, pg.body_len); + } + + if(format->videopage.len && format->audiopage.len) + { + // output the page that ends first + if(format->videopage.time < format->audiopage.time) + { + FS_Write(cls.capturevideo.videofile, format->videopage.data, format->videopage.len); + format->videopage.len = 0; + } + else + { + FS_Write(cls.capturevideo.videofile, format->audiopage.data, format->audiopage.len); + format->audiopage.len = 0; + } + } + else + break; + } +} + +static void SCR_CaptureVideo_Ogg_FlushInterleaving(void) +{ + LOAD_FORMATSPECIFIC_OGG(); + + if(cls.capturevideo.soundrate) + if(format->audiopage.len) + { + FS_Write(cls.capturevideo.videofile, format->audiopage.data, format->audiopage.len); + format->audiopage.len = 0; + } + + if(format->videopage.len) + { + FS_Write(cls.capturevideo.videofile, format->videopage.data, format->videopage.len); + format->videopage.len = 0; + } +} + +static void SCR_CaptureVideo_Ogg_EndVideo(void) +{ + LOAD_FORMATSPECIFIC_OGG(); + ogg_page pg; + ogg_packet pt; + + if(format->yuvi >= 0) + { + // send the previous (and last) frame + while(format->lastnum-- > 0) + { + qtheora_encode_YUVin(&format->ts, &format->yuv[format->yuvi]); + + while(qtheora_encode_packetout(&format->ts, !format->lastnum, &pt)) + qogg_stream_packetin(&format->to, &pt); + + SCR_CaptureVideo_Ogg_Interleave(); + } + } + + if(cls.capturevideo.soundrate) + { + qvorbis_analysis_wrote(&format->vd, 0); + while(qvorbis_analysis_blockout(&format->vd, &format->vb) == 1) + { + qvorbis_analysis(&format->vb, NULL); + qvorbis_bitrate_addblock(&format->vb); + while(qvorbis_bitrate_flushpacket(&format->vd, &pt)) + qogg_stream_packetin(&format->vo, &pt); + SCR_CaptureVideo_Ogg_Interleave(); + } + } + + SCR_CaptureVideo_Ogg_FlushInterleaving(); + + while(qogg_stream_pageout(&format->to, &pg) > 0) + { + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + } + + if(cls.capturevideo.soundrate) + { + while(qogg_stream_pageout(&format->vo, &pg) > 0) + { + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + } + } + + while (1) { + int result = qogg_stream_flush (&format->to, &pg); + if (result < 0) + fprintf (stderr, "Internal Ogg library error.\n"); // TODO Host_Error + if (result <= 0) + break; + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + } + + if(cls.capturevideo.soundrate) + { + while (1) { + int result = qogg_stream_flush (&format->vo, &pg); + if (result < 0) + fprintf (stderr, "Internal Ogg library error.\n"); // TODO Host_Error + if (result <= 0) + break; + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + } + + qogg_stream_clear(&format->vo); + qvorbis_block_clear(&format->vb); + qvorbis_dsp_clear(&format->vd); + } + + qogg_stream_clear(&format->to); + qtheora_clear(&format->ts); + qvorbis_info_clear(&format->vi); + + Mem_Free(format->yuv[0].y); + Mem_Free(format->yuv[0].u); + Mem_Free(format->yuv[0].v); + Mem_Free(format->yuv[1].y); + Mem_Free(format->yuv[1].u); + Mem_Free(format->yuv[1].v); + Mem_Free(format); + + FS_Close(cls.capturevideo.videofile); + cls.capturevideo.videofile = NULL; +} + +static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(void) +{ + LOAD_FORMATSPECIFIC_OGG(); + yuv_buffer *yuv; + int x, y; + int blockr, blockg, blockb; + unsigned char *b = cls.capturevideo.outbuffer; + int w = cls.capturevideo.width; + int h = cls.capturevideo.height; + int inpitch = w*4; + + yuv = &format->yuv[format->yuvi]; + + for(y = 0; y < h; ++y) + { + for(b = cls.capturevideo.outbuffer + (h-1-y)*w*4, x = 0; x < w; ++x) + { + blockr = b[2]; + blockg = b[1]; + blockb = b[0]; + yuv->y[x + yuv->y_stride * y] = + cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]]; + b += 4; + } + + if((y & 1) == 0) + { + for(b = cls.capturevideo.outbuffer + (h-2-y)*w*4, x = 0; x < w/2; ++x) + { + blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2; + blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2; + blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2; + yuv->u[x + yuv->uv_stride * (y/2)] = + cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128]; + yuv->v[x + yuv->uv_stride * (y/2)] = + cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128]; + b += 8; + } + } + } +} + +static void SCR_CaptureVideo_Ogg_VideoFrames(int num) +{ + LOAD_FORMATSPECIFIC_OGG(); + ogg_packet pt; + + // data is in cls.capturevideo.outbuffer as BGRA and has size width*height + + if(format->yuvi >= 0) + { + // send the previous frame + while(format->lastnum-- > 0) + { + qtheora_encode_YUVin(&format->ts, &format->yuv[format->yuvi]); + + while(qtheora_encode_packetout(&format->ts, false, &pt)) + qogg_stream_packetin(&format->to, &pt); + + SCR_CaptureVideo_Ogg_Interleave(); + } + } + + format->yuvi = (format->yuvi + 1) % 2; + SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(); + format->lastnum = num; + + // TODO maybe send num-1 frames from here already +} + +typedef int channelmapping_t[8]; +channelmapping_t mapping[8] = +{ + { 0, -1, -1, -1, -1, -1, -1, -1 }, // mono + { 0, 1, -1, -1, -1, -1, -1, -1 }, // stereo + { 0, 1, 2, -1, -1, -1, -1, -1 }, // L C R + { 0, 1, 2, 3, -1, -1, -1, -1 }, // surround40 + { 0, 2, 3, 4, 1, -1, -1, -1 }, // FL FC FR RL RR + { 0, 2, 3, 4, 1, 5, -1, -1 }, // surround51 + { 0, 2, 3, 4, 1, 5, 6, -1 }, // (not defined by vorbis spec) + { 0, 2, 3, 4, 1, 5, 6, 7 } // surround71 (not defined by vorbis spec) +}; + +static void SCR_CaptureVideo_Ogg_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length) +{ + LOAD_FORMATSPECIFIC_OGG(); + float **vorbis_buffer; + size_t i; + int j; + ogg_packet pt; + int *map = mapping[bound(1, cls.capturevideo.soundchannels, 8) - 1]; + + vorbis_buffer = qvorbis_analysis_buffer(&format->vd, length); + for(j = 0; j < cls.capturevideo.soundchannels; ++j) + { + float *b = vorbis_buffer[map[j]]; + for(i = 0; i < length; ++i) + b[i] = paintbuffer[i].sample[j] / 32768.0f; + } + qvorbis_analysis_wrote(&format->vd, length); + + while(qvorbis_analysis_blockout(&format->vd, &format->vb) == 1) + { + qvorbis_analysis(&format->vb, NULL); + qvorbis_bitrate_addblock(&format->vb); + + while(qvorbis_bitrate_flushpacket(&format->vd, &pt)) + qogg_stream_packetin(&format->vo, &pt); + } + + SCR_CaptureVideo_Ogg_Interleave(); +} + +void SCR_CaptureVideo_Ogg_BeginVideo(void) +{ + cls.capturevideo.format = CAPTUREVIDEOFORMAT_OGG_VORBIS_THEORA; + cls.capturevideo.formatextension = "ogv"; + cls.capturevideo.videofile = FS_OpenRealFile(va("%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false); + cls.capturevideo.endvideo = SCR_CaptureVideo_Ogg_EndVideo; + cls.capturevideo.videoframes = SCR_CaptureVideo_Ogg_VideoFrames; + cls.capturevideo.soundframe = SCR_CaptureVideo_Ogg_SoundFrame; + cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_ogg_formatspecific_t)); + { + LOAD_FORMATSPECIFIC_OGG(); + int num, denom, i; + ogg_page pg; + ogg_packet pt, pt2, pt3; + theora_comment tc; + vorbis_comment vc; + theora_info ti; + int vp3compat; + + format->serial1 = rand(); + qogg_stream_init(&format->to, format->serial1); + + if(cls.capturevideo.soundrate) + { + do + { + format->serial2 = rand(); + } + while(format->serial1 == format->serial2); + qogg_stream_init(&format->vo, format->serial2); + } + + format->videopage.len = format->audiopage.len = 0; + + qtheora_info_init(&ti); + ti.frame_width = cls.capturevideo.width; + ti.frame_height = cls.capturevideo.height; + ti.width = (ti.frame_width + 15) & ~15; + ti.height = (ti.frame_height + 15) & ~15; + //ti.offset_x = ((ti.width - ti.frame_width) / 2) & ~1; + //ti.offset_y = ((ti.height - ti.frame_height) / 2) & ~1; + + for(i = 0; i < 2; ++i) + { + format->yuv[i].y_width = ti.width; + format->yuv[i].y_height = ti.height; + format->yuv[i].y_stride = ti.width; + format->yuv[i].uv_width = ti.width / 2; + format->yuv[i].uv_height = ti.height / 2; + format->yuv[i].uv_stride = ti.width / 2; + format->yuv[i].y = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].y_stride * format->yuv[i].y_height); + format->yuv[i].u = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].uv_stride * format->yuv[i].uv_height); + format->yuv[i].v = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].uv_stride * format->yuv[i].uv_height); + } + format->yuvi = -1; // -1: no frame valid yet, write into 0 + + FindFraction(cls.capturevideo.framerate / cls.capturevideo.framestep, &num, &denom, 1001); + ti.fps_numerator = num; + ti.fps_denominator = denom; + + FindFraction(1 / vid_pixelheight.value, &num, &denom, 1000); + ti.aspect_numerator = num; + ti.aspect_denominator = denom; + + ti.colorspace = OC_CS_UNSPECIFIED; + ti.pixelformat = OC_PF_420; + + ti.quick_p = true; // http://mlblog.osdir.com/multimedia.ogg.theora.general/2004-07/index.shtml + ti.dropframes_p = false; + + ti.target_bitrate = cl_capturevideo_ogg_theora_bitrate.integer * 1000; + ti.quality = cl_capturevideo_ogg_theora_quality.integer; + + if(ti.target_bitrate <= 0) + { + ti.target_bitrate = -1; + ti.keyframe_data_target_bitrate = (unsigned int)-1; + } + else + { + ti.keyframe_data_target_bitrate = (int) (ti.target_bitrate * max(1, cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier.value)); + + if(ti.target_bitrate < 45000 || ti.target_bitrate > 2000000) + Con_DPrintf("WARNING: requesting an odd bitrate for theora (sensible values range from 45 to 2000 kbps)\n"); + } + + if(ti.quality < 0 || ti.quality > 63) + { + ti.quality = 63; + if(ti.target_bitrate <= 0) + { + ti.target_bitrate = 0x7FFFFFFF; + ti.keyframe_data_target_bitrate = 0x7FFFFFFF; + } + } + + // this -1 magic is because ti.keyframe_frequency and ti.keyframe_mindistance use different metrics + ti.keyframe_frequency = bound(1, cl_capturevideo_ogg_theora_keyframe_maxinterval.integer, 1000); + ti.keyframe_mindistance = bound(1, cl_capturevideo_ogg_theora_keyframe_mininterval.integer, (int) ti.keyframe_frequency) - 1; + ti.noise_sensitivity = bound(0, cl_capturevideo_ogg_theora_noise_sensitivity.integer, 6); + ti.sharpness = bound(0, cl_capturevideo_ogg_theora_sharpness.integer, 2); + ti.keyframe_auto_threshold = bound(0, cl_capturevideo_ogg_theora_keyframe_auto_threshold.integer, 100); + + ti.keyframe_frequency_force = ti.keyframe_frequency; + ti.keyframe_auto_p = (ti.keyframe_frequency != ti.keyframe_mindistance + 1); + + qtheora_encode_init(&format->ts, &ti); + qtheora_info_clear(&ti); + + if(cl_capturevideo_ogg_theora_vp3compat.integer) + { + vp3compat = 1; + qtheora_control(&format->ts, TH_ENCCTL_SET_VP3_COMPATIBLE, &vp3compat, sizeof(vp3compat)); + if(!vp3compat) + Con_DPrintf("Warning: theora stream is not fully VP3 compatible\n"); + } + + // vorbis? + if(cls.capturevideo.soundrate) + { + qvorbis_info_init(&format->vi); + qvorbis_encode_init_vbr(&format->vi, cls.capturevideo.soundchannels, cls.capturevideo.soundrate, bound(-1, cl_capturevideo_ogg_vorbis_quality.value, 10) * 0.099); + qvorbis_comment_init(&vc); + qvorbis_analysis_init(&format->vd, &format->vi); + qvorbis_block_init(&format->vd, &format->vb); + } + + qtheora_comment_init(&tc); + + /* create the remaining theora headers */ + qtheora_encode_header(&format->ts, &pt); + qogg_stream_packetin(&format->to, &pt); + if (qogg_stream_pageout (&format->to, &pg) != 1) + fprintf (stderr, "Internal Ogg library error.\n"); + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + + qtheora_encode_comment(&tc, &pt); + qogg_stream_packetin(&format->to, &pt); + qtheora_encode_tables(&format->ts, &pt); + qogg_stream_packetin (&format->to, &pt); + + qtheora_comment_clear(&tc); + + if(cls.capturevideo.soundrate) + { + qvorbis_analysis_headerout(&format->vd, &vc, &pt, &pt2, &pt3); + qogg_stream_packetin(&format->vo, &pt); + if (qogg_stream_pageout (&format->vo, &pg) != 1) + fprintf (stderr, "Internal Ogg library error.\n"); + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + + qogg_stream_packetin(&format->vo, &pt2); + qogg_stream_packetin(&format->vo, &pt3); + + qvorbis_comment_clear(&vc); + } + + for(;;) + { + int result = qogg_stream_flush (&format->to, &pg); + if (result < 0) + fprintf (stderr, "Internal Ogg library error.\n"); // TODO Host_Error + if (result <= 0) + break; + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + } + + if(cls.capturevideo.soundrate) + for(;;) + { + int result = qogg_stream_flush (&format->vo, &pg); + if (result < 0) + fprintf (stderr, "Internal Ogg library error.\n"); // TODO Host_Error + if (result <= 0) + break; + FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); + FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); + } + } +} diff --git a/misc/source/darkplaces-src/cap_ogg.h b/misc/source/darkplaces-src/cap_ogg.h new file mode 100644 index 00000000..81718f2a --- /dev/null +++ b/misc/source/darkplaces-src/cap_ogg.h @@ -0,0 +1,4 @@ +void SCR_CaptureVideo_Ogg_Init(void); +qboolean SCR_CaptureVideo_Ogg_Available(void); +void SCR_CaptureVideo_Ogg_BeginVideo(void); +void SCR_CaptureVideo_Ogg_CloseDLL(void); diff --git a/misc/source/darkplaces-src/cd_bsd.c b/misc/source/darkplaces-src/cd_bsd.c new file mode 100644 index 00000000..7b3a855a --- /dev/null +++ b/misc/source/darkplaces-src/cd_bsd.c @@ -0,0 +1,283 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#ifndef __FreeBSD__ +# include +#endif + +#include "cdaudio.h" + + +#ifndef __FreeBSD__ +# define DEFAULT_CD_DEVICE _PATH_DEV "cd0" +#else +# define DEFAULT_CD_DEVICE "/dev/acd0c" +#endif + +static int cdfile = -1; +static char cd_dev[64] = DEFAULT_CD_DEVICE; + + +void CDAudio_SysEject (void) +{ + if (cdfile == -1) + return; + + ioctl(cdfile, CDIOCALLOW); + if (ioctl(cdfile, CDIOCEJECT) == -1) + Con_Print("ioctl CDIOCEJECT failed\n"); +} + + +void CDAudio_SysCloseDoor (void) +{ + if (cdfile == -1) + return; + + ioctl(cdfile, CDIOCALLOW); + if (ioctl(cdfile, CDIOCCLOSE) == -1) + Con_Print("ioctl CDIOCCLOSE failed\n"); +} + +int CDAudio_SysGetAudioDiskInfo (void) +{ + struct ioc_toc_header tochdr; + + if (cdfile == -1) + return -1; + + if (ioctl(cdfile, CDIOREADTOCHEADER, &tochdr) == -1) + { + Con_Print("ioctl CDIOREADTOCHEADER failed\n"); + return -1; + } + + if (tochdr.starting_track < 1) + { + Con_Print("CDAudio: no music tracks\n"); + return -1; + } + + return tochdr.ending_track; +} + + +float CDAudio_SysGetVolume (void) +{ + struct ioc_vol vol; + + if (cdfile == -1) + return -1.0f; + + if (ioctl (cdfile, CDIOCGETVOL, &vol) == -1) + { + Con_Print("ioctl CDIOCGETVOL failed\n"); + return -1.0f; + } + + return (vol.vol[0] + vol.vol[1]) / 2.0f / 255.0f; +} + + +void CDAudio_SysSetVolume (float volume) +{ + struct ioc_vol vol; + + if (cdfile == -1) + return; + + vol.vol[0] = vol.vol[1] = volume * 255; + vol.vol[2] = vol.vol[3] = 0; + + if (ioctl (cdfile, CDIOCSETVOL, &vol) == -1) + Con_Printf ("ioctl CDIOCSETVOL failed\n"); +} + + +int CDAudio_SysPlay (int track) +{ + struct ioc_read_toc_entry rte; + struct cd_toc_entry entry; + struct ioc_play_track ti; + + if (cdfile == -1) + return -1; + + // don't try to play a non-audio track + rte.address_format = CD_MSF_FORMAT; + rte.starting_track = track; + rte.data_len = sizeof(entry); + rte.data = &entry; + if (ioctl(cdfile, CDIOREADTOCENTRYS, &rte) == -1) + { + Con_Print("ioctl CDIOREADTOCENTRYS failed\n"); + return -1; + } + if (entry.control & 4) // if it's a data track + { + Con_Printf("CDAudio: track %i is not audio\n", track); + return -1; + } + + if (cdPlaying) + CDAudio_Stop(); + + ti.start_track = track; + ti.end_track = track; + ti.start_index = 1; + ti.end_index = 99; + + if (ioctl(cdfile, CDIOCPLAYTRACKS, &ti) == -1) + { + Con_Print("ioctl CDIOCPLAYTRACKS failed\n"); + return -1; + } + + if (ioctl(cdfile, CDIOCRESUME) == -1) + { + Con_Print("ioctl CDIOCRESUME failed\n"); + return -1; + } + + return 0; +} + + +int CDAudio_SysStop (void) +{ + if (cdfile == -1) + return -1; + + if (ioctl(cdfile, CDIOCSTOP) == -1) + { + Con_Printf("ioctl CDIOCSTOP failed (%d)\n", errno); + return -1; + } + ioctl(cdfile, CDIOCALLOW); + + return 0; +} + +int CDAudio_SysPause (void) +{ + if (cdfile == -1) + return -1; + + if (ioctl(cdfile, CDIOCPAUSE) == -1) + { + Con_Print("ioctl CDIOCPAUSE failed\n"); + return -1; + } + + return 0; +} + + +int CDAudio_SysResume (void) +{ + if (cdfile == -1) + return -1; + + if (ioctl(cdfile, CDIOCRESUME) == -1) + Con_Print("ioctl CDIOCRESUME failed\n"); + + return 0; +} + +int CDAudio_SysUpdate (void) +{ + static time_t lastchk = 0; + struct ioc_read_subchannel subchnl; + struct cd_sub_channel_info data; + + if (cdPlaying && lastchk < time(NULL)) + { + lastchk = time(NULL) + 2; //two seconds between chks + + bzero(&subchnl, sizeof(subchnl)); + subchnl.data = &data; + subchnl.data_len = sizeof(data); + subchnl.address_format = CD_MSF_FORMAT; + subchnl.data_format = CD_CURRENT_POSITION; + + if (ioctl(cdfile, CDIOCREADSUBCHANNEL, &subchnl) == -1) + { + Con_Print("ioctl CDIOCREADSUBCHANNEL failed\n"); + cdPlaying = false; + return -1; + } + if (data.header.audio_status != CD_AS_PLAY_IN_PROGRESS && + data.header.audio_status != CD_AS_PLAY_PAUSED) + { + cdPlaying = false; + if (cdPlayLooping) + CDAudio_Play(cdPlayTrack, true); + } + else + cdPlayTrack = data.what.position.track_number; + } + + return 0; +} + +void CDAudio_SysInit (void) +{ + int i; + +// COMMANDLINEOPTION: BSD Sound: -cddev chooses which CD drive to use + if ((i = COM_CheckParm("-cddev")) != 0 && i < com_argc - 1) + strlcpy(cd_dev, com_argv[i + 1], sizeof(cd_dev)); +} + +int CDAudio_SysStartup (void) +{ +#ifndef __FreeBSD__ + char buff [80]; + + if ((cdfile = opendisk(cd_dev, O_RDONLY, buff, sizeof(buff), 0)) == -1) +#else + if ((cdfile = open(cd_dev, O_RDONLY)) < 0) +#endif + { + Con_Printf("CDAudio_SysStartup: open of \"%s\" failed (%i)\n", + cd_dev, errno); + cdfile = -1; + return -1; + } + + return 0; +} + +void CDAudio_SysShutdown (void) +{ + close(cdfile); + cdfile = -1; +} diff --git a/misc/source/darkplaces-src/cd_linux.c b/misc/source/darkplaces-src/cd_linux.c new file mode 100644 index 00000000..bc07b849 --- /dev/null +++ b/misc/source/darkplaces-src/cd_linux.c @@ -0,0 +1,256 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. + +// suggested by Zero_Dogg to fix a compile problem on Mandriva Linux +#include "quakedef.h" + +#include +#include + +#include +#include +#include +#include + +#include "cdaudio.h" + + +static int cdfile = -1; +static char cd_dev[64] = "/dev/cdrom"; + + +void CDAudio_SysEject (void) +{ + if (cdfile == -1) + return; + + if (ioctl(cdfile, CDROMEJECT) == -1) + Con_Print("ioctl CDROMEJECT failed\n"); +} + + +void CDAudio_SysCloseDoor (void) +{ + if (cdfile == -1) + return; + + if (ioctl(cdfile, CDROMCLOSETRAY) == -1) + Con_Print("ioctl CDROMCLOSETRAY failed\n"); +} + +int CDAudio_SysGetAudioDiskInfo (void) +{ + struct cdrom_tochdr tochdr; + + if (cdfile == -1) + return -1; + + if (ioctl(cdfile, CDROMREADTOCHDR, &tochdr) == -1) + { + Con_Print("ioctl CDROMREADTOCHDR failed\n"); + return -1; + } + + if (tochdr.cdth_trk0 < 1) + { + Con_Print("CDAudio: no music tracks\n"); + return -1; + } + + return tochdr.cdth_trk1; +} + + +float CDAudio_SysGetVolume (void) +{ + struct cdrom_volctrl vol; + + if (cdfile == -1) + return -1.0f; + + if (ioctl (cdfile, CDROMVOLREAD, &vol) == -1) + { + Con_Print("ioctl CDROMVOLREAD failed\n"); + return -1.0f; + } + + return (vol.channel0 + vol.channel1) / 2.0f / 255.0f; +} + + +void CDAudio_SysSetVolume (float volume) +{ + struct cdrom_volctrl vol; + + if (cdfile == -1) + return; + + vol.channel0 = vol.channel1 = (__u8)(volume * 255); + vol.channel2 = vol.channel3 = 0; + + if (ioctl (cdfile, CDROMVOLCTRL, &vol) == -1) + Con_Print("ioctl CDROMVOLCTRL failed\n"); +} + + +int CDAudio_SysPlay (int track) +{ + struct cdrom_tocentry entry; + struct cdrom_ti ti; + + if (cdfile == -1) + return -1; + + // don't try to play a non-audio track + entry.cdte_track = track; + entry.cdte_format = CDROM_MSF; + if (ioctl(cdfile, CDROMREADTOCENTRY, &entry) == -1) + { + Con_Print("ioctl CDROMREADTOCENTRY failed\n"); + return -1; + } + if (entry.cdte_ctrl == CDROM_DATA_TRACK) + { + Con_Printf("CDAudio: track %i is not audio\n", track); + return -1; + } + + if (cdPlaying) + CDAudio_Stop(); + + ti.cdti_trk0 = track; + ti.cdti_trk1 = track; + ti.cdti_ind0 = 1; + ti.cdti_ind1 = 99; + + if (ioctl(cdfile, CDROMPLAYTRKIND, &ti) == -1) + { + Con_Print("ioctl CDROMPLAYTRKIND failed\n"); + return -1; + } + + if (ioctl(cdfile, CDROMRESUME) == -1) + { + Con_Print("ioctl CDROMRESUME failed\n"); + return -1; + } + + return 0; +} + + +int CDAudio_SysStop (void) +{ + if (cdfile == -1) + return -1; + + if (ioctl(cdfile, CDROMSTOP) == -1) + { + Con_Printf("ioctl CDROMSTOP failed (%d)\n", errno); + return -1; + } + + return 0; +} + +int CDAudio_SysPause (void) +{ + if (cdfile == -1) + return -1; + + if (ioctl(cdfile, CDROMPAUSE) == -1) + { + Con_Print("ioctl CDROMPAUSE failed\n"); + return -1; + } + + return 0; +} + + +int CDAudio_SysResume (void) +{ + if (cdfile == -1) + return -1; + + if (ioctl(cdfile, CDROMRESUME) == -1) + Con_Print("ioctl CDROMRESUME failed\n"); + + return 0; +} + +int CDAudio_SysUpdate (void) +{ + struct cdrom_subchnl subchnl; + static time_t lastchk = 0; + + if (cdPlaying && lastchk < time(NULL) && cdfile != -1) + { + lastchk = time(NULL) + 2; //two seconds between chks + subchnl.cdsc_format = CDROM_MSF; + if (ioctl(cdfile, CDROMSUBCHNL, &subchnl) == -1) + { + Con_Print("ioctl CDROMSUBCHNL failed\n"); + cdPlaying = false; + return -1; + } + if (subchnl.cdsc_audiostatus != CDROM_AUDIO_PLAY && + subchnl.cdsc_audiostatus != CDROM_AUDIO_PAUSED) + { + cdPlaying = false; + if (cdPlayLooping) + CDAudio_Play(cdPlayTrack, true); + } + else + cdPlayTrack = subchnl.cdsc_trk; + } + + return 0; +} + +void CDAudio_SysInit (void) +{ + int i; + +// COMMANDLINEOPTION: Linux Sound: -cddev chooses which CD drive to use + if ((i = COM_CheckParm("-cddev")) != 0 && i < com_argc - 1) + strlcpy(cd_dev, com_argv[i + 1], sizeof(cd_dev)); +} + +int CDAudio_SysStartup (void) +{ + if ((cdfile = open(cd_dev, O_RDONLY | O_NONBLOCK)) == -1) + { + Con_Printf("CDAudio_SysStartup: open of \"%s\" failed (%i)\n", + cd_dev, errno); + cdfile = -1; + return -1; + } + + return 0; +} + +void CDAudio_SysShutdown (void) +{ + close(cdfile); + cdfile = -1; +} diff --git a/misc/source/darkplaces-src/cd_null.c b/misc/source/darkplaces-src/cd_null.c new file mode 100644 index 00000000..18987ada --- /dev/null +++ b/misc/source/darkplaces-src/cd_null.c @@ -0,0 +1,90 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" + + +void CDAudio_SysEject (void) +{ +} + + +void CDAudio_SysCloseDoor (void) +{ +} + + +int CDAudio_SysGetAudioDiskInfo (void) +{ + return -1; +} + + +float CDAudio_SysGetVolume (void) +{ + return -1.0f; +} + + +void CDAudio_SysSetVolume (float volume) +{ +} + + +int CDAudio_SysPlay (int track) +{ + return -1; +} + + +int CDAudio_SysStop (void) +{ + return -1; +} + + +int CDAudio_SysPause (void) +{ + return -1; +} + +int CDAudio_SysResume (void) +{ + return -1; +} + +int CDAudio_SysUpdate (void) +{ + return -1; +} + + +void CDAudio_SysInit (void) +{ +} + +int CDAudio_SysStartup (void) +{ + return -1; +} + +void CDAudio_SysShutdown (void) +{ +} diff --git a/misc/source/darkplaces-src/cd_sdl.c b/misc/source/darkplaces-src/cd_sdl.c new file mode 100644 index 00000000..9674e8d3 --- /dev/null +++ b/misc/source/darkplaces-src/cd_sdl.c @@ -0,0 +1,283 @@ +/* +Copyright (C) 2004 Andreas Kirsch (used cd_null.c as template) +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "cdaudio.h" + +#if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2 +// SDL 1.2 has CD audio + +#include +#include + +// If one of the functions fails, it returns -1, if not 0 + +// SDL supports multiple cd devices - so we are going to support this, too. +static void CDAudio_SDL_CDDrive_f( void ); + +// we only support playing one CD at a time +static SDL_CD *cd; + +static int ValidateDrive( void ) +{ + if( cd && SDL_CDStatus( cd ) > 0 ) + return cdValid = true; + + return cdValid = false; +} + +void CDAudio_SysEject (void) +{ + SDL_CDEject( cd ); +} + + +void CDAudio_SysCloseDoor (void) +{ + //NO SDL FUNCTION +} + +int CDAudio_SysGetAudioDiskInfo (void) +{ + if( ValidateDrive() ) // everything > 0 is ok, 0 is trayempty and -1 is error + return cd->numtracks; + return -1; +} + +float CDAudio_SysGetVolume (void) +{ + return -1.0f; +} + +void CDAudio_SysSetVolume (float volume) +{ + //NO SDL FUNCTION +} + +int CDAudio_SysPlay (int track) +{ + return SDL_CDPlayTracks(cd, track-1, 0, 1, 0); +} + +int CDAudio_SysStop (void) +{ + return SDL_CDStop( cd ); +} + +int CDAudio_SysPause (void) +{ + return SDL_CDPause( cd ); +} + +int CDAudio_SysResume (void) +{ + return SDL_CDResume( cd ); +} + +int CDAudio_SysUpdate (void) +{ + static time_t lastchk = 0; + + if (cdPlaying && lastchk < time(NULL)) + { + lastchk = time(NULL) + 2; //two seconds between chks + if( !cd || cd->status <= 0 ) { + cdValid = false; + return -1; + } + if (SDL_CDStatus( cd ) == CD_STOPPED) + { + if( cdPlayLooping ) + CDAudio_SysPlay( cdPlayTrack ); + else + cdPlaying = false; + } + } + return 0; +} + +void CDAudio_SysInit (void) +{ + if( SDL_InitSubSystem( SDL_INIT_CDROM ) == -1 ) + Con_Print( "Failed to init the CDROM SDL subsystem!\n" ); + + Cmd_AddCommand( "cddrive", CDAudio_SDL_CDDrive_f, "select an SDL-detected CD drive by number" ); +} + +static int IsAudioCD( void ) +{ + int i; + for( i = 0 ; i < cd->numtracks ; i++ ) + if( cd->track[ i ].type == SDL_AUDIO_TRACK ) + return true; + return false; +} + +int CDAudio_SysStartup (void) +{ + int i; + int numdrives; + + numdrives = SDL_CDNumDrives(); + if( numdrives == -1 ) // was the CDROM system initialized correctly? + return -1; + + Con_Printf( "Found %i cdrom drives.\n", numdrives ); + + for( i = 0 ; i < numdrives ; i++, cd = NULL ) { + cd = SDL_CDOpen( i ); + if( !cd ) { + Con_Printf( "CD drive %i is invalid.\n", i ); + continue; + } + + if( CD_INDRIVE( SDL_CDStatus( cd ) ) ) + if( IsAudioCD() ) + break; + else + Con_Printf( "The CD in drive %i is not an audio cd.\n", i ); + else + Con_Printf( "No CD in drive %i.\n", i ); + + SDL_CDClose( cd ); + } + + if( i == numdrives && !cd ) + return -1; + + return 0; +} + +void CDAudio_SysShutdown (void) +{ + if( cd ) + SDL_CDClose( cd ); +} + +void CDAudio_SDL_CDDrive_f( void ) +{ + int i; + int numdrives = SDL_CDNumDrives(); + + if( Cmd_Argc() != 2 ) { + Con_Print( "cddrive \n" ); + return; + } + + i = atoi( Cmd_Argv( 1 ) ); + if( i >= numdrives ) { + Con_Printf("Only %i drives!\n", numdrives ); + return; + } + + if( cd ) + SDL_CDClose( cd ); + + cd = SDL_CDOpen( i ); + if( !cd ) { + Con_Printf( "Couldn't open drive %i.\n", i ); + return; + } + + if( !CD_INDRIVE( SDL_CDStatus( cd ) ) ) + Con_Printf( "No cd in drive %i.\n", i ); + else if( !IsAudioCD() ) + Con_Printf( "The CD in drive %i is not an audio CD.\n", i ); + + ValidateDrive(); +} + + + + + +#else +// SDL 1.3 does not have CD audio + +void CDAudio_SysEject (void) +{ +} + + +void CDAudio_SysCloseDoor (void) +{ +} + + +int CDAudio_SysGetAudioDiskInfo (void) +{ + return -1; +} + + +float CDAudio_SysGetVolume (void) +{ + return -1.0f; +} + + +void CDAudio_SysSetVolume (float volume) +{ +} + + +int CDAudio_SysPlay (int track) +{ + return -1; +} + + +int CDAudio_SysStop (void) +{ + return -1; +} + + +int CDAudio_SysPause (void) +{ + return -1; +} + +int CDAudio_SysResume (void) +{ + return -1; +} + +int CDAudio_SysUpdate (void) +{ + return -1; +} + + +void CDAudio_SysInit (void) +{ +} + +int CDAudio_SysStartup (void) +{ + return -1; +} + +void CDAudio_SysShutdown (void) +{ +} +#endif + diff --git a/misc/source/darkplaces-src/cd_shared.c b/misc/source/darkplaces-src/cd_shared.c new file mode 100644 index 00000000..453aba62 --- /dev/null +++ b/misc/source/darkplaces-src/cd_shared.c @@ -0,0 +1,798 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. + +#include "quakedef.h" +#include "cdaudio.h" +#include "sound.h" + +// Prototypes of the system dependent functions +extern void CDAudio_SysEject (void); +extern void CDAudio_SysCloseDoor (void); +extern int CDAudio_SysGetAudioDiskInfo (void); +extern float CDAudio_SysGetVolume (void); +extern void CDAudio_SysSetVolume (float volume); +extern int CDAudio_SysPlay (int track); +extern int CDAudio_SysStop (void); +extern int CDAudio_SysPause (void); +extern int CDAudio_SysResume (void); +extern int CDAudio_SysUpdate (void); +extern void CDAudio_SysInit (void); +extern int CDAudio_SysStartup (void); +extern void CDAudio_SysShutdown (void); + +// used by menu to ghost CD audio slider +cvar_t cdaudioinitialized = {CVAR_READONLY,"cdaudioinitialized","0","indicates if CD Audio system is active"}; +cvar_t cdaudio = {CVAR_SAVE,"cdaudio","1","CD playing mode (0 = never access CD drive, 1 = play CD tracks if no replacement available, 2 = play fake tracks if no CD track available, 3 = play only real CD tracks, 4 = play real CD tracks even instead of named fake tracks)"}; + +#define MAX_PLAYLISTS 10 +int music_playlist_active = -1; +int music_playlist_playing = 0; // 0 = not playing, 1 = playing, -1 = tried and failed + +cvar_t music_playlist_index = {0, "music_playlist_index", "-1", "selects which of the music_playlist_ variables is the active one, -1 disables playlists"}; +cvar_t music_playlist_list[MAX_PLAYLISTS] = +{ + {0, "music_playlist_list0", "", "list of tracks to play"}, + {0, "music_playlist_list1", "", "list of tracks to play"}, + {0, "music_playlist_list2", "", "list of tracks to play"}, + {0, "music_playlist_list3", "", "list of tracks to play"}, + {0, "music_playlist_list4", "", "list of tracks to play"}, + {0, "music_playlist_list5", "", "list of tracks to play"}, + {0, "music_playlist_list6", "", "list of tracks to play"}, + {0, "music_playlist_list7", "", "list of tracks to play"}, + {0, "music_playlist_list8", "", "list of tracks to play"}, + {0, "music_playlist_list9", "", "list of tracks to play"} +}; +cvar_t music_playlist_current[MAX_PLAYLISTS] = +{ + {0, "music_playlist_current0", "0", "current track index to play in list"}, + {0, "music_playlist_current1", "0", "current track index to play in list"}, + {0, "music_playlist_current2", "0", "current track index to play in list"}, + {0, "music_playlist_current3", "0", "current track index to play in list"}, + {0, "music_playlist_current4", "0", "current track index to play in list"}, + {0, "music_playlist_current5", "0", "current track index to play in list"}, + {0, "music_playlist_current6", "0", "current track index to play in list"}, + {0, "music_playlist_current7", "0", "current track index to play in list"}, + {0, "music_playlist_current8", "0", "current track index to play in list"}, + {0, "music_playlist_current9", "0", "current track index to play in list"}, +}; +cvar_t music_playlist_random[MAX_PLAYLISTS] = +{ + {0, "music_playlist_random0", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random1", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random2", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random3", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random4", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random5", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random6", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random7", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random8", "0", "enables random play order if 1, 0 is sequential play"}, + {0, "music_playlist_random9", "0", "enables random play order if 1, 0 is sequential play"}, +}; +cvar_t music_playlist_sampleposition[MAX_PLAYLISTS] = +{ + {0, "music_playlist_sampleposition0", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition1", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition2", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition3", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition4", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition5", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition6", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition7", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition8", "-1", "resume position for track, -1 restarts every time"}, + {0, "music_playlist_sampleposition9", "-1", "resume position for track, -1 restarts every time"}, +}; + +static qboolean wasPlaying = false; +static qboolean initialized = false; +static qboolean enabled = false; +static float cdvolume; +typedef char filename_t[MAX_QPATH]; +#ifdef MAXTRACKS +static filename_t remap[MAXTRACKS]; +#endif +static unsigned char maxTrack; +static int faketrack = -1; + +static float saved_vol = 1.0f; + +// exported variables +qboolean cdValid = false; +qboolean cdPlaying = false; +qboolean cdPlayLooping = false; +unsigned char cdPlayTrack; + +cl_cdstate_t cd; + +static void CDAudio_Eject (void) +{ + if (!enabled) + return; + + if(cdaudio.integer == 0) + return; + + CDAudio_SysEject(); +} + + +static void CDAudio_CloseDoor (void) +{ + if (!enabled) + return; + + if(cdaudio.integer == 0) + return; + + CDAudio_SysCloseDoor(); +} + +static int CDAudio_GetAudioDiskInfo (void) +{ + int ret; + + cdValid = false; + + if(cdaudio.integer == 0) + return -1; + + ret = CDAudio_SysGetAudioDiskInfo(); + if (ret < 1) + return -1; + + cdValid = true; + maxTrack = ret; + + return 0; +} + +qboolean CDAudio_Play_real (int track, qboolean looping, qboolean complain) +{ + if(track < 1) + { + if(complain) + Con_Print("Could not load BGM track.\n"); + return false; + } + + if (!cdValid) + { + CDAudio_GetAudioDiskInfo(); + if (!cdValid) + { + if(complain) + Con_DPrint ("No CD in player.\n"); + return false; + } + } + + if (track > maxTrack) + { + if(complain) + Con_DPrintf("CDAudio: Bad track number %u.\n", track); + return false; + } + + if (CDAudio_SysPlay(track) == -1) + return false; + + if(cdaudio.integer != 3) + Con_DPrintf ("CD track %u playing...\n", track); + + return true; +} + +void CDAudio_Play_byName (const char *trackname, qboolean looping, qboolean tryreal, float startposition) +{ + unsigned int track; + sfx_t* sfx; + char filename[MAX_QPATH]; + + Host_StartVideo(); + + if (!enabled) + return; + + if(tryreal && strspn(trackname, "0123456789") == strlen(trackname)) + { + track = (unsigned char) atoi(trackname); +#ifdef MAXTRACKS + if(track > 0 && track < MAXTRACKS) + if(*remap[track]) + { + if(strspn(remap[track], "0123456789") == strlen(remap[track])) + { + trackname = remap[track]; + } + else + { + // ignore remappings to fake tracks if we're going to play a real track + switch(cdaudio.integer) + { + case 0: // we never access CD + case 1: // we have a replacement + trackname = remap[track]; + break; + case 2: // we only use fake track replacement if CD track is invalid + CDAudio_GetAudioDiskInfo(); + if(!cdValid || track > maxTrack) + trackname = remap[track]; + break; + case 3: // we always play from CD - ignore this remapping then + case 4: // we randomize anyway + break; + } + } + } +#endif + } + + if(tryreal && strspn(trackname, "0123456789") == strlen(trackname)) + { + track = (unsigned char) atoi(trackname); + if (track < 1) + { + Con_DPrintf("CDAudio: Bad track number %u.\n", track); + return; + } + } + else + track = 0; + + // div0: I assume this code was intentionally there. Maybe turn it into a cvar? + if (cdPlaying && cdPlayTrack == track && faketrack == -1) + return; + CDAudio_Stop (); + + if(track >= 1) + { + if(cdaudio.integer == 3) // only play real CD tracks at all + { + if(CDAudio_Play_real(track, looping, true)) + goto success; + return; + } + + if(cdaudio.integer == 2) // prefer real CD track over fake + { + if(CDAudio_Play_real(track, looping, false)) + goto success; + } + } + + if(cdaudio.integer == 4) // only play real CD tracks, EVEN instead of fake tracks! + { + if(CDAudio_Play_real(track, looping, false)) + goto success; + + if(cdValid && maxTrack > 0) + { + track = 1 + (rand() % maxTrack); + if(CDAudio_Play_real(track, looping, true)) + goto success; + } + else + { + Con_DPrint ("No CD in player.\n"); + } + return; + } + + // Try playing a fake track (sound file) first + if(track >= 1) + { + dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%03u.wav", track); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%03u.ogg", track); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/track%03u.ogg", track);// added by motorsep + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/track%03u.ogg", track);// added by motorsep + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%02u.wav", track); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%02u.ogg", track); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/track%02u.ogg", track);// added by motorsep + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/track%02u.ogg", track);// added by motorsep + } + else + { + dpsnprintf(filename, sizeof(filename), "%s", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "%s.wav", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "%s.ogg", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s.wav", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s.ogg", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s.wav", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s.ogg", trackname); + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/%s.ogg", trackname); // added by motorsep + if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/%s.ogg", trackname); // added by motorsep + } + if (FS_FileExists(filename) && (sfx = S_PrecacheSound (filename, false, false))) + { + faketrack = S_StartSound_StartPosition_Flags (-1, 0, sfx, vec3_origin, cdvolume, 0, startposition, (looping ? CHANNELFLAG_FORCELOOP : 0) | CHANNELFLAG_FULLVOLUME | CHANNELFLAG_LOCALSOUND); + if (faketrack != -1) + { + if(track >= 1) + { + if(cdaudio.integer != 0) // we don't need these messages if only fake tracks can be played anyway + Con_DPrintf ("Fake CD track %u playing...\n", track); + } + else + Con_DPrintf ("BGM track %s playing...\n", trackname); + } + } + + // If we can't play a fake CD track, try the real one + if (faketrack == -1) + { + if(cdaudio.integer == 0 || track < 1) + { + Con_Print("Could not load BGM track.\n"); + return; + } + else + { + if(!CDAudio_Play_real(track, looping, true)) + return; + } + } + +success: + cdPlayLooping = looping; + cdPlayTrack = track; + cdPlaying = true; + + if (cdvolume == 0.0 || bgmvolume.value == 0) + CDAudio_Pause (); +} + +void CDAudio_Play (int track, qboolean looping) +{ + char buf[20]; + if (music_playlist_index.integer >= 0) + return; + dpsnprintf(buf, sizeof(buf), "%d", (int) track); + CDAudio_Play_byName(buf, looping, true, 0); +} + +float CDAudio_GetPosition (void) +{ + if(faketrack != -1) + return S_GetChannelPosition(faketrack); + return -1; +} + +static void CDAudio_StopPlaylistTrack(void); + +void CDAudio_Stop (void) +{ + if (!enabled) + return; + + // save the playlist position + CDAudio_StopPlaylistTrack(); + + if (faketrack != -1) + { + S_StopChannel (faketrack, true, true); + faketrack = -1; + } + else if (cdPlaying && (CDAudio_SysStop() == -1)) + return; + else if(wasPlaying) + { + CDAudio_Resume(); // needed by SDL - can't stop while paused there (causing pause/stop to fail after play, pause, stop, play otherwise) + if (cdPlaying && (CDAudio_SysStop() == -1)) + return; + } + + wasPlaying = false; + cdPlaying = false; +} + +void CDAudio_Pause (void) +{ + if (!enabled || !cdPlaying) + return; + + if (faketrack != -1) + S_SetChannelFlag (faketrack, CHANNELFLAG_PAUSED, true); + else if (CDAudio_SysPause() == -1) + return; + + wasPlaying = cdPlaying; + cdPlaying = false; +} + + +void CDAudio_Resume (void) +{ + if (!enabled || cdPlaying || !wasPlaying) + return; + + if (faketrack != -1) + S_SetChannelFlag (faketrack, CHANNELFLAG_PAUSED, false); + else if (CDAudio_SysResume() == -1) + return; + cdPlaying = true; +} + +static void CD_f (void) +{ + const char *command; +#ifdef MAXTRACKS + int ret; + int n; +#endif + + command = Cmd_Argv (1); + + if (strcasecmp(command, "remap") != 0) + Host_StartVideo(); + + if (strcasecmp(command, "on") == 0) + { + enabled = true; + return; + } + + if (strcasecmp(command, "off") == 0) + { + CDAudio_Stop(); + enabled = false; + return; + } + + if (strcasecmp(command, "reset") == 0) + { + enabled = true; + CDAudio_Stop(); +#ifdef MAXTRACKS + for (n = 0; n < MAXTRACKS; n++) + *remap[n] = 0; // empty string, that is, unremapped +#endif + CDAudio_GetAudioDiskInfo(); + return; + } + + if (strcasecmp(command, "rescan") == 0) + { + CDAudio_Shutdown(); + CDAudio_Startup(); + return; + } + + if (strcasecmp(command, "remap") == 0) + { +#ifdef MAXTRACKS + ret = Cmd_Argc() - 2; + if (ret <= 0) + { + for (n = 1; n < MAXTRACKS; n++) + if (*remap[n]) + Con_Printf(" %u -> %s\n", n, remap[n]); + return; + } + for (n = 1; n <= ret; n++) + strlcpy(remap[n], Cmd_Argv (n+1), sizeof(*remap)); +#endif + return; + } + + if (strcasecmp(command, "close") == 0) + { + CDAudio_CloseDoor(); + return; + } + + if (strcasecmp(command, "play") == 0) + { + if (music_playlist_index.integer >= 0) + return; + CDAudio_Play_byName(Cmd_Argv (2), false, true, (Cmd_Argc() > 3) ? atof( Cmd_Argv(3) ) : 0); + return; + } + + if (strcasecmp(command, "loop") == 0) + { + if (music_playlist_index.integer >= 0) + return; + CDAudio_Play_byName(Cmd_Argv (2), true, true, (Cmd_Argc() > 3) ? atof( Cmd_Argv(3) ) : 0); + return; + } + + if (strcasecmp(command, "stop") == 0) + { + if (music_playlist_index.integer >= 0) + return; + CDAudio_Stop(); + return; + } + + if (strcasecmp(command, "pause") == 0) + { + if (music_playlist_index.integer >= 0) + return; + CDAudio_Pause(); + return; + } + + if (strcasecmp(command, "resume") == 0) + { + if (music_playlist_index.integer >= 0) + return; + CDAudio_Resume(); + return; + } + + if (strcasecmp(command, "eject") == 0) + { + if (faketrack == -1) + CDAudio_Stop(); + CDAudio_Eject(); + cdValid = false; + return; + } + + if (strcasecmp(command, "info") == 0) + { + CDAudio_GetAudioDiskInfo (); + if (cdValid) + Con_Printf("%u tracks on CD.\n", maxTrack); + else + Con_Print ("No CD in player.\n"); + if (cdPlaying) + Con_Printf("Currently %s track %u\n", cdPlayLooping ? "looping" : "playing", cdPlayTrack); + else if (wasPlaying) + Con_Printf("Paused %s track %u\n", cdPlayLooping ? "looping" : "playing", cdPlayTrack); + if (cdvolume >= 0) + Con_Printf("Volume is %f\n", cdvolume); + else + Con_Printf("Can't get CD volume\n"); + return; + } + + Con_Printf("CD commands:\n"); + Con_Printf("cd on - enables CD audio system\n"); + Con_Printf("cd off - stops and disables CD audio system\n"); + Con_Printf("cd reset - resets CD audio system (clears track remapping and re-reads disc information)\n"); + Con_Printf("cd rescan - rescans disks in drives (to use another disc)\n"); + Con_Printf("cd remap [remap2] [remap3] [...] - chooses (possibly emulated) CD tracks to play when a map asks for a particular track, this has many uses\n"); + Con_Printf("cd close - closes CD tray\n"); + Con_Printf("cd eject - stops playing music and opens CD tray to allow you to change disc\n"); + Con_Printf("cd play - plays selected track in remapping table\n"); + Con_Printf("cd loop - plays and repeats selected track in remapping table\n"); + Con_Printf("cd stop - stops playing current CD track\n"); + Con_Printf("cd pause - pauses CD playback\n"); + Con_Printf("cd resume - unpauses CD playback\n"); + Con_Printf("cd info - prints basic disc information (number of tracks, currently playing track, volume level)\n"); +} + +void CDAudio_SetVolume (float newvol) +{ + // If the volume hasn't changed + if (newvol == cdvolume) + return; + + // If the CD has been muted + if (newvol == 0.0f) + CDAudio_Pause (); + else + { + // If the CD has been unmuted + if (cdvolume == 0.0f) + CDAudio_Resume (); + + if (faketrack != -1) + S_SetChannelVolume (faketrack, newvol); + else + CDAudio_SysSetVolume (newvol * mastervolume.value); + } + + cdvolume = newvol; +} + +static void CDAudio_StopPlaylistTrack(void) +{ + if (music_playlist_active >= 0 && music_playlist_active < MAX_PLAYLISTS && music_playlist_sampleposition[music_playlist_active].value >= 0) + { + // save position for resume + float position = CDAudio_GetPosition(); + Cvar_SetValueQuick(&music_playlist_sampleposition[music_playlist_active], position >= 0 ? position : 0); + } + music_playlist_active = -1; + music_playlist_playing = 0; // not playing +} + +void CDAudio_StartPlaylist(qboolean resume) +{ + const char *list; + const char *t; + int index; + int current; + int randomplay; + int count; + int listindex; + float position; + char trackname[MAX_QPATH]; + CDAudio_Stop(); + index = music_playlist_index.integer; + if (index >= 0 && index < MAX_PLAYLISTS && bgmvolume.value > 0) + { + list = music_playlist_list[index].string; + current = music_playlist_current[index].integer; + randomplay = music_playlist_random[index].integer; + position = music_playlist_sampleposition[index].value; + count = 0; + trackname[0] = 0; + if (list && list[0]) + { + for (t = list;;count++) + { + if (!COM_ParseToken_Console(&t)) + break; + // if we don't find the desired track, use the first one + if (count == 0) + strlcpy(trackname, com_token, sizeof(trackname)); + } + } + if (count > 0) + { + // position < 0 means never resume track + if (position < 0) + position = 0; + // advance to next track in playlist if the last one ended + if (!resume) + { + position = 0; + current++; + if (randomplay) + current = (int)lhrandom(0, count); + } + // wrap playlist position if needed + if (current >= count) + current = 0; + // set current + Cvar_SetValueQuick(&music_playlist_current[index], current); + // get the Nth trackname + if (current >= 0 && current < count) + { + for (listindex = 0, t = list;;listindex++) + { + if (!COM_ParseToken_Console(&t)) + break; + if (listindex == current) + { + strlcpy(trackname, com_token, sizeof(trackname)); + break; + } + } + } + if (trackname[0]) + { + CDAudio_Play_byName(trackname, false, false, position); + if (faketrack != -1) + music_playlist_active = index; + } + } + } + music_playlist_playing = music_playlist_active >= 0 ? 1 : -1; +} + +void CDAudio_Update (void) +{ + static int lastplaylist = -1; + if (!enabled) + return; + + CDAudio_SetVolume (bgmvolume.value); + if (music_playlist_playing > 0 && CDAudio_GetPosition() < 0) + { + // this track ended, start a new track from the beginning + CDAudio_StartPlaylist(false); + lastplaylist = music_playlist_index.integer; + } + else if (lastplaylist != music_playlist_index.integer + || (bgmvolume.value > 0 && !music_playlist_playing && music_playlist_index.integer >= 0)) + { + // active playlist changed, save position and switch track + CDAudio_StartPlaylist(true); + lastplaylist = music_playlist_index.integer; + } + + if (faketrack == -1 && cdaudio.integer != 0 && bgmvolume.value != 0) + CDAudio_SysUpdate(); +} + +int CDAudio_Init (void) +{ + int i; + + if (cls.state == ca_dedicated) + return -1; + +// COMMANDLINEOPTION: Sound: -nocdaudio disables CD audio support + if (COM_CheckParm("-nocdaudio")) + return -1; + + CDAudio_SysInit(); + +#ifdef MAXTRACKS + for (i = 0; i < MAXTRACKS; i++) + *remap[i] = 0; +#endif + + Cvar_RegisterVariable(&cdaudio); + Cvar_RegisterVariable(&cdaudioinitialized); + Cvar_SetValueQuick(&cdaudioinitialized, true); + enabled = true; + + Cvar_RegisterVariable(&music_playlist_index); + for (i = 0;i < MAX_PLAYLISTS;i++) + { + Cvar_RegisterVariable(&music_playlist_list[i]); + Cvar_RegisterVariable(&music_playlist_current[i]); + Cvar_RegisterVariable(&music_playlist_random[i]); + Cvar_RegisterVariable(&music_playlist_sampleposition[i]); + } + + Cmd_AddCommand("cd", CD_f, "execute a CD drive command (cd on/off/reset/remap/close/play/loop/stop/pause/resume/eject/info) - use cd by itself for usage"); + + return 0; +} + +int CDAudio_Startup (void) +{ + if (COM_CheckParm("-nocdaudio")) + return -1; + + CDAudio_SysStartup (); + + if (CDAudio_GetAudioDiskInfo()) + { + Con_Print("CDAudio_Init: No CD in player.\n"); + cdValid = false; + } + + saved_vol = CDAudio_SysGetVolume (); + if (saved_vol < 0.0f) + { + Con_Print ("Can't get initial CD volume\n"); + saved_vol = 1.0f; + } + else + Con_Printf ("Initial CD volume: %g\n", saved_vol); + + initialized = true; + + Con_Print("CD Audio Initialized\n"); + + return 0; +} + +void CDAudio_Shutdown (void) +{ + if (!initialized) + return; + + CDAudio_SysSetVolume (saved_vol); + + CDAudio_Stop(); + CDAudio_SysShutdown(); + initialized = false; +} diff --git a/misc/source/darkplaces-src/cd_win.c b/misc/source/darkplaces-src/cd_win.c new file mode 100644 index 00000000..76ac51ed --- /dev/null +++ b/misc/source/darkplaces-src/cd_win.c @@ -0,0 +1,268 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. + +#include "quakedef.h" +#include +#include + +#include "cdaudio.h" + +#if defined(_MSC_VER) && (_MSC_VER < 1300) +typedef DWORD DWORD_PTR; +#endif + +extern HWND mainwindow; + +UINT wDeviceID; + +void CDAudio_SysEject(void) +{ + DWORD dwReturn; + + if ((dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN, (DWORD_PTR)NULL))) + Con_Printf("MCI_SET_DOOR_OPEN failed (%x)\n", (unsigned)dwReturn); +} + + +void CDAudio_SysCloseDoor(void) +{ + DWORD dwReturn; + + if ((dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, (DWORD_PTR)NULL))) + Con_Printf("MCI_SET_DOOR_CLOSED failed (%x)\n", (unsigned)dwReturn); +} + +int CDAudio_SysGetAudioDiskInfo(void) +{ + DWORD dwReturn; + MCI_STATUS_PARMS mciStatusParms; + + mciStatusParms.dwItem = MCI_STATUS_READY; + dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR) (LPVOID) &mciStatusParms); + if (dwReturn) + { + Con_Print("CDAudio_SysGetAudioDiskInfo: drive ready test - get status failed\n"); + return -1; + } + if (!mciStatusParms.dwReturn) + { + Con_Print("CDAudio_SysGetAudioDiskInfo: drive not ready\n"); + return -1; + } + + mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; + dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR) (LPVOID) &mciStatusParms); + if (dwReturn) + { + Con_Print("CDAudio_SysGetAudioDiskInfo: get tracks - status failed\n"); + return -1; + } + if (mciStatusParms.dwReturn < 1) + { + Con_Print("CDAudio_SysGetAudioDiskInfo: no music tracks\n"); + return -1; + } + + return mciStatusParms.dwReturn; +} + + +float CDAudio_SysGetVolume (void) +{ + // IMPLEMENTME + return -1.0f; +} + + +void CDAudio_SysSetVolume (float volume) +{ + // IMPLEMENTME +} + + +int CDAudio_SysPlay (int track) +{ + DWORD dwReturn; + MCI_PLAY_PARMS mciPlayParms; + MCI_STATUS_PARMS mciStatusParms; + + // don't try to play a non-audio track + mciStatusParms.dwItem = MCI_CDA_STATUS_TYPE_TRACK; + mciStatusParms.dwTrack = track; + dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR) (LPVOID) &mciStatusParms); + if (dwReturn) + { + Con_Printf("CDAudio_SysPlay: MCI_STATUS failed (%x)\n", (unsigned)dwReturn); + return -1; + } + if (mciStatusParms.dwReturn != MCI_CDA_TRACK_AUDIO) + { + Con_Printf("CDAudio_SysPlay: track %i is not audio\n", track); + return -1; + } + + if (cdPlaying) + CDAudio_Stop(); + + // get the length of the track to be played + mciStatusParms.dwItem = MCI_STATUS_LENGTH; + mciStatusParms.dwTrack = track; + dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR) (LPVOID) &mciStatusParms); + if (dwReturn) + { + Con_Printf("CDAudio_SysPlay: MCI_STATUS failed (%x)\n", (unsigned)dwReturn); + return -1; + } + + mciPlayParms.dwFrom = MCI_MAKE_TMSF(track, 0, 0, 0); + mciPlayParms.dwTo = (mciStatusParms.dwReturn << 8) | track; + mciPlayParms.dwCallback = (DWORD_PTR)mainwindow; + dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY | MCI_FROM | MCI_TO, (DWORD_PTR)(LPVOID) &mciPlayParms); + if (dwReturn) + { + Con_Printf("CDAudio_SysPlay: MCI_PLAY failed (%x)\n", (unsigned)dwReturn); + return -1; + } + + return 0; +} + + +int CDAudio_SysStop (void) +{ + DWORD dwReturn; + + if ((dwReturn = mciSendCommand(wDeviceID, MCI_STOP, 0, (DWORD_PTR)NULL))) + { + Con_Printf("MCI_STOP failed (%x)\n", (unsigned)dwReturn); + return -1; + } + return 0; +} + +int CDAudio_SysPause (void) +{ + DWORD dwReturn; + MCI_GENERIC_PARMS mciGenericParms; + + mciGenericParms.dwCallback = (DWORD_PTR)mainwindow; + if ((dwReturn = mciSendCommand(wDeviceID, MCI_PAUSE, 0, (DWORD_PTR)(LPVOID) &mciGenericParms))) + { + Con_Printf("MCI_PAUSE failed (%x)\n", (unsigned)dwReturn); + return -1; + } + return 0; +} + + +int CDAudio_SysResume (void) +{ + DWORD dwReturn; + MCI_PLAY_PARMS mciPlayParms; + + mciPlayParms.dwFrom = MCI_MAKE_TMSF(cdPlayTrack, 0, 0, 0); + mciPlayParms.dwTo = MCI_MAKE_TMSF(cdPlayTrack + 1, 0, 0, 0); + mciPlayParms.dwCallback = (DWORD_PTR)mainwindow; + dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_TO | MCI_NOTIFY, (DWORD_PTR)(LPVOID) &mciPlayParms); + if (dwReturn) + { + Con_Printf("CDAudio_SysResume: MCI_PLAY failed (%x)\n", (unsigned)dwReturn); + return -1; + } + return 0; +} + +LONG CDAudio_MessageHandler (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (lParam != (LPARAM)wDeviceID) + return 1; + + switch (wParam) + { + case MCI_NOTIFY_SUCCESSFUL: + if (cdPlaying) + { + cdPlaying = false; + if (cdPlayLooping) + CDAudio_Play(cdPlayTrack, true); + } + break; + + case MCI_NOTIFY_ABORTED: + case MCI_NOTIFY_SUPERSEDED: + break; + + case MCI_NOTIFY_FAILURE: + Con_Print("MCI_NOTIFY_FAILURE\n"); + CDAudio_Stop (); + cdValid = false; + break; + + default: + Con_Printf("Unexpected MM_MCINOTIFY type (%i)\n", (int)wParam); + return 1; + } + + return 0; +} + + +int CDAudio_SysUpdate (void) +{ + return 0; +} + +void CDAudio_SysInit (void) +{ +} + +int CDAudio_SysStartup (void) +{ + DWORD dwReturn; + MCI_OPEN_PARMS mciOpenParms; + MCI_SET_PARMS mciSetParms; + + mciOpenParms.lpstrDeviceType = "cdaudio"; + if ((dwReturn = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE, (DWORD_PTR) (LPVOID) &mciOpenParms))) + { + Con_Printf("CDAudio_SysStartup: MCI_OPEN failed (%x)\n", (unsigned)dwReturn); + return -1; + } + wDeviceID = mciOpenParms.wDeviceID; + + // Set the time format to track/minute/second/frame (TMSF). + mciSetParms.dwTimeFormat = MCI_FORMAT_TMSF; + if ((dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID) &mciSetParms))) + { + Con_Printf("CDAudio_SysStartup: MCI_SET_TIME_FORMAT failed (%x)\n", (unsigned)dwReturn); + mciSendCommand(wDeviceID, MCI_CLOSE, 0, (DWORD_PTR)NULL); + return -1; + } + + return 0; +} + +void CDAudio_SysShutdown (void) +{ + if (mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD_PTR)NULL)) + Con_Print("CDAudio_SysShutdown: MCI_CLOSE failed\n"); +} diff --git a/misc/source/darkplaces-src/cdaudio.h b/misc/source/darkplaces-src/cdaudio.h new file mode 100644 index 00000000..3fed67fa --- /dev/null +++ b/misc/source/darkplaces-src/cdaudio.h @@ -0,0 +1,51 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +typedef struct cl_cdstate_s +{ + qboolean Valid; + qboolean Playing; + qboolean PlayLooping; + unsigned char PlayTrack; +} +cl_cdstate_t; + +//extern cl_cdstate_t cd; + +extern qboolean cdValid; +extern qboolean cdPlaying; +extern qboolean cdPlayLooping; +extern unsigned char cdPlayTrack; + +extern cvar_t cdaudioinitialized; + +int CDAudio_Init(void); +void CDAudio_Open(void); +void CDAudio_Close(void); +void CDAudio_Play(int track, qboolean looping); +void CDAudio_Play_byName (const char *trackname, qboolean looping, qboolean tryreal, float startposition); +void CDAudio_Stop(void); +void CDAudio_Pause(void); +void CDAudio_Resume(void); +int CDAudio_Startup(void); +void CDAudio_Shutdown(void); +void CDAudio_Update(void); +float CDAudio_GetPosition(void); +void CDAudio_StartPlaylist(qboolean resume); diff --git a/misc/source/darkplaces-src/cl_collision.c b/misc/source/darkplaces-src/cl_collision.c new file mode 100644 index 00000000..8c954066 --- /dev/null +++ b/misc/source/darkplaces-src/cl_collision.c @@ -0,0 +1,940 @@ + +#include "quakedef.h" +#include "cl_collision.h" + +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +float CL_SelectTraceLine(const vec3_t start, const vec3_t pEnd, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent) +#else +float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent) +#endif +{ + float maxfrac, maxrealfrac; + int n; + entity_render_t *ent; + float tracemins[3], tracemaxs[3]; + trace_t trace; + float tempnormal[3], starttransformed[3], endtransformed[3]; +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + vec3_t end; + vec_t len = 0; + + if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) + { + // TRICK: make the trace 1 qu longer! + VectorSubtract(pEnd, start, end); + len = VectorNormalizeLength(end); + VectorMA(pEnd, collision_endposnudge.value, end, end); + } + else + VectorCopy(pEnd, end); +#endif + + memset (&trace, 0 , sizeof(trace_t)); + trace.fraction = 1; + trace.realfraction = 1; + VectorCopy (end, trace.endpos); + + if (hitent) + *hitent = 0; + if (cl.worldmodel && cl.worldmodel->TraceLine) + cl.worldmodel->TraceLine(cl.worldmodel, NULL, NULL, &trace, start, end, SUPERCONTENTS_SOLID); + + if (normal) + VectorCopy(trace.plane.normal, normal); + maxfrac = trace.fraction; + maxrealfrac = trace.realfraction; + + tracemins[0] = min(start[0], end[0]); + tracemaxs[0] = max(start[0], end[0]); + tracemins[1] = min(start[1], end[1]); + tracemaxs[1] = max(start[1], end[1]); + tracemins[2] = min(start[2], end[2]); + tracemaxs[2] = max(start[2], end[2]); + + // look for embedded bmodels + for (n = 0;n < cl.num_entities;n++) + { + if (!cl.entities_active[n]) + continue; + ent = &cl.entities[n].render; + if (!BoxesOverlap(ent->mins, ent->maxs, tracemins, tracemaxs)) + continue; + if (!ent->model || !ent->model->TraceLine) + continue; + if ((ent->flags & RENDER_EXTERIORMODEL) && !chase_active.integer) + continue; + // if transparent and not selectable, skip entity + if (!(cl.entities[n].state_current.effects & EF_SELECTABLE) && (ent->alpha < 1 || (ent->effects & (EF_ADDITIVE | EF_NODEPTHTEST)))) + continue; + if (ent == ignoreent) + continue; + Matrix4x4_Transform(&ent->inversematrix, start, starttransformed); + Matrix4x4_Transform(&ent->inversematrix, end, endtransformed); + Collision_ClipTrace_Box(&trace, ent->model->normalmins, ent->model->normalmaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, SUPERCONTENTS_SOLID, SUPERCONTENTS_SOLID, 0, NULL); +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) + Collision_ShortenTrace(&trace, len / (len + collision_endposnudge.value), pEnd); +#endif + if (maxrealfrac < trace.realfraction) + continue; + + ent->model->TraceLine(ent->model, ent->frameblend, ent->skeleton, &trace, starttransformed, endtransformed, SUPERCONTENTS_SOLID); + + if (maxrealfrac > trace.realfraction) + { + if (hitent) + *hitent = n; + maxfrac = trace.fraction; + maxrealfrac = trace.realfraction; + if (normal) + { + VectorCopy(trace.plane.normal, tempnormal); + Matrix4x4_Transform3x3(&ent->matrix, tempnormal, normal); + } + } + } + maxfrac = bound(0, maxfrac, 1); + maxrealfrac = bound(0, maxrealfrac, 1); + //if (maxfrac < 0 || maxfrac > 1) Con_Printf("fraction out of bounds %f %s:%d\n", maxfrac, __FILE__, __LINE__); + if (impact) + VectorLerp(start, maxfrac, end, impact); + return maxfrac; +} + +void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius) +{ + // FIXME: check multiple brush models + if (cl.worldmodel && cl.worldmodel->brush.FindNonSolidLocation) + cl.worldmodel->brush.FindNonSolidLocation(cl.worldmodel, in, out, radius); +} + +dp_model_t *CL_GetModelByIndex(int modelindex) +{ + if(!modelindex) + return NULL; + if (modelindex < 0) + { + modelindex = -(modelindex+1); + if (modelindex < MAX_MODELS) + return cl.csqc_model_precache[modelindex]; + } + else + { + if(modelindex < MAX_MODELS) + return cl.model_precache[modelindex]; + } + return NULL; +} + +dp_model_t *CL_GetModelFromEdict(prvm_edict_t *ed) +{ + if (!ed || ed->priv.server->free) + return NULL; + return CL_GetModelByIndex((int)PRVM_clientedictfloat(ed, modelindex)); +} + +void CL_LinkEdict(prvm_edict_t *ent) +{ + vec3_t mins, maxs; + + if (ent == prog->edicts) + return; // don't add the world + + if (ent->priv.server->free) + return; + + // set the abs box + + if (PRVM_clientedictfloat(ent, solid) == SOLID_BSP) + { + dp_model_t *model = CL_GetModelByIndex( (int)PRVM_clientedictfloat(ent, modelindex) ); + if (model == NULL) + { + Con_Printf("edict %i: SOLID_BSP with invalid modelindex!\n", PRVM_NUM_FOR_EDICT(ent)); + + model = CL_GetModelByIndex( 0 ); + } + + if( model != NULL ) + { + if (!model->TraceBox) + Con_DPrintf("edict %i: SOLID_BSP with non-collidable model\n", PRVM_NUM_FOR_EDICT(ent)); + + if (PRVM_clientedictvector(ent, angles)[0] || PRVM_clientedictvector(ent, angles)[2] || PRVM_clientedictvector(ent, avelocity)[0] || PRVM_clientedictvector(ent, avelocity)[2]) + { + VectorAdd(PRVM_clientedictvector(ent, origin), model->rotatedmins, mins); + VectorAdd(PRVM_clientedictvector(ent, origin), model->rotatedmaxs, maxs); + } + else if (PRVM_clientedictvector(ent, angles)[1] || PRVM_clientedictvector(ent, avelocity)[1]) + { + VectorAdd(PRVM_clientedictvector(ent, origin), model->yawmins, mins); + VectorAdd(PRVM_clientedictvector(ent, origin), model->yawmaxs, maxs); + } + else + { + VectorAdd(PRVM_clientedictvector(ent, origin), model->normalmins, mins); + VectorAdd(PRVM_clientedictvector(ent, origin), model->normalmaxs, maxs); + } + } + else + { + // SOLID_BSP with no model is valid, mainly because some QC setup code does so temporarily + VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); + VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); + } + } + else + { + VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); + VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); + } + + VectorCopy(mins, PRVM_clientedictvector(ent, absmin)); + VectorCopy(maxs, PRVM_clientedictvector(ent, absmax)); + + World_LinkEdict(&cl.world, ent, PRVM_clientedictvector(ent, absmin), PRVM_clientedictvector(ent, absmax)); +} + +int CL_GenericHitSuperContentsMask(const prvm_edict_t *passedict) +{ + if (passedict) + { + int dphitcontentsmask = (int)PRVM_clientedictfloat(passedict, dphitcontentsmask); + if (dphitcontentsmask) + return dphitcontentsmask; + else if (PRVM_clientedictfloat(passedict, solid) == SOLID_SLIDEBOX) + { + if ((int)PRVM_clientedictfloat(passedict, flags) & FL_MONSTER) + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_MONSTERCLIP; + else + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP; + } + else if (PRVM_clientedictfloat(passedict, solid) == SOLID_CORPSE) + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; + else if (PRVM_clientedictfloat(passedict, solid) == SOLID_TRIGGER) + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; + else + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE; + } + else + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE; +} + +/* +================== +CL_Move +================== +*/ +trace_t CL_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities) +{ + int i, bodysupercontents; + int passedictprog; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // bounding box of entire move area + vec3_t clipboxmins, clipboxmaxs; + // size when clipping against monsters + vec3_t clipmins2, clipmaxs2; + // start and end origin of move + vec3_t clipstart; + // trace results + trace_t cliptrace; + // matrices to transform into/out of other entity's space + matrix4x4_t matrix, imatrix; + // model of other entity + dp_model_t *model; + // list of entities to test for collisions + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + + if (hitnetworkentity) + *hitnetworkentity = 0; + + VectorCopy(start, clipstart); + VectorClear(clipmins2); + VectorClear(clipmaxs2); +#if COLLISIONPARANOID >= 3 + Con_Printf("move(%f %f %f)", clipstart[0], clipstart[1], clipstart[2]); +#endif + + // clip to world + Collision_ClipPointToWorld(&cliptrace, cl.worldmodel, clipstart, hitsupercontentsmask); + cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog ? prog->edicts : NULL; + if (type == MOVE_WORLDONLY) + goto finished; + + if (type == MOVE_MISSILE) + { + // LordHavoc: modified this, was = -15, now -= 15 + for (i = 0;i < 3;i++) + { + clipmins2[i] -= 15; + clipmaxs2[i] += 15; + } + } + + // create the bounding box of the entire move + for (i = 0;i < 3;i++) + { + clipboxmins[i] = clipstart[i] - 1; + clipboxmaxs[i] = clipstart[i] + 1; + } + + // debug override to test against everything + if (sv_debugmove.integer) + { + clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; + clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; + } + + // if the passedict is world, make it NULL (to avoid two checks each time) + // this checks prog because this function is often called without a CSQC + // VM context + if (prog == NULL || passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0; + // precalculate passedict's owner edict pointer for comparisons + traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL; + + // collide against network entities + if (hitnetworkbrushmodels) + { + for (i = 0;i < cl.num_brushmodel_entities;i++) + { + entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render; + if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs)) + continue; + Collision_ClipPointToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, hitsupercontentsmask); + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = cl.brushmodel_entities[i]; + Collision_CombineTraces(&cliptrace, &trace, NULL, true); + } + } + + // collide against player entities + if (hitnetworkplayers) + { + vec3_t origin, entmins, entmaxs; + matrix4x4_t entmatrix, entinversematrix; + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + // don't hit network players, if we are a nonsolid player + if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616) + goto skipnetworkplayers; + } + + for (i = 1;i <= cl.maxclients;i++) + { + entity_render_t *ent = &cl.entities[i].render; + + // don't hit ourselves + if (i == cl.playerentity) + continue; + + // don't hit players that don't exist + if (!cl.scores[i-1].name[0]) + continue; + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + // don't hit spectators or nonsolid players + if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616) + continue; + } + + Matrix4x4_OriginFromMatrix(&ent->matrix, origin); + VectorAdd(origin, cl.playerstandmins, entmins); + VectorAdd(origin, cl.playerstandmaxs, entmaxs); + if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs)) + continue; + Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]); + Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]); + Collision_ClipPointToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, hitsupercontentsmask); + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = i; + Collision_CombineTraces(&cliptrace, &trace, NULL, false); + } + +skipnetworkplayers: + ; + } + + // clip to entities + // because this uses World_EntitiestoBox, we know all entity boxes overlap + // the clip region, so we can skip culling checks in the loop below + // note: if prog is NULL then there won't be any linked entities + numtouchedicts = 0; + if (hitcsqcentities && prog != NULL) + { + numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + } + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + + if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP) + continue; + + if (passedict) + { + // don't clip against self + if (passedict == touch) + continue; + // don't clip owned entities against owner + if (traceowner == touch) + continue; + // don't clip owner against owned entities + if (passedictprog == PRVM_clientedictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + model = CL_GetModelFromEdict(touch); + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + if ((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER) + Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipstart, hitsupercontentsmask); + else + Collision_ClipPointToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, hitsupercontentsmask); + + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = 0; + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP); + } + +finished: + return cliptrace; +} + +/* +================== +CL_TraceLine +================== +*/ +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +trace_t CL_TraceLine(const vec3_t start, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities, qboolean hitsurfaces) +#else +trace_t CL_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities, qboolean hitsurfaces) +#endif +{ + int i, bodysupercontents; + int passedictprog; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // bounding box of entire move area + vec3_t clipboxmins, clipboxmaxs; + // size when clipping against monsters + vec3_t clipmins2, clipmaxs2; + // start and end origin of move + vec3_t clipstart, clipend; + // trace results + trace_t cliptrace; + // matrices to transform into/out of other entity's space + matrix4x4_t matrix, imatrix; + // model of other entity + dp_model_t *model; + // list of entities to test for collisions + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + vec3_t end; + vec_t len = 0; + + if (VectorCompare(start, pEnd)) + return CL_TracePoint(start, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); + + if(collision_endposnudge.value > 0) + { + // TRICK: make the trace 1 qu longer! + VectorSubtract(pEnd, start, end); + len = VectorNormalizeLength(end); + VectorMA(pEnd, collision_endposnudge.value, end, end); + } + else + VectorCopy(pEnd, end); +#else + if (VectorCompare(start, end)) + return CL_TracePoint(start, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); +#endif + + if (hitnetworkentity) + *hitnetworkentity = 0; + + VectorCopy(start, clipstart); + VectorCopy(end, clipend); + VectorClear(clipmins2); + VectorClear(clipmaxs2); +#if COLLISIONPARANOID >= 3 + Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); +#endif + + // clip to world + Collision_ClipLineToWorld(&cliptrace, cl.worldmodel, clipstart, clipend, hitsupercontentsmask, hitsurfaces); + cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog ? prog->edicts : NULL; + if (type == MOVE_WORLDONLY) + goto finished; + + if (type == MOVE_MISSILE) + { + // LordHavoc: modified this, was = -15, now -= 15 + for (i = 0;i < 3;i++) + { + clipmins2[i] -= 15; + clipmaxs2[i] += 15; + } + } + + // create the bounding box of the entire move + for (i = 0;i < 3;i++) + { + clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + clipmins2[i] - 1; + clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + clipmaxs2[i] + 1; + } + + // debug override to test against everything + if (sv_debugmove.integer) + { + clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; + clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; + } + + // if the passedict is world, make it NULL (to avoid two checks each time) + // this checks prog because this function is often called without a CSQC + // VM context + if (prog == NULL || passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0; + // precalculate passedict's owner edict pointer for comparisons + traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL; + + // collide against network entities + if (hitnetworkbrushmodels) + { + for (i = 0;i < cl.num_brushmodel_entities;i++) + { + entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render; + if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs)) + continue; + Collision_ClipLineToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, end, hitsupercontentsmask, hitsurfaces); + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = cl.brushmodel_entities[i]; + Collision_CombineTraces(&cliptrace, &trace, NULL, true); + } + } + + // collide against player entities + if (hitnetworkplayers) + { + vec3_t origin, entmins, entmaxs; + matrix4x4_t entmatrix, entinversematrix; + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + // don't hit network players, if we are a nonsolid player + if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616) + goto skipnetworkplayers; + } + + for (i = 1;i <= cl.maxclients;i++) + { + entity_render_t *ent = &cl.entities[i].render; + + // don't hit ourselves + if (i == cl.playerentity) + continue; + + // don't hit players that don't exist + if (!cl.scores[i-1].name[0]) + continue; + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + // don't hit spectators or nonsolid players + if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616) + continue; + } + + Matrix4x4_OriginFromMatrix(&ent->matrix, origin); + VectorAdd(origin, cl.playerstandmins, entmins); + VectorAdd(origin, cl.playerstandmaxs, entmaxs); + if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs)) + continue; + Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]); + Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]); + Collision_ClipLineToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, end, hitsupercontentsmask, hitsurfaces); + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = i; + Collision_CombineTraces(&cliptrace, &trace, NULL, false); + } + +skipnetworkplayers: + ; + } + + // clip to entities + // because this uses World_EntitiestoBox, we know all entity boxes overlap + // the clip region, so we can skip culling checks in the loop below + // note: if prog is NULL then there won't be any linked entities + numtouchedicts = 0; + if (hitcsqcentities && prog != NULL) + { + numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + } + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + + if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP) + continue; + + if (passedict) + { + // don't clip against self + if (passedict == touch) + continue; + // don't clip owned entities against owner + if (traceowner == touch) + continue; + // don't clip owner against owned entities + if (passedictprog == PRVM_clientedictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + model = CL_GetModelFromEdict(touch); + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + if (type == MOVE_MISSILE && (int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER) + Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask); + else + Collision_ClipLineToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipend, hitsupercontentsmask, hitsurfaces); + + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = 0; + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP); + } + +finished: +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) + Collision_ShortenTrace(&cliptrace, len / (len + collision_endposnudge.value), pEnd); +#endif + return cliptrace; +} + +/* +================== +CL_Move +================== +*/ +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities) +#else +trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities) +#endif +{ + vec3_t hullmins, hullmaxs; + int i, bodysupercontents; + int passedictprog; + qboolean pointtrace; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // bounding box of entire move area + vec3_t clipboxmins, clipboxmaxs; + // size of the moving object + vec3_t clipmins, clipmaxs; + // size when clipping against monsters + vec3_t clipmins2, clipmaxs2; + // start and end origin of move + vec3_t clipstart, clipend; + // trace results + trace_t cliptrace; + // matrices to transform into/out of other entity's space + matrix4x4_t matrix, imatrix; + // model of other entity + dp_model_t *model; + // list of entities to test for collisions + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + vec3_t end; + vec_t len = 0; + + if (VectorCompare(mins, maxs)) + { + vec3_t shiftstart, shiftend; + VectorAdd(start, mins, shiftstart); + VectorAdd(pEnd, mins, shiftend); + if (VectorCompare(start, pEnd)) + trace = CL_TracePoint(shiftstart, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); + else + trace = CL_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities, false); + VectorSubtract(trace.endpos, mins, trace.endpos); + return trace; + } + + if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) + { + // TRICK: make the trace 1 qu longer! + VectorSubtract(pEnd, start, end); + len = VectorNormalizeLength(end); + VectorMA(pEnd, collision_endposnudge.value, end, end); + } + else + VectorCopy(pEnd, end); +#else + if (VectorCompare(mins, maxs)) + { + vec3_t shiftstart, shiftend; + VectorAdd(start, mins, shiftstart); + VectorAdd(end, mins, shiftend); + if (VectorCompare(start, end)) + trace = CL_TracePoint(shiftstart, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); + else + trace = CL_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities, false); + VectorSubtract(trace.endpos, mins, trace.endpos); + return trace; + } +#endif + + if (hitnetworkentity) + *hitnetworkentity = 0; + + VectorCopy(start, clipstart); + VectorCopy(end, clipend); + VectorCopy(mins, clipmins); + VectorCopy(maxs, clipmaxs); + VectorCopy(mins, clipmins2); + VectorCopy(maxs, clipmaxs2); +#if COLLISIONPARANOID >= 3 + Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); +#endif + + // clip to world + Collision_ClipToWorld(&cliptrace, cl.worldmodel, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask); + cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog ? prog->edicts : NULL; + if (type == MOVE_WORLDONLY) + goto finished; + + if (type == MOVE_MISSILE) + { + // LordHavoc: modified this, was = -15, now -= 15 + for (i = 0;i < 3;i++) + { + clipmins2[i] -= 15; + clipmaxs2[i] += 15; + } + } + + // get adjusted box for bmodel collisions if the world is q1bsp or hlbsp + if (cl.worldmodel && cl.worldmodel->brush.RoundUpToHullSize) + cl.worldmodel->brush.RoundUpToHullSize(cl.worldmodel, clipmins, clipmaxs, hullmins, hullmaxs); + else + { + VectorCopy(clipmins, hullmins); + VectorCopy(clipmaxs, hullmaxs); + } + + // create the bounding box of the entire move + for (i = 0;i < 3;i++) + { + clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + min(hullmins[i], clipmins2[i]) - 1; + clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + max(hullmaxs[i], clipmaxs2[i]) + 1; + } + + // debug override to test against everything + if (sv_debugmove.integer) + { + clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; + clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; + } + + // if the passedict is world, make it NULL (to avoid two checks each time) + // this checks prog because this function is often called without a CSQC + // VM context + if (prog == NULL || passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0; + // figure out whether this is a point trace for comparisons + pointtrace = VectorCompare(clipmins, clipmaxs); + // precalculate passedict's owner edict pointer for comparisons + traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL; + + // collide against network entities + if (hitnetworkbrushmodels) + { + for (i = 0;i < cl.num_brushmodel_entities;i++) + { + entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render; + if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs)) + continue; + Collision_ClipToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, mins, maxs, end, hitsupercontentsmask); + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = cl.brushmodel_entities[i]; + Collision_CombineTraces(&cliptrace, &trace, NULL, true); + } + } + + // collide against player entities + if (hitnetworkplayers) + { + vec3_t origin, entmins, entmaxs; + matrix4x4_t entmatrix, entinversematrix; + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + // don't hit network players, if we are a nonsolid player + if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616) + goto skipnetworkplayers; + } + + for (i = 1;i <= cl.maxclients;i++) + { + entity_render_t *ent = &cl.entities[i].render; + + // don't hit ourselves + if (i == cl.playerentity) + continue; + + // don't hit players that don't exist + if (!cl.scores[i-1].name[0]) + continue; + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + // don't hit spectators or nonsolid players + if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616) + continue; + } + + Matrix4x4_OriginFromMatrix(&ent->matrix, origin); + VectorAdd(origin, cl.playerstandmins, entmins); + VectorAdd(origin, cl.playerstandmaxs, entmaxs); + if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs)) + continue; + Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]); + Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]); + Collision_ClipToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, mins, maxs, end, hitsupercontentsmask); + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = i; + Collision_CombineTraces(&cliptrace, &trace, NULL, false); + } + +skipnetworkplayers: + ; + } + + // clip to entities + // because this uses World_EntitiestoBox, we know all entity boxes overlap + // the clip region, so we can skip culling checks in the loop below + // note: if prog is NULL then there won't be any linked entities + numtouchedicts = 0; + if (hitcsqcentities && prog != NULL) + { + numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + } + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + + if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP) + continue; + + if (passedict) + { + // don't clip against self + if (passedict == touch) + continue; + // don't clip owned entities against owner + if (traceowner == touch) + continue; + // don't clip owner against owned entities + if (passedictprog == PRVM_clientedictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (pointtrace && VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + model = CL_GetModelFromEdict(touch); + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + if ((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER) + Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask); + else + Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask); + + if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) + *hitnetworkentity = 0; + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP); + } + +finished: +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) + Collision_ShortenTrace(&cliptrace, len / (len + collision_endposnudge.value), pEnd); +#endif + return cliptrace; +} diff --git a/misc/source/darkplaces-src/cl_collision.h b/misc/source/darkplaces-src/cl_collision.h new file mode 100644 index 00000000..27ce51be --- /dev/null +++ b/misc/source/darkplaces-src/cl_collision.h @@ -0,0 +1,18 @@ + +#ifndef CL_COLLISION_H +#define CL_COLLISION_H + +float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent); +void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius); + +dp_model_t *CL_GetModelByIndex(int modelindex); +dp_model_t *CL_GetModelFromEdict(prvm_edict_t *ed); + +void CL_LinkEdict(prvm_edict_t *ent); +int CL_GenericHitSuperContentsMask(const prvm_edict_t *edict); +trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities); +trace_t CL_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities, qboolean hitsurfaces); +trace_t CL_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities); +#define CL_PointSuperContents(point) (CL_TracePoint((point), sv_gameplayfix_swiminbmodels.integer ? MOVE_NOMONSTERS : MOVE_WORLDONLY, NULL, 0, true, false, NULL, false).startsupercontents) + +#endif diff --git a/misc/source/darkplaces-src/cl_demo.c b/misc/source/darkplaces-src/cl_demo.c new file mode 100644 index 00000000..ea10bff6 --- /dev/null +++ b/misc/source/darkplaces-src/cl_demo.c @@ -0,0 +1,523 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" + +extern cvar_t cl_capturevideo; +int old_vsync = 0; + +void CL_FinishTimeDemo (void); + +/* +============================================================================== + +DEMO CODE + +When a demo is playing back, all outgoing network messages are skipped, and +incoming messages are read from the demo file. + +Whenever cl.time gets past the last received message, another message is +read from the demo file. +============================================================================== +*/ + +/* +===================== +CL_NextDemo + +Called to play the next demo in the demo loop +===================== +*/ +void CL_NextDemo (void) +{ + char str[MAX_INPUTLINE]; + + if (cls.demonum == -1) + return; // don't play demos + + if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS) + { + cls.demonum = 0; + if (!cls.demos[cls.demonum][0]) + { + Con_Print("No demos listed with startdemos\n"); + cls.demonum = -1; + return; + } + } + + dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]); + Cbuf_InsertText (str); + cls.demonum++; +} + +/* +============== +CL_StopPlayback + +Called when a demo file runs out, or the user starts a game +============== +*/ +// LordHavoc: now called only by CL_Disconnect +void CL_StopPlayback (void) +{ + if (!cls.demoplayback) + return; + + FS_Close (cls.demofile); + cls.demoplayback = false; + cls.demofile = NULL; + + if (cls.timedemo) + CL_FinishTimeDemo (); + + if (COM_CheckParm("-demo") || COM_CheckParm("-capturedemo")) + Host_Quit_f(); + +} + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length and view angles +==================== +*/ +void CL_WriteDemoMessage (sizebuf_t *message) +{ + int len; + int i; + float f; + + if (cls.demopaused) // LordHavoc: pausedemo + return; + + len = LittleLong (message->cursize); + FS_Write (cls.demofile, &len, 4); + for (i=0 ; i<3 ; i++) + { + f = LittleFloat (cl.viewangles[i]); + FS_Write (cls.demofile, &f, 4); + } + FS_Write (cls.demofile, message->data, message->cursize); +} + +/* +==================== +CL_CutDemo + +Dumps the current demo to a buffer, and resets the demo to its starting point. +Used to insert csprogs.dat files as a download to the beginning of a demo file. +==================== +*/ +void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize) +{ + *buf = NULL; + *filesize = 0; + + FS_Close(cls.demofile); + *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize); + + // restart the demo recording + cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false); + if(!cls.demofile) + Host_Error("failed to reopen the demo file"); + FS_Printf(cls.demofile, "%i\n", cls.forcetrack); +} + +/* +==================== +CL_PasteDemo + +Adds the cut stuff back to the demo. Also frees the buffer. +Used to insert csprogs.dat files as a download to the beginning of a demo file. +==================== +*/ +void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize) +{ + fs_offset_t startoffset = 0; + + if(!*buf) + return; + + // skip cdtrack + while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n') + ++startoffset; + if(startoffset < *filesize) + ++startoffset; + + FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset); + + Mem_Free(*buf); + *buf = NULL; + *filesize = 0; +} + +/* +==================== +CL_ReadDemoMessage + +Handles playback of demos +==================== +*/ +void CL_ReadDemoMessage(void) +{ + int i; + float f; + + if (!cls.demoplayback) + return; + + // LordHavoc: pausedemo + if (cls.demopaused) + return; + + for (;;) + { + // decide if it is time to grab the next message + // always grab until fully connected + if (cls.signon == SIGNONS) + { + if (cls.timedemo) + { + cls.td_frames++; + cls.td_onesecondframes++; + // if this is the first official frame we can now grab the real + // td_starttime so the bogus time on the first frame doesn't + // count against the final report + if (cls.td_frames == 0) + { + cls.td_starttime = realtime; + cls.td_onesecondnexttime = cl.time + 1; + cls.td_onesecondrealtime = realtime; + cls.td_onesecondframes = 0; + cls.td_onesecondminfps = 0; + cls.td_onesecondmaxfps = 0; + cls.td_onesecondavgfps = 0; + cls.td_onesecondavgcount = 0; + } + if (cl.time >= cls.td_onesecondnexttime) + { + double fps = cls.td_onesecondframes / (realtime - cls.td_onesecondrealtime); + if (cls.td_onesecondavgcount == 0) + { + cls.td_onesecondminfps = fps; + cls.td_onesecondmaxfps = fps; + } + cls.td_onesecondrealtime = realtime; + cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps); + cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps); + cls.td_onesecondavgfps += fps; + cls.td_onesecondavgcount++; + cls.td_onesecondframes = 0; + cls.td_onesecondnexttime++; + } + } + else if (cl.time <= cl.mtime[0]) + { + // don't need another message yet + return; + } + } + + // get the next message + FS_Read(cls.demofile, &net_message.cursize, 4); + net_message.cursize = LittleLong(net_message.cursize); + if(net_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now! + { + // skip over demo packet + FS_Seek(cls.demofile, 12 + (net_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR); + continue; + } + if (net_message.cursize > net_message.maxsize) + Host_Error("Demo message (%i) > net_message.maxsize (%i)", net_message.cursize, net_message.maxsize); + VectorCopy(cl.mviewangles[0], cl.mviewangles[1]); + for (i = 0;i < 3;i++) + { + FS_Read(cls.demofile, &f, 4); + cl.mviewangles[0][i] = LittleFloat(f); + } + + if (FS_Read(cls.demofile, net_message.data, net_message.cursize) == net_message.cursize) + { + MSG_BeginReading(); + CL_ParseServerMessage(); + + if (cls.signon != SIGNONS) + Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect! + + // In case the demo contains a "svc_disconnect" message + if (!cls.demoplayback) + return; + + if (cls.timedemo) + return; + } + else + { + CL_Disconnect(); + return; + } + } +} + + +/* +==================== +CL_Stop_f + +stop recording a demo +==================== +*/ +void CL_Stop_f (void) +{ + sizebuf_t buf; + unsigned char bufdata[64]; + + if (!cls.demorecording) + { + Con_Print("Not recording a demo.\n"); + return; + } + +// write a disconnect message to the demo file + // LordHavoc: don't replace the net_message when doing this + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + SZ_Clear(&buf); + MSG_WriteByte(&buf, svc_disconnect); + CL_WriteDemoMessage(&buf); + +// finish up + if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1)) + { + FS_RemoveOnClose(cls.demofile); + Con_Print("Completed and deleted demo\n"); + } + else + Con_Print("Completed demo\n"); + FS_Close (cls.demofile); + cls.demofile = NULL; + cls.demorecording = false; +} + +/* +==================== +CL_Record_f + +record [cd track] +==================== +*/ +void CL_Record_f (void) +{ + int c, track; + char name[MAX_OSPATH]; + + c = Cmd_Argc(); + if (c != 2 && c != 3 && c != 4) + { + Con_Print("record [ [cd track]]\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Print("Relative pathnames are not allowed.\n"); + return; + } + + if (c == 2 && cls.state == ca_connected) + { + Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n"); + return; + } + + if (cls.state == ca_connected) + CL_Disconnect(); + + // write the forced cd track number, or -1 + if (c == 4) + { + track = atoi(Cmd_Argv(3)); + Con_Printf("Forcing CD track to %i\n", cls.forcetrack); + } + else + track = -1; + + // get the demo name + strlcpy (name, Cmd_Argv(1), sizeof (name)); + FS_DefaultExtension (name, ".dem", sizeof (name)); + + // start the map up + if (c > 2) + Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command); + + // open the demo file + Con_Printf("recording to %s.\n", name); + cls.demofile = FS_OpenRealFile(name, "wb", false); + if (!cls.demofile) + { + Con_Print("ERROR: couldn't open.\n"); + return; + } + strlcpy(cls.demoname, name, sizeof(cls.demoname)); + + cls.forcetrack = track; + FS_Printf(cls.demofile, "%i\n", cls.forcetrack); + + cls.demorecording = true; + cls.demo_lastcsprogssize = -1; + cls.demo_lastcsprogscrc = -1; +} + + +/* +==================== +CL_PlayDemo_f + +play [demoname] +==================== +*/ +void CL_PlayDemo_f (void) +{ + char name[MAX_QPATH]; + int c; + qboolean neg = false; + + if (Cmd_Argc() != 2) + { + Con_Print("play : plays a demo\n"); + return; + } + + // disconnect from server + CL_Disconnect (); + Host_ShutdownServer (); + + // update networking ports (this is mainly just needed at startup) + NetConn_UpdateSockets(); + + // open the demo file + strlcpy (name, Cmd_Argv(1), sizeof (name)); + FS_DefaultExtension (name, ".dem", sizeof (name)); + cls.protocol = PROTOCOL_QUAKE; + + Con_Printf("Playing demo %s.\n", name); + cls.demofile = FS_OpenVirtualFile(name, false); + if (!cls.demofile) + { + Con_Print("ERROR: couldn't open.\n"); + cls.demonum = -1; // stop demo loop + return; + } + strlcpy(cls.demoname, name, sizeof(cls.demoname)); + + cls.demoplayback = true; + cls.state = ca_connected; + cls.forcetrack = 0; + + while ((c = FS_Getc (cls.demofile)) != '\n') + if (c == '-') + neg = true; + else + cls.forcetrack = cls.forcetrack * 10 + (c - '0'); + + if (neg) + cls.forcetrack = -cls.forcetrack; +} + +/* +==================== +CL_FinishTimeDemo + +==================== +*/ +void CL_FinishTimeDemo (void) +{ + int frames; + int i; + double time, totalfpsavg; + double fpsmin, fpsavg, fpsmax; // report min/avg/max fps + static int benchmark_runs = 0; + + cls.timedemo = false; + + frames = cls.td_frames; + time = realtime - cls.td_starttime; + totalfpsavg = time > 0 ? frames / time : 0; + fpsmin = cls.td_onesecondminfps; + fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0; + fpsmax = cls.td_onesecondmaxfps; + // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max + Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); + Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | run %d | result %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), buildstring, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); + if (COM_CheckParm("-benchmark")) + { + ++benchmark_runs; + i = COM_CheckParm("-benchmarkruns"); + if(i && i + 1 < com_argc) + { + if(atoi(com_argv[i + 1]) > benchmark_runs) + { + // restart the benchmark + Cbuf_AddText(va("timedemo %s\n", cls.demoname)); + // cannot execute here + } + else + Host_Quit_f(); + } + else + Host_Quit_f(); + } +} + +/* +==================== +CL_TimeDemo_f + +timedemo [demoname] +==================== +*/ +void CL_TimeDemo_f (void) +{ + if (Cmd_Argc() != 2) + { + Con_Print("timedemo : gets demo speeds\n"); + return; + } + + srand(0); // predictable random sequence for benchmarking + + CL_PlayDemo_f (); + +// cls.td_starttime will be grabbed at the second frame of the demo, so +// all the loading time doesn't get counted + + // instantly hide console and deactivate it + key_dest = key_game; + key_consoleactive = 0; + scr_con_current = 0; + + cls.timedemo = true; + cls.td_frames = -2; // skip the first frame + cls.demonum = -1; // stop demo loop + cls.demonum = -1; // stop demo loop +} + diff --git a/misc/source/darkplaces-src/cl_dyntexture.c b/misc/source/darkplaces-src/cl_dyntexture.c new file mode 100644 index 00000000..fb378def --- /dev/null +++ b/misc/source/darkplaces-src/cl_dyntexture.c @@ -0,0 +1,93 @@ +// Andreas Kirsch 07 + +#include "quakedef.h" +#include "cl_dyntexture.h" + +typedef struct dyntexture_s { + // everything after DYNAMIC_TEXTURE_PATH_PREFIX + char name[ MAX_QPATH + 32 ]; + // texture pointer (points to r_texture_white at first) + rtexture_t *texture; +} dyntexture_t; + +static dyntexture_t dyntextures[ MAX_DYNAMIC_TEXTURE_COUNT ]; +static unsigned dyntexturecount; + +#define DEFAULT_DYNTEXTURE r_texture_grey128 + +static dyntexture_t * cl_finddyntexture( const char *name, qboolean warnonfailure ) { + unsigned i; + dyntexture_t *dyntexture = NULL; + + // sanity checks - make sure its actually a dynamic texture path + if( !name || !*name || strncmp( name, CLDYNTEXTUREPREFIX, sizeof( CLDYNTEXTUREPREFIX ) - 1 ) != 0 ) { + // TODO: print a warning or something + if (warnonfailure) + Con_Printf( "cl_finddyntexture: Bad dynamic texture name '%s'\n", name ); + return NULL; + } + + for( i = 0 ; i < dyntexturecount ; i++ ) { + dyntexture = &dyntextures[ i ]; + if( dyntexture->name && strcmp( dyntexture->name, name ) == 0 ) { + return dyntexture; + } + } + + if( dyntexturecount == MAX_DYNAMIC_TEXTURE_COUNT ) { + // TODO: warn or expand the array, etc. + return NULL; + } + dyntexture = &dyntextures[ dyntexturecount++ ]; + strlcpy( dyntexture->name, name, sizeof( dyntexture->name ) ); + dyntexture->texture = DEFAULT_DYNTEXTURE; + return dyntexture; +} + +rtexture_t * CL_GetDynTexture( const char *name ) { + dyntexture_t *dyntexture = cl_finddyntexture( name, false ); + if( dyntexture ) { + return dyntexture->texture; + } else { + return NULL; + } +} + +void CL_LinkDynTexture( const char *name, rtexture_t *texture ) { + dyntexture_t *dyntexture; + cachepic_t *cachepic; + skinframe_t *skinframe; + + dyntexture = cl_finddyntexture( name, true ); + if( !dyntexture ) { + Con_Printf( "CL_LinkDynTexture: internal error in cl_finddyntexture!\n" ); + return; + } + // TODO: assert dyntexture != NULL! + if( dyntexture->texture != texture ) { + dyntexture->texture = texture; + + cachepic = Draw_CachePic_Flags( name, CACHEPICFLAG_NOTPERSISTENT ); + // TODO: assert cachepic and skinframe should be valid pointers... + // TODO: assert cachepic->tex = dyntexture->texture + cachepic->tex = texture; + // update cachepic's size, too + cachepic->width = R_TextureWidth( texture ); + cachepic->height = R_TextureHeight( texture ); + + // update skinframes + skinframe = NULL; + while( (skinframe = R_SkinFrame_FindNextByName( skinframe, name )) != NULL ) { + skinframe->base = texture; + // simply reset the compare* attributes of skinframe + skinframe->comparecrc = 0; + skinframe->comparewidth = skinframe->compareheight = 0; + // this is kind of hacky + } + } +} + +void CL_UnlinkDynTexture( const char *name ) { + CL_LinkDynTexture( name, DEFAULT_DYNTEXTURE ); +} + diff --git a/misc/source/darkplaces-src/cl_dyntexture.h b/misc/source/darkplaces-src/cl_dyntexture.h new file mode 100644 index 00000000..130dc167 --- /dev/null +++ b/misc/source/darkplaces-src/cl_dyntexture.h @@ -0,0 +1,20 @@ +// Andreas 'Black' Kirsch 07 +#ifndef CL_DYNTEXTURE_H +#define CL_DYNTEXTURE_H + +#define CLDYNTEXTUREPREFIX "_dynamic/" + +// always path fully specified names to the dynamic texture functions! (ie. with the _dynamic/ prefix, etc.!) + +// return a valid texture handle for a dynamic texture (might be filler texture if it hasnt been initialized yet) +// or NULL if its not a valid dynamic texture name +rtexture_t * CL_GetDynTexture( const char *name ); + +// link a texture handle as dynamic texture and update texture handles in the renderer and draw_* accordingly +void CL_LinkDynTexture( const char *name, rtexture_t *texture ); + +// unlink a texture handle from its name +void CL_UnlinkDynTexture( const char *name ); + +#endif + diff --git a/misc/source/darkplaces-src/cl_gecko.c b/misc/source/darkplaces-src/cl_gecko.c new file mode 100644 index 00000000..e66482d6 --- /dev/null +++ b/misc/source/darkplaces-src/cl_gecko.c @@ -0,0 +1,1010 @@ +/* --- 8< --- 8< --- OffscreenGecko headers --- >8 --- >8 --- */ +/* NOTE: Documentation comments have been omitted. Documentation is + available in the OffscreenGecko SDK download. */ + +/* OffscreenGecko/defs.h */ + +#define OSGK_CLASSTYPE_DEF struct +#define OSGK_CLASSTYPE_REF struct + +#include +#include +#define OSGK_ASSERT(x) assert(x) + +typedef unsigned int OSGK_GeckoResult; + +#if defined(__cplusplus) || defined(__GNUC__) +# define OSGK_INLINE inline +#elif defined(_MSC_VER) +# define OSGK_INLINE __inline +#else +# define OSGK_INLINE +#endif + +/* OffscreenGecko/baseobj.h */ + +struct OSGK_BaseObject_s +{ + int reserved; +}; +typedef struct OSGK_BaseObject_s OSGK_BaseObject; + +#define OSGK_DERIVEDTYPE(T) \ + typedef struct T ## _s { \ + OSGK_BaseObject baseobj; \ + } T + +static int (*osgk_addref) (OSGK_BaseObject* obj); +static int (*osgk_release) (OSGK_BaseObject* obj); + +/*static OSGK_INLINE int osgk_addref_real (OSGK_BaseObject* obj) +{ + return osgk_addref (obj); +}*/ + +static OSGK_INLINE int osgk_release_real (OSGK_BaseObject* obj) +{ + return osgk_release (obj); +} + +#define osgk_addref(obj) osgk_addref_real (&((obj)->baseobj)) +#define osgk_release(obj) osgk_release_real (&((obj)->baseobj)) + +/* OffscreenGecko/embedding.h */ + +OSGK_DERIVEDTYPE(OSGK_EmbeddingOptions); + +static OSGK_EmbeddingOptions* (*osgk_embedding_options_create) (void); +static void (*osgk_embedding_options_add_search_path) ( + OSGK_EmbeddingOptions* options, const char* path); +/*static void (*osgk_embedding_options_add_components_path) ( + OSGK_EmbeddingOptions* options, const char* path);*/ +static void (*osgk_embedding_options_set_profile_dir) ( + OSGK_EmbeddingOptions* options, const char* profileDir, + const char* localProfileDir); + +OSGK_DERIVEDTYPE(OSGK_Embedding); + +#define OSGK_API_VERSION 1 + +static OSGK_Embedding* (*osgk_embedding_create2) ( + unsigned int apiVer, OSGK_EmbeddingOptions* options, + OSGK_GeckoResult* geckoResult); + +/*static OSGK_INLINE OSGK_Embedding* osgk_embedding_create ( + OSGK_GeckoResult* geckoResult) +{ + return osgk_embedding_create2 (OSGK_API_VERSION, 0, geckoResult); +}*/ + +static OSGK_INLINE OSGK_Embedding* osgk_embedding_create_with_options ( + OSGK_EmbeddingOptions* options, OSGK_GeckoResult* geckoResult) +{ + return osgk_embedding_create2 (OSGK_API_VERSION, options, geckoResult); +} + +/*static OSGK_GeckoMem* (*osgk_embedding_get_gecko_mem) ( + OSGK_Embedding* embedding);*/ + +/*static OSGK_ComponentMgr* (*osgk_embedding_get_component_mgr) ( + OSGK_Embedding* embedding);*/ + +/*OSGK_CLASSTYPE_DEF nsIComponentManager; +OSGK_CLASSTYPE_DEF nsIComponentRegistrar; +OSGK_CLASSTYPE_DEF nsIServiceManager;*/ + +/*static OSGK_CLASSTYPE_REF nsIComponentManager* +(*osgk_embedding_get_gecko_component_manager) (OSGK_Embedding* embedding);*/ +/*static OSGK_CLASSTYPE_REF nsIComponentRegistrar* +(*osgk_embedding_get_gecko_component_registrar) (OSGK_Embedding* embedding);*/ +/*static OSGK_CLASSTYPE_REF nsIServiceManager* +(*osgk_embedding_get_gecko_service_manager) (OSGK_Embedding* embedding);*/ + +enum +{ + jsgPrivileged = 1 +}; +/*static int (*osgk_embedding_register_js_global) ( + OSGK_Embedding* embedding, const char* name, const char* contractID, + unsigned int flags, OSGK_String** previousContract, + OSGK_GeckoResult* geckoResult);*/ + +/*static void (*osgk_embedding_clear_focus*) (OSGK_Embedding* embedding);*/ +/*void (*osgk_embedding_set_auto_focus) (OSGK_Embedding* embedding, int autoFocus);*/ +/*static int (*osgk_embedding_get_auto_focus) (OSGK_Embedding* embedding);*/ + +/* OffscreenGecko/browser.h */ +OSGK_DERIVEDTYPE(OSGK_Browser); + +static OSGK_Browser* (*osgk_browser_create) ( + OSGK_Embedding* embedding, int width, int height); +static void (*osgk_browser_navigate) (OSGK_Browser* browser, + const char* uri); + +static int (*osgk_browser_query_dirty) (OSGK_Browser* browser); +static const unsigned char* (*osgk_browser_lock_data) ( + OSGK_Browser* browser, int* isDirty); +static void (*osgk_browser_unlock_data) (OSGK_Browser* browser, + const unsigned char* data); + +typedef enum OSGK_MouseButton +{ + mbLeft, + mbRight, + mbMiddle +} OSGK_MouseButton; + +typedef enum OSGK_MouseButtonEventType +{ + meDown, + meUp, + meDoubleClick +} OSGK_MouseButtonEventType; + +static void (*osgk_browser_event_mouse_move) ( + OSGK_Browser* browser, int x, int y); +static void (*osgk_browser_event_mouse_button) ( + OSGK_Browser* browser, OSGK_MouseButton button, + OSGK_MouseButtonEventType eventType); + +typedef enum OSGK_WheelAxis +{ + waVertical, + waHorizontal +} OSGK_WheelAxis; + +typedef enum OSGK_WheelDirection +{ + wdPositive, + wdNegative, + wdPositivePage, + wdNegativePage +} OSGK_WheelDirection; + +static void (*osgk_browser_event_mouse_wheel) ( + OSGK_Browser* browser, OSGK_WheelAxis axis, + OSGK_WheelDirection direction); + +typedef enum OSGK_KeyboardEventType +{ + keDown, + keUp, + kePress +} OSGK_KeyboardEventType; + +enum +{ + OSGKKey_First = 0x110000, + + OSGKKey_Backspace = OSGKKey_First, + OSGKKey_Tab, + OSGKKey_Return, + OSGKKey_Shift, + OSGKKey_Control, + OSGKKey_Alt, + OSGKKey_CapsLock, + OSGKKey_Escape, + OSGKKey_Space, + OSGKKey_PageUp, + OSGKKey_PageDown, + OSGKKey_End, + OSGKKey_Home, + OSGKKey_Left, + OSGKKey_Up, + OSGKKey_Right, + OSGKKey_Down, + OSGKKey_Insert, + OSGKKey_Delete, + OSGKKey_F1, + OSGKKey_F2, + OSGKKey_F3, + OSGKKey_F4, + OSGKKey_F5, + OSGKKey_F6, + OSGKKey_F7, + OSGKKey_F8, + OSGKKey_F9, + OSGKKey_F10, + OSGKKey_F11, + OSGKKey_F12, + OSGKKey_NumLock, + OSGKKey_ScrollLock, + OSGKKey_Meta +}; + +static int (*osgk_browser_event_key) ( + OSGK_Browser* browser, unsigned int key, + OSGK_KeyboardEventType eventType); + +typedef enum OSGK_AntiAliasType +{ + aaNone, + aaGray, + aaSubpixel +} OSGK_AntiAliasType; + +/*static void (*osgk_browser_set_antialias) ( + OSGK_Browser* browser, OSGK_AntiAliasType aaType);*/ +/*static OSGK_AntiAliasType (*osgk_browser_get_antialias) (OSGK_Browser* browser);*/ + +/*static void (*osgk_browser_focus) (OSGK_Browser* browser);*/ + +static void (*osgk_browser_resize) (OSGK_Browser* browser, + int width, int height); + +static int (*osgk_browser_set_user_data) (OSGK_Browser* browser, + unsigned int key, void* data, int overrideData); +static int (*osgk_browser_get_user_data) (OSGK_Browser* browser, + unsigned int key, void** data); + +/* OffscreenGecko/string.h */ +OSGK_DERIVEDTYPE(OSGK_String); + +static const char* (*osgk_string_get) (OSGK_String* str); + +static OSGK_String* (*osgk_string_create) (const char* str); + +/* OffscreenGecko/scriptvariant.h */ + +OSGK_CLASSTYPE_DEF nsISupports; + +OSGK_DERIVEDTYPE(OSGK_ScriptVariant); + +typedef enum OSGK_ScriptVariantType +{ + svtEmpty, + svtInt, + svtUint, + svtFloat, + svtDouble, + svtBool, + svtChar, + svtString, + svtISupports, + svtScriptObject, + svtArray +} OSGK_ScriptVariantType; + +/*static OSGK_ScriptVariantType (*osgk_variant_get_type) ( + OSGK_ScriptVariant* variant);*/ +static OSGK_ScriptVariant* (*osgk_variant_convert) ( + OSGK_ScriptVariant* variant, OSGK_ScriptVariantType newType); + +/*static int (*osgk_variant_get_int) (OSGK_ScriptVariant* variant, + int* val);*/ +/*static int (*osgk_variant_get_uint) (OSGK_ScriptVariant* variant, + unsigned int* val);*/ +/*static int (*osgk_variant_get_float) (OSGK_ScriptVariant* variant, + float* val);*/ +/*static int (*osgk_variant_get_double) (OSGK_ScriptVariant* variant, + double* val);*/ +/*static int (*osgk_variant_get_bool) (OSGK_ScriptVariant* variant, + int* val);*/ +/*static int (*osgk_variant_get_char) (OSGK_ScriptVariant* variant, + unsigned int* val);*/ +// Does not increase ref count +static int (*osgk_variant_get_string) (OSGK_ScriptVariant* variant, + OSGK_String** val); +/*// Does not increase ref count +static int (*osgk_variant_get_isupports) (OSGK_ScriptVariant* variant, + OSGK_CLASSTYPE_REF nsISupports** val);*/ +/*static int (*osgk_variant_get_script_object) (OSGK_ScriptVariant* variant, + void** tag);*/ +/*static int (*osgk_variant_get_array_size) (OSGK_ScriptVariant* variant, + size_t* size);*/ +/*static int (*osgk_variant_get_array_item) (OSGK_ScriptVariant* variant, + OSGK_ScriptVariant** val);*/ + +/*static OSGK_ScriptVariant* (*osgk_variant_create_empty) ( + OSGK_Embedding* embedding);*/ +/*static OSGK_ScriptVariant* (*osgk_variant_create_int) ( + OSGK_Embedding* embedding, int val);*/ +/*static OSGK_ScriptVariant* (*osgk_variant_create_uint) ( + OSGK_Embedding* embedding, unsigned int val);*/ +/*static OSGK_ScriptVariant* (*osgk_variant_create_float) ( + OSGK_Embedding* embedding, float val);*/ +/*static OSGK_ScriptVariant* (*osgk_variant_create_double) ( + OSGK_Embedding* embedding, double val);*/ +/*OSGK_ScriptVariant* (*osgk_variant_create_bool) ( + OSGK_Embedding* embedding, int val);*/ +/*static OSGK_ScriptVariant* (*osgk_variant_create_char) ( + OSGK_Embedding* embedding, unsigned int val);*/ +static OSGK_ScriptVariant* (*osgk_variant_create_string) ( + OSGK_Embedding* embedding, OSGK_String* val); +/*static OSGK_ScriptVariant* (*osgk_variant_create_isupports) ( + OSGK_Embedding* embedding, OSGK_CLASSTYPE_REF nsISupports* val);*/ +/*static OSGK_ScriptVariant* (*osgk_variant_create_script_object) ( + OSGK_Embedding* embedding, void* tag);*/ +/*static OSGK_ScriptVariant* (*osgk_variant_create_array) ( + OSGK_Embedding* embedding, size_t numItems, OSGK_ScriptVariant** items);*/ + +/* OffscreenGecko/scriptobjecttemplate.h */ + +OSGK_DERIVEDTYPE(OSGK_ScriptObjectTemplate); + +typedef enum OSGK_ScriptResult +{ + srSuccess = 0, + srFailed = (int)0x80004005L /* actually NS_ERROR_FAILURE */ +} OSGK_ScriptResult; + +typedef struct OSGK_ScriptObjectCreateParams_s +{ + void* createParam; + OSGK_Browser* browser; +} OSGK_ScriptObjectCreateParams; + +typedef OSGK_ScriptResult (*OSGK_CreateObjFunc) ( + OSGK_ScriptObjectCreateParams* params, void** objTag); +typedef void (*OSGK_DestroyObjFunc) (void* objTag); + +static OSGK_ScriptObjectTemplate* (*osgk_sot_create) ( + OSGK_Embedding* embedding, + OSGK_CreateObjFunc createFunc, OSGK_DestroyObjFunc destroyFunc, + void* createParam); + +typedef OSGK_ScriptVariant* (*OSGK_GetPropertyFunc) (void* objTag, + void* propTag); +typedef OSGK_ScriptResult (*OSGK_SetPropertyFunc) (void* objTag, + void* propTag, OSGK_ScriptVariant* val); + +/*static int (*osgk_sot_add_property) ( + OSGK_ScriptObjectTemplate* templ, const char* propName, void* propTag, + OSGK_GetPropertyFunc getter, OSGK_SetPropertyFunc setter);*/ + +typedef OSGK_ScriptResult (*OSGK_FunctionCallFunc) (void* objTag, + void* methTag, size_t numParams, OSGK_ScriptVariant** params, + OSGK_ScriptVariant** returnVal); + +static int (*osgk_sot_add_function) ( + OSGK_ScriptObjectTemplate* templ, const char* funcName, void* funcTag, + OSGK_FunctionCallFunc callFunc); + +/*static OSGK_ScriptVariant* (*osgk_sot_instantiate) ( + OSGK_ScriptObjectTemplate* templ, void** objTag); */ + +static int (*osgk_sot_register) ( + OSGK_ScriptObjectTemplate* templ, OSGK_Embedding* embedding, + const char* name, unsigned int flags); + +/* --- >8 --- >8 --- End OffscreenGecko headers --- 8< --- 8< --- */ + +#include "quakedef.h" +#include "cl_dyntexture.h" +#include "cl_gecko.h" +#include "timing.h" + +#define DEFAULT_GECKO_SIZE 512 + +static rtexturepool_t *cl_geckotexturepool; +static OSGK_Embedding *cl_geckoembedding; + +struct clgecko_s { + qboolean active; + char name[ MAX_QPATH + 32 ]; + int ownerProg; + + OSGK_Browser *browser; + int width, height; + int texWidth, texHeight; + + rtexture_t *texture; +}; + +#define USERDATAKEY_CL_GECKO_T 0 + +static dllhandle_t osgk_dll = NULL; + +static clgecko_t cl_geckoinstances[ MAX_GECKO_INSTANCES ]; + +static clgecko_t * cl_gecko_findunusedinstance( void ) { + int i; + for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) { + clgecko_t *instance = &cl_geckoinstances[ i ]; + if( !instance->active ) { + return instance; + } + } + Con_DPrintf( "cl_gecko_findunusedinstance: out of geckos\n" ); + return NULL; +} + +clgecko_t * CL_Gecko_FindBrowser( const char *name ) { + int i; + + if( !name || !*name || strncmp( name, CLGECKOPREFIX, sizeof( CLGECKOPREFIX ) - 1 ) != 0 ) { + Con_DPrintf( "CL_Gecko_FindBrowser: Bad gecko texture name '%s'!\n", name ); + return NULL; + } + + for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) { + clgecko_t *instance = &cl_geckoinstances[ i ]; + if( instance->active && strcmp( instance->name, name ) == 0 ) { + return instance; + } + } + + Con_DPrintf( "CL_Gecko_FindBrowser: No browser named '%s'!\n", name ); + + return NULL; +} + +static void cl_gecko_updatecallback( rtexture_t *texture, void* callbackData ) { + clgecko_t *instance = (clgecko_t *) callbackData; + const unsigned char *data; + if( instance->browser ) { + // TODO: OSGK only supports BGRA right now + TIMING_TIMESTATEMENT(data = osgk_browser_lock_data( instance->browser, NULL )); + R_UpdateTexture( texture, data, 0, 0, 0, instance->width, instance->height, 1 ); + osgk_browser_unlock_data( instance->browser, data ); + } +} + +static void cl_gecko_linktexture( clgecko_t *instance ) { + // TODO: assert that instance->texture == NULL + instance->texture = R_LoadTexture2D( cl_geckotexturepool, instance->name, + instance->texWidth, instance->texHeight, NULL, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PERSISTENT, -1, NULL ); + R_MakeTextureDynamic( instance->texture, cl_gecko_updatecallback, instance ); + CL_LinkDynTexture( instance->name, instance->texture ); +} + +static void cl_gecko_unlinktexture( clgecko_t *instance ) { + if( instance->texture ) { + CL_UnlinkDynTexture( instance->name ); + R_FreeTexture( instance->texture ); + instance->texture = NULL; + } +} + +void CL_Gecko_Resize( clgecko_t *instance, int width, int height ) { + int newWidth, newHeight; + + // early out if bad parameters are passed (no resize to a texture size bigger than the original texture size) + if( !instance || !instance->browser) { + return; + } + + newWidth = CeilPowerOf2( width ); + newHeight = CeilPowerOf2( height ); + if ((newWidth != instance->texWidth) || (newHeight != instance->texHeight)) + { + cl_gecko_unlinktexture( instance ); + instance->texWidth = newWidth; + instance->texHeight = newHeight; + cl_gecko_linktexture( instance ); + } + else + { + /* The gecko area will only cover a part of the texture; to avoid + 'old' pixels bleeding in at the border clear the texture. */ + R_ClearTexture( instance->texture ); + } + + osgk_browser_resize( instance->browser, width, height); + instance->width = width; + instance->height = height; +} + +void CL_Gecko_GetTextureExtent( clgecko_t *instance, float* pwidth, float* pheight ) +{ + if( !instance || !instance->browser ) { + return; + } + + *pwidth = (float)instance->width / instance->texWidth; + *pheight = (float)instance->height / instance->texHeight; +} + +static OSGK_ScriptResult dpGlobal_create (OSGK_ScriptObjectCreateParams* params, + void** objTag) +{ + if (!osgk_browser_get_user_data (params->browser, USERDATAKEY_CL_GECKO_T, objTag)) + return srFailed; + return srSuccess; +} + +static OSGK_ScriptResult dpGlobal_query (void* objTag, void* methTag, + size_t numParams, + OSGK_ScriptVariant** params, + OSGK_ScriptVariant** returnVal) +{ + clgecko_t *instance = (clgecko_t *) objTag; + OSGK_ScriptVariant* strVal; + OSGK_ScriptResult result = srFailed; + prvm_prog_t * saveProg; + + /* Can happen when created from console */ + if (instance->ownerProg < 0) return srFailed; + + /* Require exactly one param, for now */ + if (numParams != 1) return srFailed; + + strVal = osgk_variant_convert (params[0], svtString); + if (strVal == 0) return srFailed; + + saveProg = prog; + PRVM_SetProg(instance->ownerProg); + + if (PRVM_clientfunction(Gecko_Query)) + { + OSGK_String* paramStr, *resultStr; + + if (!osgk_variant_get_string (strVal, ¶mStr)) return srFailed; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString (instance->name); + PRVM_G_INT(OFS_PARM1) = PRVM_SetTempString (osgk_string_get (paramStr)); + PRVM_ExecuteProgram(PRVM_clientfunction(Gecko_Query),"Gecko_Query() required"); + resultStr = osgk_string_create (PRVM_G_STRING (OFS_RETURN)); + *returnVal = osgk_variant_create_string (cl_geckoembedding, resultStr); + osgk_release (resultStr); + + result = srSuccess; + } + + prog = saveProg; + + return result; +} + +#if defined(_WIN64) +# define XULRUNNER_DIR_SUFFIX "win64" +#elif defined(WIN32) +# define XULRUNNER_DIR_SUFFIX "win32" +#elif defined(DP_OS_STR) && defined(DP_ARCH_STR) +# define XULRUNNER_DIR_SUFFIX DP_OS_STR "-" DP_ARCH_STR +#endif + +static qboolean CL_Gecko_Embedding_Init (void) +{ + char profile_path [MAX_OSPATH]; + OSGK_GeckoResult grc; + OSGK_EmbeddingOptions *options; + OSGK_ScriptObjectTemplate* dpGlobalTemplate; + + if (!osgk_dll) return false; + + if( cl_geckoembedding != NULL ) return true; + + Con_DPrintf( "CL_Gecko_Embedding_Init: setting up gecko embedding\n" ); + + options = osgk_embedding_options_create(); +#ifdef XULRUNNER_DIR_SUFFIX + osgk_embedding_options_add_search_path( options, "./xulrunner-" XULRUNNER_DIR_SUFFIX "/" ); +#endif + osgk_embedding_options_add_search_path( options, "./xulrunner/" ); + dpsnprintf (profile_path, sizeof (profile_path), "%s/xulrunner_profile/", fs_gamedir); + osgk_embedding_options_set_profile_dir( options, profile_path, 0 ); + cl_geckoembedding = osgk_embedding_create_with_options( options, &grc ); + osgk_release( options ); + + if( cl_geckoembedding == NULL ) { + Con_Printf( "CL_Gecko_Embedding_Init: Couldn't retrieve gecko embedding object (%.8x)!\n", grc ); + return false; + } + + Con_DPrintf( "CL_Gecko_Embedding_Init: Embedding set up correctly\n" ); + + dpGlobalTemplate = osgk_sot_create( cl_geckoembedding, dpGlobal_create, NULL, NULL ); + + osgk_sot_add_function (dpGlobalTemplate, "query", 0, dpGlobal_query); + + osgk_sot_register (dpGlobalTemplate, cl_geckoembedding, "Darkplaces", 0); + osgk_release( dpGlobalTemplate ); + + return true; +} + +clgecko_t * CL_Gecko_CreateBrowser( const char *name, int ownerProg ) { + clgecko_t *instance; + + if (!CL_Gecko_Embedding_Init ()) return NULL; + + // TODO: verify that we dont use a name twice + instance = cl_gecko_findunusedinstance(); + // TODO: assert != NULL + + instance->active = true; + strlcpy( instance->name, name, sizeof( instance->name ) ); + instance->browser = osgk_browser_create( cl_geckoembedding, DEFAULT_GECKO_SIZE, DEFAULT_GECKO_SIZE ); + if( instance->browser == NULL ) { + Con_Printf( "CL_Gecko_CreateBrowser: Browser object creation failed!\n" ); + } + // TODO: assert != NULL + osgk_browser_set_user_data (instance->browser, USERDATAKEY_CL_GECKO_T, + instance, 0); + instance->ownerProg = ownerProg; + + instance->width = instance->texWidth = DEFAULT_GECKO_SIZE; + instance->height = instance->texHeight = DEFAULT_GECKO_SIZE; + cl_gecko_linktexture( instance ); + + return instance; +} + +void CL_Gecko_DestroyBrowser( clgecko_t *instance ) { + if( !instance || !instance->active ) { + return; + } + + instance->active = false; + cl_gecko_unlinktexture( instance ); + + osgk_release( instance->browser ); + instance->browser = NULL; +} + +void CL_Gecko_Frame( void ) { + int i; + // FIXME: track cl_numgeckoinstances to avoid scanning the entire array? + for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) { + clgecko_t *instance = &cl_geckoinstances[ i ]; + if( instance->active ) { + if( instance->browser && osgk_browser_query_dirty( instance->browser ) == 1 ) { + R_MarkDirtyTexture( instance->texture ); + } + } + } +} + +static void cl_gecko_start( void ) +{ + int i; + cl_geckotexturepool = R_AllocTexturePool(); + + // recreate textures on module start + for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) { + clgecko_t *instance = &cl_geckoinstances[ i ]; + if( instance->active ) { + cl_gecko_linktexture( instance ); + } + } +} + +static void cl_gecko_shutdown( void ) +{ + int i; + for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) { + clgecko_t *instance = &cl_geckoinstances[ i ]; + if( instance->active ) { + cl_gecko_unlinktexture( instance ); + } + } + R_FreeTexturePool( &cl_geckotexturepool ); +} + +static void cl_gecko_newmap( void ) +{ + // DO NOTHING +} + +void CL_Gecko_Shutdown( void ) { + int i; + for( i = 0 ; i < MAX_GECKO_INSTANCES ; i++ ) { + clgecko_t *instance = &cl_geckoinstances[ i ]; + if( instance->active ) { + cl_gecko_unlinktexture( instance ); + } + } + + if (cl_geckoembedding != NULL) + { + osgk_release( cl_geckoembedding ); + cl_geckoembedding = NULL; + } + + if (osgk_dll != NULL) + { + Sys_UnloadLibrary (&osgk_dll); + } +} + +static void cl_gecko_create_f( void ) { + char name[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Con_Print("usage: gecko_create \npcreates a browser (full texture path " CLGECKOPREFIX ")\n"); + return; + } + + dpsnprintf(name, sizeof(name), CLGECKOPREFIX "%s", Cmd_Argv(1)); + CL_Gecko_CreateBrowser( name, -1 ); +} + +static void cl_gecko_destroy_f( void ) { + char name[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Con_Print("usage: gecko_destroy \ndestroys a browser\n"); + return; + } + + dpsnprintf(name, sizeof(name), CLGECKOPREFIX "%s", Cmd_Argv(1)); + CL_Gecko_DestroyBrowser( CL_Gecko_FindBrowser( name ) ); +} + +static void cl_gecko_navigate_f( void ) { + char name[MAX_QPATH]; + const char *URI; + + if (Cmd_Argc() != 3) + { + Con_Print("usage: gecko_navigate \nnavigates to a certain URI\n"); + return; + } + + dpsnprintf(name, sizeof(name), CLGECKOPREFIX "%s", Cmd_Argv(1)); + URI = Cmd_Argv( 2 ); + CL_Gecko_NavigateToURI( CL_Gecko_FindBrowser( name ), URI ); +} + +static void cl_gecko_injecttext_f( void ) { + char name[MAX_QPATH]; + const char *text; + clgecko_t *instance; + const char *p; + + if (Cmd_Argc() < 3) + { + Con_Print("usage: gecko_injecttext \ninjects a certain text into the browser\n"); + return; + } + + dpsnprintf(name, sizeof(name), CLGECKOPREFIX "%s", Cmd_Argv(1)); + instance = CL_Gecko_FindBrowser( name ); + if( !instance ) { + Con_Printf( "cl_gecko_injecttext_f: gecko instance '%s' couldn't be found!\n", name ); + return; + } + + text = Cmd_Argv( 2 ); + + for( p = text ; *p ; p++ ) { + unsigned key = *p; + switch( key ) { + case ' ': + key = K_SPACE; + break; + case '\\': + key = *++p; + switch( key ) { + case 'n': + key = K_ENTER; + break; + case '\0': + --p; + key = '\\'; + break; + } + break; + } + + CL_Gecko_Event_Key( instance, (keynum_t) key, CLG_BET_PRESS ); + } +} + +static void gl_gecko_movecursor_f( void ) { + char name[MAX_QPATH]; + float x, y; + + if (Cmd_Argc() != 4) + { + Con_Print("usage: gecko_movecursor \nmove the cursor to a certain position\n"); + return; + } + + dpsnprintf(name, sizeof(name), CLGECKOPREFIX "%s", Cmd_Argv(1)); + x = atof( Cmd_Argv( 2 ) ); + y = atof( Cmd_Argv( 3 ) ); + + CL_Gecko_Event_CursorMove( CL_Gecko_FindBrowser( name ), x, y ); +} + +#undef osgk_addref +#undef osgk_release + +static const dllfunction_t osgkFuncs[] = +{ + {"osgk_addref", (void **) &osgk_addref}, + {"osgk_release", (void **) &osgk_release}, + {"osgk_embedding_create2", (void **) &osgk_embedding_create2}, + {"osgk_embedding_options_create", (void **) &osgk_embedding_options_create}, + {"osgk_embedding_options_add_search_path", (void **) &osgk_embedding_options_add_search_path}, + {"osgk_embedding_options_set_profile_dir", (void **) &osgk_embedding_options_set_profile_dir}, + {"osgk_browser_create", (void **) &osgk_browser_create}, + {"osgk_browser_query_dirty", (void **) &osgk_browser_query_dirty}, + {"osgk_browser_navigate", (void **) &osgk_browser_navigate}, + {"osgk_browser_lock_data", (void **) &osgk_browser_lock_data}, + {"osgk_browser_unlock_data", (void **) &osgk_browser_unlock_data}, + {"osgk_browser_resize", (void **) &osgk_browser_resize}, + {"osgk_browser_event_mouse_move", (void **) &osgk_browser_event_mouse_move}, + {"osgk_browser_event_mouse_button", (void **) &osgk_browser_event_mouse_button}, + {"osgk_browser_event_mouse_wheel", (void **) &osgk_browser_event_mouse_wheel}, + {"osgk_browser_event_key", (void **) &osgk_browser_event_key}, + {"osgk_browser_set_user_data", (void **) &osgk_browser_set_user_data}, + {"osgk_browser_get_user_data", (void **) &osgk_browser_get_user_data}, + {"osgk_sot_create", (void **) &osgk_sot_create}, + {"osgk_sot_register", (void **) &osgk_sot_register}, + {"osgk_sot_add_function", (void **) &osgk_sot_add_function}, + {"osgk_string_get", (void **) &osgk_string_get}, + {"osgk_string_create", (void **) &osgk_string_create}, + {"osgk_variant_convert", (void **) &osgk_variant_convert}, + {"osgk_variant_get_string", (void **) &osgk_variant_get_string}, + {"osgk_variant_create_string", (void **) &osgk_variant_create_string}, + {NULL, NULL} +}; + +qboolean CL_Gecko_OpenLibrary (void) +{ + const char* dllnames_gecko [] = + { +#if defined(WIN32) + "OffscreenGecko.dll", +#elif defined(MACOSX) + "OffscreenGecko.dylib", +#else + "libOffscreenGecko.so", +#endif + NULL + }; + + // Already loaded? + if (osgk_dll) + return true; + +// COMMANDLINEOPTION: Sound: -nogecko disables gecko support (web browser support for menu and computer terminals) + if (COM_CheckParm("-nogecko")) + return false; + + return Sys_LoadLibrary (dllnames_gecko, &osgk_dll, osgkFuncs); +} + +void CL_Gecko_Init( void ) +{ + CL_Gecko_OpenLibrary(); + + Cmd_AddCommand( "gecko_create", cl_gecko_create_f, "Create a gecko browser instance" ); + Cmd_AddCommand( "gecko_destroy", cl_gecko_destroy_f, "Destroy a gecko browser instance" ); + Cmd_AddCommand( "gecko_navigate", cl_gecko_navigate_f, "Navigate a gecko browser to a URI" ); + Cmd_AddCommand( "gecko_injecttext", cl_gecko_injecttext_f, "Injects text into a browser" ); + Cmd_AddCommand( "gecko_movecursor", gl_gecko_movecursor_f, "Move the cursor to a certain position" ); + + R_RegisterModule( "CL_Gecko", cl_gecko_start, cl_gecko_shutdown, cl_gecko_newmap, NULL, NULL ); +} + +void CL_Gecko_NavigateToURI( clgecko_t *instance, const char *URI ) { + if( !instance || !instance->browser ) { + return; + } + + if( instance->active ) { + osgk_browser_navigate( instance->browser, URI ); + } +} + +void CL_Gecko_Event_CursorMove( clgecko_t *instance, float x, float y ) { + // TODO: assert x, y \in [0.0, 1.0] + int mappedx, mappedy; + + if( !instance || !instance->browser ) { + return; + } + + mappedx = (int) (x * instance->width); + mappedy = (int) (y * instance->height); + osgk_browser_event_mouse_move( instance->browser, mappedx, mappedy ); +} + +typedef struct geckokeymapping_s { + keynum_t keycode; + unsigned int geckokeycode; +} geckokeymapping_t; + +static geckokeymapping_t geckokeymappingtable[] = { + { K_BACKSPACE, OSGKKey_Backspace }, + { K_TAB, OSGKKey_Tab }, + { K_ENTER, OSGKKey_Return }, + { K_SHIFT, OSGKKey_Shift }, + { K_CTRL, OSGKKey_Control }, + { K_ALT, OSGKKey_Alt }, + { K_CAPSLOCK, OSGKKey_CapsLock }, + { K_ESCAPE, OSGKKey_Escape }, + { K_SPACE, OSGKKey_Space }, + { K_PGUP, OSGKKey_PageUp }, + { K_PGDN, OSGKKey_PageDown }, + { K_END, OSGKKey_End }, + { K_HOME, OSGKKey_Home }, + { K_LEFTARROW, OSGKKey_Left }, + { K_UPARROW, OSGKKey_Up }, + { K_RIGHTARROW, OSGKKey_Right }, + { K_DOWNARROW, OSGKKey_Down }, + { K_INS, OSGKKey_Insert }, + { K_DEL, OSGKKey_Delete }, + { K_F1, OSGKKey_F1 }, + { K_F2, OSGKKey_F2 }, + { K_F3, OSGKKey_F3 }, + { K_F4, OSGKKey_F4 }, + { K_F5, OSGKKey_F5 }, + { K_F6, OSGKKey_F6 }, + { K_F7, OSGKKey_F7 }, + { K_F8, OSGKKey_F8 }, + { K_F9, OSGKKey_F9 }, + { K_F10, OSGKKey_F10 }, + { K_F11, OSGKKey_F11 }, + { K_F12, OSGKKey_F12 }, + { K_NUMLOCK, OSGKKey_NumLock }, + { K_SCROLLOCK, OSGKKey_ScrollLock } +}; + +qboolean CL_Gecko_Event_Key( clgecko_t *instance, keynum_t key, clgecko_buttoneventtype_t eventtype ) { + if( !instance || !instance->browser ) { + return false; + } + + // determine whether its a keyboard event + if( key < K_OTHERDEVICESBEGIN ) { + + OSGK_KeyboardEventType mappedtype = kePress; + unsigned int mappedkey = key; + + unsigned int i; + // yes! then convert it if necessary! + for( i = 0 ; i < sizeof( geckokeymappingtable ) / sizeof( *geckokeymappingtable ) ; i++ ) { + const geckokeymapping_t * const mapping = &geckokeymappingtable[ i ]; + if( key == mapping->keycode ) { + mappedkey = mapping->geckokeycode; + break; + } + } + + // convert the eventtype + // map the type + switch( eventtype ) { + case CLG_BET_DOWN: + mappedtype = keDown; + break; + case CLG_BET_UP: + mappedtype = keUp; + break; + case CLG_BET_DOUBLECLICK: + // TODO: error message + break; + case CLG_BET_PRESS: + mappedtype = kePress; + } + + return osgk_browser_event_key( instance->browser, mappedkey, mappedtype ) != 0; + } else if( K_MOUSE1 <= key && key <= K_MOUSE3 ) { + OSGK_MouseButtonEventType mappedtype = meDoubleClick; + OSGK_MouseButton mappedbutton; + + mappedbutton = (OSGK_MouseButton) (mbLeft + (key - K_MOUSE1)); + + switch( eventtype ) { + case CLG_BET_DOWN: + mappedtype = meDown; + break; + case CLG_BET_UP: + mappedtype = meUp; + break; + case CLG_BET_DOUBLECLICK: + mappedtype = meDoubleClick; + break; + case CLG_BET_PRESS: + // hihi, hacky hacky + osgk_browser_event_mouse_button( instance->browser, mappedbutton, meDown ); + mappedtype = meUp; + break; + } + + osgk_browser_event_mouse_button( instance->browser, mappedbutton, mappedtype ); + return true; + } else if( K_MWHEELUP <= key && key <= K_MWHEELDOWN ) { + if( eventtype == CLG_BET_DOWN ) + osgk_browser_event_mouse_wheel( instance->browser, + waVertical, (key == K_MWHEELUP) ? wdNegative : wdPositive ); + return true; + } + // TODO: error? + return false; +} diff --git a/misc/source/darkplaces-src/cl_gecko.h b/misc/source/darkplaces-src/cl_gecko.h new file mode 100644 index 00000000..c95b410b --- /dev/null +++ b/misc/source/darkplaces-src/cl_gecko.h @@ -0,0 +1,39 @@ +// Andreas Kirsch 07 + +#ifndef CL_GECKO_H +#define CL_GECKO_H + +#include "cl_dyntexture.h" + +#define CLGECKOPREFIX CLDYNTEXTUREPREFIX "gecko/" + +typedef enum clgecko_buttoneventtype_e { + CLG_BET_DOWN, + CLG_BET_UP, + CLG_BET_DOUBLECLICK, + // use for up + down (but dont use both) + CLG_BET_PRESS +} clgecko_buttoneventtype_t; + +typedef struct clgecko_s clgecko_t; + +void CL_Gecko_Frame( void ); +void CL_Gecko_Init( void ); +void CL_Gecko_Shutdown( void ); + +clgecko_t * CL_Gecko_CreateBrowser( const char *name, int ownerProg ); +clgecko_t * CL_Gecko_FindBrowser( const char *name ); +void CL_Gecko_DestroyBrowser( clgecko_t *instance ); + +void CL_Gecko_NavigateToURI( clgecko_t *instance, const char *URI ); +// x and y between 0.0 and 1.0 (0.0 is top-left?) +void CL_Gecko_Event_CursorMove( clgecko_t *instance, float x, float y ); + +// returns whether the key/button event was handled or not +qboolean CL_Gecko_Event_Key( clgecko_t *instance, keynum_t key, clgecko_buttoneventtype_t eventtype ); + +void CL_Gecko_Resize( clgecko_t *instance, int width, int height ); +// get the ratio between gecko instance's size in the texture and the actual texture size.. +void CL_Gecko_GetTextureExtent( clgecko_t *instance, float* pwidth, float* pheight ); +#endif + diff --git a/misc/source/darkplaces-src/cl_input.c b/misc/source/darkplaces-src/cl_input.c new file mode 100644 index 00000000..8fe0d4cb --- /dev/null +++ b/misc/source/darkplaces-src/cl_input.c @@ -0,0 +1,2238 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl.input.c -- builds an intended movement command to send to the server + +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. + +#include "quakedef.h" +#include "csprogs.h" + +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook, in_klook; +kbutton_t in_left, in_right, in_forward, in_back; +kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; +kbutton_t in_strafe, in_speed, in_jump, in_attack, in_use; +kbutton_t in_up, in_down; +// LordHavoc: added 6 new buttons +kbutton_t in_button3, in_button4, in_button5, in_button6, in_button7, in_button8; +//even more +kbutton_t in_button9, in_button10, in_button11, in_button12, in_button13, in_button14, in_button15, in_button16; + +int in_impulse; + + + +void KeyDown (kbutton_t *b) +{ + int k; + const 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 + { + Con_Print("Three keys down for a button!\n"); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +void KeyUp (kbutton_t *b) +{ + int k; + const char *c; + + c = Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + return; // some other key is still holding it down + + if (!(b->state & 1)) + return; // still up (this should not happen) + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_MLookUp (void) +{ + KeyUp(&in_mlook); + if ( !(in_mlook.state&1) && lookspring.value) + V_StartPitchDrift(); +} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} +void IN_ForwardDown(void) {KeyDown(&in_forward);} +void IN_ForwardUp(void) {KeyUp(&in_forward);} +void IN_BackDown(void) {KeyDown(&in_back);} +void IN_BackUp(void) {KeyUp(&in_back);} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) {KeyDown(&in_moveleft);} +void IN_MoveleftUp(void) {KeyUp(&in_moveleft);} +void IN_MoverightDown(void) {KeyDown(&in_moveright);} +void IN_MoverightUp(void) {KeyUp(&in_moveright);} + +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} + +void IN_AttackDown(void) {KeyDown(&in_attack);} +void IN_AttackUp(void) {KeyUp(&in_attack);} + +void IN_UseDown(void) {KeyDown(&in_use);} +void IN_UseUp(void) {KeyUp(&in_use);} + +// LordHavoc: added 6 new buttons +void IN_Button3Down(void) {KeyDown(&in_button3);} +void IN_Button3Up(void) {KeyUp(&in_button3);} +void IN_Button4Down(void) {KeyDown(&in_button4);} +void IN_Button4Up(void) {KeyUp(&in_button4);} +void IN_Button5Down(void) {KeyDown(&in_button5);} +void IN_Button5Up(void) {KeyUp(&in_button5);} +void IN_Button6Down(void) {KeyDown(&in_button6);} +void IN_Button6Up(void) {KeyUp(&in_button6);} +void IN_Button7Down(void) {KeyDown(&in_button7);} +void IN_Button7Up(void) {KeyUp(&in_button7);} +void IN_Button8Down(void) {KeyDown(&in_button8);} +void IN_Button8Up(void) {KeyUp(&in_button8);} + +void IN_Button9Down(void) {KeyDown(&in_button9);} +void IN_Button9Up(void) {KeyUp(&in_button9);} +void IN_Button10Down(void) {KeyDown(&in_button10);} +void IN_Button10Up(void) {KeyUp(&in_button10);} +void IN_Button11Down(void) {KeyDown(&in_button11);} +void IN_Button11Up(void) {KeyUp(&in_button11);} +void IN_Button12Down(void) {KeyDown(&in_button12);} +void IN_Button12Up(void) {KeyUp(&in_button12);} +void IN_Button13Down(void) {KeyDown(&in_button13);} +void IN_Button13Up(void) {KeyUp(&in_button13);} +void IN_Button14Down(void) {KeyDown(&in_button14);} +void IN_Button14Up(void) {KeyUp(&in_button14);} +void IN_Button15Down(void) {KeyDown(&in_button15);} +void IN_Button15Up(void) {KeyUp(&in_button15);} +void IN_Button16Down(void) {KeyDown(&in_button16);} +void IN_Button16Up(void) {KeyUp(&in_button16);} + +void IN_JumpDown (void) {KeyDown(&in_jump);} +void IN_JumpUp (void) {KeyUp(&in_jump);} + +void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));} + +in_bestweapon_info_t in_bestweapon_info[IN_BESTWEAPON_MAX]; + +void IN_BestWeapon_Register(const char *name, int impulse, int weaponbit, int activeweaponcode, int ammostat, int ammomin) +{ + int i; + for(i = 0; i < IN_BESTWEAPON_MAX && in_bestweapon_info[i].impulse; ++i) + if(in_bestweapon_info[i].impulse == impulse) + break; + if(i >= IN_BESTWEAPON_MAX) + { + Con_Printf("no slot left for weapon definition; increase IN_BESTWEAPON_MAX\n"); + return; // sorry + } + strlcpy(in_bestweapon_info[i].name, name, sizeof(in_bestweapon_info[i].name)); + in_bestweapon_info[i].impulse = impulse; + if(weaponbit != -1) + in_bestweapon_info[i].weaponbit = weaponbit; + if(activeweaponcode != -1) + in_bestweapon_info[i].activeweaponcode = activeweaponcode; + if(ammostat != -1) + in_bestweapon_info[i].ammostat = ammostat; + if(ammomin != -1) + in_bestweapon_info[i].ammomin = ammomin; +} + +void IN_BestWeapon_ResetData (void) +{ + memset(in_bestweapon_info, 0, sizeof(in_bestweapon_info)); + IN_BestWeapon_Register("1", 1, IT_AXE, IT_AXE, STAT_SHELLS, 0); + IN_BestWeapon_Register("2", 2, IT_SHOTGUN, IT_SHOTGUN, STAT_SHELLS, 1); + IN_BestWeapon_Register("3", 3, IT_SUPER_SHOTGUN, IT_SUPER_SHOTGUN, STAT_SHELLS, 1); + IN_BestWeapon_Register("4", 4, IT_NAILGUN, IT_NAILGUN, STAT_NAILS, 1); + IN_BestWeapon_Register("5", 5, IT_SUPER_NAILGUN, IT_SUPER_NAILGUN, STAT_NAILS, 1); + IN_BestWeapon_Register("6", 6, IT_GRENADE_LAUNCHER, IT_GRENADE_LAUNCHER, STAT_ROCKETS, 1); + IN_BestWeapon_Register("7", 7, IT_ROCKET_LAUNCHER, IT_ROCKET_LAUNCHER, STAT_ROCKETS, 1); + IN_BestWeapon_Register("8", 8, IT_LIGHTNING, IT_LIGHTNING, STAT_CELLS, 1); + IN_BestWeapon_Register("9", 9, 128, 128, STAT_CELLS, 1); // generic energy weapon for mods + IN_BestWeapon_Register("p", 209, 128, 128, STAT_CELLS, 1); // dpmod plasma gun + IN_BestWeapon_Register("w", 210, 8388608, 8388608, STAT_CELLS, 1); // dpmod plasma wave cannon + IN_BestWeapon_Register("l", 225, HIT_LASER_CANNON, HIT_LASER_CANNON, STAT_CELLS, 1); // hipnotic laser cannon + IN_BestWeapon_Register("h", 226, HIT_MJOLNIR, HIT_MJOLNIR, STAT_CELLS, 0); // hipnotic mjolnir hammer +} + +void IN_BestWeapon_Register_f (void) +{ + if(Cmd_Argc() == 7) + { + IN_BestWeapon_Register( + Cmd_Argv(1), + atoi(Cmd_Argv(2)), + atoi(Cmd_Argv(3)), + atoi(Cmd_Argv(4)), + atoi(Cmd_Argv(5)), + atoi(Cmd_Argv(6)) + ); + } + else if(Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), "clear")) + { + memset(in_bestweapon_info, 0, sizeof(in_bestweapon_info)); + } + else if(Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), "quake")) + { + IN_BestWeapon_ResetData(); + } + else + { + Con_Printf("Usage: %s weaponshortname impulse itemcode activeweaponcode ammostat ammomin; %s clear; %s quake\n", Cmd_Argv(0), Cmd_Argv(0), Cmd_Argv(0)); + } +} + +void IN_BestWeapon (void) +{ + int i, n; + const char *t; + if (Cmd_Argc() < 2) + { + Con_Printf("bestweapon requires 1 or more parameters\n"); + return; + } + for (i = 1;i < Cmd_Argc();i++) + { + t = Cmd_Argv(i); + // figure out which weapon this character refers to + for (n = 0;n < IN_BESTWEAPON_MAX && in_bestweapon_info[n].impulse;n++) + { + if (!strcmp(in_bestweapon_info[n].name, t)) + { + // we found out what weapon this character refers to + // check if the inventory contains the weapon and enough ammo + if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n].weaponbit) && (cl.stats[in_bestweapon_info[n].ammostat] >= in_bestweapon_info[n].ammomin)) + { + // we found one of the weapons the player wanted + // send an impulse to switch to it + in_impulse = in_bestweapon_info[n].impulse; + return; + } + break; + } + } + // if we couldn't identify the weapon we just ignore it and continue checking for other weapons + } + // if we couldn't find any of the weapons, there's nothing more we can do... +} + +#if 0 +void IN_CycleWeapon (void) +{ + int i, n; + int first = -1; + qboolean found = false; + const char *t; + if (Cmd_Argc() < 2) + { + Con_Printf("bestweapon requires 1 or more parameters\n"); + return; + } + for (i = 1;i < Cmd_Argc();i++) + { + t = Cmd_Argv(i); + // figure out which weapon this character refers to + for (n = 0;n < IN_BESTWEAPON_MAX && in_bestweapon_info[n].impulse;n++) + { + if (!strcmp(in_bestweapon_info[n].name, t)) + { + // we found out what weapon this character refers to + // check if the inventory contains the weapon and enough ammo + if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n].weaponbit) && (cl.stats[in_bestweapon_info[n].ammostat] >= in_bestweapon_info[n].ammomin)) + { + // we found one of the weapons the player wanted + if(first == -1) + first = n; + if(found) + { + in_impulse = in_bestweapon_info[n].impulse; + return; + } + if(cl.stats[STAT_ACTIVEWEAPON] == in_bestweapon_info[n].activeweaponcode) + found = true; + } + break; + } + } + // if we couldn't identify the weapon we just ignore it and continue checking for other weapons + } + if(first != -1) + { + in_impulse = in_bestweapon_info[first].impulse; + return; + } + // if we couldn't find any of the weapons, there's nothing more we can do... +} +#endif + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val; + qboolean impulsedown, impulseup, down; + + impulsedown = (key->state & 2) != 0; + impulseup = (key->state & 4) != 0; + down = (key->state & 1) != 0; + val = 0; + + if (impulsedown && !impulseup) + { + if (down) + val = 0.5; // pressed and held this frame + else + val = 0; // I_Error (); + } + if (impulseup && !impulsedown) + { + if (down) + val = 0; // I_Error (); + else + val = 0; // released this frame + } + if (!impulsedown && !impulseup) + { + if (down) + val = 1.0; // held the entire frame + else + val = 0; // up the entire frame + } + if (impulsedown && impulseup) + { + if (down) + val = 0.75; // released and re-pressed this frame + else + val = 0.25; // pressed and released this frame + } + + key->state &= 1; // clear impulses + + return val; +} + + + + +//========================================================================== + +cvar_t cl_upspeed = {CVAR_SAVE, "cl_upspeed","400","vertical movement speed (while swimming or flying)"}; +cvar_t cl_forwardspeed = {CVAR_SAVE, "cl_forwardspeed","400","forward movement speed"}; +cvar_t cl_backspeed = {CVAR_SAVE, "cl_backspeed","400","backward movement speed"}; +cvar_t cl_sidespeed = {CVAR_SAVE, "cl_sidespeed","350","strafe movement speed"}; + +cvar_t cl_movespeedkey = {CVAR_SAVE, "cl_movespeedkey","2.0","how much +speed multiplies keyboard movement speed"}; +cvar_t cl_movecliptokeyboard = {0, "cl_movecliptokeyboard", "0", "if set to 1, any move is clipped to the nine keyboard states; if set to 2, only the direction is clipped, not the amount"}; + +cvar_t cl_yawspeed = {CVAR_SAVE, "cl_yawspeed","140","keyboard yaw turning speed"}; +cvar_t cl_pitchspeed = {CVAR_SAVE, "cl_pitchspeed","150","keyboard pitch turning speed"}; + +cvar_t cl_anglespeedkey = {CVAR_SAVE, "cl_anglespeedkey","1.5","how much +speed multiplies keyboard turning speed"}; + +cvar_t cl_movement = {CVAR_SAVE, "cl_movement", "0", "enables clientside prediction of your player movement"}; +cvar_t cl_movement_replay = {0, "cl_movement_replay", "1", "use engine prediction"}; +cvar_t cl_movement_nettimeout = {CVAR_SAVE, "cl_movement_nettimeout", "0.3", "stops predicting moves when server is lagging badly (avoids major performance problems), timeout in seconds"}; +cvar_t cl_movement_minping = {CVAR_SAVE, "cl_movement_minping", "0", "whether to use prediction when ping is lower than this value in milliseconds"}; +cvar_t cl_movement_track_canjump = {CVAR_SAVE, "cl_movement_track_canjump", "1", "track if the player released the jump key between two jumps to decide if he is able to jump or not; when off, this causes some \"sliding\" slightly above the floor when the jump key is held too long; if the mod allows repeated jumping by holding space all the time, this has to be set to zero too"}; +cvar_t cl_movement_maxspeed = {0, "cl_movement_maxspeed", "320", "how fast you can move (should match sv_maxspeed)"}; +cvar_t cl_movement_maxairspeed = {0, "cl_movement_maxairspeed", "30", "how fast you can move while in the air (should match sv_maxairspeed)"}; +cvar_t cl_movement_stopspeed = {0, "cl_movement_stopspeed", "100", "speed below which you will be slowed rapidly to a stop rather than sliding endlessly (should match sv_stopspeed)"}; +cvar_t cl_movement_friction = {0, "cl_movement_friction", "4", "how fast you slow down (should match sv_friction)"}; +cvar_t cl_movement_wallfriction = {0, "cl_movement_wallfriction", "1", "how fast you slow down while sliding along a wall (should match sv_wallfriction)"}; +cvar_t cl_movement_waterfriction = {0, "cl_movement_waterfriction", "-1", "how fast you slow down (should match sv_waterfriction), if less than 0 the cl_movement_friction variable is used instead"}; +cvar_t cl_movement_edgefriction = {0, "cl_movement_edgefriction", "1", "how much to slow down when you may be about to fall off a ledge (should match edgefriction)"}; +cvar_t cl_movement_stepheight = {0, "cl_movement_stepheight", "18", "how tall a step you can step in one instant (should match sv_stepheight)"}; +cvar_t cl_movement_accelerate = {0, "cl_movement_accelerate", "10", "how fast you accelerate (should match sv_accelerate)"}; +cvar_t cl_movement_airaccelerate = {0, "cl_movement_airaccelerate", "-1", "how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead"}; +cvar_t cl_movement_wateraccelerate = {0, "cl_movement_wateraccelerate", "-1", "how fast you accelerate while in water (should match sv_wateraccelerate), if less than 0 the cl_movement_accelerate variable is used instead"}; +cvar_t cl_movement_jumpvelocity = {0, "cl_movement_jumpvelocity", "270", "how fast you move upward when you begin a jump (should match the quakec code)"}; +cvar_t cl_movement_airaccel_qw = {0, "cl_movement_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration (reduces speed gain when zigzagging) (should match sv_airaccel_qw); when < 0, the speed is clamped against the maximum allowed forward speed after the move"}; +cvar_t cl_movement_airaccel_sideways_friction = {0, "cl_movement_airaccel_sideways_friction", "0", "anti-sideways movement stabilization (should match sv_airaccel_sideways_friction); when < 0, only so much friction is applied that braking (by accelerating backwards) cannot be stronger"}; + +cvar_t in_pitch_min = {0, "in_pitch_min", "-90", "how far downward you can aim (quake used -70"}; +cvar_t in_pitch_max = {0, "in_pitch_max", "90", "how far upward you can aim (quake used 80"}; + +cvar_t m_filter = {CVAR_SAVE, "m_filter","0", "smoothes mouse movement, less responsive but smoother aiming"}; +cvar_t m_accelerate = {CVAR_SAVE, "m_accelerate","1", "mouse acceleration factor (try 2)"}; +cvar_t m_accelerate_minspeed = {CVAR_SAVE, "m_accelerate_minspeed","5000", "below this speed, no acceleration is done"}; +cvar_t m_accelerate_maxspeed = {CVAR_SAVE, "m_accelerate_maxspeed","10000", "above this speed, full acceleration is done"}; +cvar_t m_accelerate_filter = {CVAR_SAVE, "m_accelerate_filter","0.1", "mouse acceleration factor filtering"}; + +cvar_t cl_netfps = {CVAR_SAVE, "cl_netfps","72", "how many input packets to send to server each second"}; +cvar_t cl_netrepeatinput = {CVAR_SAVE, "cl_netrepeatinput", "1", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server), only affects DP7 and later servers (Quake uses 0, QuakeWorld uses 2, and just for comparison Quake3 uses 1)"}; +cvar_t cl_netimmediatebuttons = {CVAR_SAVE, "cl_netimmediatebuttons", "1", "sends extra packets whenever your buttons change or an impulse is used (basically: whenever you click fire or change weapon)"}; + +cvar_t cl_nodelta = {0, "cl_nodelta", "0", "disables delta compression of non-player entities in QW network protocol"}; + +extern cvar_t v_flipped; + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles (void) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + speed = cl.realframetime * cl_anglespeedkey.value; + else + speed = cl.realframetime; + + if (!(in_strafe.state & 1)) + { + cl.viewangles[YAW] -= speed*cl_yawspeed.value*CL_KeyState (&in_right); + cl.viewangles[YAW] += speed*cl_yawspeed.value*CL_KeyState (&in_left); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + cl.viewangles[PITCH] -= speed*cl_pitchspeed.value * CL_KeyState (&in_forward); + cl.viewangles[PITCH] += speed*cl_pitchspeed.value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + cl.viewangles[PITCH] -= speed*cl_pitchspeed.value * up; + cl.viewangles[PITCH] += speed*cl_pitchspeed.value * down; + + if (up || down) + V_StopPitchDrift (); + + cl.viewangles[YAW] = ANGLEMOD(cl.viewangles[YAW]); + cl.viewangles[PITCH] = ANGLEMOD(cl.viewangles[PITCH]); + if (cl.viewangles[YAW] >= 180) + cl.viewangles[YAW] -= 360; + if (cl.viewangles[PITCH] >= 180) + cl.viewangles[PITCH] -= 360; + cl.viewangles[PITCH] = bound(in_pitch_min.value, cl.viewangles[PITCH], in_pitch_max.value); + cl.viewangles[ROLL] = bound(-180, cl.viewangles[ROLL], 180); +} + +int cl_ignoremousemoves = 2; + +/* +================ +CL_Input + +Send the intended movement message to the server +================ +*/ +void CL_Input (void) +{ + float mx, my; + static float old_mouse_x = 0, old_mouse_y = 0; + + // clamp before the move to prevent starting with bad angles + CL_AdjustAngles (); + + if(v_flipped.integer) + cl.viewangles[YAW] = -cl.viewangles[YAW]; + + // reset some of the command fields + cl.cmd.forwardmove = 0; + cl.cmd.sidemove = 0; + cl.cmd.upmove = 0; + + // get basic movement from keyboard + if (in_strafe.state & 1) + { + cl.cmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_right); + cl.cmd.sidemove -= cl_sidespeed.value * CL_KeyState (&in_left); + } + + cl.cmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_moveright); + cl.cmd.sidemove -= cl_sidespeed.value * CL_KeyState (&in_moveleft); + + cl.cmd.upmove += cl_upspeed.value * CL_KeyState (&in_up); + cl.cmd.upmove -= cl_upspeed.value * CL_KeyState (&in_down); + + if (! (in_klook.state & 1) ) + { + cl.cmd.forwardmove += cl_forwardspeed.value * CL_KeyState (&in_forward); + cl.cmd.forwardmove -= cl_backspeed.value * CL_KeyState (&in_back); + } + + // adjust for speed key + if (in_speed.state & 1) + { + cl.cmd.forwardmove *= cl_movespeedkey.value; + cl.cmd.sidemove *= cl_movespeedkey.value; + cl.cmd.upmove *= cl_movespeedkey.value; + } + + // allow mice or other external controllers to add to the move + IN_Move (); + + // apply m_accelerate if it is on + if(m_accelerate.value > 1) + { + static float averagespeed = 0; + float speed, f, mi, ma; + + speed = sqrt(in_mouse_x * in_mouse_x + in_mouse_y * in_mouse_y) / cl.realframetime; + if(m_accelerate_filter.value > 0) + f = bound(0, cl.realframetime / m_accelerate_filter.value, 1); + else + f = 1; + averagespeed = speed * f + averagespeed * (1 - f); + + mi = max(1, m_accelerate_minspeed.value); + ma = max(m_accelerate_minspeed.value + 1, m_accelerate_maxspeed.value); + + if(averagespeed <= mi) + { + f = 1; + } + else if(averagespeed >= ma) + { + f = m_accelerate.value; + } + else + { + f = averagespeed; + f = (f - mi) / (ma - mi) * (m_accelerate.value - 1) + 1; + } + + in_mouse_x *= f; + in_mouse_y *= f; + } + + // apply m_filter if it is on + mx = in_mouse_x; + my = in_mouse_y; + if (m_filter.integer) + { + in_mouse_x = (mx + old_mouse_x) * 0.5; + in_mouse_y = (my + old_mouse_y) * 0.5; + } + old_mouse_x = mx; + old_mouse_y = my; + + // ignore a mouse move if mouse was activated/deactivated this frame + if (cl_ignoremousemoves) + { + cl_ignoremousemoves--; + in_mouse_x = old_mouse_x = 0; + in_mouse_y = old_mouse_y = 0; + } + + // if not in menu, apply mouse move to viewangles/movement + if (!key_consoleactive && key_dest == key_game && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0) + { + float modulatedsensitivity = sensitivity.value * cl.sensitivityscale; + if (in_strafe.state & 1) + { + // strafing mode, all looking is movement + V_StopPitchDrift(); + cl.cmd.sidemove += m_side.value * in_mouse_x * modulatedsensitivity; + if (noclip_anglehack) + cl.cmd.upmove -= m_forward.value * in_mouse_y * modulatedsensitivity; + else + cl.cmd.forwardmove -= m_forward.value * in_mouse_y * modulatedsensitivity; + } + else if ((in_mlook.state & 1) || freelook.integer) + { + // mouselook, lookstrafe causes turning to become strafing + V_StopPitchDrift(); + if (lookstrafe.integer) + cl.cmd.sidemove += m_side.value * in_mouse_x * modulatedsensitivity; + else + cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * modulatedsensitivity * cl.viewzoom; + cl.viewangles[PITCH] += m_pitch.value * in_mouse_y * modulatedsensitivity * cl.viewzoom; + } + else + { + // non-mouselook, yaw turning and forward/back movement + cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * modulatedsensitivity * cl.viewzoom; + cl.cmd.forwardmove -= m_forward.value * in_mouse_y * modulatedsensitivity; + } + } + else // don't pitch drift when csqc is controlling the mouse + { + // mouse interacting with the scene, mostly stationary view + V_StopPitchDrift(); + // update prydon cursor + cl.cmd.cursor_screen[0] = in_windowmouse_x * 2.0 / vid.width - 1.0; + cl.cmd.cursor_screen[1] = in_windowmouse_y * 2.0 / vid.height - 1.0; + } + + if(v_flipped.integer) + { + cl.viewangles[YAW] = -cl.viewangles[YAW]; + cl.cmd.sidemove = -cl.cmd.sidemove; + } + + // clamp after the move to prevent rendering with bad angles + CL_AdjustAngles (); + + if(cl_movecliptokeyboard.integer) + { + vec_t f = 1; + if (in_speed.state & 1) + f *= cl_movespeedkey.value; + if(cl_movecliptokeyboard.integer == 2) + { + // digital direction, analog amount + vec_t wishvel_x, wishvel_y; + f *= max(cl_sidespeed.value, max(cl_forwardspeed.value, cl_backspeed.value)); + wishvel_x = fabs(cl.cmd.forwardmove); + wishvel_y = fabs(cl.cmd.sidemove); + if(wishvel_x != 0 && wishvel_y != 0 && wishvel_x != wishvel_y) + { + vec_t wishspeed = sqrt(wishvel_x * wishvel_x + wishvel_y * wishvel_y); + if(wishvel_x >= 2 * wishvel_y) + { + // pure X motion + if(cl.cmd.forwardmove > 0) + cl.cmd.forwardmove = wishspeed; + else + cl.cmd.forwardmove = -wishspeed; + cl.cmd.sidemove = 0; + } + else if(wishvel_y >= 2 * wishvel_x) + { + // pure Y motion + cl.cmd.forwardmove = 0; + if(cl.cmd.sidemove > 0) + cl.cmd.sidemove = wishspeed; + else + cl.cmd.sidemove = -wishspeed; + } + else + { + // diagonal + if(cl.cmd.forwardmove > 0) + cl.cmd.forwardmove = 0.70710678118654752440 * wishspeed; + else + cl.cmd.forwardmove = -0.70710678118654752440 * wishspeed; + if(cl.cmd.sidemove > 0) + cl.cmd.sidemove = 0.70710678118654752440 * wishspeed; + else + cl.cmd.sidemove = -0.70710678118654752440 * wishspeed; + } + } + } + else if(cl_movecliptokeyboard.integer) + { + // digital direction, digital amount + if(cl.cmd.sidemove >= cl_sidespeed.value * f * 0.5) + cl.cmd.sidemove = cl_sidespeed.value * f; + else if(cl.cmd.sidemove <= -cl_sidespeed.value * f * 0.5) + cl.cmd.sidemove = -cl_sidespeed.value * f; + else + cl.cmd.sidemove = 0; + if(cl.cmd.forwardmove >= cl_forwardspeed.value * f * 0.5) + cl.cmd.forwardmove = cl_forwardspeed.value * f; + else if(cl.cmd.forwardmove <= -cl_backspeed.value * f * 0.5) + cl.cmd.forwardmove = -cl_backspeed.value * f; + else + cl.cmd.forwardmove = 0; + } + } +} + +#include "cl_collision.h" + +void CL_UpdatePrydonCursor(void) +{ + vec3_t temp; + + if (cl_prydoncursor.integer <= 0) + VectorClear(cl.cmd.cursor_screen); + + /* + if (cl.cmd.cursor_screen[0] < -1) + { + cl.viewangles[YAW] -= m_yaw.value * (cl.cmd.cursor_screen[0] - -1) * vid.width * sensitivity.value * cl.viewzoom; + cl.cmd.cursor_screen[0] = -1; + } + if (cl.cmd.cursor_screen[0] > 1) + { + cl.viewangles[YAW] -= m_yaw.value * (cl.cmd.cursor_screen[0] - 1) * vid.width * sensitivity.value * cl.viewzoom; + cl.cmd.cursor_screen[0] = 1; + } + if (cl.cmd.cursor_screen[1] < -1) + { + cl.viewangles[PITCH] += m_pitch.value * (cl.cmd.cursor_screen[1] - -1) * vid.height * sensitivity.value * cl.viewzoom; + cl.cmd.cursor_screen[1] = -1; + } + if (cl.cmd.cursor_screen[1] > 1) + { + cl.viewangles[PITCH] += m_pitch.value * (cl.cmd.cursor_screen[1] - 1) * vid.height * sensitivity.value * cl.viewzoom; + cl.cmd.cursor_screen[1] = 1; + } + */ + cl.cmd.cursor_screen[0] = bound(-1, cl.cmd.cursor_screen[0], 1); + cl.cmd.cursor_screen[1] = bound(-1, cl.cmd.cursor_screen[1], 1); + cl.cmd.cursor_screen[2] = 1; + + // calculate current view matrix + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, cl.cmd.cursor_start); + // calculate direction vector of cursor in viewspace by using frustum slopes + VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, (v_flipped.integer ? -1 : 1) * cl.cmd.cursor_screen[0] * -r_refdef.view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_refdef.view.frustum_y * 1000000); + Matrix4x4_Transform(&r_refdef.view.matrix, temp, cl.cmd.cursor_end); + // trace from view origin to the cursor + if (cl_prydoncursor_notrace.integer) + { + cl.cmd.cursor_fraction = 1.0f; + VectorCopy(cl.cmd.cursor_end, cl.cmd.cursor_impact); + VectorClear(cl.cmd.cursor_normal); + cl.cmd.cursor_entitynumber = 0; + } + else + cl.cmd.cursor_fraction = CL_SelectTraceLine(cl.cmd.cursor_start, cl.cmd.cursor_end, cl.cmd.cursor_impact, cl.cmd.cursor_normal, &cl.cmd.cursor_entitynumber, (chase_active.integer || cl.intermission) ? &cl.entities[cl.playerentity].render : NULL); +} + +typedef enum waterlevel_e +{ + WATERLEVEL_NONE, + WATERLEVEL_WETFEET, + WATERLEVEL_SWIMMING, + WATERLEVEL_SUBMERGED +} +waterlevel_t; + +typedef struct cl_clientmovement_state_s +{ + // position + vec3_t origin; + vec3_t velocity; + // current bounding box (different if crouched vs standing) + vec3_t mins; + vec3_t maxs; + // currently on the ground + qboolean onground; + // currently crouching + qboolean crouched; + // what kind of water (SUPERCONTENTS_LAVA for instance) + int watertype; + // how deep + waterlevel_t waterlevel; + // weird hacks when jumping out of water + // (this is in seconds and counts down to 0) + float waterjumptime; + + // user command + usercmd_t cmd; +} +cl_clientmovement_state_t; + +#define NUMOFFSETS 27 +static vec3_t offsets[NUMOFFSETS] = +{ +// 1 no nudge (just return the original if this test passes) + { 0.000, 0.000, 0.000}, +// 6 simple nudges + { 0.000, 0.000, 0.125}, { 0.000, 0.000, -0.125}, + {-0.125, 0.000, 0.000}, { 0.125, 0.000, 0.000}, + { 0.000, -0.125, 0.000}, { 0.000, 0.125, 0.000}, +// 4 diagonal flat nudges + {-0.125, -0.125, 0.000}, { 0.125, -0.125, 0.000}, + {-0.125, 0.125, 0.000}, { 0.125, 0.125, 0.000}, +// 8 diagonal upward nudges + {-0.125, 0.000, 0.125}, { 0.125, 0.000, 0.125}, + { 0.000, -0.125, 0.125}, { 0.000, 0.125, 0.125}, + {-0.125, -0.125, 0.125}, { 0.125, -0.125, 0.125}, + {-0.125, 0.125, 0.125}, { 0.125, 0.125, 0.125}, +// 8 diagonal downward nudges + {-0.125, 0.000, -0.125}, { 0.125, 0.000, -0.125}, + { 0.000, -0.125, -0.125}, { 0.000, 0.125, -0.125}, + {-0.125, -0.125, -0.125}, { 0.125, -0.125, -0.125}, + {-0.125, 0.125, -0.125}, { 0.125, 0.125, -0.125}, +}; + +qboolean CL_ClientMovement_Unstick(cl_clientmovement_state_t *s) +{ + int i; + vec3_t neworigin; + for (i = 0;i < NUMOFFSETS;i++) + { + VectorAdd(offsets[i], s->origin, neworigin); + if (!CL_TraceBox(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false).startsolid) + { + VectorCopy(neworigin, s->origin); + return true; + } + } + // if all offsets failed, give up + return false; +} + +void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s) +{ + vec3_t origin1, origin2; + trace_t trace; + + // make sure player is not stuck + CL_ClientMovement_Unstick(s); + + // set crouched + if (s->cmd.crouch) + { + // wants to crouch, this always works.. + if (!s->crouched) + s->crouched = true; + } + else + { + // wants to stand, if currently crouching we need to check for a + // low ceiling first + if (s->crouched) + { + trace = CL_TraceBox(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); + if (!trace.startsolid) + s->crouched = false; + } + } + if (s->crouched) + { + VectorCopy(cl.playercrouchmins, s->mins); + VectorCopy(cl.playercrouchmaxs, s->maxs); + } + else + { + VectorCopy(cl.playerstandmins, s->mins); + VectorCopy(cl.playerstandmaxs, s->maxs); + } + + // set onground + VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + 1); + VectorSet(origin2, s->origin[0], s->origin[1], s->origin[2] - 1); // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :) + trace = CL_TraceBox(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); + s->onground = trace.fraction < 1 && trace.plane.normal[2] > 0.7; + + // set watertype/waterlevel + VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + s->mins[2] + 1); + s->waterlevel = WATERLEVEL_NONE; + s->watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK; + if (s->watertype) + { + s->waterlevel = WATERLEVEL_WETFEET; + origin1[2] = s->origin[2] + (s->mins[2] + s->maxs[2]) * 0.5f; + if (CL_TracePoint(origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) + { + s->waterlevel = WATERLEVEL_SWIMMING; + origin1[2] = s->origin[2] + 22; + if (CL_TracePoint(origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) + s->waterlevel = WATERLEVEL_SUBMERGED; + } + } + + // water jump prediction + if (s->onground || s->velocity[2] <= 0 || s->waterjumptime <= 0) + s->waterjumptime = 0; +} + +void CL_ClientMovement_Move(cl_clientmovement_state_t *s) +{ + int bump; + double t; + vec_t f; + vec3_t neworigin; + vec3_t currentorigin2; + vec3_t neworigin2; + vec3_t primalvelocity; + trace_t trace; + trace_t trace2; + trace_t trace3; + CL_ClientMovement_UpdateStatus(s); + VectorCopy(s->velocity, primalvelocity); + for (bump = 0, t = s->cmd.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++) + { + VectorMA(s->origin, t, s->velocity, neworigin); + trace = CL_TraceBox(s->origin, s->mins, s->maxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); + if (trace.fraction < 1 && trace.plane.normal[2] == 0) + { + // may be a step or wall, try stepping up + // first move forward at a higher level + VectorSet(currentorigin2, s->origin[0], s->origin[1], s->origin[2] + cl.movevars_stepheight); + VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + cl.movevars_stepheight); + trace2 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); + if (!trace2.startsolid) + { + // then move down from there + VectorCopy(trace2.endpos, currentorigin2); + VectorSet(neworigin2, trace2.endpos[0], trace2.endpos[1], s->origin[2]); + trace3 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); + //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]); + // accept the new trace if it made some progress + if (fabs(trace3.endpos[0] - trace.endpos[0]) >= 0.03125 || fabs(trace3.endpos[1] - trace.endpos[1]) >= 0.03125) + { + trace = trace2; + VectorCopy(trace3.endpos, trace.endpos); + } + } + } + + // check if it moved at all + if (trace.fraction >= 0.001) + VectorCopy(trace.endpos, s->origin); + + // check if it moved all the way + if (trace.fraction == 1) + break; + + //if (trace.plane.normal[2] > 0.7) + // s->onground = true; + + t -= t * trace.fraction; + + f = DotProduct(s->velocity, trace.plane.normal); + VectorMA(s->velocity, -f, trace.plane.normal, s->velocity); + } + if (s->waterjumptime > 0) + VectorCopy(primalvelocity, s->velocity); +} + + +void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s) +{ + vec_t wishspeed; + vec_t f; + vec3_t wishvel; + vec3_t wishdir; + + // water jump only in certain situations + // this mimics quakeworld code + if (s->cmd.jump && s->waterlevel == 2 && s->velocity[2] >= -180) + { + vec3_t forward; + vec3_t yawangles; + vec3_t spot; + VectorSet(yawangles, 0, s->cmd.viewangles[1], 0); + AngleVectors(yawangles, forward, NULL, NULL); + VectorMA(s->origin, 24, forward, spot); + spot[2] += 8; + if (CL_TracePoint(spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid) + { + spot[2] += 24; + if (!CL_TracePoint(spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid) + { + VectorScale(forward, 50, s->velocity); + s->velocity[2] = 310; + s->waterjumptime = 2; + s->onground = false; + s->cmd.canjump = false; + } + } + } + + if (!(s->cmd.forwardmove*s->cmd.forwardmove + s->cmd.sidemove*s->cmd.sidemove + s->cmd.upmove*s->cmd.upmove)) + { + // drift towards bottom + VectorSet(wishvel, 0, 0, -60); + } + else + { + // swim + vec3_t forward; + vec3_t right; + vec3_t up; + // calculate movement vector + AngleVectors(s->cmd.viewangles, forward, right, up); + VectorSet(up, 0, 0, 1); + VectorMAMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, s->cmd.upmove, up, wishvel); + } + + // split wishvel into wishspeed and wishdir + wishspeed = VectorLength(wishvel); + if (wishspeed) + VectorScale(wishvel, 1 / wishspeed, wishdir); + else + VectorSet( wishdir, 0.0, 0.0, 0.0 ); + wishspeed = min(wishspeed, cl.movevars_maxspeed) * 0.7; + + if (s->crouched) + wishspeed *= 0.5; + + if (s->waterjumptime <= 0) + { + // water friction + f = 1 - s->cmd.frametime * cl.movevars_waterfriction * (cls.protocol == PROTOCOL_QUAKEWORLD ? s->waterlevel : 1); + f = bound(0, f, 1); + VectorScale(s->velocity, f, s->velocity); + + // water acceleration + f = wishspeed - DotProduct(s->velocity, wishdir); + if (f > 0) + { + f = min(cl.movevars_wateraccelerate * s->cmd.frametime * wishspeed, f); + VectorMA(s->velocity, f, wishdir, s->velocity); + } + + // holding jump button swims upward slowly + if (s->cmd.jump) + { + if (s->watertype & SUPERCONTENTS_LAVA) + s->velocity[2] = 50; + else if (s->watertype & SUPERCONTENTS_SLIME) + s->velocity[2] = 80; + else + { + if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + s->velocity[2] = 200; + else + s->velocity[2] = 100; + } + } + } + + CL_ClientMovement_Move(s); +} + +static vec_t CL_IsMoveInDirection(vec_t forward, vec_t side, vec_t angle) +{ + if(forward == 0 && side == 0) + return 0; // avoid division by zero + angle -= RAD2DEG(atan2(side, forward)); + angle = (ANGLEMOD(angle + 180) - 180) / 45; + if(angle > 1) + return 0; + if(angle < -1) + return 0; + return 1 - fabs(angle); +} + +static vec_t CL_GeomLerp(vec_t a, vec_t lerp, vec_t b) +{ + if(a == 0) + { + if(lerp < 1) + return 0; + else + return b; + } + if(b == 0) + { + if(lerp > 0) + return 0; + else + return a; + } + return a * pow(fabs(b / a), lerp); +} + +void CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed) +{ + vec_t zspeed, speed, dot, k; + +#if 0 + // this doesn't play well with analog input + if(s->cmd.forwardmove == 0 || s->cmd.sidemove != 0) + return; + k = 32; +#else + k = 32 * (2 * CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, 0) - 1); + if(k <= 0) + return; +#endif + + k *= bound(0, wishspeed / cl.movevars_maxairspeed, 1); + + zspeed = s->velocity[2]; + s->velocity[2] = 0; + speed = VectorNormalizeLength(s->velocity); + + dot = DotProduct(s->velocity, wishdir); + + if(dot > 0) { // we can't change direction while slowing down + k *= pow(dot, cl.movevars_aircontrol_power)*s->cmd.frametime; + speed = max(0, speed - cl.movevars_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32); + k *= cl.movevars_aircontrol; + VectorMAM(speed, s->velocity, k, wishdir, s->velocity); + VectorNormalize(s->velocity); + } + + VectorScale(s->velocity, speed, s->velocity); + s->velocity[2] = zspeed; +} + +float CL_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor) +{ + return + (accelqw < 0 ? -1 : +1) + * + bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1); +} + +void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t wishspeed0, vec_t accel, vec_t accelqw, vec_t stretchfactor, vec_t sidefric, vec_t speedlimit) +{ + vec_t vel_straight; + vec_t vel_z; + vec3_t vel_perpend; + vec_t step; + vec3_t vel_xy; + vec_t vel_xy_current; + vec_t vel_xy_backward, vel_xy_forward; + vec_t speedclamp; + + if(stretchfactor > 0) + speedclamp = stretchfactor; + else if(accelqw < 0) + speedclamp = 1; + else + speedclamp = -1; // no clamping + + if(accelqw < 0) + accelqw = -accelqw; + + if(cl.moveflags & MOVEFLAG_Q2AIRACCELERATE) + wishspeed0 = wishspeed; // don't need to emulate this Q1 bug + + vel_straight = DotProduct(s->velocity, wishdir); + vel_z = s->velocity[2]; + VectorCopy(s->velocity, vel_xy); vel_xy[2] -= vel_z; + VectorMA(vel_xy, -vel_straight, wishdir, vel_perpend); + + step = accel * s->cmd.frametime * wishspeed0; + + vel_xy_current = VectorLength(vel_xy); + if(speedlimit > 0) + accelqw = CL_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed)); + vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw); + vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw); + if(vel_xy_backward < 0) + vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards + + vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw); + + if(sidefric < 0 && VectorLength2(vel_perpend)) + // negative: only apply so much sideways friction to stay below the speed you could get by "braking" + { + vec_t f, fmin; + f = max(0, 1 + s->cmd.frametime * wishspeed * sidefric); + fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / VectorLength2(vel_perpend); + // assume: fmin > 1 + // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend + // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend + // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy + // obviously, this cannot be + if(fmin <= 0) + VectorScale(vel_perpend, f, vel_perpend); + else + { + fmin = sqrt(fmin); + VectorScale(vel_perpend, max(fmin, f), vel_perpend); + } + } + else + VectorScale(vel_perpend, max(0, 1 - s->cmd.frametime * wishspeed * sidefric), vel_perpend); + + VectorMA(vel_perpend, vel_straight, wishdir, s->velocity); + + if(speedclamp >= 0) + { + vec_t vel_xy_preclamp; + vel_xy_preclamp = VectorLength(s->velocity); + if(vel_xy_preclamp > 0) // prevent division by zero + { + vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp; + if(vel_xy_current < vel_xy_preclamp) + VectorScale(s->velocity, (vel_xy_current / vel_xy_preclamp), s->velocity); + } + } + + s->velocity[2] += vel_z; +} + +void CL_ClientMovement_Physics_PM_AirAccelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed) +{ + vec3_t curvel, wishvel, acceldir, curdir; + float addspeed, accelspeed, curspeed; + float dot; + + float airforwardaccel = cl.movevars_warsowbunny_airforwardaccel; + float bunnyaccel = cl.movevars_warsowbunny_accel; + float bunnytopspeed = cl.movevars_warsowbunny_topspeed; + float turnaccel = cl.movevars_warsowbunny_turnaccel; + float backtosideratio = cl.movevars_warsowbunny_backtosideratio; + + if( !wishspeed ) + return; + + VectorCopy( s->velocity, curvel ); + curvel[2] = 0; + curspeed = VectorLength( curvel ); + + if( wishspeed > curspeed * 1.01f ) + { + float accelspeed = curspeed + airforwardaccel * cl.movevars_maxairspeed * s->cmd.frametime; + if( accelspeed < wishspeed ) + wishspeed = accelspeed; + } + else + { + float f = ( bunnytopspeed - curspeed ) / ( bunnytopspeed - cl.movevars_maxairspeed ); + if( f < 0 ) + f = 0; + wishspeed = max( curspeed, cl.movevars_maxairspeed ) + bunnyaccel * f * cl.movevars_maxairspeed * s->cmd.frametime; + } + VectorScale( wishdir, wishspeed, wishvel ); + VectorSubtract( wishvel, curvel, acceldir ); + addspeed = VectorNormalizeLength( acceldir ); + + accelspeed = turnaccel * cl.movevars_maxairspeed /* wishspeed */ * s->cmd.frametime; + if( accelspeed > addspeed ) + accelspeed = addspeed; + + if( backtosideratio < 1.0f ) + { + VectorNormalize2( curvel, curdir ); + dot = DotProduct( acceldir, curdir ); + if( dot < 0 ) + VectorMA( acceldir, -( 1.0f - backtosideratio ) * dot, curdir, acceldir ); + } + + VectorMA( s->velocity, accelspeed, acceldir, s->velocity ); +} + +void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s) +{ + vec_t friction; + vec_t wishspeed; + vec_t addspeed; + vec_t accelspeed; + vec_t f; + vec_t gravity; + vec3_t forward; + vec3_t right; + vec3_t up; + vec3_t wishvel; + vec3_t wishdir; + vec3_t yawangles; + trace_t trace; + + // jump if on ground with jump button pressed but only if it has been + // released at least once since the last jump + if (s->cmd.jump) + { + if (s->onground && (s->cmd.canjump || !cl_movement_track_canjump.integer)) // FIXME remove this cvar again when canjump logic actually works, or maybe keep it for mods that allow "pogo-ing" + { + s->velocity[2] += cl.movevars_jumpvelocity; + s->onground = false; + s->cmd.canjump = false; + } + } + else + s->cmd.canjump = true; + + // calculate movement vector + VectorSet(yawangles, 0, s->cmd.viewangles[1], 0); + AngleVectors(yawangles, forward, right, up); + VectorMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, wishvel); + + // split wishvel into wishspeed and wishdir + wishspeed = VectorLength(wishvel); + if (wishspeed) + VectorScale(wishvel, 1 / wishspeed, wishdir); + else + VectorSet( wishdir, 0.0, 0.0, 0.0 ); + // check if onground + if (s->onground) + { + wishspeed = min(wishspeed, cl.movevars_maxspeed); + if (s->crouched) + wishspeed *= 0.5; + + // apply edge friction + f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]); + if (f > 0) + { + friction = cl.movevars_friction; + if (cl.movevars_edgefriction != 1) + { + vec3_t neworigin2; + vec3_t neworigin3; + // note: QW uses the full player box for the trace, and yet still + // uses s->origin[2] + s->mins[2], which is clearly an bug, but + // this mimics it for compatibility + VectorSet(neworigin2, s->origin[0] + s->velocity[0]*(16/f), s->origin[1] + s->velocity[1]*(16/f), s->origin[2] + s->mins[2]); + VectorSet(neworigin3, neworigin2[0], neworigin2[1], neworigin2[2] - 34); + if (cls.protocol == PROTOCOL_QUAKEWORLD) + trace = CL_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); + else + trace = CL_TraceLine(neworigin2, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false, false); + if (trace.fraction == 1 && !trace.startsolid) + friction *= cl.movevars_edgefriction; + } + // apply ground friction + f = 1 - s->cmd.frametime * friction * ((f < cl.movevars_stopspeed) ? (cl.movevars_stopspeed / f) : 1); + f = max(f, 0); + VectorScale(s->velocity, f, s->velocity); + } + addspeed = wishspeed - DotProduct(s->velocity, wishdir); + if (addspeed > 0) + { + accelspeed = min(cl.movevars_accelerate * s->cmd.frametime * wishspeed, addspeed); + VectorMA(s->velocity, accelspeed, wishdir, s->velocity); + } + if(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND) + gravity = 0; + else + gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime; + if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) + s->velocity[2] -= gravity * 0.5f; + else + s->velocity[2] -= gravity; + if (cls.protocol == PROTOCOL_QUAKEWORLD) + s->velocity[2] = 0; + if (VectorLength2(s->velocity)) + CL_ClientMovement_Move(s); + if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) + s->velocity[2] -= gravity * 0.5f; + } + else + { + if (s->waterjumptime <= 0) + { + // apply air speed limit + vec_t accel, wishspeed0, wishspeed2, accelqw, strafity; + qboolean accelerating; + + accelqw = cl.movevars_airaccel_qw; + wishspeed0 = wishspeed; + wishspeed = min(wishspeed, cl.movevars_maxairspeed); + if (s->crouched) + wishspeed *= 0.5; + accel = cl.movevars_airaccelerate; + + accelerating = (DotProduct(s->velocity, wishdir) > 0); + wishspeed2 = wishspeed; + + // CPM: air control + if(cl.movevars_airstopaccelerate != 0) + { + vec3_t curdir; + curdir[0] = s->velocity[0]; + curdir[1] = s->velocity[1]; + curdir[2] = 0; + VectorNormalize(curdir); + accel = accel + (cl.movevars_airstopaccelerate - accel) * max(0, -DotProduct(curdir, wishdir)); + } + strafity = CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, -90) + CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, +90); // if one is nonzero, other is always zero + if(cl.movevars_maxairstrafespeed) + wishspeed = min(wishspeed, CL_GeomLerp(cl.movevars_maxairspeed, strafity, cl.movevars_maxairstrafespeed)); + if(cl.movevars_airstrafeaccelerate) + accel = CL_GeomLerp(cl.movevars_airaccelerate, strafity, cl.movevars_airstrafeaccelerate); + if(cl.movevars_airstrafeaccel_qw) + accelqw = + (((strafity > 0.5 ? cl.movevars_airstrafeaccel_qw : cl.movevars_airaccel_qw) >= 0) ? +1 : -1) + * + (1 - CL_GeomLerp(1 - fabs(cl.movevars_airaccel_qw), strafity, 1 - fabs(cl.movevars_airstrafeaccel_qw))); + // !CPM + + if(cl.movevars_warsowbunny_turnaccel && accelerating && s->cmd.sidemove == 0 && s->cmd.forwardmove != 0) + CL_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2); + else + CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, cl.movevars_airaccel_qw_stretchfactor, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed, cl.movevars_airspeedlimit_nonqw); + + if(cl.movevars_aircontrol) + CL_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2); + } + gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime; + if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) + s->velocity[2] -= gravity * 0.5f; + else + s->velocity[2] -= gravity; + CL_ClientMovement_Move(s); + if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) + s->velocity[2] -= gravity * 0.5f; + } +} + +void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s) +{ + //Con_Printf(" %f", frametime); + if (!s->cmd.jump) + s->cmd.canjump = true; + s->waterjumptime -= s->cmd.frametime; + CL_ClientMovement_UpdateStatus(s); + if (s->waterlevel >= WATERLEVEL_SWIMMING) + CL_ClientMovement_Physics_Swim(s); + else + CL_ClientMovement_Physics_Walk(s); +} + +extern cvar_t slowmo; +void CL_UpdateMoveVars(void) +{ + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + cl.moveflags = 0; + } + else if (cl.stats[STAT_MOVEVARS_TICRATE]) + { + cl.moveflags = cl.stats[STAT_MOVEFLAGS]; + cl.movevars_ticrate = cl.statsf[STAT_MOVEVARS_TICRATE]; + cl.movevars_timescale = cl.statsf[STAT_MOVEVARS_TIMESCALE]; + cl.movevars_gravity = cl.statsf[STAT_MOVEVARS_GRAVITY]; + cl.movevars_stopspeed = cl.statsf[STAT_MOVEVARS_STOPSPEED] ; + cl.movevars_maxspeed = cl.statsf[STAT_MOVEVARS_MAXSPEED]; + cl.movevars_spectatormaxspeed = cl.statsf[STAT_MOVEVARS_SPECTATORMAXSPEED]; + cl.movevars_accelerate = cl.statsf[STAT_MOVEVARS_ACCELERATE]; + cl.movevars_airaccelerate = cl.statsf[STAT_MOVEVARS_AIRACCELERATE]; + cl.movevars_wateraccelerate = cl.statsf[STAT_MOVEVARS_WATERACCELERATE]; + cl.movevars_entgravity = cl.statsf[STAT_MOVEVARS_ENTGRAVITY]; + cl.movevars_jumpvelocity = cl.statsf[STAT_MOVEVARS_JUMPVELOCITY]; + cl.movevars_edgefriction = cl.statsf[STAT_MOVEVARS_EDGEFRICTION]; + cl.movevars_maxairspeed = cl.statsf[STAT_MOVEVARS_MAXAIRSPEED]; + cl.movevars_stepheight = cl.statsf[STAT_MOVEVARS_STEPHEIGHT]; + cl.movevars_airaccel_qw = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW]; + cl.movevars_airaccel_qw_stretchfactor = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR]; + cl.movevars_airaccel_sideways_friction = cl.statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION]; + cl.movevars_friction = cl.statsf[STAT_MOVEVARS_FRICTION]; + cl.movevars_wallfriction = cl.statsf[STAT_MOVEVARS_WALLFRICTION]; + cl.movevars_waterfriction = cl.statsf[STAT_MOVEVARS_WATERFRICTION]; + cl.movevars_airstopaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTOPACCELERATE]; + cl.movevars_airstrafeaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE]; + cl.movevars_maxairstrafespeed = cl.statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED]; + cl.movevars_airstrafeaccel_qw = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW]; + cl.movevars_aircontrol = cl.statsf[STAT_MOVEVARS_AIRCONTROL]; + cl.movevars_aircontrol_power = cl.statsf[STAT_MOVEVARS_AIRCONTROL_POWER]; + cl.movevars_aircontrol_penalty = cl.statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY]; + cl.movevars_warsowbunny_airforwardaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL]; + cl.movevars_warsowbunny_accel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL]; + cl.movevars_warsowbunny_topspeed = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED]; + cl.movevars_warsowbunny_turnaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL]; + cl.movevars_warsowbunny_backtosideratio = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO]; + cl.movevars_airspeedlimit_nonqw = cl.statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW]; + } + else + { + cl.moveflags = 0; + cl.movevars_ticrate = slowmo.value / bound(1.0f, cl_netfps.value, 1000.0f); + cl.movevars_timescale = slowmo.value; + cl.movevars_gravity = sv_gravity.value; + cl.movevars_stopspeed = cl_movement_stopspeed.value; + cl.movevars_maxspeed = cl_movement_maxspeed.value; + cl.movevars_spectatormaxspeed = cl_movement_maxspeed.value; + cl.movevars_accelerate = cl_movement_accelerate.value; + cl.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value; + cl.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value; + cl.movevars_friction = cl_movement_friction.value; + cl.movevars_wallfriction = cl_movement_wallfriction.value; + cl.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value; + cl.movevars_entgravity = 1; + cl.movevars_jumpvelocity = cl_movement_jumpvelocity.value; + cl.movevars_edgefriction = cl_movement_edgefriction.value; + cl.movevars_maxairspeed = cl_movement_maxairspeed.value; + cl.movevars_stepheight = cl_movement_stepheight.value; + cl.movevars_airaccel_qw = cl_movement_airaccel_qw.value; + cl.movevars_airaccel_qw_stretchfactor = 0; + cl.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value; + cl.movevars_airstopaccelerate = 0; + cl.movevars_airstrafeaccelerate = 0; + cl.movevars_maxairstrafespeed = 0; + cl.movevars_airstrafeaccel_qw = 0; + cl.movevars_aircontrol = 0; + cl.movevars_aircontrol_power = 2; + cl.movevars_aircontrol_penalty = 0; + cl.movevars_warsowbunny_airforwardaccel = 0; + cl.movevars_warsowbunny_accel = 0; + cl.movevars_warsowbunny_topspeed = 0; + cl.movevars_warsowbunny_turnaccel = 0; + cl.movevars_warsowbunny_backtosideratio = 0; + cl.movevars_airspeedlimit_nonqw = 0; + } + + if(!(cl.moveflags & MOVEFLAG_VALID)) + { + if(gamemode == GAME_NEXUIZ) + cl.moveflags = MOVEFLAG_Q2AIRACCELERATE; + } + + if(cl.movevars_aircontrol_power <= 0) + cl.movevars_aircontrol_power = 2; // CPMA default +} + +void CL_ClientMovement_Replay(void) +{ + int i; + double totalmovemsec; + cl_clientmovement_state_t s; + + if (cl.movement_predicted && !cl.movement_replay) + return; + + if (!cl_movement_replay.integer) + return; + + // set up starting state for the series of moves + memset(&s, 0, sizeof(s)); + VectorCopy(cl.entities[cl.playerentity].state_current.origin, s.origin); + VectorCopy(cl.mvelocity[0], s.velocity); + s.crouched = true; // will be updated on first move + //Con_Printf("movement replay starting org %f %f %f vel %f %f %f\n", s.origin[0], s.origin[1], s.origin[2], s.velocity[0], s.velocity[1], s.velocity[2]); + + totalmovemsec = 0; + for (i = 0;i < CL_MAX_USERCMDS;i++) + if (cl.movecmd[i].sequence > cls.servermovesequence) + totalmovemsec += cl.movecmd[i].msec; + cl.movement_predicted = totalmovemsec >= cl_movement_minping.value && cls.servermovesequence && (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission); + //Con_Printf("%i = %.0f >= %.0f && %i && (%i && %i && %i == %i && %i > 0 && %i\n", cl.movement_predicted, totalmovemsec, cl_movement_minping.value, cls.servermovesequence, cl_movement.integer, !cls.demoplayback, cls.signon, SIGNONS, cl.stats[STAT_HEALTH], !cl.intermission); + if (cl.movement_predicted) + { + //Con_Printf("%ims\n", cl.movecmd[0].msec); + + // replay the input queue to predict current location + // note: this relies on the fact there's always one queue item at the end + + // find how many are still valid + for (i = 0;i < CL_MAX_USERCMDS;i++) + if (cl.movecmd[i].sequence <= cls.servermovesequence) + break; + // now walk them in oldest to newest order + for (i--;i >= 0;i--) + { + s.cmd = cl.movecmd[i]; + if (i < CL_MAX_USERCMDS - 1) + s.cmd.canjump = cl.movecmd[i+1].canjump; + // if a move is more than 50ms, do it as two moves (matching qwsv) + //Con_Printf("%i ", s.cmd.msec); + if(s.cmd.frametime > 0.0005) + { + if (s.cmd.frametime > 0.05) + { + s.cmd.frametime /= 2; + CL_ClientMovement_PlayerMove(&s); + } + CL_ClientMovement_PlayerMove(&s); + cl.movecmd[i].canjump = s.cmd.canjump; + } + } + //Con_Printf("\n"); + CL_ClientMovement_UpdateStatus(&s); + } + else + { + // get the first movement queue entry to know whether to crouch and such + s.cmd = cl.movecmd[0]; + } + + if (cls.demoplayback) // for bob, speedometer + VectorCopy(cl.mvelocity[0], cl.movement_velocity); + else + { + cl.movement_replay = false; + // update the interpolation target position and velocity + VectorCopy(s.origin, cl.movement_origin); + VectorCopy(s.velocity, cl.movement_velocity); + } + + // update the onground flag if appropriate + if (cl.movement_predicted) + { + // when predicted we simply set the flag according to the UpdateStatus + cl.onground = s.onground; + } + else + { + // when not predicted, cl.onground is cleared by cl_parse.c each time + // an update packet is received, but can be forced on here to hide + // server inconsistencies in the onground flag + // (which mostly occur when stepping up stairs at very high framerates + // where after the step up the move continues forward and not + // downward so the ground is not detected) + // + // such onground inconsistencies can cause jittery gun bobbing and + // stair smoothing, so we set onground if UpdateStatus says so + if (s.onground) + cl.onground = true; + } + + // react to onground state changes (for gun bob) + if (cl.onground) + { + if (!cl.oldonground) + cl.hitgroundtime = cl.movecmd[0].time; + cl.lastongroundtime = cl.movecmd[0].time; + } + cl.oldonground = cl.onground; +} + +void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to) +{ + int bits; + + bits = 0; + if (to->viewangles[0] != from->viewangles[0]) + bits |= QW_CM_ANGLE1; + if (to->viewangles[1] != from->viewangles[1]) + bits |= QW_CM_ANGLE2; + if (to->viewangles[2] != from->viewangles[2]) + bits |= QW_CM_ANGLE3; + if (to->forwardmove != from->forwardmove) + bits |= QW_CM_FORWARD; + if (to->sidemove != from->sidemove) + bits |= QW_CM_SIDE; + if (to->upmove != from->upmove) + bits |= QW_CM_UP; + if (to->buttons != from->buttons) + bits |= QW_CM_BUTTONS; + if (to->impulse != from->impulse) + bits |= QW_CM_IMPULSE; + + MSG_WriteByte(buf, bits); + if (bits & QW_CM_ANGLE1) + MSG_WriteAngle16i(buf, to->viewangles[0]); + if (bits & QW_CM_ANGLE2) + MSG_WriteAngle16i(buf, to->viewangles[1]); + if (bits & QW_CM_ANGLE3) + MSG_WriteAngle16i(buf, to->viewangles[2]); + if (bits & QW_CM_FORWARD) + MSG_WriteShort(buf, (short) to->forwardmove); + if (bits & QW_CM_SIDE) + MSG_WriteShort(buf, (short) to->sidemove); + if (bits & QW_CM_UP) + MSG_WriteShort(buf, (short) to->upmove); + if (bits & QW_CM_BUTTONS) + MSG_WriteByte(buf, to->buttons); + if (bits & QW_CM_IMPULSE) + MSG_WriteByte(buf, to->impulse); + MSG_WriteByte(buf, to->msec); +} + +void CL_NewFrameReceived(int num) +{ + if (developer_networkentities.integer >= 10) + Con_Printf("recv: svc_entities %i\n", num); + cl.latestframenums[cl.latestframenumsposition] = num; + cl.latestsendnums[cl.latestframenumsposition] = cl.cmd.sequence; + cl.latestframenumsposition = (cl.latestframenumsposition + 1) % LATESTFRAMENUMS; +} + +void CL_RotateMoves(const matrix4x4_t *m) +{ + // rotate viewangles in all previous moves + vec3_t v; + vec3_t f, r, u; + int i; + for (i = 0;i < CL_MAX_USERCMDS;i++) + { + if (cl.movecmd[i].sequence > cls.servermovesequence) + { + usercmd_t *c = &cl.movecmd[i]; + AngleVectors(c->viewangles, f, r, u); + Matrix4x4_Transform(m, f, v); VectorCopy(v, f); + Matrix4x4_Transform(m, u, v); VectorCopy(v, u); + AnglesFromVectors(c->viewangles, f, u, false); + } + } +} + +/* +============== +CL_SendMove +============== +*/ +usercmd_t nullcmd; // for delta compression of qw moves +void CL_SendMove(void) +{ + int i, j, packetloss; + int checksumindex; + int bits; + int maxusercmds; + usercmd_t *cmd; + sizebuf_t buf; + unsigned char data[1024]; + double packettime; + int msecdelta; + qboolean quemove; + qboolean important; + + // if playing a demo, do nothing + if (!cls.netcon) + return; + + // we don't que moves during a lag spike (potential network timeout) + quemove = realtime - cl.last_received_message < cl_movement_nettimeout.value; + + // we build up cl.cmd and then decide whether to send or not + // we store this into cl.movecmd[0] for prediction each frame even if we + // do not send, to make sure that prediction is instant + cl.cmd.time = cl.time; + cl.cmd.sequence = cls.netcon->outgoing_unreliable_sequence; + + // set button bits + // LordHavoc: added 6 new buttons and use and chat buttons, and prydon cursor active button + bits = 0; + if (in_attack.state & 3) bits |= 1; + if (in_jump.state & 3) bits |= 2; + if (in_button3.state & 3) bits |= 4; + if (in_button4.state & 3) bits |= 8; + if (in_button5.state & 3) bits |= 16; + if (in_button6.state & 3) bits |= 32; + if (in_button7.state & 3) bits |= 64; + if (in_button8.state & 3) bits |= 128; + if (in_use.state & 3) bits |= 256; + if (key_dest != key_game || key_consoleactive) bits |= 512; + if (cl_prydoncursor.integer > 0) bits |= 1024; + if (in_button9.state & 3) bits |= 2048; + if (in_button10.state & 3) bits |= 4096; + if (in_button11.state & 3) bits |= 8192; + if (in_button12.state & 3) bits |= 16384; + if (in_button13.state & 3) bits |= 32768; + if (in_button14.state & 3) bits |= 65536; + if (in_button15.state & 3) bits |= 131072; + if (in_button16.state & 3) bits |= 262144; + // button bits 19-31 unused currently + // rotate/zoom view serverside if PRYDON_CLIENTCURSOR cursor is at edge of screen + if(cl_prydoncursor.integer > 0) + { + if (cl.cmd.cursor_screen[0] <= -1) bits |= 8; + if (cl.cmd.cursor_screen[0] >= 1) bits |= 16; + if (cl.cmd.cursor_screen[1] <= -1) bits |= 32; + if (cl.cmd.cursor_screen[1] >= 1) bits |= 64; + } + + // set buttons and impulse + cl.cmd.buttons = bits; + cl.cmd.impulse = in_impulse; + + // set viewangles + VectorCopy(cl.viewangles, cl.cmd.viewangles); + + msecdelta = (int)(floor(cl.cmd.time * 1000) - floor(cl.movecmd[1].time * 1000)); + cl.cmd.msec = (unsigned char)bound(0, msecdelta, 255); + // ridiculous value rejection (matches qw) + if (cl.cmd.msec > 250) + cl.cmd.msec = 100; + cl.cmd.frametime = cl.cmd.msec * (1.0 / 1000.0); + + cl.cmd.predicted = cl_movement.integer != 0; + + // movement is set by input code (forwardmove/sidemove/upmove) + // always dump the first two moves, because they may contain leftover inputs from the last level + if (cl.cmd.sequence <= 2) + cl.cmd.forwardmove = cl.cmd.sidemove = cl.cmd.upmove = cl.cmd.impulse = cl.cmd.buttons = 0; + + cl.cmd.jump = (cl.cmd.buttons & 2) != 0; + cl.cmd.crouch = 0; + switch (cls.protocol) + { + case PROTOCOL_QUAKEWORLD: + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + case PROTOCOL_NEHAHRAMOVIE: + case PROTOCOL_NEHAHRABJP: + case PROTOCOL_NEHAHRABJP2: + case PROTOCOL_NEHAHRABJP3: + case PROTOCOL_DARKPLACES1: + case PROTOCOL_DARKPLACES2: + case PROTOCOL_DARKPLACES3: + case PROTOCOL_DARKPLACES4: + case PROTOCOL_DARKPLACES5: + break; + case PROTOCOL_DARKPLACES6: + case PROTOCOL_DARKPLACES7: + // FIXME: cl.cmd.buttons & 16 is +button5, Nexuiz/Xonotic specific + cl.cmd.crouch = (cl.cmd.buttons & 16) != 0; + break; + case PROTOCOL_UNKNOWN: + break; + } + + if (quemove) + cl.movecmd[0] = cl.cmd; + + // don't predict more than 200fps + if (realtime >= cl.lastpackettime + 0.005) + cl.movement_replay = true; // redo the prediction + + // now decide whether to actually send this move + // (otherwise it is only for prediction) + + // don't send too often or else network connections can get clogged by a + // high renderer framerate + packettime = 1.0 / bound(1, cl_netfps.value, 1000); + if (cl.movevars_timescale && cl.movevars_ticrate) + { + float maxtic = cl.movevars_ticrate / cl.movevars_timescale; + packettime = min(packettime, maxtic); + } + + // do not send 0ms packets because they mess up physics + if(cl.cmd.msec == 0 && cl.time > cl.oldtime && (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS)) + return; + // always send if buttons changed or an impulse is pending + // even if it violates the rate limit! + important = (cl.cmd.impulse || (cl_netimmediatebuttons.integer && cl.cmd.buttons != cl.movecmd[1].buttons)); + // don't send too often (cl_netfps) + if (!important && realtime < cl.lastpackettime + packettime) + return; + // don't choke the connection with packets (obey rate limit) + // it is important that this check be last, because it adds a new + // frame to the shownetgraph output and any cancelation after this + // will produce a nasty spike-like look to the netgraph + // we also still send if it is important + if (!NetConn_CanSend(cls.netcon) && !important) + return; + // try to round off the lastpackettime to a multiple of the packet interval + // (this causes it to emit packets at a steady beat) + if (packettime > 0) + cl.lastpackettime = floor(realtime / packettime) * packettime; + else + cl.lastpackettime = realtime; + + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + // send the movement message + // PROTOCOL_QUAKE clc_move = 16 bytes total + // PROTOCOL_QUAKEDP clc_move = 16 bytes total + // PROTOCOL_NEHAHRAMOVIE clc_move = 16 bytes total + // PROTOCOL_DARKPLACES1 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES2 clc_move = 25 bytes total + // PROTOCOL_DARKPLACES3 clc_move = 25 bytes total + // PROTOCOL_DARKPLACES4 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES5 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES6 clc_move = 52 bytes total + // PROTOCOL_DARKPLACES7 clc_move = 56 bytes total per move (can be up to 16 moves) + // PROTOCOL_QUAKEWORLD clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll) + + // set prydon cursor info + CL_UpdatePrydonCursor(); + + if (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS) + { + switch (cls.protocol) + { + case PROTOCOL_QUAKEWORLD: + MSG_WriteByte(&buf, qw_clc_move); + // save the position for a checksum byte + checksumindex = buf.cursize; + MSG_WriteByte(&buf, 0); + // packet loss percentage + for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++) + if (cls.netcon->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) + packetloss++; + packetloss = packetloss * 100 / NETGRAPH_PACKETS; + MSG_WriteByte(&buf, packetloss); + // write most recent 3 moves + QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, &cl.movecmd[2]); + QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[2], &cl.movecmd[1]); + QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.cmd); + // calculate the checksum + buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->outgoing_unreliable_sequence); + // if delta compression history overflows, request no delta + if (cls.netcon->outgoing_unreliable_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1) + cl.qw_validsequence = 0; + // request delta compression if appropriate + if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording) + { + cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = cl.qw_validsequence; + MSG_WriteByte(&buf, qw_clc_delta); + MSG_WriteByte(&buf, cl.qw_validsequence & 255); + } + else + cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = -1; + break; + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + case PROTOCOL_NEHAHRAMOVIE: + case PROTOCOL_NEHAHRABJP: + case PROTOCOL_NEHAHRABJP2: + case PROTOCOL_NEHAHRABJP3: + // 5 bytes + MSG_WriteByte (&buf, clc_move); + MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time + // 3 bytes (6 bytes in proquake) + if (cls.proquake_servermod == 1) // MOD_PROQUAKE + { + for (i = 0;i < 3;i++) + MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]); + } + else + { + for (i = 0;i < 3;i++) + MSG_WriteAngle8i (&buf, cl.cmd.viewangles[i]); + } + // 6 bytes + MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); + MSG_WriteCoord16i (&buf, cl.cmd.sidemove); + MSG_WriteCoord16i (&buf, cl.cmd.upmove); + // 2 bytes + MSG_WriteByte (&buf, cl.cmd.buttons); + MSG_WriteByte (&buf, cl.cmd.impulse); + break; + case PROTOCOL_DARKPLACES2: + case PROTOCOL_DARKPLACES3: + // 5 bytes + MSG_WriteByte (&buf, clc_move); + MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time + // 12 bytes + for (i = 0;i < 3;i++) + MSG_WriteAngle32f (&buf, cl.cmd.viewangles[i]); + // 6 bytes + MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); + MSG_WriteCoord16i (&buf, cl.cmd.sidemove); + MSG_WriteCoord16i (&buf, cl.cmd.upmove); + // 2 bytes + MSG_WriteByte (&buf, cl.cmd.buttons); + MSG_WriteByte (&buf, cl.cmd.impulse); + break; + case PROTOCOL_DARKPLACES1: + case PROTOCOL_DARKPLACES4: + case PROTOCOL_DARKPLACES5: + // 5 bytes + MSG_WriteByte (&buf, clc_move); + MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time + // 6 bytes + for (i = 0;i < 3;i++) + MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]); + // 6 bytes + MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); + MSG_WriteCoord16i (&buf, cl.cmd.sidemove); + MSG_WriteCoord16i (&buf, cl.cmd.upmove); + // 2 bytes + MSG_WriteByte (&buf, cl.cmd.buttons); + MSG_WriteByte (&buf, cl.cmd.impulse); + case PROTOCOL_DARKPLACES6: + case PROTOCOL_DARKPLACES7: + // set the maxusercmds variable to limit how many should be sent + maxusercmds = bound(1, cl_netrepeatinput.integer + 1, min(3, CL_MAX_USERCMDS)); + // when movement prediction is off, there's not much point in repeating old input as it will just be ignored + if (!cl.cmd.predicted) + maxusercmds = 1; + + // send the latest moves in order, the old ones will be + // ignored by the server harmlessly, however if the previous + // packets were lost these moves will be used + // + // this reduces packet loss impact on gameplay. + for (j = 0, cmd = &cl.movecmd[maxusercmds-1];j < maxusercmds;j++, cmd--) + { + // don't repeat any stale moves + if (cmd->sequence && cmd->sequence < cls.servermovesequence) + continue; + // 5/9 bytes + MSG_WriteByte (&buf, clc_move); + if (cls.protocol != PROTOCOL_DARKPLACES6) + MSG_WriteLong (&buf, cmd->predicted ? cmd->sequence : 0); + MSG_WriteFloat (&buf, cmd->time); // last server packet time + // 6 bytes + for (i = 0;i < 3;i++) + MSG_WriteAngle16i (&buf, cmd->viewangles[i]); + // 6 bytes + MSG_WriteCoord16i (&buf, cmd->forwardmove); + MSG_WriteCoord16i (&buf, cmd->sidemove); + MSG_WriteCoord16i (&buf, cmd->upmove); + // 5 bytes + MSG_WriteLong (&buf, cmd->buttons); + MSG_WriteByte (&buf, cmd->impulse); + // PRYDON_CLIENTCURSOR + // 30 bytes + MSG_WriteShort (&buf, (short)(cmd->cursor_screen[0] * 32767.0f)); + MSG_WriteShort (&buf, (short)(cmd->cursor_screen[1] * 32767.0f)); + MSG_WriteFloat (&buf, cmd->cursor_start[0]); + MSG_WriteFloat (&buf, cmd->cursor_start[1]); + MSG_WriteFloat (&buf, cmd->cursor_start[2]); + MSG_WriteFloat (&buf, cmd->cursor_impact[0]); + MSG_WriteFloat (&buf, cmd->cursor_impact[1]); + MSG_WriteFloat (&buf, cmd->cursor_impact[2]); + MSG_WriteShort (&buf, cmd->cursor_entitynumber); + } + break; + case PROTOCOL_UNKNOWN: + break; + } + } + + if (cls.protocol != PROTOCOL_QUAKEWORLD && buf.cursize) + { + // ack entity frame numbers received since the last input was sent + // (redundent to improve handling of client->server packet loss) + // if cl_netrepeatinput is 1 and client framerate matches server + // framerate, this is 10 bytes, if client framerate is lower this + // will be more... + int i, j; + int oldsequence = cl.cmd.sequence - bound(1, cl_netrepeatinput.integer + 1, 3); + if (oldsequence < 1) + oldsequence = 1; + for (i = 0;i < LATESTFRAMENUMS;i++) + { + j = (cl.latestframenumsposition + i) % LATESTFRAMENUMS; + if (cl.latestsendnums[j] >= oldsequence) + { + if (developer_networkentities.integer >= 10) + Con_Printf("send clc_ackframe %i\n", cl.latestframenums[j]); + MSG_WriteByte(&buf, clc_ackframe); + MSG_WriteLong(&buf, cl.latestframenums[j]); + } + } + } + + // PROTOCOL_DARKPLACES6 = 67 bytes per packet + // PROTOCOL_DARKPLACES7 = 71 bytes per packet + + // acknowledge any recently received data blocks + for (i = 0;i < CL_MAX_DOWNLOADACKS && (cls.dp_downloadack[i].start || cls.dp_downloadack[i].size);i++) + { + MSG_WriteByte(&buf, clc_ackdownloaddata); + MSG_WriteLong(&buf, cls.dp_downloadack[i].start); + MSG_WriteShort(&buf, cls.dp_downloadack[i].size); + cls.dp_downloadack[i].start = 0; + cls.dp_downloadack[i].size = 0; + } + + // send the reliable message (forwarded commands) if there is one + if (buf.cursize || cls.netcon->message.cursize) + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer), false); + + if (quemove) + { + // update the cl.movecmd array which holds the most recent moves, + // because we now need a new slot for the next input + for (i = CL_MAX_USERCMDS - 1;i >= 1;i--) + cl.movecmd[i] = cl.movecmd[i-1]; + cl.movecmd[0].msec = 0; + cl.movecmd[0].frametime = 0; + } + + // clear button 'click' states + in_attack.state &= ~2; + in_jump.state &= ~2; + in_button3.state &= ~2; + in_button4.state &= ~2; + in_button5.state &= ~2; + in_button6.state &= ~2; + in_button7.state &= ~2; + in_button8.state &= ~2; + in_use.state &= ~2; + in_button9.state &= ~2; + in_button10.state &= ~2; + in_button11.state &= ~2; + in_button12.state &= ~2; + in_button13.state &= ~2; + in_button14.state &= ~2; + in_button15.state &= ~2; + in_button16.state &= ~2; + // clear impulse + in_impulse = 0; + + if (cls.netcon->message.overflowed) + { + Con_Print("CL_SendMove: lost server connection\n"); + CL_Disconnect(); + Host_ShutdownServer(); + } +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput (void) +{ + Cmd_AddCommand ("+moveup",IN_UpDown, "swim upward"); + Cmd_AddCommand ("-moveup",IN_UpUp, "stop swimming upward"); + Cmd_AddCommand ("+movedown",IN_DownDown, "swim downward"); + Cmd_AddCommand ("-movedown",IN_DownUp, "stop swimming downward"); + Cmd_AddCommand ("+left",IN_LeftDown, "turn left"); + Cmd_AddCommand ("-left",IN_LeftUp, "stop turning left"); + Cmd_AddCommand ("+right",IN_RightDown, "turn right"); + Cmd_AddCommand ("-right",IN_RightUp, "stop turning right"); + Cmd_AddCommand ("+forward",IN_ForwardDown, "move forward"); + Cmd_AddCommand ("-forward",IN_ForwardUp, "stop moving forward"); + Cmd_AddCommand ("+back",IN_BackDown, "move backward"); + Cmd_AddCommand ("-back",IN_BackUp, "stop moving backward"); + Cmd_AddCommand ("+lookup", IN_LookupDown, "look upward"); + Cmd_AddCommand ("-lookup", IN_LookupUp, "stop looking upward"); + Cmd_AddCommand ("+lookdown", IN_LookdownDown, "look downward"); + Cmd_AddCommand ("-lookdown", IN_LookdownUp, "stop looking downward"); + Cmd_AddCommand ("+strafe", IN_StrafeDown, "activate strafing mode (move instead of turn)"); + Cmd_AddCommand ("-strafe", IN_StrafeUp, "deactivate strafing mode"); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown, "strafe left"); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp, "stop strafing left"); + Cmd_AddCommand ("+moveright", IN_MoverightDown, "strafe right"); + Cmd_AddCommand ("-moveright", IN_MoverightUp, "stop strafing right"); + Cmd_AddCommand ("+speed", IN_SpeedDown, "activate run mode (faster movement and turning)"); + Cmd_AddCommand ("-speed", IN_SpeedUp, "deactivate run mode"); + Cmd_AddCommand ("+attack", IN_AttackDown, "begin firing"); + Cmd_AddCommand ("-attack", IN_AttackUp, "stop firing"); + Cmd_AddCommand ("+jump", IN_JumpDown, "jump"); + Cmd_AddCommand ("-jump", IN_JumpUp, "end jump (so you can jump again)"); + Cmd_AddCommand ("impulse", IN_Impulse, "send an impulse number to server (select weapon, use item, etc)"); + Cmd_AddCommand ("+klook", IN_KLookDown, "activate keyboard looking mode, do not recenter view"); + Cmd_AddCommand ("-klook", IN_KLookUp, "deactivate keyboard looking mode"); + Cmd_AddCommand ("+mlook", IN_MLookDown, "activate mouse looking mode, do not recenter view"); + Cmd_AddCommand ("-mlook", IN_MLookUp, "deactivate mouse looking mode"); + + // LordHavoc: added use button + Cmd_AddCommand ("+use", IN_UseDown, "use something (may be used by some mods)"); + Cmd_AddCommand ("-use", IN_UseUp, "stop using something"); + + // LordHavoc: added 6 new buttons + Cmd_AddCommand ("+button3", IN_Button3Down, "activate button3 (behavior depends on mod)"); + Cmd_AddCommand ("-button3", IN_Button3Up, "deactivate button3"); + Cmd_AddCommand ("+button4", IN_Button4Down, "activate button4 (behavior depends on mod)"); + Cmd_AddCommand ("-button4", IN_Button4Up, "deactivate button4"); + Cmd_AddCommand ("+button5", IN_Button5Down, "activate button5 (behavior depends on mod)"); + Cmd_AddCommand ("-button5", IN_Button5Up, "deactivate button5"); + Cmd_AddCommand ("+button6", IN_Button6Down, "activate button6 (behavior depends on mod)"); + Cmd_AddCommand ("-button6", IN_Button6Up, "deactivate button6"); + Cmd_AddCommand ("+button7", IN_Button7Down, "activate button7 (behavior depends on mod)"); + Cmd_AddCommand ("-button7", IN_Button7Up, "deactivate button7"); + Cmd_AddCommand ("+button8", IN_Button8Down, "activate button8 (behavior depends on mod)"); + Cmd_AddCommand ("-button8", IN_Button8Up, "deactivate button8"); + Cmd_AddCommand ("+button9", IN_Button9Down, "activate button9 (behavior depends on mod)"); + Cmd_AddCommand ("-button9", IN_Button9Up, "deactivate button9"); + Cmd_AddCommand ("+button10", IN_Button10Down, "activate button10 (behavior depends on mod)"); + Cmd_AddCommand ("-button10", IN_Button10Up, "deactivate button10"); + Cmd_AddCommand ("+button11", IN_Button11Down, "activate button11 (behavior depends on mod)"); + Cmd_AddCommand ("-button11", IN_Button11Up, "deactivate button11"); + Cmd_AddCommand ("+button12", IN_Button12Down, "activate button12 (behavior depends on mod)"); + Cmd_AddCommand ("-button12", IN_Button12Up, "deactivate button12"); + Cmd_AddCommand ("+button13", IN_Button13Down, "activate button13 (behavior depends on mod)"); + Cmd_AddCommand ("-button13", IN_Button13Up, "deactivate button13"); + Cmd_AddCommand ("+button14", IN_Button14Down, "activate button14 (behavior depends on mod)"); + Cmd_AddCommand ("-button14", IN_Button14Up, "deactivate button14"); + Cmd_AddCommand ("+button15", IN_Button15Down, "activate button15 (behavior depends on mod)"); + Cmd_AddCommand ("-button15", IN_Button15Up, "deactivate button15"); + Cmd_AddCommand ("+button16", IN_Button16Down, "activate button16 (behavior depends on mod)"); + Cmd_AddCommand ("-button16", IN_Button16Up, "deactivate button16"); + + // LordHavoc: added bestweapon command + Cmd_AddCommand ("bestweapon", IN_BestWeapon, "send an impulse number to server to select the first usable weapon out of several (example: 8 7 6 5 4 3 2 1)"); +#if 0 + Cmd_AddCommand ("cycleweapon", IN_CycleWeapon, "send an impulse number to server to select the next usable weapon out of several (example: 9 4 8) if you are holding one of these, and choose the first one if you are holding none of these"); +#endif + Cmd_AddCommand ("register_bestweapon", IN_BestWeapon_Register_f, "(for QC usage only) change weapon parameters to be used by bestweapon; stuffcmd this in ClientConnect"); + + Cvar_RegisterVariable(&cl_movecliptokeyboard); + Cvar_RegisterVariable(&cl_movement); + Cvar_RegisterVariable(&cl_movement_replay); + Cvar_RegisterVariable(&cl_movement_nettimeout); + Cvar_RegisterVariable(&cl_movement_minping); + Cvar_RegisterVariable(&cl_movement_track_canjump); + Cvar_RegisterVariable(&cl_movement_maxspeed); + Cvar_RegisterVariable(&cl_movement_maxairspeed); + Cvar_RegisterVariable(&cl_movement_stopspeed); + Cvar_RegisterVariable(&cl_movement_friction); + Cvar_RegisterVariable(&cl_movement_wallfriction); + Cvar_RegisterVariable(&cl_movement_waterfriction); + Cvar_RegisterVariable(&cl_movement_edgefriction); + Cvar_RegisterVariable(&cl_movement_stepheight); + Cvar_RegisterVariable(&cl_movement_accelerate); + Cvar_RegisterVariable(&cl_movement_airaccelerate); + Cvar_RegisterVariable(&cl_movement_wateraccelerate); + Cvar_RegisterVariable(&cl_movement_jumpvelocity); + Cvar_RegisterVariable(&cl_movement_airaccel_qw); + Cvar_RegisterVariable(&cl_movement_airaccel_sideways_friction); + + Cvar_RegisterVariable(&in_pitch_min); + Cvar_RegisterVariable(&in_pitch_max); + Cvar_RegisterVariable(&m_filter); + Cvar_RegisterVariable(&m_accelerate); + Cvar_RegisterVariable(&m_accelerate_minspeed); + Cvar_RegisterVariable(&m_accelerate_maxspeed); + Cvar_RegisterVariable(&m_accelerate_filter); + + Cvar_RegisterVariable(&cl_netfps); + Cvar_RegisterVariable(&cl_netrepeatinput); + Cvar_RegisterVariable(&cl_netimmediatebuttons); + + Cvar_RegisterVariable(&cl_nodelta); +} + diff --git a/misc/source/darkplaces-src/cl_main.c b/misc/source/darkplaces-src/cl_main.c new file mode 100644 index 00000000..420203b4 --- /dev/null +++ b/misc/source/darkplaces-src/cl_main.c @@ -0,0 +1,2508 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_main.c -- client main loop + +#include "quakedef.h" +#include "cl_collision.h" +#include "cl_gecko.h" +#include "cl_video.h" +#include "image.h" +#include "csprogs.h" +#include "r_shadow.h" +#include "libcurl.h" +#include "snd_main.h" + +// we need to declare some mouse variables here, because the menu system +// references them even when on a unix system. + +cvar_t csqc_progname = {0, "csqc_progname","csprogs.dat","name of csprogs.dat file to load"}; +cvar_t csqc_progcrc = {CVAR_READONLY, "csqc_progcrc","-1","CRC of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1"}; +cvar_t csqc_progsize = {CVAR_READONLY, "csqc_progsize","-1","file size of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1"}; + +cvar_t cl_shownet = {0, "cl_shownet","0","1 = print packet size, 2 = print packet message list"}; +cvar_t cl_nolerp = {0, "cl_nolerp", "0","network update smoothing"}; +cvar_t cl_lerpexcess = {0, "cl_lerpexcess", "0","maximum allowed lerp excess (hides, not fixes, some packet loss)"}; +cvar_t cl_lerpanim_maxdelta_server = {0, "cl_lerpanim_maxdelta_server", "0.1","maximum frame delta for smoothing between server-controlled animation frames (when 0, one network frame)"}; +cvar_t cl_lerpanim_maxdelta_framegroups = {0, "cl_lerpanim_maxdelta_framegroups", "0.1","maximum frame delta for smoothing between framegroups (when 0, one network frame)"}; + +cvar_t cl_itembobheight = {0, "cl_itembobheight", "0","how much items bob up and down (try 8)"}; +cvar_t cl_itembobspeed = {0, "cl_itembobspeed", "0.5","how frequently items bob up and down"}; + +cvar_t lookspring = {CVAR_SAVE, "lookspring","0","returns pitch to level with the floor when no longer holding a pitch key"}; +cvar_t lookstrafe = {CVAR_SAVE, "lookstrafe","0","move instead of turning"}; +cvar_t sensitivity = {CVAR_SAVE, "sensitivity","3","mouse speed multiplier"}; + +cvar_t m_pitch = {CVAR_SAVE, "m_pitch","0.022","mouse pitch speed multiplier"}; +cvar_t m_yaw = {CVAR_SAVE, "m_yaw","0.022","mouse yaw speed multiplier"}; +cvar_t m_forward = {CVAR_SAVE, "m_forward","1","mouse forward speed multiplier"}; +cvar_t m_side = {CVAR_SAVE, "m_side","0.8","mouse side speed multiplier"}; + +cvar_t freelook = {CVAR_SAVE, "freelook", "1","mouse controls pitch instead of forward/back"}; + +cvar_t cl_autodemo = {CVAR_SAVE, "cl_autodemo", "0", "records every game played, using the date/time and map name to name the demo file" }; +cvar_t cl_autodemo_nameformat = {CVAR_SAVE, "cl_autodemo_nameformat", "autodemos/%Y-%m-%d_%H-%M", "The format of the cl_autodemo filename, followed by the map name (the date is encoded using strftime escapes)" }; +cvar_t cl_autodemo_delete = {0, "cl_autodemo_delete", "0", "Delete demos after recording. This is a bitmask, bit 1 gives the default, bit 0 the value for the current demo. Thus, the values are: 0 = disabled; 1 = delete current demo only; 2 = delete all demos except the current demo; 3 = delete all demos from now on" }; + +cvar_t r_draweffects = {0, "r_draweffects", "1","renders temporary sprite effects"}; + +cvar_t cl_explosions_alpha_start = {CVAR_SAVE, "cl_explosions_alpha_start", "1.5","starting alpha of an explosion shell"}; +cvar_t cl_explosions_alpha_end = {CVAR_SAVE, "cl_explosions_alpha_end", "0","end alpha of an explosion shell (just before it disappears)"}; +cvar_t cl_explosions_size_start = {CVAR_SAVE, "cl_explosions_size_start", "16","starting size of an explosion shell"}; +cvar_t cl_explosions_size_end = {CVAR_SAVE, "cl_explosions_size_end", "128","ending alpha of an explosion shell (just before it disappears)"}; +cvar_t cl_explosions_lifetime = {CVAR_SAVE, "cl_explosions_lifetime", "0.5","how long an explosion shell lasts"}; + +cvar_t cl_stainmaps = {CVAR_SAVE, "cl_stainmaps", "0","stains lightmaps, much faster than decals but blurred"}; +cvar_t cl_stainmaps_clearonload = {CVAR_SAVE, "cl_stainmaps_clearonload", "1","clear stainmaps on map restart"}; + +cvar_t cl_beams_polygons = {CVAR_SAVE, "cl_beams_polygons", "1","use beam polygons instead of models"}; +cvar_t cl_beams_quakepositionhack = {CVAR_SAVE, "cl_beams_quakepositionhack", "1", "makes your lightning gun appear to fire from your waist (as in Quake and QuakeWorld)"}; +cvar_t cl_beams_instantaimhack = {CVAR_SAVE, "cl_beams_instantaimhack", "0", "makes your lightning gun aiming update instantly"}; +cvar_t cl_beams_lightatend = {CVAR_SAVE, "cl_beams_lightatend", "0", "make a light at the end of the beam"}; + +cvar_t cl_deathfade = {CVAR_SAVE, "cl_deathfade", "0", "fade screen to dark red when dead, value represents how fast the fade is (higher is faster)"}; + +cvar_t cl_noplayershadow = {CVAR_SAVE, "cl_noplayershadow", "0","hide player shadow"}; + +cvar_t cl_dlights_decayradius = {CVAR_SAVE, "cl_dlights_decayradius", "1", "reduces size of light flashes over time"}; +cvar_t cl_dlights_decaybrightness = {CVAR_SAVE, "cl_dlights_decaybrightness", "1", "reduces brightness of light flashes over time"}; + +cvar_t qport = {0, "qport", "0", "identification key for playing on qw servers (allows you to maintain a connection to a quakeworld server even if your port changes)"}; + +cvar_t cl_prydoncursor = {0, "cl_prydoncursor", "0", "enables a mouse pointer which is able to click on entities in the world, useful for point and click mods, see PRYDON_CLIENTCURSOR extension in dpextensions.qc"}; +cvar_t cl_prydoncursor_notrace = {0, "cl_prydoncursor_notrace", "0", "disables traceline used in prydon cursor reporting to the game, saving some cpu time"}; + +cvar_t cl_deathnoviewmodel = {0, "cl_deathnoviewmodel", "1", "hides gun model when dead"}; + +cvar_t cl_locs_enable = {CVAR_SAVE, "locs_enable", "1", "enables replacement of certain % codes in chat messages: %l (location), %d (last death location), %h (health), %a (armor), %x (rockets), %c (cells), %r (rocket launcher status), %p (powerup status), %w (weapon status), %t (current time in level)"}; +cvar_t cl_locs_show = {0, "locs_show", "0", "shows defined locations for editing purposes"}; + +extern cvar_t r_equalize_entities_fullbright; + +client_static_t cls; +client_state_t cl; + +/* +===================== +CL_ClearState + +===================== +*/ +void CL_VM_ShutDown (void); +void CL_ClearState(void) +{ + int i; + entity_t *ent; + + CL_VM_ShutDown(); + +// wipe the entire cl structure + Mem_EmptyPool(cls.levelmempool); + memset (&cl, 0, sizeof(cl)); + + S_StopAllSounds(); + + // reset the view zoom interpolation + cl.mviewzoom[0] = cl.mviewzoom[1] = 1; + cl.sensitivityscale = 1.0f; + + // enable rendering of the world and such + cl.csqc_vidvars.drawworld = r_drawworld.integer != 0; + cl.csqc_vidvars.drawenginesbar = true; + cl.csqc_vidvars.drawcrosshair = true; + + // set up the float version of the stats array for easier access to float stats + cl.statsf = (float *)cl.stats; + + cl.num_entities = 0; + cl.num_static_entities = 0; + cl.num_brushmodel_entities = 0; + + // tweak these if the game runs out + cl.max_csqcrenderentities = 0; + cl.max_entities = MAX_ENITIES_INITIAL; + cl.max_static_entities = MAX_STATICENTITIES; + cl.max_effects = MAX_EFFECTS; + cl.max_beams = MAX_BEAMS; + cl.max_dlights = MAX_DLIGHTS; + cl.max_lightstyle = MAX_LIGHTSTYLES; + cl.max_brushmodel_entities = MAX_EDICTS; + cl.max_particles = MAX_PARTICLES_INITIAL; // grows dynamically + cl.max_decals = MAX_DECALS_INITIAL; // grows dynamically + cl.max_showlmps = 0; + + cl.num_dlights = 0; + cl.num_effects = 0; + cl.num_beams = 0; + + cl.csqcrenderentities = NULL; + cl.entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_entities * sizeof(entity_t)); + cl.entities_active = (unsigned char *)Mem_Alloc(cls.levelmempool, cl.max_brushmodel_entities * sizeof(unsigned char)); + cl.static_entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_static_entities * sizeof(entity_t)); + cl.effects = (cl_effect_t *)Mem_Alloc(cls.levelmempool, cl.max_effects * sizeof(cl_effect_t)); + cl.beams = (beam_t *)Mem_Alloc(cls.levelmempool, cl.max_beams * sizeof(beam_t)); + cl.dlights = (dlight_t *)Mem_Alloc(cls.levelmempool, cl.max_dlights * sizeof(dlight_t)); + cl.lightstyle = (lightstyle_t *)Mem_Alloc(cls.levelmempool, cl.max_lightstyle * sizeof(lightstyle_t)); + cl.brushmodel_entities = (int *)Mem_Alloc(cls.levelmempool, cl.max_brushmodel_entities * sizeof(int)); + cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t)); + cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t)); + cl.showlmps = NULL; + + // LordHavoc: have to set up the baseline info for alpha and other stuff + for (i = 0;i < cl.max_entities;i++) + { + cl.entities[i].state_baseline = defaultstate; + cl.entities[i].state_previous = defaultstate; + cl.entities[i].state_current = defaultstate; + } + + if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + VectorSet(cl.playerstandmins, -16, -16, -24); + VectorSet(cl.playerstandmaxs, 16, 16, 45); + VectorSet(cl.playercrouchmins, -16, -16, -24); + VectorSet(cl.playercrouchmaxs, 16, 16, 25); + } + else + { + VectorSet(cl.playerstandmins, -16, -16, -24); + VectorSet(cl.playerstandmaxs, 16, 16, 24); + VectorSet(cl.playercrouchmins, -16, -16, -24); + VectorSet(cl.playercrouchmaxs, 16, 16, 24); + } + + // disable until we get textures for it + R_ResetSkyBox(); + + ent = &cl.entities[0]; + // entire entity array was cleared, so just fill in a few fields + ent->state_current.active = true; + ent->render.model = cl.worldmodel = NULL; // no world model yet + ent->render.alpha = 1; + ent->render.flags = RENDER_SHADOW | RENDER_LIGHT; + Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, 0, 0, 0, 0, 0, 0, 1); + ent->render.allowdecals = true; + CL_UpdateRenderEntity(&ent->render); + + // noclip is turned off at start + noclip_anglehack = false; + + // mark all frames invalid for delta + memset(cl.qw_deltasequence, -1, sizeof(cl.qw_deltasequence)); + + // set bestweapon data back to Quake data + IN_BestWeapon_ResetData(); + + CL_Screen_NewMap(); +} + +void CL_SetInfo(const char *key, const char *value, qboolean send, qboolean allowstarkey, qboolean allowmodel, qboolean quiet) +{ + int i; + qboolean fail = false; + if (!allowstarkey && key[0] == '*') + fail = true; + if (!allowmodel && (!strcasecmp(key, "pmodel") || !strcasecmp(key, "emodel"))) + fail = true; + for (i = 0;key[i];i++) + if (ISWHITESPACE(key[i]) || key[i] == '\"') + fail = true; + for (i = 0;value[i];i++) + if (value[i] == '\r' || value[i] == '\n' || value[i] == '\"') + fail = true; + if (fail) + { + if (!quiet) + Con_Printf("Can't setinfo \"%s\" \"%s\"\n", key, value); + return; + } + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), key, value); + if (cls.state == ca_connected && cls.netcon) + { + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("setinfo \"%s\" \"%s\"", key, value)); + } + else if (!strcasecmp(key, "name")) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("name \"%s\"", value)); + } + else if (!strcasecmp(key, "playermodel")) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("playermodel \"%s\"", value)); + } + else if (!strcasecmp(key, "playerskin")) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("playerskin \"%s\"", value)); + } + else if (!strcasecmp(key, "topcolor")) + { + // don't send anything, the combined color code will be updated manually + } + else if (!strcasecmp(key, "bottomcolor")) + { + // don't send anything, the combined color code will be updated manually + } + else if (!strcasecmp(key, "rate")) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("rate \"%s\"", value)); + } + } +} + +void CL_ExpandEntities(int num) +{ + int i, oldmaxentities; + entity_t *oldentities; + if (num >= cl.max_entities) + { + if (!cl.entities) + Sys_Error("CL_ExpandEntities: cl.entities not initialized"); + if (num >= MAX_EDICTS) + Host_Error("CL_ExpandEntities: num %i >= %i", num, MAX_EDICTS); + oldmaxentities = cl.max_entities; + oldentities = cl.entities; + cl.max_entities = (num & ~255) + 256; + cl.entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_entities * sizeof(entity_t)); + memcpy(cl.entities, oldentities, oldmaxentities * sizeof(entity_t)); + Mem_Free(oldentities); + for (i = oldmaxentities;i < cl.max_entities;i++) + { + cl.entities[i].state_baseline = defaultstate; + cl.entities[i].state_previous = defaultstate; + cl.entities[i].state_current = defaultstate; + } + } +} + +void CL_ExpandCSQCRenderEntities(int num) +{ + int i; + int oldmaxcsqcrenderentities; + entity_render_t *oldcsqcrenderentities; + if (num >= cl.max_csqcrenderentities) + { + if (num >= MAX_EDICTS) + Host_Error("CL_ExpandEntities: num %i >= %i", num, MAX_EDICTS); + oldmaxcsqcrenderentities = cl.max_csqcrenderentities; + oldcsqcrenderentities = cl.csqcrenderentities; + cl.max_csqcrenderentities = (num & ~255) + 256; + cl.csqcrenderentities = (entity_render_t *)Mem_Alloc(cls.levelmempool, cl.max_csqcrenderentities * sizeof(entity_render_t)); + if (oldcsqcrenderentities) + { + memcpy(cl.csqcrenderentities, oldcsqcrenderentities, oldmaxcsqcrenderentities * sizeof(entity_render_t)); + for (i = 0;i < r_refdef.scene.numentities;i++) + if(r_refdef.scene.entities[i] >= oldcsqcrenderentities && r_refdef.scene.entities[i] < (oldcsqcrenderentities + oldmaxcsqcrenderentities)) + r_refdef.scene.entities[i] = cl.csqcrenderentities + (r_refdef.scene.entities[i] - oldcsqcrenderentities); + Mem_Free(oldcsqcrenderentities); + } + } +} + +/* +===================== +CL_Disconnect + +Sends a disconnect message to the server +This is also called on Host_Error, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect(void) +{ + if (cls.state == ca_dedicated) + return; + + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(false); + + Curl_Clear_forthismap(); + + Con_DPrintf("CL_Disconnect\n"); + + Cvar_SetValueQuick(&csqc_progcrc, -1); + Cvar_SetValueQuick(&csqc_progsize, -1); + CL_VM_ShutDown(); +// stop sounds (especially looping!) + S_StopAllSounds (); + + cl.parsingtextexpectingpingforscores = 0; // just in case no reply has come yet + + // clear contents blends + cl.cshifts[0].percent = 0; + cl.cshifts[1].percent = 0; + cl.cshifts[2].percent = 0; + cl.cshifts[3].percent = 0; + + cl.worldmodel = NULL; + + CL_Parse_ErrorCleanUp(); + + if (cls.demoplayback) + CL_StopPlayback(); + else if (cls.netcon) + { + sizebuf_t buf; + unsigned char bufdata[8]; + if (cls.demorecording) + CL_Stop_f(); + + // send disconnect message 3 times to improve chances of server + // receiving it (but it still fails sometimes) + memset(&buf, 0, sizeof(buf)); + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + Con_DPrint("Sending drop command\n"); + MSG_WriteByte(&buf, qw_clc_stringcmd); + MSG_WriteString(&buf, "drop"); + } + else + { + Con_DPrint("Sending clc_disconnect\n"); + MSG_WriteByte(&buf, clc_disconnect); + } + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, false); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, false); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, false); + NetConn_Close(cls.netcon); + cls.netcon = NULL; + } + cls.state = ca_disconnected; + + cls.demoplayback = cls.timedemo = false; + cls.signon = 0; +} + +void CL_Disconnect_f(void) +{ + CL_Disconnect (); + if (sv.active) + Host_ShutdownServer (); +} + + + + +/* +===================== +CL_EstablishConnection + +Host should be either "local" or a net address +===================== +*/ +void CL_EstablishConnection(const char *host, int firstarg) +{ + if (cls.state == ca_dedicated) + return; + + // don't connect to a server if we're benchmarking a demo + if (COM_CheckParm("-benchmark")) + return; + + // clear menu's connect error message + M_Update_Return_Reason(""); + cls.demonum = -1; + + // stop demo loop in case this fails + if (cls.demoplayback) + CL_StopPlayback(); + + // if downloads are running, cancel their finishing action + Curl_Clear_forthismap(); + + // make sure the client ports are open before attempting to connect + NetConn_UpdateSockets(); + + // run a network frame + //NetConn_ClientFrame();SV_VM_Begin();NetConn_ServerFrame();SV_VM_End(); + + if (LHNETADDRESS_FromString(&cls.connect_address, host, 26000) && (cls.connect_mysocket = NetConn_ChooseClientSocketForAddress(&cls.connect_address))) + { + cls.connect_trying = true; + cls.connect_remainingtries = 3; + cls.connect_nextsendtime = 0; + + // only NOW, set connect_userinfo + if(firstarg >= 0) + { + int i; + *cls.connect_userinfo = 0; + for(i = firstarg; i+2 <= Cmd_Argc(); i += 2) + InfoString_SetValue(cls.connect_userinfo, sizeof(cls.connect_userinfo), Cmd_Argv(i), Cmd_Argv(i+1)); + } + else if(firstarg < -1) + { + // -1: keep as is (reconnect) + // -2: clear + *cls.connect_userinfo = 0; + } + + M_Update_Return_Reason("Trying to connect..."); + + // run several network frames to jump into the game quickly + //if (sv.active) + //{ + // NetConn_ClientFrame();SV_VM_Begin();NetConn_ServerFrame();SV_VM_End(); + // NetConn_ClientFrame();SV_VM_Begin();NetConn_ServerFrame();SV_VM_End(); + // NetConn_ClientFrame();SV_VM_Begin();NetConn_ServerFrame();SV_VM_End(); + // NetConn_ClientFrame();SV_VM_Begin();NetConn_ServerFrame();SV_VM_End(); + //} + } + else + { + Con_Print("Unable to find a suitable network socket to connect to server.\n"); + M_Update_Return_Reason("No network"); + } +} + +/* +============== +CL_PrintEntities_f +============== +*/ +static void CL_PrintEntities_f(void) +{ + entity_t *ent; + int i; + + for (i = 0, ent = cl.entities;i < cl.num_entities;i++, ent++) + { + const char* modelname; + + if (!ent->state_current.active) + continue; + + if (ent->render.model) + modelname = ent->render.model->name; + else + modelname = "--no model--"; + Con_Printf("%3i: %-25s:%4i (%5i %5i %5i) [%3i %3i %3i] %4.2f %5.3f\n", i, modelname, ent->render.framegroupblend[0].frame, (int) ent->state_current.origin[0], (int) ent->state_current.origin[1], (int) ent->state_current.origin[2], (int) ent->state_current.angles[0] % 360, (int) ent->state_current.angles[1] % 360, (int) ent->state_current.angles[2] % 360, ent->render.scale, ent->render.alpha); + } +} + +/* +=============== +CL_ModelIndexList_f + +List information on all models in the client modelindex +=============== +*/ +static void CL_ModelIndexList_f(void) +{ + int i; + dp_model_t *model; + + // Print Header + Con_Printf("%3s: %-30s %-8s %-8s\n", "ID", "Name", "Type", "Triangles"); + + for (i = -MAX_MODELS;i < MAX_MODELS;i++) + { + model = CL_GetModelByIndex(i); + if(model->loaded || i == 1) + Con_Printf("%3i: %-30s %-8s %-10i\n", i, model->name, model->modeldatatypestring, model->surfmesh.num_triangles); + else + Con_Printf("%3i: %-30s %-30s\n", i, model->name, "--no local model found--"); + i++; + } +} + +/* +=============== +CL_SoundIndexList_f + +List all sounds in the client soundindex +=============== +*/ +static void CL_SoundIndexList_f(void) +{ + int i = 1; + + while(cl.sound_precache[i] && i != MAX_SOUNDS) + { // Valid Sound + Con_Printf("%i : %s\n", i, cl.sound_precache[i]->name); + i++; + } +} + +/* +=============== +CL_UpdateRenderEntity + +Updates inversematrix, animation interpolation factors, scale, and mins/maxs +=============== +*/ +void CL_UpdateRenderEntity(entity_render_t *ent) +{ + vec3_t org; + vec_t scale; + dp_model_t *model = ent->model; + // update the inverse matrix for the renderer + Matrix4x4_Invert_Simple(&ent->inversematrix, &ent->matrix); + // update the animation blend state + VM_FrameBlendFromFrameGroupBlend(ent->frameblend, ent->framegroupblend, ent->model); + // we need the matrix origin to center the box + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + // update entity->render.scale because the renderer needs it + ent->scale = scale = Matrix4x4_ScaleFromMatrix(&ent->matrix); + if (model) + { + // NOTE: this directly extracts vector components from the matrix, which relies on the matrix orientation! +#ifdef MATRIX4x4_OPENGLORIENTATION + if (ent->matrix.m[0][2] != 0 || ent->matrix.m[1][2] != 0) +#else + if (ent->matrix.m[2][0] != 0 || ent->matrix.m[2][1] != 0) +#endif + { + // pitch or roll + VectorMA(org, scale, model->rotatedmins, ent->mins); + VectorMA(org, scale, model->rotatedmaxs, ent->maxs); + } +#ifdef MATRIX4x4_OPENGLORIENTATION + else if (ent->matrix.m[1][0] != 0 || ent->matrix.m[0][1] != 0) +#else + else if (ent->matrix.m[0][1] != 0 || ent->matrix.m[1][0] != 0) +#endif + { + // yaw + VectorMA(org, scale, model->yawmins, ent->mins); + VectorMA(org, scale, model->yawmaxs, ent->maxs); + } + else + { + VectorMA(org, scale, model->normalmins, ent->mins); + VectorMA(org, scale, model->normalmaxs, ent->maxs); + } + } + else + { + ent->mins[0] = org[0] - 16; + ent->mins[1] = org[1] - 16; + ent->mins[2] = org[2] - 16; + ent->maxs[0] = org[0] + 16; + ent->maxs[1] = org[1] + 16; + ent->maxs[2] = org[2] + 16; + } +} + +/* +=============== +CL_LerpPoint + +Determines the fraction between the last two messages that the objects +should be put at. +=============== +*/ +static float CL_LerpPoint(void) +{ + float f; + + if (cl_nettimesyncboundmode.integer == 1) + cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); + + // LordHavoc: lerp in listen games as the server is being capped below the client (usually) + if (cl.mtime[0] <= cl.mtime[1]) + { + cl.time = cl.mtime[0]; + return 1; + } + + f = (cl.time - cl.mtime[1]) / (cl.mtime[0] - cl.mtime[1]); + return bound(0, f, 1 + cl_lerpexcess.value); +} + +void CL_ClearTempEntities (void) +{ + r_refdef.scene.numtempentities = 0; + // grow tempentities buffer on request + if (r_refdef.scene.expandtempentities) + { + Con_Printf("CL_NewTempEntity: grow maxtempentities from %i to %i\n", r_refdef.scene.maxtempentities, r_refdef.scene.maxtempentities * 2); + r_refdef.scene.maxtempentities *= 2; + r_refdef.scene.tempentities = (entity_render_t *)Mem_Realloc(cls.permanentmempool, r_refdef.scene.tempentities, sizeof(entity_render_t) * r_refdef.scene.maxtempentities); + r_refdef.scene.expandtempentities = false; + } +} + +entity_render_t *CL_NewTempEntity(double shadertime) +{ + entity_render_t *render; + + if (r_refdef.scene.numentities >= r_refdef.scene.maxentities) + return NULL; + if (r_refdef.scene.numtempentities >= r_refdef.scene.maxtempentities) + { + r_refdef.scene.expandtempentities = true; // will be reallocated next frame since current frame may have pointers set already + return NULL; + } + render = &r_refdef.scene.tempentities[r_refdef.scene.numtempentities++]; + memset (render, 0, sizeof(*render)); + r_refdef.scene.entities[r_refdef.scene.numentities++] = render; + + render->shadertime = shadertime; + render->alpha = 1; + VectorSet(render->colormod, 1, 1, 1); + VectorSet(render->glowmod, 1, 1, 1); + return render; +} + +void CL_Effect(vec3_t org, int modelindex, int startframe, int framecount, float framerate) +{ + int i; + cl_effect_t *e; + if (!modelindex) // sanity check + return; + if (framerate < 1) + { + Con_Printf("CL_Effect: framerate %f is < 1\n", framerate); + return; + } + if (framecount < 1) + { + Con_Printf("CL_Effect: framecount %i is < 1\n", framecount); + return; + } + for (i = 0, e = cl.effects;i < cl.max_effects;i++, e++) + { + if (e->active) + continue; + e->active = true; + VectorCopy(org, e->origin); + e->modelindex = modelindex; + e->starttime = cl.time; + e->startframe = startframe; + e->endframe = startframe + framecount; + e->framerate = framerate; + + e->frame = 0; + e->frame1time = cl.time; + e->frame2time = cl.time; + cl.num_effects = max(cl.num_effects, i + 1); + break; + } +} + +void CL_AllocLightFlash(entity_render_t *ent, matrix4x4_t *matrix, float radius, float red, float green, float blue, float decay, float lifetime, int cubemapnum, int style, int shadowenable, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) +{ + int i; + dlight_t *dl; + +// then look for anything else + dl = cl.dlights; + for (i = 0;i < cl.max_dlights;i++, dl++) + if (!dl->radius) + break; + + // unable to find one + if (i == cl.max_dlights) + return; + + //Con_Printf("dlight %i : %f %f %f : %f %f %f\n", i, org[0], org[1], org[2], red * radius, green * radius, blue * radius); + memset (dl, 0, sizeof(*dl)); + cl.num_dlights = max(cl.num_dlights, i + 1); + Matrix4x4_Normalize(&dl->matrix, matrix); + dl->ent = ent; + Matrix4x4_OriginFromMatrix(&dl->matrix, dl->origin); + CL_FindNonSolidLocation(dl->origin, dl->origin, 6); + Matrix4x4_SetOrigin(&dl->matrix, dl->origin[0], dl->origin[1], dl->origin[2]); + dl->radius = radius; + dl->color[0] = red; + dl->color[1] = green; + dl->color[2] = blue; + dl->initialradius = radius; + dl->initialcolor[0] = red; + dl->initialcolor[1] = green; + dl->initialcolor[2] = blue; + dl->decay = decay / radius; // changed decay to be a percentage decrease + dl->intensity = 1; // this is what gets decayed + if (lifetime) + dl->die = cl.time + lifetime; + else + dl->die = 0; + if (cubemapnum > 0) + dpsnprintf(dl->cubemapname, sizeof(dl->cubemapname), "cubemaps/%i", cubemapnum); + else + dl->cubemapname[0] = 0; + dl->style = style; + dl->shadow = shadowenable; + dl->corona = corona; + dl->flags = flags; + dl->coronasizescale = coronasizescale; + dl->ambientscale = ambientscale; + dl->diffusescale = diffusescale; + dl->specularscale = specularscale; +} + +void CL_DecayLightFlashes(void) +{ + int i, oldmax; + dlight_t *dl; + float time; + + time = bound(0, cl.time - cl.oldtime, 0.1); + oldmax = cl.num_dlights; + cl.num_dlights = 0; + for (i = 0, dl = cl.dlights;i < oldmax;i++, dl++) + { + if (dl->radius) + { + dl->intensity -= time * dl->decay; + if (cl.time < dl->die && dl->intensity > 0) + { + if (cl_dlights_decayradius.integer) + dl->radius = dl->initialradius * dl->intensity; + else + dl->radius = dl->initialradius; + if (cl_dlights_decaybrightness.integer) + VectorScale(dl->initialcolor, dl->intensity, dl->color); + else + VectorCopy(dl->initialcolor, dl->color); + cl.num_dlights = i + 1; + } + else + dl->radius = 0; + } + } +} + +// called before entity relinking +void CL_RelinkLightFlashes(void) +{ + int i, j, k, l; + dlight_t *dl; + float frac, f; + matrix4x4_t tempmatrix; + + if (r_dynamic.integer) + { + for (i = 0, dl = cl.dlights;i < cl.num_dlights && r_refdef.scene.numlights < MAX_DLIGHTS;i++, dl++) + { + if (dl->radius) + { + tempmatrix = dl->matrix; + Matrix4x4_Scale(&tempmatrix, dl->radius, 1); + // we need the corona fading to be persistent + R_RTLight_Update(&dl->rtlight, false, &tempmatrix, dl->color, dl->style, dl->cubemapname, dl->shadow, dl->corona, dl->coronasizescale, dl->ambientscale, dl->diffusescale, dl->specularscale, dl->flags); + r_refdef.scene.lights[r_refdef.scene.numlights++] = &dl->rtlight; + } + } + } + + if (!cl.lightstyle) + { + for (j = 0;j < cl.max_lightstyle;j++) + { + r_refdef.scene.rtlightstylevalue[j] = 1; + r_refdef.scene.lightstylevalue[j] = 256; + } + return; + } + +// light animations +// 'm' is normal light, 'a' is no light, 'z' is double bright + f = cl.time * 10; + i = (int)floor(f); + frac = f - i; + for (j = 0;j < cl.max_lightstyle;j++) + { + if (!cl.lightstyle[j].length) + { + r_refdef.scene.rtlightstylevalue[j] = 1; + r_refdef.scene.lightstylevalue[j] = 256; + continue; + } + // static lightstyle "=value" + if (cl.lightstyle[j].map[0] == '=') + { + r_refdef.scene.rtlightstylevalue[j] = atof(cl.lightstyle[j].map + 1); + if ( r_lerplightstyles.integer || ((int)f - f) < 0.01) + r_refdef.scene.lightstylevalue[j] = r_refdef.scene.rtlightstylevalue[j]; + continue; + } + k = i % cl.lightstyle[j].length; + l = (i-1) % cl.lightstyle[j].length; + k = cl.lightstyle[j].map[k] - 'a'; + l = cl.lightstyle[j].map[l] - 'a'; + // rtlightstylevalue is always interpolated because it has no bad + // consequences for performance + // lightstylevalue is subject to a cvar for performance reasons; + // skipping lightmap updates on most rendered frames substantially + // improves framerates (but makes light fades look bad) + r_refdef.scene.rtlightstylevalue[j] = ((k*frac)+(l*(1-frac)))*(22/256.0f); + r_refdef.scene.lightstylevalue[j] = r_lerplightstyles.integer ? (unsigned short)(((k*frac)+(l*(1-frac)))*22) : k*22; + } +} + +void CL_AddQWCTFFlagModel(entity_t *player, int skin) +{ + int frame = player->render.framegroupblend[0].frame; + float f; + entity_render_t *flagrender; + matrix4x4_t flagmatrix; + + // this code taken from QuakeWorld + f = 14; + if (frame >= 29 && frame <= 40) + { + if (frame >= 29 && frame <= 34) + { //axpain + if (frame == 29) f = f + 2; + else if (frame == 30) f = f + 8; + else if (frame == 31) f = f + 12; + else if (frame == 32) f = f + 11; + else if (frame == 33) f = f + 10; + else if (frame == 34) f = f + 4; + } + else if (frame >= 35 && frame <= 40) + { // pain + if (frame == 35) f = f + 2; + else if (frame == 36) f = f + 10; + else if (frame == 37) f = f + 10; + else if (frame == 38) f = f + 8; + else if (frame == 39) f = f + 4; + else if (frame == 40) f = f + 2; + } + } + else if (frame >= 103 && frame <= 118) + { + if (frame >= 103 && frame <= 104) f = f + 6; //nailattack + else if (frame >= 105 && frame <= 106) f = f + 6; //light + else if (frame >= 107 && frame <= 112) f = f + 7; //rocketattack + else if (frame >= 112 && frame <= 118) f = f + 7; //shotattack + } + // end of code taken from QuakeWorld + + flagrender = CL_NewTempEntity(player->render.shadertime); + if (!flagrender) + return; + + flagrender->model = CL_GetModelByIndex(cl.qw_modelindex_flag); + flagrender->skinnum = skin; + flagrender->alpha = 1; + VectorSet(flagrender->colormod, 1, 1, 1); + VectorSet(flagrender->glowmod, 1, 1, 1); + // attach the flag to the player matrix + Matrix4x4_CreateFromQuakeEntity(&flagmatrix, -f, -22, 0, 0, 0, -45, 1); + Matrix4x4_Concat(&flagrender->matrix, &player->render.matrix, &flagmatrix); + CL_UpdateRenderEntity(flagrender); +} + +matrix4x4_t viewmodelmatrix_withbob; +matrix4x4_t viewmodelmatrix_nobob; + +static const vec3_t muzzleflashorigin = {18, 0, 0}; + +extern void V_DriftPitch(void); +extern void V_FadeViewFlashs(void); +extern void V_CalcViewBlend(void); +extern void V_CalcRefdef(void); + +void CL_SetEntityColormapColors(entity_render_t *ent, int colormap) +{ + const unsigned char *cbcolor; + if (colormap >= 0) + { + cbcolor = palette_rgb_pantscolormap[colormap & 0xF]; + VectorScale(cbcolor, (1.0f / 255.0f), ent->colormap_pantscolor); + cbcolor = palette_rgb_shirtcolormap[(colormap & 0xF0) >> 4]; + VectorScale(cbcolor, (1.0f / 255.0f), ent->colormap_shirtcolor); + } + else + { + VectorClear(ent->colormap_pantscolor); + VectorClear(ent->colormap_shirtcolor); + } +} + +// note this is a recursive function, recursionlimit should be 32 or so on the initial call +void CL_UpdateNetworkEntity(entity_t *e, int recursionlimit, qboolean interpolate) +{ + const matrix4x4_t *matrix; + matrix4x4_t blendmatrix, tempmatrix, matrix2; + int frame; + float origin[3], angles[3], lerp; + entity_t *t; + entity_render_t *r; + //entity_persistent_t *p = &e->persistent; + //entity_render_t *r = &e->render; + // skip inactive entities and world + if (!e->state_current.active || e == cl.entities) + return; + if (recursionlimit < 1) + return; + e->render.alpha = e->state_current.alpha * (1.0f / 255.0f); // FIXME: interpolate? + e->render.scale = e->state_current.scale * (1.0f / 16.0f); // FIXME: interpolate? + e->render.flags = e->state_current.flags; + e->render.effects = e->state_current.effects; + VectorScale(e->state_current.colormod, (1.0f / 32.0f), e->render.colormod); + VectorScale(e->state_current.glowmod, (1.0f / 32.0f), e->render.glowmod); + if(e >= cl.entities && e < cl.entities + cl.num_entities) + e->render.entitynumber = e - cl.entities; + else + e->render.entitynumber = 0; + if (e->state_current.flags & RENDER_COLORMAPPED) + CL_SetEntityColormapColors(&e->render, e->state_current.colormap); + else if (e->state_current.colormap > 0 && e->state_current.colormap <= cl.maxclients && cl.scores != NULL) + CL_SetEntityColormapColors(&e->render, cl.scores[e->state_current.colormap-1].colors); + else + CL_SetEntityColormapColors(&e->render, -1); + e->render.skinnum = e->state_current.skin; + if (e->state_current.tagentity) + { + // attached entity (gun held in player model's hand, etc) + // if the tag entity is currently impossible, skip it + if (e->state_current.tagentity >= cl.num_entities) + return; + t = cl.entities + e->state_current.tagentity; + // if the tag entity is inactive, skip it + if (t->state_current.active) + { + // update the parent first + CL_UpdateNetworkEntity(t, recursionlimit - 1, interpolate); + r = &t->render; + } + else + { + // it may still be a CSQC entity... trying to use its + // info from last render frame (better than nothing) + if(!cl.csqc_server2csqcentitynumber[e->state_current.tagentity]) + return; + r = cl.csqcrenderentities + cl.csqc_server2csqcentitynumber[e->state_current.tagentity]; + if(!r->entitynumber) + return; // neither CSQC nor legacy entity... can't attach + } + // make relative to the entity + matrix = &r->matrix; + // some properties of the tag entity carry over + e->render.flags |= r->flags & (RENDER_EXTERIORMODEL | RENDER_VIEWMODEL); + // if a valid tagindex is used, make it relative to that tag instead + if (e->state_current.tagentity && e->state_current.tagindex >= 1 && r->model) + { + if(!Mod_Alias_GetTagMatrix(r->model, r->frameblend, r->skeleton, e->state_current.tagindex - 1, &blendmatrix)) // i.e. no error + { + // concat the tag matrices onto the entity matrix + Matrix4x4_Concat(&tempmatrix, &r->matrix, &blendmatrix); + // use the constructed tag matrix + matrix = &tempmatrix; + } + } + } + else if (e->render.flags & RENDER_VIEWMODEL) + { + // view-relative entity (guns and such) + if (e->render.effects & EF_NOGUNBOB) + matrix = &viewmodelmatrix_nobob; // really attached to view + else + matrix = &viewmodelmatrix_withbob; // attached to gun bob matrix + } + else + { + // world-relative entity (the normal kind) + matrix = &identitymatrix; + } + + // movement lerp + // if it's the predicted player entity, update according to client movement + // but don't lerp if going through a teleporter as it causes a bad lerp + // also don't use the predicted location if fixangle was set on both of + // the most recent server messages, as that cause means you are spectating + // someone or watching a cutscene of some sort + if (cl_nolerp.integer || cls.timedemo) + interpolate = false; + if (e == cl.entities + cl.playerentity && cl.movement_predicted && (!cl.fixangle[1] || !cl.fixangle[0])) + { + VectorCopy(cl.movement_origin, origin); + VectorSet(angles, 0, cl.viewangles[1], 0); + } + else if (interpolate && e->persistent.lerpdeltatime > 0 && (lerp = (cl.time - e->persistent.lerpstarttime) / e->persistent.lerpdeltatime) < 1 + cl_lerpexcess.value) + { + // interpolate the origin and angles + lerp = max(0, lerp); + VectorLerp(e->persistent.oldorigin, lerp, e->persistent.neworigin, origin); +#if 0 + // this fails at the singularity of euler angles + VectorSubtract(e->persistent.newangles, e->persistent.oldangles, delta); + if (delta[0] < -180) delta[0] += 360;else if (delta[0] >= 180) delta[0] -= 360; + if (delta[1] < -180) delta[1] += 360;else if (delta[1] >= 180) delta[1] -= 360; + if (delta[2] < -180) delta[2] += 360;else if (delta[2] >= 180) delta[2] -= 360; + VectorMA(e->persistent.oldangles, lerp, delta, angles); +#else + { + vec3_t f0, u0, f1, u1; + AngleVectors(e->persistent.oldangles, f0, NULL, u0); + AngleVectors(e->persistent.newangles, f1, NULL, u1); + VectorMAM(1-lerp, f0, lerp, f1, f0); + VectorMAM(1-lerp, u0, lerp, u1, u0); + AnglesFromVectors(angles, f0, u0, false); + } +#endif + } + else + { + // no interpolation + VectorCopy(e->persistent.neworigin, origin); + VectorCopy(e->persistent.newangles, angles); + } + + // model setup and some modelflags + frame = e->state_current.frame; + e->render.model = CL_GetModelByIndex(e->state_current.modelindex); + if (e->render.model) + { + if (e->render.skinnum >= e->render.model->numskins) + e->render.skinnum = 0; + if (frame >= e->render.model->numframes) + frame = 0; + // models can set flags such as EF_ROCKET + // this 0xFF800000 mask is EF_NOMODELFLAGS plus all the higher EF_ flags such as EF_ROCKET + if (!(e->render.effects & 0xFF800000)) + e->render.effects |= e->render.model->effects; + // if model is alias or this is a tenebrae-like dlight, reverse pitch direction + if (e->render.model->type == mod_alias) + angles[0] = -angles[0]; + if ((e->render.effects & EF_SELECTABLE) && cl.cmd.cursor_entitynumber == e->state_current.number) + { + VectorScale(e->render.colormod, 2, e->render.colormod); + VectorScale(e->render.glowmod, 2, e->render.glowmod); + } + } + // if model is alias or this is a tenebrae-like dlight, reverse pitch direction + else if (e->state_current.lightpflags & PFLAGS_FULLDYNAMIC) + angles[0] = -angles[0]; + // NOTE: this must be synced to SV_GetPitchSign! + + if ((e->render.effects & EF_ROTATE) && !(e->render.flags & RENDER_VIEWMODEL)) + { + angles[1] = ANGLEMOD(100*cl.time); + if (cl_itembobheight.value) + origin[2] += (cos(cl.time * cl_itembobspeed.value * (2.0 * M_PI)) + 1.0) * 0.5 * cl_itembobheight.value; + } + + // animation lerp + e->render.skeleton = NULL; + if (e->render.flags & RENDER_COMPLEXANIMATION) + { + e->render.framegroupblend[0] = e->state_current.framegroupblend[0]; + e->render.framegroupblend[1] = e->state_current.framegroupblend[1]; + e->render.framegroupblend[2] = e->state_current.framegroupblend[2]; + e->render.framegroupblend[3] = e->state_current.framegroupblend[3]; + if (e->state_current.skeletonobject.model && e->state_current.skeletonobject.relativetransforms) + e->render.skeleton = &e->state_current.skeletonobject; + } + else if (e->render.framegroupblend[0].frame == frame) + { + // update frame lerp fraction + e->render.framegroupblend[0].lerp = 1; + e->render.framegroupblend[1].lerp = 0; + if (e->render.framegroupblend[0].start > e->render.framegroupblend[1].start) + { + // make sure frame lerp won't last longer than 100ms + // (this mainly helps with models that use framegroups and + // switch between them infrequently) + float maxdelta = cl_lerpanim_maxdelta_server.value; + if(e->render.model) + if(e->render.model->animscenes) + if(e->render.model->animscenes[e->render.framegroupblend[0].frame].framecount > 1 || e->render.model->animscenes[e->render.framegroupblend[1].frame].framecount > 1) + maxdelta = cl_lerpanim_maxdelta_framegroups.value; + maxdelta = max(maxdelta, cl.mtime[0] - cl.mtime[1]); + e->render.framegroupblend[0].lerp = (cl.time - e->render.framegroupblend[0].start) / min(e->render.framegroupblend[0].start - e->render.framegroupblend[1].start, maxdelta); + e->render.framegroupblend[0].lerp = bound(0, e->render.framegroupblend[0].lerp, 1); + e->render.framegroupblend[1].lerp = 1 - e->render.framegroupblend[0].lerp; + } + } + else + { + // begin a new frame lerp + e->render.framegroupblend[1] = e->render.framegroupblend[0]; + e->render.framegroupblend[1].lerp = 1; + e->render.framegroupblend[0].frame = frame; + e->render.framegroupblend[0].start = cl.time; + e->render.framegroupblend[0].lerp = 0; + } + + // set up the render matrix + if (matrix) + { + // attached entity, this requires a matrix multiply (concat) + // FIXME: e->render.scale should go away + Matrix4x4_CreateFromQuakeEntity(&matrix2, origin[0], origin[1], origin[2], angles[0], angles[1], angles[2], e->render.scale); + // concat the matrices to make the entity relative to its tag + Matrix4x4_Concat(&e->render.matrix, matrix, &matrix2); + // get the origin from the new matrix + Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); + } + else + { + // unattached entities are faster to process + Matrix4x4_CreateFromQuakeEntity(&e->render.matrix, origin[0], origin[1], origin[2], angles[0], angles[1], angles[2], e->render.scale); + } + + // tenebrae's sprites are all additive mode (weird) + if (gamemode == GAME_TENEBRAE && e->render.model && e->render.model->type == mod_sprite) + e->render.flags |= RENDER_ADDITIVE; + // player model is only shown with chase_active on + if (e->state_current.number == cl.viewentity) + e->render.flags |= RENDER_EXTERIORMODEL; + // either fullbright or lit + if(!r_fullbright.integer) + { + if (!(e->render.effects & EF_FULLBRIGHT)) + e->render.flags |= RENDER_LIGHT; + else if(r_equalize_entities_fullbright.integer) + e->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; + } + // hide player shadow during intermission or nehahra movie + if (!(e->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) + && (e->render.alpha >= 1) + && !(e->render.flags & RENDER_VIEWMODEL) + && (!(e->render.flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer))) + e->render.flags |= RENDER_SHADOW; + if (e->render.flags & RENDER_VIEWMODEL) + e->render.flags |= RENDER_NOSELFSHADOW; + if (e->render.effects & EF_NOSELFSHADOW) + e->render.flags |= RENDER_NOSELFSHADOW; + if (e->render.effects & EF_NODEPTHTEST) + e->render.flags |= RENDER_NODEPTHTEST; + if (e->render.effects & EF_ADDITIVE) + e->render.flags |= RENDER_ADDITIVE; + if (e->render.effects & EF_DOUBLESIDED) + e->render.flags |= RENDER_DOUBLESIDED; + + // make the other useful stuff + e->render.allowdecals = true; + CL_UpdateRenderEntity(&e->render); +} + +// creates light and trails from an entity +void CL_UpdateNetworkEntityTrail(entity_t *e) +{ + effectnameindex_t trailtype; + vec3_t origin; + + // bmodels are treated specially since their origin is usually '0 0 0' and + // their actual geometry is far from '0 0 0' + if (e->render.model && e->render.model->soundfromcenter) + { + vec3_t o; + VectorMAM(0.5f, e->render.model->normalmins, 0.5f, e->render.model->normalmaxs, o); + Matrix4x4_Transform(&e->render.matrix, o, origin); + } + else + Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); + + // handle particle trails and such effects now that we know where this + // entity is in the world... + trailtype = EFFECT_NONE; + // LordHavoc: if the entity has no effects, don't check each + if (e->render.effects & (EF_BRIGHTFIELD | EF_FLAME | EF_STARDUST)) + { + if (e->render.effects & EF_BRIGHTFIELD) + { + if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + trailtype = EFFECT_TR_NEXUIZPLASMA; + else + CL_EntityParticles(e); + } + if (e->render.effects & EF_FLAME) + CL_ParticleTrail(EFFECT_EF_FLAME, bound(0, cl.time - cl.oldtime, 0.1), origin, origin, vec3_origin, vec3_origin, NULL, 0, false, true, NULL, NULL); + if (e->render.effects & EF_STARDUST) + CL_ParticleTrail(EFFECT_EF_STARDUST, bound(0, cl.time - cl.oldtime, 0.1), origin, origin, vec3_origin, vec3_origin, NULL, 0, false, true, NULL, NULL); + } + if (e->render.internaleffects & (INTEF_FLAG1QW | INTEF_FLAG2QW)) + { + // these are only set on player entities + CL_AddQWCTFFlagModel(e, (e->render.internaleffects & INTEF_FLAG2QW) != 0); + } + // muzzleflash fades over time + if (e->persistent.muzzleflash > 0) + e->persistent.muzzleflash -= bound(0, cl.time - cl.oldtime, 0.1) * 20; + // LordHavoc: if the entity has no effects, don't check each + if (e->render.effects && !(e->render.flags & RENDER_VIEWMODEL)) + { + if (e->render.effects & EF_GIB) + trailtype = EFFECT_TR_BLOOD; + else if (e->render.effects & EF_ZOMGIB) + trailtype = EFFECT_TR_SLIGHTBLOOD; + else if (e->render.effects & EF_TRACER) + trailtype = EFFECT_TR_WIZSPIKE; + else if (e->render.effects & EF_TRACER2) + trailtype = EFFECT_TR_KNIGHTSPIKE; + else if (e->render.effects & EF_ROCKET) + trailtype = EFFECT_TR_ROCKET; + else if (e->render.effects & EF_GRENADE) + { + // LordHavoc: e->render.alpha == -1 is for Nehahra dem compatibility (cigar smoke) + trailtype = e->render.alpha == -1 ? EFFECT_TR_NEHAHRASMOKE : EFFECT_TR_GRENADE; + } + else if (e->render.effects & EF_TRACER3) + trailtype = EFFECT_TR_VORESPIKE; + } + // do trails + if (e->render.flags & RENDER_GLOWTRAIL) + trailtype = EFFECT_TR_GLOWTRAIL; + if (e->state_current.traileffectnum) + trailtype = (effectnameindex_t)e->state_current.traileffectnum; + // check if a trail is allowed (it is not after a teleport for example) + if (trailtype && e->persistent.trail_allowed) + { + float len; + vec3_t vel; + VectorSubtract(e->state_current.origin, e->state_previous.origin, vel); + len = e->state_current.time - e->state_previous.time; + if (len > 0) + len = 1.0f / len; + VectorScale(vel, len, vel); + CL_ParticleTrail(trailtype, 1, e->persistent.trail_origin, origin, vel, vel, e, e->state_current.glowcolor, false, true, NULL, NULL); + } + // now that the entity has survived one trail update it is allowed to + // leave a real trail on later frames + e->persistent.trail_allowed = true; + VectorCopy(origin, e->persistent.trail_origin); +} + + +/* +=============== +CL_UpdateViewEntities +=============== +*/ +void CL_UpdateViewEntities(void) +{ + int i; + // update any RENDER_VIEWMODEL entities to use the new view matrix + for (i = 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + entity_t *ent = cl.entities + i; + if ((ent->render.flags & RENDER_VIEWMODEL) || ent->state_current.tagentity) + CL_UpdateNetworkEntity(ent, 32, true); + } + } + // and of course the engine viewmodel needs updating as well + CL_UpdateNetworkEntity(&cl.viewent, 32, true); +} + +/* +=============== +CL_UpdateNetworkCollisionEntities +=============== +*/ +void CL_UpdateNetworkCollisionEntities(void) +{ + entity_t *ent; + int i; + + // start on the entity after the world + cl.num_brushmodel_entities = 0; + for (i = cl.maxclients + 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + ent = cl.entities + i; + if (ent->state_current.active && ent->render.model && ent->render.model->name[0] == '*' && ent->render.model->TraceBox) + { + // do not interpolate the bmodels for this + CL_UpdateNetworkEntity(ent, 32, false); + cl.brushmodel_entities[cl.num_brushmodel_entities++] = i; + } + } + } +} + +extern void R_DecalSystem_Reset(decalsystem_t *decalsystem); + +/* +=============== +CL_UpdateNetworkEntities +=============== +*/ +void CL_UpdateNetworkEntities(void) +{ + entity_t *ent; + int i; + + // start on the entity after the world + for (i = 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + ent = cl.entities + i; + if (ent->state_current.active) + { + CL_UpdateNetworkEntity(ent, 32, true); + // view models should never create light/trails + if (!(ent->render.flags & RENDER_VIEWMODEL)) + CL_UpdateNetworkEntityTrail(ent); + } + else + { + R_DecalSystem_Reset(&ent->render.decalsystem); + cl.entities_active[i] = false; + } + } + } +} + +void CL_UpdateViewModel(void) +{ + entity_t *ent; + ent = &cl.viewent; + ent->state_previous = ent->state_current; + ent->state_current = defaultstate; + ent->state_current.time = cl.time; + ent->state_current.number = (unsigned short)-1; + ent->state_current.active = true; + ent->state_current.modelindex = cl.stats[STAT_WEAPON]; + ent->state_current.frame = cl.stats[STAT_WEAPONFRAME]; + ent->state_current.flags = RENDER_VIEWMODEL; + if ((cl.stats[STAT_HEALTH] <= 0 && cl_deathnoviewmodel.integer) || cl.intermission) + ent->state_current.modelindex = 0; + else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) + { + if (gamemode == GAME_TRANSFUSION) + ent->state_current.alpha = 128; + else + ent->state_current.modelindex = 0; + } + ent->state_current.alpha = cl.entities[cl.viewentity].state_current.alpha; + ent->state_current.effects = EF_NOSHADOW | (cl.entities[cl.viewentity].state_current.effects & (EF_ADDITIVE | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB)); + + // reset animation interpolation on weaponmodel if model changed + if (ent->state_previous.modelindex != ent->state_current.modelindex) + { + ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; + ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; + } + CL_UpdateNetworkEntity(ent, 32, true); +} + +// note this is a recursive function, but it can never get in a runaway loop (because of the delayedlink flags) +void CL_LinkNetworkEntity(entity_t *e) +{ + effectnameindex_t trailtype; + vec3_t origin; + vec3_t dlightcolor; + vec_t dlightradius; + + // skip inactive entities and world + if (!e->state_current.active || e == cl.entities) + return; + if (e->state_current.tagentity) + { + // if the tag entity is currently impossible, skip it + if (e->state_current.tagentity >= cl.num_entities) + return; + // if the tag entity is inactive, skip it + if (!cl.entities[e->state_current.tagentity].state_current.active) + { + if(!cl.csqc_server2csqcentitynumber[e->state_current.tagentity]) + return; + if(!cl.csqcrenderentities[cl.csqc_server2csqcentitynumber[e->state_current.tagentity]].entitynumber) + return; + // if we get here, it's properly csqc networked and attached + } + } + + // create entity dlights associated with this entity + if (e->render.model && e->render.model->soundfromcenter) + { + // bmodels are treated specially since their origin is usually '0 0 0' + vec3_t o; + VectorMAM(0.5f, e->render.model->normalmins, 0.5f, e->render.model->normalmaxs, o); + Matrix4x4_Transform(&e->render.matrix, o, origin); + } + else + Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); + trailtype = EFFECT_NONE; + dlightradius = 0; + dlightcolor[0] = 0; + dlightcolor[1] = 0; + dlightcolor[2] = 0; + // LordHavoc: if the entity has no effects, don't check each + if (e->render.effects & (EF_BRIGHTFIELD | EF_DIMLIGHT | EF_BRIGHTLIGHT | EF_RED | EF_BLUE | EF_FLAME | EF_STARDUST)) + { + if (e->render.effects & EF_BRIGHTFIELD) + { + if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + trailtype = EFFECT_TR_NEXUIZPLASMA; + } + if (e->render.effects & EF_DIMLIGHT) + { + dlightradius = max(dlightradius, 200); + dlightcolor[0] += 1.50f; + dlightcolor[1] += 1.50f; + dlightcolor[2] += 1.50f; + } + if (e->render.effects & EF_BRIGHTLIGHT) + { + dlightradius = max(dlightradius, 400); + dlightcolor[0] += 3.00f; + dlightcolor[1] += 3.00f; + dlightcolor[2] += 3.00f; + } + // LordHavoc: more effects + if (e->render.effects & EF_RED) // red + { + dlightradius = max(dlightradius, 200); + dlightcolor[0] += 1.50f; + dlightcolor[1] += 0.15f; + dlightcolor[2] += 0.15f; + } + if (e->render.effects & EF_BLUE) // blue + { + dlightradius = max(dlightradius, 200); + dlightcolor[0] += 0.15f; + dlightcolor[1] += 0.15f; + dlightcolor[2] += 1.50f; + } + if (e->render.effects & EF_FLAME) + CL_ParticleTrail(EFFECT_EF_FLAME, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0, true, false, NULL, NULL); + if (e->render.effects & EF_STARDUST) + CL_ParticleTrail(EFFECT_EF_STARDUST, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0, true, false, NULL, NULL); + } + // muzzleflash fades over time, and is offset a bit + if (e->persistent.muzzleflash > 0 && r_refdef.scene.numlights < MAX_DLIGHTS) + { + vec3_t v2; + vec3_t color; + trace_t trace; + matrix4x4_t tempmatrix; + Matrix4x4_Transform(&e->render.matrix, muzzleflashorigin, v2); + trace = CL_TraceLine(origin, v2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, NULL, false, false); + Matrix4x4_Normalize(&tempmatrix, &e->render.matrix); + Matrix4x4_SetOrigin(&tempmatrix, trace.endpos[0], trace.endpos[1], trace.endpos[2]); + Matrix4x4_Scale(&tempmatrix, 150, 1); + VectorSet(color, e->persistent.muzzleflash * 4.0f, e->persistent.muzzleflash * 4.0f, e->persistent.muzzleflash * 4.0f); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, color, -1, NULL, true, 0, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + // LordHavoc: if the model has no flags, don't check each + if (e->render.model && e->render.effects && !(e->render.flags & RENDER_VIEWMODEL)) + { + if (e->render.effects & EF_GIB) + trailtype = EFFECT_TR_BLOOD; + else if (e->render.effects & EF_ZOMGIB) + trailtype = EFFECT_TR_SLIGHTBLOOD; + else if (e->render.effects & EF_TRACER) + trailtype = EFFECT_TR_WIZSPIKE; + else if (e->render.effects & EF_TRACER2) + trailtype = EFFECT_TR_KNIGHTSPIKE; + else if (e->render.effects & EF_ROCKET) + trailtype = EFFECT_TR_ROCKET; + else if (e->render.effects & EF_GRENADE) + { + // LordHavoc: e->render.alpha == -1 is for Nehahra dem compatibility (cigar smoke) + trailtype = e->render.alpha == -1 ? EFFECT_TR_NEHAHRASMOKE : EFFECT_TR_GRENADE; + } + else if (e->render.effects & EF_TRACER3) + trailtype = EFFECT_TR_VORESPIKE; + } + // LordHavoc: customizable glow + if (e->state_current.glowsize) + { + // * 4 for the expansion from 0-255 to 0-1023 range, + // / 255 to scale down byte colors + dlightradius = max(dlightradius, e->state_current.glowsize * 4); + VectorMA(dlightcolor, (1.0f / 255.0f), palette_rgb[e->state_current.glowcolor], dlightcolor); + } + // custom rtlight + if ((e->state_current.lightpflags & PFLAGS_FULLDYNAMIC) && r_refdef.scene.numlights < MAX_DLIGHTS) + { + matrix4x4_t dlightmatrix; + float light[4]; + VectorScale(e->state_current.light, (1.0f / 256.0f), light); + light[3] = e->state_current.light[3]; + if (light[0] == 0 && light[1] == 0 && light[2] == 0) + VectorSet(light, 1, 1, 1); + if (light[3] == 0) + light[3] = 350; + // FIXME: add ambient/diffuse/specular scales as an extension ontop of TENEBRAE_GFX_DLIGHTS? + Matrix4x4_Normalize(&dlightmatrix, &e->render.matrix); + Matrix4x4_Scale(&dlightmatrix, light[3], 1); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &dlightmatrix, light, e->state_current.lightstyle, e->state_current.skin > 0 ? va("cubemaps/%i", e->state_current.skin) : NULL, !(e->state_current.lightpflags & PFLAGS_NOSHADOW), (e->state_current.lightpflags & PFLAGS_CORONA) != 0, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + // make the glow dlight + else if (dlightradius > 0 && (dlightcolor[0] || dlightcolor[1] || dlightcolor[2]) && !(e->render.flags & RENDER_VIEWMODEL) && r_refdef.scene.numlights < MAX_DLIGHTS) + { + matrix4x4_t dlightmatrix; + Matrix4x4_Normalize(&dlightmatrix, &e->render.matrix); + // hack to make glowing player light shine on their gun + //if (e->state_current.number == cl.viewentity/* && !chase_active.integer*/) + // Matrix4x4_AdjustOrigin(&dlightmatrix, 0, 0, 30); + Matrix4x4_Scale(&dlightmatrix, dlightradius, 1); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &dlightmatrix, dlightcolor, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + // do trail light + if (e->render.flags & RENDER_GLOWTRAIL) + trailtype = EFFECT_TR_GLOWTRAIL; + if (e->state_current.traileffectnum) + trailtype = (effectnameindex_t)e->state_current.traileffectnum; + if (trailtype) + CL_ParticleTrail(trailtype, 1, origin, origin, vec3_origin, vec3_origin, NULL, e->state_current.glowcolor, true, false, NULL, NULL); + + // don't show entities with no modelindex (note: this still shows + // entities which have a modelindex that resolved to a NULL model) + if (e->render.model && !(e->render.effects & EF_NODRAW) && r_refdef.scene.numentities < r_refdef.scene.maxentities) + r_refdef.scene.entities[r_refdef.scene.numentities++] = &e->render; + //if (cl.viewentity && e->state_current.number == cl.viewentity) + // Matrix4x4_Print(&e->render.matrix); +} + +void CL_RelinkWorld(void) +{ + entity_t *ent = &cl.entities[0]; + // FIXME: this should be done at load + ent->render.matrix = identitymatrix; + ent->render.flags = RENDER_SHADOW; + if (!r_fullbright.integer) + ent->render.flags |= RENDER_LIGHT; + VectorSet(ent->render.colormod, 1, 1, 1); + VectorSet(ent->render.glowmod, 1, 1, 1); + ent->render.allowdecals = true; + CL_UpdateRenderEntity(&ent->render); + r_refdef.scene.worldentity = &ent->render; + r_refdef.scene.worldmodel = cl.worldmodel; +} + +static void CL_RelinkStaticEntities(void) +{ + int i; + entity_t *e; + for (i = 0, e = cl.static_entities;i < cl.num_static_entities && r_refdef.scene.numentities < r_refdef.scene.maxentities;i++, e++) + { + e->render.flags = 0; + // if the model was not loaded when the static entity was created we + // need to re-fetch the model pointer + e->render.model = CL_GetModelByIndex(e->state_baseline.modelindex); + // either fullbright or lit + if(!r_fullbright.integer) + { + if (!(e->render.effects & EF_FULLBRIGHT)) + e->render.flags |= RENDER_LIGHT; + else if(r_equalize_entities_fullbright.integer) + e->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; + } + // hide player shadow during intermission or nehahra movie + if (!(e->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (e->render.alpha >= 1)) + e->render.flags |= RENDER_SHADOW; + VectorSet(e->render.colormod, 1, 1, 1); + VectorSet(e->render.glowmod, 1, 1, 1); + VM_FrameBlendFromFrameGroupBlend(e->render.frameblend, e->render.framegroupblend, e->render.model); + e->render.allowdecals = true; + CL_UpdateRenderEntity(&e->render); + r_refdef.scene.entities[r_refdef.scene.numentities++] = &e->render; + } +} + +/* +=============== +CL_RelinkEntities +=============== +*/ +static void CL_RelinkNetworkEntities(void) +{ + entity_t *ent; + int i; + + // start on the entity after the world + for (i = 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + ent = cl.entities + i; + if (ent->state_current.active) + CL_LinkNetworkEntity(ent); + else + cl.entities_active[i] = false; + } + } +} + +static void CL_RelinkEffects(void) +{ + int i, intframe; + cl_effect_t *e; + entity_render_t *entrender; + float frame; + + for (i = 0, e = cl.effects;i < cl.num_effects;i++, e++) + { + if (e->active) + { + frame = (cl.time - e->starttime) * e->framerate + e->startframe; + intframe = (int)frame; + if (intframe < 0 || intframe >= e->endframe) + { + memset(e, 0, sizeof(*e)); + while (cl.num_effects > 0 && !cl.effects[cl.num_effects - 1].active) + cl.num_effects--; + continue; + } + + if (intframe != e->frame) + { + e->frame = intframe; + e->frame1time = e->frame2time; + e->frame2time = cl.time; + } + + // if we're drawing effects, get a new temp entity + // (NewTempEntity adds it to the render entities list for us) + if (r_draweffects.integer && (entrender = CL_NewTempEntity(e->starttime))) + { + // interpolation stuff + entrender->framegroupblend[0].frame = intframe; + entrender->framegroupblend[0].lerp = 1 - frame - intframe; + entrender->framegroupblend[0].start = e->frame1time; + if (intframe + 1 >= e->endframe) + { + entrender->framegroupblend[1].frame = 0; // disappear + entrender->framegroupblend[1].lerp = 0; + entrender->framegroupblend[1].start = 0; + } + else + { + entrender->framegroupblend[1].frame = intframe + 1; + entrender->framegroupblend[1].lerp = frame - intframe; + entrender->framegroupblend[1].start = e->frame2time; + } + + // normal stuff + entrender->model = CL_GetModelByIndex(e->modelindex); + entrender->alpha = 1; + VectorSet(entrender->colormod, 1, 1, 1); + VectorSet(entrender->glowmod, 1, 1, 1); + + Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, e->origin[0], e->origin[1], e->origin[2], 0, 0, 0, 1); + CL_UpdateRenderEntity(entrender); + } + } + } +} + +void CL_Beam_CalculatePositions(const beam_t *b, vec3_t start, vec3_t end) +{ + VectorCopy(b->start, start); + VectorCopy(b->end, end); + + // if coming from the player, update the start position + if (b->entity == cl.viewentity) + { + if (cl_beams_quakepositionhack.integer && !chase_active.integer) + { + // LordHavoc: this is a stupid hack from Quake that makes your + // lightning appear to come from your waist and cover less of your + // view + // in Quake this hack was applied to all players (causing the + // infamous crotch-lightning), but in darkplaces and QuakeWorld it + // only applies to your own lightning, and only in first person + Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, start); + } + if (cl_beams_instantaimhack.integer) + { + vec3_t dir, localend; + vec_t len; + // LordHavoc: this updates the beam direction to match your + // viewangles + VectorSubtract(end, start, dir); + len = VectorLength(dir); + VectorNormalize(dir); + VectorSet(localend, len, 0, 0); + Matrix4x4_Transform(&r_refdef.view.matrix, localend, end); + } + } +} + +void CL_RelinkBeams(void) +{ + int i; + beam_t *b; + vec3_t dist, org, start, end; + float d; + entity_render_t *entrender; + double yaw, pitch; + float forward; + matrix4x4_t tempmatrix; + + for (i = 0, b = cl.beams;i < cl.num_beams;i++, b++) + { + if (!b->model) + continue; + if (b->endtime < cl.time) + { + b->model = NULL; + continue; + } + + CL_Beam_CalculatePositions(b, start, end); + + if (b->lightning) + { + if (cl_beams_lightatend.integer && r_refdef.scene.numlights < MAX_DLIGHTS) + { + // FIXME: create a matrix from the beam start/end orientation + vec3_t dlightcolor; + VectorSet(dlightcolor, 0.3, 0.7, 1); + Matrix4x4_CreateFromQuakeEntity(&tempmatrix, end[0], end[1], end[2], 0, 0, 0, 200); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, dlightcolor, -1, NULL, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + if (cl_beams_polygons.integer) + continue; + } + + // calculate pitch and yaw + // (this is similar to the QuakeC builtin function vectoangles) + VectorSubtract(end, start, dist); + if (dist[1] == 0 && dist[0] == 0) + { + yaw = 0; + if (dist[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = atan2(dist[1], dist[0]) * 180 / M_PI; + if (yaw < 0) + yaw += 360; + + forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]); + pitch = atan2(dist[2], forward) * 180 / M_PI; + if (pitch < 0) + pitch += 360; + } + + // add new entities for the lightning + VectorCopy (start, org); + d = VectorNormalizeLength(dist); + while (d > 0) + { + entrender = CL_NewTempEntity (0); + if (!entrender) + return; + //VectorCopy (org, ent->render.origin); + entrender->model = b->model; + //ent->render.effects = EF_FULLBRIGHT; + //ent->render.angles[0] = pitch; + //ent->render.angles[1] = yaw; + //ent->render.angles[2] = rand()%360; + Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, org[0], org[1], org[2], -pitch, yaw, lhrandom(0, 360), 1); + CL_UpdateRenderEntity(entrender); + VectorMA(org, 30, dist, org); + d -= 30; + } + } + + while (cl.num_beams > 0 && !cl.beams[cl.num_beams - 1].model) + cl.num_beams--; +} + +static void CL_RelinkQWNails(void) +{ + int i; + vec_t *v; + entity_render_t *entrender; + + for (i = 0;i < cl.qw_num_nails;i++) + { + v = cl.qw_nails[i]; + + // if we're drawing effects, get a new temp entity + // (NewTempEntity adds it to the render entities list for us) + if (!(entrender = CL_NewTempEntity(0))) + continue; + + // normal stuff + entrender->model = CL_GetModelByIndex(cl.qw_modelindex_spike); + entrender->alpha = 1; + VectorSet(entrender->colormod, 1, 1, 1); + VectorSet(entrender->glowmod, 1, 1, 1); + + Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, v[0], v[1], v[2], v[3], v[4], v[5], 1); + CL_UpdateRenderEntity(entrender); + } +} + +void CL_LerpPlayer(float frac) +{ + int i; + + cl.viewzoom = cl.mviewzoom[1] + frac * (cl.mviewzoom[0] - cl.mviewzoom[1]); + for (i = 0;i < 3;i++) + { + cl.punchangle[i] = cl.mpunchangle[1][i] + frac * (cl.mpunchangle[0][i] - cl.mpunchangle[1][i]); + cl.punchvector[i] = cl.mpunchvector[1][i] + frac * (cl.mpunchvector[0][i] - cl.mpunchvector[1][i]); + cl.velocity[i] = cl.mvelocity[1][i] + frac * (cl.mvelocity[0][i] - cl.mvelocity[1][i]); + } + + // interpolate the angles if playing a demo or spectating someone + if (cls.demoplayback || cl.fixangle[0]) + { + for (i = 0;i < 3;i++) + { + float d = cl.mviewangles[0][i] - cl.mviewangles[1][i]; + if (d > 180) + d -= 360; + else if (d < -180) + d += 360; + cl.viewangles[i] = cl.mviewangles[1][i] + frac * d; + } + } +} + +void CSQC_RelinkAllEntities (int drawmask) +{ + // link stuff + CL_RelinkWorld(); + CL_RelinkStaticEntities(); + CL_RelinkBeams(); + CL_RelinkEffects(); + + // link stuff + if (drawmask & ENTMASK_ENGINE) + { + CL_RelinkNetworkEntities(); + if (drawmask & ENTMASK_ENGINEVIEWMODELS) + CL_LinkNetworkEntity(&cl.viewent); // link gun model + CL_RelinkQWNails(); + } + + // update view blend + V_CalcViewBlend(); +} + +/* +=============== +CL_UpdateWorld + +Update client game world for a new frame +=============== +*/ +void CL_UpdateWorld(void) +{ + r_refdef.scene.extraupdate = !r_speeds.integer; + r_refdef.scene.numentities = 0; + r_refdef.scene.numlights = 0; + r_refdef.view.matrix = identitymatrix; + r_refdef.view.quality = 1; + + cl.num_brushmodel_entities = 0; + + if (cls.state == ca_connected && cls.signon == SIGNONS) + { + // prepare for a new frame + CL_LerpPlayer(CL_LerpPoint()); + CL_DecayLightFlashes(); + CL_ClearTempEntities(); + V_DriftPitch(); + V_FadeViewFlashs(); + + // if prediction is enabled we have to update all the collidable + // network entities before the prediction code can be run + CL_UpdateNetworkCollisionEntities(); + + // now update the player prediction + CL_ClientMovement_Replay(); + + // update the player entity (which may be predicted) + CL_UpdateNetworkEntity(cl.entities + cl.viewentity, 32, true); + + // now update the view (which depends on that player entity) + V_CalcRefdef(); + + // now update all the network entities and create particle trails + // (some entities may depend on the view) + CL_UpdateNetworkEntities(); + + // update the engine-based viewmodel + CL_UpdateViewModel(); + + CL_RelinkLightFlashes(); + CSQC_RelinkAllEntities(ENTMASK_ENGINE | ENTMASK_ENGINEVIEWMODELS); + + // decals, particles, and explosions will be updated during rneder + } + + r_refdef.scene.time = cl.time; +} + +// LordHavoc: pausedemo command +static void CL_PauseDemo_f (void) +{ + cls.demopaused = !cls.demopaused; + if (cls.demopaused) + Con_Print("Demo paused\n"); + else + Con_Print("Demo unpaused\n"); +} + +/* +====================== +CL_Fog_f +====================== +*/ +static void CL_Fog_f (void) +{ + if (Cmd_Argc () == 1) + { + Con_Printf("\"fog\" is \"%f %f %f %f %f %f %f %f %f\"\n", r_refdef.fog_density, r_refdef.fog_red, r_refdef.fog_green, r_refdef.fog_blue, r_refdef.fog_alpha, r_refdef.fog_start, r_refdef.fog_end, r_refdef.fog_height, r_refdef.fog_fadedepth); + return; + } + FOG_clear(); // so missing values get good defaults + if(Cmd_Argc() > 1) + r_refdef.fog_density = atof(Cmd_Argv(1)); + if(Cmd_Argc() > 2) + r_refdef.fog_red = atof(Cmd_Argv(2)); + if(Cmd_Argc() > 3) + r_refdef.fog_green = atof(Cmd_Argv(3)); + if(Cmd_Argc() > 4) + r_refdef.fog_blue = atof(Cmd_Argv(4)); + if(Cmd_Argc() > 5) + r_refdef.fog_alpha = atof(Cmd_Argv(5)); + if(Cmd_Argc() > 6) + r_refdef.fog_start = atof(Cmd_Argv(6)); + if(Cmd_Argc() > 7) + r_refdef.fog_end = atof(Cmd_Argv(7)); + if(Cmd_Argc() > 8) + r_refdef.fog_height = atof(Cmd_Argv(8)); + if(Cmd_Argc() > 9) + r_refdef.fog_fadedepth = atof(Cmd_Argv(9)); +} + +/* +====================== +CL_FogHeightTexture_f +====================== +*/ +static void CL_Fog_HeightTexture_f (void) +{ + if (Cmd_Argc () < 11) + { + Con_Printf("\"fog_heighttexture\" is \"%f %f %f %f %f %f %f %f %f %s\"\n", r_refdef.fog_density, r_refdef.fog_red, r_refdef.fog_green, r_refdef.fog_blue, r_refdef.fog_alpha, r_refdef.fog_start, r_refdef.fog_end, r_refdef.fog_height, r_refdef.fog_fadedepth, r_refdef.fog_height_texturename); + return; + } + FOG_clear(); // so missing values get good defaults + r_refdef.fog_density = atof(Cmd_Argv(1)); + r_refdef.fog_red = atof(Cmd_Argv(2)); + r_refdef.fog_green = atof(Cmd_Argv(3)); + r_refdef.fog_blue = atof(Cmd_Argv(4)); + r_refdef.fog_alpha = atof(Cmd_Argv(5)); + r_refdef.fog_start = atof(Cmd_Argv(6)); + r_refdef.fog_end = atof(Cmd_Argv(7)); + r_refdef.fog_height = atof(Cmd_Argv(8)); + r_refdef.fog_fadedepth = atof(Cmd_Argv(9)); + strlcpy(r_refdef.fog_height_texturename, Cmd_Argv(10), sizeof(r_refdef.fog_height_texturename)); +} + + +/* +==================== +CL_TimeRefresh_f + +For program optimization +==================== +*/ +static void CL_TimeRefresh_f (void) +{ + int i; + float timestart, timedelta; + + r_refdef.scene.extraupdate = false; + + timestart = Sys_DoubleTime(); + for (i = 0;i < 128;i++) + { + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], 0, i / 128.0 * 360.0, 0, 1); + r_refdef.view.quality = 1; + CL_UpdateScreen(); + } + timedelta = Sys_DoubleTime() - timestart; + + Con_Printf("%f seconds (%f fps)\n", timedelta, 128/timedelta); +} + +void CL_AreaStats_f(void) +{ + World_PrintAreaStats(&cl.world, "client"); +} + +cl_locnode_t *CL_Locs_FindNearest(const vec3_t point) +{ + int i; + cl_locnode_t *loc; + cl_locnode_t *best; + vec3_t nearestpoint; + vec_t dist, bestdist; + best = NULL; + bestdist = 0; + for (loc = cl.locnodes;loc;loc = loc->next) + { + for (i = 0;i < 3;i++) + nearestpoint[i] = bound(loc->mins[i], point[i], loc->maxs[i]); + dist = VectorDistance2(nearestpoint, point); + if (bestdist > dist || !best) + { + bestdist = dist; + best = loc; + if (bestdist < 1) + break; + } + } + return best; +} + +void CL_Locs_FindLocationName(char *buffer, size_t buffersize, vec3_t point) +{ + cl_locnode_t *loc; + loc = CL_Locs_FindNearest(point); + if (loc) + strlcpy(buffer, loc->name, buffersize); + else + dpsnprintf(buffer, buffersize, "LOC=%.0f:%.0f:%.0f", point[0], point[1], point[2]); +} + +void CL_Locs_FreeNode(cl_locnode_t *node) +{ + cl_locnode_t **pointer, **next; + for (pointer = &cl.locnodes;*pointer;pointer = next) + { + next = &(*pointer)->next; + if (*pointer == node) + { + *pointer = node->next; + Mem_Free(node); + return; + } + } + Con_Printf("CL_Locs_FreeNode: no such node! (%p)\n", (void *)node); +} + +void CL_Locs_AddNode(vec3_t mins, vec3_t maxs, const char *name) +{ + cl_locnode_t *node, **pointer; + int namelen; + if (!name) + name = ""; + namelen = strlen(name); + node = (cl_locnode_t *) Mem_Alloc(cls.levelmempool, sizeof(cl_locnode_t) + namelen + 1); + VectorSet(node->mins, min(mins[0], maxs[0]), min(mins[1], maxs[1]), min(mins[2], maxs[2])); + VectorSet(node->maxs, max(mins[0], maxs[0]), max(mins[1], maxs[1]), max(mins[2], maxs[2])); + node->name = (char *)(node + 1); + memcpy(node->name, name, namelen); + node->name[namelen] = 0; + // link it into the tail of the list to preserve the order + for (pointer = &cl.locnodes;*pointer;pointer = &(*pointer)->next) + ; + *pointer = node; +} + +void CL_Locs_Add_f(void) +{ + vec3_t mins, maxs; + if (Cmd_Argc() != 5 && Cmd_Argc() != 8) + { + Con_Printf("usage: %s x y z[ x y z] name\n", Cmd_Argv(0)); + return; + } + mins[0] = atof(Cmd_Argv(1)); + mins[1] = atof(Cmd_Argv(2)); + mins[2] = atof(Cmd_Argv(3)); + if (Cmd_Argc() == 8) + { + maxs[0] = atof(Cmd_Argv(4)); + maxs[1] = atof(Cmd_Argv(5)); + maxs[2] = atof(Cmd_Argv(6)); + CL_Locs_AddNode(mins, maxs, Cmd_Argv(7)); + } + else + CL_Locs_AddNode(mins, mins, Cmd_Argv(4)); +} + +void CL_Locs_RemoveNearest_f(void) +{ + cl_locnode_t *loc; + loc = CL_Locs_FindNearest(r_refdef.view.origin); + if (loc) + CL_Locs_FreeNode(loc); + else + Con_Printf("no loc point or box found for your location\n"); +} + +void CL_Locs_Clear_f(void) +{ + while (cl.locnodes) + CL_Locs_FreeNode(cl.locnodes); +} + +void CL_Locs_Save_f(void) +{ + cl_locnode_t *loc; + qfile_t *outfile; + char locfilename[MAX_QPATH]; + if (!cl.locnodes) + { + Con_Printf("No loc points/boxes exist!\n"); + return; + } + if (cls.state != ca_connected || !cl.worldmodel) + { + Con_Printf("No level loaded!\n"); + return; + } + dpsnprintf(locfilename, sizeof(locfilename), "%s.loc", cl.worldnamenoextension); + + outfile = FS_OpenRealFile(locfilename, "w", false); + if (!outfile) + return; + // if any boxes are used then this is a proquake-format loc file, which + // allows comments, so add some relevant information at the start + for (loc = cl.locnodes;loc;loc = loc->next) + if (!VectorCompare(loc->mins, loc->maxs)) + break; + if (loc) + { + FS_Printf(outfile, "// %s %s saved by %s\n// x,y,z,x,y,z,\"name\"\n\n", locfilename, Sys_TimeString("%Y-%m-%d"), engineversion); + for (loc = cl.locnodes;loc;loc = loc->next) + if (VectorCompare(loc->mins, loc->maxs)) + break; + if (loc) + Con_Printf("Warning: writing loc file containing a mixture of qizmo-style points and proquake-style boxes may not work in qizmo or proquake!\n"); + } + for (loc = cl.locnodes;loc;loc = loc->next) + { + if (VectorCompare(loc->mins, loc->maxs)) + { + int len; + const char *s; + const char *in = loc->name; + char name[MAX_INPUTLINE]; + for (len = 0;len < (int)sizeof(name) - 1 && *in;) + { + if (*in == ' ') {s = "$loc_name_separator";in++;} + else if (!strncmp(in, "SSG", 3)) {s = "$loc_name_ssg";in += 3;} + else if (!strncmp(in, "NG", 2)) {s = "$loc_name_ng";in += 2;} + else if (!strncmp(in, "SNG", 3)) {s = "$loc_name_sng";in += 3;} + else if (!strncmp(in, "GL", 2)) {s = "$loc_name_gl";in += 2;} + else if (!strncmp(in, "RL", 2)) {s = "$loc_name_rl";in += 2;} + else if (!strncmp(in, "LG", 2)) {s = "$loc_name_lg";in += 2;} + else if (!strncmp(in, "GA", 2)) {s = "$loc_name_ga";in += 2;} + else if (!strncmp(in, "YA", 2)) {s = "$loc_name_ya";in += 2;} + else if (!strncmp(in, "RA", 2)) {s = "$loc_name_ra";in += 2;} + else if (!strncmp(in, "MEGA", 4)) {s = "$loc_name_mh";in += 4;} + else s = NULL; + if (s) + { + while (len < (int)sizeof(name) - 1 && *s) + name[len++] = *s++; + continue; + } + name[len++] = *in++; + } + name[len] = 0; + FS_Printf(outfile, "%.0f %.0f %.0f %s\n", loc->mins[0]*8, loc->mins[1]*8, loc->mins[2]*8, name); + } + else + FS_Printf(outfile, "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,\"%s\"\n", loc->mins[0], loc->mins[1], loc->mins[2], loc->maxs[0], loc->maxs[1], loc->maxs[2], loc->name); + } + FS_Close(outfile); +} + +void CL_Locs_Reload_f(void) +{ + int i, linenumber, limit, len; + const char *s; + char *filedata, *text, *textend, *linestart, *linetext, *lineend; + fs_offset_t filesize; + vec3_t mins, maxs; + char locfilename[MAX_QPATH]; + char name[MAX_INPUTLINE]; + + if (cls.state != ca_connected || !cl.worldmodel) + { + Con_Printf("No level loaded!\n"); + return; + } + + CL_Locs_Clear_f(); + + // try maps/something.loc first (LordHavoc: where I think they should be) + dpsnprintf(locfilename, sizeof(locfilename), "%s.loc", cl.worldnamenoextension); + filedata = (char *)FS_LoadFile(locfilename, cls.levelmempool, false, &filesize); + if (!filedata) + { + // try proquake name as well (LordHavoc: I hate path mangling) + dpsnprintf(locfilename, sizeof(locfilename), "locs/%s.loc", cl.worldbasename); + filedata = (char *)FS_LoadFile(locfilename, cls.levelmempool, false, &filesize); + if (!filedata) + return; + } + text = filedata; + textend = filedata + filesize; + for (linenumber = 1;text < textend;linenumber++) + { + linestart = text; + for (;text < textend && *text != '\r' && *text != '\n';text++) + ; + lineend = text; + if (text + 1 < textend && *text == '\r' && text[1] == '\n') + text++; + if (text < textend) + text++; + // trim trailing whitespace + while (lineend > linestart && ISWHITESPACE(lineend[-1])) + lineend--; + // trim leading whitespace + while (linestart < lineend && ISWHITESPACE(*linestart)) + linestart++; + // check if this is a comment + if (linestart + 2 <= lineend && !strncmp(linestart, "//", 2)) + continue; + linetext = linestart; + limit = 3; + for (i = 0;i < limit;i++) + { + if (linetext >= lineend) + break; + // note: a missing number is interpreted as 0 + if (i < 3) + mins[i] = atof(linetext); + else + maxs[i - 3] = atof(linetext); + // now advance past the number + while (linetext < lineend && !ISWHITESPACE(*linetext) && *linetext != ',') + linetext++; + // advance through whitespace + if (linetext < lineend) + { + if (*linetext == ',') + { + linetext++; + limit = 6; + // note: comma can be followed by whitespace + } + if (ISWHITESPACE(*linetext)) + { + // skip whitespace + while (linetext < lineend && ISWHITESPACE(*linetext)) + linetext++; + } + } + } + // if this is a quoted name, remove the quotes + if (i == 6) + { + if (linetext >= lineend || *linetext != '"') + continue; // proquake location names are always quoted + lineend--; + linetext++; + len = min(lineend - linetext, (int)sizeof(name) - 1); + memcpy(name, linetext, len); + name[len] = 0; + // add the box to the list + CL_Locs_AddNode(mins, maxs, name); + } + // if a point was parsed, it needs to be scaled down by 8 (since + // point-based loc files were invented by a proxy which dealt + // directly with quake protocol coordinates, which are *8), turn + // it into a box + else if (i == 3) + { + // interpret silly fuhquake macros + for (len = 0;len < (int)sizeof(name) - 1 && linetext < lineend;) + { + if (*linetext == '$') + { + if (linetext + 18 <= lineend && !strncmp(linetext, "$loc_name_separator", 19)) {s = " ";linetext += 19;} + else if (linetext + 13 <= lineend && !strncmp(linetext, "$loc_name_ssg", 13)) {s = "SSG";linetext += 13;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ng", 12)) {s = "NG";linetext += 12;} + else if (linetext + 13 <= lineend && !strncmp(linetext, "$loc_name_sng", 13)) {s = "SNG";linetext += 13;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_gl", 12)) {s = "GL";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_rl", 12)) {s = "RL";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_lg", 12)) {s = "LG";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ga", 12)) {s = "GA";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ya", 12)) {s = "YA";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ra", 12)) {s = "RA";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_mh", 12)) {s = "MEGA";linetext += 12;} + else s = NULL; + if (s) + { + while (len < (int)sizeof(name) - 1 && *s) + name[len++] = *s++; + continue; + } + } + name[len++] = *linetext++; + } + name[len] = 0; + // add the point to the list + VectorScale(mins, (1.0 / 8.0), mins); + CL_Locs_AddNode(mins, mins, name); + } + else + continue; + } +} + +/* +=========== +CL_Shutdown +=========== +*/ +void CL_Shutdown (void) +{ + CL_Screen_Shutdown(); + CL_Particles_Shutdown(); + CL_Parse_Shutdown(); + + Mem_FreePool (&cls.permanentmempool); + Mem_FreePool (&cls.levelmempool); +} + +/* +================= +CL_Init +================= +*/ +void CL_Init (void) +{ + + cls.levelmempool = Mem_AllocPool("client (per-level memory)", 0, NULL); + cls.permanentmempool = Mem_AllocPool("client (long term memory)", 0, NULL); + + memset(&r_refdef, 0, sizeof(r_refdef)); + // max entities sent to renderer per frame + r_refdef.scene.maxentities = MAX_EDICTS + 256 + 512; + r_refdef.scene.entities = (entity_render_t **)Mem_Alloc(cls.permanentmempool, sizeof(entity_render_t *) * r_refdef.scene.maxentities); + + // max temp entities + r_refdef.scene.maxtempentities = MAX_TEMPENTITIES; + r_refdef.scene.tempentities = (entity_render_t *)Mem_Alloc(cls.permanentmempool, sizeof(entity_render_t) * r_refdef.scene.maxtempentities); + + CL_InitInput (); + +// +// register our commands +// + Cvar_RegisterVariable (&cl_upspeed); + Cvar_RegisterVariable (&cl_forwardspeed); + Cvar_RegisterVariable (&cl_backspeed); + Cvar_RegisterVariable (&cl_sidespeed); + Cvar_RegisterVariable (&cl_movespeedkey); + Cvar_RegisterVariable (&cl_yawspeed); + Cvar_RegisterVariable (&cl_pitchspeed); + Cvar_RegisterVariable (&cl_anglespeedkey); + Cvar_RegisterVariable (&cl_shownet); + Cvar_RegisterVariable (&cl_nolerp); + Cvar_RegisterVariable (&cl_lerpexcess); + Cvar_RegisterVariable (&cl_lerpanim_maxdelta_server); + Cvar_RegisterVariable (&cl_lerpanim_maxdelta_framegroups); + Cvar_RegisterVariable (&cl_deathfade); + Cvar_RegisterVariable (&lookspring); + Cvar_RegisterVariable (&lookstrafe); + Cvar_RegisterVariable (&sensitivity); + Cvar_RegisterVariable (&freelook); + + Cvar_RegisterVariable (&m_pitch); + Cvar_RegisterVariable (&m_yaw); + Cvar_RegisterVariable (&m_forward); + Cvar_RegisterVariable (&m_side); + + Cvar_RegisterVariable (&cl_itembobspeed); + Cvar_RegisterVariable (&cl_itembobheight); + + Cmd_AddCommand ("entities", CL_PrintEntities_f, "print information on network entities known to client"); + Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server (or disconnect all clients if running a server)"); + Cmd_AddCommand ("record", CL_Record_f, "record a demo"); + Cmd_AddCommand ("stop", CL_Stop_f, "stop recording or playing a demo"); + Cmd_AddCommand ("playdemo", CL_PlayDemo_f, "watch a demo file"); + Cmd_AddCommand ("timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log"); + + // Support Client-side Model Index List + Cmd_AddCommand ("cl_modelindexlist", CL_ModelIndexList_f, "list information on all models in the client modelindex"); + // Support Client-side Sound Index List + Cmd_AddCommand ("cl_soundindexlist", CL_SoundIndexList_f, "list all sounds in the client soundindex"); + + Cvar_RegisterVariable (&cl_autodemo); + Cvar_RegisterVariable (&cl_autodemo_nameformat); + Cvar_RegisterVariable (&cl_autodemo_delete); + + Cmd_AddCommand ("fog", CL_Fog_f, "set global fog parameters (density red green blue [alpha [mindist [maxdist [top [fadedepth]]]]])"); + Cmd_AddCommand ("fog_heighttexture", CL_Fog_HeightTexture_f, "set global fog parameters (density red green blue alpha mindist maxdist top depth textures/mapname/fogheight.tga)"); + + // LordHavoc: added pausedemo + Cmd_AddCommand ("pausedemo", CL_PauseDemo_f, "pause demo playback (can also safely pause demo recording if using QUAKE, QUAKEDP or NEHAHRAMOVIE protocol, useful for making movies)"); + + Cmd_AddCommand ("cl_areastats", CL_AreaStats_f, "prints statistics on entity culling during collision traces"); + + Cvar_RegisterVariable(&r_draweffects); + Cvar_RegisterVariable(&cl_explosions_alpha_start); + Cvar_RegisterVariable(&cl_explosions_alpha_end); + Cvar_RegisterVariable(&cl_explosions_size_start); + Cvar_RegisterVariable(&cl_explosions_size_end); + Cvar_RegisterVariable(&cl_explosions_lifetime); + Cvar_RegisterVariable(&cl_stainmaps); + Cvar_RegisterVariable(&cl_stainmaps_clearonload); + Cvar_RegisterVariable(&cl_beams_polygons); + Cvar_RegisterVariable(&cl_beams_quakepositionhack); + Cvar_RegisterVariable(&cl_beams_instantaimhack); + Cvar_RegisterVariable(&cl_beams_lightatend); + Cvar_RegisterVariable(&cl_noplayershadow); + Cvar_RegisterVariable(&cl_dlights_decayradius); + Cvar_RegisterVariable(&cl_dlights_decaybrightness); + + Cvar_RegisterVariable(&cl_prydoncursor); + Cvar_RegisterVariable(&cl_prydoncursor_notrace); + + Cvar_RegisterVariable(&cl_deathnoviewmodel); + + // for QW connections + Cvar_RegisterVariable(&qport); + Cvar_SetValueQuick(&qport, (rand() * RAND_MAX + rand()) & 0xffff); + + Cmd_AddCommand("timerefresh", CL_TimeRefresh_f, "turn quickly and print rendering statistcs"); + + Cvar_RegisterVariable(&cl_locs_enable); + Cvar_RegisterVariable(&cl_locs_show); + Cmd_AddCommand("locs_add", CL_Locs_Add_f, "add a point or box location (usage: x y z[ x y z] \"name\", if two sets of xyz are supplied it is a box, otherwise point)"); + Cmd_AddCommand("locs_removenearest", CL_Locs_RemoveNearest_f, "remove the nearest point or box (note: you need to be very near a box to remove it)"); + Cmd_AddCommand("locs_clear", CL_Locs_Clear_f, "remove all loc points/boxes"); + Cmd_AddCommand("locs_reload", CL_Locs_Reload_f, "reload .loc file for this map"); + Cmd_AddCommand("locs_save", CL_Locs_Save_f, "save .loc file for this map containing currently defined points and boxes"); + + CL_Parse_Init(); + CL_Particles_Init(); + CL_Screen_Init(); + + CL_Video_Init(); + CL_Gecko_Init(); +} + + + diff --git a/misc/source/darkplaces-src/cl_parse.c b/misc/source/darkplaces-src/cl_parse.c new file mode 100644 index 00000000..3d56d111 --- /dev/null +++ b/misc/source/darkplaces-src/cl_parse.c @@ -0,0 +1,4253 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_parse.c -- parse a message received from the server + +#include "quakedef.h" +#include "cdaudio.h" +#include "cl_collision.h" +#include "csprogs.h" +#include "libcurl.h" +#include "utf8lib.h" +#include "menu.h" +#include "cl_video.h" + +const char *svc_strings[128] = +{ + "svc_bad", + "svc_nop", + "svc_disconnect", + "svc_updatestat", + "svc_version", // [int] server version + "svc_setview", // [short] entity number + "svc_sound", // + "svc_time", // [float] server time + "svc_print", // [string] null terminated string + "svc_stufftext", // [string] stuffed into client's console buffer + // the string should be \n terminated + "svc_setangle", // [vec3] set the view angle to this absolute value + + "svc_serverinfo", // [int] version + // [string] signon string + // [string]..[0]model cache [string]...[0]sounds cache + // [string]..[0]item cache + "svc_lightstyle", // [byte] [string] + "svc_updatename", // [byte] [string] + "svc_updatefrags", // [byte] [short] + "svc_clientdata", // + "svc_stopsound", // + "svc_updatecolors", // [byte] [byte] + "svc_particle", // [vec3] + "svc_damage", // [byte] impact [byte] blood [vec3] from + + "svc_spawnstatic", + "OBSOLETE svc_spawnbinary", + "svc_spawnbaseline", + + "svc_temp_entity", // + "svc_setpause", + "svc_signonnum", + "svc_centerprint", + "svc_killedmonster", + "svc_foundsecret", + "svc_spawnstaticsound", + "svc_intermission", + "svc_finale", // [string] music [string] text + "svc_cdtrack", // [byte] track [byte] looptrack + "svc_sellscreen", + "svc_cutscene", + "svc_showlmp", // [string] iconlabel [string] lmpfile [short] x [short] y + "svc_hidelmp", // [string] iconlabel + "svc_skybox", // [string] skyname + "", // 38 + "", // 39 + "", // 40 + "", // 41 + "", // 42 + "", // 43 + "", // 44 + "", // 45 + "", // 46 + "", // 47 + "", // 48 + "", // 49 + "svc_downloaddata", // 50 // [int] start [short] size [variable length] data + "svc_updatestatubyte", // 51 // [byte] stat [byte] value + "svc_effect", // 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate + "svc_effect2", // 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate + "svc_sound2", // 54 // short soundindex instead of byte + "svc_spawnbaseline2", // 55 // short modelindex instead of byte + "svc_spawnstatic2", // 56 // short modelindex instead of byte + "svc_entities", // 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata + "svc_csqcentities", // 58 // [short] entnum [variable length] entitydata ... [short] 0x0000 + "svc_spawnstaticsound2", // 59 // [coord3] [short] samp [byte] vol [byte] aten + "svc_trailparticles", // 60 // [short] entnum [short] effectnum [vector] start [vector] end + "svc_pointparticles", // 61 // [short] effectnum [vector] start [vector] velocity [short] count + "svc_pointparticles1", // 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 +}; + +const char *qw_svc_strings[128] = +{ + "qw_svc_bad", // 0 + "qw_svc_nop", // 1 + "qw_svc_disconnect", // 2 + "qw_svc_updatestat", // 3 // [byte] [byte] + "", // 4 + "qw_svc_setview", // 5 // [short] entity number + "qw_svc_sound", // 6 // + "", // 7 + "qw_svc_print", // 8 // [byte] id [string] null terminated string + "qw_svc_stufftext", // 9 // [string] stuffed into client's console buffer + "qw_svc_setangle", // 10 // [angle3] set the view angle to this absolute value + "qw_svc_serverdata", // 11 // [long] protocol ... + "qw_svc_lightstyle", // 12 // [byte] [string] + "", // 13 + "qw_svc_updatefrags", // 14 // [byte] [short] + "", // 15 + "qw_svc_stopsound", // 16 // + "", // 17 + "", // 18 + "qw_svc_damage", // 19 + "qw_svc_spawnstatic", // 20 + "", // 21 + "qw_svc_spawnbaseline", // 22 + "qw_svc_temp_entity", // 23 // variable + "qw_svc_setpause", // 24 // [byte] on / off + "", // 25 + "qw_svc_centerprint", // 26 // [string] to put in center of the screen + "qw_svc_killedmonster", // 27 + "qw_svc_foundsecret", // 28 + "qw_svc_spawnstaticsound", // 29 // [coord3] [byte] samp [byte] vol [byte] aten + "qw_svc_intermission", // 30 // [vec3_t] origin [vec3_t] angle + "qw_svc_finale", // 31 // [string] text + "qw_svc_cdtrack", // 32 // [byte] track + "qw_svc_sellscreen", // 33 + "qw_svc_smallkick", // 34 // set client punchangle to 2 + "qw_svc_bigkick", // 35 // set client punchangle to 4 + "qw_svc_updateping", // 36 // [byte] [short] + "qw_svc_updateentertime", // 37 // [byte] [float] + "qw_svc_updatestatlong", // 38 // [byte] [long] + "qw_svc_muzzleflash", // 39 // [short] entity + "qw_svc_updateuserinfo", // 40 // [byte] slot [long] uid + "qw_svc_download", // 41 // [short] size [size bytes] + "qw_svc_playerinfo", // 42 // variable + "qw_svc_nails", // 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 + "qw_svc_chokecount", // 44 // [byte] packets choked + "qw_svc_modellist", // 45 // [strings] + "qw_svc_soundlist", // 46 // [strings] + "qw_svc_packetentities", // 47 // [...] + "qw_svc_deltapacketentities", // 48 // [...] + "qw_svc_maxspeed", // 49 // maxspeed change, for prediction + "qw_svc_entgravity", // 50 // gravity change, for prediction + "qw_svc_setinfo", // 51 // setinfo on a client + "qw_svc_serverinfo", // 52 // serverinfo + "qw_svc_updatepl", // 53 // [byte] [byte] +}; + +//============================================================================= + +cvar_t cl_worldmessage = {CVAR_READONLY, "cl_worldmessage", "", "title of current level"}; +cvar_t cl_worldname = {CVAR_READONLY, "cl_worldname", "", "name of current worldmodel"}; +cvar_t cl_worldnamenoextension = {CVAR_READONLY, "cl_worldnamenoextension", "", "name of current worldmodel without extension"}; +cvar_t cl_worldbasename = {CVAR_READONLY, "cl_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"}; + +cvar_t developer_networkentities = {0, "developer_networkentities", "0", "prints received entities, value is 0-4 (higher for more info)"}; +cvar_t cl_gameplayfix_soundsmovewithentities = {0, "cl_gameplayfix_soundsmovewithentities", "1", "causes sounds made by lifts, players, projectiles, and any other entities, to move with the entity, so for example a rocket noise follows the rocket rather than staying at the starting position"}; +cvar_t cl_sound_wizardhit = {0, "cl_sound_wizardhit", "wizard/hit.wav", "sound to play during TE_WIZSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_hknighthit = {0, "cl_sound_hknighthit", "hknight/hit.wav", "sound to play during TE_KNIGHTSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_tink1 = {0, "cl_sound_tink1", "weapons/tink1.wav", "sound to play with 80% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_ric1 = {0, "cl_sound_ric1", "weapons/ric1.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_ric2 = {0, "cl_sound_ric2", "weapons/ric2.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_ric3 = {0, "cl_sound_ric3", "weapons/ric3.wav", "sound to play with 10% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_readpicture_force = {0, "cl_readpicture_force", "0", "when enabled, the low quality pictures read by ReadPicture() are preferred over the high quality pictures on the file system"}; + +#define RIC_GUNSHOT 1 +#define RIC_GUNSHOTQUAD 2 +cvar_t cl_sound_ric_gunshot = {0, "cl_sound_ric_gunshot", "0", "specifies if and when the related cl_sound_ric and cl_sound_tink sounds apply to TE_GUNSHOT/TE_GUNSHOTQUAD, 0 = no sound, 1 = TE_GUNSHOT, 2 = TE_GUNSHOTQUAD, 3 = TE_GUNSHOT and TE_GUNSHOTQUAD"}; +cvar_t cl_sound_r_exp3 = {0, "cl_sound_r_exp3", "weapons/r_exp3.wav", "sound to play during TE_EXPLOSION and related effects (empty cvar disables sound)"}; +cvar_t cl_serverextension_download = {0, "cl_serverextension_download", "0", "indicates whether the server supports the download command"}; +cvar_t cl_joinbeforedownloadsfinish = {CVAR_SAVE, "cl_joinbeforedownloadsfinish", "1", "if non-zero the game will begin after the map is loaded before other downloads finish"}; +cvar_t cl_nettimesyncfactor = {CVAR_SAVE, "cl_nettimesyncfactor", "0", "rate at which client time adapts to match server time, 1 = instantly, 0.125 = slowly, 0 = not at all (bounding still applies)"}; +cvar_t cl_nettimesyncboundmode = {CVAR_SAVE, "cl_nettimesyncboundmode", "6", "method of restricting client time to valid values, 0 = no correction, 1 = tight bounding (jerky with packet loss), 2 = loose bounding (corrects it if out of bounds), 3 = leniant bounding (ignores temporary errors due to varying framerate), 4 = slow adjustment method from Quake3, 5 = slighttly nicer version of Quake3 method, 6 = bounding + Quake3"}; +cvar_t cl_nettimesyncboundtolerance = {CVAR_SAVE, "cl_nettimesyncboundtolerance", "0.25", "how much error is tolerated by bounding check, as a fraction of frametime, 0.25 = up to 25% margin of error tolerated, 1 = use only new time, 0 = use only old time (same effect as setting cl_nettimesyncfactor to 1)"}; +cvar_t cl_iplog_name = {CVAR_SAVE, "cl_iplog_name", "darkplaces_iplog.txt", "name of iplog file containing player addresses for iplog_list command and automatic ip logging when parsing status command"}; + +static qboolean QW_CL_CheckOrDownloadFile(const char *filename); +static void QW_CL_RequestNextDownload(void); +static void QW_CL_NextUpload(void); +void QW_CL_StartUpload(unsigned char *data, int size); +//static qboolean QW_CL_IsUploading(void); +static void QW_CL_StopUpload(void); +void CL_VM_UpdateIntermissionState(int intermission); +qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos); + +/* +================== +CL_ParseStartSoundPacket +================== +*/ +void CL_ParseStartSoundPacket(int largesoundindex) +{ + vec3_t pos; + int channel, ent; + int sound_num; + int volume; + int field_mask; + float attenuation; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + channel = MSG_ReadShort(); + + if (channel & (1<<15)) + volume = MSG_ReadByte (); + else + volume = DEFAULT_SOUND_PACKET_VOLUME; + + if (channel & (1<<14)) + attenuation = MSG_ReadByte () / 64.0; + else + attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + + ent = (channel>>3)&1023; + channel &= 7; + + sound_num = MSG_ReadByte (); + } + else + { + field_mask = MSG_ReadByte(); + + if (field_mask & SND_VOLUME) + volume = MSG_ReadByte (); + else + volume = DEFAULT_SOUND_PACKET_VOLUME; + + if (field_mask & SND_ATTENUATION) + attenuation = MSG_ReadByte () / 64.0; + else + attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + + if (field_mask & SND_LARGEENTITY) + { + ent = (unsigned short) MSG_ReadShort (); + channel = MSG_ReadChar (); + } + else + { + channel = (unsigned short) MSG_ReadShort (); + ent = channel >> 3; + channel &= 7; + } + + if (largesoundindex || (field_mask & SND_LARGESOUND) || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) + sound_num = (unsigned short) MSG_ReadShort (); + else + sound_num = MSG_ReadByte (); + } + + channel = CHAN_NET2ENGINE(channel); + + MSG_ReadVector(pos, cls.protocol); + + if (sound_num >= MAX_SOUNDS) + { + Con_Printf("CL_ParseStartSoundPacket: sound_num (%i) >= MAX_SOUNDS (%i)\n", sound_num, MAX_SOUNDS); + return; + } + + if (ent >= MAX_EDICTS) + { + Con_Printf("CL_ParseStartSoundPacket: ent = %i", ent); + return; + } + + if (ent >= cl.max_entities) + CL_ExpandEntities(ent); + + if( !CL_VM_Event_Sound(sound_num, volume / 255.0f, channel, attenuation, ent, pos) ) + S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0f, attenuation); +} + +/* +================== +CL_KeepaliveMessage + +When the client is taking a long time to load stuff, send keepalive messages +so the server doesn't disconnect. +================== +*/ + +static unsigned char olddata[NET_MAXMESSAGE]; +void CL_KeepaliveMessage (qboolean readmessages) +{ + float time; + static double nextmsg = -1; + static double nextupdate = -1; +#if 0 + static double lasttime = -1; +#endif + int oldreadcount; + qboolean oldbadread; + sizebuf_t old; + + if(cls.state != ca_dedicated) + { + if((time = Sys_DoubleTime()) >= nextupdate) + { + SCR_UpdateLoadingScreenIfShown(); + nextupdate = time + 2; + } + } + + // no need if server is local and definitely not if this is a demo + if (!cls.netcon || cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon >= SIGNONS) + return; + + if (readmessages) + { + // read messages from server, should just be nops + oldreadcount = msg_readcount; + oldbadread = msg_badread; + old = net_message; + memcpy(olddata, net_message.data, net_message.cursize); + + NetConn_ClientFrame(); + + msg_readcount = oldreadcount; + msg_badread = oldbadread; + net_message = old; + memcpy(net_message.data, olddata, net_message.cursize); + } + +#if 0 + if((time = Sys_DoubleTime()) >= lasttime + 1) + { + Con_Printf("long delta: %f\n", time - lasttime); + } + lasttime = Sys_DoubleTime(); +#endif + + if (cls.netcon && (time = Sys_DoubleTime()) >= nextmsg) + { + sizebuf_t msg; + unsigned char buf[4]; + nextmsg = time + 5; + // write out a nop + // LordHavoc: must use unreliable because reliable could kill the sigon message! + Con_Print("--> client to server keepalive\n"); + memset(&msg, 0, sizeof(msg)); + msg.data = buf; + msg.maxsize = sizeof(buf); + MSG_WriteChar(&msg, clc_nop); + NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, false); + } +} + +void CL_ParseEntityLump(char *entdata) +{ + const char *data; + char key[128], value[MAX_INPUTLINE]; + FOG_clear(); // LordHavoc: no fog until set + // LordHavoc: default to the map's sky (q3 shader parsing sets this) + R_SetSkyBox(cl.worldmodel->brush.skybox); + data = entdata; + if (!data) + return; + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + if (com_token[0] != '{') + return; // error + while (1) + { + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strlcpy (key, com_token + 1, sizeof (key)); + else + strlcpy (key, com_token, sizeof (key)); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + strlcpy (value, com_token, sizeof (value)); + if (!strcmp("sky", key)) + R_SetSkyBox(value); + else if (!strcmp("skyname", key)) // non-standard, introduced by QuakeForge... sigh. + R_SetSkyBox(value); + else if (!strcmp("qlsky", key)) // non-standard, introduced by QuakeLives (EEK) + R_SetSkyBox(value); + else if (!strcmp("fog", key)) + { + FOG_clear(); // so missing values get good defaults + r_refdef.fog_start = 0; + r_refdef.fog_alpha = 1; + r_refdef.fog_end = 16384; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + sscanf(value, "%f %f %f %f %f %f %f %f %f", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth); + } + else if (!strcmp("fog_density", key)) + r_refdef.fog_density = atof(value); + else if (!strcmp("fog_red", key)) + r_refdef.fog_red = atof(value); + else if (!strcmp("fog_green", key)) + r_refdef.fog_green = atof(value); + else if (!strcmp("fog_blue", key)) + r_refdef.fog_blue = atof(value); + else if (!strcmp("fog_alpha", key)) + r_refdef.fog_alpha = atof(value); + else if (!strcmp("fog_start", key)) + r_refdef.fog_start = atof(value); + else if (!strcmp("fog_end", key)) + r_refdef.fog_end = atof(value); + else if (!strcmp("fog_height", key)) + r_refdef.fog_height = atof(value); + else if (!strcmp("fog_fadedepth", key)) + r_refdef.fog_fadedepth = atof(value); + else if (!strcmp("fog_heighttexture", key)) + { + FOG_clear(); // so missing values get good defaults +#if _MSC_VER >= 1400 + sscanf_s(value, "%f %f %f %f %f %f %f %f %f %s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename, (unsigned int)sizeof(r_refdef.fog_height_texturename)); +#else + sscanf(value, "%f %f %f %f %f %f %f %f %f %63s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename); +#endif + r_refdef.fog_height_texturename[63] = 0; + } + } +} + +extern void CL_Locs_Reload_f(void); +extern void CL_VM_Init (void); +static const vec3_t defaultmins = {-4096, -4096, -4096}; +static const vec3_t defaultmaxs = {4096, 4096, 4096}; +static void CL_SetupWorldModel(void) +{ + // update the world model + cl.entities[0].render.model = cl.worldmodel = CL_GetModelByIndex(1); + CL_UpdateRenderEntity(&cl.entities[0].render); + + // make sure the cl.worldname and related cvars are set up now that we know the world model name + // set up csqc world for collision culling + if (cl.worldmodel) + { + strlcpy(cl.worldname, cl.worldmodel->name, sizeof(cl.worldname)); + FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, !strncmp(cl.worldnamenoextension, "maps/", 5) ? cl.worldnamenoextension + 5 : cl.worldnamenoextension, sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + World_SetSize(&cl.world, cl.worldname, cl.worldmodel->normalmins, cl.worldmodel->normalmaxs); + } + else + { + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldnamenoextension, ""); + Cvar_SetQuick(&cl_worldbasename, ""); + World_SetSize(&cl.world, "", defaultmins, defaultmaxs); + } + World_Start(&cl.world); + + // load or reload .loc file for team chat messages + CL_Locs_Reload_f(); + + // make sure we send enough keepalives + CL_KeepaliveMessage(false); + + // reset particles and other per-level things + R_Modules_NewMap(); + + // make sure we send enough keepalives + CL_KeepaliveMessage(false); + + // load the team chat beep if possible + cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav"); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // make menu know + MR_NewMap(); + + // load the csqc now + if (cl.loadcsqc) + { + cl.loadcsqc = false; + + CL_VM_Init(); + } +} + +static qboolean QW_CL_CheckOrDownloadFile(const char *filename) +{ + qfile_t *file; + + // see if the file already exists + file = FS_OpenVirtualFile(filename, true); + if (file) + { + FS_Close(file); + return true; + } + + // download messages in a demo would be bad + if (cls.demorecording) + { + Con_Printf("Unable to download \"%s\" when recording.\n", filename); + return true; + } + + // don't try to download when playing a demo + if (!cls.netcon) + return true; + + strlcpy(cls.qw_downloadname, filename, sizeof(cls.qw_downloadname)); + Con_Printf("Downloading %s\n", filename); + + if (!cls.qw_downloadmemory) + { + cls.qw_downloadmemory = NULL; + cls.qw_downloadmemorycursize = 0; + cls.qw_downloadmemorymaxsize = 1024*1024; // start out with a 1MB buffer + } + + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("download %s", filename)); + + cls.qw_downloadnumber++; + cls.qw_downloadpercent = 0; + cls.qw_downloadmemorycursize = 0; + + return false; +} + +static void QW_CL_ProcessUserInfo(int slot); +static void QW_CL_RequestNextDownload(void) +{ + int i; + + // clear name of file that just finished + cls.qw_downloadname[0] = 0; + + switch (cls.qw_downloadtype) + { + case dl_single: + break; + case dl_skin: + if (cls.qw_downloadnumber == 0) + Con_Printf("Checking skins...\n"); + for (;cls.qw_downloadnumber < cl.maxclients;cls.qw_downloadnumber++) + { + if (!cl.scores[cls.qw_downloadnumber].name[0]) + continue; + // check if we need to download the file, and return if so + if (!QW_CL_CheckOrDownloadFile(va("skins/%s.pcx", cl.scores[cls.qw_downloadnumber].qw_skin))) + return; + } + + cls.qw_downloadtype = dl_none; + + // load any newly downloaded skins + for (i = 0;i < cl.maxclients;i++) + QW_CL_ProcessUserInfo(i); + + // if we're still in signon stages, request the next one + if (cls.signon != SIGNONS) + { + cls.signon = SIGNONS-1; + // we'll go to SIGNONS when the first entity update is received + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("begin %i", cl.qw_servercount)); + } + break; + case dl_model: + if (cls.qw_downloadnumber == 0) + { + Con_Printf("Checking models...\n"); + cls.qw_downloadnumber = 1; + } + + for (;cls.qw_downloadnumber < MAX_MODELS && cl.model_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++) + { + // skip submodels + if (cl.model_name[cls.qw_downloadnumber][0] == '*') + continue; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/spike.mdl")) + cl.qw_modelindex_spike = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/player.mdl")) + cl.qw_modelindex_player = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/flag.mdl")) + cl.qw_modelindex_flag = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/s_explod.spr")) + cl.qw_modelindex_s_explod = cls.qw_downloadnumber; + // check if we need to download the file, and return if so + if (!QW_CL_CheckOrDownloadFile(cl.model_name[cls.qw_downloadnumber])) + return; + } + + cls.qw_downloadtype = dl_none; + + // touch all of the precached models that are still loaded so we can free + // anything that isn't needed + if (!sv.active) + Mod_ClearUsed(); + for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++) + Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL); + // precache any models used by the client (this also marks them used) + cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL); + cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL); + cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL); + cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL); + + // we purge the models and sounds later in CL_SignonReply + //Mod_PurgeUnused(); + + // now we try to load everything that is new + + // world model + cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, NULL); + if (cl.model_precache[1]->Draw == NULL) + Con_Printf("Map %s could not be found or downloaded\n", cl.model_name[1]); + + // normal models + for (i = 2;i < MAX_MODELS && cl.model_name[i][0];i++) + if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL))->Draw == NULL) + Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // now that we have a world model, set up the world entity, renderer + // modules and csqc + CL_SetupWorldModel(); + + // add pmodel/emodel CRCs to userinfo + CL_SetInfo("pmodel", va("%i", FS_CRCFile("progs/player.mdl", NULL)), true, true, true, true); + CL_SetInfo("emodel", va("%i", FS_CRCFile("progs/eyes.mdl", NULL)), true, true, true, true); + + // done checking sounds and models, send a prespawn command now + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("prespawn %i 0 %i", cl.qw_servercount, cl.model_precache[1]->brush.qw_md4sum2)); + + if (cls.qw_downloadmemory) + { + Mem_Free(cls.qw_downloadmemory); + cls.qw_downloadmemory = NULL; + } + + // done loading + cl.loadfinished = true; + break; + case dl_sound: + if (cls.qw_downloadnumber == 0) + { + Con_Printf("Checking sounds...\n"); + cls.qw_downloadnumber = 1; + } + + for (;cl.sound_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++) + { + // check if we need to download the file, and return if so + if (!QW_CL_CheckOrDownloadFile(va("sound/%s", cl.sound_name[cls.qw_downloadnumber]))) + return; + } + + cls.qw_downloadtype = dl_none; + + // clear sound usage flags for purging of unused sounds + S_ClearUsed(); + + // precache any sounds used by the client + cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true); + cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true); + cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true); + cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true); + cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true); + cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true); + cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true); + + // sounds used by the game + for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++) + cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true); + + // we purge the models and sounds later in CL_SignonReply + //S_PurgeUnused(); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // done with sound downloads, next we check models + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("modellist %i %i", cl.qw_servercount, 0)); + break; + case dl_none: + default: + Con_Printf("Unknown download type.\n"); + } +} + +static void QW_CL_ParseDownload(void) +{ + int size = (signed short)MSG_ReadShort(); + int percent = MSG_ReadByte(); + + //Con_Printf("download %i %i%% (%i/%i)\n", size, percent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize); + + // skip the download fragment if playing a demo + if (!cls.netcon) + { + if (size > 0) + msg_readcount += size; + return; + } + + if (size == -1) + { + Con_Printf("File not found.\n"); + QW_CL_RequestNextDownload(); + return; + } + + if (msg_readcount + (unsigned short)size > net_message.cursize) + Host_Error("corrupt download message\n"); + + // make sure the buffer is big enough to include this new fragment + if (!cls.qw_downloadmemory || cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size) + { + unsigned char *old; + while (cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size) + cls.qw_downloadmemorymaxsize *= 2; + old = cls.qw_downloadmemory; + cls.qw_downloadmemory = (unsigned char *)Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize); + if (old) + { + memcpy(cls.qw_downloadmemory, old, cls.qw_downloadmemorycursize); + Mem_Free(old); + } + } + + // read the fragment out of the packet + MSG_ReadBytes(size, cls.qw_downloadmemory + cls.qw_downloadmemorycursize); + cls.qw_downloadmemorycursize += size; + cls.qw_downloadspeedcount += size; + + cls.qw_downloadpercent = percent; + + if (percent != 100) + { + // request next fragment + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "nextdl"); + } + else + { + // finished file + Con_Printf("Downloaded \"%s\"\n", cls.qw_downloadname); + + FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + + cls.qw_downloadpercent = 0; + + // start downloading the next file (or join the game) + QW_CL_RequestNextDownload(); + } +} + +static void QW_CL_ParseModelList(void) +{ + int n; + int nummodels = MSG_ReadByte(); + char *str; + + // parse model precache list + for (;;) + { + str = MSG_ReadString(); + if (!str[0]) + break; + nummodels++; + if (nummodels==MAX_MODELS) + Host_Error("Server sent too many model precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); + strlcpy(cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); + } + + n = MSG_ReadByte(); + if (n) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("modellist %i %i", cl.qw_servercount, n)); + return; + } + + cls.signon = 2; + cls.qw_downloadnumber = 0; + cls.qw_downloadtype = dl_model; + QW_CL_RequestNextDownload(); +} + +static void QW_CL_ParseSoundList(void) +{ + int n; + int numsounds = MSG_ReadByte(); + char *str; + + // parse sound precache list + for (;;) + { + str = MSG_ReadString(); + if (!str[0]) + break; + numsounds++; + if (numsounds==MAX_SOUNDS) + Host_Error("Server sent too many sound precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); + strlcpy(cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); + } + + n = MSG_ReadByte(); + + if (n) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, n)); + return; + } + + cls.signon = 2; + cls.qw_downloadnumber = 0; + cls.qw_downloadtype = dl_sound; + QW_CL_RequestNextDownload(); +} + +static void QW_CL_Skins_f(void) +{ + cls.qw_downloadnumber = 0; + cls.qw_downloadtype = dl_skin; + QW_CL_RequestNextDownload(); +} + +static void QW_CL_Changing_f(void) +{ + if (cls.qw_downloadmemory) // don't change when downloading + return; + + S_StopAllSounds(); + cl.intermission = 0; + cls.signon = 1; // not active anymore, but not disconnected + Con_Printf("\nChanging map...\n"); +} + +void QW_CL_NextUpload(void) +{ + int r, percent, size; + + if (!cls.qw_uploaddata) + return; + + r = cls.qw_uploadsize - cls.qw_uploadpos; + if (r > 768) + r = 768; + size = min(1, cls.qw_uploadsize); + percent = (cls.qw_uploadpos+r)*100/size; + + MSG_WriteByte(&cls.netcon->message, qw_clc_upload); + MSG_WriteShort(&cls.netcon->message, r); + MSG_WriteByte(&cls.netcon->message, percent); + SZ_Write(&cls.netcon->message, cls.qw_uploaddata + cls.qw_uploadpos, r); + + Con_DPrintf("UPLOAD: %6d: %d written\n", cls.qw_uploadpos, r); + + cls.qw_uploadpos += r; + + if (cls.qw_uploadpos < cls.qw_uploadsize) + return; + + Con_Printf("Upload completed\n"); + + QW_CL_StopUpload(); +} + +void QW_CL_StartUpload(unsigned char *data, int size) +{ + // do nothing in demos or if not connected + if (!cls.netcon) + return; + + // abort existing upload if in progress + QW_CL_StopUpload(); + + Con_DPrintf("Starting upload of %d bytes...\n", size); + + cls.qw_uploaddata = (unsigned char *)Mem_Alloc(cls.permanentmempool, size); + memcpy(cls.qw_uploaddata, data, size); + cls.qw_uploadsize = size; + cls.qw_uploadpos = 0; + + QW_CL_NextUpload(); +} + +#if 0 +qboolean QW_CL_IsUploading(void) +{ + return cls.qw_uploaddata != NULL; +} +#endif + +void QW_CL_StopUpload(void) +{ + if (cls.qw_uploaddata) + Mem_Free(cls.qw_uploaddata); + cls.qw_uploaddata = NULL; + cls.qw_uploadsize = 0; + cls.qw_uploadpos = 0; +} + +static void QW_CL_ProcessUserInfo(int slot) +{ + int topcolor, bottomcolor; + char temp[2048]; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "name", cl.scores[slot].name, sizeof(cl.scores[slot].name)); + InfoString_GetValue(cl.scores[slot].qw_userinfo, "topcolor", temp, sizeof(temp));topcolor = atoi(temp); + InfoString_GetValue(cl.scores[slot].qw_userinfo, "bottomcolor", temp, sizeof(temp));bottomcolor = atoi(temp); + cl.scores[slot].colors = topcolor * 16 + bottomcolor; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "*spectator", temp, sizeof(temp)); + cl.scores[slot].qw_spectator = temp[0] != 0; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "team", cl.scores[slot].qw_team, sizeof(cl.scores[slot].qw_team)); + InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin)); + if (!cl.scores[slot].qw_skin[0]) + strlcpy(cl.scores[slot].qw_skin, "base", sizeof(cl.scores[slot].qw_skin)); + // TODO: skin cache +} + +static void QW_CL_UpdateUserInfo(void) +{ + int slot; + slot = MSG_ReadByte(); + if (slot >= cl.maxclients) + { + Con_Printf("svc_updateuserinfo >= cl.maxclients\n"); + MSG_ReadLong(); + MSG_ReadString(); + return; + } + cl.scores[slot].qw_userid = MSG_ReadLong(); + strlcpy(cl.scores[slot].qw_userinfo, MSG_ReadString(), sizeof(cl.scores[slot].qw_userinfo)); + + QW_CL_ProcessUserInfo(slot); +} + +static void QW_CL_SetInfo(void) +{ + int slot; + char key[2048]; + char value[2048]; + slot = MSG_ReadByte(); + strlcpy(key, MSG_ReadString(), sizeof(key)); + strlcpy(value, MSG_ReadString(), sizeof(value)); + if (slot >= cl.maxclients) + { + Con_Printf("svc_setinfo >= cl.maxclients\n"); + return; + } + InfoString_SetValue(cl.scores[slot].qw_userinfo, sizeof(cl.scores[slot].qw_userinfo), key, value); + + QW_CL_ProcessUserInfo(slot); +} + +static void QW_CL_ServerInfo(void) +{ + char key[2048]; + char value[2048]; + char temp[32]; + strlcpy(key, MSG_ReadString(), sizeof(key)); + strlcpy(value, MSG_ReadString(), sizeof(value)); + Con_DPrintf("SERVERINFO: %s=%s\n", key, value); + InfoString_SetValue(cl.qw_serverinfo, sizeof(cl.qw_serverinfo), key, value); + InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp)); + cl.qw_teamplay = atoi(temp); +} + +static void QW_CL_ParseNails(void) +{ + int i, j; + int numnails = MSG_ReadByte(); + vec_t *v; + unsigned char bits[6]; + for (i = 0;i < numnails;i++) + { + for (j = 0;j < 6;j++) + bits[j] = MSG_ReadByte(); + if (cl.qw_num_nails > 255) + continue; + v = cl.qw_nails[cl.qw_num_nails++]; + v[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096; + v[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096; + v[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096; + v[3] = -360*(bits[4]>>4)/16; + v[4] = 360*bits[5]/256; + v[5] = 0; + } +} + +static void CL_UpdateItemsAndWeapon(void) +{ + int j; + // check for important changes + + // set flash times + if (cl.olditems != cl.stats[STAT_ITEMS]) + for (j = 0;j < 32;j++) + if ((cl.stats[STAT_ITEMS] & (1<= 0 + && cl_serverextension_download.integer + && (FS_CRCFile(csqc_progname.string, &progsize) != csqc_progcrc.integer || ((int)progsize != csqc_progsize.integer && csqc_progsize.integer != -1)) + && !FS_FileExists(va("dlcache/%s.%i.%i", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer))) + { + Con_Printf("Downloading new CSQC code to dlcache/%s.%i.%i\n", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer); + if(cl_serverextension_download.integer == 2 && FS_HasZlib()) + Cmd_ForwardStringToServer(va("download %s deflate", csqc_progname.string)); + else + Cmd_ForwardStringToServer(va("download %s", csqc_progname.string)); + return; + } + } + + if (cl.loadmodel_current < cl.loadmodel_total) + { + // loading models + if(cl.loadmodel_current == 1) + { + // worldmodel counts as 16 models (15 + world model setup), for better progress bar + SCR_PushLoadingScreen(false, "Loading precached models", + ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) + ); + SCR_BeginLoadingPlaque(); + } + for (;cl.loadmodel_current < cl.loadmodel_total;cl.loadmodel_current++) + { + SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], + ( + (cl.loadmodel_current == 1) ? LOADPROGRESSWEIGHT_WORLDMODEL : LOADPROGRESSWEIGHT_MODEL + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) + ); + if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw) + { + SCR_PopLoadingScreen(false); + if(cl.loadmodel_current == 1) + { + SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], 1.0 / cl.loadmodel_total); + SCR_PopLoadingScreen(false); + } + continue; + } + CL_KeepaliveMessage(true); + + // if running a local game, calling Mod_ForName is a completely wasted effort... + if (sv.active) + cl.model_precache[cl.loadmodel_current] = sv.models[cl.loadmodel_current]; + else + { + if(cl.loadmodel_current == 1) + { + // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3 + Mod_FreeQ3Shaders(); + } + cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.model_name[cl.loadmodel_current][0] == '*' ? cl.model_name[1] : NULL); + } + SCR_PopLoadingScreen(false); + if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw && cl.loadmodel_current == 1) + { + // we now have the worldmodel so we can set up the game world + SCR_PushLoadingScreen(true, "world model setup", + ( + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) + ); + CL_SetupWorldModel(); + SCR_PopLoadingScreen(true); + if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) + { + cl.loadfinished = true; + // now issue the spawn to move on to signon 2 like normal + if (cls.netcon) + Cmd_ForwardStringToServer("prespawn"); + } + } + } + SCR_PopLoadingScreen(false); + // finished loading models + } + + if (cl.loadsound_current < cl.loadsound_total) + { + // loading sounds + if(cl.loadsound_current == 1) + SCR_PushLoadingScreen(false, "Loading precached sounds", + ( + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) + ); + for (;cl.loadsound_current < cl.loadsound_total;cl.loadsound_current++) + { + SCR_PushLoadingScreen(false, cl.sound_name[cl.loadsound_current], 1.0 / cl.loadsound_total); + if (cl.sound_precache[cl.loadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.loadsound_current])) + { + SCR_PopLoadingScreen(false); + continue; + } + CL_KeepaliveMessage(true); + cl.sound_precache[cl.loadsound_current] = S_PrecacheSound(cl.sound_name[cl.loadsound_current], false, true); + SCR_PopLoadingScreen(false); + } + SCR_PopLoadingScreen(false); + // finished loading sounds + } + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + Cvar_SetValueQuick(&cl_serverextension_download, false); + // in Nexuiz/Xonotic, the built in download protocol is kinda broken (misses lots + // of dependencies) anyway, and can mess around with the game directory; + // until this is fixed, only support pk3 downloads via curl, and turn off + // individual file downloads other than for CSQC + // on the other end of the download protocol, GAME_NEXUIZ/GAME_XONOTIC enforces writing + // to dlcache only + // idea: support download of pk3 files using this protocol later + + // note: the reason these loops skip already-loaded things is that it + // enables this command to be issued during the game if desired + + if (cl.downloadmodel_current < cl.loadmodel_total) + { + // loading models + + for (;cl.downloadmodel_current < cl.loadmodel_total;cl.downloadmodel_current++) + { + if (aborteddownload) + { + + if (cl.downloadmodel_current == 1) + { + // the worldmodel failed, but we need to set up anyway + Mod_FreeQ3Shaders(); + CL_SetupWorldModel(); + if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) + { + cl.loadfinished = true; + // now issue the spawn to move on to signon 2 like normal + if (cls.netcon) + Cmd_ForwardStringToServer("prespawn"); + } + } + aborteddownload = false; + continue; + } + if (cl.model_precache[cl.downloadmodel_current] && cl.model_precache[cl.downloadmodel_current]->Draw) + continue; + CL_KeepaliveMessage(true); + if (cl.model_name[cl.downloadmodel_current][0] != '*' && strcmp(cl.model_name[cl.downloadmodel_current], "null") && !FS_FileExists(cl.model_name[cl.downloadmodel_current])) + { + if (cl.downloadmodel_current == 1) + Con_Printf("Map %s not found\n", cl.model_name[cl.downloadmodel_current]); + else + Con_Printf("Model %s not found\n", cl.model_name[cl.downloadmodel_current]); + // regarding the * check: don't try to download submodels + if (cl_serverextension_download.integer && cls.netcon && cl.model_name[cl.downloadmodel_current][0] != '*' && !sv.active) + { + Cmd_ForwardStringToServer(va("download %s", cl.model_name[cl.downloadmodel_current])); + // we'll try loading again when the download finishes + return; + } + } + + if(cl.downloadmodel_current == 1) + { + // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3 + Mod_FreeQ3Shaders(); + } + + cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, false, cl.model_name[cl.downloadmodel_current][0] == '*' ? cl.model_name[1] : NULL); + if (cl.downloadmodel_current == 1) + { + // we now have the worldmodel so we can set up the game world + // or maybe we do not have it (cl_serverextension_download 0) + // then we need to continue loading ANYWAY! + CL_SetupWorldModel(); + if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) + { + cl.loadfinished = true; + // now issue the spawn to move on to signon 2 like normal + if (cls.netcon) + Cmd_ForwardStringToServer("prespawn"); + } + } + } + + // finished loading models + } + + if (cl.downloadsound_current < cl.loadsound_total) + { + // loading sounds + + for (;cl.downloadsound_current < cl.loadsound_total;cl.downloadsound_current++) + { + char soundname[MAX_QPATH]; + if (aborteddownload) + { + aborteddownload = false; + continue; + } + if (cl.sound_precache[cl.downloadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.downloadsound_current])) + continue; + CL_KeepaliveMessage(true); + dpsnprintf(soundname, sizeof(soundname), "sound/%s", cl.sound_name[cl.downloadsound_current]); + if (!FS_FileExists(soundname) && !FS_FileExists(cl.sound_name[cl.downloadsound_current])) + { + Con_Printf("Sound %s not found\n", soundname); + if (cl_serverextension_download.integer && cls.netcon && !sv.active) + { + Cmd_ForwardStringToServer(va("download %s", soundname)); + // we'll try loading again when the download finishes + return; + } + } + cl.sound_precache[cl.downloadsound_current] = S_PrecacheSound(cl.sound_name[cl.downloadsound_current], false, true); + } + + // finished loading sounds + } + + SCR_PopLoadingScreen(false); + + if (!cl.loadfinished) + { + cl.loadfinished = true; + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // now issue the spawn to move on to signon 2 like normal + if (cls.netcon) + Cmd_ForwardStringToServer("prespawn"); + } +} + +void CL_BeginDownloads_f(void) +{ + // prevent cl_begindownloads from being issued multiple times in one match + // to prevent accidentally cancelled downloads + if(cl.loadbegun) + Con_Printf("cl_begindownloads is only valid once per match\n"); + else + CL_BeginDownloads(false); +} + +void CL_StopDownload(int size, int crc) +{ + if (cls.qw_downloadmemory && cls.qw_downloadmemorycursize == size && CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize) == crc) + { + int existingcrc; + size_t existingsize; + const char *extension; + + if(cls.qw_download_deflate) + { + unsigned char *out; + size_t inflated_size; + out = FS_Inflate(cls.qw_downloadmemory, cls.qw_downloadmemorycursize, &inflated_size, tempmempool); + Mem_Free(cls.qw_downloadmemory); + if(out) + { + Con_Printf("Inflated download: new size: %u (%g%%)\n", (unsigned)inflated_size, 100.0 - 100.0*(cls.qw_downloadmemorycursize / (float)inflated_size)); + cls.qw_downloadmemory = out; + cls.qw_downloadmemorycursize = inflated_size; + } + else + { + cls.qw_downloadmemory = NULL; + cls.qw_downloadmemorycursize = 0; + Con_Printf("Cannot inflate download, possibly corrupt or zlib not present\n"); + } + } + + if(!cls.qw_downloadmemory) + { + Con_Printf("Download \"%s\" is corrupt (see above!)\n", cls.qw_downloadname); + } + else + { + crc = CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + size = cls.qw_downloadmemorycursize; + // finished file + // save to disk only if we don't already have it + // (this is mainly for playing back demos) + existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize); + if (existingsize || gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC || !strcmp(cls.qw_downloadname, csqc_progname.string)) + // let csprogs ALWAYS go to dlcache, to prevent "viral csprogs"; also, never put files outside dlcache for Nexuiz/Xonotic + { + if ((int)existingsize != size || existingcrc != crc) + { + // we have a mismatching file, pick another name for it + char name[MAX_QPATH*2]; + dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc); + if (!FS_FileExists(name)) + { + Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc); + FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + } + } + } + else + { + // we either don't have it or have a mismatching file... + // so it's time to accept the file + // but if we already have a mismatching file we need to rename + // this new one, and if we already have this file in renamed form, + // we do nothing + Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc); + FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + extension = FS_FileExtension(cls.qw_downloadname); + if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) + FS_Rescan(); + } + } + } + else if (cls.qw_downloadmemory && size) + { + Con_Printf("Download \"%s\" is corrupt (%i bytes, %i CRC, should be %i bytes, %i CRC), discarding\n", cls.qw_downloadname, size, crc, (int)cls.qw_downloadmemorycursize, (int)CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize)); + CL_BeginDownloads(true); + } + + if (cls.qw_downloadmemory) + Mem_Free(cls.qw_downloadmemory); + cls.qw_downloadmemory = NULL; + cls.qw_downloadname[0] = 0; + cls.qw_downloadmemorymaxsize = 0; + cls.qw_downloadmemorycursize = 0; + cls.qw_downloadpercent = 0; +} + +void CL_ParseDownload(void) +{ + int i, start, size; + static unsigned char data[NET_MAXMESSAGE]; + start = MSG_ReadLong(); + size = (unsigned short)MSG_ReadShort(); + + // record the start/size information to ack in the next input packet + for (i = 0;i < CL_MAX_DOWNLOADACKS;i++) + { + if (!cls.dp_downloadack[i].start && !cls.dp_downloadack[i].size) + { + cls.dp_downloadack[i].start = start; + cls.dp_downloadack[i].size = size; + break; + } + } + + MSG_ReadBytes(size, data); + + if (!cls.qw_downloadname[0]) + { + if (size > 0) + Con_Printf("CL_ParseDownload: received %i bytes with no download active\n", size); + return; + } + + if (start + size > cls.qw_downloadmemorymaxsize) + Host_Error("corrupt download message\n"); + + // only advance cursize if the data is at the expected position + // (gaps are unacceptable) + memcpy(cls.qw_downloadmemory + start, data, size); + cls.qw_downloadmemorycursize = start + size; + cls.qw_downloadpercent = (int)floor((start+size) * 100.0 / cls.qw_downloadmemorymaxsize); + cls.qw_downloadpercent = bound(0, cls.qw_downloadpercent, 100); + cls.qw_downloadspeedcount += size; +} + +void CL_DownloadBegin_f(void) +{ + int size = atoi(Cmd_Argv(1)); + + if (size < 0 || size > 1<<30 || FS_CheckNastyPath(Cmd_Argv(2), false)) + { + Con_Printf("cl_downloadbegin: received bogus information\n"); + CL_StopDownload(0, 0); + return; + } + + if (cls.qw_downloadname[0]) + Con_Printf("Download of %s aborted\n", cls.qw_downloadname); + + CL_StopDownload(0, 0); + + // we're really beginning a download now, so initialize stuff + strlcpy(cls.qw_downloadname, Cmd_Argv(2), sizeof(cls.qw_downloadname)); + cls.qw_downloadmemorymaxsize = size; + cls.qw_downloadmemory = (unsigned char *) Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize); + cls.qw_downloadnumber++; + + cls.qw_download_deflate = false; + if(Cmd_Argc() >= 4) + { + if(!strcmp(Cmd_Argv(3), "deflate")) + cls.qw_download_deflate = true; + // check further encodings here + } + + Cmd_ForwardStringToServer("sv_startdownload"); +} + +void CL_StopDownload_f(void) +{ + Curl_CancelAll(); + if (cls.qw_downloadname[0]) + { + Con_Printf("Download of %s aborted\n", cls.qw_downloadname); + CL_StopDownload(0, 0); + } + CL_BeginDownloads(true); +} + +void CL_DownloadFinished_f(void) +{ + if (Cmd_Argc() < 3) + { + Con_Printf("Malformed cl_downloadfinished command\n"); + return; + } + CL_StopDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2))); + CL_BeginDownloads(false); +} + +static void CL_SendPlayerInfo(void) +{ + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va("name \"%s\"", cl_name.string)); + + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va("color %i %i", cl_color.integer >> 4, cl_color.integer & 15)); + + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va("rate %i", cl_rate.integer)); + + if (cl_pmodel.integer) + { + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va("pmodel %i", cl_pmodel.integer)); + } + if (*cl_playermodel.string) + { + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va("playermodel %s", cl_playermodel.string)); + } + if (*cl_playerskin.string) + { + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va("playerskin %s", cl_playerskin.string)); + } +} + +/* +===================== +CL_SignonReply + +An svc_signonnum has been received, perform a client side setup +===================== +*/ +static void CL_SignonReply (void) +{ + Con_DPrintf("CL_SignonReply: %i\n", cls.signon); + + switch (cls.signon) + { + case 1: + if (cls.netcon) + { + // send player info before we begin downloads + // (so that the server can see the player name while downloading) + CL_SendPlayerInfo(); + + // execute cl_begindownloads next frame + // (after any commands added by svc_stufftext have been executed) + // when done with downloads the "prespawn" will be sent + Cbuf_AddText("\ncl_begindownloads\n"); + + //MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + //MSG_WriteString (&cls.netcon->message, "prespawn"); + } + else // playing a demo... make sure loading occurs as soon as possible + CL_BeginDownloads(false); + break; + + case 2: + if (cls.netcon) + { + // LordHavoc: quake sent the player info here but due to downloads + // it is sent earlier instead + // CL_SendPlayerInfo(); + + // LordHavoc: changed to begin a loading stage and issue this when done + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, "spawn"); + } + break; + + case 3: + if (cls.netcon) + { + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, "begin"); + } + break; + + case 4: + // after the level has been loaded, we shouldn't need the shaders, and + // if they are needed again they will be automatically loaded... + // we also don't need the unused models or sounds from the last level + Mod_FreeQ3Shaders(); + Mod_PurgeUnused(); + S_PurgeUnused(); + + Con_ClearNotify(); + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(true); + break; + } +} + +/* +================== +CL_ParseServerInfo +================== +*/ +void CL_ParseServerInfo (void) +{ + char *str; + int i; + protocolversion_t protocol; + int nummodels, numsounds; + + // if we start loading a level and a video is still playing, stop it + CL_VideoStop(); + + Con_DPrint("Serverinfo packet received.\n"); + Collision_Cache_Reset(true); + + // if server is active, we already began a loading plaque + if (!sv.active) + { + SCR_BeginLoadingPlaque(); + S_StopAllSounds(); + // free q3 shaders so that any newly downloaded shaders will be active + Mod_FreeQ3Shaders(); + } + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // clear cl_serverextension cvars + Cvar_SetValueQuick(&cl_serverextension_download, 0); + +// +// wipe the client_state_t struct +// + CL_ClearState (); + +// parse protocol version number + i = MSG_ReadLong (); + protocol = Protocol_EnumForNumber(i); + if (protocol == PROTOCOL_UNKNOWN) + { + Host_Error("CL_ParseServerInfo: Server is unrecognized protocol number (%i)", i); + return; + } + // hack for unmarked Nehahra movie demos which had a custom protocol + if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && gamemode == GAME_NEHAHRA) + protocol = PROTOCOL_NEHAHRAMOVIE; + cls.protocol = protocol; + Con_DPrintf("Server protocol is %s\n", Protocol_NameForEnum(cls.protocol)); + + cl.num_entities = 1; + + if (protocol == PROTOCOL_QUAKEWORLD) + { + char gamedir[1][MAX_QPATH]; + + cl.qw_servercount = MSG_ReadLong(); + + str = MSG_ReadString(); + Con_Printf("server gamedir is %s\n", str); + strlcpy(gamedir[0], str, sizeof(gamedir[0])); + + // change gamedir if needed + if (!FS_ChangeGameDirs(1, gamedir, true, false)) + Host_Error("CL_ParseServerInfo: unable to switch to server specified gamedir"); + + cl.gametype = GAME_DEATHMATCH; + cl.maxclients = 32; + + // parse player number + i = MSG_ReadByte(); + // cl.qw_spectator is an unneeded flag, cl.scores[cl.playerentity].qw_spectator works better (it can be updated by the server during the game) + //cl.qw_spectator = (i & 128) != 0; + cl.realplayerentity = cl.playerentity = cl.viewentity = (i & 127) + 1; + cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores)); + + // get the full level name + str = MSG_ReadString (); + strlcpy (cl.worldmessage, str, sizeof(cl.worldmessage)); + + // get the movevars that are defined in the qw protocol + cl.movevars_gravity = MSG_ReadFloat(); + cl.movevars_stopspeed = MSG_ReadFloat(); + cl.movevars_maxspeed = MSG_ReadFloat(); + cl.movevars_spectatormaxspeed = MSG_ReadFloat(); + cl.movevars_accelerate = MSG_ReadFloat(); + cl.movevars_airaccelerate = MSG_ReadFloat(); + cl.movevars_wateraccelerate = MSG_ReadFloat(); + cl.movevars_friction = MSG_ReadFloat(); + cl.movevars_waterfriction = MSG_ReadFloat(); + cl.movevars_entgravity = MSG_ReadFloat(); + + // other movevars not in the protocol... + cl.movevars_wallfriction = 0; + cl.movevars_timescale = 1; + cl.movevars_jumpvelocity = 270; + cl.movevars_edgefriction = 1; + cl.movevars_maxairspeed = 30; + cl.movevars_stepheight = 18; + cl.movevars_airaccel_qw = 1; + cl.movevars_airaccel_sideways_friction = 0; + + // seperate the printfs so the server message can have a color + Con_Printf("\n\n<===================================>\n\n\2%s\n", str); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + if (cls.netcon) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, 0)); + } + + cl.loadbegun = false; + cl.loadfinished = false; + + cls.state = ca_connected; + cls.signon = 1; + + // note: on QW protocol we can't set up the gameworld until after + // downloads finish... + // (we don't even know the name of the map yet) + // this also means cl_autodemo does not work on QW protocol... + + strlcpy(cl.worldname, "", sizeof(cl.worldname)); + strlcpy(cl.worldnamenoextension, "", sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, "qw", sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + } + else + { + // parse maxclients + cl.maxclients = MSG_ReadByte (); + if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) + { + Host_Error("Bad maxclients (%u) from server", cl.maxclients); + return; + } + cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores)); + + // parse gametype + cl.gametype = MSG_ReadByte (); + // the original id singleplayer demos are bugged and contain + // GAME_DEATHMATCH even for singleplayer + if (cl.maxclients == 1 && cls.protocol == PROTOCOL_QUAKE) + cl.gametype = GAME_COOP; + + // parse signon message + str = MSG_ReadString (); + strlcpy (cl.worldmessage, str, sizeof(cl.worldmessage)); + + // seperate the printfs so the server message can have a color + if (cls.protocol != PROTOCOL_NEHAHRAMOVIE) // no messages when playing the Nehahra movie + Con_Printf("\n<===================================>\n\n\2%s\n", str); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // parse model precache list + for (nummodels=1 ; ; nummodels++) + { + str = MSG_ReadString(); + if (!str[0]) + break; + if (nummodels==MAX_MODELS) + Host_Error ("Server sent too many model precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error ("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); + strlcpy (cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); + } + // parse sound precache list + for (numsounds=1 ; ; numsounds++) + { + str = MSG_ReadString(); + if (!str[0]) + break; + if (numsounds==MAX_SOUNDS) + Host_Error("Server sent too many sound precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); + strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); + } + + // set the base name for level-specific things... this gets updated again by CL_SetupWorldModel later + strlcpy(cl.worldname, cl.model_name[1], sizeof(cl.worldname)); + FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, !strncmp(cl.worldnamenoextension, "maps/", 5) ? cl.worldnamenoextension + 5 : cl.worldnamenoextension, sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + + // touch all of the precached models that are still loaded so we can free + // anything that isn't needed + if (!sv.active) + Mod_ClearUsed(); + for (i = 1;i < nummodels;i++) + Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL); + // precache any models used by the client (this also marks them used) + cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL); + cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL); + cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL); + cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL); + + // we purge the models and sounds later in CL_SignonReply + //Mod_PurgeUnused(); + //S_PurgeUnused(); + + // clear sound usage flags for purging of unused sounds + S_ClearUsed(); + + // precache any sounds used by the client + cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true); + cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true); + cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true); + cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true); + cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true); + cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true); + cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true); + + // sounds used by the game + for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++) + cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true); + + // now we try to load everything that is new + cl.loadmodel_current = 1; + cl.downloadmodel_current = 1; + cl.loadmodel_total = nummodels; + cl.loadsound_current = 1; + cl.downloadsound_current = 1; + cl.loadsound_total = numsounds; + cl.downloadcsqc = true; + cl.loadbegun = false; + cl.loadfinished = false; + cl.loadcsqc = true; + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // if cl_autodemo is set, automatically start recording a demo if one isn't being recorded already + if (cl_autodemo.integer && cls.netcon && cls.protocol != PROTOCOL_QUAKEWORLD) + { + char demofile[MAX_OSPATH]; + + if (cls.demorecording) + { + // finish the previous level's demo file + CL_Stop_f(); + } + + // start a new demo file + dpsnprintf (demofile, sizeof(demofile), "%s_%s.dem", Sys_TimeString (cl_autodemo_nameformat.string), cl.worldbasename); + + Con_Printf ("Auto-recording to %s.\n", demofile); + + // Reset bit 0 for every new demo + Cvar_SetValueQuick(&cl_autodemo_delete, + (cl_autodemo_delete.integer & ~0x1) + | + ((cl_autodemo_delete.integer & 0x2) ? 0x1 : 0) + ); + + cls.demofile = FS_OpenRealFile(demofile, "wb", false); + if (cls.demofile) + { + cls.forcetrack = -1; + FS_Printf (cls.demofile, "%i\n", cls.forcetrack); + cls.demorecording = true; + strlcpy(cls.demoname, demofile, sizeof(cls.demoname)); + cls.demo_lastcsprogssize = -1; + cls.demo_lastcsprogscrc = -1; + } + else + Con_Print ("ERROR: couldn't open.\n"); + } + } +} + +void CL_ValidateState(entity_state_t *s) +{ + dp_model_t *model; + + if (!s->active) + return; + + if (s->modelindex >= MAX_MODELS) + Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS); + + // these warnings are only warnings, no corrections are made to the state + // because states are often copied for decoding, which otherwise would + // propogate some of the corrections accidentally + // (this used to happen, sometimes affecting skin and frame) + + // colormap is client index + 1 + if (!(s->flags & RENDER_COLORMAPPED) && s->colormap > cl.maxclients) + Con_DPrintf("CL_ValidateState: colormap (%i) > cl.maxclients (%i)\n", s->colormap, cl.maxclients); + + if (developer_extra.integer) + { + model = CL_GetModelByIndex(s->modelindex); + if (model && model->type && s->frame >= model->numframes) + Con_DPrintf("CL_ValidateState: no such frame %i in \"%s\" (which has %i frames)\n", s->frame, model->name, model->numframes); + if (model && model->type && s->skin > 0 && s->skin >= model->numskins && !(s->lightpflags & PFLAGS_FULLDYNAMIC)) + Con_DPrintf("CL_ValidateState: no such skin %i in \"%s\" (which has %i skins)\n", s->skin, model->name, model->numskins); + } +} + +void CL_MoveLerpEntityStates(entity_t *ent) +{ + float odelta[3], adelta[3]; + VectorSubtract(ent->state_current.origin, ent->persistent.neworigin, odelta); + VectorSubtract(ent->state_current.angles, ent->persistent.newangles, adelta); + if (!ent->state_previous.active || ent->state_previous.modelindex != ent->state_current.modelindex) + { + // reset all persistent stuff if this is a new entity + ent->persistent.lerpdeltatime = 0; + ent->persistent.lerpstarttime = cl.mtime[1]; + VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); + VectorCopy(ent->state_current.angles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + // reset animation interpolation as well + ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; + ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; + ent->render.shadertime = cl.time; + // reset various persistent stuff + ent->persistent.muzzleflash = 0; + ent->persistent.trail_allowed = false; + } + else if ((ent->state_previous.effects & EF_TELEPORT_BIT) != (ent->state_current.effects & EF_TELEPORT_BIT)) + { + // don't interpolate the move + ent->persistent.lerpdeltatime = 0; + ent->persistent.lerpstarttime = cl.mtime[1]; + VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); + VectorCopy(ent->state_current.angles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + ent->persistent.trail_allowed = false; + + // if(ent->state_current.frame != ent->state_previous.frame) + // do this even if we did change the frame + // teleport bit is only used if an animation restart, or a jump, is necessary + // so it should be always harmless to do this + { + ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; + ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; + } + + // note that this case must do everything the following case does too + } + else if ((ent->state_previous.effects & EF_RESTARTANIM_BIT) != (ent->state_current.effects & EF_RESTARTANIM_BIT)) + { + ent->render.framegroupblend[1] = ent->render.framegroupblend[0]; + ent->render.framegroupblend[1].lerp = 1; + ent->render.framegroupblend[0].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = cl.time; + ent->render.framegroupblend[0].lerp = 0; + } + else if (DotProduct(odelta, odelta) > 1000*1000 + || (cl.fixangle[0] && !cl.fixangle[1]) + || (ent->state_previous.tagindex != ent->state_current.tagindex) + || (ent->state_previous.tagentity != ent->state_current.tagentity)) + { + // don't interpolate the move + // (the fixangle[] check detects teleports, but not constant fixangles + // such as when spectating) + ent->persistent.lerpdeltatime = 0; + ent->persistent.lerpstarttime = cl.mtime[1]; + VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); + VectorCopy(ent->state_current.angles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + ent->persistent.trail_allowed = false; + } + else if (ent->state_current.flags & RENDER_STEP) + { + // monster interpolation + if (DotProduct(odelta, odelta) + DotProduct(adelta, adelta) > 0.01) + { + ent->persistent.lerpdeltatime = bound(0, cl.mtime[1] - ent->persistent.lerpstarttime, 0.1); + ent->persistent.lerpstarttime = cl.mtime[1]; + VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin); + VectorCopy(ent->persistent.newangles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + } + } + else + { + // not a monster + ent->persistent.lerpstarttime = ent->state_previous.time; + // no lerp if it's singleplayer + if (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) + ent->persistent.lerpdeltatime = 0; + else + ent->persistent.lerpdeltatime = bound(0, ent->state_current.time - ent->state_previous.time, 0.1); + VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin); + VectorCopy(ent->persistent.newangles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + } + // trigger muzzleflash effect if necessary + if (ent->state_current.effects & EF_MUZZLEFLASH) + ent->persistent.muzzleflash = 1; + + // restart animation bit + if ((ent->state_previous.effects & EF_RESTARTANIM_BIT) != (ent->state_current.effects & EF_RESTARTANIM_BIT)) + { + ent->render.framegroupblend[1] = ent->render.framegroupblend[0]; + ent->render.framegroupblend[1].lerp = 1; + ent->render.framegroupblend[0].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = cl.time; + ent->render.framegroupblend[0].lerp = 0; + } +} + +/* +================== +CL_ParseBaseline +================== +*/ +void CL_ParseBaseline (entity_t *ent, int large) +{ + int i; + + ent->state_baseline = defaultstate; + // FIXME: set ent->state_baseline.number? + ent->state_baseline.active = true; + if (large) + { + ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort (); + ent->state_baseline.frame = (unsigned short) MSG_ReadShort (); + } + else if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) + { + ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort (); + ent->state_baseline.frame = MSG_ReadByte (); + } + else + { + ent->state_baseline.modelindex = MSG_ReadByte (); + ent->state_baseline.frame = MSG_ReadByte (); + } + ent->state_baseline.colormap = MSG_ReadByte(); + ent->state_baseline.skin = MSG_ReadByte(); + for (i = 0;i < 3;i++) + { + ent->state_baseline.origin[i] = MSG_ReadCoord(cls.protocol); + ent->state_baseline.angles[i] = MSG_ReadAngle(cls.protocol); + } + ent->state_previous = ent->state_current = ent->state_baseline; +} + + +/* +================== +CL_ParseClientdata + +Server information pertaining to this client only +================== +*/ +void CL_ParseClientdata (void) +{ + int i, bits; + + VectorCopy (cl.mpunchangle[0], cl.mpunchangle[1]); + VectorCopy (cl.mpunchvector[0], cl.mpunchvector[1]); + VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); + cl.mviewzoom[1] = cl.mviewzoom[0]; + + if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3 || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5) + { + cl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT; + cl.stats[STAT_ITEMS] = 0; + cl.stats[STAT_VIEWZOOM] = 255; + } + cl.idealpitch = 0; + cl.mpunchangle[0][0] = 0; + cl.mpunchangle[0][1] = 0; + cl.mpunchangle[0][2] = 0; + cl.mpunchvector[0][0] = 0; + cl.mpunchvector[0][1] = 0; + cl.mpunchvector[0][2] = 0; + cl.mvelocity[0][0] = 0; + cl.mvelocity[0][1] = 0; + cl.mvelocity[0][2] = 0; + cl.mviewzoom[0] = 1; + + bits = (unsigned short) MSG_ReadShort (); + if (bits & SU_EXTEND1) + bits |= (MSG_ReadByte() << 16); + if (bits & SU_EXTEND2) + bits |= (MSG_ReadByte() << 24); + + if (bits & SU_VIEWHEIGHT) + cl.stats[STAT_VIEWHEIGHT] = MSG_ReadChar (); + + if (bits & SU_IDEALPITCH) + cl.idealpitch = MSG_ReadChar (); + + for (i = 0;i < 3;i++) + { + if (bits & (SU_PUNCH1<= cl.max_static_entities) + Host_Error ("Too many static entities"); + ent = &cl.static_entities[cl.num_static_entities++]; + CL_ParseBaseline (ent, large); + + if (ent->state_baseline.modelindex == 0) + { + Con_DPrintf("svc_parsestatic: static entity without model at %f %f %f\n", ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2]); + cl.num_static_entities--; + // This is definitely a cheesy way to conserve resources... + return; + } + +// copy it to the current state + ent->render.model = CL_GetModelByIndex(ent->state_baseline.modelindex); + ent->render.framegroupblend[0].frame = ent->state_baseline.frame; + ent->render.framegroupblend[0].lerp = 1; + // make torchs play out of sync + ent->render.framegroupblend[0].start = lhrandom(-10, -1); + ent->render.skinnum = ent->state_baseline.skin; + ent->render.effects = ent->state_baseline.effects; + ent->render.alpha = 1; + + //VectorCopy (ent->state_baseline.origin, ent->render.origin); + //VectorCopy (ent->state_baseline.angles, ent->render.angles); + + Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2], ent->state_baseline.angles[0], ent->state_baseline.angles[1], ent->state_baseline.angles[2], 1); + ent->render.allowdecals = true; + CL_UpdateRenderEntity(&ent->render); +} + +/* +=================== +CL_ParseStaticSound +=================== +*/ +void CL_ParseStaticSound (int large) +{ + vec3_t org; + int sound_num, vol, atten; + + MSG_ReadVector(org, cls.protocol); + if (large || cls.protocol == PROTOCOL_NEHAHRABJP2) + sound_num = (unsigned short) MSG_ReadShort (); + else + sound_num = MSG_ReadByte (); + vol = MSG_ReadByte (); + atten = MSG_ReadByte (); + + S_StaticSound (cl.sound_precache[sound_num], org, vol/255.0f, atten); +} + +void CL_ParseEffect (void) +{ + vec3_t org; + int modelindex, startframe, framecount, framerate; + + MSG_ReadVector(org, cls.protocol); + modelindex = MSG_ReadByte (); + startframe = MSG_ReadByte (); + framecount = MSG_ReadByte (); + framerate = MSG_ReadByte (); + + CL_Effect(org, modelindex, startframe, framecount, framerate); +} + +void CL_ParseEffect2 (void) +{ + vec3_t org; + int modelindex, startframe, framecount, framerate; + + MSG_ReadVector(org, cls.protocol); + modelindex = (unsigned short) MSG_ReadShort (); + startframe = (unsigned short) MSG_ReadShort (); + framecount = MSG_ReadByte (); + framerate = MSG_ReadByte (); + + CL_Effect(org, modelindex, startframe, framecount, framerate); +} + +void CL_NewBeam (int ent, vec3_t start, vec3_t end, dp_model_t *m, int lightning) +{ + int i; + beam_t *b = NULL; + + if (ent >= MAX_EDICTS) + { + Con_Printf("CL_NewBeam: invalid entity number %i\n", ent); + ent = 0; + } + + if (ent >= cl.max_entities) + CL_ExpandEntities(ent); + + // override any beam with the same entity + i = cl.max_beams; + if (ent) + for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++) + if (b->entity == ent) + break; + // if the entity was not found then just replace an unused beam + if (i == cl.max_beams) + for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++) + if (!b->model) + break; + if (i < cl.max_beams) + { + cl.num_beams = max(cl.num_beams, i + 1); + b->entity = ent; + b->lightning = lightning; + b->model = m; + b->endtime = cl.mtime[0] + 0.2; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + } + else + Con_Print("beam list overflow!\n"); +} + +void CL_ParseBeam (dp_model_t *m, int lightning) +{ + int ent; + vec3_t start, end; + + ent = (unsigned short) MSG_ReadShort (); + MSG_ReadVector(start, cls.protocol); + MSG_ReadVector(end, cls.protocol); + + if (ent >= MAX_EDICTS) + { + Con_Printf("CL_ParseBeam: invalid entity number %i\n", ent); + ent = 0; + } + + CL_NewBeam(ent, start, end, m, lightning); +} + +void CL_ParseTempEntity(void) +{ + int type; + vec3_t pos, pos2; + vec3_t vel1, vel2; + vec3_t dir; + vec3_t color; + int rnd; + int colorStart, colorLength, count; + float velspeed, radius; + unsigned char *tempcolor; + matrix4x4_t tempmatrix; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + type = MSG_ReadByte(); + switch (type) + { + case QW_TE_WIZSPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); + break; + + case QW_TE_KNIGHTSPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); + break; + + case QW_TE_SPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case QW_TE_SUPERSPIKE: + // super spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + + case QW_TE_EXPLOSION: + // rocket explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + CL_Effect(pos, cl.qw_modelindex_s_explod, 0, 6, 10); + break; + + case QW_TE_TAREXPLOSION: + // tarbaby explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case QW_TE_LIGHTNING1: + // lightning bolts + CL_ParseBeam(cl.model_bolt, true); + break; + + case QW_TE_LIGHTNING2: + // lightning bolts + CL_ParseBeam(cl.model_bolt2, true); + break; + + case QW_TE_LIGHTNING3: + // lightning bolts + CL_ParseBeam(cl.model_bolt3, false); + break; + + case QW_TE_LAVASPLASH: + MSG_ReadVector(pos, cls.protocol); + CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case QW_TE_TELEPORT: + MSG_ReadVector(pos, cls.protocol); + CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case QW_TE_GUNSHOT: + // bullet hitting wall + radius = MSG_ReadByte(); + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + VectorSet(pos2, pos[0] + radius, pos[1] + radius, pos[2] + radius); + VectorSet(pos, pos[0] - radius, pos[1] - radius, pos[2] - radius); + CL_ParticleEffect(EFFECT_TE_GUNSHOT, radius, pos, pos2, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOT) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } + break; + + case QW_TE_BLOOD: + count = MSG_ReadByte(); + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case QW_TE_LIGHTNINGBLOOD: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_BLOOD, 2.5, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + default: + Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); + } + } + else + { + type = MSG_ReadByte(); + switch (type) + { + case TE_WIZSPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); + break; + + case TE_KNIGHTSPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); + break; + + case TE_SPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SPIKEQUAD: + // quad spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SPIKEQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SUPERSPIKE: + // super spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SUPERSPIKEQUAD: + // quad super spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKEQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + // LordHavoc: added for improved blood splatters + case TE_BLOOD: + // blood puff + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + dir[0] = MSG_ReadChar(); + dir[1] = MSG_ReadChar(); + dir[2] = MSG_ReadChar(); + count = MSG_ReadByte(); + CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos, dir, dir, NULL, 0); + break; + case TE_SPARK: + // spark shower + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + dir[0] = MSG_ReadChar(); + dir[1] = MSG_ReadChar(); + dir[2] = MSG_ReadChar(); + count = MSG_ReadByte(); + CL_ParticleEffect(EFFECT_TE_SPARK, count, pos, pos, dir, dir, NULL, 0); + break; + case TE_PLASMABURN: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_PLASMABURN, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + // LordHavoc: added for improved gore + case TE_BLOODSHOWER: + // vaporized body + MSG_ReadVector(pos, cls.protocol); // mins + MSG_ReadVector(pos2, cls.protocol); // maxs + velspeed = MSG_ReadCoord(cls.protocol); // speed + count = (unsigned short) MSG_ReadShort(); // number of particles + vel1[0] = -velspeed; + vel1[1] = -velspeed; + vel1[2] = -velspeed; + vel2[0] = velspeed; + vel2[1] = velspeed; + vel2[2] = velspeed; + CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos2, vel1, vel2, NULL, 0); + break; + + case TE_PARTICLECUBE: + // general purpose particle effect + MSG_ReadVector(pos, cls.protocol); // mins + MSG_ReadVector(pos2, cls.protocol); // maxs + MSG_ReadVector(dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(); // number of particles + colorStart = MSG_ReadByte(); // color + colorLength = MSG_ReadByte(); // gravity (1 or 0) + velspeed = MSG_ReadCoord(cls.protocol); // randomvel + CL_ParticleCube(pos, pos2, dir, count, colorStart, colorLength != 0, velspeed); + break; + + case TE_PARTICLERAIN: + // general purpose particle effect + MSG_ReadVector(pos, cls.protocol); // mins + MSG_ReadVector(pos2, cls.protocol); // maxs + MSG_ReadVector(dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(); // number of particles + colorStart = MSG_ReadByte(); // color + CL_ParticleRain(pos, pos2, dir, count, colorStart, 0); + break; + + case TE_PARTICLESNOW: + // general purpose particle effect + MSG_ReadVector(pos, cls.protocol); // mins + MSG_ReadVector(pos2, cls.protocol); // maxs + MSG_ReadVector(dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(); // number of particles + colorStart = MSG_ReadByte(); // color + CL_ParticleRain(pos, pos2, dir, count, colorStart, 1); + break; + + case TE_GUNSHOT: + // bullet hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_GUNSHOT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOT) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } + break; + + case TE_GUNSHOTQUAD: + // quad bullet hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_GUNSHOTQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOTQUAD) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } + break; + + case TE_EXPLOSION: + // rocket explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_EXPLOSIONQUAD: + // quad rocket explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSIONQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_EXPLOSION3: + // Nehahra movie colored lighting explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + color[0] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); + color[1] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); + color[2] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); + CL_ParticleExplosion(pos); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_EXPLOSIONRGB: + // colored lighting explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleExplosion(pos); + color[0] = MSG_ReadByte() * (2.0f / 255.0f); + color[1] = MSG_ReadByte() * (2.0f / 255.0f); + color[2] = MSG_ReadByte() * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_TAREXPLOSION: + // tarbaby explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_SMALLFLASH: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_SMALLFLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case TE_CUSTOMFLASH: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + radius = (MSG_ReadByte() + 1) * 8; + velspeed = (MSG_ReadByte() + 1) * (1.0 / 256.0); + color[0] = MSG_ReadByte() * (2.0f / 255.0f); + color[1] = MSG_ReadByte() * (2.0f / 255.0f); + color[2] = MSG_ReadByte() * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocLightFlash(NULL, &tempmatrix, radius, color[0], color[1], color[2], radius / velspeed, velspeed, 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + break; + + case TE_FLAMEJET: + MSG_ReadVector(pos, cls.protocol); + MSG_ReadVector(dir, cls.protocol); + count = MSG_ReadByte(); + CL_ParticleEffect(EFFECT_TE_FLAMEJET, count, pos, pos, dir, dir, NULL, 0); + break; + + case TE_LIGHTNING1: + // lightning bolts + CL_ParseBeam(cl.model_bolt, true); + break; + + case TE_LIGHTNING2: + // lightning bolts + CL_ParseBeam(cl.model_bolt2, true); + break; + + case TE_LIGHTNING3: + // lightning bolts + CL_ParseBeam(cl.model_bolt3, false); + break; + + // PGM 01/21/97 + case TE_BEAM: + // grappling hook beam + CL_ParseBeam(cl.model_beam, false); + break; + // PGM 01/21/97 + + // LordHavoc: for compatibility with the Nehahra movie... + case TE_LIGHTNING4NEH: + CL_ParseBeam(Mod_ForName(MSG_ReadString(), true, false, NULL), false); + break; + + case TE_LAVASPLASH: + MSG_ReadVector(pos, cls.protocol); + CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case TE_TELEPORT: + MSG_ReadVector(pos, cls.protocol); + CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case TE_EXPLOSION2: + // color mapped explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + colorStart = MSG_ReadByte(); + colorLength = MSG_ReadByte(); + CL_ParticleExplosion2(pos, colorStart, colorLength); + tempcolor = palette_rgb[(rand()%colorLength) + colorStart]; + color[0] = tempcolor[0] * (2.0f / 255.0f); + color[1] = tempcolor[1] * (2.0f / 255.0f); + color[2] = tempcolor[2] * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_TEI_G3: + MSG_ReadVector(pos, cls.protocol); + MSG_ReadVector(pos2, cls.protocol); + MSG_ReadVector(dir, cls.protocol); + CL_ParticleEffect(EFFECT_TE_TEI_G3, 1, pos, pos2, dir, dir, NULL, 0); + break; + + case TE_TEI_SMOKE: + MSG_ReadVector(pos, cls.protocol); + MSG_ReadVector(dir, cls.protocol); + count = MSG_ReadByte(); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_TEI_SMOKE, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case TE_TEI_BIGEXPLOSION: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_TEI_BIGEXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_TEI_PLASMAHIT: + MSG_ReadVector(pos, cls.protocol); + MSG_ReadVector(dir, cls.protocol); + count = MSG_ReadByte(); + CL_FindNonSolidLocation(pos, pos, 5); + CL_ParticleEffect(EFFECT_TE_TEI_PLASMAHIT, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + default: + Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); + } + } +} + +void CL_ParseTrailParticles(void) +{ + int entityindex; + int effectindex; + vec3_t start, end; + entityindex = (unsigned short)MSG_ReadShort(); + if (entityindex >= MAX_EDICTS) + entityindex = 0; + if (entityindex >= cl.max_entities) + CL_ExpandEntities(entityindex); + effectindex = (unsigned short)MSG_ReadShort(); + MSG_ReadVector(start, cls.protocol); + MSG_ReadVector(end, cls.protocol); + CL_ParticleEffect(effectindex, 1, start, end, vec3_origin, vec3_origin, entityindex > 0 ? cl.entities + entityindex : NULL, 0); +} + +void CL_ParsePointParticles(void) +{ + int effectindex, count; + vec3_t origin, velocity; + effectindex = (unsigned short)MSG_ReadShort(); + MSG_ReadVector(origin, cls.protocol); + MSG_ReadVector(velocity, cls.protocol); + count = (unsigned short)MSG_ReadShort(); + CL_ParticleEffect(effectindex, count, origin, origin, velocity, velocity, NULL, 0); +} + +void CL_ParsePointParticles1(void) +{ + int effectindex; + vec3_t origin; + effectindex = (unsigned short)MSG_ReadShort(); + MSG_ReadVector(origin, cls.protocol); + CL_ParticleEffect(effectindex, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0); +} + +typedef struct cl_iplog_item_s +{ + char *address; + char *name; +} +cl_iplog_item_t; + +static qboolean cl_iplog_loaded = false; +static int cl_iplog_numitems = 0; +static int cl_iplog_maxitems = 0; +static cl_iplog_item_t *cl_iplog_items; + +static void CL_IPLog_Load(void); +static void CL_IPLog_Add(const char *address, const char *name, qboolean checkexisting, qboolean addtofile) +{ + int i; + if (!address || !address[0] || !name || !name[0]) + return; + if (!cl_iplog_loaded) + CL_IPLog_Load(); + if (developer_extra.integer) + Con_DPrintf("CL_IPLog_Add(\"%s\", \"%s\", %i, %i);\n", address, name, checkexisting, addtofile); + // see if it already exists + if (checkexisting) + { + for (i = 0;i < cl_iplog_numitems;i++) + { + if (!strcmp(cl_iplog_items[i].address, address) && !strcmp(cl_iplog_items[i].name, name)) + { + if (developer_extra.integer) + Con_DPrintf("... found existing \"%s\" \"%s\"\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + return; + } + } + } + // it does not already exist in the iplog, so add it + if (cl_iplog_maxitems <= cl_iplog_numitems || !cl_iplog_items) + { + cl_iplog_item_t *olditems = cl_iplog_items; + cl_iplog_maxitems = max(1024, cl_iplog_maxitems + 256); + cl_iplog_items = (cl_iplog_item_t *) Mem_Alloc(cls.permanentmempool, cl_iplog_maxitems * sizeof(cl_iplog_item_t)); + if (olditems) + { + if (cl_iplog_numitems) + memcpy(cl_iplog_items, olditems, cl_iplog_numitems * sizeof(cl_iplog_item_t)); + Mem_Free(olditems); + } + } + cl_iplog_items[cl_iplog_numitems].address = (char *) Mem_Alloc(cls.permanentmempool, strlen(address) + 1); + cl_iplog_items[cl_iplog_numitems].name = (char *) Mem_Alloc(cls.permanentmempool, strlen(name) + 1); + strlcpy(cl_iplog_items[cl_iplog_numitems].address, address, strlen(address) + 1); + // TODO: maybe it would be better to strip weird characters from name when + // copying it here rather than using a straight strcpy? + strlcpy(cl_iplog_items[cl_iplog_numitems].name, name, strlen(name) + 1); + cl_iplog_numitems++; + if (addtofile) + { + // add it to the iplog.txt file + // TODO: this ought to open the one in the userpath version of the base + // gamedir, not the current gamedir + Log_Printf(cl_iplog_name.string, "%s %s\n", address, name); + if (developer_extra.integer) + Con_DPrintf("CL_IPLog_Add: appending this line to %s: %s %s\n", cl_iplog_name.string, address, name); + } +} + +static void CL_IPLog_Load(void) +{ + int i, len, linenumber; + char *text, *textend; + unsigned char *filedata; + fs_offset_t filesize; + char line[MAX_INPUTLINE]; + char address[MAX_INPUTLINE]; + cl_iplog_loaded = true; + // TODO: this ought to open the one in the userpath version of the base + // gamedir, not the current gamedir + filedata = FS_LoadFile(cl_iplog_name.string, tempmempool, true, &filesize); + if (!filedata) + return; + text = (char *)filedata; + textend = text + filesize; + for (linenumber = 1;text < textend;linenumber++) + { + for (len = 0;text < textend && *text != '\r' && *text != '\n';text++) + if (len < (int)sizeof(line) - 1) + line[len++] = *text; + line[len] = 0; + if (text < textend && *text == '\r' && text[1] == '\n') + text++; + if (text < textend && *text == '\n') + text++; + if (line[0] == '/' && line[1] == '/') + continue; // skip comments if anyone happens to add them + for (i = 0;i < len && !ISWHITESPACE(line[i]);i++) + address[i] = line[i]; + address[i] = 0; + // skip exactly one space character + i++; + // address contains the address with termination, + // line + i contains the name with termination + if (address[0] && line[i]) + CL_IPLog_Add(address, line + i, false, false); + else + Con_Printf("%s:%i: could not parse address and name:\n%s\n", cl_iplog_name.string, linenumber, line); + } +} + +static void CL_IPLog_List_f(void) +{ + int i, j; + const char *addressprefix; + if (Cmd_Argc() > 2) + { + Con_Printf("usage: %s 123.456.789.\n", Cmd_Argv(0)); + return; + } + addressprefix = ""; + if (Cmd_Argc() >= 2) + addressprefix = Cmd_Argv(1); + if (!cl_iplog_loaded) + CL_IPLog_Load(); + if (addressprefix && addressprefix[0]) + Con_Printf("Listing iplog addresses beginning with %s\n", addressprefix); + else + Con_Printf("Listing all iplog entries\n"); + Con_Printf("address name\n"); + for (i = 0;i < cl_iplog_numitems;i++) + { + if (addressprefix && addressprefix[0]) + { + for (j = 0;addressprefix[j];j++) + if (addressprefix[j] != cl_iplog_items[i].address[j]) + break; + // if this address does not begin with the addressprefix string + // simply omit it from the output + if (addressprefix[j]) + continue; + } + // if name is less than 15 characters, left justify it and pad + // if name is more than 15 characters, print all of it, not worrying + // about the fact it will misalign the columns + if (strlen(cl_iplog_items[i].address) < 15) + Con_Printf("%-15s %s\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + else + Con_Printf("%5s %s\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + } +} + +// look for anything interesting like player IP addresses or ping reports +qboolean CL_ExaminePrintString(const char *text) +{ + int len; + const char *t; + char temp[MAX_INPUTLINE]; + if (!strcmp(text, "Client ping times:\n")) + { + cl.parsingtextmode = CL_PARSETEXTMODE_PING; + // hide ping reports in demos + if (cls.demoplayback) + cl.parsingtextexpectingpingforscores = 1; + for(cl.parsingtextplayerindex = 0; cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0]; cl.parsingtextplayerindex++) + ; + if (cl.parsingtextplayerindex >= cl.maxclients) // should never happen, since the client itself should be in cl.scores + { + Con_Printf("ping reply but empty scoreboard?!?\n"); + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; + cl.parsingtextexpectingpingforscores = 0; + } + cl.parsingtextexpectingpingforscores = cl.parsingtextexpectingpingforscores ? 2 : 0; + return !cl.parsingtextexpectingpingforscores; + } + if (!strncmp(text, "host: ", 9)) + { + // cl.parsingtextexpectingpingforscores = false; // really? + cl.parsingtextmode = CL_PARSETEXTMODE_STATUS; + cl.parsingtextplayerindex = 0; + return true; + } + if (cl.parsingtextmode == CL_PARSETEXTMODE_PING) + { + // if anything goes wrong, we'll assume this is not a ping report + qboolean expected = cl.parsingtextexpectingpingforscores != 0; + cl.parsingtextexpectingpingforscores = 0; + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; + t = text; + while (*t == ' ') + t++; + if ((*t >= '0' && *t <= '9') || *t == '-') + { + int ping = atoi(t); + while ((*t >= '0' && *t <= '9') || *t == '-') + t++; + if (*t == ' ') + { + int charindex = 0; + t++; + if(cl.parsingtextplayerindex < cl.maxclients) + { + for (charindex = 0;cl.scores[cl.parsingtextplayerindex].name[charindex] == t[charindex];charindex++) + ; + // note: the matching algorithm stops at the end of the player name because some servers append text such as " READY" after the player name in the scoreboard but not in the ping report + //if (cl.scores[cl.parsingtextplayerindex].name[charindex] == 0 && t[charindex] == '\n') + if (t[charindex] == '\n') + { + cl.scores[cl.parsingtextplayerindex].qw_ping = bound(0, ping, 9999); + for (cl.parsingtextplayerindex++;cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0];cl.parsingtextplayerindex++) + ; + //if (cl.parsingtextplayerindex < cl.maxclients) // we could still get unconnecteds! + { + // we parsed a valid ping entry, so expect another to follow + cl.parsingtextmode = CL_PARSETEXTMODE_PING; + cl.parsingtextexpectingpingforscores = expected; + } + return !expected; + } + } + if (!strncmp(t, "unconnected\n", 12)) + { + // just ignore + cl.parsingtextmode = CL_PARSETEXTMODE_PING; + cl.parsingtextexpectingpingforscores = expected; + return !expected; + } + else + Con_DPrintf("player names '%s' and '%s' didn't match\n", cl.scores[cl.parsingtextplayerindex].name, t); + } + } + } + if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS) + { + if (!strncmp(text, "players: ", 9)) + { + cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID; + cl.parsingtextplayerindex = 0; + return true; + } + else if (!strstr(text, ": ")) + { + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; // status report ended + return true; + } + } + if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERID) + { + // if anything goes wrong, we'll assume this is not a status report + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; + if (text[0] == '#' && text[1] >= '0' && text[1] <= '9') + { + t = text + 1; + cl.parsingtextplayerindex = atoi(t) - 1; + while (*t >= '0' && *t <= '9') + t++; + if (*t == ' ') + { + cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERIP; + return true; + } + // the player name follows here, along with frags and time + } + } + if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERIP) + { + // if anything goes wrong, we'll assume this is not a status report + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; + if (text[0] == ' ') + { + t = text; + while (*t == ' ') + t++; + for (len = 0;*t && *t != '\n';t++) + if (len < (int)sizeof(temp) - 1) + temp[len++] = *t; + temp[len] = 0; + // botclient is perfectly valid, but we don't care about bots + // also don't try to look up the name of an invalid player index + if (strcmp(temp, "botclient") + && cl.parsingtextplayerindex >= 0 + && cl.parsingtextplayerindex < cl.maxclients + && cl.scores[cl.parsingtextplayerindex].name[0]) + { + // log the player name and IP address string + // (this operates entirely on strings to avoid issues with the + // nature of a network address) + CL_IPLog_Add(temp, cl.scores[cl.parsingtextplayerindex].name, true, true); + } + cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID; + return true; + } + } + return true; +} + +extern cvar_t slowmo; +extern cvar_t cl_lerpexcess; +extern void CSQC_UpdateNetworkTimes(double newtime, double oldtime); +static void CL_NetworkTimeReceived(double newtime) +{ + double timehigh; + cl.mtime[1] = cl.mtime[0]; + cl.mtime[0] = newtime; + if (cl_nolerp.integer || cls.timedemo || (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) || cl.mtime[1] == cl.mtime[0] || cls.signon < SIGNONS) + cl.time = cl.mtime[1] = newtime; + else if (cls.demoplayback) + { + // when time falls behind during demo playback it means the cl.mtime[1] was altered + // due to a large time gap, so treat it as an instant change in time + // (this can also happen during heavy packet loss in the demo) + if (cl.time < newtime - 0.1) + cl.mtime[1] = cl.time = newtime; + } + else if (cls.protocol != PROTOCOL_QUAKEWORLD) + { + cl.mtime[1] = max(cl.mtime[1], cl.mtime[0] - 0.1); + if (developer_extra.integer && vid_activewindow) + { + if (cl.time < cl.mtime[1] - (cl.mtime[0] - cl.mtime[1])) + Con_DPrintf("--- cl.time < cl.mtime[1] (%f < %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); + else if (cl.time > cl.mtime[0] + (cl.mtime[0] - cl.mtime[1])) + Con_DPrintf("--- cl.time > cl.mtime[0] (%f > %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); + } + cl.time += (cl.mtime[1] - cl.time) * bound(0, cl_nettimesyncfactor.value, 1); + timehigh = cl.mtime[1] + (cl.mtime[0] - cl.mtime[1]) * cl_nettimesyncboundtolerance.value; + if (cl_nettimesyncboundmode.integer == 1) + cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); + else if (cl_nettimesyncboundmode.integer == 2) + { + if (cl.time < cl.mtime[1] || cl.time > timehigh) + cl.time = cl.mtime[1]; + } + else if (cl_nettimesyncboundmode.integer == 3) + { + if ((cl.time < cl.mtime[1] && cl.oldtime < cl.mtime[1]) || (cl.time > timehigh && cl.oldtime > timehigh)) + cl.time = cl.mtime[1]; + } + else if (cl_nettimesyncboundmode.integer == 4) + { + if (fabs(cl.time - cl.mtime[1]) > 0.5) + cl.time = cl.mtime[1]; // reset + else if (fabs(cl.time - cl.mtime[1]) > 0.1) + cl.time += 0.5 * (cl.mtime[1] - cl.time); // fast + else if (cl.time > cl.mtime[1]) + cl.time -= 0.002 * cl.movevars_timescale; // fall into the past by 2ms + else + cl.time += 0.001 * cl.movevars_timescale; // creep forward 1ms + } + else if (cl_nettimesyncboundmode.integer == 5) + { + if (fabs(cl.time - cl.mtime[1]) > 0.5) + cl.time = cl.mtime[1]; // reset + else if (fabs(cl.time - cl.mtime[1]) > 0.1) + cl.time += 0.5 * (cl.mtime[1] - cl.time); // fast + else + cl.time = bound(cl.time - 0.002 * cl.movevars_timescale, cl.mtime[1], cl.time + 0.001 * cl.movevars_timescale); + } + else if (cl_nettimesyncboundmode.integer == 6) + { + cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); + cl.time = bound(cl.time - 0.002 * cl.movevars_timescale, cl.mtime[1], cl.time + 0.001 * cl.movevars_timescale); + } + } + // this packet probably contains a player entity update, so we will need + // to update the prediction + cl.movement_replay = true; + // this may get updated later in parsing by svc_clientdata + cl.onground = false; + // if true the cl.viewangles are interpolated from cl.mviewangles[] + // during this frame + // (makes spectating players much smoother and prevents mouse movement from turning) + cl.fixangle[1] = cl.fixangle[0]; + cl.fixangle[0] = false; + if (!cls.demoplayback) + VectorCopy(cl.mviewangles[0], cl.mviewangles[1]); + // update the csqc's server timestamps, critical for proper sync + CSQC_UpdateNetworkTimes(cl.mtime[0], cl.mtime[1]); + + if (cl.mtime[0] > cl.mtime[1]) + World_Physics_Frame(&cl.world, cl.mtime[0] - cl.mtime[1], cl.movevars_gravity); + + // only lerp entities that also get an update in this frame, when lerp excess is used + if(cl_lerpexcess.value > 0) + { + int i; + for (i = 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + entity_t *ent = cl.entities + i; + ent->persistent.lerpdeltatime = 0; + } + } + } +} + +#define SHOWNET(x) if(cl_shownet.integer==2)Con_Printf("%3i:%s(%i)\n", msg_readcount-1, x, cmd); + +//[515]: csqc +qboolean CL_VM_Parse_TempEntity (void); +void CL_VM_Parse_StuffCmd (const char *msg); +void CL_VM_Parse_CenterPrint (const char *msg); +void CSQC_AddPrintText (const char *msg); +void CSQC_ReadEntities (void); + +/* +===================== +CL_ParseServerMessage +===================== +*/ +int parsingerror = false; +extern void CL_UpdateMoveVars(void); +void CL_ParseServerMessage(void) +{ + int cmd; + int i; + protocolversion_t protocol; + unsigned char cmdlog[32]; + const char *cmdlogname[32], *temp; + int cmdindex, cmdcount = 0; + qboolean qwplayerupdatereceived; + qboolean strip_pqc; + + // LordHavoc: moved demo message writing from before the packet parse to + // after the packet parse so that CL_Stop_f can be called by cl_autodemo + // code in CL_ParseServerinfo + //if (cls.demorecording) + // CL_WriteDemoMessage (&net_message); + + cl.last_received_message = realtime; + + CL_KeepaliveMessage(false); + +// +// if recording demos, copy the message out +// + if (cl_shownet.integer == 1) + Con_Printf("%f %i\n", realtime, net_message.cursize); + else if (cl_shownet.integer == 2) + Con_Print("------------------\n"); + +// +// parse the message +// + //MSG_BeginReading (); + + parsingerror = true; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + CL_NetworkTimeReceived(realtime); // qw has no clock + + // kill all qw nails + cl.qw_num_nails = 0; + + // fade weapon view kick + cl.qw_weaponkick = min(cl.qw_weaponkick + 10 * bound(0, cl.time - cl.oldtime, 0.1), 0); + + cls.servermovesequence = cls.netcon->qw.incoming_sequence; + + qwplayerupdatereceived = false; + + while (1) + { + if (msg_badread) + Host_Error ("CL_ParseServerMessage: Bad QW server message"); + + cmd = MSG_ReadByte (); + + if (cmd == -1) + { + SHOWNET("END OF MESSAGE"); + break; // end of message + } + + cmdindex = cmdcount & 31; + cmdcount++; + cmdlog[cmdindex] = cmd; + + SHOWNET(qw_svc_strings[cmd]); + cmdlogname[cmdindex] = qw_svc_strings[cmd]; + if (!cmdlogname[cmdindex]) + { + // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) + temp = ""; + cmdlogname[cmdindex] = temp; + } + + // other commands + switch (cmd) + { + default: + { + char description[32*64], temp[64]; + int count; + strlcpy(description, "packet dump: ", sizeof(description)); + i = cmdcount - 32; + if (i < 0) + i = 0; + count = cmdcount - i; + i &= 31; + while(count > 0) + { + dpsnprintf(temp, sizeof(temp), "%3i:%s ", cmdlog[i], cmdlogname[i]); + strlcat(description, temp, sizeof(description)); + count--; + i++; + i &= 31; + } + description[strlen(description)-1] = '\n'; // replace the last space with a newline + Con_Print(description); + Host_Error("CL_ParseServerMessage: Illegible server message"); + } + break; + + case qw_svc_nop: + //Con_Printf("qw_svc_nop\n"); + break; + + case qw_svc_disconnect: + Con_Printf("Server disconnected\n"); + if (cls.demonum != -1) + CL_NextDemo(); + else + CL_Disconnect(); + return; + + case qw_svc_print: + i = MSG_ReadByte(); + temp = MSG_ReadString(); + if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports + { + if (i == 3) // chat + CSQC_AddPrintText(va("\1%s", temp)); //[515]: csqc + else + CSQC_AddPrintText(temp); + } + break; + + case qw_svc_centerprint: + CL_VM_Parse_CenterPrint(MSG_ReadString ()); //[515]: csqc + break; + + case qw_svc_stufftext: + CL_VM_Parse_StuffCmd(MSG_ReadString ()); //[515]: csqc + break; + + case qw_svc_damage: + // svc_damage protocol is identical to nq + V_ParseDamage (); + break; + + case qw_svc_serverdata: + //Cbuf_Execute(); // make sure any stuffed commands are done + CL_ParseServerInfo(); + break; + + case qw_svc_setangle: + for (i=0 ; i<3 ; i++) + cl.viewangles[i] = MSG_ReadAngle (cls.protocol); + if (!cls.demoplayback) + { + cl.fixangle[0] = true; + VectorCopy(cl.viewangles, cl.mviewangles[0]); + // disable interpolation if this is new + if (!cl.fixangle[1]) + VectorCopy(cl.viewangles, cl.mviewangles[1]); + } + break; + + case qw_svc_lightstyle: + i = MSG_ReadByte (); + if (i >= cl.max_lightstyle) + { + Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); + break; + } + strlcpy (cl.lightstyle[i].map, MSG_ReadString(), sizeof (cl.lightstyle[i].map)); + cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; + cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); + break; + + case qw_svc_sound: + CL_ParseStartSoundPacket(false); + break; + + case qw_svc_stopsound: + i = (unsigned short) MSG_ReadShort(); + S_StopSound(i>>3, i&7); + break; + + case qw_svc_updatefrags: + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); + cl.scores[i].frags = (signed short) MSG_ReadShort(); + break; + + case qw_svc_updateping: + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updateping >= cl.maxclients"); + cl.scores[i].qw_ping = MSG_ReadShort(); + break; + + case qw_svc_updatepl: + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatepl >= cl.maxclients"); + cl.scores[i].qw_packetloss = MSG_ReadByte(); + break; + + case qw_svc_updateentertime: + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updateentertime >= cl.maxclients"); + // seconds ago + cl.scores[i].qw_entertime = cl.time - MSG_ReadFloat(); + break; + + case qw_svc_spawnbaseline: + i = (unsigned short) MSG_ReadShort(); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl.max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline(cl.entities + i, false); + break; + case qw_svc_spawnstatic: + CL_ParseStatic(false); + break; + case qw_svc_temp_entity: + if(!CL_VM_Parse_TempEntity()) + CL_ParseTempEntity (); + break; + + case qw_svc_killedmonster: + cl.stats[STAT_MONSTERS]++; + break; + + case qw_svc_foundsecret: + cl.stats[STAT_SECRETS]++; + break; + + case qw_svc_updatestat: + i = MSG_ReadByte (); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadByte (); + break; + + case qw_svc_updatestatlong: + i = MSG_ReadByte (); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestatlong: %i is invalid", i); + cl.stats[i] = MSG_ReadLong (); + break; + + case qw_svc_spawnstaticsound: + CL_ParseStaticSound (false); + break; + + case qw_svc_cdtrack: + cl.cdtrack = cl.looptrack = MSG_ReadByte (); + if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) + CDAudio_Play ((unsigned char)cls.forcetrack, true); + else + CDAudio_Play ((unsigned char)cl.cdtrack, true); + break; + + case qw_svc_intermission: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 1; + MSG_ReadVector(cl.qw_intermission_origin, cls.protocol); + for (i = 0;i < 3;i++) + cl.qw_intermission_angles[i] = MSG_ReadAngle(cls.protocol); + break; + + case qw_svc_finale: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 2; + SCR_CenterPrint(MSG_ReadString ()); + break; + + case qw_svc_sellscreen: + Cmd_ExecuteString ("help", src_command); + break; + + case qw_svc_smallkick: + cl.qw_weaponkick = -2; + break; + case qw_svc_bigkick: + cl.qw_weaponkick = -4; + break; + + case qw_svc_muzzleflash: + i = (unsigned short) MSG_ReadShort(); + // NOTE: in QW this only worked on clients + if (i < 0 || i >= MAX_EDICTS) + Host_Error("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl.max_entities) + CL_ExpandEntities(i); + cl.entities[i].persistent.muzzleflash = 1.0f; + break; + + case qw_svc_updateuserinfo: + QW_CL_UpdateUserInfo(); + break; + + case qw_svc_setinfo: + QW_CL_SetInfo(); + break; + + case qw_svc_serverinfo: + QW_CL_ServerInfo(); + break; + + case qw_svc_download: + QW_CL_ParseDownload(); + break; + + case qw_svc_playerinfo: + // slightly kill qw player entities now that we know there is + // an update of player entities this frame... + if (!qwplayerupdatereceived) + { + qwplayerupdatereceived = true; + for (i = 1;i < cl.maxclients;i++) + cl.entities_active[i] = false; + } + EntityStateQW_ReadPlayerUpdate(); + break; + + case qw_svc_nails: + QW_CL_ParseNails(); + break; + + case qw_svc_chokecount: + i = MSG_ReadByte(); + // FIXME: apply to netgraph + //for (j = 0;j < i;j++) + // cl.frames[(cls.netcon->qw.incoming_acknowledged-1-j)&QW_UPDATE_MASK].receivedtime = -2; + break; + + case qw_svc_modellist: + QW_CL_ParseModelList(); + break; + + case qw_svc_soundlist: + QW_CL_ParseSoundList(); + break; + + case qw_svc_packetentities: + EntityFrameQW_CL_ReadFrame(false); + // first update is the final signon stage + if (cls.signon == SIGNONS - 1) + { + cls.signon = SIGNONS; + CL_SignonReply (); + } + break; + + case qw_svc_deltapacketentities: + EntityFrameQW_CL_ReadFrame(true); + // first update is the final signon stage + if (cls.signon == SIGNONS - 1) + { + cls.signon = SIGNONS; + CL_SignonReply (); + } + break; + + case qw_svc_maxspeed: + cl.movevars_maxspeed = MSG_ReadFloat(); + break; + + case qw_svc_entgravity: + cl.movevars_entgravity = MSG_ReadFloat(); + if (!cl.movevars_entgravity) + cl.movevars_entgravity = 1.0f; + break; + + case qw_svc_setpause: + cl.paused = MSG_ReadByte () != 0; + if (cl.paused) + CDAudio_Pause (); + else + CDAudio_Resume (); + S_PauseGameSounds (cl.paused); + break; + } + } + + if (qwplayerupdatereceived) + { + // fully kill any player entities that were not updated this frame + for (i = 1;i <= cl.maxclients;i++) + if (!cl.entities_active[i]) + cl.entities[i].state_current.active = false; + } + } + else + { + while (1) + { + if (msg_badread) + Host_Error ("CL_ParseServerMessage: Bad server message"); + + cmd = MSG_ReadByte (); + + if (cmd == -1) + { +// R_TimeReport("END OF MESSAGE"); + SHOWNET("END OF MESSAGE"); + break; // end of message + } + + cmdindex = cmdcount & 31; + cmdcount++; + cmdlog[cmdindex] = cmd; + + // if the high bit of the command byte is set, it is a fast update + if (cmd & 128) + { + // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) + temp = "entity"; + cmdlogname[cmdindex] = temp; + SHOWNET("fast update"); + if (cls.signon == SIGNONS - 1) + { + // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } + EntityFrameQuake_ReadEntity (cmd&127); + continue; + } + + SHOWNET(svc_strings[cmd]); + cmdlogname[cmdindex] = svc_strings[cmd]; + if (!cmdlogname[cmdindex]) + { + // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) + temp = ""; + cmdlogname[cmdindex] = temp; + } + + // other commands + switch (cmd) + { + default: + { + char description[32*64], temp[64]; + int count; + strlcpy (description, "packet dump: ", sizeof(description)); + i = cmdcount - 32; + if (i < 0) + i = 0; + count = cmdcount - i; + i &= 31; + while(count > 0) + { + dpsnprintf (temp, sizeof (temp), "%3i:%s ", cmdlog[i], cmdlogname[i]); + strlcat (description, temp, sizeof (description)); + count--; + i++; + i &= 31; + } + description[strlen(description)-1] = '\n'; // replace the last space with a newline + Con_Print(description); + Host_Error ("CL_ParseServerMessage: Illegible server message"); + } + break; + + case svc_nop: + if (cls.signon < SIGNONS) + Con_Print("<-- server to client keepalive\n"); + break; + + case svc_time: + CL_NetworkTimeReceived(MSG_ReadFloat()); + break; + + case svc_clientdata: + CL_ParseClientdata(); + break; + + case svc_version: + i = MSG_ReadLong (); + protocol = Protocol_EnumForNumber(i); + if (protocol == PROTOCOL_UNKNOWN) + Host_Error("CL_ParseServerMessage: Server is unrecognized protocol number (%i)", i); + // hack for unmarked Nehahra movie demos which had a custom protocol + if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && gamemode == GAME_NEHAHRA) + protocol = PROTOCOL_NEHAHRAMOVIE; + cls.protocol = protocol; + break; + + case svc_disconnect: + Con_Printf ("Server disconnected\n"); + if (cls.demonum != -1) + CL_NextDemo (); + else + CL_Disconnect (); + break; + + case svc_print: + temp = MSG_ReadString(); + if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports + CSQC_AddPrintText(temp); //[515]: csqc + break; + + case svc_centerprint: + CL_VM_Parse_CenterPrint(MSG_ReadString ()); //[515]: csqc + break; + + case svc_stufftext: + temp = MSG_ReadString(); + /* if(utf8_enable.integer) + { + strip_pqc = true; + // we can safely strip and even + // interpret these in utf8 mode + } + else */ switch(cls.protocol) + { + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + // maybe add other protocols if + // so desired, but not DP7 + strip_pqc = true; + break; + case PROTOCOL_DARKPLACES7: + default: + // ProQuake does not support + // these protocols + strip_pqc = false; + break; + } + if(strip_pqc) + { + // skip over ProQuake messages, + // TODO actually interpret them + // (they are sbar team score + // updates), see proquake cl_parse.c + if(*temp == 0x01) + { + ++temp; + while(*temp >= 0x01 && *temp <= 0x1F) + ++temp; + } + } + CL_VM_Parse_StuffCmd(temp); //[515]: csqc + break; + + case svc_damage: + V_ParseDamage (); + break; + + case svc_serverinfo: + CL_ParseServerInfo (); + break; + + case svc_setangle: + for (i=0 ; i<3 ; i++) + cl.viewangles[i] = MSG_ReadAngle (cls.protocol); + if (!cls.demoplayback) + { + cl.fixangle[0] = true; + VectorCopy(cl.viewangles, cl.mviewangles[0]); + // disable interpolation if this is new + if (!cl.fixangle[1]) + VectorCopy(cl.viewangles, cl.mviewangles[1]); + } + break; + + case svc_setview: + cl.viewentity = (unsigned short)MSG_ReadShort (); + if (cl.viewentity >= MAX_EDICTS) + Host_Error("svc_setview >= MAX_EDICTS"); + if (cl.viewentity >= cl.max_entities) + CL_ExpandEntities(cl.viewentity); + // LordHavoc: assume first setview recieved is the real player entity + if (!cl.realplayerentity) + cl.realplayerentity = cl.viewentity; + // update cl.playerentity to this one if it is a valid player + if (cl.viewentity >= 1 && cl.viewentity <= cl.maxclients) + cl.playerentity = cl.viewentity; + break; + + case svc_lightstyle: + i = MSG_ReadByte (); + if (i >= cl.max_lightstyle) + { + Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); + break; + } + strlcpy (cl.lightstyle[i].map, MSG_ReadString(), sizeof (cl.lightstyle[i].map)); + cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; + cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); + break; + + case svc_sound: + CL_ParseStartSoundPacket(false); + break; + + case svc_precache: + if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) + { + // was svc_sound2 in protocols 1, 2, 3, removed in 4, 5, changed to svc_precache in 6 + CL_ParseStartSoundPacket(true); + } + else + { + int i = (unsigned short)MSG_ReadShort(); + char *s = MSG_ReadString(); + if (i < 32768) + { + if (i >= 1 && i < MAX_MODELS) + { + dp_model_t *model = Mod_ForName(s, false, false, s[0] == '*' ? cl.model_name[1] : NULL); + if (!model) + Con_DPrintf("svc_precache: Mod_ForName(\"%s\") failed\n", s); + cl.model_precache[i] = model; + } + else + Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_MODELS); + } + else + { + i -= 32768; + if (i >= 1 && i < MAX_SOUNDS) + { + sfx_t *sfx = S_PrecacheSound (s, true, true); + if (!sfx && snd_initialized.integer) + Con_DPrintf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); + cl.sound_precache[i] = sfx; + } + else + Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_SOUNDS); + } + } + break; + + case svc_stopsound: + i = (unsigned short) MSG_ReadShort(); + S_StopSound(i>>3, i&7); + break; + + case svc_updatename: + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatename >= cl.maxclients"); + strlcpy (cl.scores[i].name, MSG_ReadString (), sizeof (cl.scores[i].name)); + break; + + case svc_updatefrags: + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); + cl.scores[i].frags = (signed short) MSG_ReadShort (); + break; + + case svc_updatecolors: + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatecolors >= cl.maxclients"); + cl.scores[i].colors = MSG_ReadByte (); + break; + + case svc_particle: + CL_ParseParticleEffect (); + break; + + case svc_effect: + CL_ParseEffect (); + break; + + case svc_effect2: + CL_ParseEffect2 (); + break; + + case svc_spawnbaseline: + i = (unsigned short) MSG_ReadShort (); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl.max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline (cl.entities + i, false); + break; + case svc_spawnbaseline2: + i = (unsigned short) MSG_ReadShort (); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline2: invalid entity number %i", i); + if (i >= cl.max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline (cl.entities + i, true); + break; + case svc_spawnstatic: + CL_ParseStatic (false); + break; + case svc_spawnstatic2: + CL_ParseStatic (true); + break; + case svc_temp_entity: + if(!CL_VM_Parse_TempEntity()) + CL_ParseTempEntity (); + break; + + case svc_setpause: + cl.paused = MSG_ReadByte () != 0; + if (cl.paused) + CDAudio_Pause (); + else + CDAudio_Resume (); + S_PauseGameSounds (cl.paused); + break; + + case svc_signonnum: + i = MSG_ReadByte (); + // LordHavoc: it's rude to kick off the client if they missed the + // reconnect somehow, so allow signon 1 even if at signon 1 + if (i <= cls.signon && i != 1) + Host_Error ("Received signon %i when at %i", i, cls.signon); + cls.signon = i; + CL_SignonReply (); + break; + + case svc_killedmonster: + cl.stats[STAT_MONSTERS]++; + break; + + case svc_foundsecret: + cl.stats[STAT_SECRETS]++; + break; + + case svc_updatestat: + i = MSG_ReadByte (); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadLong (); + break; + + case svc_updatestatubyte: + i = MSG_ReadByte (); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadByte (); + break; + + case svc_spawnstaticsound: + CL_ParseStaticSound (false); + break; + + case svc_spawnstaticsound2: + CL_ParseStaticSound (true); + break; + + case svc_cdtrack: + cl.cdtrack = MSG_ReadByte (); + cl.looptrack = MSG_ReadByte (); + if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) + CDAudio_Play ((unsigned char)cls.forcetrack, true); + else + CDAudio_Play ((unsigned char)cl.cdtrack, true); + break; + + case svc_intermission: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 1; + CL_VM_UpdateIntermissionState(cl.intermission); + break; + + case svc_finale: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 2; + CL_VM_UpdateIntermissionState(cl.intermission); + SCR_CenterPrint(MSG_ReadString ()); + break; + + case svc_cutscene: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 3; + CL_VM_UpdateIntermissionState(cl.intermission); + SCR_CenterPrint(MSG_ReadString ()); + break; + + case svc_sellscreen: + Cmd_ExecuteString ("help", src_command); + break; + case svc_hidelmp: + if (gamemode == GAME_TENEBRAE) + { + // repeating particle effect + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + (void) MSG_ReadByte(); + MSG_ReadLong(); + MSG_ReadLong(); + MSG_ReadString(); + } + else + SHOWLMP_decodehide(); + break; + case svc_showlmp: + if (gamemode == GAME_TENEBRAE) + { + // particle effect + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + (void) MSG_ReadByte(); + MSG_ReadString(); + } + else + SHOWLMP_decodeshow(); + break; + case svc_skybox: + R_SetSkyBox(MSG_ReadString()); + break; + case svc_entities: + if (cls.signon == SIGNONS - 1) + { + // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } + if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) + EntityFrame_CL_ReadFrame(); + else if (cls.protocol == PROTOCOL_DARKPLACES4) + EntityFrame4_CL_ReadFrame(); + else + EntityFrame5_CL_ReadFrame(); + break; + case svc_csqcentities: + CSQC_ReadEntities(); + break; + case svc_downloaddata: + CL_ParseDownload(); + break; + case svc_trailparticles: + CL_ParseTrailParticles(); + break; + case svc_pointparticles: + CL_ParsePointParticles(); + break; + case svc_pointparticles1: + CL_ParsePointParticles1(); + break; + } +// R_TimeReport(svc_strings[cmd]); + } + } + + if (cls.signon == SIGNONS) + CL_UpdateItemsAndWeapon(); +// R_TimeReport("UpdateItems"); + + EntityFrameQuake_ISeeDeadEntities(); +// R_TimeReport("ISeeDeadEntities"); + + CL_UpdateMoveVars(); +// R_TimeReport("UpdateMoveVars"); + + parsingerror = false; + + // LordHavoc: this was at the start of the function before cl_autodemo was + // implemented + if (cls.demorecording) + { + CL_WriteDemoMessage (&net_message); +// R_TimeReport("WriteDemo"); + } +} + +void CL_Parse_DumpPacket(void) +{ + if (!parsingerror) + return; + Con_Print("Packet dump:\n"); + SZ_HexDumpToConsole(&net_message); + parsingerror = false; +} + +void CL_Parse_ErrorCleanUp(void) +{ + CL_StopDownload(0, 0); + QW_CL_StopUpload(); +} + +void CL_Parse_Init(void) +{ + Cvar_RegisterVariable(&cl_worldmessage); + Cvar_RegisterVariable(&cl_worldname); + Cvar_RegisterVariable(&cl_worldnamenoextension); + Cvar_RegisterVariable(&cl_worldbasename); + + Cvar_RegisterVariable(&developer_networkentities); + Cvar_RegisterVariable(&cl_gameplayfix_soundsmovewithentities); + + Cvar_RegisterVariable(&cl_sound_wizardhit); + Cvar_RegisterVariable(&cl_sound_hknighthit); + Cvar_RegisterVariable(&cl_sound_tink1); + Cvar_RegisterVariable(&cl_sound_ric1); + Cvar_RegisterVariable(&cl_sound_ric2); + Cvar_RegisterVariable(&cl_sound_ric3); + Cvar_RegisterVariable(&cl_sound_ric_gunshot); + Cvar_RegisterVariable(&cl_sound_r_exp3); + + Cvar_RegisterVariable(&cl_joinbeforedownloadsfinish); + + // server extension cvars set by commands issued from the server during connect + Cvar_RegisterVariable(&cl_serverextension_download); + + Cvar_RegisterVariable(&cl_nettimesyncfactor); + Cvar_RegisterVariable(&cl_nettimesyncboundmode); + Cvar_RegisterVariable(&cl_nettimesyncboundtolerance); + Cvar_RegisterVariable(&cl_iplog_name); + Cvar_RegisterVariable(&cl_readpicture_force); + + Cmd_AddCommand("nextul", QW_CL_NextUpload, "sends next fragment of current upload buffer (screenshot for example)"); + Cmd_AddCommand("stopul", QW_CL_StopUpload, "aborts current upload (screenshot for example)"); + Cmd_AddCommand("skins", QW_CL_Skins_f, "downloads missing qw skins from server"); + Cmd_AddCommand("changing", QW_CL_Changing_f, "sent by qw servers to tell client to wait for level change"); + Cmd_AddCommand("cl_begindownloads", CL_BeginDownloads_f, "used internally by darkplaces client while connecting (causes loading of models and sounds or triggers downloads for missing ones)"); + Cmd_AddCommand("cl_downloadbegin", CL_DownloadBegin_f, "(networking) informs client of download file information, client replies with sv_startsoundload to begin the transfer"); + Cmd_AddCommand("stopdownload", CL_StopDownload_f, "terminates a download"); + Cmd_AddCommand("cl_downloadfinished", CL_DownloadFinished_f, "signals that a download has finished and provides the client with file size and crc to check its integrity"); + Cmd_AddCommand("iplog_list", CL_IPLog_List_f, "lists names of players whose IP address begins with the supplied text (example: iplog_list 123.456.789)"); +} + +void CL_Parse_Shutdown(void) +{ +} diff --git a/misc/source/darkplaces-src/cl_particles.c b/misc/source/darkplaces-src/cl_particles.c new file mode 100644 index 00000000..6c28e575 --- /dev/null +++ b/misc/source/darkplaces-src/cl_particles.c @@ -0,0 +1,3014 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" + +#include "cl_collision.h" +#include "image.h" +#include "r_shadow.h" + +// must match ptype_t values +particletype_t particletype[pt_total] = +{ + {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen) + {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic + {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static + {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark + {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam + {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain + {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal + {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow + {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble + {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood + {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke + {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal + {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle +}; + +#define PARTICLEEFFECT_UNDERWATER 1 +#define PARTICLEEFFECT_NOTUNDERWATER 2 + +typedef struct particleeffectinfo_s +{ + int effectnameindex; // which effect this belongs to + // PARTICLEEFFECT_* bits + int flags; + // blood effects may spawn very few particles, so proper fraction-overflow + // handling is very important, this variable keeps track of the fraction + double particleaccumulator; + // the math is: countabsolute + requestedcount * countmultiplier * quality + // absolute number of particles to spawn, often used for decals + // (unaffected by quality and requestedcount) + float countabsolute; + // multiplier for the number of particles CL_ParticleEffect was told to + // spawn, most effects do not really have a count and hence use 1, so + // this is often the actual count to spawn, not merely a multiplier + float countmultiplier; + // if > 0 this causes the particle to spawn in an evenly spaced line from + // originmins to originmaxs (causing them to describe a trail, not a box) + float trailspacing; + // type of particle to spawn (defines some aspects of behavior) + ptype_t particletype; + // blending mode used on this particle type + pblend_t blendmode; + // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc) + porientation_t orientation; + // range of colors to choose from in hex RRGGBB (like HTML color tags), + // randomly interpolated at spawn + unsigned int color[2]; + // a random texture is chosen in this range (note the second value is one + // past the last choosable, so for example 8,16 chooses any from 8 up and + // including 15) + // if start and end of the range are the same, no randomization is done + int tex[2]; + // range of size values randomly chosen when spawning, plus size increase over time + float size[3]; + // range of alpha values randomly chosen when spawning, plus alpha fade + float alpha[3]; + // how long the particle should live (note it is also removed if alpha drops to 0) + float time[2]; + // how much gravity affects this particle (negative makes it fly up!) + float gravity; + // how much bounce the particle has when it hits a surface + // if negative the particle is removed on impact + float bounce; + // if in air this friction is applied + // if negative the particle accelerates + float airfriction; + // if in liquid (water/slime/lava) this friction is applied + // if negative the particle accelerates + float liquidfriction; + // these offsets are added to the values given to particleeffect(), and + // then an ellipsoid-shaped jitter is added as defined by these + // (they are the 3 radii) + float stretchfactor; + // stretch velocity factor (used for sparks) + float originoffset[3]; + float velocityoffset[3]; + float originjitter[3]; + float velocityjitter[3]; + float velocitymultiplier; + // an effect can also spawn a dlight + float lightradiusstart; + float lightradiusfade; + float lighttime; + float lightcolor[3]; + qboolean lightshadow; + int lightcubemapnum; + unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color! + int staintex[2]; + float stainalpha[2]; + float stainsize[2]; + // other parameters + float rotate[4]; // min/max base angle, min/max rotation over time +} +particleeffectinfo_t; + +char particleeffectname[MAX_PARTICLEEFFECTNAME][64]; + +int numparticleeffectinfo; +particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO]; + +static int particlepalette[256]; +/* + 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7 + 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15 + 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23 + 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31 + 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39 + 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47 + 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55 + 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63 + 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71 + 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79 + 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87 + 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95 + 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103 + 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111 + 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119 + 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127 + 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135 + 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143 + 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151 + 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159 + 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167 + 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175 + 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183 + 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191 + 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199 + 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207 + 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215 + 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223 + 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231 + 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239 + 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247 + 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255 +*/ + +int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61}; +int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66}; +int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3}; + +//static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff}; + +// particletexture_t is a rectangle in the particlefonttexture +typedef struct particletexture_s +{ + rtexture_t *texture; + float s1, t1, s2, t2; +} +particletexture_t; + +static rtexturepool_t *particletexturepool; +static rtexture_t *particlefonttexture; +static particletexture_t particletexture[MAX_PARTICLETEXTURES]; +skinframe_t *decalskinframe; + +// texture numbers in particle font +static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7}; +static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15}; +static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23}; +static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31}; +static const int tex_rainsplash = 32; +static const int tex_particle = 63; +static const int tex_bubble = 62; +static const int tex_raindrop = 61; +static const int tex_beam = 60; + +particleeffectinfo_t baselineparticleeffectinfo = +{ + 0, //int effectnameindex; // which effect this belongs to + // PARTICLEEFFECT_* bits + 0, //int flags; + // blood effects may spawn very few particles, so proper fraction-overflow + // handling is very important, this variable keeps track of the fraction + 0.0, //double particleaccumulator; + // the math is: countabsolute + requestedcount * countmultiplier * quality + // absolute number of particles to spawn, often used for decals + // (unaffected by quality and requestedcount) + 0.0f, //float countabsolute; + // multiplier for the number of particles CL_ParticleEffect was told to + // spawn, most effects do not really have a count and hence use 1, so + // this is often the actual count to spawn, not merely a multiplier + 0.0f, //float countmultiplier; + // if > 0 this causes the particle to spawn in an evenly spaced line from + // originmins to originmaxs (causing them to describe a trail, not a box) + 0.0f, //float trailspacing; + // type of particle to spawn (defines some aspects of behavior) + pt_alphastatic, //ptype_t particletype; + // blending mode used on this particle type + PBLEND_ALPHA, //pblend_t blendmode; + // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc) + PARTICLE_BILLBOARD, //porientation_t orientation; + // range of colors to choose from in hex RRGGBB (like HTML color tags), + // randomly interpolated at spawn + {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2]; + // a random texture is chosen in this range (note the second value is one + // past the last choosable, so for example 8,16 chooses any from 8 up and + // including 15) + // if start and end of the range are the same, no randomization is done + {63, 63 /* tex_particle */}, //int tex[2]; + // range of size values randomly chosen when spawning, plus size increase over time + {1, 1, 0.0f}, //float size[3]; + // range of alpha values randomly chosen when spawning, plus alpha fade + {0.0f, 256.0f, 256.0f}, //float alpha[3]; + // how long the particle should live (note it is also removed if alpha drops to 0) + {16777216.0f, 16777216.0f}, //float time[2]; + // how much gravity affects this particle (negative makes it fly up!) + 0.0f, //float gravity; + // how much bounce the particle has when it hits a surface + // if negative the particle is removed on impact + 0.0f, //float bounce; + // if in air this friction is applied + // if negative the particle accelerates + 0.0f, //float airfriction; + // if in liquid (water/slime/lava) this friction is applied + // if negative the particle accelerates + 0.0f, //float liquidfriction; + // these offsets are added to the values given to particleeffect(), and + // then an ellipsoid-shaped jitter is added as defined by these + // (they are the 3 radii) + 1.0f, //float stretchfactor; + // stretch velocity factor (used for sparks) + {0.0f, 0.0f, 0.0f}, //float originoffset[3]; + {0.0f, 0.0f, 0.0f}, //float velocityoffset[3]; + {0.0f, 0.0f, 0.0f}, //float originjitter[3]; + {0.0f, 0.0f, 0.0f}, //float velocityjitter[3]; + 0.0f, //float velocitymultiplier; + // an effect can also spawn a dlight + 0.0f, //float lightradiusstart; + 0.0f, //float lightradiusfade; + 16777216.0f, //float lighttime; + {1.0f, 1.0f, 1.0f}, //float lightcolor[3]; + true, //qboolean lightshadow; + 0, //int lightcubemapnum; + {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color! + {-1, -1}, //int staintex[2]; + {1.0f, 1.0f}, //float stainalpha[2]; + {2.0f, 2.0f}, //float stainsize[2]; + // other parameters + {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time +}; + +cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"}; +cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"}; +cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"}; +cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"}; +cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"}; +cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"}; +cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"}; +cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"}; +cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"}; +cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"}; +cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"}; +cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"}; +cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"}; +cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"}; +cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"}; +cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"}; +cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"}; +cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"}; +cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"}; +cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"}; +cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"}; +cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"}; +cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"}; +cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"}; +cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"}; +cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"}; +cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"}; +cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"}; +cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"}; +cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"}; +cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"}; +cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"}; +cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"}; + + +void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename) +{ + int arrayindex; + int argc; + int linenumber; + particleeffectinfo_t *info = NULL; + const char *text = textstart; + char argv[16][1024]; + for (linenumber = 1;;linenumber++) + { + argc = 0; + for (arrayindex = 0;arrayindex < 16;arrayindex++) + argv[arrayindex][0] = 0; + for (;;) + { + if (!COM_ParseToken_Simple(&text, true, false)) + return; + if (!strcmp(com_token, "\n")) + break; + if (argc < 16) + { + strlcpy(argv[argc], com_token, sizeof(argv[argc])); + argc++; + } + } + if (argc < 1) + continue; +#define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;} +#define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0) +#define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex]) +#define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0) +#define readfloat(var) checkparms(2);var = atof(argv[1]) +#define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0 + if (!strcmp(argv[0], "effect")) + { + int effectnameindex; + checkparms(2); + if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO) + { + Con_Printf("%s:%i: too many effects!\n", filename, linenumber); + break; + } + for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++) + { + if (particleeffectname[effectnameindex][0]) + { + if (!strcmp(particleeffectname[effectnameindex], argv[1])) + break; + } + else + { + strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex])); + break; + } + } + // if we run out of names, abort + if (effectnameindex == MAX_PARTICLEEFFECTNAME) + { + Con_Printf("%s:%i: too many effects!\n", filename, linenumber); + break; + } + info = particleeffectinfo + numparticleeffectinfo++; + // copy entire info from baseline, then fix up the nameindex + *info = baselineparticleeffectinfo; + info->effectnameindex = effectnameindex; + } + else if (info == NULL) + { + Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]); + break; + } + else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);} + else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);} + else if (!strcmp(argv[0], "type")) + { + checkparms(2); + if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic; + else if (!strcmp(argv[1], "static")) info->particletype = pt_static; + else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark; + else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam; + else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain; + else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal; + else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow; + else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble; + else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;} + else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke; + else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal; + else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle; + else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]); + info->blendmode = particletype[info->particletype].blendmode; + info->orientation = particletype[info->particletype].orientation; + } + else if (!strcmp(argv[0], "blend")) + { + checkparms(2); + if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA; + else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD; + else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD; + else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]); + } + else if (!strcmp(argv[0], "orientation")) + { + checkparms(2); + if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD; + else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK; + else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED; + else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM; + else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]); + } + else if (!strcmp(argv[0], "color")) {readints(info->color, 2);} + else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);} + else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);} + else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);} + else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);} + else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);} + else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);} + else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);} + else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);} + else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);} + else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);} + else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);} + else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);} + else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);} + else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);} + else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);} + else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);} + else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);} + else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);} + else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);} + else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);} + else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;} + else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;} + else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;} + else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);} + else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);} + else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);} + else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);} + else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);} + else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; } + else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);} + else + Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]); +#undef checkparms +#undef readints +#undef readfloats +#undef readint +#undef readfloat + } +} + +int CL_ParticleEffectIndexForName(const char *name) +{ + int i; + for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++) + if (!strcmp(particleeffectname[i], name)) + return i; + return 0; +} + +const char *CL_ParticleEffectNameForIndex(int i) +{ + if (i < 1 || i >= MAX_PARTICLEEFFECTNAME) + return NULL; + return particleeffectname[i]; +} + +// MUST match effectnameindex_t in client.h +static const char *standardeffectnames[EFFECT_TOTAL] = +{ + "", + "TE_GUNSHOT", + "TE_GUNSHOTQUAD", + "TE_SPIKE", + "TE_SPIKEQUAD", + "TE_SUPERSPIKE", + "TE_SUPERSPIKEQUAD", + "TE_WIZSPIKE", + "TE_KNIGHTSPIKE", + "TE_EXPLOSION", + "TE_EXPLOSIONQUAD", + "TE_TAREXPLOSION", + "TE_TELEPORT", + "TE_LAVASPLASH", + "TE_SMALLFLASH", + "TE_FLAMEJET", + "EF_FLAME", + "TE_BLOOD", + "TE_SPARK", + "TE_PLASMABURN", + "TE_TEI_G3", + "TE_TEI_SMOKE", + "TE_TEI_BIGEXPLOSION", + "TE_TEI_PLASMAHIT", + "EF_STARDUST", + "TR_ROCKET", + "TR_GRENADE", + "TR_BLOOD", + "TR_WIZSPIKE", + "TR_SLIGHTBLOOD", + "TR_KNIGHTSPIKE", + "TR_VORESPIKE", + "TR_NEHAHRASMOKE", + "TR_NEXUIZPLASMA", + "TR_GLOWTRAIL", + "SVC_PARTICLE" +}; + +void CL_Particles_LoadEffectInfo(void) +{ + int i; + int filepass; + unsigned char *filedata; + fs_offset_t filesize; + char filename[MAX_QPATH]; + numparticleeffectinfo = 0; + memset(particleeffectinfo, 0, sizeof(particleeffectinfo)); + memset(particleeffectname, 0, sizeof(particleeffectname)); + for (i = 0;i < EFFECT_TOTAL;i++) + strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i])); + for (filepass = 0;;filepass++) + { + if (filepass == 0) + dpsnprintf(filename, sizeof(filename), "effectinfo.txt"); + else if (filepass == 1) + { + if (!cl.worldbasename[0]) + continue; + dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension); + } + else + break; + filedata = FS_LoadFile(filename, tempmempool, true, &filesize); + if (!filedata) + continue; + CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename); + Mem_Free(filedata); + } +} + +/* +=============== +CL_InitParticles +=============== +*/ +void CL_ReadPointFile_f (void); +void CL_Particles_Init (void) +{ + Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)"); + Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)"); + + Cvar_RegisterVariable (&cl_particles); + Cvar_RegisterVariable (&cl_particles_quality); + Cvar_RegisterVariable (&cl_particles_alpha); + Cvar_RegisterVariable (&cl_particles_size); + Cvar_RegisterVariable (&cl_particles_quake); + Cvar_RegisterVariable (&cl_particles_blood); + Cvar_RegisterVariable (&cl_particles_blood_alpha); + Cvar_RegisterVariable (&cl_particles_blood_decal_alpha); + Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin); + Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax); + Cvar_RegisterVariable (&cl_particles_blood_bloodhack); + Cvar_RegisterVariable (&cl_particles_explosions_sparks); + Cvar_RegisterVariable (&cl_particles_explosions_shell); + Cvar_RegisterVariable (&cl_particles_bulletimpacts); + Cvar_RegisterVariable (&cl_particles_rain); + Cvar_RegisterVariable (&cl_particles_snow); + Cvar_RegisterVariable (&cl_particles_smoke); + Cvar_RegisterVariable (&cl_particles_smoke_alpha); + Cvar_RegisterVariable (&cl_particles_smoke_alphafade); + Cvar_RegisterVariable (&cl_particles_sparks); + Cvar_RegisterVariable (&cl_particles_bubbles); + Cvar_RegisterVariable (&cl_particles_visculling); + Cvar_RegisterVariable (&cl_particles_collisions); + Cvar_RegisterVariable (&cl_decals); + Cvar_RegisterVariable (&cl_decals_visculling); + Cvar_RegisterVariable (&cl_decals_time); + Cvar_RegisterVariable (&cl_decals_fadetime); + Cvar_RegisterVariable (&cl_decals_newsystem); + Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier); + Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain); + Cvar_RegisterVariable (&cl_decals_models); + Cvar_RegisterVariable (&cl_decals_bias); + Cvar_RegisterVariable (&cl_decals_max); +} + +void CL_Particles_Shutdown (void) +{ +} + +void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha); +void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2); + +// list of all 26 parameters: +// ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file +// pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color +// ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle +// psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM) +// palpha - opacity of particle as 0-255 (can be more than 255) +// palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second) +// ptime - how long the particle can live (note it is also removed if alpha drops to nothing) +// pgravity - how much effect gravity has on the particle (0-1) +// pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions +// px,py,pz - starting origin of particle +// pvx,pvy,pvz - starting velocity of particle +// pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1) +// blendmode - one of the PBLEND_ values +// orientation - one of the PARTICLE_ values +// staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none) +// staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none) +// stainalpha: opacity of the stain as factor for alpha +// stainsize: size of the stain as factor for palpha +// angle: base rotation of the particle geometry around its center normal +// spin: rotation speed of the particle geometry around its center normal +particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4]) +{ + int l1, l2, r, g, b; + particle_t *part; + vec3_t v; + if (!cl_particles.integer) + return NULL; + for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++); + if (cl.free_particle >= cl.max_particles) + return NULL; + if (!lifetime) + lifetime = palpha / min(1, palphafade); + part = &cl.particles[cl.free_particle++]; + if (cl.num_particles < cl.free_particle) + cl.num_particles = cl.free_particle; + memset(part, 0, sizeof(*part)); + VectorCopy(sortorigin, part->sortorigin); + part->typeindex = ptypeindex; + part->blendmode = blendmode; + if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM) + { + particletexture_t *tex = &particletexture[ptex]; + if(tex->t1 == 0 && tex->t2 == 1) // full height of texture? + part->orientation = PARTICLE_VBEAM; + else + part->orientation = PARTICLE_HBEAM; + } + else + part->orientation = orientation; + l2 = (int)lhrandom(0.5, 256.5); + l1 = 256 - l2; + part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF; + part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF; + part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF; + if (vid.sRGB3D) + { + part->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[0]) * 256.0f); + part->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[1]) * 256.0f); + part->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[2]) * 256.0f); + } + part->alpha = palpha; + part->alphafade = palphafade; + part->staintexnum = staintex; + if(staincolor1 >= 0 && staincolor2 >= 0) + { + l2 = (int)lhrandom(0.5, 256.5); + l1 = 256 - l2; + if(blendmode == PBLEND_INVMOD) + { + r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant + g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000; + b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000; + } + else + { + r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant + g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000; + b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000; + } + if(r > 0xFF) r = 0xFF; + if(g > 0xFF) g = 0xFF; + if(b > 0xFF) b = 0xFF; + } + else + { + r = part->color[0]; // -1 is shorthand for stain = particle color + g = part->color[1]; + b = part->color[2]; + } + part->staincolor[0] = r; + part->staincolor[1] = g; + part->staincolor[2] = b; + part->stainalpha = palpha * stainalpha; + part->stainsize = psize * stainsize; + if(tint) + { + if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting + { + part->color[0] *= tint[0]; + part->color[1] *= tint[1]; + part->color[2] *= tint[2]; + } + part->alpha *= tint[3]; + part->alphafade *= tint[3]; + part->stainalpha *= tint[3]; + } + part->texnum = ptex; + part->size = psize; + part->sizeincrease = psizeincrease; + part->gravity = pgravity; + part->bounce = pbounce; + part->stretch = stretch; + VectorRandom(v); + part->org[0] = px + originjitter * v[0]; + part->org[1] = py + originjitter * v[1]; + part->org[2] = pz + originjitter * v[2]; + part->vel[0] = pvx + velocityjitter * v[0]; + part->vel[1] = pvy + velocityjitter * v[1]; + part->vel[2] = pvz + velocityjitter * v[2]; + part->time2 = 0; + part->airfriction = pairfriction; + part->liquidfriction = pliquidfriction; + part->die = cl.time + lifetime; + part->delayedspawn = cl.time; +// part->delayedcollisions = 0; + part->qualityreduction = pqualityreduction; + part->angle = angle; + part->spin = spin; + // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance + if (part->typeindex == pt_rain) + { + int i; + particle_t *part2; + float lifetime = part->die - cl.time; + vec3_t endvec; + trace_t trace; + // turn raindrop into simple spark and create delayedspawn splash effect + part->typeindex = pt_spark; + part->bounce = 0; + VectorMA(part->org, lifetime, part->vel, endvec); + trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false); + part->die = cl.time + lifetime * trace.fraction; + part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL); + if (part2) + { + part2->delayedspawn = part->die; + part2->die += part->die - cl.time; + for (i = rand() & 7;i < 10;i++) + { + part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + if (part2) + { + part2->delayedspawn = part->die; + part2->die += part->die - cl.time; + } + } + } + } +#if 0 + else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow) + { + float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1); + vec3_t endvec; + trace_t trace; + VectorMA(part->org, lifetime, part->vel, endvec); + trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false); + part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1; + } +#endif + + return part; +} + +static void CL_ImmediateBloodStain(particle_t *part) +{ + vec3_t v; + int staintex; + + // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot + if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer) + { + VectorCopy(part->vel, v); + VectorNormalize(v); + staintex = part->staintexnum; + R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize); + } + + // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot + if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer) + { + VectorCopy(part->vel, v); + VectorNormalize(v); + staintex = tex_blooddecal[rand()&7]; + R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2); + } +} + +void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha) +{ + int l1, l2; + decal_t *decal; + entity_render_t *ent = &cl.entities[hitent].render; + unsigned char color[3]; + if (!cl_decals.integer) + return; + if (!ent->allowdecals) + return; + + l2 = (int)lhrandom(0.5, 256.5); + l1 = 256 - l2; + color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF; + color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF; + color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF; + + if (cl_decals_newsystem.integer) + { + if (vid.sRGB3D) + R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size); + else + R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size); + return; + } + + for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++); + if (cl.free_decal >= cl.max_decals) + return; + decal = &cl.decals[cl.free_decal++]; + if (cl.num_decals < cl.free_decal) + cl.num_decals = cl.free_decal; + memset(decal, 0, sizeof(*decal)); + decal->decalsequence = cl.decalsequence++; + decal->typeindex = pt_decal; + decal->texnum = texnum; + VectorMA(org, cl_decals_bias.value, normal, decal->org); + VectorCopy(normal, decal->normal); + decal->size = size; + decal->alpha = alpha; + decal->time2 = cl.time; + decal->color[0] = color[0]; + decal->color[1] = color[1]; + decal->color[2] = color[2]; + if (vid.sRGB3D) + { + decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f); + decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f); + decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f); + } + decal->owner = hitent; + decal->clusterindex = -1000; // no vis culling unless we're sure + if (hitent) + { + // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0) + decal->ownermodel = cl.entities[decal->owner].render.model; + Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin); + Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal); + } + else + { + if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) + { + mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org); + if(leaf) + decal->clusterindex = leaf->clusterindex; + } + } +} + +void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2) +{ + int i; + float bestfrac, bestorg[3], bestnormal[3]; + float org2[3]; + int besthitent = 0, hitent; + trace_t trace; + bestfrac = 10; + for (i = 0;i < 32;i++) + { + VectorRandom(org2); + VectorMA(org, maxdist, org2, org2); + trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true); + // take the closest trace result that doesn't end up hitting a NOMARKS + // surface (sky for example) + if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)) + { + bestfrac = trace.fraction; + besthitent = hitent; + VectorCopy(trace.endpos, bestorg); + VectorCopy(trace.plane.normal, bestnormal); + } + } + if (bestfrac < 1) + CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha); +} + +static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount); +static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount); +void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles) +{ + vec3_t center; + matrix4x4_t tempmatrix; + particle_t *part; + VectorLerp(originmins, 0.5, originmaxs, center); + Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]); + if (effectnameindex == EFFECT_SVC_PARTICLE) + { + if (cl_particles.integer) + { + // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead + if (count == 1024) + CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225)) + CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + else + { + count *= cl_particles_quality.value; + for (;count > 0;count--) + { + int k = particlepalette[(palettecolor & ~7) + (rand()&7)]; + CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + } + else if (effectnameindex == EFFECT_TE_WIZSPIKE) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20); + else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226); + else if (effectnameindex == EFFECT_TE_SPIKE) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + { + if (cl_particles_smoke.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + } + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + } + else if (effectnameindex == EFFECT_TE_SPIKEQUAD) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + { + if (cl_particles_smoke.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + } + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_SUPERSPIKE) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + { + if (cl_particles_smoke.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + } + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + } + else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + { + if (cl_particles_smoke.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + } + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_BLOOD) + { + if (!cl_particles_blood.integer) + return; + if (cl_particles_quake.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73); + else + { + static double bloodaccumulator = 0; + qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1); + //CL_NewParticle(center, pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, NULL); + bloodaccumulator += count * 0.333 * cl_particles_quality.value; + for (;bloodaccumulator > 0;bloodaccumulator--) + { + part = CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + if (immediatebloodstain && part) + { + immediatebloodstain = false; + CL_ImmediateBloodStain(part); + } + } + } + } + else if (effectnameindex == EFFECT_TE_SPARK) + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count); + else if (effectnameindex == EFFECT_TE_PLASMABURN) + { + // plasma scorch mark + R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_GUNSHOT) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + } + else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_EXPLOSION) + { + CL_ParticleExplosion(center); + CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD) + { + CL_ParticleExplosion(center); + CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_TAREXPLOSION) + { + if (cl_particles_quake.integer) + { + int i; + for (i = 0;i < 1024 * cl_particles_quality.value;i++) + { + if (i & 1) + CL_NewParticle(center, pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(center, pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else + CL_ParticleExplosion(center); + CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_SMALLFLASH) + CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + else if (effectnameindex == EFFECT_TE_FLAMEJET) + { + count *= cl_particles_quality.value; + while (count-- > 0) + CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (effectnameindex == EFFECT_TE_LAVASPLASH) + { + float i, j, inc, vel; + vec3_t dir, org; + + inc = 8 / cl_particles_quality.value; + for (i = -128;i < 128;i += inc) + { + for (j = -128;j < 128;j += inc) + { + dir[0] = j + lhrandom(0, inc); + dir[1] = i + lhrandom(0, inc); + dir[2] = 256; + org[0] = center[0] + dir[0]; + org[1] = center[1] + dir[1]; + org[2] = center[2] + lhrandom(0, 64); + vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale + CL_NewParticle(center, pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + else if (effectnameindex == EFFECT_TE_TELEPORT) + { + float i, j, k, inc, vel; + vec3_t dir; + + if (cl_particles_quake.integer) + inc = 4 / cl_particles_quality.value; + else + inc = 8 / cl_particles_quality.value; + for (i = -16;i < 16;i += inc) + { + for (j = -16;j < 16;j += inc) + { + for (k = -24;k < 32;k += inc) + { + VectorSet(dir, i*8, j*8, k*8); + VectorNormalize(dir); + vel = lhrandom(50, 113); + if (cl_particles_quake.integer) + CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + if (!cl_particles_quake.integer) + CL_NewParticle(center, pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_TEI_G3) + CL_NewParticle(center, pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); + else if (effectnameindex == EFFECT_TE_TEI_SMOKE) + { + if (cl_particles_smoke.integer) + { + count *= 0.25f * cl_particles_quality.value; + while (count-- > 0) + CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION) + { + CL_ParticleExplosion(center); + CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT) + { + float f; + R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + if (cl_particles_smoke.integer) + for (f = 0;f < count;f += 4.0f / cl_particles_quality.value) + CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + if (cl_particles_sparks.integer) + for (f = 0;f < count;f += 1.0f / cl_particles_quality.value) + CL_NewParticle(center, pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_EF_FLAME) + { + count *= 300 * cl_particles_quality.value; + while (count-- > 0) + CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_EF_STARDUST) + { + count *= 200 * cl_particles_quality.value; + while (count-- > 0) + CL_NewParticle(center, pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3)) + { + vec3_t dir, pos; + float len, dec, qd; + int smoke, blood, bubbles, r, color; + + if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS) + { + vec4_t light; + Vector4Set(light, 0, 0, 0, 0); + + if (effectnameindex == EFFECT_TR_ROCKET) + Vector4Set(light, 3.0f, 1.5f, 0.5f, 200); + else if (effectnameindex == EFFECT_TR_VORESPIKE) + { + if (gamemode == GAME_PRYDON && !cl_particles_quake.integer) + Vector4Set(light, 0.3f, 0.6f, 1.2f, 100); + else + Vector4Set(light, 1.2f, 0.5f, 1.0f, 200); + } + else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA) + Vector4Set(light, 0.75f, 1.5f, 3.0f, 200); + + if (light[3]) + { + matrix4x4_t tempmatrix; + Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + } + + if (!spawnparticles) + return; + + if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2]) + return; + + VectorSubtract(originmaxs, originmins, dir); + len = VectorNormalizeLength(dir); + if (ent) + { + dec = -ent->persistent.trail_time; + ent->persistent.trail_time += len; + if (ent->persistent.trail_time < 0.01f) + return; + + // if we skip out, leave it reset + ent->persistent.trail_time = 0.0f; + } + else + dec = 0; + + // advance into this frame to reach the first puff location + VectorMA(originmins, dec, dir, pos); + len -= dec; + + smoke = cl_particles.integer && cl_particles_smoke.integer; + blood = cl_particles.integer && cl_particles_blood.integer; + bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)); + qd = 1.0f / cl_particles_quality.value; + + while (len >= 0) + { + dec = 3; + if (blood) + { + if (effectnameindex == EFFECT_TR_BLOOD) + { + if (cl_particles_quake.integer) + { + color = particlepalette[67 + (rand()&3)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + dec = 16; + CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD) + { + if (cl_particles_quake.integer) + { + dec = 6; + color = particlepalette[67 + (rand()&3)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + dec = 32; + CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + if (smoke) + { + if (effectnameindex == EFFECT_TR_ROCKET) + { + if (cl_particles_quake.integer) + { + r = rand()&3; + color = particlepalette[ramp3[r]]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(center, pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_GRENADE) + { + if (cl_particles_quake.integer) + { + r = 2 + (rand()%5); + color = particlepalette[ramp3[r]]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_WIZSPIKE) + { + if (cl_particles_quake.integer) + { + dec = 6; + color = particlepalette[52 + (rand()&7)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (gamemode == GAME_GOODVSBAD2) + { + dec = 6; + CL_NewParticle(center, pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + color = particlepalette[20 + (rand()&7)]; + CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE) + { + if (cl_particles_quake.integer) + { + dec = 6; + color = particlepalette[230 + (rand()&7)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + color = particlepalette[226 + (rand()&7)]; + CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_VORESPIKE) + { + if (cl_particles_quake.integer) + { + color = particlepalette[152 + (rand()&3)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (gamemode == GAME_GOODVSBAD2) + { + dec = 6; + CL_NewParticle(center, pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (gamemode == GAME_PRYDON) + { + dec = 6; + CL_NewParticle(center, pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + CL_NewParticle(center, pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE) + { + dec = 7; + CL_NewParticle(center, pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA) + { + dec = 4; + CL_NewParticle(center, pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (effectnameindex == EFFECT_TR_GLOWTRAIL) + CL_NewParticle(center, pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + if (bubbles) + { + if (effectnameindex == EFFECT_TR_ROCKET) + CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else if (effectnameindex == EFFECT_TR_GRENADE) + CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + // advance to next time and position + dec *= qd; + len -= dec; + VectorMA (pos, dec, dir, pos); + } + if (ent) + ent->persistent.trail_time = len; + } + else + Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]); +} + +// this is also called on point effects with spawndlight = true and +// spawnparticles = true +// it is called CL_ParticleTrail because most code does not want to supply +// these parameters, only trail handling does +void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4]) +{ + qboolean found = false; + if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0]) + { + Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex); + return; // no such effect + } + if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex) + { + int effectinfoindex; + int supercontents; + int tex, staintex; + particleeffectinfo_t *info; + vec3_t center; + vec3_t traildir; + vec3_t trailpos; + vec3_t rvec; + vec_t traillen; + vec_t trailstep; + qboolean underwater; + qboolean immediatebloodstain; + particle_t *part; + float avgtint[4], tint[4], tintlerp; + // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one + VectorLerp(originmins, 0.5, originmaxs, center); + supercontents = CL_PointSuperContents(center); + underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0; + VectorSubtract(originmaxs, originmins, traildir); + traillen = VectorLength(traildir); + VectorNormalize(traildir); + if(tintmins) + { + Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint); + } + else + { + Vector4Set(avgtint, 1, 1, 1, 1); + } + for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++) + { + if (info->effectnameindex == effectnameindex) + { + found = true; + if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater) + continue; + if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater) + continue; + + // spawn a dlight if requested + if (info->lightradiusstart > 0 && spawndlight) + { + matrix4x4_t tempmatrix; + if (info->trailspacing > 0) + Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]); + else + Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]); + if (info->lighttime > 0 && info->lightradiusfade > 0) + { + // light flash (explosion, etc) + // called when effect starts + CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (r_refdef.scene.numlights < MAX_DLIGHTS) + { + // glowing entity + // called by CL_LinkNetworkEntity + Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1); + rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3]; + rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3]; + rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3]; + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + } + + if (!spawnparticles) + continue; + + // spawn particles + tex = info->tex[0]; + if (info->tex[1] > info->tex[0]) + { + tex = (int)lhrandom(info->tex[0], info->tex[1]); + tex = min(tex, info->tex[1] - 1); + } + if(info->staintex[0] < 0) + staintex = info->staintex[0]; + else + { + staintex = (int)lhrandom(info->staintex[0], info->staintex[1]); + staintex = min(staintex, info->staintex[1] - 1); + } + if (info->particletype == pt_decal) + CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]); + else if (info->orientation == PARTICLE_HBEAM) + CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL); + else + { + if (!cl_particles.integer) + continue; + switch (info->particletype) + { + case pt_smoke: if (!cl_particles_smoke.integer) continue;break; + case pt_spark: if (!cl_particles_sparks.integer) continue;break; + case pt_bubble: if (!cl_particles_bubbles.integer) continue;break; + case pt_blood: if (!cl_particles_blood.integer) continue;break; + case pt_rain: if (!cl_particles_rain.integer) continue;break; + case pt_snow: if (!cl_particles_snow.integer) continue;break; + default: break; + } + VectorCopy(originmins, trailpos); + if (info->trailspacing > 0) + { + info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount; + trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount); + immediatebloodstain = false; + } + else + { + info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value; + trailstep = 0; + immediatebloodstain = + ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood)) + || + ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex); + } + info->particleaccumulator = bound(0, info->particleaccumulator, 16384); + for (;info->particleaccumulator >= 1;info->particleaccumulator--) + { + if (info->tex[1] > info->tex[0]) + { + tex = (int)lhrandom(info->tex[0], info->tex[1]); + tex = min(tex, info->tex[1] - 1); + } + if (!trailstep) + { + trailpos[0] = lhrandom(originmins[0], originmaxs[0]); + trailpos[1] = lhrandom(originmins[1], originmaxs[1]); + trailpos[2] = lhrandom(originmins[2], originmaxs[2]); + } + if(tintmins) + { + tintlerp = lhrandom(0, 1); + Vector4Lerp(tintmins, tintlerp, tintmaxs, tint); + } + VectorRandom(rvec); + part = CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL); + if (immediatebloodstain && part) + { + immediatebloodstain = false; + CL_ImmediateBloodStain(part); + } + if (trailstep) + VectorMA(trailpos, trailstep, traildir, trailpos); + } + } + } + } + } + if (!found) + CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles); +} + +void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor) +{ + CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL); +} + +/* +=============== +CL_EntityParticles +=============== +*/ +void CL_EntityParticles (const entity_t *ent) +{ + int i; + float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3]; + static vec3_t avelocities[NUMVERTEXNORMALS]; + if (!cl_particles.integer) return; + if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused + + Matrix4x4_OriginFromMatrix(&ent->render.matrix, org); + + if (!avelocities[0][0]) + for (i = 0;i < NUMVERTEXNORMALS * 3;i++) + avelocities[0][i] = lhrandom(0, 2.55); + + for (i = 0;i < NUMVERTEXNORMALS;i++) + { + yaw = cl.time * avelocities[i][0]; + pitch = cl.time * avelocities[i][1]; + v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength; + v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength; + v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength; + CL_NewParticle(org, pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + + +void CL_ReadPointFile_f (void) +{ + vec3_t org, leakorg; + int r, c, s; + char *pointfile = NULL, *pointfilepos, *t, tchar; + char name[MAX_QPATH]; + + if (!cl.worldmodel) + return; + + dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension); + pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL); + if (!pointfile) + { + Con_Printf("Could not open %s\n", name); + return; + } + + Con_Printf("Reading %s...\n", name); + VectorClear(leakorg); + c = 0; + s = 0; + pointfilepos = pointfile; + while (*pointfilepos) + { + while (*pointfilepos == '\n' || *pointfilepos == '\r') + pointfilepos++; + if (!*pointfilepos) + break; + t = pointfilepos; + while (*t && *t != '\n' && *t != '\r') + t++; + tchar = *t; + *t = 0; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]); + *t = tchar; + pointfilepos = t; + if (r != 3) + break; + if (c == 0) + VectorCopy(org, leakorg); + c++; + + if (cl.num_particles < cl.max_particles - 3) + { + s++; + CL_NewParticle(org, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + Mem_Free(pointfile); + VectorCopy(leakorg, org); + Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]); + + CL_NewParticle(org, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(org, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(org, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); +} + +/* +=============== +CL_ParseParticleEffect + +Parse an effect out of the server message +=============== +*/ +void CL_ParseParticleEffect (void) +{ + vec3_t org, dir; + int i, count, msgcount, color; + + MSG_ReadVector(org, cls.protocol); + for (i=0 ; i<3 ; i++) + dir[i] = MSG_ReadChar () * (1.0 / 16.0); + msgcount = MSG_ReadByte (); + color = MSG_ReadByte (); + + if (msgcount == 255) + count = 1024; + else + count = msgcount; + + CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color); +} + +/* +=============== +CL_ParticleExplosion + +=============== +*/ +void CL_ParticleExplosion (const vec3_t org) +{ + int i; + trace_t trace; + //vec3_t v; + //vec3_t v2; + R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + + if (cl_particles_quake.integer) + { + for (i = 0;i < 1024;i++) + { + int r, color; + r = rand()&3; + if (i & 1) + { + color = particlepalette[ramp1[r]]; + CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + color = particlepalette[ramp2[r]]; + CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + else + { + i = CL_PointSuperContents(org); + if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER)) + { + if (cl_particles.integer && cl_particles_bubbles.integer) + for (i = 0;i < 128 * cl_particles_quality.value;i++) + CL_NewParticle(org, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer) + { + for (i = 0;i < 512 * cl_particles_quality.value;i++) + { + int k = 0; + vec3_t v, v2; + do + { + VectorRandom(v2); + VectorMA(org, 128, v2, v); + trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false); + } + while (k < 16 && trace.fraction < 0.1f); + VectorSubtract(trace.endpos, org, v2); + VectorScale(v2, 2.0f, v2); + CL_NewParticle(org, pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + } + + if (cl_particles_explosions_shell.integer) + R_NewExplosion(org); +} + +/* +=============== +CL_ParticleExplosion2 + +=============== +*/ +void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength) +{ + int i, k; + if (!cl_particles.integer) return; + + for (i = 0;i < 512 * cl_particles_quality.value;i++) + { + k = particlepalette[colorStart + (i % colorLength)]; + if (cl_particles_quake.integer) + CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + +static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount) +{ + vec3_t center; + VectorMAM(0.5f, originmins, 0.5f, originmaxs, center); + if (cl_particles_sparks.integer) + { + sparkcount *= cl_particles_quality.value; + while(sparkcount-- > 0) + CL_NewParticle(center, pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + +static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount) +{ + vec3_t center; + VectorMAM(0.5f, originmins, 0.5f, originmaxs, center); + if (cl_particles_smoke.integer) + { + smokecount *= cl_particles_quality.value; + while(smokecount-- > 0) + CL_NewParticle(center, pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + +void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel) +{ + vec3_t center; + int k; + if (!cl_particles.integer) return; + VectorMAM(0.5f, mins, 0.5f, maxs, center); + + count = (int)(count * cl_particles_quality.value); + while (count--) + { + k = particlepalette[colorbase + (rand()&3)]; + CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + +void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type) +{ + int k; + float minz, maxz, lifetime = 30; + vec3_t org; + if (!cl_particles.integer) return; + if (dir[2] < 0) // falling + { + minz = maxs[2] + dir[2] * 0.1; + maxz = maxs[2]; + if (cl.worldmodel) + lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]); + } + else // rising?? + { + minz = mins[2]; + maxz = maxs[2] + dir[2] * 0.1; + if (cl.worldmodel) + lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]); + } + + count = (int)(count * cl_particles_quality.value); + + switch(type) + { + case 0: + if (!cl_particles_rain.integer) break; + count *= 4; // ick, this should be in the mod or maps? + + while(count--) + { + k = particlepalette[colorbase + (rand()&3)]; + VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz)); + if (gamemode == GAME_GOODVSBAD2) + CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + } + break; + case 1: + if (!cl_particles_snow.integer) break; + while(count--) + { + k = particlepalette[colorbase + (rand()&3)]; + VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz)); + if (gamemode == GAME_GOODVSBAD2) + CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + break; + default: + Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type); + } +} + +cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"}; +static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"}; +static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"}; +static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"}; +cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"}; +static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"}; + +#define PARTICLETEXTURESIZE 64 +#define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8) + +static unsigned char shadebubble(float dx, float dy, vec3_t light) +{ + float dz, f, dot; + vec3_t normal; + dz = 1 - (dx*dx+dy*dy); + if (dz > 0) // it does hit the sphere + { + f = 0; + // back side + normal[0] = dx;normal[1] = dy;normal[2] = dz; + VectorNormalize(normal); + dot = DotProduct(normal, light); + if (dot > 0.5) // interior reflection + f += ((dot * 2) - 1); + else if (dot < -0.5) // exterior reflection + f += ((dot * -2) - 1); + // front side + normal[0] = dx;normal[1] = dy;normal[2] = -dz; + VectorNormalize(normal); + dot = DotProduct(normal, light); + if (dot > 0.5) // interior reflection + f += ((dot * 2) - 1); + else if (dot < -0.5) // exterior reflection + f += ((dot * -2) - 1); + f *= 128; + f += 16; // just to give it a haze so you can see the outline + f = bound(0, f, 255); + return (unsigned char) f; + } + else + return 0; +} + +int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols; +void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height) +{ + *basex = (texnum % particlefontcols) * particlefontcellwidth; + *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight; + *width = particlefontcellwidth; + *height = particlefontcellheight; +} + +static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata) +{ + int basex, basey, w, h, y; + CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h); + if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE) + Sys_Error("invalid particle texture size for autogenerating"); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4); +} + +void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha) +{ + int x, y; + float cx, cy, dx, dy, f, iradius; + unsigned char *d; + cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f; + cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f; + iradius = 1.0f / radius; + alpha *= (1.0f / 255.0f); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - cx); + dy = (y - cy); + f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha; + if (f > 0) + { + if (f > 1) + f = 1; + d = data + (y * PARTICLETEXTURESIZE + x) * 4; + d[0] += (int)(f * (blue - d[0])); + d[1] += (int)(f * (green - d[1])); + d[2] += (int)(f * (red - d[2])); + } + } + } +} + +void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb) +{ + int i; + for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4) + { + data[0] = bound(minb, data[0], maxb); + data[1] = bound(ming, data[1], maxg); + data[2] = bound(minr, data[2], maxr); + } +} + +void particletextureinvert(unsigned char *data) +{ + int i; + for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4) + { + data[0] = 255 - data[0]; + data[1] = 255 - data[1]; + data[2] = 255 - data[2]; + } +} + +// Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC +static void R_InitBloodTextures (unsigned char *particletexturedata) +{ + int i, j, k, m; + size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4; + unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize); + + // blood particles + for (i = 0;i < 8;i++) + { + memset(data, 255, datasize); + for (k = 0;k < 24;k++) + particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160); + //particletextureclamp(data, 32, 32, 32, 255, 255, 255); + particletextureinvert(data); + setuptex(tex_bloodparticle[i], data, particletexturedata); + } + + // blood decals + for (i = 0;i < 8;i++) + { + memset(data, 255, datasize); + m = 8; + for (j = 1;j < 10;j++) + for (k = min(j, m - 1);k < m;k++) + particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8); + //particletextureclamp(data, 32, 32, 32, 255, 255, 255); + particletextureinvert(data); + setuptex(tex_blooddecal[i], data, particletexturedata); + } + + Mem_Free(data); +} + +//uncomment this to make engine save out particle font to a tga file when run +//#define DUMPPARTICLEFONT + +static void R_InitParticleTexture (void) +{ + int x, y, d, i, k, m; + int basex, basey, w, h; + float dx, dy, f, s1, t1, s2, t2; + vec3_t light; + char *buf; + fs_offset_t filesize; + char texturename[MAX_QPATH]; + skinframe_t *sf; + + // a note: decals need to modulate (multiply) the background color to + // properly darken it (stain), and they need to be able to alpha fade, + // this is a very difficult challenge because it means fading to white + // (no change to background) rather than black (darkening everything + // behind the whole decal polygon), and to accomplish this the texture is + // inverted (dark red blood on white background becomes brilliant cyan + // and white on black background) so we can alpha fade it to black, then + // we invert it again during the blendfunc to make it work... + +#ifndef DUMPPARTICLEFONT + decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false); + if (decalskinframe) + { + particlefonttexture = decalskinframe->base; + // TODO maybe allow custom grid size? + particlefontwidth = image_width; + particlefontheight = image_height; + particlefontcellwidth = image_width / 8; + particlefontcellheight = image_height / 8; + particlefontcols = 8; + particlefontrows = 8; + } + else +#endif + { + unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4); + size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4; + unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize); + unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2); + unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2); + + particlefontwidth = particlefontheight = PARTICLEFONTSIZE; + particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE; + particlefontcols = 8; + particlefontrows = 8; + + memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4); + + // smoke + for (i = 0;i < 8;i++) + { + memset(data, 255, datasize); + do + { + fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8); + fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4); + m = 0; + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192; + if (d > 0) + d = (int)(d * (1-(dx*dx+dy*dy))); + d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7; + d = bound(0, d, 255); + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d; + if (m < d) + m = d; + } + } + } + while (m < 224); + setuptex(tex_smoke[i], data, particletexturedata); + } + + // rain splash + memset(data, 255, datasize); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy))); + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f)); + } + } + setuptex(tex_rainsplash, data, particletexturedata); + + // normal particle + memset(data, 255, datasize); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + d = (int)(256 * (1 - (dx*dx+dy*dy))); + d = bound(0, d, 255); + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d; + } + } + setuptex(tex_particle, data, particletexturedata); + + // rain + memset(data, 255, datasize); + light[0] = 1;light[1] = 1;light[2] = 1; + VectorNormalize(light); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + // stretch upper half of bubble by +50% and shrink lower half by -50% + // (this gives an elongated teardrop shape) + if (dy > 0.5f) + dy = (dy - 0.5f) * 2.0f; + else + dy = (dy - 0.5f) / 1.5f; + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + // shrink bubble width to half + dx *= 2.0f; + data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light); + } + } + setuptex(tex_raindrop, data, particletexturedata); + + // bubble + memset(data, 255, datasize); + light[0] = 1;light[1] = 1;light[2] = 1; + VectorNormalize(light); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light); + } + } + setuptex(tex_bubble, data, particletexturedata); + + // Blood particles and blood decals + R_InitBloodTextures (particletexturedata); + + // bullet decals + for (i = 0;i < 8;i++) + { + memset(data, 255, datasize); + for (k = 0;k < 12;k++) + particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128); + for (k = 0;k < 3;k++) + particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160); + //particletextureclamp(data, 64, 64, 64, 255, 255, 255); + particletextureinvert(data); + setuptex(tex_bulletdecal[i], data, particletexturedata); + } + +#ifdef DUMPPARTICLEFONT + Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata); +#endif + + decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false); + particlefonttexture = decalskinframe->base; + + Mem_Free(particletexturedata); + Mem_Free(data); + Mem_Free(noise1); + Mem_Free(noise2); + } + for (i = 0;i < MAX_PARTICLETEXTURES;i++) + { + CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h); + particletexture[i].texture = particlefonttexture; + particletexture[i].s1 = (basex + 1) / (float)particlefontwidth; + particletexture[i].t1 = (basey + 1) / (float)particlefontheight; + particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth; + particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight; + } + +#ifndef DUMPPARTICLEFONT + particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D); + if (!particletexture[tex_beam].texture) +#endif + { + unsigned char noise3[64][64], data2[64][16][4]; + // nexbeam + fractalnoise(&noise3[0][0], 64, 4); + m = 0; + for (y = 0;y < 64;y++) + { + dy = (y - 0.5f*64) / (64*0.5f-1); + for (x = 0;x < 16;x++) + { + dx = (x - 0.5f*16) / (16*0.5f-2); + d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]); + data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255); + data2[y][x][3] = 255; + } + } + +#ifdef DUMPPARTICLEFONT + Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]); +#endif + particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL); + } + particletexture[tex_beam].s1 = 0; + particletexture[tex_beam].t1 = 0; + particletexture[tex_beam].s2 = 1; + particletexture[tex_beam].t2 = 1; + + // now load an texcoord/texture override file + buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize); + if(buf) + { + const char *bufptr; + bufptr = buf; + for(;;) + { + if(!COM_ParseToken_Simple(&bufptr, true, false)) + break; + if(!strcmp(com_token, "\n")) + continue; // empty line + i = atoi(com_token); + + texturename[0] = 0; + s1 = 0; + t1 = 0; + s2 = 1; + t2 = 1; + + if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n")) + { + strlcpy(texturename, com_token, sizeof(texturename)); + s1 = atof(com_token); + if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n")) + { + texturename[0] = 0; + t1 = atof(com_token); + if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n")) + { + s2 = atof(com_token); + if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n")) + { + t2 = atof(com_token); + strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename)); + if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n")) + strlcpy(texturename, com_token, sizeof(texturename)); + } + } + } + else + s1 = 0; + } + if (!texturename[0]) + { + Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n"); + continue; + } + if (i < 0 || i >= MAX_PARTICLETEXTURES) + { + Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES); + continue; + } + sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); + if(!sf) + { + // R_SkinFrame_LoadExternal already complained + continue; + } + particletexture[i].texture = sf->base; + particletexture[i].s1 = s1; + particletexture[i].t1 = t1; + particletexture[i].s2 = s2; + particletexture[i].t2 = t2; + } + Mem_Free(buf); + } +} + +static void r_part_start(void) +{ + int i; + // generate particlepalette for convenience from the main one + for (i = 0;i < 256;i++) + particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2]; + particletexturepool = R_AllocTexturePool(); + R_InitParticleTexture (); + CL_Particles_LoadEffectInfo(); +} + +static void r_part_shutdown(void) +{ + R_FreeTexturePool(&particletexturepool); +} + +static void r_part_newmap(void) +{ + if (decalskinframe) + R_SkinFrame_MarkUsed(decalskinframe); + CL_Particles_LoadEffectInfo(); +} + +unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6]; +float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16]; + +void R_Particles_Init (void) +{ + int i; + for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++) + { + particle_elements[i*6+0] = i*4+0; + particle_elements[i*6+1] = i*4+1; + particle_elements[i*6+2] = i*4+2; + particle_elements[i*6+3] = i*4+0; + particle_elements[i*6+4] = i*4+2; + particle_elements[i*6+5] = i*4+3; + } + + Cvar_RegisterVariable(&r_drawparticles); + Cvar_RegisterVariable(&r_drawparticles_drawdistance); + Cvar_RegisterVariable(&r_drawparticles_nearclip_min); + Cvar_RegisterVariable(&r_drawparticles_nearclip_max); + Cvar_RegisterVariable(&r_drawdecals); + Cvar_RegisterVariable(&r_drawdecals_drawdistance); + R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL); +} + +void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex; + const decal_t *d; + float *v3f, *t2f, *c4f; + particletexture_t *tex; + float right[3], up[3], size, ca; + float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value; + + RSurf_ActiveWorldEntity(); + + r_refdef.stats.drawndecals += numsurfaces; +// R_Mesh_ResetTextureState(); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + + // generate all the vertices at once + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + d = cl.decals + surfacelist[surfacelistindex]; + + // calculate color + c4f = particle_color4f + 16*surfacelistindex; + ca = d->alpha * alphascale; + // ensure alpha multiplier saturates properly + if (ca > 1.0f / 256.0f) + ca = 1.0f / 256.0f; + if (r_refdef.fogenabled) + ca *= RSurf_FogVertex(d->org); + Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1); + Vector4Copy(c4f, c4f + 4); + Vector4Copy(c4f, c4f + 8); + Vector4Copy(c4f, c4f + 12); + + // calculate vertex positions + size = d->size * cl_particles_size.value; + VectorVectors(d->normal, right, up); + VectorScale(right, size, right); + VectorScale(up, size, up); + v3f = particle_vertex3f + 12*surfacelistindex; + v3f[ 0] = d->org[0] - right[0] - up[0]; + v3f[ 1] = d->org[1] - right[1] - up[1]; + v3f[ 2] = d->org[2] - right[2] - up[2]; + v3f[ 3] = d->org[0] - right[0] + up[0]; + v3f[ 4] = d->org[1] - right[1] + up[1]; + v3f[ 5] = d->org[2] - right[2] + up[2]; + v3f[ 6] = d->org[0] + right[0] + up[0]; + v3f[ 7] = d->org[1] + right[1] + up[1]; + v3f[ 8] = d->org[2] + right[2] + up[2]; + v3f[ 9] = d->org[0] + right[0] - up[0]; + v3f[10] = d->org[1] + right[1] - up[1]; + v3f[11] = d->org[2] + right[2] - up[2]; + + // calculate texcoords + tex = &particletexture[d->texnum]; + t2f = particle_texcoord2f + 8*surfacelistindex; + t2f[0] = tex->s1;t2f[1] = tex->t2; + t2f[2] = tex->s1;t2f[3] = tex->t1; + t2f[4] = tex->s2;t2f[5] = tex->t1; + t2f[6] = tex->s2;t2f[7] = tex->t2; + } + + // now render the decals all at once + // (this assumes they all use one particle font texture!) + GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false); + R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f); + R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0); +} + +void R_DrawDecals (void) +{ + int i; + int drawdecals = r_drawdecals.integer; + decal_t *decal; + float frametime; + float decalfade; + float drawdist2; + int killsequence = cl.decalsequence - max(0, cl_decals_max.integer); + + frametime = bound(0, cl.time - cl.decals_updatetime, 1); + cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1); + + // LordHavoc: early out conditions + if (!cl.num_decals) + return; + + decalfade = frametime * 256 / cl_decals_fadetime.value; + drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality; + drawdist2 = drawdist2*drawdist2; + + for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++) + { + if (!decal->typeindex) + continue; + + if (killsequence - decal->decalsequence > 0) + goto killdecal; + + if (cl.time > decal->time2 + cl_decals_time.value) + { + decal->alpha -= decalfade; + if (decal->alpha <= 0) + goto killdecal; + } + + if (decal->owner) + { + if (cl.entities[decal->owner].render.model == decal->ownermodel) + { + Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org); + Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal); + } + else + goto killdecal; + } + + if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex)) + continue; + + if (!drawdecals) + continue; + + if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size)) + R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL); + continue; +killdecal: + decal->typeindex = 0; + if (cl.free_decal > i) + cl.free_decal = i; + } + + // reduce cl.num_decals if possible + while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0) + cl.num_decals--; + + if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS) + { + decal_t *olddecals = cl.decals; + cl.max_decals = min(cl.max_decals * 2, MAX_DECALS); + cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t)); + memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t)); + Mem_Free(olddecals); + } + + r_refdef.stats.totaldecals = cl.num_decals; +} + +void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex; + int batchstart, batchcount; + const particle_t *p; + pblend_t blendmode; + rtexture_t *texture; + float *v3f, *t2f, *c4f; + particletexture_t *tex; + float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha; +// float ambient[3], diffuse[3], diffusenormal[3]; + float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3]; + vec4_t colormultiplier; + float minparticledist_start, minparticledist_end; + qboolean dofade; + + RSurf_ActiveWorldEntity(); + + Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f)); + + r_refdef.stats.particles += numsurfaces; +// R_Mesh_ResetTextureState(); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + + spintime = r_refdef.scene.time; + + minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value; + minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value; + dofade = (minparticledist_start < minparticledist_end); + + // first generate all the vertices at once + for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4) + { + p = cl.particles + surfacelist[surfacelistindex]; + + blendmode = (pblend_t)p->blendmode; + palpha = p->alpha; + if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM) + palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start)); + alpha = palpha * colormultiplier[3]; + // ensure alpha multiplier saturates properly + if (alpha > 1.0f) + alpha = 1.0f; + + switch (blendmode) + { + case PBLEND_INVALID: + case PBLEND_INVMOD: + // additive and modulate can just fade out in fog (this is correct) + if (r_refdef.fogenabled) + alpha *= RSurf_FogVertex(p->org); + // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures) + alpha *= 1.0f / 256.0f; + c4f[0] = p->color[0] * alpha; + c4f[1] = p->color[1] * alpha; + c4f[2] = p->color[2] * alpha; + c4f[3] = 0; + break; + case PBLEND_ADD: + // additive and modulate can just fade out in fog (this is correct) + if (r_refdef.fogenabled) + alpha *= RSurf_FogVertex(p->org); + // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures) + c4f[0] = p->color[0] * colormultiplier[0] * alpha; + c4f[1] = p->color[1] * colormultiplier[1] * alpha; + c4f[2] = p->color[2] * colormultiplier[2] * alpha; + c4f[3] = 0; + break; + case PBLEND_ALPHA: + c4f[0] = p->color[0] * colormultiplier[0]; + c4f[1] = p->color[1] * colormultiplier[1]; + c4f[2] = p->color[2] * colormultiplier[2]; + c4f[3] = alpha; + // note: lighting is not cheap! + if (particletype[p->typeindex].lighting) + R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); + // mix in the fog color + if (r_refdef.fogenabled) + { + fog = RSurf_FogVertex(p->org); + ifog = 1 - fog; + c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog; + c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog; + c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog; + } + // for premultiplied alpha we have to apply the alpha to the color (after fog of course) + VectorScale(c4f, alpha, c4f); + break; + } + // copy the color into the other three vertices + Vector4Copy(c4f, c4f + 4); + Vector4Copy(c4f, c4f + 8); + Vector4Copy(c4f, c4f + 12); + + size = p->size * cl_particles_size.value; + tex = &particletexture[p->texnum]; + switch(p->orientation) + { +// case PARTICLE_INVALID: + case PARTICLE_BILLBOARD: + if (p->angle + p->spin) + { + spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f); + spinsin = sin(spinrad) * size; + spincos = cos(spinrad) * size; + spinm1 = -p->stretch * spincos; + spinm2 = -spinsin; + spinm3 = spinsin; + spinm4 = -p->stretch * spincos; + VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right); + VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up); + } + else + { + VectorScale(r_refdef.view.left, -size * p->stretch, right); + VectorScale(r_refdef.view.up, size, up); + } + + v3f[ 0] = p->org[0] - right[0] - up[0]; + v3f[ 1] = p->org[1] - right[1] - up[1]; + v3f[ 2] = p->org[2] - right[2] - up[2]; + v3f[ 3] = p->org[0] - right[0] + up[0]; + v3f[ 4] = p->org[1] - right[1] + up[1]; + v3f[ 5] = p->org[2] - right[2] + up[2]; + v3f[ 6] = p->org[0] + right[0] + up[0]; + v3f[ 7] = p->org[1] + right[1] + up[1]; + v3f[ 8] = p->org[2] + right[2] + up[2]; + v3f[ 9] = p->org[0] + right[0] - up[0]; + v3f[10] = p->org[1] + right[1] - up[1]; + v3f[11] = p->org[2] + right[2] - up[2]; + t2f[0] = tex->s1;t2f[1] = tex->t2; + t2f[2] = tex->s1;t2f[3] = tex->t1; + t2f[4] = tex->s2;t2f[5] = tex->t1; + t2f[6] = tex->s2;t2f[7] = tex->t2; + break; + case PARTICLE_ORIENTED_DOUBLESIDED: + VectorVectors(p->vel, baseright, baseup); + if (p->angle + p->spin) + { + spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f); + spinsin = sin(spinrad) * size; + spincos = cos(spinrad) * size; + spinm1 = p->stretch * spincos; + spinm2 = -spinsin; + spinm3 = spinsin; + spinm4 = p->stretch * spincos; + VectorMAM(spinm1, baseright, spinm2, baseup, right); + VectorMAM(spinm3, baseright, spinm4, baseup, up); + } + else + { + VectorScale(baseright, size * p->stretch, right); + VectorScale(baseup, size, up); + } + v3f[ 0] = p->org[0] - right[0] - up[0]; + v3f[ 1] = p->org[1] - right[1] - up[1]; + v3f[ 2] = p->org[2] - right[2] - up[2]; + v3f[ 3] = p->org[0] - right[0] + up[0]; + v3f[ 4] = p->org[1] - right[1] + up[1]; + v3f[ 5] = p->org[2] - right[2] + up[2]; + v3f[ 6] = p->org[0] + right[0] + up[0]; + v3f[ 7] = p->org[1] + right[1] + up[1]; + v3f[ 8] = p->org[2] + right[2] + up[2]; + v3f[ 9] = p->org[0] + right[0] - up[0]; + v3f[10] = p->org[1] + right[1] - up[1]; + v3f[11] = p->org[2] + right[2] - up[2]; + t2f[0] = tex->s1;t2f[1] = tex->t2; + t2f[2] = tex->s1;t2f[3] = tex->t1; + t2f[4] = tex->s2;t2f[5] = tex->t1; + t2f[6] = tex->s2;t2f[7] = tex->t2; + break; + case PARTICLE_SPARK: + len = VectorLength(p->vel); + VectorNormalize2(p->vel, up); + lenfactor = p->stretch * 0.04 * len; + if(lenfactor < size * 0.5) + lenfactor = size * 0.5; + VectorMA(p->org, -lenfactor, up, v); + VectorMA(p->org, lenfactor, up, up2); + R_CalcBeam_Vertex3f(v3f, v, up2, size); + t2f[0] = tex->s1;t2f[1] = tex->t2; + t2f[2] = tex->s1;t2f[3] = tex->t1; + t2f[4] = tex->s2;t2f[5] = tex->t1; + t2f[6] = tex->s2;t2f[7] = tex->t2; + break; + case PARTICLE_VBEAM: + R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size); + VectorSubtract(p->vel, p->org, up); + VectorNormalize(up); + v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch; + v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch; + t2f[0] = tex->s2;t2f[1] = v[0]; + t2f[2] = tex->s1;t2f[3] = v[0]; + t2f[4] = tex->s1;t2f[5] = v[1]; + t2f[6] = tex->s2;t2f[7] = v[1]; + break; + case PARTICLE_HBEAM: + R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size); + VectorSubtract(p->vel, p->org, up); + VectorNormalize(up); + v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch; + v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch; + t2f[0] = v[0];t2f[1] = tex->t1; + t2f[2] = v[0];t2f[3] = tex->t2; + t2f[4] = v[1];t2f[5] = tex->t2; + t2f[6] = v[1];t2f[7] = tex->t1; + break; + } + } + + // now render batches of particles based on blendmode and texture + blendmode = PBLEND_INVALID; + texture = NULL; + batchstart = 0; + batchcount = 0; + R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;) + { + p = cl.particles + surfacelist[surfacelistindex]; + + if (texture != particletexture[p->texnum].texture) + { + texture = particletexture[p->texnum].texture; + R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false); + } + + if (p->blendmode == PBLEND_INVMOD) + { + // inverse modulate blend - group these + GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + // iterate until we find a change in settings + batchstart = surfacelistindex++; + for (;surfacelistindex < numsurfaces;surfacelistindex++) + { + p = cl.particles + surfacelist[surfacelistindex]; + if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture) + break; + } + } + else + { + // additive or alpha blend - group these + // (we can group these because we premultiplied the texture alpha) + GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + // iterate until we find a change in settings + batchstart = surfacelistindex++; + for (;surfacelistindex < numsurfaces;surfacelistindex++) + { + p = cl.particles + surfacelist[surfacelistindex]; + if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture) + break; + } + } + + batchcount = surfacelistindex - batchstart; + R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0); + } +} + +void R_DrawParticles (void) +{ + int i, a; + int drawparticles = r_drawparticles.integer; + float minparticledist_start; + particle_t *p; + float gravity, frametime, f, dist, oldorg[3]; + float drawdist2; + int hitent; + trace_t trace; + qboolean update; + + frametime = bound(0, cl.time - cl.particles_updatetime, 1); + cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1); + + // LordHavoc: early out conditions + if (!cl.num_particles) + return; + + minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value; + gravity = frametime * cl.movevars_gravity; + update = frametime > 0; + drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality; + drawdist2 = drawdist2*drawdist2; + + for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++) + { + if (!p->typeindex) + { + if (cl.free_particle > i) + cl.free_particle = i; + continue; + } + + if (update) + { + if (p->delayedspawn > cl.time) + continue; + + p->size += p->sizeincrease * frametime; + p->alpha -= p->alphafade * frametime; + + if (p->alpha <= 0 || p->die <= cl.time) + goto killparticle; + + if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0) + { + if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)) + { + if (p->typeindex == pt_blood) + p->size += frametime * 8; + else + p->vel[2] -= p->gravity * gravity; + f = 1.0f - min(p->liquidfriction * frametime, 1); + VectorScale(p->vel, f, p->vel); + } + else + { + p->vel[2] -= p->gravity * gravity; + if (p->airfriction) + { + f = 1.0f - min(p->airfriction * frametime, 1); + VectorScale(p->vel, f, p->vel); + } + } + + VectorCopy(p->org, oldorg); + VectorMA(p->org, frametime, p->vel, p->org); +// if (p->bounce && cl.time >= p->delayedcollisions) + if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel)) + { + trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false, false); + // if the trace started in or hit something of SUPERCONTENTS_NODROP + // or if the trace hit something flagged as NOIMPACT + // then remove the particle + if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID)) + goto killparticle; + VectorCopy(trace.endpos, p->org); + // react if the particle hit something + if (trace.fraction < 1) + { + VectorCopy(trace.endpos, p->org); + + if (p->staintexnum >= 0) + { + // blood - splash on solid + if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)) + { + R_Stain(p->org, 16, + p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)), + p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f))); + if (cl_decals.integer) + { + // create a decal for the blood splat + a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]); + CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals! + } + } + } + + if (p->typeindex == pt_blood) + { + // blood - splash on solid + if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS) + goto killparticle; + if(p->staintexnum == -1) // staintex < -1 means no stains at all + { + R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f))); + if (cl_decals.integer) + { + // create a decal for the blood splat + CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768); + } + } + goto killparticle; + } + else if (p->bounce < 0) + { + // bounce -1 means remove on impact + goto killparticle; + } + else + { + // anything else - bounce off solid + dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce; + VectorMA(p->vel, dist, trace.plane.normal, p->vel); + } + } + } + + if (VectorLength2(p->vel) < 0.03) + { + if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off + goto killparticle; + VectorClear(p->vel); + } + } + + if (p->typeindex != pt_static) + { + switch (p->typeindex) + { + case pt_entityparticle: + // particle that removes itself after one rendered frame + if (p->time2) + goto killparticle; + else + p->time2 = 1; + break; + case pt_blood: + a = CL_PointSuperContents(p->org); + if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP)) + goto killparticle; + break; + case pt_bubble: + a = CL_PointSuperContents(p->org); + if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))) + goto killparticle; + break; + case pt_rain: + a = CL_PointSuperContents(p->org); + if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK)) + goto killparticle; + break; + case pt_snow: + if (cl.time > p->time2) + { + // snow flutter + p->time2 = cl.time + (rand() & 3) * 0.1; + p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32); + p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32); + } + a = CL_PointSuperContents(p->org); + if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK)) + goto killparticle; + break; + default: + break; + } + } + } + else if (p->delayedspawn > cl.time) + continue; + if (!drawparticles) + continue; + // don't render particles too close to the view (they chew fillrate) + // also don't render particles behind the view (useless) + // further checks to cull to the frustum would be too slow here + switch(p->typeindex) + { + case pt_beam: + // beams have no culling + R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL); + break; + default: + if(cl_particles_visculling.integer) + if (!r_refdef.viewcache.world_novis) + if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) + { + mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org); + if(leaf) + if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex)) + continue; + } + // anything else just has to be in front of the viewer and visible at this distance + if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size)) + R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL); + break; + } + + continue; +killparticle: + p->typeindex = 0; + if (cl.free_particle > i) + cl.free_particle = i; + } + + // reduce cl.num_particles if possible + while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0) + cl.num_particles--; + + if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES) + { + particle_t *oldparticles = cl.particles; + cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES); + cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t)); + memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t)); + Mem_Free(oldparticles); + } +} diff --git a/misc/source/darkplaces-src/cl_screen.c b/misc/source/darkplaces-src/cl_screen.c new file mode 100644 index 00000000..f9c2086a --- /dev/null +++ b/misc/source/darkplaces-src/cl_screen.c @@ -0,0 +1,2402 @@ + +#include "quakedef.h" +#include "cl_video.h" +#include "image.h" +#include "jpeg.h" +#include "image_png.h" +#include "cl_collision.h" +#include "libcurl.h" +#include "csprogs.h" +#include "cap_avi.h" +#include "cap_ogg.h" + +// we have to include snd_main.h here only to get access to snd_renderbuffer->format.speed when writing the AVI headers +#include "snd_main.h" + +cvar_t scr_viewsize = {CVAR_SAVE, "viewsize","100", "how large the view should be, 110 disables inventory bar, 120 disables status bar"}; +cvar_t scr_fov = {CVAR_SAVE, "fov","90", "field of vision, 1-170 degrees, default 90, some players use 110-130"}; +cvar_t scr_conalpha = {CVAR_SAVE, "scr_conalpha", "1", "opacity of console background gfx/conback"}; +cvar_t scr_conalphafactor = {CVAR_SAVE, "scr_conalphafactor", "1", "opacity of console background gfx/conback relative to scr_conalpha; when 0, gfx/conback is not drawn"}; +cvar_t scr_conalpha2factor = {CVAR_SAVE, "scr_conalpha2factor", "0", "opacity of console background gfx/conback2 relative to scr_conalpha; when 0, gfx/conback2 is not drawn"}; +cvar_t scr_conalpha3factor = {CVAR_SAVE, "scr_conalpha3factor", "0", "opacity of console background gfx/conback3 relative to scr_conalpha; when 0, gfx/conback3 is not drawn"}; +cvar_t scr_conbrightness = {CVAR_SAVE, "scr_conbrightness", "1", "brightness of console background (0 = black, 1 = image)"}; +cvar_t scr_conforcewhiledisconnected = {0, "scr_conforcewhiledisconnected", "1", "forces fullscreen console while disconnected"}; +cvar_t scr_conscroll_x = {CVAR_SAVE, "scr_conscroll_x", "0", "scroll speed of gfx/conback in x direction"}; +cvar_t scr_conscroll_y = {CVAR_SAVE, "scr_conscroll_y", "0", "scroll speed of gfx/conback in y direction"}; +cvar_t scr_conscroll2_x = {CVAR_SAVE, "scr_conscroll2_x", "0", "scroll speed of gfx/conback2 in x direction"}; +cvar_t scr_conscroll2_y = {CVAR_SAVE, "scr_conscroll2_y", "0", "scroll speed of gfx/conback2 in y direction"}; +cvar_t scr_conscroll3_x = {CVAR_SAVE, "scr_conscroll3_x", "0", "scroll speed of gfx/conback3 in x direction"}; +cvar_t scr_conscroll3_y = {CVAR_SAVE, "scr_conscroll3_y", "0", "scroll speed of gfx/conback3 in y direction"}; +cvar_t scr_menuforcewhiledisconnected = {0, "scr_menuforcewhiledisconnected", "0", "forces menu while disconnected"}; +cvar_t scr_centertime = {0, "scr_centertime","2", "how long centerprint messages show"}; +cvar_t scr_showram = {CVAR_SAVE, "showram","1", "show ram icon if low on surface cache memory (not used)"}; +cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0", "show turtle icon when framerate is too low"}; +cvar_t scr_showpause = {CVAR_SAVE, "showpause","1", "show pause icon when game is paused"}; +cvar_t scr_showbrand = {0, "showbrand","0", "shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered)"}; +cvar_t scr_printspeed = {0, "scr_printspeed","0", "speed of intermission printing (episode end texts), a value of 0 disables the slow printing"}; +cvar_t scr_loadingscreen_background = {0, "scr_loadingscreen_background","0", "show the last visible background during loading screen (costs one screenful of video memory)"}; +cvar_t scr_loadingscreen_scale = {0, "scr_loadingscreen_scale","1", "scale factor of the background"}; +cvar_t scr_loadingscreen_scale_base = {0, "scr_loadingscreen_scale_base","0", "0 = console pixels, 1 = video pixels"}; +cvar_t scr_loadingscreen_scale_limit = {0, "scr_loadingscreen_scale_limit","0", "0 = no limit, 1 = until first edge hits screen edge, 2 = until last edge hits screen edge, 3 = until width hits screen width, 4 = until height hits screen height"}; +cvar_t scr_loadingscreen_count = {0, "scr_loadingscreen_count","1", "number of loading screen files to use randomly (named loading.tga, loading2.tga, loading3.tga, ...)"}; +cvar_t scr_loadingscreen_barcolor = {0, "scr_loadingscreen_barcolor", "0 0 1", "rgb color of loadingscreen progress bar"}; +cvar_t scr_loadingscreen_barheight = {0, "scr_loadingscreen_barheight", "8", "the height of the loadingscreen progress bar"}; +cvar_t scr_infobar_height = {0, "scr_infobar_height", "8", "the height of the infobar items"}; +cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640", "virtual width of 2D graphics system"}; +cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480", "virtual height of 2D graphics system"}; +cvar_t vid_pixelheight = {CVAR_SAVE, "vid_pixelheight", "1", "adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example)"}; +cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","1", "save jpeg instead of targa"}; +cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9", "image quality of saved jpeg"}; +cvar_t scr_screenshot_png = {CVAR_SAVE, "scr_screenshot_png","0", "save png instead of targa"}; +cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1", "gamma correction on saved screenshots and videos, 1.0 saves unmodified images"}; +cvar_t scr_screenshot_hwgamma = {CVAR_SAVE, "scr_screenshot_hwgamma","1", "apply the video gamma ramp to saved screenshots and videos"}; +cvar_t scr_screenshot_alpha = {CVAR_SAVE, "scr_screenshot_alpha","0", "try to write an alpha channel to screenshots (debugging feature)"}; +// scr_screenshot_name is defined in fs.c +cvar_t cl_capturevideo = {0, "cl_capturevideo", "0", "enables saving of video to a .avi file using uncompressed I420 colorspace and PCM audio, note that scr_screenshot_gammaboost affects the brightness of the output)"}; +cvar_t cl_capturevideo_printfps = {CVAR_SAVE, "cl_capturevideo_printfps", "1", "prints the frames per second captured in capturevideo (is only written to the log file, not to the console, as that would be visible on the video)"}; +cvar_t cl_capturevideo_width = {CVAR_SAVE, "cl_capturevideo_width", "0", "scales all frames to this resolution before saving the video"}; +cvar_t cl_capturevideo_height = {CVAR_SAVE, "cl_capturevideo_height", "0", "scales all frames to this resolution before saving the video"}; +cvar_t cl_capturevideo_realtime = {0, "cl_capturevideo_realtime", "0", "causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over a minute"}; +cvar_t cl_capturevideo_fps = {CVAR_SAVE, "cl_capturevideo_fps", "30", "how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful)"}; +cvar_t cl_capturevideo_nameformat = {CVAR_SAVE, "cl_capturevideo_nameformat", "dpvideo", "prefix for saved videos (the date is encoded using strftime escapes)"}; +cvar_t cl_capturevideo_number = {CVAR_SAVE, "cl_capturevideo_number", "1", "number to append to video filename, incremented each time a capture begins"}; +cvar_t cl_capturevideo_ogg = {CVAR_SAVE, "cl_capturevideo_ogg", "1", "save captured video data as Ogg/Vorbis/Theora streams"}; +cvar_t cl_capturevideo_framestep = {CVAR_SAVE, "cl_capturevideo_framestep", "1", "when set to n >= 1, render n frames to capture one (useful for motion blur like effects)"}; +cvar_t r_letterbox = {0, "r_letterbox", "0", "reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes)"}; +cvar_t r_stereo_separation = {0, "r_stereo_separation", "4", "separation distance of eyes in the world (negative values are only useful for cross-eyed viewing)"}; +cvar_t r_stereo_sidebyside = {0, "r_stereo_sidebyside", "0", "side by side views for those who can't afford glasses but can afford eye strain (note: use a negative r_stereo_separation if you want cross-eyed viewing)"}; +cvar_t r_stereo_horizontal = {0, "r_stereo_horizontal", "0", "aspect skewed side by side view for special decoder/display hardware"}; +cvar_t r_stereo_vertical = {0, "r_stereo_vertical", "0", "aspect skewed top and bottom view for special decoder/display hardware"}; +cvar_t r_stereo_redblue = {0, "r_stereo_redblue", "0", "red/blue anaglyph stereo glasses (note: most of these glasses are actually red/cyan, try that one too)"}; +cvar_t r_stereo_redcyan = {0, "r_stereo_redcyan", "0", "red/cyan anaglyph stereo glasses, the kind given away at drive-in movies like Creature From The Black Lagoon In 3D"}; +cvar_t r_stereo_redgreen = {0, "r_stereo_redgreen", "0", "red/green anaglyph stereo glasses (for those who don't mind yellow)"}; +cvar_t r_stereo_angle = {0, "r_stereo_angle", "0", "separation angle of eyes (makes the views look different directions, as an example, 90 gives a 90 degree separation where the views are 45 degrees left and 45 degrees right)"}; +cvar_t scr_zoomwindow = {CVAR_SAVE, "scr_zoomwindow", "0", "displays a zoomed in overlay window"}; +cvar_t scr_zoomwindow_viewsizex = {CVAR_SAVE, "scr_zoomwindow_viewsizex", "20", "horizontal viewsize of zoom window"}; +cvar_t scr_zoomwindow_viewsizey = {CVAR_SAVE, "scr_zoomwindow_viewsizey", "20", "vertical viewsize of zoom window"}; +cvar_t scr_zoomwindow_fov = {CVAR_SAVE, "scr_zoomwindow_fov", "20", "fov of zoom window"}; +cvar_t scr_stipple = {0, "scr_stipple", "0", "interlacing-like stippling of the display"}; +cvar_t scr_refresh = {0, "scr_refresh", "1", "allows you to completely shut off rendering for benchmarking purposes"}; +cvar_t scr_screenshot_name_in_mapdir = {CVAR_SAVE, "scr_screenshot_name_in_mapdir", "0", "if set to 1, screenshots are placed in a subdirectory named like the map they are from"}; +cvar_t shownetgraph = {CVAR_SAVE, "shownetgraph", "0", "shows a graph of packet sizes and other information, 0 = off, 1 = show client netgraph, 2 = show client and server netgraphs (when hosting a server)"}; +cvar_t cl_demo_mousegrab = {0, "cl_demo_mousegrab", "0", "Allows reading the mouse input while playing demos. Useful for camera mods developed in csqc. (0: never, 1: always)"}; +cvar_t timedemo_screenshotframelist = {0, "timedemo_screenshotframelist", "", "when performing a timedemo, take screenshots of each frame in this space-separated list - example: 1 201 401"}; +cvar_t vid_touchscreen_outlinealpha = {0, "vid_touchscreen_outlinealpha", "0.25", "opacity of touchscreen area outlines"}; +cvar_t vid_touchscreen_overlayalpha = {0, "vid_touchscreen_overlayalpha", "0.25", "opacity of touchscreen area icons"}; + +extern cvar_t v_glslgamma; +extern cvar_t sbar_info_pos; +extern cvar_t r_fog_clear; +#define WANT_SCREENSHOT_HWGAMMA (scr_screenshot_hwgamma.integer && vid_usinghwgamma) + +int jpeg_supported = false; + +qboolean scr_initialized; // ready to draw + +float scr_con_current; +int scr_con_margin_bottom; + +extern int con_vislines; + +static void SCR_ScreenShot_f (void); +static void R_Envmap_f (void); + +// backend +void R_ClearScreen(qboolean fogcolor); + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + +char scr_centerstring[MAX_INPUTLINE]; +float scr_centertime_start; // for slow victory printing +float scr_centertime_off; +int scr_center_lines; +int scr_erase_lines; +int scr_erase_center; +char scr_infobarstring[MAX_INPUTLINE]; +float scr_infobartime_off; + +/* +============== +SCR_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void SCR_CenterPrint(const char *str) +{ + strlcpy (scr_centerstring, str, sizeof (scr_centerstring)); + scr_centertime_off = scr_centertime.value; + scr_centertime_start = cl.time; + +// count the number of lines for centering + scr_center_lines = 1; + while (*str) + { + if (*str == '\n') + scr_center_lines++; + str++; + } +} + + +void SCR_DrawCenterString (void) +{ + char *start; + int x, y; + int remaining; + int color; + + if(cl.intermission == 2) // in finale, + if(sb_showscores) // make TAB hide the finale message (sb_showscores overrides finale in sbar.c) + return; + + if(scr_centertime.value <= 0 && !cl.intermission) + return; + +// the finale prints the characters one at a time, except if printspeed is an absurdly high value + if (cl.intermission && scr_printspeed.value > 0 && scr_printspeed.value < 1000000) + remaining = (int)(scr_printspeed.value * (cl.time - scr_centertime_start)); + else + remaining = 9999; + + scr_erase_center = 0; + start = scr_centerstring; + + if (remaining < 1) + return; + + if (scr_center_lines <= 4) + y = (int)(vid_conheight.integer*0.35); + else + y = 48; + + color = -1; + do + { + // scan the number of characters on the line, not counting color codes + char *newline = strchr(start, '\n'); + int l = newline ? (newline - start) : (int)strlen(start); + float width = DrawQ_TextWidth(start, l, 8, 8, false, FONT_CENTERPRINT); + + x = (int) (vid_conwidth.integer - width)/2; + if (l > 0) + { + if (remaining < l) + l = remaining; + DrawQ_String(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color, false, FONT_CENTERPRINT); + remaining -= l; + if (remaining <= 0) + return; + } + y += 8; + + if (!newline) + break; + start = newline + 1; // skip the \n + } while (1); +} + +void SCR_CheckDrawCenterString (void) +{ + if (scr_center_lines > scr_erase_lines) + scr_erase_lines = scr_center_lines; + + if (cl.time > cl.oldtime) + scr_centertime_off -= cl.time - cl.oldtime; + + // don't draw if this is a normal stats-screen intermission, + // only if it is not an intermission, or a finale intermission + if (cl.intermission == 1) + return; + if (scr_centertime_off <= 0 && !cl.intermission) + return; + if (key_dest != key_game) + return; + + SCR_DrawCenterString (); +} + +void SCR_DrawNetGraph_DrawGraph (int graphx, int graphy, int graphwidth, int graphheight, float graphscale, const char *label, float textsize, int packetcounter, netgraphitem_t *netgraph) +{ + netgraphitem_t *graph; + int j, x, y, numlines; + int totalbytes = 0; + char bytesstring[128]; + float g[NETGRAPH_PACKETS][6]; + float *a; + float *b; + float vertex3f[(NETGRAPH_PACKETS+2)*5*2*3]; + float color4f[(NETGRAPH_PACKETS+2)*5*2*4]; + float *v; + float *c; + DrawQ_Fill(graphx, graphy, graphwidth, graphheight + textsize * 2, 0, 0, 0, 0.5, 0); + // draw the bar graph itself + // advance the packet counter because it is the latest packet column being + // built up and should come last + packetcounter = (packetcounter + 1) % NETGRAPH_PACKETS; + memset(g, 0, sizeof(g)); + for (j = 0;j < NETGRAPH_PACKETS;j++) + { + graph = netgraph + j; + g[j][0] = 1.0f - 0.25f * (realtime - graph->time); + g[j][1] = 1.0f; + g[j][2] = 1.0f; + g[j][3] = 1.0f; + g[j][4] = 1.0f; + g[j][5] = 1.0f; + if (graph->unreliablebytes == NETGRAPH_LOSTPACKET) + g[j][1] = 0.00f; + else if (graph->unreliablebytes == NETGRAPH_CHOKEDPACKET) + g[j][2] = 0.96f; + else + { + g[j][3] = 1.0f - graph->unreliablebytes * graphscale; + g[j][4] = g[j][3] - graph->reliablebytes * graphscale; + g[j][5] = g[j][4] - graph->ackbytes * graphscale; + // count bytes in the last second + if (realtime - graph->time < 1.0f) + totalbytes += graph->unreliablebytes + graph->reliablebytes + graph->ackbytes; + } + g[j][1] = bound(0.0f, g[j][1], 1.0f); + g[j][2] = bound(0.0f, g[j][2], 1.0f); + g[j][3] = bound(0.0f, g[j][3], 1.0f); + g[j][4] = bound(0.0f, g[j][4], 1.0f); + g[j][5] = bound(0.0f, g[j][5], 1.0f); + } + // render the lines for the graph + numlines = 0; + v = vertex3f; + c = color4f; + for (j = 0;j < NETGRAPH_PACKETS;j++) + { + a = g[j]; + b = g[(j+1)%NETGRAPH_PACKETS]; + if (a[0] < 0.0f || b[0] > 1.0f || b[0] < a[0]) + continue; + VectorSet(v, graphx + graphwidth * a[0], graphy + graphheight * a[2], 0.0f);v += 3;Vector4Set(c, 1.0f, 1.0f, 0.0f, 1.0f);c += 4; + VectorSet(v, graphx + graphwidth * b[0], graphy + graphheight * b[2], 0.0f);v += 3;Vector4Set(c, 1.0f, 1.0f, 0.0f, 1.0f);c += 4; + + VectorSet(v, graphx + graphwidth * a[0], graphy + graphheight * a[1], 0.0f);v += 3;Vector4Set(c, 1.0f, 0.0f, 0.0f, 1.0f);c += 4; + VectorSet(v, graphx + graphwidth * b[0], graphy + graphheight * b[1], 0.0f);v += 3;Vector4Set(c, 1.0f, 0.0f, 0.0f, 1.0f);c += 4; + + VectorSet(v, graphx + graphwidth * a[0], graphy + graphheight * a[5], 0.0f);v += 3;Vector4Set(c, 0.0f, 1.0f, 0.0f, 1.0f);c += 4; + VectorSet(v, graphx + graphwidth * b[0], graphy + graphheight * b[5], 0.0f);v += 3;Vector4Set(c, 0.0f, 1.0f, 0.0f, 1.0f);c += 4; + + VectorSet(v, graphx + graphwidth * a[0], graphy + graphheight * a[4], 0.0f);v += 3;Vector4Set(c, 1.0f, 1.0f, 1.0f, 1.0f);c += 4; + VectorSet(v, graphx + graphwidth * b[0], graphy + graphheight * b[4], 0.0f);v += 3;Vector4Set(c, 1.0f, 1.0f, 1.0f, 1.0f);c += 4; + + VectorSet(v, graphx + graphwidth * a[0], graphy + graphheight * a[3], 0.0f);v += 3;Vector4Set(c, 1.0f, 0.5f, 0.0f, 1.0f);c += 4; + VectorSet(v, graphx + graphwidth * b[0], graphy + graphheight * b[3], 0.0f);v += 3;Vector4Set(c, 1.0f, 0.5f, 0.0f, 1.0f);c += 4; + + numlines += 5; + } + if (numlines > 0) + DrawQ_Lines(0.0f, numlines, vertex3f, color4f, 0); + x = graphx; + y = graphy + graphheight; + dpsnprintf(bytesstring, sizeof(bytesstring), "%i", totalbytes); + DrawQ_String(x, y, label , 0, textsize, textsize, 1.0f, 1.0f, 1.0f, 1.0f, 0, NULL, false, FONT_DEFAULT);y += textsize; + DrawQ_String(x, y, bytesstring, 0, textsize, textsize, 1.0f, 1.0f, 1.0f, 1.0f, 0, NULL, false, FONT_DEFAULT);y += textsize; +} + +/* +============== +SCR_DrawNetGraph +============== +*/ +void SCR_DrawNetGraph (void) +{ + int i, separator1, separator2, graphwidth, graphheight, netgraph_x, netgraph_y, textsize, index, netgraphsperrow; + float graphscale; + netconn_t *c; + + if (cls.state != ca_connected) + return; + if (!cls.netcon) + return; + if (!shownetgraph.integer) + return; + + separator1 = 2; + separator2 = 4; + textsize = 8; + graphwidth = 120; + graphheight = 70; + graphscale = 1.0f / 1500.0f; + + netgraphsperrow = (vid_conwidth.integer + separator2) / (graphwidth * 2 + separator1 + separator2); + netgraphsperrow = max(netgraphsperrow, 1); + + index = 0; + netgraph_x = (vid_conwidth.integer + separator2) - (1 + (index % netgraphsperrow)) * (graphwidth * 2 + separator1 + separator2); + netgraph_y = (vid_conheight.integer - 48 - sbar_info_pos.integer + separator2) - (1 + (index / netgraphsperrow)) * (graphheight + textsize + separator2); + c = cls.netcon; + SCR_DrawNetGraph_DrawGraph(netgraph_x , netgraph_y, graphwidth, graphheight, graphscale, "incoming", textsize, c->incoming_packetcounter, c->incoming_netgraph); + SCR_DrawNetGraph_DrawGraph(netgraph_x + graphwidth + separator1, netgraph_y, graphwidth, graphheight, graphscale, "outgoing", textsize, c->outgoing_packetcounter, c->outgoing_netgraph); + index++; + + if (sv.active && shownetgraph.integer >= 2) + { + for (i = 0;i < svs.maxclients;i++) + { + c = svs.clients[i].netconnection; + if (!c) + continue; + netgraph_x = (vid_conwidth.integer + separator2) - (1 + (index % netgraphsperrow)) * (graphwidth * 2 + separator1 + separator2); + netgraph_y = (vid_conheight.integer - 48 + separator2) - (1 + (index / netgraphsperrow)) * (graphheight + textsize + separator2); + SCR_DrawNetGraph_DrawGraph(netgraph_x , netgraph_y, graphwidth, graphheight, graphscale, va("%s", svs.clients[i].name), textsize, c->outgoing_packetcounter, c->outgoing_netgraph); + SCR_DrawNetGraph_DrawGraph(netgraph_x + graphwidth + separator1, netgraph_y, graphwidth, graphheight, graphscale, "" , textsize, c->incoming_packetcounter, c->incoming_netgraph); + index++; + } + } +} + +/* +============== +SCR_DrawTurtle +============== +*/ +void SCR_DrawTurtle (void) +{ + static int count; + + if (cls.state != ca_connected) + return; + + if (!scr_showturtle.integer) + return; + + if (cl.realframetime < 0.1) + { + count = 0; + return; + } + + count++; + if (count < 3) + return; + + DrawQ_Pic (0, 0, Draw_CachePic ("gfx/turtle"), 0, 0, 1, 1, 1, 1, 0); +} + +/* +============== +SCR_DrawNet +============== +*/ +void SCR_DrawNet (void) +{ + if (cls.state != ca_connected) + return; + if (realtime - cl.last_received_message < 0.3) + return; + if (cls.demoplayback) + return; + + DrawQ_Pic (64, 0, Draw_CachePic ("gfx/net"), 0, 0, 1, 1, 1, 1, 0); +} + +/* +============== +DrawPause +============== +*/ +void SCR_DrawPause (void) +{ + cachepic_t *pic; + + if (cls.state != ca_connected) + return; + + if (!scr_showpause.integer) // turn off for screenshots + return; + + if (!cl.paused) + return; + + pic = Draw_CachePic ("gfx/pause"); + DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, pic, 0, 0, 1, 1, 1, 1, 0); +} + +/* +============== +SCR_DrawBrand +============== +*/ +void SCR_DrawBrand (void) +{ + cachepic_t *pic; + float x, y; + + if (!scr_showbrand.value) + return; + + pic = Draw_CachePic ("gfx/brand"); + + switch ((int)scr_showbrand.value) + { + case 1: // bottom left + x = 0; + y = vid_conheight.integer - pic->height; + break; + case 2: // bottom centre + x = (vid_conwidth.integer - pic->width) / 2; + y = vid_conheight.integer - pic->height; + break; + case 3: // bottom right + x = vid_conwidth.integer - pic->width; + y = vid_conheight.integer - pic->height; + break; + case 4: // centre right + x = vid_conwidth.integer - pic->width; + y = (vid_conheight.integer - pic->height) / 2; + break; + case 5: // top right + x = vid_conwidth.integer - pic->width; + y = 0; + break; + case 6: // top centre + x = (vid_conwidth.integer - pic->width) / 2; + y = 0; + break; + case 7: // top left + x = 0; + y = 0; + break; + case 8: // centre left + x = 0; + y = (vid_conheight.integer - pic->height) / 2; + break; + default: + return; + } + + DrawQ_Pic (x, y, pic, 0, 0, 1, 1, 1, 1, 0); +} + +/* +============== +SCR_DrawQWDownload +============== +*/ +static int SCR_DrawQWDownload(int offset) +{ + // sync with SCR_InfobarHeight + int len; + float x, y; + float size = scr_infobar_height.value; + char temp[256]; + + if (!cls.qw_downloadname[0]) + { + cls.qw_downloadspeedrate = 0; + cls.qw_downloadspeedtime = realtime; + cls.qw_downloadspeedcount = 0; + return 0; + } + if (realtime >= cls.qw_downloadspeedtime + 1) + { + cls.qw_downloadspeedrate = cls.qw_downloadspeedcount; + cls.qw_downloadspeedtime = realtime; + cls.qw_downloadspeedcount = 0; + } + if (cls.protocol == PROTOCOL_QUAKEWORLD) + dpsnprintf(temp, sizeof(temp), "Downloading %s %3i%% (%i) at %i bytes/s", cls.qw_downloadname, cls.qw_downloadpercent, cls.qw_downloadmemorycursize, cls.qw_downloadspeedrate); + else + dpsnprintf(temp, sizeof(temp), "Downloading %s %3i%% (%i/%i) at %i bytes/s", cls.qw_downloadname, cls.qw_downloadpercent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize, cls.qw_downloadspeedrate); + len = (int)strlen(temp); + x = (vid_conwidth.integer - DrawQ_TextWidth(temp, len, size, size, true, FONT_INFOBAR)) / 2; + y = vid_conheight.integer - size - offset; + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); + DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + return size; +} +/* +============== +SCR_DrawInfobarString +============== +*/ +static int SCR_DrawInfobarString(int offset) +{ + int len; + float x, y; + float size = scr_infobar_height.value; + + len = (int)strlen(scr_infobarstring); + x = (vid_conwidth.integer - DrawQ_TextWidth(scr_infobarstring, len, size, size, false, FONT_INFOBAR)) / 2; + y = vid_conheight.integer - size - offset; + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); + DrawQ_String(x, y, scr_infobarstring, len, size, size, 1, 1, 1, 1, 0, NULL, false, FONT_INFOBAR); + return size; +} + +/* +============== +SCR_DrawCurlDownload +============== +*/ +static int SCR_DrawCurlDownload(int offset) +{ + // sync with SCR_InfobarHeight + int len; + int nDownloads; + int i; + float x, y; + float size = scr_infobar_height.value; + Curl_downloadinfo_t *downinfo; + char temp[256]; + const char *addinfo; + + downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo); + if(!downinfo) + return 0; + + y = vid_conheight.integer - size * nDownloads - offset; + + if(addinfo) + { + len = (int)strlen(addinfo); + x = (vid_conwidth.integer - DrawQ_TextWidth(addinfo, len, size, size, true, FONT_INFOBAR)) / 2; + DrawQ_Fill(0, y - size, vid_conwidth.integer, size, 1, 1, 1, cls.signon == SIGNONS ? 0.8 : 1, 0); + DrawQ_String(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0, NULL, true, FONT_INFOBAR); + } + + for(i = 0; i != nDownloads; ++i) + { + if(downinfo[i].queued) + dpsnprintf(temp, sizeof(temp), "Still in queue: %s", downinfo[i].filename); + else if(downinfo[i].progress <= 0) + dpsnprintf(temp, sizeof(temp), "Downloading %s ... ???.?%% @ %.1f KiB/s", downinfo[i].filename, downinfo[i].speed / 1024.0); + else + dpsnprintf(temp, sizeof(temp), "Downloading %s ... %5.1f%% @ %.1f KiB/s", downinfo[i].filename, 100.0 * downinfo[i].progress, downinfo[i].speed / 1024.0); + len = (int)strlen(temp); + x = (vid_conwidth.integer - DrawQ_TextWidth(temp, len, size, size, true, FONT_INFOBAR)) / 2; + DrawQ_Fill(0, y + i * size, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); + DrawQ_String(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + } + + Z_Free(downinfo); + + return size * (nDownloads + (addinfo ? 1 : 0)); +} + +/* +============== +SCR_DrawInfobar +============== +*/ +static void SCR_DrawInfobar(void) +{ + int offset = 0; + offset += SCR_DrawQWDownload(offset); + offset += SCR_DrawCurlDownload(offset); + if(scr_infobartime_off > 0) + offset += SCR_DrawInfobarString(offset); + if(offset != scr_con_margin_bottom) + Con_DPrintf("broken console margin calculation: %d != %d\n", offset, scr_con_margin_bottom); +} + +static int SCR_InfobarHeight(void) +{ + int offset = 0; + Curl_downloadinfo_t *downinfo; + const char *addinfo; + int nDownloads; + + if (cl.time > cl.oldtime) + scr_infobartime_off -= cl.time - cl.oldtime; + if(scr_infobartime_off > 0) + offset += 1; + if(cls.qw_downloadname[0]) + offset += 1; + + downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo); + if(downinfo) + { + offset += (nDownloads + (addinfo ? 1 : 0)); + Z_Free(downinfo); + } + offset *= scr_infobar_height.value; + + return offset; +} + +/* +============== +SCR_InfoBar_f +============== +*/ +void SCR_InfoBar_f(void) +{ + if(Cmd_Argc() == 3) + { + scr_infobartime_off = atof(Cmd_Argv(1)); + strlcpy(scr_infobarstring, Cmd_Argv(2), sizeof(scr_infobarstring)); + } + else + { + Con_Printf("usage:\ninfobar expiretime \"string\"\n"); + } +} +//============================================================================= + +/* +================== +SCR_SetUpToDrawConsole +================== +*/ +void SCR_SetUpToDrawConsole (void) +{ + // lines of console to display + float conlines; + static int framecounter = 0; + + Con_CheckResize (); + + if (scr_menuforcewhiledisconnected.integer && key_dest == key_game && cls.state == ca_disconnected) + { + if (framecounter >= 2) + MR_ToggleMenu(1); + else + framecounter++; + } + else + framecounter = 0; + + if (scr_conforcewhiledisconnected.integer && key_dest == key_game && cls.signon != SIGNONS) + key_consoleactive |= KEY_CONSOLEACTIVE_FORCED; + else + key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED; + +// decide on the height of the console + if (key_consoleactive & KEY_CONSOLEACTIVE_USER) + conlines = vid_conheight.integer/2; // half screen + else + conlines = 0; // none visible + + scr_con_current = conlines; +} + +/* +================== +SCR_DrawConsole +================== +*/ +void SCR_DrawConsole (void) +{ + scr_con_margin_bottom = SCR_InfobarHeight(); + if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED) + { + // full screen + Con_DrawConsole (vid_conheight.integer - scr_con_margin_bottom); + } + else if (scr_con_current) + Con_DrawConsole (min((int)scr_con_current, vid_conheight.integer - scr_con_margin_bottom)); + else + con_vislines = 0; +} + +/* +=============== +SCR_BeginLoadingPlaque + +================ +*/ +void SCR_BeginLoadingPlaque (void) +{ + // save console log up to this point to log_file if it was set by configs + Log_Start(); + + Host_StartVideo(); + SCR_UpdateLoadingScreen(false); +} + +//============================================================================= + +char r_speeds_timestring[4096]; +int speedstringcount, r_timereport_active; +double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0; +int r_speeds_longestitem = 0; + +void R_TimeReport(const char *desc) +{ + char tempbuf[256]; + int length; + int t; + + if (r_speeds.integer < 2 || !r_timereport_active) + return; + + CHECKGLERROR + if (r_speeds.integer == 2) + GL_Finish(); + CHECKGLERROR + r_timereport_temp = r_timereport_current; + r_timereport_current = Sys_DoubleTime(); + t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0 + 0.5); + + length = dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %s", t, desc); + length = min(length, (int)sizeof(tempbuf) - 1); + if (r_speeds_longestitem < length) + r_speeds_longestitem = length; + for (;length < r_speeds_longestitem;length++) + tempbuf[length] = ' '; + tempbuf[length] = 0; + + if (speedstringcount + length > (vid_conwidth.integer / 8)) + { + strlcat(r_speeds_timestring, "\n", sizeof(r_speeds_timestring)); + speedstringcount = 0; + } + strlcat(r_speeds_timestring, tempbuf, sizeof(r_speeds_timestring)); + speedstringcount += length; +} + +void R_TimeReport_BeginFrame(void) +{ + speedstringcount = 0; + r_speeds_timestring[0] = 0; + r_timereport_active = false; + memset(&r_refdef.stats, 0, sizeof(r_refdef.stats)); + + if (r_speeds.integer >= 2) + { + r_timereport_active = true; + r_timereport_start = r_timereport_current = Sys_DoubleTime(); + } +} + +static int R_CountLeafTriangles(const dp_model_t *model, const mleaf_t *leaf) +{ + int i, triangles = 0; + for (i = 0;i < leaf->numleafsurfaces;i++) + triangles += model->data_surfaces[leaf->firstleafsurface[i]].num_triangles; + return triangles; +} + +extern cvar_t r_viewscale; +extern float viewscalefpsadjusted; +void R_TimeReport_EndFrame(void) +{ + int i, j, lines, y; + cl_locnode_t *loc; + char string[1024+4096]; + mleaf_t *viewleaf; + + string[0] = 0; + if (r_speeds.integer) + { + // put the location name in the r_speeds display as it greatly helps + // when creating loc files + loc = CL_Locs_FindNearest(cl.movement_origin); + viewleaf = (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) ? r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, r_refdef.view.origin) : NULL; + dpsnprintf(string, sizeof(string), +"%6.0fus rendertime %3.0f%% viewscale %s%s %.3f cl.time\n" +"%3i renders org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n" +"%5i viewleaf%5i cluster%3i area%4i brushes%4i surfaces(%7i triangles)\n" +"%7i surfaces%7i triangles %5i entities (%7i surfaces%7i triangles)\n" +"%5i leafs%5i portals%6i/%6i particles%6i/%6i decals %3i%% quality\n" +"%7i lightmap updates (%7i pixels)%8iKB/%8iKB framedata\n" +"%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n" +"bouncegrid:%4i lights%6i particles%6i traces%6i hits%6i splats%6i bounces\n" +"collision cache efficiency:%6i cached%6i traced%6ianimated\n" +"%6i draws%8i vertices%8i triangles bloompixels%8i copied%8i drawn\n" +"updated%5i indexbuffers%8i bytes%5i vertexbuffers%8i bytes\n" +"%s" +, r_refdef.lastdrawscreentime * 1000000.0, r_viewscale.value * sqrt(viewscalefpsadjusted) * 100.0f, loc ? "Location: " : "", loc ? loc->name : "", cl.time +, r_refdef.stats.renders, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], r_refdef.view.forward[0], r_refdef.view.forward[1], r_refdef.view.forward[2] +, viewleaf ? (int)(viewleaf - r_refdef.scene.worldmodel->brush.data_leafs) : -1, viewleaf ? viewleaf->clusterindex : -1, viewleaf ? viewleaf->areaindex : -1, viewleaf ? viewleaf->numleafbrushes : 0, viewleaf ? viewleaf->numleafsurfaces : 0, viewleaf ? R_CountLeafTriangles(r_refdef.scene.worldmodel, viewleaf) : 0 +, r_refdef.stats.world_surfaces, r_refdef.stats.world_triangles, r_refdef.stats.entities, r_refdef.stats.entities_surfaces, r_refdef.stats.entities_triangles +, r_refdef.stats.world_leafs, r_refdef.stats.world_portals, r_refdef.stats.particles, cl.num_particles, r_refdef.stats.drawndecals, r_refdef.stats.totaldecals, (int)(100 * r_refdef.view.quality) +, r_refdef.stats.lightmapupdates, r_refdef.stats.lightmapupdatepixels, (r_refdef.stats.framedatacurrent+512) / 1024, (r_refdef.stats.framedatasize+512)/1024 +, r_refdef.stats.lights, r_refdef.stats.lights_clears, r_refdef.stats.lights_scissored, r_refdef.stats.lights_lighttriangles, r_refdef.stats.lights_shadowtriangles, r_refdef.stats.lights_dynamicshadowtriangles +, r_refdef.stats.bouncegrid_lights, r_refdef.stats.bouncegrid_particles, r_refdef.stats.bouncegrid_traces, r_refdef.stats.bouncegrid_hits, r_refdef.stats.bouncegrid_splats, r_refdef.stats.bouncegrid_bounces +, r_refdef.stats.collisioncache_cached, r_refdef.stats.collisioncache_traced, r_refdef.stats.collisioncache_animated +, r_refdef.stats.draws, r_refdef.stats.draws_vertices, r_refdef.stats.draws_elements / 3, r_refdef.stats.bloom_copypixels, r_refdef.stats.bloom_drawpixels +, r_refdef.stats.indexbufferuploadcount, r_refdef.stats.indexbufferuploadsize, r_refdef.stats.vertexbufferuploadcount, r_refdef.stats.vertexbufferuploadsize +, r_speeds_timestring); + + memset(&r_refdef.stats, 0, sizeof(r_refdef.stats)); + + speedstringcount = 0; + r_speeds_timestring[0] = 0; + r_timereport_active = false; + + if (r_speeds.integer >= 2) + { + r_timereport_active = true; + r_timereport_start = r_timereport_current = Sys_DoubleTime(); + } + } + + if (string[0]) + { + if (string[strlen(string)-1] == '\n') + string[strlen(string)-1] = 0; + lines = 1; + for (i = 0;string[i];i++) + if (string[i] == '\n') + lines++; + y = vid_conheight.integer - sb_lines - lines * 8; + i = j = 0; + r_draw2d_force = true; + DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0); + while (string[i]) + { + j = i; + while (string[i] && string[i] != '\n') + i++; + if (i - j > 0) + DrawQ_String(0, y, string + j, i - j, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT); + if (string[i] == '\n') + i++; + y += 8; + } + r_draw2d_force = false; + } +} + +/* +================= +SCR_SizeUp_f + +Keybinding command +================= +*/ +void SCR_SizeUp_f (void) +{ + Cvar_SetValue ("viewsize",scr_viewsize.value+10); +} + + +/* +================= +SCR_SizeDown_f + +Keybinding command +================= +*/ +void SCR_SizeDown_f (void) +{ + Cvar_SetValue ("viewsize",scr_viewsize.value-10); +} + +void SCR_CaptureVideo_EndVideo(void); +void CL_Screen_Shutdown(void) +{ + SCR_CaptureVideo_EndVideo(); +} + +void CL_Screen_Init(void) +{ + Cvar_RegisterVariable (&scr_fov); + Cvar_RegisterVariable (&scr_viewsize); + Cvar_RegisterVariable (&scr_conalpha); + Cvar_RegisterVariable (&scr_conalphafactor); + Cvar_RegisterVariable (&scr_conalpha2factor); + Cvar_RegisterVariable (&scr_conalpha3factor); + Cvar_RegisterVariable (&scr_conscroll_x); + Cvar_RegisterVariable (&scr_conscroll_y); + Cvar_RegisterVariable (&scr_conscroll2_x); + Cvar_RegisterVariable (&scr_conscroll2_y); + Cvar_RegisterVariable (&scr_conscroll3_x); + Cvar_RegisterVariable (&scr_conscroll3_y); + Cvar_RegisterVariable (&scr_conbrightness); + Cvar_RegisterVariable (&scr_conforcewhiledisconnected); + Cvar_RegisterVariable (&scr_menuforcewhiledisconnected); + Cvar_RegisterVariable (&scr_loadingscreen_background); + Cvar_RegisterVariable (&scr_loadingscreen_scale); + Cvar_RegisterVariable (&scr_loadingscreen_scale_base); + Cvar_RegisterVariable (&scr_loadingscreen_scale_limit); + Cvar_RegisterVariable (&scr_loadingscreen_count); + Cvar_RegisterVariable (&scr_loadingscreen_barcolor); + Cvar_RegisterVariable (&scr_loadingscreen_barheight); + Cvar_RegisterVariable (&scr_infobar_height); + Cvar_RegisterVariable (&scr_showram); + Cvar_RegisterVariable (&scr_showturtle); + Cvar_RegisterVariable (&scr_showpause); + Cvar_RegisterVariable (&scr_showbrand); + Cvar_RegisterVariable (&scr_centertime); + Cvar_RegisterVariable (&scr_printspeed); + Cvar_RegisterVariable (&vid_conwidth); + Cvar_RegisterVariable (&vid_conheight); + Cvar_RegisterVariable (&vid_pixelheight); + Cvar_RegisterVariable (&scr_screenshot_jpeg); + Cvar_RegisterVariable (&scr_screenshot_jpeg_quality); + Cvar_RegisterVariable (&scr_screenshot_png); + Cvar_RegisterVariable (&scr_screenshot_gammaboost); + Cvar_RegisterVariable (&scr_screenshot_hwgamma); + Cvar_RegisterVariable (&scr_screenshot_name_in_mapdir); + Cvar_RegisterVariable (&scr_screenshot_alpha); + Cvar_RegisterVariable (&cl_capturevideo); + Cvar_RegisterVariable (&cl_capturevideo_printfps); + Cvar_RegisterVariable (&cl_capturevideo_width); + Cvar_RegisterVariable (&cl_capturevideo_height); + Cvar_RegisterVariable (&cl_capturevideo_realtime); + Cvar_RegisterVariable (&cl_capturevideo_fps); + Cvar_RegisterVariable (&cl_capturevideo_nameformat); + Cvar_RegisterVariable (&cl_capturevideo_number); + Cvar_RegisterVariable (&cl_capturevideo_ogg); + Cvar_RegisterVariable (&cl_capturevideo_framestep); + Cvar_RegisterVariable (&r_letterbox); + Cvar_RegisterVariable(&r_stereo_separation); + Cvar_RegisterVariable(&r_stereo_sidebyside); + Cvar_RegisterVariable(&r_stereo_horizontal); + Cvar_RegisterVariable(&r_stereo_vertical); + Cvar_RegisterVariable(&r_stereo_redblue); + Cvar_RegisterVariable(&r_stereo_redcyan); + Cvar_RegisterVariable(&r_stereo_redgreen); + Cvar_RegisterVariable(&r_stereo_angle); + Cvar_RegisterVariable(&scr_zoomwindow); + Cvar_RegisterVariable(&scr_zoomwindow_viewsizex); + Cvar_RegisterVariable(&scr_zoomwindow_viewsizey); + Cvar_RegisterVariable(&scr_zoomwindow_fov); + Cvar_RegisterVariable(&scr_stipple); + Cvar_RegisterVariable(&scr_refresh); + Cvar_RegisterVariable(&shownetgraph); + Cvar_RegisterVariable(&cl_demo_mousegrab); + Cvar_RegisterVariable(&timedemo_screenshotframelist); + Cvar_RegisterVariable(&vid_touchscreen_outlinealpha); + Cvar_RegisterVariable(&vid_touchscreen_overlayalpha); + + Cmd_AddCommand ("sizeup",SCR_SizeUp_f, "increase view size (increases viewsize cvar)"); + Cmd_AddCommand ("sizedown",SCR_SizeDown_f, "decrease view size (decreases viewsize cvar)"); + Cmd_AddCommand ("screenshot",SCR_ScreenShot_f, "takes a screenshot of the next rendered frame"); + Cmd_AddCommand ("envmap", R_Envmap_f, "render a cubemap (skybox) of the current scene"); + Cmd_AddCommand ("infobar", SCR_InfoBar_f, "display a text in the infobar (usage: infobar expiretime string)"); + + SCR_CaptureVideo_Ogg_Init(); + + scr_initialized = true; +} + +/* +================== +SCR_ScreenShot_f +================== +*/ +void SCR_ScreenShot_f (void) +{ + static int shotnumber; + static char old_prefix_name[MAX_QPATH]; + char prefix_name[MAX_QPATH]; + char filename[MAX_QPATH]; + unsigned char *buffer1; + unsigned char *buffer2; + qboolean jpeg = (scr_screenshot_jpeg.integer != 0); + qboolean png = (scr_screenshot_png.integer != 0) && !jpeg; + + if (Cmd_Argc() == 2) + { + const char *ext; + strlcpy(filename, Cmd_Argv(1), sizeof(filename)); + ext = FS_FileExtension(filename); + if (!strcasecmp(ext, "jpg")) + { + jpeg = true; + png = false; + } + else if (!strcasecmp(ext, "tga")) + { + jpeg = false; + png = false; + } + else if (!strcasecmp(ext, "png")) + { + jpeg = false; + png = true; + } + else + { + Con_Printf("screenshot: supplied filename must end in .jpg or .tga or .png\n"); + return; + } + } + else + { + // TODO maybe make capturevideo and screenshot use similar name patterns? + if (scr_screenshot_name_in_mapdir.integer && cl.worldbasename[0]) + dpsnprintf (prefix_name, sizeof(prefix_name), "%s/%s", cl.worldbasename, Sys_TimeString(scr_screenshot_name.string)); + else + dpsnprintf (prefix_name, sizeof(prefix_name), "%s", Sys_TimeString(scr_screenshot_name.string)); + + if (strcmp(old_prefix_name, prefix_name)) + { + dpsnprintf(old_prefix_name, sizeof(old_prefix_name), "%s", prefix_name ); + shotnumber = 0; + } + + // find a file name to save it to + for (;shotnumber < 1000000;shotnumber++) + if (!FS_SysFileExists(va("%s/screenshots/%s%06d.tga", fs_gamedir, prefix_name, shotnumber)) && !FS_SysFileExists(va("%s/screenshots/%s%06d.jpg", fs_gamedir, prefix_name, shotnumber)) && !FS_SysFileExists(va("%s/screenshots/%s%06d.png", fs_gamedir, prefix_name, shotnumber))) + break; + if (shotnumber >= 1000000) + { + Con_Print("Couldn't create the image file\n"); + return; + } + + dpsnprintf(filename, sizeof(filename), "screenshots/%s%06d.%s", prefix_name, shotnumber, jpeg ? "jpg" : png ? "png" : "tga"); + } + + buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); + buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * (scr_screenshot_alpha.integer ? 4 : 3)); + + if (SCR_ScreenShot (filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, jpeg, png, true, scr_screenshot_alpha.integer != 0)) + Con_Printf("Wrote %s\n", filename); + else + { + Con_Printf("Unable to write %s\n", filename); + if(jpeg || png) + { + if(SCR_ScreenShot (filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, false, false, true, scr_screenshot_alpha.integer != 0)) + { + strlcpy(filename + strlen(filename) - 3, "tga", 4); + Con_Printf("Wrote %s\n", filename); + } + } + } + + Mem_Free (buffer1); + Mem_Free (buffer2); + + shotnumber++; +} + +void SCR_CaptureVideo_BeginVideo(void) +{ + double r, g, b; + unsigned int i; + int width = cl_capturevideo_width.integer, height = cl_capturevideo_height.integer; + if (cls.capturevideo.active) + return; + memset(&cls.capturevideo, 0, sizeof(cls.capturevideo)); + // soundrate is figured out on the first SoundFrame + + if(width == 0 && height != 0) + width = (int) (height * (double)vid.width / ((double)vid.height * vid_pixelheight.value)); // keep aspect + if(width != 0 && height == 0) + height = (int) (width * ((double)vid.height * vid_pixelheight.value) / (double)vid.width); // keep aspect + + if(width < 2 || width > vid.width) // can't scale up + width = vid.width; + if(height < 2 || height > vid.height) // can't scale up + height = vid.height; + + // ensure it's all even; if not, scale down a little + if(width % 1) + --width; + if(height % 1) + --height; + + cls.capturevideo.width = width; + cls.capturevideo.height = height; + cls.capturevideo.active = true; + cls.capturevideo.framerate = bound(1, cl_capturevideo_fps.value, 1001) * bound(1, cl_capturevideo_framestep.integer, 64); + cls.capturevideo.framestep = cl_capturevideo_framestep.integer; + cls.capturevideo.soundrate = S_GetSoundRate(); + cls.capturevideo.soundchannels = S_GetSoundChannels(); + cls.capturevideo.startrealtime = realtime; + cls.capturevideo.frame = cls.capturevideo.lastfpsframe = 0; + cls.capturevideo.starttime = cls.capturevideo.lastfpstime = Sys_DoubleTime(); + cls.capturevideo.soundsampleframe = 0; + cls.capturevideo.realtime = cl_capturevideo_realtime.integer != 0; + cls.capturevideo.screenbuffer = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); + cls.capturevideo.outbuffer = (unsigned char *)Mem_Alloc(tempmempool, width * height * (4+4) + 18); + dpsnprintf(cls.capturevideo.basename, sizeof(cls.capturevideo.basename), "video/%s%03i", Sys_TimeString(cl_capturevideo_nameformat.string), cl_capturevideo_number.integer); + Cvar_SetValueQuick(&cl_capturevideo_number, cl_capturevideo_number.integer + 1); + + /* + for (i = 0;i < 256;i++) + { + unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255); + cls.capturevideo.rgbgammatable[0][i] = j; + cls.capturevideo.rgbgammatable[1][i] = j; + cls.capturevideo.rgbgammatable[2][i] = j; + } + */ +/* +R = Y + 1.4075 * (Cr - 128); +G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128); +B = Y + 1.7790 * (Cb - 128); +Y = R * .299 + G * .587 + B * .114; +Cb = R * -.169 + G * -.332 + B * .500 + 128.; +Cr = R * .500 + G * -.419 + B * -.0813 + 128.; +*/ + + if(WANT_SCREENSHOT_HWGAMMA) + { + VID_BuildGammaTables(&cls.capturevideo.vidramp[0], 256); + } + else + { + // identity gamma table + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp, 256); + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp + 256, 256); + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp + 256*2, 256); + } + if(scr_screenshot_gammaboost.value != 1) + { + double igamma = 1 / scr_screenshot_gammaboost.value; + for (i = 0;i < 256 * 3;i++) + cls.capturevideo.vidramp[i] = (unsigned short) (0.5 + pow(cls.capturevideo.vidramp[i] * (1.0 / 65535.0), igamma) * 65535.0); + } + + for (i = 0;i < 256;i++) + { + r = 255*cls.capturevideo.vidramp[i]/65535.0; + g = 255*cls.capturevideo.vidramp[i+256]/65535.0; + b = 255*cls.capturevideo.vidramp[i+512]/65535.0; + // NOTE: we have to round DOWN here, or integer overflows happen. Sorry for slightly wrong looking colors sometimes... + // Y weights from RGB + cls.capturevideo.rgbtoyuvscaletable[0][0][i] = (short)(r * 0.299); + cls.capturevideo.rgbtoyuvscaletable[0][1][i] = (short)(g * 0.587); + cls.capturevideo.rgbtoyuvscaletable[0][2][i] = (short)(b * 0.114); + // Cb weights from RGB + cls.capturevideo.rgbtoyuvscaletable[1][0][i] = (short)(r * -0.169); + cls.capturevideo.rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332); + cls.capturevideo.rgbtoyuvscaletable[1][2][i] = (short)(b * 0.500); + // Cr weights from RGB + cls.capturevideo.rgbtoyuvscaletable[2][0][i] = (short)(r * 0.500); + cls.capturevideo.rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419); + cls.capturevideo.rgbtoyuvscaletable[2][2][i] = (short)(b * -0.0813); + // range reduction of YCbCr to valid signal range + cls.capturevideo.yuvnormalizetable[0][i] = 16 + i * (236-16) / 256; + cls.capturevideo.yuvnormalizetable[1][i] = 16 + i * (240-16) / 256; + cls.capturevideo.yuvnormalizetable[2][i] = 16 + i * (240-16) / 256; + } + + if (cl_capturevideo_ogg.integer) + { + if(SCR_CaptureVideo_Ogg_Available()) + { + SCR_CaptureVideo_Ogg_BeginVideo(); + return; + } + else + Con_Print("cl_capturevideo_ogg: libraries not available. Capturing in AVI instead.\n"); + } + + SCR_CaptureVideo_Avi_BeginVideo(); +} + +void SCR_CaptureVideo_EndVideo(void) +{ + if (!cls.capturevideo.active) + return; + cls.capturevideo.active = false; + + Con_Printf("Finishing capture of %s.%s (%d frames, %d audio frames)\n", cls.capturevideo.basename, cls.capturevideo.formatextension, cls.capturevideo.frame, cls.capturevideo.soundsampleframe); + + if (cls.capturevideo.videofile) + { + cls.capturevideo.endvideo(); + } + + if (cls.capturevideo.screenbuffer) + { + Mem_Free (cls.capturevideo.screenbuffer); + cls.capturevideo.screenbuffer = NULL; + } + + if (cls.capturevideo.outbuffer) + { + Mem_Free (cls.capturevideo.outbuffer); + cls.capturevideo.outbuffer = NULL; + } + + memset(&cls.capturevideo, 0, sizeof(cls.capturevideo)); +} + +static void SCR_ScaleDownBGRA(unsigned char *in, int inw, int inh, unsigned char *out, int outw, int outh) +{ + // TODO optimize this function + + int x, y; + float area; + + // memcpy is faster than me + if(inw == outw && inh == outh) + { + memcpy(out, in, 4 * inw * inh); + return; + } + + // otherwise: a box filter + area = (float)outw * (float)outh / (float)inw / (float)inh; + for(y = 0; y < outh; ++y) + { + float iny0 = y / (float)outh * inh; int iny0_i = (int) floor(iny0); + float iny1 = (y+1) / (float)outh * inh; int iny1_i = (int) ceil(iny1); + for(x = 0; x < outw; ++x) + { + float inx0 = x / (float)outw * inw; int inx0_i = (int) floor(inx0); + float inx1 = (x+1) / (float)outw * inw; int inx1_i = (int) ceil(inx1); + float r = 0, g = 0, b = 0, alpha = 0; + int xx, yy; + + for(yy = iny0_i; yy < iny1_i; ++yy) + { + float ya = min(yy+1, iny1) - max(iny0, yy); + for(xx = inx0_i; xx < inx1_i; ++xx) + { + float a = ya * (min(xx+1, inx1) - max(inx0, xx)); + r += a * in[4*(xx + inw * yy)+0]; + g += a * in[4*(xx + inw * yy)+1]; + b += a * in[4*(xx + inw * yy)+2]; + alpha += a * in[4*(xx + inw * yy)+3]; + } + } + + out[4*(x + outw * y)+0] = (unsigned char) (r * area); + out[4*(x + outw * y)+1] = (unsigned char) (g * area); + out[4*(x + outw * y)+2] = (unsigned char) (b * area); + out[4*(x + outw * y)+3] = (unsigned char) (alpha * area); + } + } +} + +void SCR_CaptureVideo_VideoFrame(int newframestepframenum) +{ + int x = 0, y = 0; + int width = cls.capturevideo.width, height = cls.capturevideo.height; + + if(newframestepframenum == cls.capturevideo.framestepframe) + return; + + CHECKGLERROR + // speed is critical here, so do saving as directly as possible + + GL_ReadPixelsBGRA(x, y, vid.width, vid.height, cls.capturevideo.screenbuffer); + + SCR_ScaleDownBGRA (cls.capturevideo.screenbuffer, vid.width, vid.height, cls.capturevideo.outbuffer, width, height); + + cls.capturevideo.videoframes(newframestepframenum - cls.capturevideo.framestepframe); + cls.capturevideo.framestepframe = newframestepframenum; + + if(cl_capturevideo_printfps.integer) + { + char buf[80]; + double t = Sys_DoubleTime(); + if(t > cls.capturevideo.lastfpstime + 1) + { + double fps1 = (cls.capturevideo.frame - cls.capturevideo.lastfpsframe) / (t - cls.capturevideo.lastfpstime + 0.0000001); + double fps = (cls.capturevideo.frame ) / (t - cls.capturevideo.starttime + 0.0000001); + dpsnprintf(buf, sizeof(buf), "capturevideo: (%.1fs) last second %.3ffps, total %.3ffps\n", cls.capturevideo.frame / cls.capturevideo.framerate, fps1, fps); + Sys_PrintToTerminal(buf); + cls.capturevideo.lastfpstime = t; + cls.capturevideo.lastfpsframe = cls.capturevideo.frame; + } + } +} + +void SCR_CaptureVideo_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length) +{ + cls.capturevideo.soundsampleframe += length; + cls.capturevideo.soundframe(paintbuffer, length); +} + +void SCR_CaptureVideo(void) +{ + int newframenum; + if (cl_capturevideo.integer) + { + if (!cls.capturevideo.active) + SCR_CaptureVideo_BeginVideo(); + if (cls.capturevideo.framerate != cl_capturevideo_fps.value * cl_capturevideo_framestep.integer) + { + Con_Printf("You can not change the video framerate while recording a video.\n"); + Cvar_SetValueQuick(&cl_capturevideo_fps, cls.capturevideo.framerate / (double) cl_capturevideo_framestep.integer); + } + // for AVI saving we have to make sure that sound is saved before video + if (cls.capturevideo.soundrate && !cls.capturevideo.soundsampleframe) + return; + if (cls.capturevideo.realtime) + { + // preserve sound sync by duplicating frames when running slow + newframenum = (int)((realtime - cls.capturevideo.startrealtime) * cls.capturevideo.framerate); + } + else + newframenum = cls.capturevideo.frame + 1; + // if falling behind more than one second, stop + if (newframenum - cls.capturevideo.frame > 60 * (int)ceil(cls.capturevideo.framerate)) + { + Cvar_SetValueQuick(&cl_capturevideo, 0); + Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo.frame); + SCR_CaptureVideo_EndVideo(); + return; + } + // write frames + SCR_CaptureVideo_VideoFrame(newframenum / cls.capturevideo.framestep); + cls.capturevideo.frame = newframenum; + if (cls.capturevideo.error) + { + Cvar_SetValueQuick(&cl_capturevideo, 0); + Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo.frame); + SCR_CaptureVideo_EndVideo(); + } + } + else if (cls.capturevideo.active) + SCR_CaptureVideo_EndVideo(); +} + +/* +=============== +R_Envmap_f + +Grab six views for environment mapping tests +=============== +*/ +struct envmapinfo_s +{ + float angles[3]; + const char *name; + qboolean flipx, flipy, flipdiagonaly; +} +envmapinfo[12] = +{ + {{ 0, 0, 0}, "rt", false, false, false}, + {{ 0, 270, 0}, "ft", false, false, false}, + {{ 0, 180, 0}, "lf", false, false, false}, + {{ 0, 90, 0}, "bk", false, false, false}, + {{-90, 180, 0}, "up", true, true, false}, + {{ 90, 180, 0}, "dn", true, true, false}, + + {{ 0, 0, 0}, "px", true, true, true}, + {{ 0, 90, 0}, "py", false, true, false}, + {{ 0, 180, 0}, "nx", false, false, true}, + {{ 0, 270, 0}, "ny", true, false, false}, + {{-90, 180, 0}, "pz", false, false, true}, + {{ 90, 180, 0}, "nz", false, false, true} +}; + +static void R_Envmap_f (void) +{ + int j, size; + char filename[MAX_QPATH], basename[MAX_QPATH]; + unsigned char *buffer1; + unsigned char *buffer2; + + if (Cmd_Argc() != 3) + { + Con_Print("envmap : save out 6 cubic environment map images, usable with loadsky, note that size must one of 128, 256, 512, or 1024 and can't be bigger than your current resolution\n"); + return; + } + + strlcpy (basename, Cmd_Argv(1), sizeof (basename)); + size = atoi(Cmd_Argv(2)); + if (size != 128 && size != 256 && size != 512 && size != 1024) + { + Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n"); + return; + } + if (size > vid.width || size > vid.height) + { + Con_Print("envmap: your resolution is not big enough to render that size\n"); + return; + } + + r_refdef.envmap = true; + + R_UpdateVariables(); + + r_refdef.view.x = 0; + r_refdef.view.y = 0; + r_refdef.view.z = 0; + r_refdef.view.width = size; + r_refdef.view.height = size; + r_refdef.view.depth = 1; + r_refdef.view.useperspective = true; + r_refdef.view.isoverlay = false; + + r_refdef.view.frustum_x = 1; // tan(45 * M_PI / 180.0); + r_refdef.view.frustum_y = 1; // tan(45 * M_PI / 180.0); + + buffer1 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 4); + buffer2 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3); + + for (j = 0;j < 12;j++) + { + dpsnprintf(filename, sizeof(filename), "env/%s%s.tga", basename, envmapinfo[j].name); + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], envmapinfo[j].angles[0], envmapinfo[j].angles[1], envmapinfo[j].angles[2], 1); + r_refdef.view.quality = 1; + r_refdef.view.clear = true; + R_Mesh_Start(); + R_RenderView(); + R_Mesh_Finish(); + SCR_ScreenShot(filename, buffer1, buffer2, 0, vid.height - (r_refdef.view.y + r_refdef.view.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false, false, false); + } + + Mem_Free (buffer1); + Mem_Free (buffer2); + + r_refdef.envmap = false; +} + +//============================================================================= + +void SHOWLMP_decodehide(void) +{ + int i; + char *lmplabel; + lmplabel = MSG_ReadString(); + for (i = 0;i < cl.num_showlmps;i++) + if (cl.showlmps[i].isactive && strcmp(cl.showlmps[i].label, lmplabel) == 0) + { + cl.showlmps[i].isactive = false; + return; + } +} + +void SHOWLMP_decodeshow(void) +{ + int k; + char lmplabel[256], picname[256]; + float x, y; + strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel)); + strlcpy (picname, MSG_ReadString(), sizeof (picname)); + if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk + { + x = MSG_ReadByte(); + y = MSG_ReadByte(); + } + else + { + x = MSG_ReadShort(); + y = MSG_ReadShort(); + } + if (!cl.showlmps || cl.num_showlmps >= cl.max_showlmps) + { + showlmp_t *oldshowlmps = cl.showlmps; + cl.max_showlmps += 16; + cl.showlmps = (showlmp_t *) Mem_Alloc(cls.levelmempool, cl.max_showlmps * sizeof(showlmp_t)); + if (cl.num_showlmps) + memcpy(cl.showlmps, oldshowlmps, cl.num_showlmps * sizeof(showlmp_t)); + if (oldshowlmps) + Mem_Free(oldshowlmps); + } + for (k = 0;k < cl.max_showlmps;k++) + if (cl.showlmps[k].isactive && !strcmp(cl.showlmps[k].label, lmplabel)) + break; + if (k == cl.max_showlmps) + for (k = 0;k < cl.max_showlmps;k++) + if (!cl.showlmps[k].isactive) + break; + cl.showlmps[k].isactive = true; + strlcpy (cl.showlmps[k].label, lmplabel, sizeof (cl.showlmps[k].label)); + strlcpy (cl.showlmps[k].pic, picname, sizeof (cl.showlmps[k].pic)); + cl.showlmps[k].x = x; + cl.showlmps[k].y = y; + cl.num_showlmps = max(cl.num_showlmps, k + 1); +} + +void SHOWLMP_drawall(void) +{ + int i; + for (i = 0;i < cl.num_showlmps;i++) + if (cl.showlmps[i].isactive) + DrawQ_Pic(cl.showlmps[i].x, cl.showlmps[i].y, Draw_CachePic_Flags (cl.showlmps[i].pic, CACHEPICFLAG_NOTPERSISTENT), 0, 0, 1, 1, 1, 1, 0); +} + +/* +============================================================================== + + SCREEN SHOTS + +============================================================================== +*/ + +// buffer1: 4*w*h +// buffer2: 3*w*h (or 4*w*h if screenshotting alpha too) +qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean png, qboolean gammacorrect, qboolean keep_alpha) +{ + int indices[4] = {0,1,2,3}; // BGRA + qboolean ret; + + GL_ReadPixelsBGRA(x, y, width, height, buffer1); + + if(gammacorrect && (scr_screenshot_gammaboost.value != 1 || WANT_SCREENSHOT_HWGAMMA)) + { + int i; + double igamma = 1.0 / scr_screenshot_gammaboost.value; + unsigned short vidramp[256 * 3]; + if(WANT_SCREENSHOT_HWGAMMA) + { + VID_BuildGammaTables(&vidramp[0], 256); + } + else + { + // identity gamma table + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp, 256); + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp + 256, 256); + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp + 256*2, 256); + } + if(scr_screenshot_gammaboost.value != 1) + { + for (i = 0;i < 256 * 3;i++) + vidramp[i] = (unsigned short) (0.5 + pow(vidramp[i] * (1.0 / 65535.0), igamma) * 65535.0); + } + for (i = 0;i < width*height*4;i += 4) + { + buffer1[i] = (unsigned char) (vidramp[buffer1[i] + 512] * 255.0 / 65535.0 + 0.5); // B + buffer1[i+1] = (unsigned char) (vidramp[buffer1[i+1] + 256] * 255.0 / 65535.0 + 0.5); // G + buffer1[i+2] = (unsigned char) (vidramp[buffer1[i+2]] * 255.0 / 65535.0 + 0.5); // R + // A + } + } + + if(keep_alpha && !jpeg) + { + if(!png) + flipy = !flipy; // TGA: not preflipped + Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 4, 4, indices); + if (png) + ret = PNG_SaveImage_preflipped (filename, width, height, true, buffer2); + else + ret = Image_WriteTGABGRA(filename, width, height, buffer2); + } + else + { + if(jpeg) + { + indices[0] = 2; + indices[2] = 0; // RGB + } + Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 3, 4, indices); + if (jpeg) + ret = JPEG_SaveImage_preflipped (filename, width, height, buffer2); + else if (png) + ret = PNG_SaveImage_preflipped (filename, width, height, false, buffer2); + else + ret = Image_WriteTGABGR_preflipped (filename, width, height, buffer2); + } + + return ret; +} + +//============================================================================= + +int scr_numtouchscreenareas; +scr_touchscreenarea_t scr_touchscreenareas[16]; + +static void SCR_DrawTouchscreenOverlay(void) +{ + int i; + scr_touchscreenarea_t *a; + cachepic_t *pic; + for (i = 0, a = scr_touchscreenareas;i < scr_numtouchscreenareas;i++, a++) + { + if (vid_touchscreen_outlinealpha.value > 0 && a->rect[0] >= 0 && a->rect[1] >= 0 && a->rect[2] >= 4 && a->rect[3] >= 4) + { + DrawQ_Fill(a->rect[0] + 2, a->rect[1] , a->rect[2] - 4, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] + 1, a->rect[1] + 1, a->rect[2] - 2, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] , a->rect[1] + 2, 2 , a->rect[3] - 2, 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] + a->rect[2] - 2, a->rect[1] + 2, 2 , a->rect[3] - 2, 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] + 1, a->rect[1] + a->rect[3] - 2, a->rect[2] - 2, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] + 2, a->rect[1] + a->rect[3] - 1, a->rect[2] - 4, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + } + pic = a->pic ? Draw_CachePic(a->pic) : NULL; + if (pic && pic->tex != r_texture_notexture) + DrawQ_Pic(a->rect[0], a->rect[1], Draw_CachePic(a->pic), a->rect[2], a->rect[3], 1, 1, 1, vid_touchscreen_overlayalpha.value * (0.5f + 0.5f * a->active), 0); + } +} + +extern void R_UpdateFogColor(void); +void R_ClearScreen(qboolean fogcolor) +{ + float clearcolor[4]; + // clear to black + Vector4Clear(clearcolor); + if (fogcolor) + { + R_UpdateFogColor(); + if (r_fog_clear.integer) + VectorCopy(r_refdef.fogcolor, clearcolor); + } + // clear depth is 1.0 + // LordHavoc: we use a stencil centered around 128 instead of 0, + // to avoid clamping interfering with strange shadow volume + // drawing orders + // clear the screen + GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (vid.stencil ? GL_STENCIL_BUFFER_BIT : 0), clearcolor, 1.0f, 128); +} + +qboolean CL_VM_UpdateView (void); +void SCR_DrawConsole (void); +void R_Shadow_EditLights_DrawSelectedLightProperties(void); + +int r_stereo_side; + +extern void Sbar_ShowFPS(void); +void SCR_DrawScreen (void) +{ + Draw_Frame(); + + R_Mesh_Start(); + + R_UpdateVariables(); + + // Quake uses clockwise winding, so these are swapped + r_refdef.view.cullface_front = GL_BACK; + r_refdef.view.cullface_back = GL_FRONT; + + if (cls.signon == SIGNONS) + { + float size; + + size = scr_viewsize.value * (1.0 / 100.0); + size = min(size, 1); + + if (r_stereo_sidebyside.integer) + { + r_refdef.view.width = (int)(vid.width * size / 2.5); + r_refdef.view.height = (int)(vid.height * size / 2.5 * (1 - bound(0, r_letterbox.value, 100) / 100)); + r_refdef.view.depth = 1; + r_refdef.view.x = (int)((vid.width - r_refdef.view.width * 2.5) * 0.5); + r_refdef.view.y = (int)((vid.height - r_refdef.view.height)/2); + r_refdef.view.z = 0; + if (r_stereo_side) + r_refdef.view.x += (int)(r_refdef.view.width * 1.5); + } + else if (r_stereo_horizontal.integer) + { + r_refdef.view.width = (int)(vid.width * size / 2); + r_refdef.view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100)); + r_refdef.view.depth = 1; + r_refdef.view.x = (int)((vid.width - r_refdef.view.width * 2.0)/2); + r_refdef.view.y = (int)((vid.height - r_refdef.view.height)/2); + r_refdef.view.z = 0; + if (r_stereo_side) + r_refdef.view.x += (int)(r_refdef.view.width); + } + else if (r_stereo_vertical.integer) + { + r_refdef.view.width = (int)(vid.width * size); + r_refdef.view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100) / 2); + r_refdef.view.depth = 1; + r_refdef.view.x = (int)((vid.width - r_refdef.view.width)/2); + r_refdef.view.y = (int)((vid.height - r_refdef.view.height * 2.0)/2); + r_refdef.view.z = 0; + if (r_stereo_side) + r_refdef.view.y += (int)(r_refdef.view.height); + } + else + { + r_refdef.view.width = (int)(vid.width * size); + r_refdef.view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100)); + r_refdef.view.depth = 1; + r_refdef.view.x = (int)((vid.width - r_refdef.view.width)/2); + r_refdef.view.y = (int)((vid.height - r_refdef.view.height)/2); + r_refdef.view.z = 0; + } + + // LordHavoc: viewzoom (zoom in for sniper rifles, etc) + // LordHavoc: this is designed to produce widescreen fov values + // when the screen is wider than 4/3 width/height aspect, to do + // this it simply assumes the requested fov is the vertical fov + // for a 4x3 display, if the ratio is not 4x3 this makes the fov + // higher/lower according to the ratio + r_refdef.view.useperspective = true; + r_refdef.view.frustum_y = tan(scr_fov.value * M_PI / 360.0) * (3.0/4.0) * cl.viewzoom; + r_refdef.view.frustum_x = r_refdef.view.frustum_y * (float)r_refdef.view.width / (float)r_refdef.view.height / vid_pixelheight.value; + + r_refdef.view.frustum_x *= r_refdef.frustumscale_x; + r_refdef.view.frustum_y *= r_refdef.frustumscale_y; + + if(!CL_VM_UpdateView()) + R_RenderView(); + + if (scr_zoomwindow.integer) + { + float sizex = bound(10, scr_zoomwindow_viewsizex.value, 100) / 100.0; + float sizey = bound(10, scr_zoomwindow_viewsizey.value, 100) / 100.0; + r_refdef.view.width = (int)(vid.width * sizex); + r_refdef.view.height = (int)(vid.height * sizey); + r_refdef.view.depth = 1; + r_refdef.view.x = (int)((vid.width - r_refdef.view.width)/2); + r_refdef.view.y = 0; + r_refdef.view.z = 0; + + r_refdef.view.useperspective = true; + r_refdef.view.frustum_y = tan(scr_zoomwindow_fov.value * M_PI / 360.0) * (3.0/4.0) * cl.viewzoom; + r_refdef.view.frustum_x = r_refdef.view.frustum_y * vid_pixelheight.value * (float)r_refdef.view.width / (float)r_refdef.view.height; + + r_refdef.view.frustum_x *= r_refdef.frustumscale_x; + r_refdef.view.frustum_y *= r_refdef.frustumscale_y; + + if(!CL_VM_UpdateView()) + R_RenderView(); + } + } + + if (!r_stereo_sidebyside.integer && !r_stereo_horizontal.integer && !r_stereo_vertical.integer) + { + r_refdef.view.width = vid.width; + r_refdef.view.height = vid.height; + r_refdef.view.depth = 1; + r_refdef.view.x = 0; + r_refdef.view.y = 0; + r_refdef.view.z = 0; + r_refdef.view.useperspective = false; + } + + if (cls.timedemo && cls.td_frames > 0 && timedemo_screenshotframelist.string && timedemo_screenshotframelist.string[0]) + { + const char *t; + int framenum; + t = timedemo_screenshotframelist.string; + while (*t) + { + while (*t == ' ') + t++; + if (!*t) + break; + framenum = atof(t); + if (framenum == cls.td_frames) + break; + while (*t && *t != ' ') + t++; + } + if (*t) + { + // we need to take a screenshot of this frame... + char filename[MAX_QPATH]; + unsigned char *buffer1; + unsigned char *buffer2; + dpsnprintf(filename, sizeof(filename), "timedemoscreenshots/%s%06d.tga", cls.demoname, cls.td_frames); + buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); + buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3); + SCR_ScreenShot(filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, false, false, true, false); + Mem_Free(buffer1); + Mem_Free(buffer2); + } + } + + // draw 2D stuff + if(!scr_con_current && !(key_consoleactive & KEY_CONSOLEACTIVE_FORCED)) + if ((key_dest == key_game || key_dest == key_message) && !r_letterbox.value) + Con_DrawNotify (); // only draw notify in game + + if (cls.signon == SIGNONS) + { + SCR_DrawNet (); + SCR_DrawTurtle (); + SCR_DrawPause (); + if (!r_letterbox.value) + Sbar_Draw(); + SHOWLMP_drawall(); + SCR_CheckDrawCenterString(); + } + SCR_DrawNetGraph (); + MR_Draw(); + CL_DrawVideo(); + R_Shadow_EditLights_DrawSelectedLightProperties(); + + SCR_DrawConsole(); + + SCR_DrawBrand(); + + SCR_DrawInfobar(); + + SCR_DrawTouchscreenOverlay(); + + if (r_timereport_active) + R_TimeReport("2d"); + + R_TimeReport_EndFrame(); + R_TimeReport_BeginFrame(); + Sbar_ShowFPS(); + + DrawQ_Finish(); + + R_DrawGamma(); + + R_Mesh_Finish(); +} + +typedef struct loadingscreenstack_s +{ + struct loadingscreenstack_s *prev; + char msg[MAX_QPATH]; + float absolute_loading_amount_min; // this corresponds to relative completion 0 of this item + float absolute_loading_amount_len; // this corresponds to relative completion 1 of this item + float relative_completion; // 0 .. 1 +} +loadingscreenstack_t; +static loadingscreenstack_t *loadingscreenstack = NULL; +static qboolean loadingscreendone = false; +static qboolean loadingscreencleared = false; +static float loadingscreenheight = 0; +rtexture_t *loadingscreentexture = NULL; +static float loadingscreentexture_vertex3f[12]; +static float loadingscreentexture_texcoord2f[8]; +static int loadingscreenpic_number = 0; + +static void SCR_ClearLoadingScreenTexture(void) +{ + if(loadingscreentexture) + R_FreeTexture(loadingscreentexture); + loadingscreentexture = NULL; +} + +extern rtexturepool_t *r_main_texturepool; +static void SCR_SetLoadingScreenTexture(void) +{ + int w, h; + float loadingscreentexture_w; + float loadingscreentexture_h; + + SCR_ClearLoadingScreenTexture(); + + if (vid.support.arb_texture_non_power_of_two) + { + w = vid.width; h = vid.height; + loadingscreentexture_w = loadingscreentexture_h = 1; + } + else + { + w = CeilPowerOf2(vid.width); h = CeilPowerOf2(vid.height); + loadingscreentexture_w = vid.width / (float) w; + loadingscreentexture_h = vid.height / (float) h; + } + + loadingscreentexture = R_LoadTexture2D(r_main_texturepool, "loadingscreentexture", w, h, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCENEAREST | TEXF_CLAMP, -1, NULL); + R_Mesh_CopyToTexture(loadingscreentexture, 0, 0, 0, 0, vid.width, vid.height); + + loadingscreentexture_vertex3f[2] = loadingscreentexture_vertex3f[5] = loadingscreentexture_vertex3f[8] = loadingscreentexture_vertex3f[11] = 0; + loadingscreentexture_vertex3f[0] = loadingscreentexture_vertex3f[9] = 0; + loadingscreentexture_vertex3f[1] = loadingscreentexture_vertex3f[4] = 0; + loadingscreentexture_vertex3f[3] = loadingscreentexture_vertex3f[6] = vid_conwidth.integer; + loadingscreentexture_vertex3f[7] = loadingscreentexture_vertex3f[10] = vid_conheight.integer; + loadingscreentexture_texcoord2f[0] = 0;loadingscreentexture_texcoord2f[1] = loadingscreentexture_h; + loadingscreentexture_texcoord2f[2] = loadingscreentexture_w;loadingscreentexture_texcoord2f[3] = loadingscreentexture_h; + loadingscreentexture_texcoord2f[4] = loadingscreentexture_w;loadingscreentexture_texcoord2f[5] = 0; + loadingscreentexture_texcoord2f[6] = 0;loadingscreentexture_texcoord2f[7] = 0; +} + +void SCR_UpdateLoadingScreenIfShown(void) +{ + if(loadingscreendone) + SCR_UpdateLoadingScreen(loadingscreencleared); +} + +void SCR_PushLoadingScreen (qboolean redraw, const char *msg, float len_in_parent) +{ + loadingscreenstack_t *s = (loadingscreenstack_t *) Z_Malloc(sizeof(loadingscreenstack_t)); + s->prev = loadingscreenstack; + loadingscreenstack = s; + + strlcpy(s->msg, msg, sizeof(s->msg)); + s->relative_completion = 0; + + if(s->prev) + { + s->absolute_loading_amount_min = s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len * s->prev->relative_completion; + s->absolute_loading_amount_len = s->prev->absolute_loading_amount_len * len_in_parent; + if(s->absolute_loading_amount_len > s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len - s->absolute_loading_amount_min) + s->absolute_loading_amount_len = s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len - s->absolute_loading_amount_min; + } + else + { + s->absolute_loading_amount_min = 0; + s->absolute_loading_amount_len = 1; + } + + if(redraw) + SCR_UpdateLoadingScreenIfShown(); +} + +void SCR_PopLoadingScreen (qboolean redraw) +{ + loadingscreenstack_t *s = loadingscreenstack; + + if(!s) + { + Con_DPrintf("Popping a loading screen item from an empty stack!\n"); + return; + } + + loadingscreenstack = s->prev; + if(s->prev) + s->prev->relative_completion = (s->absolute_loading_amount_min + s->absolute_loading_amount_len - s->prev->absolute_loading_amount_min) / s->prev->absolute_loading_amount_len; + Z_Free(s); + + if(redraw) + SCR_UpdateLoadingScreenIfShown(); +} + +void SCR_ClearLoadingScreen (qboolean redraw) +{ + while(loadingscreenstack) + SCR_PopLoadingScreen(redraw && !loadingscreenstack->prev); +} + +static float SCR_DrawLoadingStack_r(loadingscreenstack_t *s, float y, float size) +{ + float x; + size_t len; + float total; + + total = 0; +#if 0 + if(s) + { + total += SCR_DrawLoadingStack_r(s->prev, y, 8); + y -= total; + if(!s->prev || strcmp(s->msg, s->prev->msg)) + { + len = strlen(s->msg); + x = (vid_conwidth.integer - DrawQ_TextWidth(s->msg, len, size, size, true, FONT_INFOBAR)) / 2; + y -= size; + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 1, 0); + DrawQ_String(x, y, s->msg, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + total += size; + } + } +#else + if(s) + { + len = strlen(s->msg); + x = (vid_conwidth.integer - DrawQ_TextWidth(s->msg, len, size, size, true, FONT_INFOBAR)) / 2; + y -= size; + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 1, 0); + DrawQ_String(x, y, s->msg, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + total += size; + } +#endif + return total; +} + +static void SCR_DrawLoadingStack(void) +{ + float verts[12]; + float colors[16]; + + loadingscreenheight = SCR_DrawLoadingStack_r(loadingscreenstack, vid_conheight.integer, scr_loadingscreen_barheight.value); + if(loadingscreenstack) + { + // height = 32; // sorry, using the actual one is ugly + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(false); +// R_Mesh_ResetTextureState(); + verts[2] = verts[5] = verts[8] = verts[11] = 0; + verts[0] = verts[9] = 0; + verts[1] = verts[4] = vid_conheight.integer - scr_loadingscreen_barheight.value; + verts[3] = verts[6] = vid_conwidth.integer * loadingscreenstack->absolute_loading_amount_min; + verts[7] = verts[10] = vid_conheight.integer; + +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + // ^^^^^^^^^^ blue component + // ^^^^^^ bottom row + // ^^^^^^^^^^^^ alpha is always on + colors[0] = 0; colors[1] = 0; colors[2] = 0; colors[3] = 1; + colors[4] = 0; colors[5] = 0; colors[6] = 0; colors[7] = 1; + sscanf(scr_loadingscreen_barcolor.string, "%f %f %f", &colors[8], &colors[9], &colors[10]); colors[11] = 1; + sscanf(scr_loadingscreen_barcolor.string, "%f %f %f", &colors[12], &colors[13], &colors[14]); colors[15] = 1; + + R_Mesh_PrepareVertices_Generic_Arrays(4, verts, colors, NULL); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + + // make sure everything is cleared, including the progress indicator + if(loadingscreenheight < 8) + loadingscreenheight = 8; + } +} + +static cachepic_t *loadingscreenpic; +static float loadingscreenpic_vertex3f[12]; +static float loadingscreenpic_texcoord2f[8]; + +static void SCR_DrawLoadingScreen_SharedSetup (qboolean clear) +{ + r_viewport_t viewport; + float x, y, w, h, sw, sh, f; + // release mouse grab while loading + if (!vid.fullscreen) + VID_SetMouse(false, false, false); +// CHECKGLERROR + r_refdef.draw2dstage = true; + R_Viewport_InitOrtho(&viewport, &identitymatrix, 0, 0, vid.width, vid.height, 0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100, NULL); + R_Mesh_ResetRenderTargets(); + R_SetViewport(&viewport); + GL_ColorMask(1,1,1,1); + // when starting up a new video mode, make sure the screen is cleared to black + if (clear || loadingscreentexture) + GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 0); + R_Textures_Frame(); + R_Mesh_Start(); + R_EntityMatrix(&identitymatrix); + // draw the loading plaque + loadingscreenpic = Draw_CachePic_Flags (loadingscreenpic_number ? va("gfx/loading%d", loadingscreenpic_number+1) : "gfx/loading", loadingscreenpic_number ? CACHEPICFLAG_NOTPERSISTENT : 0); + + w = loadingscreenpic->width; + h = loadingscreenpic->height; + + // apply scale + w *= scr_loadingscreen_scale.value; + h *= scr_loadingscreen_scale.value; + + // apply scale base + if(scr_loadingscreen_scale_base.integer) + { + w *= vid_conwidth.integer / (float) vid.width; + h *= vid_conheight.integer / (float) vid.height; + } + + // apply scale limit + sw = w / vid_conwidth.integer; + sh = h / vid_conheight.integer; + f = 1; + switch(scr_loadingscreen_scale_limit.integer) + { + case 1: + f = max(sw, sh); + break; + case 2: + f = min(sw, sh); + break; + case 3: + f = sw; + break; + case 4: + f = sh; + break; + } + if(f > 1) + { + w /= f; + h /= f; + } + + x = (vid_conwidth.integer - w)/2; + y = (vid_conheight.integer - h)/2; + loadingscreenpic_vertex3f[2] = loadingscreenpic_vertex3f[5] = loadingscreenpic_vertex3f[8] = loadingscreenpic_vertex3f[11] = 0; + loadingscreenpic_vertex3f[0] = loadingscreenpic_vertex3f[9] = x; + loadingscreenpic_vertex3f[1] = loadingscreenpic_vertex3f[4] = y; + loadingscreenpic_vertex3f[3] = loadingscreenpic_vertex3f[6] = x + w; + loadingscreenpic_vertex3f[7] = loadingscreenpic_vertex3f[10] = y + h; + loadingscreenpic_texcoord2f[0] = 0;loadingscreenpic_texcoord2f[1] = 0; + loadingscreenpic_texcoord2f[2] = 1;loadingscreenpic_texcoord2f[3] = 0; + loadingscreenpic_texcoord2f[4] = 1;loadingscreenpic_texcoord2f[5] = 1; + loadingscreenpic_texcoord2f[6] = 0;loadingscreenpic_texcoord2f[7] = 1; +} + +static void SCR_DrawLoadingScreen (qboolean clear) +{ + // we only need to draw the image if it isn't already there + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(false); +// R_Mesh_ResetTextureState(); + GL_Color(1,1,1,1); + if(loadingscreentexture) + { + R_Mesh_PrepareVertices_Generic_Arrays(4, loadingscreentexture_vertex3f, NULL, loadingscreentexture_texcoord2f); + R_SetupShader_Generic(loadingscreentexture, NULL, GL_MODULATE, 1, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } + R_Mesh_PrepareVertices_Generic_Arrays(4, loadingscreenpic_vertex3f, NULL, loadingscreenpic_texcoord2f); + R_SetupShader_Generic(Draw_GetPicTexture(loadingscreenpic), NULL, GL_MODULATE, 1, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + SCR_DrawLoadingStack(); +} + +static void SCR_DrawLoadingScreen_SharedFinish (qboolean clear) +{ + R_Mesh_Finish(); + // refresh + VID_Finish(); +} + +void SCR_UpdateLoadingScreen (qboolean clear) +{ + keydest_t old_key_dest; + int old_key_consoleactive; + + // don't do anything if not initialized yet + if (vid_hidden || cls.state == ca_dedicated) + return; + + if(!scr_loadingscreen_background.integer) + clear = true; + + if(loadingscreendone) + clear |= loadingscreencleared; + + if(!loadingscreendone) + loadingscreenpic_number = rand() % (scr_loadingscreen_count.integer > 1 ? scr_loadingscreen_count.integer : 1); + + if(clear) + SCR_ClearLoadingScreenTexture(); + else if(!loadingscreendone) + SCR_SetLoadingScreenTexture(); + + if(!loadingscreendone) + { + loadingscreendone = true; + loadingscreenheight = 0; + } + loadingscreencleared = clear; + + if (qglDrawBuffer) + qglDrawBuffer(GL_BACK); + SCR_DrawLoadingScreen_SharedSetup(clear); + if (vid.stereobuffer) + { + qglDrawBuffer(GL_BACK_LEFT); + SCR_DrawLoadingScreen(clear); + qglDrawBuffer(GL_BACK_RIGHT); + SCR_DrawLoadingScreen(clear); + } + else + { + if (qglDrawBuffer) + qglDrawBuffer(GL_BACK); + SCR_DrawLoadingScreen(clear); + } + SCR_DrawLoadingScreen_SharedFinish(clear); + + // this goes into the event loop, and should prevent unresponsive cursor on vista + old_key_dest = key_dest; + old_key_consoleactive = key_consoleactive; + key_dest = key_void; + key_consoleactive = false; + Key_EventQueue_Block(); Sys_SendKeyEvents(); + key_dest = old_key_dest; + key_consoleactive = old_key_consoleactive; +} + +qboolean R_Stereo_ColorMasking(void) +{ + return r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer; +} + +qboolean R_Stereo_Active(void) +{ + return (vid.stereobuffer || r_stereo_sidebyside.integer || r_stereo_horizontal.integer || r_stereo_vertical.integer || R_Stereo_ColorMasking()); +} + +extern cvar_t cl_minfps; +extern cvar_t cl_minfps_fade; +extern cvar_t cl_minfps_qualitymax; +extern cvar_t cl_minfps_qualitymin; +extern cvar_t cl_minfps_qualitypower; +extern cvar_t cl_minfps_qualityscale; +extern cvar_t r_viewscale_fpsscaling; +static double cl_updatescreen_rendertime = 0; +static double cl_updatescreen_quality = 1; +extern void Sbar_ShowFPS_Update(void); +void CL_UpdateScreen(void) +{ + vec3_t vieworigin; + double rendertime1; + double drawscreenstart; + float conwidth, conheight; + float f; + r_viewport_t viewport; + + Sbar_ShowFPS_Update(); + + if (!scr_initialized || !con_initialized || !scr_refresh.integer) + return; // not initialized yet + + loadingscreendone = false; + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + // play a bit with the palette (experimental) + palette_rgb_pantscolormap[15][0] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 0.0f*M_PI/3.0f)); + palette_rgb_pantscolormap[15][1] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 2.0f*M_PI/3.0f)); + palette_rgb_pantscolormap[15][2] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 4.0f*M_PI/3.0f)); + palette_rgb_shirtcolormap[15][0] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 5.0f*M_PI/3.0f)); + palette_rgb_shirtcolormap[15][1] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 3.0f*M_PI/3.0f)); + palette_rgb_shirtcolormap[15][2] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 1.0f*M_PI/3.0f)); + memcpy(palette_rgb_pantsscoreboard[15], palette_rgb_pantscolormap[15], sizeof(*palette_rgb_pantscolormap)); + memcpy(palette_rgb_shirtscoreboard[15], palette_rgb_shirtcolormap[15], sizeof(*palette_rgb_shirtcolormap)); + } + + if (vid_hidden) + { + VID_Finish(); + return; + } + + rendertime1 = Sys_DoubleTime(); + + conwidth = bound(160, vid_conwidth.value, 32768); + conheight = bound(90, vid_conheight.value, 24576); + if (vid_conwidth.value != conwidth) + Cvar_SetValue("vid_conwidth", conwidth); + if (vid_conheight.value != conheight) + Cvar_SetValue("vid_conheight", conheight); + + // bound viewsize + if (scr_viewsize.value < 30) + Cvar_Set ("viewsize","30"); + if (scr_viewsize.value > 120) + Cvar_Set ("viewsize","120"); + + // bound field of view + if (scr_fov.value < 1) + Cvar_Set ("fov","1"); + if (scr_fov.value > 170) + Cvar_Set ("fov","170"); + + // intermission is always full screen + if (cl.intermission) + sb_lines = 0; + else + { + if (scr_viewsize.value >= 120) + sb_lines = 0; // no status bar at all + else if (scr_viewsize.value >= 110) + sb_lines = 24; // no inventory + else + sb_lines = 24+16+8; + } + + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, vieworigin); + R_HDR_UpdateIrisAdaptation(vieworigin); + + r_refdef.view.colormask[0] = 1; + r_refdef.view.colormask[1] = 1; + r_refdef.view.colormask[2] = 1; + + SCR_SetUpToDrawConsole(); + + if (qglDrawBuffer) + { + CHECKGLERROR + qglDrawBuffer(GL_BACK);CHECKGLERROR + // set dithering mode + if (gl_dither.integer) + { + qglEnable(GL_DITHER);CHECKGLERROR + } + else + { + qglDisable(GL_DITHER);CHECKGLERROR + } + } + + R_Viewport_InitOrtho(&viewport, &identitymatrix, 0, 0, vid.width, vid.height, 0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100, NULL); + R_Mesh_ResetRenderTargets(); + R_SetViewport(&viewport); + GL_ScissorTest(false); + GL_ColorMask(1,1,1,1); + GL_DepthMask(true); + + R_ClearScreen(false); + r_refdef.view.clear = false; + r_refdef.view.isoverlay = false; + f = pow((float)cl_updatescreen_quality, cl_minfps_qualitypower.value) * cl_minfps_qualityscale.value; + r_refdef.view.quality = bound(cl_minfps_qualitymin.value, f, cl_minfps_qualitymax.value); + + if (qglPolygonStipple) + { + if(scr_stipple.integer) + { + GLubyte stipple[128]; + int i, s, width, parts; + static int frame = 0; + ++frame; + + s = scr_stipple.integer; + parts = (s & 007); + width = (s & 070) >> 3; + + qglEnable(GL_POLYGON_STIPPLE);CHECKGLERROR // 0x0B42 + for(i = 0; i < 128; ++i) + { + int line = i/4; + stipple[i] = (((line >> width) + frame) & ((1 << parts) - 1)) ? 0x00 : 0xFF; + } + qglPolygonStipple(stipple);CHECKGLERROR + } + else + { + qglDisable(GL_POLYGON_STIPPLE);CHECKGLERROR + } + } + + if (r_viewscale_fpsscaling.integer) + GL_Finish(); + drawscreenstart = Sys_DoubleTime(); + if (R_Stereo_Active()) + { + r_stereo_side = 0; + + if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer) + { + r_refdef.view.colormask[0] = 1; + r_refdef.view.colormask[1] = 0; + r_refdef.view.colormask[2] = 0; + } + + if (vid.stereobuffer) + qglDrawBuffer(GL_BACK_RIGHT); + + SCR_DrawScreen(); + + r_stereo_side = 1; + + if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer) + { + r_refdef.view.colormask[0] = 0; + r_refdef.view.colormask[1] = r_stereo_redcyan.integer || r_stereo_redgreen.integer; + r_refdef.view.colormask[2] = r_stereo_redcyan.integer || r_stereo_redblue.integer; + } + + if (vid.stereobuffer) + qglDrawBuffer(GL_BACK_LEFT); + + SCR_DrawScreen(); + } + else + SCR_DrawScreen(); + if (r_viewscale_fpsscaling.integer) + GL_Finish(); + r_refdef.lastdrawscreentime = Sys_DoubleTime() - drawscreenstart; + + SCR_CaptureVideo(); + + if (qglFlush) + qglFlush(); // FIXME: should we really be using qglFlush here? + + // quality adjustment according to render time + cl_updatescreen_rendertime += ((Sys_DoubleTime() - rendertime1) - cl_updatescreen_rendertime) * bound(0, cl_minfps_fade.value, 1); + if (cl_minfps.value > 0 && cl_updatescreen_rendertime > 0 && !cls.timedemo && (!cls.capturevideo.active || !cls.capturevideo.realtime)) + cl_updatescreen_quality = 1 / (cl_updatescreen_rendertime * cl_minfps.value); + else + cl_updatescreen_quality = 1; + + if (!vid_activewindow) + VID_SetMouse(false, false, false); + else if (key_consoleactive) + VID_SetMouse(vid.fullscreen, false, false); + else if (key_dest == key_menu_grabbed) + VID_SetMouse(true, vid_mouse.integer && !in_client_mouse && !vid_touchscreen.integer, !vid_touchscreen.integer); + else if (key_dest == key_menu) + VID_SetMouse(vid.fullscreen, vid_mouse.integer && !in_client_mouse && !vid_touchscreen.integer, !vid_touchscreen.integer); + else + VID_SetMouse(vid.fullscreen, vid_mouse.integer && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0 && (!cls.demoplayback || cl_demo_mousegrab.integer) && !vid_touchscreen.integer, !vid_touchscreen.integer); + + VID_Finish(); +} + +void CL_Screen_NewMap(void) +{ +} diff --git a/misc/source/darkplaces-src/cl_screen.h b/misc/source/darkplaces-src/cl_screen.h new file mode 100644 index 00000000..faf906b2 --- /dev/null +++ b/misc/source/darkplaces-src/cl_screen.h @@ -0,0 +1,27 @@ + +#ifndef CL_SCREEN_H +#define CL_SCREEN_H + +void SHOWLMP_decodehide(void); +void SHOWLMP_decodeshow(void); +void SHOWLMP_drawall(void); + +extern cvar_t vid_conwidth; +extern cvar_t vid_conheight; +extern cvar_t vid_pixelheight; +extern cvar_t scr_screenshot_jpeg; +extern cvar_t scr_screenshot_jpeg_quality; +extern cvar_t scr_screenshot_png; +extern cvar_t scr_screenshot_gammaboost; +extern cvar_t scr_screenshot_name; + +void CL_Screen_NewMap(void); +void CL_Screen_Init(void); +void CL_Screen_Shutdown(void); +void CL_UpdateScreen(void); + +qboolean R_Stereo_Active(void); +qboolean R_Stereo_ColorMasking(void); + +#endif + diff --git a/misc/source/darkplaces-src/cl_video.c b/misc/source/darkplaces-src/cl_video.c new file mode 100644 index 00000000..4e37eb75 --- /dev/null +++ b/misc/source/darkplaces-src/cl_video.c @@ -0,0 +1,703 @@ + +#include "quakedef.h" +#include "cl_dyntexture.h" +#include "cl_video.h" +#include "dpvsimpledecode.h" + +// VorteX: JAM video module used by Blood Omnicide +#define USEJAM +#ifdef USEJAM + #include "cl_video_jamdecode.c" +#endif + +// cvars +cvar_t cl_video_subtitles = {CVAR_SAVE, "cl_video_subtitles", "0", "show subtitles for videos (if they are present)"}; +cvar_t cl_video_subtitles_lines = {CVAR_SAVE, "cl_video_subtitles_lines", "4", "how many lines to occupy for subtitles"}; +cvar_t cl_video_subtitles_textsize = {CVAR_SAVE, "cl_video_subtitles_textsize", "16", "textsize for subtitles"}; +cvar_t cl_video_scale = {CVAR_SAVE, "cl_video_scale", "1", "scale of video, 1 = fullscreen, 0.75 - 3/4 of screen etc."}; +cvar_t cl_video_scale_vpos = {CVAR_SAVE, "cl_video_scale_vpos", "0", "vertical align of scaled video, -1 is top, 1 is bottom"}; +cvar_t cl_video_stipple = {CVAR_SAVE, "cl_video_stipple", "0", "draw interlacing-like effect on videos, similar to scr_stipple but static and used only with video playing."}; +cvar_t cl_video_brightness = {CVAR_SAVE, "cl_video_brightness", "1", "brightness of video, 1 = fullbright, 0.75 - 3/4 etc."}; +cvar_t cl_video_keepaspectratio = {CVAR_SAVE, "cl_video_keepaspectratio", "0", "keeps aspect ratio of fullscreen videos, leaving black color on unfilled areas, a value of 2 let video to be stretched horizontally with top & bottom being sliced out"}; +cvar_t cl_video_fadein = {CVAR_SAVE, "cl_video_fadein", "0", "fading-from-black effect once video is started, in seconds"}; +cvar_t cl_video_fadeout = {CVAR_SAVE, "cl_video_fadeout", "0", "fading-to-black effect once video is ended, in seconds"}; + +// constants (and semi-constants) +static int cl_videormask; +static int cl_videobmask; +static int cl_videogmask; +static int cl_videobytesperpixel; + +static int cl_num_videos; +static clvideo_t cl_videos[ MAXCLVIDEOS ]; +static rtexturepool_t *cl_videotexturepool; + +static clvideo_t *FindUnusedVid( void ) +{ + int i; + for( i = 1 ; i < MAXCLVIDEOS ; i++ ) + if( cl_videos[ i ].state == CLVIDEO_UNUSED ) + return &cl_videos[ i ]; + return NULL; +} + +static qboolean OpenStream( clvideo_t * video ) +{ + const char *errorstring; + video->stream = dpvsimpledecode_open( video, video->filename, &errorstring); + if (!video->stream ) + { +#ifdef USEJAM + video->stream = jam_open( video, video->filename, &errorstring); + if (video->stream) + return true; +#endif + Con_Printf("unable to open \"%s\", error: %s\n", video->filename, errorstring); + return false; + } + return true; +} + +static void VideoUpdateCallback(rtexture_t *rt, void *data) +{ + clvideo_t *video = (clvideo_t *) data; + R_UpdateTexture( video->cpif.tex, (unsigned char *)video->imagedata, 0, 0, 0, video->cpif.width, video->cpif.height, 1 ); +} + +static void LinkVideoTexture( clvideo_t *video ) +{ + video->cpif.tex = R_LoadTexture2D( cl_videotexturepool, video->cpif.name, video->cpif.width, video->cpif.height, NULL, TEXTYPE_BGRA, TEXF_PERSISTENT | TEXF_CLAMP, -1, NULL ); + R_MakeTextureDynamic( video->cpif.tex, VideoUpdateCallback, video ); + CL_LinkDynTexture( video->cpif.name, video->cpif.tex ); +} + +static void UnlinkVideoTexture( clvideo_t *video ) +{ + CL_UnlinkDynTexture( video->cpif.name ); + // free the texture + R_FreeTexture( video->cpif.tex ); + video->cpif.tex = NULL; + // free the image data + Mem_Free( video->imagedata ); +} + +static void SuspendVideo( clvideo_t * video ) +{ + if (video->suspended) + return; + video->suspended = true; + UnlinkVideoTexture(video); + // if we are in firstframe mode, also close the stream + if (video->state == CLVIDEO_FIRSTFRAME) + video->close(video->stream); +} + +static qboolean WakeVideo( clvideo_t * video ) +{ + if( !video->suspended ) + return true; + video->suspended = false; + + if( video->state == CLVIDEO_FIRSTFRAME ) + if( !OpenStream( video ) ) { + video->state = CLVIDEO_UNUSED; + return false; + } + + video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel ); + LinkVideoTexture( video ); + + // update starttime + video->starttime += realtime - video->lasttime; + + return true; +} + +static void LoadSubtitles( clvideo_t *video, const char *subtitlesfile ) +{ + char *subtitle_text; + const char *data; + float subtime, sublen; + int numsubs = 0; + + if (gamemode == GAME_BLOODOMNICIDE) + { + char overridename[MAX_QPATH]; + cvar_t *langcvar; + + langcvar = Cvar_FindVar("language"); + subtitle_text = NULL; + if (langcvar) + { + dpsnprintf(overridename, sizeof(overridename), "script/locale/%s/%s", langcvar->string, subtitlesfile); + subtitle_text = (char *)FS_LoadFile(overridename, cls.permanentmempool, false, NULL); + } + if (!subtitle_text) + subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL); + } + else + { + subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL); + } + if (!subtitle_text) + { + Con_DPrintf( "LoadSubtitles: can't open subtitle file '%s'!\n", subtitlesfile ); + return; + } + + // parse subtitle_text + // line is: x y "text" where + // x - start time + // y - seconds last (if 0 - last thru next sub, if negative - last to next sub - this amount of seconds) + + data = subtitle_text; + for (;;) + { + if (!COM_ParseToken_QuakeC(&data, false)) + break; + subtime = atof( com_token ); + if (!COM_ParseToken_QuakeC(&data, false)) + break; + sublen = atof( com_token ); + if (!COM_ParseToken_QuakeC(&data, false)) + break; + if (!com_token[0]) + continue; + // check limits + if (video->subtitles == CLVIDEO_MAX_SUBTITLES) + { + Con_Printf("WARNING: CLVIDEO_MAX_SUBTITLES = %i reached when reading subtitles from '%s'\n", CLVIDEO_MAX_SUBTITLES, subtitlesfile); + break; + } + // add a sub + video->subtitle_text[numsubs] = (char *) Mem_Alloc(cls.permanentmempool, strlen(com_token) + 1); + memcpy(video->subtitle_text[numsubs], com_token, strlen(com_token) + 1); + video->subtitle_start[numsubs] = subtime; + video->subtitle_end[numsubs] = sublen; + if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles + { + if (video->subtitle_end[numsubs-1] <= 0) + video->subtitle_end[numsubs-1] = max(video->subtitle_start[numsubs-1], video->subtitle_start[numsubs] + video->subtitle_end[numsubs-1]); + else + video->subtitle_end[numsubs-1] = min(video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1], video->subtitle_start[numsubs]); + } + numsubs++; + // todo: check timing for consistency? + } + if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles + { + if (video->subtitle_end[numsubs-1] <= 0) + video->subtitle_end[numsubs-1] = 99999999; // fixme: make it end when video ends? + else + video->subtitle_end[numsubs-1] = video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1]; + } + Z_Free( subtitle_text ); + video->subtitles = numsubs; +/* + Con_Printf( "video->subtitles: %i\n", video->subtitles ); + for (numsubs = 0; numsubs < video->subtitles; numsubs++) + Con_Printf( " %03.2f %03.2f : %s\n", video->subtitle_start[numsubs], video->subtitle_end[numsubs], video->subtitle_text[numsubs] ); +*/ +} + +static clvideo_t* OpenVideo( clvideo_t *video, const char *filename, const char *name, int owner, const char *subtitlesfile ) +{ + strlcpy( video->filename, filename, sizeof(video->filename) ); + video->ownertag = owner; + if( strncmp( name, CLVIDEOPREFIX, sizeof( CLVIDEOPREFIX ) - 1 ) ) + return NULL; + strlcpy( video->cpif.name, name, sizeof(video->cpif.name) ); + + if( !OpenStream( video ) ) + return NULL; + + video->state = CLVIDEO_FIRSTFRAME; + video->framenum = -1; + video->framerate = video->getframerate( video->stream ); + video->lasttime = realtime; + video->subtitles = 0; + + video->cpif.width = video->getwidth( video->stream ); + video->cpif.height = video->getheight( video->stream ); + video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel ); + LinkVideoTexture( video ); + + // VorteX: load simple subtitle_text file + if (subtitlesfile[0]) + LoadSubtitles( video, subtitlesfile ); + + return video; +} + +clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile ) +{ + clvideo_t *video; + // sanity check + if( !name || !*name || strncmp( name, CLVIDEOPREFIX, sizeof( CLVIDEOPREFIX ) - 1 ) != 0 ) { + Con_DPrintf( "CL_OpenVideo: Bad video texture name '%s'!\n", name ); + return NULL; + } + + video = FindUnusedVid(); + if( !video ) { + Con_Printf( "CL_OpenVideo: unable to open video \"%s\" - video limit reached\n", filename ); + return NULL; + } + video = OpenVideo( video, filename, name, owner, subtitlesfile ); + // expand the active range to include the new entry + if (video) { + cl_num_videos = max(cl_num_videos, (int)(video - cl_videos) + 1); + } + return video; +} + +static clvideo_t* CL_GetVideoBySlot( int slot ) +{ + clvideo_t *video = &cl_videos[ slot ]; + + if( video->suspended ) + { + if( !WakeVideo( video ) ) + return NULL; + else if( video->state == CLVIDEO_RESETONWAKEUP ) + video->framenum = -1; + } + + video->lasttime = realtime; + + return video; +} + +clvideo_t *CL_GetVideoByName( const char *name ) +{ + int i; + + for( i = 0 ; i < cl_num_videos ; i++ ) + if( cl_videos[ i ].state != CLVIDEO_UNUSED + && !strcmp( cl_videos[ i ].cpif.name , name ) ) + break; + if( i != cl_num_videos ) + return CL_GetVideoBySlot( i ); + else + return NULL; +} + +void CL_SetVideoState(clvideo_t *video, clvideostate_t state) +{ + if (!video) + return; + + video->lasttime = realtime; + video->state = state; + if (state == CLVIDEO_FIRSTFRAME) + CL_RestartVideo(video); +} + +void CL_RestartVideo(clvideo_t *video) +{ + if (!video) + return; + + // reset time + video->starttime = video->lasttime = realtime; + video->framenum = -1; + + // reopen stream + video->close(video->stream); + if (!OpenStream(video)) + video->state = CLVIDEO_UNUSED; +} + +// close video +void CL_CloseVideo(clvideo_t * video) +{ + int i; + + if (!video || video->state == CLVIDEO_UNUSED) + return; + + // close stream + if (!video->suspended || video->state != CLVIDEO_FIRSTFRAME) + video->close(video->stream); + // unlink texture + if (!video->suspended) + UnlinkVideoTexture(video); + // purge subtitles + if (video->subtitles) + { + for (i = 0; i < video->subtitles; i++) + Z_Free( video->subtitle_text[i] ); + video->subtitles = 0; + } + video->state = CLVIDEO_UNUSED; +} + +// update all videos +void CL_Video_Frame(void) +{ + clvideo_t *video; + int destframe; + int i; + + if (!cl_num_videos) + return; + for (video = cl_videos, i = 0 ; i < cl_num_videos ; video++, i++) + { + if (video->state != CLVIDEO_UNUSED && !video->suspended) + { + if (realtime - video->lasttime > CLTHRESHOLD) + { + SuspendVideo(video); + continue; + } + if (video->state == CLVIDEO_PAUSE) + { + video->starttime = realtime - video->framenum * video->framerate; + continue; + } + // read video frame from stream if time has come + if (video->state == CLVIDEO_FIRSTFRAME ) + destframe = 0; + else + destframe = (int)((realtime - video->starttime) * video->framerate); + if (destframe < 0) + destframe = 0; + if (video->framenum < destframe) + { + do { + video->framenum++; + if (video->decodeframe(video->stream, video->imagedata, cl_videormask, cl_videogmask, cl_videobmask, cl_videobytesperpixel, cl_videobytesperpixel * video->cpif.width)) + { + // finished? + CL_RestartVideo(video); + if (video->state == CLVIDEO_PLAY) + video->state = CLVIDEO_FIRSTFRAME; + return; + } + } while(video->framenum < destframe); + R_MarkDirtyTexture(video->cpif.tex); + } + } + } + + // stop main video + if (cl_videos->state == CLVIDEO_FIRSTFRAME) + CL_VideoStop(); + + // reduce range to exclude unnecessary entries + while(cl_num_videos > 0 && cl_videos[cl_num_videos-1].state == CLVIDEO_UNUSED) + cl_num_videos--; +} + +void CL_Video_Shutdown( void ) +{ + int i; + for (i = 0 ; i < cl_num_videos ; i++) + CL_CloseVideo(&cl_videos[ i ]); +} + +void CL_PurgeOwner( int owner ) +{ + int i; + + for (i = 0 ; i < cl_num_videos ; i++) + if (cl_videos[i].ownertag == owner) + CL_CloseVideo(&cl_videos[i]); +} + +typedef struct +{ + dp_font_t *font; + float x; + float y; + float width; + float height; + float alignment; // 0 = left, 0.5 = center, 1 = right + float fontsize; + float textalpha; +} +cl_video_subtitle_info_t; + +float CL_DrawVideo_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth) +{ + cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough; + + if(w == NULL) + return si->fontsize * si->font->maxwidth; + if(maxWidth >= 0) + return DrawQ_TextWidth_UntilWidth(w, length, si->fontsize, si->fontsize, false, si->font, -maxWidth); // -maxWidth: we want at least one char + else if(maxWidth == -1) + return DrawQ_TextWidth(w, *length, si->fontsize, si->fontsize, false, si->font); + else + return 0; +} + +int CL_DrawVideo_DisplaySubtitleLine(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) +{ + cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough; + + int x = (int) (si->x + (si->width - width) * si->alignment); + if (length > 0) + DrawQ_String(x, si->y, line, length, si->fontsize, si->fontsize, 1.0, 1.0, 1.0, si->textalpha, 0, NULL, false, si->font); + si->y += si->fontsize; + return 1; +} + +int cl_videoplaying = false; // old, but still supported + +void CL_DrawVideo(void) +{ + clvideo_t *video; + float videotime, px, py, sx, sy, st[8], b; + cl_video_subtitle_info_t si; + int i; + + if (!cl_videoplaying) + return; + + video = CL_GetVideoBySlot( 0 ); + + // fix cvars + if (cl_video_scale.value <= 0 || cl_video_scale.value > 1) + Cvar_SetValueQuick( &cl_video_scale, 1); + if (cl_video_brightness.value <= 0 || cl_video_brightness.value > 10) + Cvar_SetValueQuick( &cl_video_brightness, 1); + + // calc video proportions + px = 0; + py = 0; + sx = vid_conwidth.integer; + sy = vid_conheight.integer; + st[0] = 0.0; st[1] = 0.0; + st[2] = 1.0; st[3] = 0.0; + st[4] = 0.0; st[5] = 1.0; + st[6] = 1.0; st[7] = 1.0; + if (cl_video_keepaspectratio.integer) + { + float a = ((float)video->cpif.width / (float)video->cpif.height) / ((float)vid.width / (float)vid.height); + if (cl_video_keepaspectratio.integer >= 2) + { + // clip instead of scale + if (a < 1.0) // clip horizontally + { + st[1] = st[3] = (1 - a)*0.5; + st[5] = st[7] = 1 - (1 - a)*0.5; + } + else if (a > 1.0) // clip vertically + { + st[0] = st[4] = (1 - 1/a)*0.5; + st[2] = st[6] = (1/a)*0.5; + } + } + else if (a < 1.0) // scale horizontally + { + px += sx * (1 - a) * 0.5; + sx *= a; + } + else if (a > 1.0) // scale vertically + { + a = 1 / a; + py += sy * (1 - a); + sy *= a; + } + } + + if (cl_video_scale.value != 1) + { + px += sx * (1 - cl_video_scale.value) * 0.5; + py += sy * (1 - cl_video_scale.value) * ((bound(-1, cl_video_scale_vpos.value, 1) + 1) / 2); + sx *= cl_video_scale.value; + sy *= cl_video_scale.value; + } + + // calc brightness for fadein and fadeout effects + b = cl_video_brightness.value; + if (cl_video_fadein.value && (realtime - video->starttime) < cl_video_fadein.value) + b = pow((realtime - video->starttime)/cl_video_fadein.value, 2); + else if (cl_video_fadeout.value && ((video->starttime + video->framenum * video->framerate) - realtime) < cl_video_fadeout.value) + b = pow(((video->starttime + video->framenum * video->framerate) - realtime)/cl_video_fadeout.value, 2); + + // draw black bg in case stipple is active or video is scaled + if (cl_video_stipple.integer || px != 0 || py != 0 || sx != vid_conwidth.integer || sy != vid_conheight.integer) + DrawQ_Fill(0, 0, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, 1, 0); + + // enable video-only polygon stipple (of global stipple is not active) + if (qglPolygonStipple && !scr_stipple.integer && cl_video_stipple.integer) + { + GLubyte stipple[128]; + int i, s, width, parts; + + s = cl_video_stipple.integer; + parts = (s & 007); + width = (s & 070) >> 3; + qglEnable(GL_POLYGON_STIPPLE);CHECKGLERROR // 0x0B42 + for(i = 0; i < 128; ++i) + { + int line = i/4; + stipple[i] = ((line >> width) & ((1 << parts) - 1)) ? 0x00 : 0xFF; + } + qglPolygonStipple(stipple);CHECKGLERROR + } + + // draw video + DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, 1, st[2], st[3], b, b, b, 1, st[4], st[5], b, b, b, 1, st[6], st[7], b, b, b, 1, 0); + + // disable video-only stipple + if (qglPolygonStipple && !scr_stipple.integer && cl_video_stipple.integer) + qglDisable(GL_POLYGON_STIPPLE);CHECKGLERROR + + // VorteX: draw subtitle_text + if (!video->subtitles || !cl_video_subtitles.integer) + return; + + // find current subtitle + videotime = realtime - video->starttime; + for (i = 0; i < video->subtitles; i++) + { + if (videotime >= video->subtitle_start[i] && videotime <= video->subtitle_end[i]) + { + // found, draw it + si.font = FONT_NOTIFY; + si.x = vid_conwidth.integer * 0.1; + si.y = vid_conheight.integer - (max(1, cl_video_subtitles_lines.value) * cl_video_subtitles_textsize.value); + si.width = vid_conwidth.integer * 0.8; + si.height = max(1, cl_video_subtitles_lines.integer) * cl_video_subtitles_textsize.value; + si.alignment = 0.5; + si.fontsize = cl_video_subtitles_textsize.value; + si.textalpha = min(1, (videotime - video->subtitle_start[i])/0.5) * min(1, ((video->subtitle_end[i] - videotime)/0.3)); // fade in and fade out + COM_Wordwrap(video->subtitle_text[i], strlen(video->subtitle_text[i]), 0, si.width, CL_DrawVideo_WordWidthFunc, &si, CL_DrawVideo_DisplaySubtitleLine, &si); + break; + } + } +} + +void CL_VideoStart(char *filename, const char *subtitlesfile) +{ + Host_StartVideo(); + + if( cl_videos->state != CLVIDEO_UNUSED ) + CL_CloseVideo( cl_videos ); + // already contains video/ + if( !OpenVideo( cl_videos, filename, va( CLDYNTEXTUREPREFIX "%s", filename ), 0, subtitlesfile ) ) + return; + // expand the active range to include the new entry + cl_num_videos = max(cl_num_videos, 1); + + cl_videoplaying = true; + + CL_SetVideoState( cl_videos, CLVIDEO_PLAY ); + CL_RestartVideo( cl_videos ); +} + +void CL_Video_KeyEvent( int key, int ascii, qboolean down ) +{ + // only react to up events, to allow the user to delay the abortion point if it suddenly becomes interesting.. + if( !down ) { + if( key == K_ESCAPE || key == K_ENTER || key == K_SPACE ) { + CL_VideoStop(); + } + } +} + +void CL_VideoStop(void) +{ + cl_videoplaying = false; + + CL_CloseVideo( cl_videos ); +} + +static void CL_PlayVideo_f(void) +{ + char name[MAX_QPATH], subtitlesfile[MAX_QPATH]; + const char *extension; + + Host_StartVideo(); + + if (COM_CheckParm("-benchmark")) + return; + + if (Cmd_Argc() < 2) + { + Con_Print("usage: playvideo [custom_subtitles_file]\nplays video named video/.dpv\nif custom subtitles file is not presented\nit tries video/.sub"); + return; + } + + extension = FS_FileExtension(Cmd_Argv(1)); + if (extension[0]) + dpsnprintf(name, sizeof(name), "video/%s", Cmd_Argv(1)); + else + dpsnprintf(name, sizeof(name), "video/%s.dpv", Cmd_Argv(1)); + if ( Cmd_Argc() > 2) + CL_VideoStart(name, Cmd_Argv(2)); + else + { + dpsnprintf(subtitlesfile, sizeof(subtitlesfile), "video/%s.dpsubs", Cmd_Argv(1)); + CL_VideoStart(name, subtitlesfile); + } +} + +static void CL_StopVideo_f(void) +{ + CL_VideoStop(); +} + +static void cl_video_start( void ) +{ + int i; + clvideo_t *video; + + cl_videotexturepool = R_AllocTexturePool(); + + for( video = cl_videos, i = 0 ; i < cl_num_videos ; i++, video++ ) + if( video->state != CLVIDEO_UNUSED && !video->suspended ) + LinkVideoTexture( video ); +} + +static void cl_video_shutdown( void ) +{ + int i; + clvideo_t *video; + + for( video = cl_videos, i = 0 ; i < cl_num_videos ; i++, video++ ) + if( video->state != CLVIDEO_UNUSED && !video->suspended ) + SuspendVideo( video ); + R_FreeTexturePool( &cl_videotexturepool ); +} + +static void cl_video_newmap( void ) +{ +} + +void CL_Video_Init( void ) +{ + union + { + unsigned char b[4]; + unsigned int i; + } + bgra; + + cl_num_videos = 0; + cl_videobytesperpixel = 4; + + // set masks in an endian-independent way (as they really represent bytes) + bgra.i = 0;bgra.b[0] = 0xFF;cl_videobmask = bgra.i; + bgra.i = 0;bgra.b[1] = 0xFF;cl_videogmask = bgra.i; + bgra.i = 0;bgra.b[2] = 0xFF;cl_videormask = bgra.i; + + Cmd_AddCommand( "playvideo", CL_PlayVideo_f, "play a .dpv video file" ); + Cmd_AddCommand( "stopvideo", CL_StopVideo_f, "stop playing a .dpv video file" ); + + Cvar_RegisterVariable(&cl_video_subtitles); + Cvar_RegisterVariable(&cl_video_subtitles_lines); + Cvar_RegisterVariable(&cl_video_subtitles_textsize); + Cvar_RegisterVariable(&cl_video_scale); + Cvar_RegisterVariable(&cl_video_scale_vpos); + Cvar_RegisterVariable(&cl_video_brightness); + Cvar_RegisterVariable(&cl_video_stipple); + Cvar_RegisterVariable(&cl_video_keepaspectratio); + Cvar_RegisterVariable(&cl_video_fadein); + Cvar_RegisterVariable(&cl_video_fadeout); + + R_RegisterModule( "CL_Video", cl_video_start, cl_video_shutdown, cl_video_newmap, NULL, NULL ); +} \ No newline at end of file diff --git a/misc/source/darkplaces-src/cl_video.h b/misc/source/darkplaces-src/cl_video.h new file mode 100644 index 00000000..4277124e --- /dev/null +++ b/misc/source/darkplaces-src/cl_video.h @@ -0,0 +1,96 @@ + +#ifndef CL_VIDEO_H +#define CL_VIDEO_H + +#include "cl_dyntexture.h" + +// yields DYNAMIC_TEXTURE_PATH_PREFIX CLVIDEOPREFIX video name for a path +#define CLVIDEOPREFIX CLDYNTEXTUREPREFIX "video/" +#define CLTHRESHOLD 2.0 + +#define MENUOWNER 1 + +typedef enum clvideostate_e +{ + CLVIDEO_UNUSED, + CLVIDEO_PLAY, + CLVIDEO_LOOP, + CLVIDEO_PAUSE, + CLVIDEO_FIRSTFRAME, + CLVIDEO_RESETONWAKEUP, + CLVIDEO_STATECOUNT +} clvideostate_t; + +#define CLVIDEO_MAX_SUBTITLES 512 + +extern cvar_t cl_video_subtitles; +extern cvar_t cl_video_subtitles_lines; +extern cvar_t cl_video_subtitles_textsize; +extern cvar_t cl_video_scale; +extern cvar_t cl_video_scale_vpos; +extern cvar_t cl_video_stipple; +extern cvar_t cl_video_brightness; +extern cvar_t cl_video_keepaspectratio; + +typedef struct clvideo_s +{ + int ownertag; + clvideostate_t state; + + // private stuff + void *stream; + + double starttime; + int framenum; + double framerate; + + void *imagedata; + + cachepic_t cpif; + + // VorteX: subtitles array + int subtitles; + char *subtitle_text[CLVIDEO_MAX_SUBTITLES]; + float subtitle_start[CLVIDEO_MAX_SUBTITLES]; + float subtitle_end[CLVIDEO_MAX_SUBTITLES]; + + // this functions gets filled by video format module + void (*close) (void *stream); + unsigned int (*getwidth) (void *stream); + unsigned int (*getheight) (void *stream); + double (*getframerate) (void *stream); + int (*decodeframe) (void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); + + // if a video is suspended, it is automatically paused (else we'd still have to process the frames) + // used to determine whether the video's resources should be freed or not + double lasttime; + // when lasttime - realtime > THRESHOLD, all but the stream is freed + qboolean suspended; + + char filename[MAX_QPATH]; +} clvideo_t; + +clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile ); +clvideo_t* CL_GetVideoByName( const char *name ); +void CL_SetVideoState( clvideo_t *video, clvideostate_t state ); +void CL_RestartVideo( clvideo_t *video ); + +void CL_CloseVideo( clvideo_t * video ); +void CL_PurgeOwner( int owner ); + +void CL_Video_Frame( void ); // update all videos +void CL_Video_Init( void ); +void CL_Video_Shutdown( void ); + +// old interface +extern int cl_videoplaying; + +void CL_DrawVideo( void ); +void CL_VideoStart( char *filename, const char *subtitlesfile ); +void CL_VideoStop( void ); + +// new function used for fullscreen videos +// TODO: Andreas Kirsch: move this subsystem somewhere else (preferably host) since the cl_video system shouldnt do such work like managing key events.. +void CL_Video_KeyEvent( int key, int ascii, qboolean down ); + +#endif diff --git a/misc/source/darkplaces-src/cl_video_jamdecode.c b/misc/source/darkplaces-src/cl_video_jamdecode.c new file mode 100644 index 00000000..a652dbc2 --- /dev/null +++ b/misc/source/darkplaces-src/cl_video_jamdecode.c @@ -0,0 +1,355 @@ +// JAM format decoder, used by Blood Omnicide + +typedef struct jamdecodestream_s +{ + int error; + + qfile_t *file; + double info_framerate; + unsigned int info_frames; + unsigned int info_imagewidth; + unsigned int info_imageheight; + int doubleres; + float colorscale; + unsigned char colorsub; + float stipple; + + // info used durign decoding + unsigned char *videopixels; + unsigned char *compressed; + unsigned char *framedata; + unsigned char *prevframedata; + unsigned char colormap[768]; + unsigned int framesize; + unsigned int framenum; + + // channel the sound file is being played on + int sndchan; +} +jamdecodestream_t; + +#define JAMDECODEERROR_NONE 0 +#define JAMDECODEERROR_EOF 1 +#define JAMDECODEERROR_READERROR 2 +#define JAMDECODEERROR_BAD_FRAME_HEADER 3 +#define JAMDECODEERROR_BAD_OUTPUT_SIZE 4 +#define JAMDECODEERROR_BAD_COLORMAP 5 + +// opens a stream +void jam_close(void *stream); +unsigned int jam_getwidth(void *stream); +unsigned int jam_getheight(void *stream); +double jam_getframerate(void *stream); +int jam_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); +void *jam_open(clvideo_t *video, char *filename, const char **errorstring) +{ + unsigned char jamHead[16]; + char *wavename; + jamdecodestream_t *s; + qfile_t *file; + + s = (jamdecodestream_t *)Z_Malloc(sizeof(jamdecodestream_t)); + if (s != NULL) + { + if ((file = FS_OpenVirtualFile(filename, false))) + { + s->file = file; + if (FS_Read(s->file, &jamHead, 16)) + { + if (!memcmp(jamHead, "JAM", 3)) + { + s->info_imagewidth = LittleLong(*(jamHead + 4)); + s->info_imageheight = LittleLong(*(jamHead + 8)); + s->info_frames = LittleLong(*(jamHead + 12)); + s->info_framerate = 15; + s->doubleres = 0; + s->colorscale = 0.70; + s->colorsub = 8; + s->stipple = 0.4; + s->framesize = s->info_imagewidth * s->info_imageheight; + if (s->framesize > 0) + { + s->compressed = (unsigned char *)Z_Malloc(s->framesize); + s->framedata = (unsigned char *)Z_Malloc(s->framesize * 2); + s->prevframedata = (unsigned char *)Z_Malloc(s->framesize * 2); + s->videopixels = (unsigned char *)Z_Malloc(s->framesize * 4); // bgra, doubleres + if (s->compressed != NULL && s->framedata != NULL && s->prevframedata != NULL && s->videopixels != NULL) + { + size_t namelen; + + namelen = strlen(filename) + 10; + wavename = (char *)Z_Malloc(namelen); + if (wavename) + { + sfx_t* sfx; + + FS_StripExtension(filename, wavename, namelen); + strlcat(wavename, ".wav", namelen); + sfx = S_PrecacheSound(wavename, false, false); + if (sfx != NULL) + s->sndchan = S_StartSound (-1, 0, sfx, vec3_origin, 1.0f, 0); + else + s->sndchan = -1; + Z_Free(wavename); + } + // all is well... + // set the module functions + s->framenum = 0; + video->close = jam_close; + video->getwidth = jam_getwidth; + video->getheight = jam_getheight; + video->getframerate = jam_getframerate; + video->decodeframe = jam_video; + return s; + } + else if (errorstring != NULL) + *errorstring = "unable to allocate memory for stream info structure"; + if (s->compressed != NULL) + Z_Free(s->compressed); + if (s->framedata != NULL) + Z_Free(s->framedata); + if (s->prevframedata != NULL) + Z_Free(s->prevframedata); + if (s->videopixels != NULL) + Z_Free(s->videopixels); + } + else if (errorstring != NULL) + *errorstring = "bad framesize"; + } + else if (errorstring != NULL) + *errorstring = "not JAM videofile"; + } + else if (errorstring != NULL) + *errorstring = "unexpected EOF"; + FS_Close(file); + } + else if (errorstring != NULL) + *errorstring = "unable to open videofile"; + Z_Free(s); + } + else if (errorstring != NULL) + *errorstring = "unable to allocate memory for stream info structure"; + return NULL; +} + +// closes a stream +void jam_close(void *stream) +{ + jamdecodestream_t *s = (jamdecodestream_t *)stream; + if (s == NULL) + return; + Z_Free(s->compressed); + Z_Free(s->framedata); + Z_Free(s->prevframedata); + Z_Free(s->videopixels); + if (s->sndchan != -1) + S_StopChannel(s->sndchan, true, true); + if (s->file) + FS_Close(s->file); + Z_Free(s); +} + +// returns the width of the image data +unsigned int jam_getwidth(void *stream) +{ + jamdecodestream_t *s = (jamdecodestream_t *)stream; + if (s->doubleres) + return s->info_imagewidth * 2; + return s->info_imagewidth; +} + +// returns the height of the image data +unsigned int jam_getheight(void *stream) +{ + jamdecodestream_t *s = (jamdecodestream_t *)stream; + if (s->doubleres) + return s->info_imageheight * 2; + return s->info_imageheight; +} + +// returns the framerate of the stream +double jam_getframerate(void *stream) +{ + jamdecodestream_t *s = (jamdecodestream_t *)stream; + return s->info_framerate; +} + + +// decode JAM frame +void jam_decodeframe(unsigned char *inbuf, unsigned char *outbuf, unsigned char *prevbuf, int outsize, int frametype) +{ + unsigned char *srcptr, *destptr, *prevptr; + int bytesleft; + unsigned int mark; + unsigned short int bits; + int rep; + int backoffs; + unsigned char *back; + int i; + + srcptr = inbuf; + destptr = outbuf; + prevptr = prevbuf; + bytesleft = outsize; + + if (frametype == 2) + { + memcpy(outbuf, inbuf, outsize); + return; + } + while(bytesleft > 0) + { + memcpy(&mark, srcptr, 4); + srcptr += 4; + for(i=0; i<32 && bytesleft > 0; i++,mark=mark>>1) + { + if(mark & 1) + { + *destptr = *srcptr; + destptr ++; + prevptr ++; + srcptr ++; + bytesleft --; + } + else + { + bits = srcptr[0] + 256*srcptr[1]; + rep = (bits >> 11) + 3; + if(frametype == 1) + { + backoffs = 0x821 - (bits & 0x7ff); + back = destptr - backoffs; + } + else + { + backoffs = 0x400 - (bits & 0x7ff); + back = prevptr - backoffs; + } + srcptr += 2; + memcpy(destptr, back, rep); + destptr += rep; + prevptr += rep; + bytesleft -= rep; + } + } + } +} + +// decodes a video frame to the supplied output pixels +int jam_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) +{ + unsigned char frameHead[16], *b; + unsigned int compsize, outsize, i, j; + jamdecodestream_t *s = (jamdecodestream_t *)stream; + + s->error = DPVSIMPLEDECODEERROR_NONE; + if (s->framenum < s->info_frames) + { +readframe: + if (FS_Read(s->file, &frameHead, 16)) + { + compsize = LittleLong(*(frameHead + 8)) - 16; + outsize = LittleLong(*(frameHead + 12)); + if (compsize > s->framesize || outsize > s->framesize) + s->error = JAMDECODEERROR_BAD_FRAME_HEADER; + else if (FS_Read(s->file, s->compressed, compsize)) + { + // palette goes interleaved with special flag + if (frameHead[0] == 2) + { + if (compsize == 768) + { + memcpy(s->colormap, s->compressed, 768); + for(i = 0; i < 768; i++) + s->colormap[i] = (unsigned char)(bound(0, (s->colormap[i] * s->colorscale) - s->colorsub, 255)); + goto readframe; + } + //else + // s->error = JAMDECODEERROR_BAD_COLORMAP; + } + else + { + // decode frame + // shift buffers to provide current and previous one, decode + b = s->prevframedata; + s->prevframedata = s->framedata; + s->framedata = b; + jam_decodeframe(s->compressed, s->framedata, s->prevframedata, outsize, frameHead[4]); + // make 32bit imagepixels from 8bit palettized frame + if (s->doubleres) + b = s->videopixels; + else + b = (unsigned char *)imagedata; + for(i = 0; i < s->framesize; i++) + { + // bgra + *b++ = s->colormap[s->framedata[i]*3 + 2]; + *b++ = s->colormap[s->framedata[i]*3 + 1]; + *b++ = s->colormap[s->framedata[i]*3]; + *b++ = 255; + } + // nearest 2x + if (s->doubleres) + { + for (i = 0; i < s->info_imageheight; i++) + { + b = (unsigned char *)imagedata + (s->info_imagewidth*2*4)*(i*2); + for (j = 0; j < s->info_imagewidth; j++) + { + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 1]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 2]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 3]; + // + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 1]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 2]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 3]; + } + b = (unsigned char *)imagedata + (s->info_imagewidth*2*4)*(i*2 + 1); + for (j = 0; j < s->info_imagewidth; j++) + { + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 1]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 2]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 3]; + // + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 1]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 2]; + *b++ = s->videopixels[i*s->info_imagewidth*4 + j*4 + 3]; + } + } + // do stippling + if (s->stipple) + { + for (i = 0; i < s->info_imageheight; i++) + { + b = (unsigned char *)imagedata + (s->info_imagewidth * 4 * 2 * 2 * i); + for (j = 0; j < s->info_imagewidth; j++) + { + b[0] = b[0] * s->stipple; + b[1] = b[1] * s->stipple; + b[2] = b[2] * s->stipple; + b += 4; + b[0] = b[0] * s->stipple; + b[1] = b[1] * s->stipple; + b[2] = b[2] * s->stipple; + b += 4; + } + } + } + } + + } + } + else + s->error = JAMDECODEERROR_READERROR; + } + else + s->error = JAMDECODEERROR_READERROR; + } + else + s->error = DPVSIMPLEDECODEERROR_EOF; + return s->error; +} diff --git a/misc/source/darkplaces-src/client.h b/misc/source/darkplaces-src/client.h new file mode 100644 index 00000000..57d50dd1 --- /dev/null +++ b/misc/source/darkplaces-src/client.h @@ -0,0 +1,1842 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// client.h + +#ifndef CLIENT_H +#define CLIENT_H + +#include "matrixlib.h" +#include "snd_main.h" + +// flags for rtlight rendering +#define LIGHTFLAG_NORMALMODE 1 +#define LIGHTFLAG_REALTIMEMODE 2 + +typedef struct tridecal_s +{ + // color and initial alpha value + float texcoord2f[3][2]; + float vertex3f[3][3]; + float color4f[3][4]; + float plane[4]; // backface culling + // how long this decal has lived so far (the actual fade begins at cl_decals_time) + float lived; + // if >= 0 this indicates the decal should follow an animated triangle + int triangleindex; + // for visibility culling + int surfaceindex; + // old decals are killed to obey cl_decals_max + int decalsequence; +} +tridecal_t; + +typedef struct decalsystem_s +{ + dp_model_t *model; + double lastupdatetime; + int maxdecals; + int freedecal; + int numdecals; + tridecal_t *decals; + float *vertex3f; + float *texcoord2f; + float *color4f; + int *element3i; + unsigned short *element3s; +} +decalsystem_t; + +typedef struct effect_s +{ + int active; + vec3_t origin; + double starttime; + float framerate; + int modelindex; + int startframe; + int endframe; + // these are for interpolation + int frame; + double frame1time; + double frame2time; +} +cl_effect_t; + +typedef struct beam_s +{ + int entity; + // draw this as lightning polygons, or a model? + int lightning; + struct model_s *model; + float endtime; + vec3_t start, end; +} +beam_t; + +typedef struct rtlight_particle_s +{ + float origin[3]; + float color[3]; +} +rtlight_particle_t; + +typedef struct rtlight_s +{ + // shadow volumes are done entirely in model space, so there are no matrices for dealing with them... they just use the origin + + // note that the world to light matrices are inversely scaled (divided) by lightradius + + // core properties + /// matrix for transforming light filter coordinates to world coordinates + matrix4x4_t matrix_lighttoworld; + /// matrix for transforming world coordinates to light filter coordinates + matrix4x4_t matrix_worldtolight; + /// typically 1 1 1, can be lower (dim) or higher (overbright) + vec3_t color; + /// size of the light (remove?) + vec_t radius; + /// light filter + char cubemapname[64]; + /// light style to monitor for brightness + int style; + /// whether light should render shadows + int shadow; + /// intensity of corona to render + vec_t corona; + /// radius scale of corona to render (1.0 means same as light radius) + vec_t coronasizescale; + /// ambient intensity to render + vec_t ambientscale; + /// diffuse intensity to render + vec_t diffusescale; + /// specular intensity to render + vec_t specularscale; + /// LIGHTFLAG_* flags + int flags; + + // generated properties + /// used only for shadow volumes + vec3_t shadoworigin; + /// culling + vec3_t cullmins; + vec3_t cullmaxs; + // culling + //vec_t cullradius; + // squared cullradius + //vec_t cullradius2; + + // rendering properties, updated each time a light is rendered + // this is rtlight->color * d_lightstylevalue + vec3_t currentcolor; + /// used by corona updates, due to occlusion query + float corona_visibility; + unsigned int corona_queryindex_visiblepixels; + unsigned int corona_queryindex_allpixels; + /// this is R_GetCubemap(rtlight->cubemapname) + rtexture_t *currentcubemap; + /// set by R_Shadow_PrepareLight to decide whether R_Shadow_DrawLight should draw it + qboolean draw; + /// these fields are set by R_Shadow_PrepareLight for later drawing + int cached_numlightentities; + int cached_numlightentities_noselfshadow; + int cached_numshadowentities; + int cached_numshadowentities_noselfshadow; + int cached_numsurfaces; + struct entity_render_s **cached_lightentities; + struct entity_render_s **cached_lightentities_noselfshadow; + struct entity_render_s **cached_shadowentities; + struct entity_render_s **cached_shadowentities_noselfshadow; + unsigned char *cached_shadowtrispvs; + unsigned char *cached_lighttrispvs; + int *cached_surfacelist; + // reduced light cullbox from GetLightInfo + vec3_t cached_cullmins; + vec3_t cached_cullmaxs; + // current shadow-caster culling planes based on view + // (any geometry outside these planes can not contribute to the visible + // shadows in any way, and thus can be culled safely) + int cached_numfrustumplanes; + mplane_t cached_frustumplanes[5]; // see R_Shadow_ComputeShadowCasterCullingPlanes + + /// static light info + /// true if this light should be compiled as a static light + int isstatic; + /// true if this is a compiled world light, cleared if the light changes + int compiled; + /// the shadowing mode used to compile this light + int shadowmode; + /// premade shadow volumes to render for world entity + shadowmesh_t *static_meshchain_shadow_zpass; + shadowmesh_t *static_meshchain_shadow_zfail; + shadowmesh_t *static_meshchain_shadow_shadowmap; + /// used for visibility testing (more exact than bbox) + int static_numleafs; + int static_numleafpvsbytes; + int *static_leaflist; + unsigned char *static_leafpvs; + /// surfaces seen by light + int static_numsurfaces; + int *static_surfacelist; + /// flag bits indicating which triangles of the world model should cast + /// shadows, and which ones should be lit + /// + /// this avoids redundantly scanning the triangles in each surface twice + /// for whether they should cast shadows, once in culling and once in the + /// actual shadowmarklist production. + int static_numshadowtrispvsbytes; + unsigned char *static_shadowtrispvs; + /// this allows the lighting batch code to skip backfaces andother culled + /// triangles not relevant for lighting + /// (important on big surfaces such as terrain) + int static_numlighttrispvsbytes; + unsigned char *static_lighttrispvs; + /// masks of all shadowmap sides that have any potential static receivers or casters + int static_shadowmap_receivers; + int static_shadowmap_casters; + /// particle-tracing cache for global illumination + int particlecache_numparticles; + int particlecache_maxparticles; + int particlecache_updateparticle; + rtlight_particle_t *particlecache_particles; + + /// bouncegrid light info + float photoncolor[3]; + float photons; +} +rtlight_t; + +typedef struct dlight_s +{ + // destroy light after this time + // (dlight only) + vec_t die; + // the entity that owns this light (can be NULL) + // (dlight only) + struct entity_render_s *ent; + // location + // (worldlight: saved to .rtlights file) + vec3_t origin; + // worldlight orientation + // (worldlight only) + // (worldlight: saved to .rtlights file) + vec3_t angles; + // dlight orientation/scaling/location + // (dlight only) + matrix4x4_t matrix; + // color of light + // (worldlight: saved to .rtlights file) + vec3_t color; + // cubemap name to use on this light + // (worldlight: saved to .rtlights file) + char cubemapname[64]; + // make light flash while selected + // (worldlight only) + int selected; + // brightness (not really radius anymore) + // (worldlight: saved to .rtlights file) + vec_t radius; + // drop intensity this much each second + // (dlight only) + vec_t decay; + // intensity value which is dropped over time + // (dlight only) + vec_t intensity; + // initial values for intensity to modify + // (dlight only) + vec_t initialradius; + vec3_t initialcolor; + // light style which controls intensity of this light + // (worldlight: saved to .rtlights file) + int style; + // cast shadows + // (worldlight: saved to .rtlights file) + int shadow; + // corona intensity + // (worldlight: saved to .rtlights file) + vec_t corona; + // radius scale of corona to render (1.0 means same as light radius) + // (worldlight: saved to .rtlights file) + vec_t coronasizescale; + // ambient intensity to render + // (worldlight: saved to .rtlights file) + vec_t ambientscale; + // diffuse intensity to render + // (worldlight: saved to .rtlights file) + vec_t diffusescale; + // specular intensity to render + // (worldlight: saved to .rtlights file) + vec_t specularscale; + // LIGHTFLAG_* flags + // (worldlight: saved to .rtlights file) + int flags; + // linked list of world lights + // (worldlight only) + struct dlight_s *next; + // embedded rtlight struct for renderer + // (worldlight only) + rtlight_t rtlight; +} +dlight_t; + +// this is derived from processing of the framegroupblend array +// note: technically each framegroupblend can produce two of these, but that +// never happens in practice because no one blends between more than 2 +// framegroups at once +#define MAX_FRAMEBLENDS (MAX_FRAMEGROUPBLENDS * 2) +typedef struct frameblend_s +{ + int subframe; + float lerp; +} +frameblend_t; + +// LordHavoc: this struct is intended for the renderer but some fields are +// used by the client. +// +// The renderer should not rely on any changes to this struct to be persistent +// across multiple frames because temp entities are wiped every frame, but it +// is acceptable to cache things in this struct that are not critical. +// +// For example the r_cullentities_trace code does such caching. +typedef struct entity_render_s +{ + // location + //vec3_t origin; + // orientation + //vec3_t angles; + // transform matrix for model to world + matrix4x4_t matrix; + // transform matrix for world to model + matrix4x4_t inversematrix; + // opacity (alpha) of the model + float alpha; + // size the model is shown + float scale; + // transparent sorting offset + float transparent_offset; + + // NULL = no model + dp_model_t *model; + // number of the entity represents, or 0 for non-network entities + int entitynumber; + // literal colormap colors for renderer, if both are 0 0 0 it is not colormapped + vec3_t colormap_pantscolor; + vec3_t colormap_shirtcolor; + // light, particles, etc + int effects; + // qw CTF flags and other internal-use-only effect bits + int internaleffects; + // for Alias models + int skinnum; + // render flags + int flags; + + // colormod tinting of models + float colormod[3]; + float glowmod[3]; + + // interpolated animation - active framegroups and blend factors + framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; + + // time of last model change (for shader animations) + double shadertime; + + // calculated by the renderer (but not persistent) + + // calculated during R_AddModelEntities + vec3_t mins, maxs; + // subframe numbers (-1 if not used) and their blending scalers (0-1), if interpolation is not desired, use subframeblend[0].subframe + frameblend_t frameblend[MAX_FRAMEBLENDS]; + // skeletal animation data (if skeleton.relativetransforms is not NULL, it overrides frameblend) + skeleton_t *skeleton; + + // animation cache (pointers allocated using R_FrameData_Alloc) + // ONLY valid during R_RenderView! may be NULL (not cached) + float *animcache_vertex3f; + float *animcache_normal3f; + float *animcache_svector3f; + float *animcache_tvector3f; + // interleaved arrays for rendering and dynamic vertex buffers for them + r_meshbuffer_t *animcache_vertex3fbuffer; + r_vertexmesh_t *animcache_vertexmesh; + r_meshbuffer_t *animcache_vertexmeshbuffer; + + // current lighting from map (updated ONLY by client code, not renderer) + vec3_t modellight_ambient; + vec3_t modellight_diffuse; // q3bsp + vec3_t modellight_lightdir; // q3bsp + + // storage of decals on this entity + // (note: if allowdecals is set, be sure to call R_DecalSystem_Reset on removal!) + int allowdecals; + decalsystem_t decalsystem; + + // FIELDS UPDATED BY RENDERER: + // last time visible during trace culling + double last_trace_visibility; + + // user wavefunc parameters (from csqc) + float userwavefunc_param[Q3WAVEFUNC_USER_COUNT]; +} +entity_render_t; + +typedef struct entity_persistent_s +{ + vec3_t trail_origin; + + // particle trail + float trail_time; + qboolean trail_allowed; // set to false by teleports, true by update code, prevents bad lerps + + // muzzleflash fading + float muzzleflash; + + // interpolated movement + + // start time of move + float lerpstarttime; + // time difference from start to end of move + float lerpdeltatime; + // the move itself, start and end + float oldorigin[3]; + float oldangles[3]; + float neworigin[3]; + float newangles[3]; +} +entity_persistent_t; + +typedef struct entity_s +{ + // baseline state (default values) + entity_state_t state_baseline; + // previous state (interpolating from this) + entity_state_t state_previous; + // current state (interpolating to this) + entity_state_t state_current; + + // used for regenerating parts of render + entity_persistent_t persistent; + + // the only data the renderer should know about + entity_render_t render; +} +entity_t; + +typedef struct usercmd_s +{ + vec3_t viewangles; + +// intended velocities + float forwardmove; + float sidemove; + float upmove; + + vec3_t cursor_screen; + vec3_t cursor_start; + vec3_t cursor_end; + vec3_t cursor_impact; + vec3_t cursor_normal; + vec_t cursor_fraction; + int cursor_entitynumber; + + double time; // time the move is executed for (cl_movement: clienttime, non-cl_movement: receivetime) + double receivetime; // time the move was received at + double clienttime; // time to which server state the move corresponds to + int msec; // for predicted moves + int buttons; + int impulse; + int sequence; + qboolean applied; // if false we're still accumulating a move + qboolean predicted; // if true the sequence should be sent as 0 + + // derived properties + double frametime; + qboolean canjump; + qboolean jump; + qboolean crouch; +} usercmd_t; + +typedef struct lightstyle_s +{ + int length; + char map[MAX_STYLESTRING]; +} lightstyle_t; + +typedef struct scoreboard_s +{ + char name[MAX_SCOREBOARDNAME]; + int frags; + int colors; // two 4 bit fields + // QW fields: + int qw_userid; + char qw_userinfo[MAX_USERINFO_STRING]; + float qw_entertime; + int qw_ping; + int qw_packetloss; + int qw_movementloss; + int qw_spectator; + char qw_team[8]; + char qw_skin[MAX_QPATH]; +} scoreboard_t; + +typedef struct cshift_s +{ + float destcolor[3]; + float percent; // 0-255 + float alphafade; // (any speed) +} cshift_t; + +#define CSHIFT_CONTENTS 0 +#define CSHIFT_DAMAGE 1 +#define CSHIFT_BONUS 2 +#define CSHIFT_POWERUP 3 +#define CSHIFT_VCSHIFT 4 +#define NUM_CSHIFTS 5 + +#define NAME_LENGTH 64 + + +// +// client_state_t should hold all pieces of the client state +// + +#define SIGNONS 4 // signon messages to receive before connected + +typedef enum cactive_e +{ + ca_uninitialized, // during early startup + ca_dedicated, // a dedicated server with no ability to start a client + ca_disconnected, // full screen console with no connection + ca_connected // valid netcon, talking to a server +} +cactive_t; + +typedef enum qw_downloadtype_e +{ + dl_none, + dl_single, + dl_skin, + dl_model, + dl_sound +} +qw_downloadtype_t; + +typedef enum capturevideoformat_e +{ + CAPTUREVIDEOFORMAT_AVI_I420, + CAPTUREVIDEOFORMAT_OGG_VORBIS_THEORA +} +capturevideoformat_t; + +typedef struct capturevideostate_s +{ + double startrealtime; + double framerate; + int framestep; + int framestepframe; + qboolean active; + qboolean realtime; + qboolean error; + int soundrate; + int soundchannels; + int frame; + double starttime; + double lastfpstime; + int lastfpsframe; + int soundsampleframe; + unsigned char *screenbuffer; + unsigned char *outbuffer; + char basename[MAX_QPATH]; + int width, height; + + // precomputed RGB to YUV tables + // converts the RGB values to YUV (see cap_avi.c for how to use them) + short rgbtoyuvscaletable[3][3][256]; + unsigned char yuvnormalizetable[3][256]; + + // precomputed gamma ramp (only needed if the capturevideo module uses RGB output) + // note: to map from these values to RGB24, you have to multiply by 255.0/65535.0, then add 0.5, then cast to integer + unsigned short vidramp[256 * 3]; + + // stuff to be filled in by the video format module + capturevideoformat_t format; + const char *formatextension; + qfile_t *videofile; + // always use this: + // cls.capturevideo.videofile = FS_OpenRealFile(va("%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false); + void (*endvideo) (void); + void (*videoframes) (int num); + void (*soundframe) (const portable_sampleframe_t *paintbuffer, size_t length); + + // format specific data + void *formatspecific; +} +capturevideostate_t; + +#define CL_MAX_DOWNLOADACKS 4 + +typedef struct cl_downloadack_s +{ + int start, size; +} +cl_downloadack_t; + +typedef struct cl_soundstats_s +{ + int mixedsounds; + int totalsounds; + int latency_milliseconds; +} +cl_soundstats_t; + +// +// the client_static_t structure is persistent through an arbitrary number +// of server connections +// +typedef struct client_static_s +{ + cactive_t state; + + // all client memory allocations go in these pools + mempool_t *levelmempool; + mempool_t *permanentmempool; + +// demo loop control + // -1 = don't play demos + int demonum; + // list of demos in loop + char demos[MAX_DEMOS][MAX_DEMONAME]; + // the actively playing demo (set by CL_PlayDemo_f) + char demoname[MAX_QPATH]; + +// demo recording info must be here, because record is started before +// entering a map (and clearing client_state_t) + qboolean demorecording; + fs_offset_t demo_lastcsprogssize; + int demo_lastcsprogscrc; + qboolean demoplayback; + qboolean timedemo; + // -1 = use normal cd track + int forcetrack; + qfile_t *demofile; + // realtime at second frame of timedemo (LordHavoc: changed to double) + double td_starttime; + int td_frames; // total frames parsed + double td_onesecondnexttime; + double td_onesecondframes; + double td_onesecondrealtime; + double td_onesecondminfps; + double td_onesecondmaxfps; + double td_onesecondavgfps; + int td_onesecondavgcount; + // LordHavoc: pausedemo + qboolean demopaused; + + // sound mixer statistics for showsound display + cl_soundstats_t soundstats; + + qboolean connect_trying; + int connect_remainingtries; + double connect_nextsendtime; + lhnetsocket_t *connect_mysocket; + lhnetaddress_t connect_address; + // protocol version of the server we're connected to + // (kept outside client_state_t because it's used between levels) + protocolversion_t protocol; + +#define MAX_RCONS 16 + int rcon_trying; + lhnetaddress_t rcon_addresses[MAX_RCONS]; + char rcon_commands[MAX_RCONS][MAX_INPUTLINE]; + double rcon_timeout[MAX_RCONS]; + int rcon_ringpos; + +// connection information + // 0 to SIGNONS + int signon; + // network connection + netconn_t *netcon; + + // download information + // (note: qw_download variables are also used) + cl_downloadack_t dp_downloadack[CL_MAX_DOWNLOADACKS]; + + // input sequence numbers are not reset on level change, only connect + int movesequence; + int servermovesequence; + + // quakeworld stuff below + + // value of "qport" cvar at time of connection + int qw_qport; + // copied from cls.netcon->qw. variables every time they change, or set by demos (which have no cls.netcon) + int qw_incoming_sequence; + int qw_outgoing_sequence; + + // current file download buffer (only saved when file is completed) + char qw_downloadname[MAX_QPATH]; + unsigned char *qw_downloadmemory; + int qw_downloadmemorycursize; + int qw_downloadmemorymaxsize; + int qw_downloadnumber; + int qw_downloadpercent; + qw_downloadtype_t qw_downloadtype; + // transfer rate display + double qw_downloadspeedtime; + int qw_downloadspeedcount; + int qw_downloadspeedrate; + qboolean qw_download_deflate; + + // current file upload buffer (for uploading screenshots to server) + unsigned char *qw_uploaddata; + int qw_uploadsize; + int qw_uploadpos; + + // user infostring + // this normally contains the following keys in quakeworld: + // password spectator name team skin topcolor bottomcolor rate noaim msg *ver *ip + char userinfo[MAX_USERINFO_STRING]; + + // extra user info for the "connect" command + char connect_userinfo[MAX_USERINFO_STRING]; + + // video capture stuff + capturevideostate_t capturevideo; + + // crypto channel + crypto_t crypto; + + // ProQuake compatibility stuff + int proquake_servermod; // 0 = not proquake, 1 = proquake + int proquake_serverversion; // actual proquake server version * 10 (3.40 = 34, etc) + int proquake_serverflags; // 0 (PQF_CHEATFREE not supported) +} +client_static_t; + +extern client_static_t cls; + +typedef struct client_movementqueue_s +{ + double time; + float frametime; + int sequence; + float viewangles[3]; + float move[3]; + qboolean jump; + qboolean crouch; + qboolean canjump; +} +client_movementqueue_t; + +//[515]: csqc +typedef struct +{ + qboolean drawworld; + qboolean drawenginesbar; + qboolean drawcrosshair; +}csqc_vidvars_t; + +typedef enum +{ + PARTICLE_BILLBOARD = 0, + PARTICLE_SPARK = 1, + PARTICLE_ORIENTED_DOUBLESIDED = 2, + PARTICLE_VBEAM = 3, + PARTICLE_HBEAM = 4, + PARTICLE_INVALID = -1 +} +porientation_t; + +typedef enum +{ + PBLEND_ALPHA = 0, + PBLEND_ADD = 1, + PBLEND_INVMOD = 2, + PBLEND_INVALID = -1 +} +pblend_t; + +typedef struct particletype_s +{ + pblend_t blendmode; + porientation_t orientation; + qboolean lighting; +} +particletype_t; + +typedef enum ptype_e +{ + pt_dead, pt_alphastatic, pt_static, pt_spark, pt_beam, pt_rain, pt_raindecal, pt_snow, pt_bubble, pt_blood, pt_smoke, pt_decal, pt_entityparticle, pt_total +} +ptype_t; + +typedef struct decal_s +{ + // fields used by rendering: (44 bytes) + unsigned short typeindex; + unsigned short texnum; + int decalsequence; + vec3_t org; + vec3_t normal; + float size; + float alpha; // 0-255 + unsigned char color[3]; + unsigned char unused1; + int clusterindex; // cheap culling by pvs + + // fields not used by rendering: (36 bytes in 32bit, 40 bytes in 64bit) + float time2; // used for decal fade + unsigned int owner; // decal stuck to this entity + dp_model_t *ownermodel; // model the decal is stuck to (used to make sure the entity is still alive) + vec3_t relativeorigin; // decal at this location in entity's coordinate space + vec3_t relativenormal; // decal oriented this way relative to entity's coordinate space +} +decal_t; + +typedef struct particle_s +{ + // for faster batch rendering, particles are rendered in groups by effect (resulting in less perfect sorting but far less state changes) + + // fields used by rendering: (48 bytes) + vec3_t sortorigin; // sort by this group origin, not particle org + vec3_t org; + vec3_t vel; // velocity of particle, or orientation of decal, or end point of beam + float size; + float alpha; // 0-255 + float stretch; // only for sparks + + // fields not used by rendering: (44 bytes) + float stainsize; + float stainalpha; + float sizeincrease; // rate of size change per second + float alphafade; // how much alpha reduces per second + float time2; // used for snow fluttering and decal fade + float bounce; // how much bounce-back from a surface the particle hits (0 = no physics, 1 = stop and slide, 2 = keep bouncing forever, 1.5 is typical) + float gravity; // how much gravity affects this particle (1.0 = normal gravity, 0.0 = none) + float airfriction; // how much air friction affects this object (objects with a low mass/size ratio tend to get more air friction) + float liquidfriction; // how much liquid friction affects this object (objects with a low mass/size ratio tend to get more liquid friction) +// float delayedcollisions; // time that p->bounce becomes active + float delayedspawn; // time that particle appears and begins moving + float die; // time when this particle should be removed, regardless of alpha + + // short variables grouped to save memory (4 bytes) + short angle; // base rotation of particle + short spin; // geometry rotation speed around the particle center normal + + // byte variables grouped to save memory (12 bytes) + unsigned char color[3]; + unsigned char qualityreduction; // enables skipping of this particle according to r_refdef.view.qualityreduction + unsigned char typeindex; + unsigned char blendmode; + unsigned char orientation; + unsigned char texnum; + unsigned char staincolor[3]; + signed char staintexnum; +} +particle_t; + +typedef enum cl_parsingtextmode_e +{ + CL_PARSETEXTMODE_NONE, + CL_PARSETEXTMODE_PING, + CL_PARSETEXTMODE_STATUS, + CL_PARSETEXTMODE_STATUS_PLAYERID, + CL_PARSETEXTMODE_STATUS_PLAYERIP +} +cl_parsingtextmode_t; + +typedef struct cl_locnode_s +{ + struct cl_locnode_s *next; + char *name; + vec3_t mins, maxs; +} +cl_locnode_t; + +typedef struct showlmp_s +{ + qboolean isactive; + float x; + float y; + char label[32]; + char pic[128]; +} +showlmp_t; + +// +// the client_state_t structure is wiped completely at every +// server signon +// +typedef struct client_state_s +{ + // true if playing in a local game and no one else is connected + int islocalgame; + + // send a clc_nop periodically until connected + float sendnoptime; + + // current input being accumulated by mouse/joystick/etc input + usercmd_t cmd; + // latest moves sent to the server that have not been confirmed yet + usercmd_t movecmd[CL_MAX_USERCMDS]; + +// information for local display + // health, etc + int stats[MAX_CL_STATS]; + float *statsf; // points to stats[] array + // last known inventory bit flags, for blinking + int olditems; + // cl.time of acquiring item, for blinking + float item_gettime[32]; + // last known STAT_ACTIVEWEAPON + int activeweapon; + // cl.time of changing STAT_ACTIVEWEAPON + float weapontime; + // use pain anim frame if cl.time < this + float faceanimtime; + // for stair smoothing + float stairsmoothz; + double stairsmoothtime; + + // color shifts for damage, powerups + cshift_t cshifts[NUM_CSHIFTS]; + // and content types + cshift_t prev_cshifts[NUM_CSHIFTS]; + +// the client maintains its own idea of view angles, which are +// sent to the server each frame. The server sets punchangle when +// the view is temporarily offset, and an angle reset commands at the start +// of each level and after teleporting. + + // mviewangles is read from demo + // viewangles is either client controlled or lerped from mviewangles + vec3_t mviewangles[2], viewangles; + // update by server, used by qc to do weapon recoil + vec3_t mpunchangle[2], punchangle; + // update by server, can be used by mods to kick view around + vec3_t mpunchvector[2], punchvector; + // update by server, used for lean+bob (0 is newest) + vec3_t mvelocity[2], velocity; + // update by server, can be used by mods for zooming + vec_t mviewzoom[2], viewzoom; + // if true interpolation the mviewangles and other interpolation of the + // player is disabled until the next network packet + // this is used primarily by teleporters, and when spectating players + // special checking of the old fixangle[1] is used to differentiate + // between teleporting and spectating + qboolean fixangle[2]; + + // client movement simulation + // these fields are only updated by CL_ClientMovement (called by CL_SendMove after parsing each network packet) + // set by CL_ClientMovement_Replay functions + qboolean movement_predicted; + // if true the CL_ClientMovement_Replay function will update origin, etc + qboolean movement_replay; + // simulated data (this is valid even if cl.movement is false) + vec3_t movement_origin; + vec3_t movement_velocity; + // whether the replay should allow a jump at the first sequence + qboolean movement_replay_canjump; + + // previous gun angles (for leaning effects) + vec3_t gunangles_prev; + vec3_t gunangles_highpass; + vec3_t gunangles_adjustment_lowpass; + vec3_t gunangles_adjustment_highpass; + // previous gun angles (for leaning effects) + vec3_t gunorg_prev; + vec3_t gunorg_highpass; + vec3_t gunorg_adjustment_lowpass; + vec3_t gunorg_adjustment_highpass; + +// pitch drifting vars + float idealpitch; + float pitchvel; + qboolean nodrift; + float driftmove; + double laststop; + +//[515]: added for csqc purposes + float sensitivityscale; + csqc_vidvars_t csqc_vidvars; //[515]: these parms must be set to true by default + qboolean csqc_wantsmousemove; + qboolean csqc_paused; // vortex: int because could be flags + struct model_s *csqc_model_precache[MAX_MODELS]; + + // local amount for smoothing stepups + //float crouch; + + // sent by server + qboolean paused; + qboolean onground; + qboolean inwater; + + // used by bob + qboolean oldonground; + double lastongroundtime; + double hitgroundtime; + float bob2_smooth; + float bobfall_speed; + float bobfall_swing; + + // don't change view angle, full screen, etc + int intermission; + // latched at intermission start + double completed_time; + + // the timestamp of the last two messages + double mtime[2]; + + // clients view of time, time should be between mtime[0] and mtime[1] to + // generate a lerp point for other data, oldtime is the previous frame's + // value of time, frametime is the difference between time and oldtime + // note: cl.time may be beyond cl.mtime[0] if packet loss is occuring, it + // is only forcefully limited when a packet is received + double time, oldtime; + // how long it has been since the previous client frame in real time + // (not game time, for that use cl.time - cl.oldtime) + double realframetime; + + // fade var for fading while dead + float deathfade; + + // motionblur alpha level variable + float motionbluralpha; + + // copy of realtime from last recieved message, for net trouble icon + float last_received_message; + +// information that is static for the entire time connected to a server + struct model_s *model_precache[MAX_MODELS]; + struct sfx_s *sound_precache[MAX_SOUNDS]; + + // FIXME: this is a lot of memory to be keeping around, this really should be dynamically allocated and freed somehow + char model_name[MAX_MODELS][MAX_QPATH]; + char sound_name[MAX_SOUNDS][MAX_QPATH]; + + // for display on solo scoreboard + char worldmessage[40]; // map title (not related to filename) + // variants of map name + char worldbasename[MAX_QPATH]; // %s + char worldname[MAX_QPATH]; // maps/%s.bsp + char worldnamenoextension[MAX_QPATH]; // maps/%s + // cl_entitites[cl.viewentity] = player + int viewentity; + // the real player entity (normally same as viewentity, + // different than viewentity if mod uses chasecam or other tricks) + int realplayerentity; + // this is updated to match cl.viewentity whenever it is in the clients + // range, basically this is used in preference to cl.realplayerentity for + // most purposes because when spectating another player it should show + // their information rather than yours + int playerentity; + // max players that can be in this game + int maxclients; + // type of game (deathmatch, coop, singleplayer) + int gametype; + + // models and sounds used by engine code (particularly cl_parse.c) + dp_model_t *model_bolt; + dp_model_t *model_bolt2; + dp_model_t *model_bolt3; + dp_model_t *model_beam; + sfx_t *sfx_wizhit; + sfx_t *sfx_knighthit; + sfx_t *sfx_tink1; + sfx_t *sfx_ric1; + sfx_t *sfx_ric2; + sfx_t *sfx_ric3; + sfx_t *sfx_r_exp3; + // indicates that the file "sound/misc/talk2.wav" was found (for use by team chat messages) + qboolean foundtalk2wav; + +// refresh related state + + // cl_entitites[0].model + struct model_s *worldmodel; + + // the gun model + entity_t viewent; + + // cd audio + int cdtrack, looptrack; + +// frag scoreboard + + // [cl.maxclients] + scoreboard_t *scores; + + // keep track of svc_print parsing state (analyzes ping reports and status reports) + cl_parsingtextmode_t parsingtextmode; + int parsingtextplayerindex; + // set by scoreboard code when sending ping command, this causes the next ping results to be hidden + // (which could eat the wrong ping report if the player issues one + // manually, but they would still see a ping report, just a later one + // caused by the scoreboard code rather than the one they intentionally + // issued) + int parsingtextexpectingpingforscores; + + // entity database stuff + // latest received entity frame numbers +#define LATESTFRAMENUMS 32 + int latestframenumsposition; + int latestframenums[LATESTFRAMENUMS]; + int latestsendnums[LATESTFRAMENUMS]; + entityframe_database_t *entitydatabase; + entityframe4_database_t *entitydatabase4; + entityframeqw_database_t *entitydatabaseqw; + + // keep track of quake entities because they need to be killed if they get stale + int lastquakeentity; + unsigned char isquakeentity[MAX_EDICTS]; + + // bounding boxes for clientside movement + vec3_t playerstandmins; + vec3_t playerstandmaxs; + vec3_t playercrouchmins; + vec3_t playercrouchmaxs; + + // old decals are killed based on this + int decalsequence; + + int max_entities; + int max_csqcrenderentities; + int max_static_entities; + int max_effects; + int max_beams; + int max_dlights; + int max_lightstyle; + int max_brushmodel_entities; + int max_particles; + int max_decals; + int max_showlmps; + + entity_t *entities; + entity_render_t *csqcrenderentities; + unsigned char *entities_active; + entity_t *static_entities; + cl_effect_t *effects; + beam_t *beams; + dlight_t *dlights; + lightstyle_t *lightstyle; + int *brushmodel_entities; + particle_t *particles; + decal_t *decals; + showlmp_t *showlmps; + + int num_entities; + int num_static_entities; + int num_brushmodel_entities; + int num_effects; + int num_beams; + int num_dlights; + int num_particles; + int num_decals; + int num_showlmps; + + double particles_updatetime; + double decals_updatetime; + int free_particle; + int free_decal; + + // cl_serverextension_download feature + int loadmodel_current; + int downloadmodel_current; + int loadmodel_total; + int loadsound_current; + int downloadsound_current; + int loadsound_total; + qboolean downloadcsqc; + qboolean loadcsqc; + qboolean loadbegun; + qboolean loadfinished; + + // quakeworld stuff + + // local copy of the server infostring + char qw_serverinfo[MAX_SERVERINFO_STRING]; + + // time of last qw "pings" command sent to server while showing scores + double last_ping_request; + + // used during connect + int qw_servercount; + + // updated from serverinfo + int qw_teamplay; + + // unused: indicates whether the player is spectating + // use cl.scores[cl.playerentity-1].qw_spectator instead + //qboolean qw_spectator; + + // last time an input packet was sent + double lastpackettime; + + // movement parameters for client prediction + unsigned int moveflags; + float movevars_wallfriction; + float movevars_waterfriction; + float movevars_friction; + float movevars_timescale; + float movevars_gravity; + float movevars_stopspeed; + float movevars_maxspeed; + float movevars_spectatormaxspeed; + float movevars_accelerate; + float movevars_airaccelerate; + float movevars_wateraccelerate; + float movevars_entgravity; + float movevars_jumpvelocity; + float movevars_edgefriction; + float movevars_maxairspeed; + float movevars_stepheight; + float movevars_airaccel_qw; + float movevars_airaccel_qw_stretchfactor; + float movevars_airaccel_sideways_friction; + float movevars_airstopaccelerate; + float movevars_airstrafeaccelerate; + float movevars_maxairstrafespeed; + float movevars_airstrafeaccel_qw; + float movevars_aircontrol; + float movevars_aircontrol_power; + float movevars_aircontrol_penalty; + float movevars_warsowbunny_airforwardaccel; + float movevars_warsowbunny_accel; + float movevars_warsowbunny_topspeed; + float movevars_warsowbunny_turnaccel; + float movevars_warsowbunny_backtosideratio; + float movevars_ticrate; + float movevars_airspeedlimit_nonqw; + + // models used by qw protocol + int qw_modelindex_spike; + int qw_modelindex_player; + int qw_modelindex_flag; + int qw_modelindex_s_explod; + + vec3_t qw_intermission_origin; + vec3_t qw_intermission_angles; + + // 255 is the most nails the QW protocol could send + int qw_num_nails; + vec_t qw_nails[255][6]; + + float qw_weaponkick; + + int qw_validsequence; + + int qw_deltasequence[QW_UPDATE_BACKUP]; + + // csqc stuff: + // server entity number corresponding to a clientside entity + unsigned short csqc_server2csqcentitynumber[MAX_EDICTS]; + qboolean csqc_loaded; + vec3_t csqc_vieworigin; + vec3_t csqc_viewangles; + vec3_t csqc_vieworiginfromengine; + vec3_t csqc_viewanglesfromengine; + matrix4x4_t csqc_viewmodelmatrixfromengine; + qboolean csqc_usecsqclistener; + matrix4x4_t csqc_listenermatrix; + char csqc_printtextbuf[MAX_INPUTLINE]; + + // collision culling data + world_t world; + + // loc file stuff (points and boxes describing locations in the level) + cl_locnode_t *locnodes; + // this is updated to cl.movement_origin whenever health is < 1 + // used by %d print in say/say_team messages if cl_locs_enable is on + vec3_t lastdeathorigin; + + // processing buffer used by R_BuildLightMap, reallocated as needed, + // freed on each level change + size_t buildlightmapmemorysize; + unsigned char *buildlightmapmemory; + + // used by EntityState5_ReadUpdate + skeleton_t *engineskeletonobjects; +} +client_state_t; + +// +// cvars +// +extern cvar_t cl_name; +extern cvar_t cl_color; +extern cvar_t cl_rate; +extern cvar_t cl_pmodel; +extern cvar_t cl_playermodel; +extern cvar_t cl_playerskin; + +extern cvar_t rcon_password; +extern cvar_t rcon_address; + +extern cvar_t cl_upspeed; +extern cvar_t cl_forwardspeed; +extern cvar_t cl_backspeed; +extern cvar_t cl_sidespeed; + +extern cvar_t cl_movespeedkey; + +extern cvar_t cl_yawspeed; +extern cvar_t cl_pitchspeed; + +extern cvar_t cl_anglespeedkey; + +extern cvar_t cl_autofire; + +extern cvar_t cl_shownet; +extern cvar_t cl_nolerp; +extern cvar_t cl_nettimesyncfactor; +extern cvar_t cl_nettimesyncboundmode; +extern cvar_t cl_nettimesyncboundtolerance; + +extern cvar_t cl_pitchdriftspeed; +extern cvar_t lookspring; +extern cvar_t lookstrafe; +extern cvar_t sensitivity; + +extern cvar_t freelook; + +extern cvar_t m_pitch; +extern cvar_t m_yaw; +extern cvar_t m_forward; +extern cvar_t m_side; + +extern cvar_t cl_autodemo; +extern cvar_t cl_autodemo_nameformat; +extern cvar_t cl_autodemo_delete; + +extern cvar_t r_draweffects; + +extern cvar_t cl_explosions_alpha_start; +extern cvar_t cl_explosions_alpha_end; +extern cvar_t cl_explosions_size_start; +extern cvar_t cl_explosions_size_end; +extern cvar_t cl_explosions_lifetime; +extern cvar_t cl_stainmaps; +extern cvar_t cl_stainmaps_clearonload; + +extern cvar_t cl_prydoncursor; +extern cvar_t cl_prydoncursor_notrace; + +extern cvar_t cl_locs_enable; + +extern client_state_t cl; + +extern void CL_AllocLightFlash (entity_render_t *ent, matrix4x4_t *matrix, float radius, float red, float green, float blue, float decay, float lifetime, int cubemapnum, int style, int shadowenable, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags); + +cl_locnode_t *CL_Locs_FindNearest(const vec3_t point); +void CL_Locs_FindLocationName(char *buffer, size_t buffersize, vec3_t point); + +//============================================================================= + +// +// cl_main +// + +void CL_Shutdown (void); +void CL_Init (void); + +void CL_EstablishConnection(const char *host, int firstarg); + +void CL_Disconnect (void); +void CL_Disconnect_f (void); + +void CL_UpdateRenderEntity(entity_render_t *ent); +void CL_SetEntityColormapColors(entity_render_t *ent, int colormap); +void CL_UpdateViewEntities(void); + +// +// cl_input +// +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} +kbutton_t; + +extern kbutton_t in_mlook, in_klook; +extern kbutton_t in_strafe; +extern kbutton_t in_speed; + +void CL_InitInput (void); +void CL_SendMove (void); + +void CL_ValidateState(entity_state_t *s); +void CL_MoveLerpEntityStates(entity_t *ent); +void CL_LerpUpdate(entity_t *e); +void CL_ParseTEnt (void); +void CL_NewBeam (int ent, vec3_t start, vec3_t end, dp_model_t *m, int lightning); +void CL_RelinkBeams (void); +void CL_Beam_CalculatePositions (const beam_t *b, vec3_t start, vec3_t end); +void CL_ClientMovement_Replay(void); + +void CL_ClearTempEntities (void); +entity_render_t *CL_NewTempEntity (double shadertime); + +void CL_Effect(vec3_t org, int modelindex, int startframe, int framecount, float framerate); + +void CL_ClearState (void); +void CL_ExpandEntities(int num); +void CL_ExpandCSQCRenderEntities(int num); +void CL_SetInfo(const char *key, const char *value, qboolean send, qboolean allowstarkey, qboolean allowmodel, qboolean quiet); + + +void CL_UpdateWorld (void); +void CL_WriteToServer (void); +void CL_Input (void); +extern int cl_ignoremousemoves; + + +float CL_KeyState (kbutton_t *key); +const char *Key_KeynumToString (int keynum); +int Key_StringToKeynum (const char *str); + +// +// cl_demo.c +// +void CL_StopPlayback(void); +void CL_ReadDemoMessage(void); +void CL_WriteDemoMessage(sizebuf_t *mesage); + +void CL_CutDemo(unsigned char **buf, fs_offset_t *filesize); +void CL_PasteDemo(unsigned char **buf, fs_offset_t *filesize); + +void CL_NextDemo(void); +void CL_Stop_f(void); +void CL_Record_f(void); +void CL_PlayDemo_f(void); +void CL_TimeDemo_f(void); + +// +// cl_parse.c +// +void CL_Parse_Init(void); +void CL_Parse_Shutdown(void); +void CL_ParseServerMessage(void); +void CL_Parse_DumpPacket(void); +void CL_Parse_ErrorCleanUp(void); +void QW_CL_StartUpload(unsigned char *data, int size); +extern cvar_t qport; +void CL_KeepaliveMessage(qboolean readmessages); // call this during loading of large content + +// +// view +// +void V_StartPitchDrift (void); +void V_StopPitchDrift (void); + +void V_Init (void); +float V_CalcRoll (vec3_t angles, vec3_t velocity); +void V_UpdateBlends (void); +void V_ParseDamage (void); + +// +// cl_part +// + +extern cvar_t cl_particles; +extern cvar_t cl_particles_quality; +extern cvar_t cl_particles_size; +extern cvar_t cl_particles_quake; +extern cvar_t cl_particles_blood; +extern cvar_t cl_particles_blood_alpha; +extern cvar_t cl_particles_blood_decal_alpha; +extern cvar_t cl_particles_blood_decal_scalemin; +extern cvar_t cl_particles_blood_decal_scalemax; +extern cvar_t cl_particles_blood_bloodhack; +extern cvar_t cl_particles_bulletimpacts; +extern cvar_t cl_particles_explosions_sparks; +extern cvar_t cl_particles_explosions_shell; +extern cvar_t cl_particles_rain; +extern cvar_t cl_particles_snow; +extern cvar_t cl_particles_smoke; +extern cvar_t cl_particles_smoke_alpha; +extern cvar_t cl_particles_smoke_alphafade; +extern cvar_t cl_particles_sparks; +extern cvar_t cl_particles_bubbles; +extern cvar_t cl_decals; +extern cvar_t cl_decals_time; +extern cvar_t cl_decals_fadetime; + +void CL_Particles_Clear(void); +void CL_Particles_Init(void); +void CL_Particles_Shutdown(void); +particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4]); + +typedef enum effectnameindex_s +{ + EFFECT_NONE, + EFFECT_TE_GUNSHOT, + EFFECT_TE_GUNSHOTQUAD, + EFFECT_TE_SPIKE, + EFFECT_TE_SPIKEQUAD, + EFFECT_TE_SUPERSPIKE, + EFFECT_TE_SUPERSPIKEQUAD, + EFFECT_TE_WIZSPIKE, + EFFECT_TE_KNIGHTSPIKE, + EFFECT_TE_EXPLOSION, + EFFECT_TE_EXPLOSIONQUAD, + EFFECT_TE_TAREXPLOSION, + EFFECT_TE_TELEPORT, + EFFECT_TE_LAVASPLASH, + EFFECT_TE_SMALLFLASH, + EFFECT_TE_FLAMEJET, + EFFECT_EF_FLAME, + EFFECT_TE_BLOOD, + EFFECT_TE_SPARK, + EFFECT_TE_PLASMABURN, + EFFECT_TE_TEI_G3, + EFFECT_TE_TEI_SMOKE, + EFFECT_TE_TEI_BIGEXPLOSION, + EFFECT_TE_TEI_PLASMAHIT, + EFFECT_EF_STARDUST, + EFFECT_TR_ROCKET, + EFFECT_TR_GRENADE, + EFFECT_TR_BLOOD, + EFFECT_TR_WIZSPIKE, + EFFECT_TR_SLIGHTBLOOD, + EFFECT_TR_KNIGHTSPIKE, + EFFECT_TR_VORESPIKE, + EFFECT_TR_NEHAHRASMOKE, + EFFECT_TR_NEXUIZPLASMA, + EFFECT_TR_GLOWTRAIL, + EFFECT_SVC_PARTICLE, + EFFECT_TOTAL +} +effectnameindex_t; + +int CL_ParticleEffectIndexForName(const char *name); +const char *CL_ParticleEffectNameForIndex(int i); +void CL_ParticleEffect(int effectindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor); +void CL_ParticleTrail(int effectindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4]); +void CL_ParseParticleEffect (void); +void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel); +void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type); +void CL_EntityParticles (const entity_t *ent); +void CL_ParticleExplosion (const vec3_t org); +void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength); +void R_NewExplosion(const vec3_t org); + +void Debug_PolygonBegin(const char *picname, int flags); +void Debug_PolygonVertex(float x, float y, float z, float s, float t, float r, float g, float b, float a); +void Debug_PolygonEnd(void); + +#include "cl_screen.h" + +extern qboolean sb_showscores; + +float RSurf_FogVertex(const vec3_t p); +float RSurf_FogPoint(const vec3_t p); + +typedef struct r_refdef_stats_s +{ + int renders; + int entities; + int entities_surfaces; + int entities_triangles; + int world_leafs; + int world_portals; + int world_surfaces; + int world_triangles; + int lightmapupdates; + int lightmapupdatepixels; + int particles; + int drawndecals; + int totaldecals; + int draws; + int draws_vertices; + int draws_elements; + int lights; + int lights_clears; + int lights_scissored; + int lights_lighttriangles; + int lights_shadowtriangles; + int lights_dynamicshadowtriangles; + int bouncegrid_lights; + int bouncegrid_particles; + int bouncegrid_traces; + int bouncegrid_hits; + int bouncegrid_splats; + int bouncegrid_bounces; + int collisioncache_animated; + int collisioncache_cached; + int collisioncache_traced; + int bloom; + int bloom_copypixels; + int bloom_drawpixels; + int indexbufferuploadcount; + int indexbufferuploadsize; + int vertexbufferuploadcount; + int vertexbufferuploadsize; + int framedatacurrent; + int framedatasize; +} +r_refdef_stats_t; + +typedef enum r_viewport_type_e +{ + R_VIEWPORTTYPE_ORTHO, + R_VIEWPORTTYPE_PERSPECTIVE, + R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP, + R_VIEWPORTTYPE_PERSPECTIVECUBESIDE, + R_VIEWPORTTYPE_TOTAL +} +r_viewport_type_t; + +typedef struct r_viewport_s +{ + matrix4x4_t cameramatrix; // from entity (transforms from camera entity to world) + matrix4x4_t viewmatrix; // actual matrix for rendering (transforms to viewspace) + matrix4x4_t projectmatrix; // actual projection matrix (transforms from viewspace to screen) + int x; + int y; + int z; + int width; + int height; + int depth; + r_viewport_type_t type; + float screentodepth[2]; // used by deferred renderer to calculate linear depth from device depth coordinates +} +r_viewport_t; + +typedef struct r_refdef_view_s +{ + // view information (changes multiple times per frame) + // if any of these variables change then r_refdef.viewcache must be regenerated + // by calling R_View_Update + // (which also updates viewport, scissor, colormask) + + // it is safe and expected to copy this into a structure on the stack and + // call the renderer recursively, then restore from the stack afterward + // (as long as R_View_Update is called) + + // eye position information + matrix4x4_t matrix, inverse_matrix; + vec3_t origin; + vec3_t forward; + vec3_t left; + vec3_t right; + vec3_t up; + int numfrustumplanes; + mplane_t frustum[6]; + qboolean useclipplane; + qboolean usecustompvs; // uses r_refdef.viewcache.pvsbits as-is rather than computing it + mplane_t clipplane; + float frustum_x, frustum_y; + vec3_t frustumcorner[4]; + // if turned off it renders an ortho view + int useperspective; + float ortho_x, ortho_y; + + // screen area to render in + int x; + int y; + int z; + int width; + int height; + int depth; + r_viewport_t viewport; // note: if r_viewscale is used, the viewport.width and viewport.height may be less than width and height + + // which color components to allow (for anaglyph glasses) + int colormask[4]; + + // global RGB color multiplier for rendering, this is required by HDR + float colorscale; + + // whether to call R_ClearScreen before rendering stuff + qboolean clear; + // if true, don't clear or do any post process effects (bloom, etc) + qboolean isoverlay; + + // whether to draw r_showtris and such, this is only true for the main + // view render, all secondary renders (HDR, mirrors, portals, cameras, + // distortion effects, etc) omit such debugging information + qboolean showdebug; + + // these define which values to use in GL_CullFace calls to request frontface or backface culling + int cullface_front; + int cullface_back; + + // render quality (0 to 1) - affects r_drawparticles_drawdistance and others + float quality; +} +r_refdef_view_t; + +typedef struct r_refdef_viewcache_s +{ + // updated by gl_main_newmap() + int maxentities; + int world_numclusters; + int world_numclusterbytes; + int world_numleafs; + int world_numsurfaces; + + // these properties are generated by R_View_Update() + + // which entities are currently visible for this viewpoint + // (the used range is 0...r_refdef.scene.numentities) + unsigned char *entityvisible; + + // flag arrays used for visibility checking on world model + // (all other entities have no per-surface/per-leaf visibility checks) + unsigned char *world_pvsbits; + unsigned char *world_leafvisible; + unsigned char *world_surfacevisible; + // if true, the view is currently in a leaf without pvs data + qboolean world_novis; +} +r_refdef_viewcache_t; + +// TODO: really think about which fields should go into scene and which one should stay in refdef [1/7/2008 Black] +// maybe also refactor some of the functions to support different setting sources (ie. fogenabled, etc.) for different scenes +typedef struct r_refdef_scene_s { + // whether to call S_ExtraUpdate during render to reduce sound chop + qboolean extraupdate; + + // (client gameworld) time for rendering time based effects + double time; + + // the world + entity_render_t *worldentity; + + // same as worldentity->model + dp_model_t *worldmodel; + + // renderable entities (excluding world) + entity_render_t **entities; + int numentities; + int maxentities; + + // field of temporary entities that is reset each (client) frame + entity_render_t *tempentities; + int numtempentities; + int maxtempentities; + qboolean expandtempentities; + + // renderable dynamic lights + rtlight_t *lights[MAX_DLIGHTS]; + rtlight_t templights[MAX_DLIGHTS]; + int numlights; + + // intensities for light styles right now, controls rtlights + float rtlightstylevalue[MAX_LIGHTSTYLES]; // float fraction of base light value + // 8.8bit fixed point intensities for light styles + // controls intensity lightmap layers + unsigned short lightstylevalue[MAX_LIGHTSTYLES]; // 8.8 fraction of base light value + + float ambient; + + qboolean rtworld; + qboolean rtworldshadows; + qboolean rtdlight; + qboolean rtdlightshadows; +} r_refdef_scene_t; + +typedef struct r_refdef_s +{ + // these fields define the basic rendering information for the world + // but not the view, which could change multiple times in one rendered + // frame (for example when rendering textures for certain effects) + + // these are set for water warping before + // frustum_x/frustum_y are calculated + float frustumscale_x, frustumscale_y; + + // current view settings (these get reset a few times during rendering because of water rendering, reflections, etc) + r_refdef_view_t view; + r_refdef_viewcache_t viewcache; + + // minimum visible distance (pixels closer than this disappear) + double nearclip; + // maximum visible distance (pixels further than this disappear in 16bpp modes, + // in 32bpp an infinite-farclip matrix is used instead) + double farclip; + + // fullscreen color blend + float viewblend[4]; + + r_refdef_scene_t scene; + + float fogplane[4]; + float fogplaneviewdist; + qboolean fogplaneviewabove; + float fogheightfade; + float fogcolor[3]; + float fogrange; + float fograngerecip; + float fogmasktabledistmultiplier; +#define FOGMASKTABLEWIDTH 1024 + float fogmasktable[FOGMASKTABLEWIDTH]; + float fogmasktable_start, fogmasktable_alpha, fogmasktable_range, fogmasktable_density; + float fog_density; + float fog_red; + float fog_green; + float fog_blue; + float fog_alpha; + float fog_start; + float fog_end; + float fog_height; + float fog_fadedepth; + qboolean fogenabled; + qboolean oldgl_fogenable; + + // new flexible texture height fog (overrides normal fog) + char fog_height_texturename[64]; // note: must be 64 for the sscanf code + unsigned char *fog_height_table1d; + unsigned char *fog_height_table2d; + int fog_height_tablesize; // enable + float fog_height_tablescale; + float fog_height_texcoordscale; + char fogheighttexturename[64]; // detects changes to active fog height texture + + int draw2dstage; // 0 = no, 1 = yes, other value = needs setting up again + + // true during envmap command capture + qboolean envmap; + + // brightness of world lightmaps and related lighting + // (often reduced when world rtlights are enabled) + float lightmapintensity; + // whether to draw world lights realtime, dlights realtime, and their shadows + float polygonfactor; + float polygonoffset; + float shadowpolygonfactor; + float shadowpolygonoffset; + + // how long R_RenderView took on the previous frame + double lastdrawscreentime; + + // rendering stats for r_speeds display + // (these are incremented in many places) + r_refdef_stats_t stats; +} +r_refdef_t; + +extern r_refdef_t r_refdef; + +// warpzone prediction hack (CSQC builtin) +void CL_RotateMoves(const matrix4x4_t *m); + +#endif + diff --git a/misc/source/darkplaces-src/clprogdefs.h b/misc/source/darkplaces-src/clprogdefs.h new file mode 100644 index 00000000..bee62abd --- /dev/null +++ b/misc/source/darkplaces-src/clprogdefs.h @@ -0,0 +1,98 @@ +/* file generated by qcc, do not modify */ + + +#ifndef CLPROGDEFS_H +#define CLPROGDEFS_H + +/* +typedef struct cl_globalvars_s +{ + int pad[28]; + int self; + int other; + int world; + float time; + float frametime; + float player_localentnum; + float player_localnum; + float maxclients; + float clientcommandframe; + float servercommandframe; + string_t mapname; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + int trace_ent; + float trace_inopen; + float trace_inwater; + func_t CSQC_Init; + func_t CSQC_Shutdown; + func_t CSQC_InputEvent; + func_t CSQC_UpdateView; + func_t CSQC_ConsoleCommand; + vec3_t pmove_org; + vec3_t pmove_vel; + vec3_t pmove_mins; + vec3_t pmove_maxs; + float input_timelength; + vec3_t input_angles; + vec3_t input_movevalues; + float input_buttons; + float movevar_gravity; + float movevar_stopspeed; + float movevar_maxspeed; + float movevar_spectatormaxspeed; + float movevar_accelerate; + float movevar_airaccelerate; + float movevar_wateraccelerate; + float movevar_friction; + float movevar_waterfriction; + float movevar_entgravity; +} cl_globalvars_t; + +typedef struct cl_entvars_s +{ + float modelindex; + vec3_t absmin; + vec3_t absmax; + float entnum; + float drawmask; + func_t predraw; + float movetype; + float solid; + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t angles; + vec3_t avelocity; + string_t classname; + string_t model; + float frame; + float skin; + float effects; + vec3_t mins; + vec3_t maxs; + vec3_t size; + func_t touch; + func_t use; + func_t think; + func_t blocked; + float nextthink; + int chain; + string_t netname; + int enemy; + float flags; + float colormap; + int owner; +} cl_entvars_t; + +#define CL_PROGHEADER_CRC 52195 +*/ + +#endif diff --git a/misc/source/darkplaces-src/clvm_cmds.c b/misc/source/darkplaces-src/clvm_cmds.c new file mode 100644 index 00000000..5b0b8043 --- /dev/null +++ b/misc/source/darkplaces-src/clvm_cmds.c @@ -0,0 +1,4739 @@ +#include "quakedef.h" + +#include "prvm_cmds.h" +#include "csprogs.h" +#include "cl_collision.h" +#include "r_shadow.h" +#include "jpeg.h" +#include "image.h" + +//============================================================================ +// Client +//[515]: unsolved PROBLEMS +//- finish player physics code (cs_runplayerphysics) +//- EntWasFreed ? +//- RF_DEPTHHACK is not like it should be +//- add builtin that sets cl.viewangles instead of reading "input_angles" global +//- finish lines support for R_Polygon*** +//- insert selecttraceline into traceline somehow + +//4 feature darkplaces csqc: add builtin to clientside qc for reading triangles of model meshes (useful to orient a ui along a triangle of a model mesh) +//4 feature darkplaces csqc: add builtins to clientside qc for gl calls + +extern cvar_t v_flipped; +extern cvar_t r_equalize_entities_fullbright; + +sfx_t *S_FindName(const char *name); +int Sbar_GetSortedPlayerIndex (int index); +void Sbar_SortFrags (void); +void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius); +void CSQC_RelinkAllEntities (int drawmask); +void CSQC_RelinkCSQCEntities (void); + +// #1 void(vector ang) makevectors +static void VM_CL_makevectors (void) +{ + VM_SAFEPARMCOUNT(1, VM_CL_makevectors); + AngleVectors (PRVM_G_VECTOR(OFS_PARM0), PRVM_clientglobalvector(v_forward), PRVM_clientglobalvector(v_right), PRVM_clientglobalvector(v_up)); +} + +// #2 void(entity e, vector o) setorigin +void VM_CL_setorigin (void) +{ + prvm_edict_t *e; + float *org; + VM_SAFEPARMCOUNT(2, VM_CL_setorigin); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning("setorigin: can not modify world entity\n"); + return; + } + if (e->priv.required->free) + { + VM_Warning("setorigin: can not modify free entity\n"); + return; + } + org = PRVM_G_VECTOR(OFS_PARM1); + VectorCopy (org, PRVM_clientedictvector(e, origin)); + CL_LinkEdict(e); +} + +static void SetMinMaxSize (prvm_edict_t *e, float *min, float *max) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (min[i] > max[i]) + PRVM_ERROR("SetMinMaxSize: backwards mins/maxs"); + + // set derived values + VectorCopy (min, PRVM_clientedictvector(e, mins)); + VectorCopy (max, PRVM_clientedictvector(e, maxs)); + VectorSubtract (max, min, PRVM_clientedictvector(e, size)); + + CL_LinkEdict (e); +} + +// #3 void(entity e, string m) setmodel +void VM_CL_setmodel (void) +{ + prvm_edict_t *e; + const char *m; + dp_model_t *mod; + int i; + + VM_SAFEPARMCOUNT(2, VM_CL_setmodel); + + e = PRVM_G_EDICT(OFS_PARM0); + PRVM_clientedictfloat(e, modelindex) = 0; + PRVM_clientedictstring(e, model) = 0; + + m = PRVM_G_STRING(OFS_PARM1); + mod = NULL; + for (i = 0;i < MAX_MODELS && cl.csqc_model_precache[i];i++) + { + if (!strcmp(cl.csqc_model_precache[i]->name, m)) + { + mod = cl.csqc_model_precache[i]; + PRVM_clientedictstring(e, model) = PRVM_SetEngineString(mod->name); + PRVM_clientedictfloat(e, modelindex) = -(i+1); + break; + } + } + + if( !mod ) { + for (i = 0;i < MAX_MODELS;i++) + { + mod = cl.model_precache[i]; + if (mod && !strcmp(mod->name, m)) + { + PRVM_clientedictstring(e, model) = PRVM_SetEngineString(mod->name); + PRVM_clientedictfloat(e, modelindex) = i; + break; + } + } + } + + if( mod ) { + // TODO: check if this breaks needed consistency and maybe add a cvar for it too?? [1/10/2008 Black] + //SetMinMaxSize (e, mod->normalmins, mod->normalmaxs); + } + else + { + SetMinMaxSize (e, vec3_origin, vec3_origin); + VM_Warning ("setmodel: model '%s' not precached\n", m); + } +} + +// #4 void(entity e, vector min, vector max) setsize +static void VM_CL_setsize (void) +{ + prvm_edict_t *e; + float *min, *max; + VM_SAFEPARMCOUNT(3, VM_CL_setsize); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning("setsize: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning("setsize: can not modify free entity\n"); + return; + } + min = PRVM_G_VECTOR(OFS_PARM1); + max = PRVM_G_VECTOR(OFS_PARM2); + + SetMinMaxSize( e, min, max ); + + CL_LinkEdict(e); +} + +// #8 void(entity e, float chan, string samp, float volume, float atten) sound +static void VM_CL_sound (void) +{ + const char *sample; + int channel; + prvm_edict_t *entity; + float volume; + float attenuation; + float pitchchange; + int flags; + vec3_t org; + + VM_SAFEPARMCOUNTRANGE(5, 7, VM_CL_sound); + + entity = PRVM_G_EDICT(OFS_PARM0); + channel = (int)PRVM_G_FLOAT(OFS_PARM1); + sample = PRVM_G_STRING(OFS_PARM2); + volume = PRVM_G_FLOAT(OFS_PARM3); + attenuation = PRVM_G_FLOAT(OFS_PARM4); + + if (volume < 0 || volume > 1) + { + VM_Warning("VM_CL_sound: volume must be in range 0-1\n"); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + VM_Warning("VM_CL_sound: attenuation must be in range 0-4\n"); + return; + } + + if (prog->argc < 6) + pitchchange = 0; + else + pitchchange = PRVM_G_FLOAT(OFS_PARM5); + // ignoring prog->argc < 7 for now (no flags supported yet) + + if (prog->argc < 7) + flags = 0; + else + flags = PRVM_G_FLOAT(OFS_PARM6); + + channel = CHAN_USER2ENGINE(channel); + + if (!IS_CHAN(channel)) + { + VM_Warning("VM_CL_sound: channel must be in range 0-127\n"); + return; + } + + CL_VM_GetEntitySoundOrigin(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), org); + S_StartSound(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), channel, S_FindName(sample), org, volume, attenuation); +} + +// #483 void(vector origin, string sample, float volume, float attenuation) pointsound +static void VM_CL_pointsound(void) +{ + const char *sample; + float volume; + float attenuation; + vec3_t org; + + VM_SAFEPARMCOUNT(4, VM_CL_pointsound); + + VectorCopy( PRVM_G_VECTOR(OFS_PARM0), org); + sample = PRVM_G_STRING(OFS_PARM1); + volume = PRVM_G_FLOAT(OFS_PARM2); + attenuation = PRVM_G_FLOAT(OFS_PARM3); + + if (volume < 0 || volume > 1) + { + VM_Warning("VM_CL_pointsound: volume must be in range 0-1\n"); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + VM_Warning("VM_CL_pointsound: attenuation must be in range 0-4\n"); + return; + } + + // Send World Entity as Entity to Play Sound (for CSQC, that is MAX_EDICTS) + S_StartSound(MAX_EDICTS, 0, S_FindName(sample), org, volume, attenuation); +} + +// #14 entity() spawn +static void VM_CL_spawn (void) +{ + prvm_edict_t *ed; + ed = PRVM_ED_Alloc(); + VM_RETURN_EDICT(ed); +} + +void CL_VM_SetTraceGlobals(const trace_t *trace, int svent) +{ + VM_SetTraceGlobals(trace); + PRVM_clientglobalfloat(trace_networkentity) = svent; +} + +#define CL_HitNetworkBrushModels(move) !((move) == MOVE_WORLDONLY) +#define CL_HitNetworkPlayers(move) !((move) == MOVE_WORLDONLY || (move) == MOVE_NOMONSTERS) + +// #16 void(vector v1, vector v2, float movetype, entity ignore) traceline +static void VM_CL_traceline (void) +{ + float *v1, *v2; + trace_t trace; + int move, svent; + prvm_edict_t *ent; + +// R_TimeReport("pretraceline"); + + VM_SAFEPARMCOUNTRANGE(4, 4, VM_CL_traceline); + + prog->xfunction->builtinsprofile += 30; + + v1 = PRVM_G_VECTOR(OFS_PARM0); + v2 = PRVM_G_VECTOR(OFS_PARM1); + move = (int)PRVM_G_FLOAT(OFS_PARM2); + ent = PRVM_G_EDICT(OFS_PARM3); + + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) + PRVM_ERROR("%s: NAN errors detected in traceline('%f %f %f', '%f %f %f', %i, entity %i)\n", PRVM_NAME, v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); + + trace = CL_TraceLine(v1, v2, move, ent, CL_GenericHitSuperContentsMask(ent), CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true, false); + + CL_VM_SetTraceGlobals(&trace, svent); +// R_TimeReport("traceline"); +} + +/* +================= +VM_CL_tracebox + +Used for use tracing and shot targeting +Traces are blocked by bbox and exact bsp entityes, and also slide box entities +if the tryents flag is set. + +tracebox (vector1, vector mins, vector maxs, vector2, tryents) +================= +*/ +// LordHavoc: added this for my own use, VERY useful, similar to traceline +static void VM_CL_tracebox (void) +{ + float *v1, *v2, *m1, *m2; + trace_t trace; + int move, svent; + prvm_edict_t *ent; + +// R_TimeReport("pretracebox"); + VM_SAFEPARMCOUNTRANGE(6, 8, VM_CL_tracebox); // allow more parameters for future expansion + + prog->xfunction->builtinsprofile += 30; + + v1 = PRVM_G_VECTOR(OFS_PARM0); + m1 = PRVM_G_VECTOR(OFS_PARM1); + m2 = PRVM_G_VECTOR(OFS_PARM2); + v2 = PRVM_G_VECTOR(OFS_PARM3); + move = (int)PRVM_G_FLOAT(OFS_PARM4); + ent = PRVM_G_EDICT(OFS_PARM5); + + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) + PRVM_ERROR("%s: NAN errors detected in tracebox('%f %f %f', '%f %f %f', '%f %f %f', '%f %f %f', %i, entity %i)\n", PRVM_NAME, v1[0], v1[1], v1[2], m1[0], m1[1], m1[2], m2[0], m2[1], m2[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); + + trace = CL_TraceBox(v1, m1, m2, v2, move, ent, CL_GenericHitSuperContentsMask(ent), CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true); + + CL_VM_SetTraceGlobals(&trace, svent); +// R_TimeReport("tracebox"); +} + +trace_t CL_Trace_Toss (prvm_edict_t *tossent, prvm_edict_t *ignore, int *svent) +{ + int i; + float gravity; + vec3_t move, end; + vec3_t original_origin; + vec3_t original_velocity; + vec3_t original_angles; + vec3_t original_avelocity; + trace_t trace; + + VectorCopy(PRVM_clientedictvector(tossent, origin) , original_origin ); + VectorCopy(PRVM_clientedictvector(tossent, velocity) , original_velocity ); + VectorCopy(PRVM_clientedictvector(tossent, angles) , original_angles ); + VectorCopy(PRVM_clientedictvector(tossent, avelocity), original_avelocity); + + gravity = PRVM_clientedictfloat(tossent, gravity); + if (!gravity) + gravity = 1.0f; + gravity *= cl.movevars_gravity * 0.05; + + for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds + { + PRVM_clientedictvector(tossent, velocity)[2] -= gravity; + VectorMA (PRVM_clientedictvector(tossent, angles), 0.05, PRVM_clientedictvector(tossent, avelocity), PRVM_clientedictvector(tossent, angles)); + VectorScale (PRVM_clientedictvector(tossent, velocity), 0.05, move); + VectorAdd (PRVM_clientedictvector(tossent, origin), move, end); + trace = CL_TraceBox(PRVM_clientedictvector(tossent, origin), PRVM_clientedictvector(tossent, mins), PRVM_clientedictvector(tossent, maxs), end, MOVE_NORMAL, tossent, CL_GenericHitSuperContentsMask(tossent), true, true, NULL, true); + VectorCopy (trace.endpos, PRVM_clientedictvector(tossent, origin)); + + if (trace.fraction < 1) + break; + } + + VectorCopy(original_origin , PRVM_clientedictvector(tossent, origin) ); + VectorCopy(original_velocity , PRVM_clientedictvector(tossent, velocity) ); + VectorCopy(original_angles , PRVM_clientedictvector(tossent, angles) ); + VectorCopy(original_avelocity, PRVM_clientedictvector(tossent, avelocity)); + + return trace; +} + +static void VM_CL_tracetoss (void) +{ + trace_t trace; + prvm_edict_t *ent; + prvm_edict_t *ignore; + int svent = 0; + + prog->xfunction->builtinsprofile += 600; + + VM_SAFEPARMCOUNT(2, VM_CL_tracetoss); + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning("tracetoss: can not use world entity\n"); + return; + } + ignore = PRVM_G_EDICT(OFS_PARM1); + + trace = CL_Trace_Toss (ent, ignore, &svent); + + CL_VM_SetTraceGlobals(&trace, svent); +} + + +// #20 void(string s) precache_model +void VM_CL_precache_model (void) +{ + const char *name; + int i; + dp_model_t *m; + + VM_SAFEPARMCOUNT(1, VM_CL_precache_model); + + name = PRVM_G_STRING(OFS_PARM0); + for (i = 0;i < MAX_MODELS && cl.csqc_model_precache[i];i++) + { + if(!strcmp(cl.csqc_model_precache[i]->name, name)) + { + PRVM_G_FLOAT(OFS_RETURN) = -(i+1); + return; + } + } + PRVM_G_FLOAT(OFS_RETURN) = 0; + m = Mod_ForName(name, false, false, name[0] == '*' ? cl.model_name[1] : NULL); + if(m && m->loaded) + { + for (i = 0;i < MAX_MODELS;i++) + { + if (!cl.csqc_model_precache[i]) + { + cl.csqc_model_precache[i] = (dp_model_t*)m; + PRVM_G_FLOAT(OFS_RETURN) = -(i+1); + return; + } + } + VM_Warning("VM_CL_precache_model: no free models\n"); + return; + } + VM_Warning("VM_CL_precache_model: model \"%s\" not found\n", name); +} + +int CSQC_EntitiesInBox (vec3_t mins, vec3_t maxs, int maxlist, prvm_edict_t **list) +{ + prvm_edict_t *ent; + int i, k; + + ent = PRVM_NEXT_EDICT(prog->edicts); + for(k=0,i=1; inum_edicts ;i++, ent = PRVM_NEXT_EDICT(ent)) + { + if (ent->priv.required->free) + continue; + if(BoxesOverlap(mins, maxs, PRVM_clientedictvector(ent, absmin), PRVM_clientedictvector(ent, absmax))) + list[k++] = ent; + } + return k; +} + +// #22 entity(vector org, float rad) findradius +static void VM_CL_findradius (void) +{ + prvm_edict_t *ent, *chain; + vec_t radius, radius2; + vec3_t org, eorg, mins, maxs; + int i, numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_findradius); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if(chainfield < 0) + PRVM_ERROR("VM_findchain: %s doesnt have the specified chain field !", PRVM_NAME); + + chain = (prvm_edict_t *)prog->edicts; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + radius = PRVM_G_FLOAT(OFS_PARM1); + radius2 = radius * radius; + + mins[0] = org[0] - (radius + 1); + mins[1] = org[1] - (radius + 1); + mins[2] = org[2] - (radius + 1); + maxs[0] = org[0] + (radius + 1); + maxs[1] = org[1] + (radius + 1); + maxs[2] = org[2] + (radius + 1); + numtouchedicts = CSQC_EntitiesInBox(mins, maxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens //[515]: for what then ? + Con_Printf("CSQC_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + ent = touchedicts[i]; + // Quake did not return non-solid entities but darkplaces does + // (note: this is the reason you can't blow up fallen zombies) + if (PRVM_clientedictfloat(ent, solid) == SOLID_NOT && !sv_gameplayfix_blowupfallenzombies.integer) + continue; + // LordHavoc: compare against bounding box rather than center so it + // doesn't miss large objects, and use DotProduct instead of Length + // for a major speedup + VectorSubtract(org, PRVM_clientedictvector(ent, origin), eorg); + if (sv_gameplayfix_findradiusdistancetobox.integer) + { + eorg[0] -= bound(PRVM_clientedictvector(ent, mins)[0], eorg[0], PRVM_clientedictvector(ent, maxs)[0]); + eorg[1] -= bound(PRVM_clientedictvector(ent, mins)[1], eorg[1], PRVM_clientedictvector(ent, maxs)[1]); + eorg[2] -= bound(PRVM_clientedictvector(ent, mins)[2], eorg[2], PRVM_clientedictvector(ent, maxs)[2]); + } + else + VectorMAMAM(1, eorg, -0.5f, PRVM_clientedictvector(ent, mins), -0.5f, PRVM_clientedictvector(ent, maxs), eorg); + if (DotProduct(eorg, eorg) < radius2) + { + PRVM_EDICTFIELDEDICT(ent, chainfield) = PRVM_EDICT_TO_PROG(chain); + chain = ent; + } + } + + VM_RETURN_EDICT(chain); +} + +// #34 float() droptofloor +static void VM_CL_droptofloor (void) +{ + prvm_edict_t *ent; + vec3_t end; + trace_t trace; + + VM_SAFEPARMCOUNTRANGE(0, 2, VM_CL_droptofloor); // allow 2 parameters because the id1 defs.qc had an incorrect prototype + + // assume failure if it returns early + PRVM_G_FLOAT(OFS_RETURN) = 0; + + ent = PRVM_PROG_TO_EDICT(PRVM_clientglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning("droptofloor: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("droptofloor: can not modify free entity\n"); + return; + } + + VectorCopy (PRVM_clientedictvector(ent, origin), end); + end[2] -= 256; + + trace = CL_TraceBox(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), PRVM_clientedictvector(ent, maxs), end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true); + + if (trace.fraction != 1) + { + VectorCopy (trace.endpos, PRVM_clientedictvector(ent, origin)); + PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) | FL_ONGROUND; + PRVM_clientedictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + PRVM_G_FLOAT(OFS_RETURN) = 1; + // if support is destroyed, keep suspended (gross hack for floating items in various maps) +// ent->priv.server->suspendedinairflag = true; + } +} + +// #35 void(float style, string value) lightstyle +static void VM_CL_lightstyle (void) +{ + int i; + const char *c; + + VM_SAFEPARMCOUNT(2, VM_CL_lightstyle); + + i = (int)PRVM_G_FLOAT(OFS_PARM0); + c = PRVM_G_STRING(OFS_PARM1); + if (i >= cl.max_lightstyle) + { + VM_Warning("VM_CL_lightstyle >= MAX_LIGHTSTYLES\n"); + return; + } + strlcpy (cl.lightstyle[i].map, c, sizeof (cl.lightstyle[i].map)); + cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; + cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); +} + +// #40 float(entity e) checkbottom +static void VM_CL_checkbottom (void) +{ + static int cs_yes, cs_no; + prvm_edict_t *ent; + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VM_SAFEPARMCOUNT(1, VM_CL_checkbottom); + ent = PRVM_G_EDICT(OFS_PARM0); + PRVM_G_FLOAT(OFS_RETURN) = 0; + + VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); + VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (!(CL_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) + goto realcheck; + } + + cs_yes++; + PRVM_G_FLOAT(OFS_RETURN) = true; + return; // we got out easy + +realcheck: + cs_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*sv_stepheight.value; + trace = CL_TraceLine(start, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true, false); + + if (trace.fraction == 1.0) + return; + + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = CL_TraceLine(start, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true, false); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) + return; + } + + cs_yes++; + PRVM_G_FLOAT(OFS_RETURN) = true; +} + +// #41 float(vector v) pointcontents +static void VM_CL_pointcontents (void) +{ + VM_SAFEPARMCOUNT(1, VM_CL_pointcontents); + PRVM_G_FLOAT(OFS_RETURN) = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, CL_PointSuperContents(PRVM_G_VECTOR(OFS_PARM0))); +} + +// #48 void(vector o, vector d, float color, float count) particle +static void VM_CL_particle (void) +{ + float *org, *dir; + int count; + unsigned char color; + VM_SAFEPARMCOUNT(4, VM_CL_particle); + + org = PRVM_G_VECTOR(OFS_PARM0); + dir = PRVM_G_VECTOR(OFS_PARM1); + color = (int)PRVM_G_FLOAT(OFS_PARM2); + count = (int)PRVM_G_FLOAT(OFS_PARM3); + CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color); +} + +// #74 void(vector pos, string samp, float vol, float atten) ambientsound +static void VM_CL_ambientsound (void) +{ + float *f; + sfx_t *s; + VM_SAFEPARMCOUNT(4, VM_CL_ambientsound); + s = S_FindName(PRVM_G_STRING(OFS_PARM0)); + f = PRVM_G_VECTOR(OFS_PARM1); + S_StaticSound (s, f, PRVM_G_FLOAT(OFS_PARM2), PRVM_G_FLOAT(OFS_PARM3)*64); +} + +// #92 vector(vector org[, float lpflag]) getlight (DP_QC_GETLIGHT) +static void VM_CL_getlight (void) +{ + vec3_t ambientcolor, diffusecolor, diffusenormal; + vec_t *p; + + VM_SAFEPARMCOUNTRANGE(1, 2, VM_CL_getlight); + + p = PRVM_G_VECTOR(OFS_PARM0); + VectorClear(ambientcolor); + VectorClear(diffusecolor); + VectorClear(diffusenormal); + if (prog->argc >= 2) + R_CompleteLightPoint(ambientcolor, diffusecolor, diffusenormal, p, PRVM_G_FLOAT(OFS_PARM1)); + else if (cl.worldmodel && cl.worldmodel->brush.LightPoint) + cl.worldmodel->brush.LightPoint(cl.worldmodel, p, ambientcolor, diffusecolor, diffusenormal); + VectorMA(ambientcolor, 0.5, diffusecolor, PRVM_G_VECTOR(OFS_RETURN)); +} + +//============================================================================ +//[515]: SCENE MANAGER builtins +extern qboolean CSQC_AddRenderEdict (prvm_edict_t *ed, int edictnum);//csprogs.c + +void CSQC_R_RecalcView (void) +{ + extern matrix4x4_t viewmodelmatrix_nobob; + extern matrix4x4_t viewmodelmatrix_withbob; + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, cl.csqc_vieworigin[0], cl.csqc_vieworigin[1], cl.csqc_vieworigin[2], cl.csqc_viewangles[0], cl.csqc_viewangles[1], cl.csqc_viewangles[2], 1); + Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); + Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); + Matrix4x4_Concat(&viewmodelmatrix_withbob, &r_refdef.view.matrix, &cl.csqc_viewmodelmatrixfromengine); +} + +void CL_RelinkLightFlashes(void); +//#300 void() clearscene (EXT_CSQC) +void VM_CL_R_ClearScene (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_R_ClearScene); + // clear renderable entity and light lists + r_refdef.scene.numentities = 0; + r_refdef.scene.numlights = 0; + // FIXME: restore these to the values from VM_CL_UpdateView + r_refdef.view.x = 0; + r_refdef.view.y = 0; + r_refdef.view.z = 0; + r_refdef.view.width = vid.width; + r_refdef.view.height = vid.height; + r_refdef.view.depth = 1; + // FIXME: restore frustum_x/frustum_y + r_refdef.view.useperspective = true; + r_refdef.view.frustum_y = tan(scr_fov.value * M_PI / 360.0) * (3.0/4.0) * cl.viewzoom; + r_refdef.view.frustum_x = r_refdef.view.frustum_y * (float)r_refdef.view.width / (float)r_refdef.view.height / vid_pixelheight.value; + r_refdef.view.frustum_x *= r_refdef.frustumscale_x; + r_refdef.view.frustum_y *= r_refdef.frustumscale_y; + r_refdef.view.ortho_x = scr_fov.value * (3.0 / 4.0) * (float)r_refdef.view.width / (float)r_refdef.view.height / vid_pixelheight.value; + r_refdef.view.ortho_y = scr_fov.value * (3.0 / 4.0); + r_refdef.view.clear = true; + r_refdef.view.isoverlay = false; + VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin); + VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles); + cl.csqc_vidvars.drawworld = r_drawworld.integer != 0; + cl.csqc_vidvars.drawenginesbar = false; + cl.csqc_vidvars.drawcrosshair = false; + CSQC_R_RecalcView(); +} + +//#301 void(float mask) addentities (EXT_CSQC) +extern void CSQC_Predraw (prvm_edict_t *ed);//csprogs.c +extern void CSQC_Think (prvm_edict_t *ed);//csprogs.c +void VM_CL_R_AddEntities (void) +{ + double t = Sys_DoubleTime(); + int i, drawmask; + prvm_edict_t *ed; + VM_SAFEPARMCOUNT(1, VM_CL_R_AddEntities); + drawmask = (int)PRVM_G_FLOAT(OFS_PARM0); + CSQC_RelinkAllEntities(drawmask); + CL_RelinkLightFlashes(); + + PRVM_clientglobalfloat(time) = cl.time; + for(i=1;inum_edicts;i++) + { + // so we can easily check if CSQC entity #edictnum is currently drawn + cl.csqcrenderentities[i].entitynumber = 0; + ed = &prog->edicts[i]; + if(ed->priv.required->free) + continue; + CSQC_Think(ed); + if(ed->priv.required->free) + continue; + // note that for RF_USEAXIS entities, Predraw sets v_forward/v_right/v_up globals that are read by CSQC_AddRenderEdict + CSQC_Predraw(ed); + if(ed->priv.required->free) + continue; + if(!((int)PRVM_clientedictfloat(ed, drawmask) & drawmask)) + continue; + CSQC_AddRenderEdict(ed, i); + } + + // callprofile fixing hack: do not include this time in what is counted for CSQC_UpdateView + prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= Sys_DoubleTime() - t; +} + +//#302 void(entity ent) addentity (EXT_CSQC) +void VM_CL_R_AddEntity (void) +{ + double t = Sys_DoubleTime(); + VM_SAFEPARMCOUNT(1, VM_CL_R_AddEntity); + CSQC_AddRenderEdict(PRVM_G_EDICT(OFS_PARM0), 0); + prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= Sys_DoubleTime() - t; +} + +//#303 float(float property, ...) setproperty (EXT_CSQC) +//#303 float(float property) getproperty +//#303 vector(float property) getpropertyvec +// VorteX: make this function be able to return previously set property if new value is not given +void VM_CL_R_SetView (void) +{ + int c; + float *f; + float k; + + VM_SAFEPARMCOUNTRANGE(1, 3, VM_CL_R_SetView); + + c = (int)PRVM_G_FLOAT(OFS_PARM0); + + // return value? + if (prog->argc < 2) + { + switch(c) + { + case VF_MIN: + VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.x, r_refdef.view.y, 0); + break; + case VF_MIN_X: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.x; + break; + case VF_MIN_Y: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.y; + break; + case VF_SIZE: + VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.width, r_refdef.view.height, 0); + break; + case VF_SIZE_X: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.width; + break; + case VF_SIZE_Y: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.height; + break; + case VF_VIEWPORT: + VM_Warning("VM_CL_R_GetView : VF_VIEWPORT can't be retrieved, use VF_MIN/VF_SIZE instead\n"); + break; + case VF_FOV: + VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.ortho_x, r_refdef.view.ortho_y, 0); + break; + case VF_FOVX: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ortho_x; + break; + case VF_FOVY: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ortho_y; + break; + case VF_ORIGIN: + VectorCopy(cl.csqc_vieworigin, PRVM_G_VECTOR(OFS_RETURN)); + break; + case VF_ORIGIN_X: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[0]; + break; + case VF_ORIGIN_Y: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[1]; + break; + case VF_ORIGIN_Z: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[2]; + break; + case VF_ANGLES: + VectorCopy(cl.csqc_viewangles, PRVM_G_VECTOR(OFS_RETURN)); + break; + case VF_ANGLES_X: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[0]; + break; + case VF_ANGLES_Y: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[1]; + break; + case VF_ANGLES_Z: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[2]; + break; + case VF_DRAWWORLD: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawworld; + break; + case VF_DRAWENGINESBAR: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawenginesbar; + break; + case VF_DRAWCROSSHAIR: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawcrosshair; + break; + case VF_CL_VIEWANGLES: + VectorCopy(cl.viewangles, PRVM_G_VECTOR(OFS_RETURN));; + break; + case VF_CL_VIEWANGLES_X: + PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[0]; + break; + case VF_CL_VIEWANGLES_Y: + PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[1]; + break; + case VF_CL_VIEWANGLES_Z: + PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[2]; + break; + case VF_PERSPECTIVE: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.useperspective; + break; + case VF_CLEARSCREEN: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.isoverlay; + break; + default: + PRVM_G_FLOAT(OFS_RETURN) = 0; + VM_Warning("VM_CL_R_GetView : unknown parm %i\n", c); + return; + } + return; + } + + f = PRVM_G_VECTOR(OFS_PARM1); + k = PRVM_G_FLOAT(OFS_PARM1); + switch(c) + { + case VF_MIN: + r_refdef.view.x = (int)(f[0]); + r_refdef.view.y = (int)(f[1]); + DrawQ_RecalcView(); + break; + case VF_MIN_X: + r_refdef.view.x = (int)(k); + DrawQ_RecalcView(); + break; + case VF_MIN_Y: + r_refdef.view.y = (int)(k); + DrawQ_RecalcView(); + break; + case VF_SIZE: + r_refdef.view.width = (int)(f[0]); + r_refdef.view.height = (int)(f[1]); + DrawQ_RecalcView(); + break; + case VF_SIZE_X: + r_refdef.view.width = (int)(k); + DrawQ_RecalcView(); + break; + case VF_SIZE_Y: + r_refdef.view.height = (int)(k); + DrawQ_RecalcView(); + break; + case VF_VIEWPORT: + r_refdef.view.x = (int)(f[0]); + r_refdef.view.y = (int)(f[1]); + f = PRVM_G_VECTOR(OFS_PARM2); + r_refdef.view.width = (int)(f[0]); + r_refdef.view.height = (int)(f[1]); + DrawQ_RecalcView(); + break; + case VF_FOV: + r_refdef.view.frustum_x = tan(f[0] * M_PI / 360.0);r_refdef.view.ortho_x = f[0]; + r_refdef.view.frustum_y = tan(f[1] * M_PI / 360.0);r_refdef.view.ortho_y = f[1]; + break; + case VF_FOVX: + r_refdef.view.frustum_x = tan(k * M_PI / 360.0);r_refdef.view.ortho_x = k; + break; + case VF_FOVY: + r_refdef.view.frustum_y = tan(k * M_PI / 360.0);r_refdef.view.ortho_y = k; + break; + case VF_ORIGIN: + VectorCopy(f, cl.csqc_vieworigin); + CSQC_R_RecalcView(); + break; + case VF_ORIGIN_X: + cl.csqc_vieworigin[0] = k; + CSQC_R_RecalcView(); + break; + case VF_ORIGIN_Y: + cl.csqc_vieworigin[1] = k; + CSQC_R_RecalcView(); + break; + case VF_ORIGIN_Z: + cl.csqc_vieworigin[2] = k; + CSQC_R_RecalcView(); + break; + case VF_ANGLES: + VectorCopy(f, cl.csqc_viewangles); + CSQC_R_RecalcView(); + break; + case VF_ANGLES_X: + cl.csqc_viewangles[0] = k; + CSQC_R_RecalcView(); + break; + case VF_ANGLES_Y: + cl.csqc_viewangles[1] = k; + CSQC_R_RecalcView(); + break; + case VF_ANGLES_Z: + cl.csqc_viewangles[2] = k; + CSQC_R_RecalcView(); + break; + case VF_DRAWWORLD: + cl.csqc_vidvars.drawworld = ((k != 0) && r_drawworld.integer); + break; + case VF_DRAWENGINESBAR: + cl.csqc_vidvars.drawenginesbar = k != 0; + break; + case VF_DRAWCROSSHAIR: + cl.csqc_vidvars.drawcrosshair = k != 0; + break; + case VF_CL_VIEWANGLES: + VectorCopy(f, cl.viewangles); + break; + case VF_CL_VIEWANGLES_X: + cl.viewangles[0] = k; + break; + case VF_CL_VIEWANGLES_Y: + cl.viewangles[1] = k; + break; + case VF_CL_VIEWANGLES_Z: + cl.viewangles[2] = k; + break; + case VF_PERSPECTIVE: + r_refdef.view.useperspective = k != 0; + break; + case VF_CLEARSCREEN: + r_refdef.view.isoverlay = !k; + break; + default: + PRVM_G_FLOAT(OFS_RETURN) = 0; + VM_Warning("VM_CL_R_SetView : unknown parm %i\n", c); + return; + } + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +//#305 void(vector org, float radius, vector lightcolours[, float style, string cubemapname, float pflags]) adddynamiclight (EXT_CSQC) +void VM_CL_R_AddDynamicLight (void) +{ + double t = Sys_DoubleTime(); + vec_t *org; + float radius = 300; + vec_t *col; + int style = -1; + const char *cubemapname = NULL; + int pflags = PFLAGS_CORONA | PFLAGS_FULLDYNAMIC; + float coronaintensity = 1; + float coronasizescale = 0.25; + qboolean castshadow = true; + float ambientscale = 0; + float diffusescale = 1; + float specularscale = 1; + matrix4x4_t matrix; + vec3_t forward, left, up; + VM_SAFEPARMCOUNTRANGE(3, 8, VM_CL_R_AddDynamicLight); + + // if we've run out of dlights, just return + if (r_refdef.scene.numlights >= MAX_DLIGHTS) + return; + + org = PRVM_G_VECTOR(OFS_PARM0); + radius = PRVM_G_FLOAT(OFS_PARM1); + col = PRVM_G_VECTOR(OFS_PARM2); + if (prog->argc >= 4) + { + style = (int)PRVM_G_FLOAT(OFS_PARM3); + if (style >= MAX_LIGHTSTYLES) + { + Con_DPrintf("VM_CL_R_AddDynamicLight: out of bounds lightstyle index %i\n", style); + style = -1; + } + } + if (prog->argc >= 5) + cubemapname = PRVM_G_STRING(OFS_PARM4); + if (prog->argc >= 6) + pflags = (int)PRVM_G_FLOAT(OFS_PARM5); + coronaintensity = (pflags & PFLAGS_CORONA) != 0; + castshadow = (pflags & PFLAGS_NOSHADOW) == 0; + + VectorScale(PRVM_clientglobalvector(v_forward), radius, forward); + VectorScale(PRVM_clientglobalvector(v_right), -radius, left); + VectorScale(PRVM_clientglobalvector(v_up), radius, up); + Matrix4x4_FromVectors(&matrix, forward, left, up, org); + + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &matrix, col, style, cubemapname, castshadow, coronaintensity, coronasizescale, ambientscale, diffusescale, specularscale, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= Sys_DoubleTime() - t; +} + +//============================================================================ + +//#310 vector (vector v) cs_unproject (EXT_CSQC) +static void VM_CL_unproject (void) +{ + float *f; + vec3_t temp; + + VM_SAFEPARMCOUNT(1, VM_CL_unproject); + f = PRVM_G_VECTOR(OFS_PARM0); + VectorSet(temp, + f[2], + (-1.0 + 2.0 * (f[0] / vid_conwidth.integer)) * f[2] * -r_refdef.view.frustum_x, + (-1.0 + 2.0 * (f[1] / vid_conheight.integer)) * f[2] * -r_refdef.view.frustum_y); + if(v_flipped.integer) + temp[1] = -temp[1]; + Matrix4x4_Transform(&r_refdef.view.matrix, temp, PRVM_G_VECTOR(OFS_RETURN)); +} + +//#311 vector (vector v) cs_project (EXT_CSQC) +static void VM_CL_project (void) +{ + float *f; + vec3_t v; + matrix4x4_t m; + + VM_SAFEPARMCOUNT(1, VM_CL_project); + f = PRVM_G_VECTOR(OFS_PARM0); + Matrix4x4_Invert_Simple(&m, &r_refdef.view.matrix); + Matrix4x4_Transform(&m, f, v); + if(v_flipped.integer) + v[1] = -v[1]; + VectorSet(PRVM_G_VECTOR(OFS_RETURN), + vid_conwidth.integer * (0.5*(1.0+v[1]/v[0]/-r_refdef.view.frustum_x)), + vid_conheight.integer * (0.5*(1.0+v[2]/v[0]/-r_refdef.view.frustum_y)), + v[0]); + // explanation: + // after transforming, relative position to viewport (0..1) = 0.5 * (1 + v[2]/v[0]/-frustum_{x \or y}) + // as 2D drawing honors the viewport too, to get the same pixel, we simply multiply this by conwidth/height +} + +//#330 float(float stnum) getstatf (EXT_CSQC) +static void VM_CL_getstatf (void) +{ + int i; + union + { + float f; + int l; + }dat; + VM_SAFEPARMCOUNT(1, VM_CL_getstatf); + i = (int)PRVM_G_FLOAT(OFS_PARM0); + if(i < 0 || i >= MAX_CL_STATS) + { + VM_Warning("VM_CL_getstatf: index>=MAX_CL_STATS or index<0\n"); + return; + } + dat.l = cl.stats[i]; + PRVM_G_FLOAT(OFS_RETURN) = dat.f; +} + +//#331 float(float stnum) getstati (EXT_CSQC) +static void VM_CL_getstati (void) +{ + int i, index; + int firstbit, bitcount; + + VM_SAFEPARMCOUNTRANGE(1, 3, VM_CL_getstati); + + index = (int)PRVM_G_FLOAT(OFS_PARM0); + if (prog->argc > 1) + { + firstbit = (int)PRVM_G_FLOAT(OFS_PARM1); + if (prog->argc > 2) + bitcount = (int)PRVM_G_FLOAT(OFS_PARM2); + else + bitcount = 1; + } + else + { + firstbit = 0; + bitcount = 32; + } + + if(index < 0 || index >= MAX_CL_STATS) + { + VM_Warning("VM_CL_getstati: index>=MAX_CL_STATS or index<0\n"); + return; + } + i = cl.stats[index]; + if (bitcount != 32) //32 causes the mask to overflow, so there's nothing to subtract from. + i = (((unsigned int)i)&(((1<>firstbit; + PRVM_G_FLOAT(OFS_RETURN) = i; +} + +//#332 string(float firststnum) getstats (EXT_CSQC) +static void VM_CL_getstats (void) +{ + int i; + char t[17]; + VM_SAFEPARMCOUNT(1, VM_CL_getstats); + i = (int)PRVM_G_FLOAT(OFS_PARM0); + if(i < 0 || i > MAX_CL_STATS-4) + { + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + VM_Warning("VM_CL_getstats: index>MAX_CL_STATS-4 or index<0\n"); + return; + } + strlcpy(t, (char*)&cl.stats[i], sizeof(t)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t); +} + +//#333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) +static void VM_CL_setmodelindex (void) +{ + int i; + prvm_edict_t *t; + struct model_s *model; + + VM_SAFEPARMCOUNT(2, VM_CL_setmodelindex); + + t = PRVM_G_EDICT(OFS_PARM0); + + i = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_clientedictstring(t, model) = 0; + PRVM_clientedictfloat(t, modelindex) = 0; + + if (!i) + return; + + model = CL_GetModelByIndex(i); + if (!model) + { + VM_Warning("VM_CL_setmodelindex: null model\n"); + return; + } + PRVM_clientedictstring(t, model) = PRVM_SetEngineString(model->name); + PRVM_clientedictfloat(t, modelindex) = i; + + // TODO: check if this breaks needed consistency and maybe add a cvar for it too?? [1/10/2008 Black] + if (model) + { + SetMinMaxSize (t, model->normalmins, model->normalmaxs); + } + else + SetMinMaxSize (t, vec3_origin, vec3_origin); +} + +//#334 string(float mdlindex) modelnameforindex (EXT_CSQC) +static void VM_CL_modelnameforindex (void) +{ + dp_model_t *model; + + VM_SAFEPARMCOUNT(1, VM_CL_modelnameforindex); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + model = CL_GetModelByIndex((int)PRVM_G_FLOAT(OFS_PARM0)); + PRVM_G_INT(OFS_RETURN) = model ? PRVM_SetEngineString(model->name) : 0; +} + +//#335 float(string effectname) particleeffectnum (EXT_CSQC) +static void VM_CL_particleeffectnum (void) +{ + int i; + VM_SAFEPARMCOUNT(1, VM_CL_particleeffectnum); + i = CL_ParticleEffectIndexForName(PRVM_G_STRING(OFS_PARM0)); + if (i == 0) + i = -1; + PRVM_G_FLOAT(OFS_RETURN) = i; +} + +// #336 void(entity ent, float effectnum, vector start, vector end[, float color]) trailparticles (EXT_CSQC) +static void VM_CL_trailparticles (void) +{ + int i; + float *start, *end; + prvm_edict_t *t; + VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_trailparticles); + + t = PRVM_G_EDICT(OFS_PARM0); + i = (int)PRVM_G_FLOAT(OFS_PARM1); + start = PRVM_G_VECTOR(OFS_PARM2); + end = PRVM_G_VECTOR(OFS_PARM3); + + if (i < 0) + return; + CL_ParticleEffect(i, 1, start, end, PRVM_clientedictvector(t, velocity), PRVM_clientedictvector(t, velocity), NULL, prog->argc >= 5 ? (int)PRVM_G_FLOAT(OFS_PARM4) : 0); +} + +//#337 void(float effectnum, vector origin, vector dir, float count[, float color]) pointparticles (EXT_CSQC) +static void VM_CL_pointparticles (void) +{ + int i; + float n; + float *f, *v; + VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_pointparticles); + i = (int)PRVM_G_FLOAT(OFS_PARM0); + f = PRVM_G_VECTOR(OFS_PARM1); + v = PRVM_G_VECTOR(OFS_PARM2); + n = PRVM_G_FLOAT(OFS_PARM3); + if (i < 0) + return; + CL_ParticleEffect(i, n, f, f, v, v, NULL, prog->argc >= 5 ? (int)PRVM_G_FLOAT(OFS_PARM4) : 0); +} + +//#502 void(float effectnum, entity own, vector origin_from, vector origin_to, vector dir_from, vector dir_to, float count, float extflags) boxparticles (DP_CSQC_BOXPARTICLES) +static void VM_CL_boxparticles (void) +{ + int effectnum; + // prvm_edict_t *own; + float *origin_from, *origin_to, *dir_from, *dir_to; + float count; + int flags; + float tintmins[4], tintmaxs[4]; + VM_SAFEPARMCOUNTRANGE(7, 8, VM_CL_boxparticles); + + effectnum = (int)PRVM_G_FLOAT(OFS_PARM0); + // own = PRVM_G_EDICT(OFS_PARM1); // TODO find use for this + origin_from = PRVM_G_VECTOR(OFS_PARM2); + origin_to = PRVM_G_VECTOR(OFS_PARM3); + dir_from = PRVM_G_VECTOR(OFS_PARM4); + dir_to = PRVM_G_VECTOR(OFS_PARM5); + count = PRVM_G_FLOAT(OFS_PARM6); + if(prog->argc >= 8) + flags = PRVM_G_FLOAT(OFS_PARM7); + else + flags = 0; + Vector4Set(tintmins, 1, 1, 1, 1); + Vector4Set(tintmaxs, 1, 1, 1, 1); + if(flags & 1) // read alpha + { + tintmins[3] = PRVM_clientglobalfloat(particles_alphamin); + tintmaxs[3] = PRVM_clientglobalfloat(particles_alphamax); + } + if(flags & 2) // read color + { + VectorCopy(PRVM_clientglobalvector(particles_colormin), tintmins); + VectorCopy(PRVM_clientglobalvector(particles_colormax), tintmaxs); + } + if (effectnum < 0) + return; + CL_ParticleTrail(effectnum, count, origin_from, origin_to, dir_from, dir_to, NULL, 0, true, true, tintmins, tintmaxs); +} + +//#531 void(float pause) setpause +static void VM_CL_setpause(void) +{ + VM_SAFEPARMCOUNT(1, VM_CL_setpause); + if ((int)PRVM_G_FLOAT(OFS_PARM0) != 0) + cl.csqc_paused = true; + else + cl.csqc_paused = false; +} + +//#343 void(float usecursor) setcursormode (EXT_CSQC) +static void VM_CL_setcursormode (void) +{ + VM_SAFEPARMCOUNT(1, VM_CL_setcursormode); + cl.csqc_wantsmousemove = PRVM_G_FLOAT(OFS_PARM0) != 0; + cl_ignoremousemoves = 2; +} + +//#344 vector() getmousepos (EXT_CSQC) +static void VM_CL_getmousepos(void) +{ + VM_SAFEPARMCOUNT(0,VM_CL_getmousepos); + + if (key_consoleactive || key_dest != key_game) + VectorSet(PRVM_G_VECTOR(OFS_RETURN), 0, 0, 0); + else if (cl.csqc_wantsmousemove) + VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_windowmouse_x * vid_conwidth.integer / vid.width, in_windowmouse_y * vid_conheight.integer / vid.height, 0); + else + VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height, 0); +} + +//#345 float(float framenum) getinputstate (EXT_CSQC) +static void VM_CL_getinputstate (void) +{ + int i, frame; + VM_SAFEPARMCOUNT(1, VM_CL_getinputstate); + frame = (int)PRVM_G_FLOAT(OFS_PARM0); + PRVM_G_FLOAT(OFS_RETURN) = false; + for (i = 0;i < CL_MAX_USERCMDS;i++) + { + if (cl.movecmd[i].sequence == frame) + { + VectorCopy(cl.movecmd[i].viewangles, PRVM_clientglobalvector(input_angles)); + PRVM_clientglobalfloat(input_buttons) = cl.movecmd[i].buttons; // FIXME: this should not be directly exposed to csqc (translation layer needed?) + PRVM_clientglobalvector(input_movevalues)[0] = cl.movecmd[i].forwardmove; + PRVM_clientglobalvector(input_movevalues)[1] = cl.movecmd[i].sidemove; + PRVM_clientglobalvector(input_movevalues)[2] = cl.movecmd[i].upmove; + PRVM_clientglobalfloat(input_timelength) = cl.movecmd[i].frametime; + if(cl.movecmd[i].crouch) + { + VectorCopy(cl.playercrouchmins, PRVM_clientglobalvector(pmove_mins)); + VectorCopy(cl.playercrouchmaxs, PRVM_clientglobalvector(pmove_maxs)); + } + else + { + VectorCopy(cl.playerstandmins, PRVM_clientglobalvector(pmove_mins)); + VectorCopy(cl.playerstandmaxs, PRVM_clientglobalvector(pmove_maxs)); + } + PRVM_G_FLOAT(OFS_RETURN) = true; + } + } +} + +//#346 void(float sens) setsensitivityscaler (EXT_CSQC) +static void VM_CL_setsensitivityscale (void) +{ + VM_SAFEPARMCOUNT(1, VM_CL_setsensitivityscale); + cl.sensitivityscale = PRVM_G_FLOAT(OFS_PARM0); +} + +//#347 void() runstandardplayerphysics (EXT_CSQC) +static void VM_CL_runplayerphysics (void) +{ +} + +//#348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) +static void VM_CL_getplayerkey (void) +{ + int i; + char t[128]; + const char *c; + + VM_SAFEPARMCOUNT(2, VM_CL_getplayerkey); + + i = (int)PRVM_G_FLOAT(OFS_PARM0); + c = PRVM_G_STRING(OFS_PARM1); + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + Sbar_SortFrags(); + + if (i < 0) + i = Sbar_GetSortedPlayerIndex(-1-i); + if(i < 0 || i >= cl.maxclients) + return; + + t[0] = 0; + + if(!strcasecmp(c, "name")) + strlcpy(t, cl.scores[i].name, sizeof(t)); + else + if(!strcasecmp(c, "frags")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].frags); + else + if(!strcasecmp(c, "ping")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_ping); + else + if(!strcasecmp(c, "pl")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_packetloss); + else + if(!strcasecmp(c, "movementloss")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_movementloss); + else + if(!strcasecmp(c, "entertime")) + dpsnprintf(t, sizeof(t), "%f", cl.scores[i].qw_entertime); + else + if(!strcasecmp(c, "colors")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].colors); + else + if(!strcasecmp(c, "topcolor")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].colors & 0xf0); + else + if(!strcasecmp(c, "bottomcolor")) + dpsnprintf(t, sizeof(t), "%i", (cl.scores[i].colors &15)<<4); + else + if(!strcasecmp(c, "viewentity")) + dpsnprintf(t, sizeof(t), "%i", i+1); + else + if(gamemode == GAME_XONOTIC && !strcasecmp(c, "TEMPHACK_origin")) + { + // PLEASE REMOVE THIS once deltalisten() of EXT_CSQC_1 + // is implemented, or Xonotic uses CSQC-networked + // players, whichever comes first + entity_t *e = cl.entities + (i+1); + if(e->state_current.active) + { + vec3_t origin; + Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); + dpsnprintf(t, sizeof(t), "%.9g %.9g %.9g", origin[0], origin[1], origin[2]); + } + } + if(!t[0]) + return; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t); +} + +//#351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) +static void VM_CL_setlistener (void) +{ + VM_SAFEPARMCOUNT(4, VM_CL_setlistener); + Matrix4x4_FromVectors(&cl.csqc_listenermatrix, PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), PRVM_G_VECTOR(OFS_PARM3), PRVM_G_VECTOR(OFS_PARM0)); + cl.csqc_usecsqclistener = true; //use csqc listener at this frame +} + +//#352 void(string cmdname) registercommand (EXT_CSQC) +static void VM_CL_registercmd (void) +{ + char *t; + VM_SAFEPARMCOUNT(1, VM_CL_registercmd); + if(!Cmd_Exists(PRVM_G_STRING(OFS_PARM0))) + { + size_t alloclen; + + alloclen = strlen(PRVM_G_STRING(OFS_PARM0)) + 1; + t = (char *)Z_Malloc(alloclen); + memcpy(t, PRVM_G_STRING(OFS_PARM0), alloclen); + Cmd_AddCommand(t, NULL, "console command created by QuakeC"); + } + else + Cmd_AddCommand(PRVM_G_STRING(OFS_PARM0), NULL, "console command created by QuakeC"); + +} + +//#360 float() readbyte (EXT_CSQC) +static void VM_CL_ReadByte (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadByte); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadByte(); +} + +//#361 float() readchar (EXT_CSQC) +static void VM_CL_ReadChar (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadChar); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadChar(); +} + +//#362 float() readshort (EXT_CSQC) +static void VM_CL_ReadShort (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadShort); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadShort(); +} + +//#363 float() readlong (EXT_CSQC) +static void VM_CL_ReadLong (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadLong); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadLong(); +} + +//#364 float() readcoord (EXT_CSQC) +static void VM_CL_ReadCoord (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadCoord); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadCoord(cls.protocol); +} + +//#365 float() readangle (EXT_CSQC) +static void VM_CL_ReadAngle (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadAngle); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadAngle(cls.protocol); +} + +//#366 string() readstring (EXT_CSQC) +static void VM_CL_ReadString (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadString); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(MSG_ReadString()); +} + +//#367 float() readfloat (EXT_CSQC) +static void VM_CL_ReadFloat (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadFloat); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadFloat(); +} + +//#501 string() readpicture (DP_CSQC_READWRITEPICTURE) +extern cvar_t cl_readpicture_force; +static void VM_CL_ReadPicture (void) +{ + const char *name; + unsigned char *data; + unsigned char *buf; + int size; + int i; + cachepic_t *pic; + + VM_SAFEPARMCOUNT(0, VM_CL_ReadPicture); + + name = MSG_ReadString(); + size = MSG_ReadShort(); + + // check if a texture of that name exists + // if yes, it is used and the data is discarded + // if not, the (low quality) data is used to build a new texture, whose name will get returned + + pic = Draw_CachePic_Flags (name, CACHEPICFLAG_NOTPERSISTENT); + + if(size) + { + if(pic->tex == r_texture_notexture) + pic->tex = NULL; // don't overwrite the notexture by Draw_NewPic + if(pic->tex && !cl_readpicture_force.integer) + { + // texture found and loaded + // skip over the jpeg as we don't need it + for(i = 0; i < size; ++i) + (void) MSG_ReadByte(); + } + else + { + // texture not found + // use the attached jpeg as texture + buf = (unsigned char *) Mem_Alloc(tempmempool, size); + MSG_ReadBytes(size, buf); + data = JPEG_LoadImage_BGRA(buf, size, NULL); + Mem_Free(buf); + Draw_NewPic(name, image_width, image_height, false, data); + Mem_Free(data); + } + } + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(name); +} + +////////////////////////////////////////////////////////// + +static void VM_CL_makestatic (void) +{ + prvm_edict_t *ent; + + VM_SAFEPARMCOUNT(1, VM_CL_makestatic); + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning("makestatic: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("makestatic: can not modify free entity\n"); + return; + } + + if (cl.num_static_entities < cl.max_static_entities) + { + int renderflags; + entity_t *staticent = &cl.static_entities[cl.num_static_entities++]; + + // copy it to the current state + memset(staticent, 0, sizeof(*staticent)); + staticent->render.model = CL_GetModelByIndex((int)PRVM_clientedictfloat(ent, modelindex)); + staticent->render.framegroupblend[0].frame = (int)PRVM_clientedictfloat(ent, frame); + staticent->render.framegroupblend[0].lerp = 1; + // make torchs play out of sync + staticent->render.framegroupblend[0].start = lhrandom(-10, -1); + staticent->render.skinnum = (int)PRVM_clientedictfloat(ent, skin); + staticent->render.effects = (int)PRVM_clientedictfloat(ent, effects); + staticent->render.alpha = PRVM_clientedictfloat(ent, alpha); + staticent->render.scale = PRVM_clientedictfloat(ent, scale); + VectorCopy(PRVM_clientedictvector(ent, colormod), staticent->render.colormod); + VectorCopy(PRVM_clientedictvector(ent, glowmod), staticent->render.glowmod); + + // sanitize values + if (!staticent->render.alpha) + staticent->render.alpha = 1.0f; + if (!staticent->render.scale) + staticent->render.scale = 1.0f; + if (!VectorLength2(staticent->render.colormod)) + VectorSet(staticent->render.colormod, 1, 1, 1); + if (!VectorLength2(staticent->render.glowmod)) + VectorSet(staticent->render.glowmod, 1, 1, 1); + + renderflags = (int)PRVM_clientedictfloat(ent, renderflags); + if (renderflags & RF_USEAXIS) + { + vec3_t left; + VectorNegate(PRVM_clientglobalvector(v_right), left); + Matrix4x4_FromVectors(&staticent->render.matrix, PRVM_clientglobalvector(v_forward), left, PRVM_clientglobalvector(v_up), PRVM_clientedictvector(ent, origin)); + Matrix4x4_Scale(&staticent->render.matrix, staticent->render.scale, 1); + } + else + Matrix4x4_CreateFromQuakeEntity(&staticent->render.matrix, PRVM_clientedictvector(ent, origin)[0], PRVM_clientedictvector(ent, origin)[1], PRVM_clientedictvector(ent, origin)[2], PRVM_clientedictvector(ent, angles)[0], PRVM_clientedictvector(ent, angles)[1], PRVM_clientedictvector(ent, angles)[2], staticent->render.scale); + + // either fullbright or lit + if(!r_fullbright.integer) + { + if (!(staticent->render.effects & EF_FULLBRIGHT)) + staticent->render.flags |= RENDER_LIGHT; + else if(r_equalize_entities_fullbright.integer) + staticent->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; + } + // turn off shadows from transparent objects + if (!(staticent->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (staticent->render.alpha >= 1)) + staticent->render.flags |= RENDER_SHADOW; + if (staticent->render.effects & EF_NODEPTHTEST) + staticent->render.flags |= RENDER_NODEPTHTEST; + if (staticent->render.effects & EF_ADDITIVE) + staticent->render.flags |= RENDER_ADDITIVE; + if (staticent->render.effects & EF_DOUBLESIDED) + staticent->render.flags |= RENDER_DOUBLESIDED; + + staticent->render.allowdecals = true; + CL_UpdateRenderEntity(&staticent->render); + } + else + Con_Printf("Too many static entities"); + +// throw the entity away now + PRVM_ED_Free (ent); +} + +//=================================================================// + +/* +================= +VM_CL_copyentity + +copies data from one entity to another + +copyentity(src, dst) +================= +*/ +static void VM_CL_copyentity (void) +{ + prvm_edict_t *in, *out; + VM_SAFEPARMCOUNT(2, VM_CL_copyentity); + in = PRVM_G_EDICT(OFS_PARM0); + if (in == prog->edicts) + { + VM_Warning("copyentity: can not read world entity\n"); + return; + } + if (in->priv.server->free) + { + VM_Warning("copyentity: can not read free entity\n"); + return; + } + out = PRVM_G_EDICT(OFS_PARM1); + if (out == prog->edicts) + { + VM_Warning("copyentity: can not modify world entity\n"); + return; + } + if (out->priv.server->free) + { + VM_Warning("copyentity: can not modify free entity\n"); + return; + } + memcpy(out->fields.vp, in->fields.vp, prog->entityfields * 4); + CL_LinkEdict(out); +} + +//=================================================================// + +// #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) +static void VM_CL_effect (void) +{ + VM_SAFEPARMCOUNT(5, VM_CL_effect); + CL_Effect(PRVM_G_VECTOR(OFS_PARM0), (int)PRVM_G_FLOAT(OFS_PARM1), (int)PRVM_G_FLOAT(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), PRVM_G_FLOAT(OFS_PARM4)); +} + +// #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) +static void VM_CL_te_blood (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(3, VM_CL_te_blood); + if (PRVM_G_FLOAT(OFS_PARM2) < 1) + return; + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_BLOOD, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM1), NULL, 0); +} + +// #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) +static void VM_CL_te_bloodshower (void) +{ + vec_t speed; + vec3_t vel1, vel2; + VM_SAFEPARMCOUNT(4, VM_CL_te_bloodshower); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + speed = PRVM_G_FLOAT(OFS_PARM2); + vel1[0] = -speed; + vel1[1] = -speed; + vel1[2] = -speed; + vel2[0] = speed; + vel2[1] = speed; + vel2[2] = speed; + CL_ParticleEffect(EFFECT_TE_BLOOD, PRVM_G_FLOAT(OFS_PARM3), PRVM_G_VECTOR(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), vel1, vel2, NULL, 0); +} + +// #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) +static void VM_CL_te_explosionrgb (void) +{ + float *pos; + vec3_t pos2; + matrix4x4_t tempmatrix; + VM_SAFEPARMCOUNT(2, VM_CL_te_explosionrgb); + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleExplosion(pos2); + Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, PRVM_G_VECTOR(OFS_PARM1)[0], PRVM_G_VECTOR(OFS_PARM1)[1], PRVM_G_VECTOR(OFS_PARM1)[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); +} + +// #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) +static void VM_CL_te_particlecube (void) +{ + VM_SAFEPARMCOUNT(7, VM_CL_te_particlecube); + CL_ParticleCube(PRVM_G_VECTOR(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), PRVM_G_FLOAT(OFS_PARM5), PRVM_G_FLOAT(OFS_PARM6)); +} + +// #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) +static void VM_CL_te_particlerain (void) +{ + VM_SAFEPARMCOUNT(5, VM_CL_te_particlerain); + CL_ParticleRain(PRVM_G_VECTOR(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), 0); +} + +// #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) +static void VM_CL_te_particlesnow (void) +{ + VM_SAFEPARMCOUNT(5, VM_CL_te_particlesnow); + CL_ParticleRain(PRVM_G_VECTOR(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), 1); +} + +// #411 void(vector org, vector vel, float howmany) te_spark +static void VM_CL_te_spark (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(3, VM_CL_te_spark); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SPARK, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM1), NULL, 0); +} + +extern cvar_t cl_sound_ric_gunshot; +// #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) +static void VM_CL_te_gunshotquad (void) +{ + float *pos; + vec3_t pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_gunshotquad); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_GUNSHOTQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer >= 2) + { + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } + } +} + +// #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) +static void VM_CL_te_spikequad (void) +{ + float *pos; + vec3_t pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_spikequad); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SPIKEQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } +} + +// #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) +static void VM_CL_te_superspikequad (void) +{ + float *pos; + vec3_t pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_superspikequad); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKEQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } +} + +// #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) +static void VM_CL_te_explosionquad (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_explosionquad); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSIONQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); +} + +// #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) +static void VM_CL_te_smallflash (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_smallflash); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleEffect(EFFECT_TE_SMALLFLASH, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); +} + +// #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) +static void VM_CL_te_customflash (void) +{ + float *pos; + vec3_t pos2; + matrix4x4_t tempmatrix; + VM_SAFEPARMCOUNT(4, VM_CL_te_customflash); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); + CL_AllocLightFlash(NULL, &tempmatrix, PRVM_G_FLOAT(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM3)[0], PRVM_G_VECTOR(OFS_PARM3)[1], PRVM_G_VECTOR(OFS_PARM3)[2], PRVM_G_FLOAT(OFS_PARM1) / PRVM_G_FLOAT(OFS_PARM2), PRVM_G_FLOAT(OFS_PARM2), 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); +} + +// #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_gunshot (void) +{ + float *pos; + vec3_t pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_gunshot); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_GUNSHOT, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer == 1 || cl_sound_ric_gunshot.integer == 3) + { + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } + } +} + +// #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_spike (void) +{ + float *pos; + vec3_t pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_spike); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } +} + +// #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_superspike (void) +{ + float *pos; + vec3_t pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_superspike); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } +} + +// #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_explosion (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_explosion); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); +} + +// #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_tarexplosion (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_tarexplosion); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); +} + +// #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_wizspike (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_wizspike); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_wizhit, pos2, 1, 1); +} + +// #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_knightspike (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_knightspike); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_knighthit, pos2, 1, 1); +} + +// #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_lavasplash (void) +{ + VM_SAFEPARMCOUNT(1, VM_CL_te_lavasplash); + CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, PRVM_G_VECTOR(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM0), vec3_origin, vec3_origin, NULL, 0); +} + +// #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_teleport (void) +{ + VM_SAFEPARMCOUNT(1, VM_CL_te_teleport); + CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, PRVM_G_VECTOR(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM0), vec3_origin, vec3_origin, NULL, 0); +} + +// #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_explosion2 (void) +{ + float *pos; + vec3_t pos2, color; + matrix4x4_t tempmatrix; + int colorStart, colorLength; + unsigned char *tempcolor; + VM_SAFEPARMCOUNT(3, VM_CL_te_explosion2); + + pos = PRVM_G_VECTOR(OFS_PARM0); + colorStart = (int)PRVM_G_FLOAT(OFS_PARM1); + colorLength = (int)PRVM_G_FLOAT(OFS_PARM2); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleExplosion2(pos2, colorStart, colorLength); + tempcolor = palette_rgb[(rand()%colorLength) + colorStart]; + color[0] = tempcolor[0] * (2.0f / 255.0f); + color[1] = tempcolor[1] * (2.0f / 255.0f); + color[2] = tempcolor[2] * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); +} + + +// #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_lightning1 (void) +{ + VM_SAFEPARMCOUNT(3, VM_CL_te_lightning1); + CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt, true); +} + +// #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_lightning2 (void) +{ + VM_SAFEPARMCOUNT(3, VM_CL_te_lightning2); + CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt2, true); +} + +// #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_lightning3 (void) +{ + VM_SAFEPARMCOUNT(3, VM_CL_te_lightning3); + CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt3, false); +} + +// #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_beam (void) +{ + VM_SAFEPARMCOUNT(3, VM_CL_te_beam); + CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_beam, false); +} + +// #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) +static void VM_CL_te_plasmaburn (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_plasmaburn); + + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_PLASMABURN, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); +} + +// #457 void(vector org, vector velocity, float howmany) te_flamejet (DP_TE_FLAMEJET) +static void VM_CL_te_flamejet (void) +{ + float *pos; + vec3_t pos2; + VM_SAFEPARMCOUNT(3, VM_CL_te_flamejet); + if (PRVM_G_FLOAT(OFS_PARM2) < 1) + return; + pos = PRVM_G_VECTOR(OFS_PARM0); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_FLAMEJET, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM1), NULL, 0); +} + + +// #443 void(entity e, entity tagentity, string tagname) setattachment +void VM_CL_setattachment (void) +{ + prvm_edict_t *e; + prvm_edict_t *tagentity; + const char *tagname; + int modelindex; + int tagindex; + dp_model_t *model; + VM_SAFEPARMCOUNT(3, VM_CL_setattachment); + + e = PRVM_G_EDICT(OFS_PARM0); + tagentity = PRVM_G_EDICT(OFS_PARM1); + tagname = PRVM_G_STRING(OFS_PARM2); + + if (e == prog->edicts) + { + VM_Warning("setattachment: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning("setattachment: can not modify free entity\n"); + return; + } + + if (tagentity == NULL) + tagentity = prog->edicts; + + tagindex = 0; + if (tagentity != NULL && tagentity != prog->edicts && tagname && tagname[0]) + { + modelindex = (int)PRVM_clientedictfloat(tagentity, modelindex); + model = CL_GetModelByIndex(modelindex); + if (model) + { + tagindex = Mod_Alias_GetTagIndexForName(model, (int)PRVM_clientedictfloat(tagentity, skin), tagname); + if (tagindex == 0) + Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i (model \"%s\") but could not find it\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity), model->name); + } + else + Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i but it has no model\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity)); + } + + PRVM_clientedictedict(e, tag_entity) = PRVM_EDICT_TO_PROG(tagentity); + PRVM_clientedictfloat(e, tag_index) = tagindex; +} + +///////////////////////////////////////// +// DP_MD3_TAGINFO extension coded by VorteX + +int CL_GetTagIndex (prvm_edict_t *e, const char *tagname) +{ + dp_model_t *model = CL_GetModelFromEdict(e); + if (model) + return Mod_Alias_GetTagIndexForName(model, (int)PRVM_clientedictfloat(e, skin), tagname); + else + return -1; +} + +int CL_GetExtendedTagInfo (prvm_edict_t *e, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) +{ + int r; + dp_model_t *model; + + *tagname = NULL; + *parentindex = 0; + Matrix4x4_CreateIdentity(tag_localmatrix); + + if (tagindex >= 0 + && (model = CL_GetModelFromEdict(e)) + && model->animscenes) + { + r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)PRVM_clientedictfloat(e, skin), e->priv.server->frameblend, &e->priv.server->skeleton, tagindex - 1, parentindex, tagname, tag_localmatrix); + + if(!r) // success? + *parentindex += 1; + + return r; + } + + return 1; +} + +int CL_GetPitchSign(prvm_edict_t *ent) +{ + dp_model_t *model; + if ((model = CL_GetModelFromEdict(ent)) && model->type == mod_alias) + return -1; + return 1; +} + +void CL_GetEntityMatrix (prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix) +{ + float scale; + float pitchsign = 1; + + scale = PRVM_clientedictfloat(ent, scale); + if (!scale) + scale = 1.0f; + + if(viewmatrix) + *out = r_refdef.view.matrix; + else if ((int)PRVM_clientedictfloat(ent, renderflags) & RF_USEAXIS) + { + vec3_t forward; + vec3_t left; + vec3_t up; + vec3_t origin; + VectorScale(PRVM_clientglobalvector(v_forward), scale, forward); + VectorScale(PRVM_clientglobalvector(v_right), -scale, left); + VectorScale(PRVM_clientglobalvector(v_up), scale, up); + VectorCopy(PRVM_clientedictvector(ent, origin), origin); + Matrix4x4_FromVectors(out, forward, left, up, origin); + } + else + { + pitchsign = CL_GetPitchSign(ent); + Matrix4x4_CreateFromQuakeEntity(out, PRVM_clientedictvector(ent, origin)[0], PRVM_clientedictvector(ent, origin)[1], PRVM_clientedictvector(ent, origin)[2], pitchsign * PRVM_clientedictvector(ent, angles)[0], PRVM_clientedictvector(ent, angles)[1], PRVM_clientedictvector(ent, angles)[2], scale); + } +} + +int CL_GetEntityLocalTagMatrix(prvm_edict_t *ent, int tagindex, matrix4x4_t *out) +{ + dp_model_t *model; + if (tagindex >= 0 + && (model = CL_GetModelFromEdict(ent)) + && model->animscenes) + { + VM_GenerateFrameGroupBlend(ent->priv.server->framegroupblend, ent); + VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(ent, model, ent->priv.server->frameblend); + return Mod_Alias_GetTagMatrix(model, ent->priv.server->frameblend, &ent->priv.server->skeleton, tagindex, out); + } + *out = identitymatrix; + return 0; +} + +// Warnings/errors code: +// 0 - normal (everything all-right) +// 1 - world entity +// 2 - free entity +// 3 - null or non-precached model +// 4 - no tags with requested index +// 5 - runaway loop at attachment chain +extern cvar_t cl_bob; +extern cvar_t cl_bobcycle; +extern cvar_t cl_bobup; +int CL_GetTagMatrix (matrix4x4_t *out, prvm_edict_t *ent, int tagindex) +{ + int ret; + int attachloop; + matrix4x4_t entitymatrix, tagmatrix, attachmatrix; + dp_model_t *model; + + *out = identitymatrix; // warnings and errors return identical matrix + + if (ent == prog->edicts) + return 1; + if (ent->priv.server->free) + return 2; + + model = CL_GetModelFromEdict(ent); + if(!model) + return 3; + + tagmatrix = identitymatrix; + attachloop = 0; + for(;;) + { + if(attachloop >= 256) + return 5; + // apply transformation by child's tagindex on parent entity and then + // by parent entity itself + ret = CL_GetEntityLocalTagMatrix(ent, tagindex - 1, &attachmatrix); + if(ret && attachloop == 0) + return ret; + CL_GetEntityMatrix(ent, &entitymatrix, false); + Matrix4x4_Concat(&tagmatrix, &attachmatrix, out); + Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); + // next iteration we process the parent entity + if (PRVM_clientedictedict(ent, tag_entity)) + { + tagindex = (int)PRVM_clientedictfloat(ent, tag_index); + ent = PRVM_EDICT_NUM(PRVM_clientedictedict(ent, tag_entity)); + } + else + break; + attachloop++; + } + + // RENDER_VIEWMODEL magic + if ((int)PRVM_clientedictfloat(ent, renderflags) & RF_VIEWMODEL) + { + Matrix4x4_Copy(&tagmatrix, out); + + CL_GetEntityMatrix(prog->edicts, &entitymatrix, true); + Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); + + /* + // Cl_bob, ported from rendering code + if (PRVM_clientedictfloat(ent, health) > 0 && cl_bob.value && cl_bobcycle.value) + { + double bob, cycle; + // LordHavoc: this code is *weird*, but not replacable (I think it + // should be done in QC on the server, but oh well, quake is quake) + // LordHavoc: figured out bobup: the time at which the sin is at 180 + // degrees (which allows lengthening or squishing the peak or valley) + cycle = cl.time/cl_bobcycle.value; + cycle -= (int)cycle; + if (cycle < cl_bobup.value) + cycle = sin(M_PI * cycle / cl_bobup.value); + else + cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); + // bob is proportional to velocity in the xy plane + // (don't count Z, or jumping messes it up) + bob = sqrt(PRVM_clientedictvector(ent, velocity)[0]*PRVM_clientedictvector(ent, velocity)[0] + PRVM_clientedictvector(ent, velocity)[1]*PRVM_clientedictvector(ent, velocity)[1])*cl_bob.value; + bob = bob*0.3 + bob*0.7*cycle; + Matrix4x4_AdjustOrigin(out, 0, 0, bound(-7, bob, 4)); + } + */ + } + return 0; +} + +// #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) +void VM_CL_gettagindex (void) +{ + prvm_edict_t *ent; + const char *tag_name; + int tag_index; + + VM_SAFEPARMCOUNT(2, VM_CL_gettagindex); + + ent = PRVM_G_EDICT(OFS_PARM0); + tag_name = PRVM_G_STRING(OFS_PARM1); + if (ent == prog->edicts) + { + VM_Warning("VM_CL_gettagindex(entity #%i): can't affect world entity\n", PRVM_NUM_FOR_EDICT(ent)); + return; + } + if (ent->priv.server->free) + { + VM_Warning("VM_CL_gettagindex(entity #%i): can't affect free entity\n", PRVM_NUM_FOR_EDICT(ent)); + return; + } + + tag_index = 0; + if (!CL_GetModelFromEdict(ent)) + Con_DPrintf("VM_CL_gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent)); + else + { + tag_index = CL_GetTagIndex(ent, tag_name); + if (tag_index == 0) + Con_DPrintf("VM_CL_gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name); + } + PRVM_G_FLOAT(OFS_RETURN) = tag_index; +} + +// #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) +void VM_CL_gettaginfo (void) +{ + prvm_edict_t *e; + int tagindex; + matrix4x4_t tag_matrix; + matrix4x4_t tag_localmatrix; + int parentindex; + const char *tagname; + int returncode; + vec3_t fo, le, up, trans; + const dp_model_t *model; + + VM_SAFEPARMCOUNT(2, VM_CL_gettaginfo); + + e = PRVM_G_EDICT(OFS_PARM0); + tagindex = (int)PRVM_G_FLOAT(OFS_PARM1); + returncode = CL_GetTagMatrix(&tag_matrix, e, tagindex); + Matrix4x4_ToVectors(&tag_matrix, PRVM_clientglobalvector(v_forward), le, PRVM_clientglobalvector(v_up), PRVM_G_VECTOR(OFS_RETURN)); + VectorScale(le, -1, PRVM_clientglobalvector(v_right)); + model = CL_GetModelFromEdict(e); + VM_GenerateFrameGroupBlend(e->priv.server->framegroupblend, e); + VM_FrameBlendFromFrameGroupBlend(e->priv.server->frameblend, e->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(e, model, e->priv.server->frameblend); + CL_GetExtendedTagInfo(e, tagindex, &parentindex, &tagname, &tag_localmatrix); + Matrix4x4_ToVectors(&tag_localmatrix, fo, le, up, trans); + + PRVM_clientglobalfloat(gettaginfo_parent) = parentindex; + PRVM_clientglobalstring(gettaginfo_name) = tagname ? PRVM_SetTempString(tagname) : 0; + VectorCopy(trans, PRVM_clientglobalvector(gettaginfo_offset)); + VectorCopy(fo, PRVM_clientglobalvector(gettaginfo_forward)); + VectorScale(le, -1, PRVM_clientglobalvector(gettaginfo_right)); + VectorCopy(up, PRVM_clientglobalvector(gettaginfo_up)); + + switch(returncode) + { + case 1: + VM_Warning("gettagindex: can't affect world entity\n"); + break; + case 2: + VM_Warning("gettagindex: can't affect free entity\n"); + break; + case 3: + Con_DPrintf("CL_GetTagMatrix(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(e)); + break; + case 4: + Con_DPrintf("CL_GetTagMatrix(entity #%i): model has no tag with requested index %i\n", PRVM_NUM_FOR_EDICT(e), tagindex); + break; + case 5: + Con_DPrintf("CL_GetTagMatrix(entity #%i): runaway loop at attachment chain\n", PRVM_NUM_FOR_EDICT(e)); + break; + } +} + +//============================================================================ + +//==================== +// DP_CSQC_SPAWNPARTICLE +// a QC hook to engine's CL_NewParticle +//==================== + +// particle theme struct +typedef struct vmparticletheme_s +{ + unsigned short typeindex; + qboolean initialized; + pblend_t blendmode; + porientation_t orientation; + int color1; + int color2; + int tex; + float size; + float sizeincrease; + float alpha; + float alphafade; + float gravity; + float bounce; + float airfriction; + float liquidfriction; + float originjitter; + float velocityjitter; + qboolean qualityreduction; + float lifetime; + float stretch; + int staincolor1; + int staincolor2; + int staintex; + float stainalpha; + float stainsize; + float delayspawn; + float delaycollision; + float angle; + float spin; +}vmparticletheme_t; + +// particle spawner +typedef struct vmparticlespawner_s +{ + mempool_t *pool; + qboolean initialized; + qboolean verified; + vmparticletheme_t *themes; + int max_themes; + // global addresses + float *particle_type; + float *particle_blendmode; + float *particle_orientation; + float *particle_color1; + float *particle_color2; + float *particle_tex; + float *particle_size; + float *particle_sizeincrease; + float *particle_alpha; + float *particle_alphafade; + float *particle_time; + float *particle_gravity; + float *particle_bounce; + float *particle_airfriction; + float *particle_liquidfriction; + float *particle_originjitter; + float *particle_velocityjitter; + float *particle_qualityreduction; + float *particle_stretch; + float *particle_staincolor1; + float *particle_staincolor2; + float *particle_stainalpha; + float *particle_stainsize; + float *particle_staintex; + float *particle_delayspawn; + float *particle_delaycollision; + float *particle_angle; + float *particle_spin; +}vmparticlespawner_t; + +vmparticlespawner_t vmpartspawner; + +// TODO: automatic max_themes grow +static void VM_InitParticleSpawner (int maxthemes) +{ + // bound max themes to not be an insane value + if (maxthemes < 4) + maxthemes = 4; + if (maxthemes > 2048) + maxthemes = 2048; + // allocate and set up structure + if (vmpartspawner.initialized) // reallocate + { + Mem_FreePool(&vmpartspawner.pool); + memset(&vmpartspawner, 0, sizeof(vmparticlespawner_t)); + } + vmpartspawner.pool = Mem_AllocPool("VMPARTICLESPAWNER", 0, NULL); + vmpartspawner.themes = (vmparticletheme_t *)Mem_Alloc(vmpartspawner.pool, sizeof(vmparticletheme_t)*maxthemes); + vmpartspawner.max_themes = maxthemes; + vmpartspawner.initialized = true; + vmpartspawner.verified = true; + // get field addresses for fast querying (we can do 1000 calls of spawnparticle in a frame) + vmpartspawner.particle_type = &PRVM_clientglobalfloat(particle_type); + vmpartspawner.particle_blendmode = &PRVM_clientglobalfloat(particle_blendmode); + vmpartspawner.particle_orientation = &PRVM_clientglobalfloat(particle_orientation); + vmpartspawner.particle_color1 = PRVM_clientglobalvector(particle_color1); + vmpartspawner.particle_color2 = PRVM_clientglobalvector(particle_color2); + vmpartspawner.particle_tex = &PRVM_clientglobalfloat(particle_tex); + vmpartspawner.particle_size = &PRVM_clientglobalfloat(particle_size); + vmpartspawner.particle_sizeincrease = &PRVM_clientglobalfloat(particle_sizeincrease); + vmpartspawner.particle_alpha = &PRVM_clientglobalfloat(particle_alpha); + vmpartspawner.particle_alphafade = &PRVM_clientglobalfloat(particle_alphafade); + vmpartspawner.particle_time = &PRVM_clientglobalfloat(particle_time); + vmpartspawner.particle_gravity = &PRVM_clientglobalfloat(particle_gravity); + vmpartspawner.particle_bounce = &PRVM_clientglobalfloat(particle_bounce); + vmpartspawner.particle_airfriction = &PRVM_clientglobalfloat(particle_airfriction); + vmpartspawner.particle_liquidfriction = &PRVM_clientglobalfloat(particle_liquidfriction); + vmpartspawner.particle_originjitter = &PRVM_clientglobalfloat(particle_originjitter); + vmpartspawner.particle_velocityjitter = &PRVM_clientglobalfloat(particle_velocityjitter); + vmpartspawner.particle_qualityreduction = &PRVM_clientglobalfloat(particle_qualityreduction); + vmpartspawner.particle_stretch = &PRVM_clientglobalfloat(particle_stretch); + vmpartspawner.particle_staincolor1 = PRVM_clientglobalvector(particle_staincolor1); + vmpartspawner.particle_staincolor2 = PRVM_clientglobalvector(particle_staincolor2); + vmpartspawner.particle_stainalpha = &PRVM_clientglobalfloat(particle_stainalpha); + vmpartspawner.particle_stainsize = &PRVM_clientglobalfloat(particle_stainsize); + vmpartspawner.particle_staintex = &PRVM_clientglobalfloat(particle_staintex); + vmpartspawner.particle_staintex = &PRVM_clientglobalfloat(particle_staintex); + vmpartspawner.particle_delayspawn = &PRVM_clientglobalfloat(particle_delayspawn); + vmpartspawner.particle_delaycollision = &PRVM_clientglobalfloat(particle_delaycollision); + vmpartspawner.particle_angle = &PRVM_clientglobalfloat(particle_angle); + vmpartspawner.particle_spin = &PRVM_clientglobalfloat(particle_spin); + #undef getglobal + #undef getglobalvector +} + +// reset particle theme to default values +static void VM_ResetParticleTheme (vmparticletheme_t *theme) +{ + theme->initialized = true; + theme->typeindex = pt_static; + theme->blendmode = PBLEND_ADD; + theme->orientation = PARTICLE_BILLBOARD; + theme->color1 = 0x808080; + theme->color2 = 0xFFFFFF; + theme->tex = 63; + theme->size = 2; + theme->sizeincrease = 0; + theme->alpha = 256; + theme->alphafade = 512; + theme->gravity = 0.0f; + theme->bounce = 0.0f; + theme->airfriction = 1.0f; + theme->liquidfriction = 4.0f; + theme->originjitter = 0.0f; + theme->velocityjitter = 0.0f; + theme->qualityreduction = false; + theme->lifetime = 4; + theme->stretch = 1; + theme->staincolor1 = -1; + theme->staincolor2 = -1; + theme->staintex = -1; + theme->delayspawn = 0.0f; + theme->delaycollision = 0.0f; + theme->angle = 0.0f; + theme->spin = 0.0f; +} + +// particle theme -> QC globals +void VM_CL_ParticleThemeToGlobals(vmparticletheme_t *theme) +{ + *vmpartspawner.particle_type = theme->typeindex; + *vmpartspawner.particle_blendmode = theme->blendmode; + *vmpartspawner.particle_orientation = theme->orientation; + vmpartspawner.particle_color1[0] = (theme->color1 >> 16) & 0xFF; // VorteX: int only can store 0-255, not 0-256 which means 0 - 0,99609375... + vmpartspawner.particle_color1[1] = (theme->color1 >> 8) & 0xFF; + vmpartspawner.particle_color1[2] = (theme->color1 >> 0) & 0xFF; + vmpartspawner.particle_color2[0] = (theme->color2 >> 16) & 0xFF; + vmpartspawner.particle_color2[1] = (theme->color2 >> 8) & 0xFF; + vmpartspawner.particle_color2[2] = (theme->color2 >> 0) & 0xFF; + *vmpartspawner.particle_tex = (float)theme->tex; + *vmpartspawner.particle_size = theme->size; + *vmpartspawner.particle_sizeincrease = theme->sizeincrease; + *vmpartspawner.particle_alpha = theme->alpha/256; + *vmpartspawner.particle_alphafade = theme->alphafade/256; + *vmpartspawner.particle_time = theme->lifetime; + *vmpartspawner.particle_gravity = theme->gravity; + *vmpartspawner.particle_bounce = theme->bounce; + *vmpartspawner.particle_airfriction = theme->airfriction; + *vmpartspawner.particle_liquidfriction = theme->liquidfriction; + *vmpartspawner.particle_originjitter = theme->originjitter; + *vmpartspawner.particle_velocityjitter = theme->velocityjitter; + *vmpartspawner.particle_qualityreduction = theme->qualityreduction; + *vmpartspawner.particle_stretch = theme->stretch; + vmpartspawner.particle_staincolor1[0] = ((int)theme->staincolor1 >> 16) & 0xFF; + vmpartspawner.particle_staincolor1[1] = ((int)theme->staincolor1 >> 8) & 0xFF; + vmpartspawner.particle_staincolor1[2] = ((int)theme->staincolor1 >> 0) & 0xFF; + vmpartspawner.particle_staincolor2[0] = ((int)theme->staincolor2 >> 16) & 0xFF; + vmpartspawner.particle_staincolor2[1] = ((int)theme->staincolor2 >> 8) & 0xFF; + vmpartspawner.particle_staincolor2[2] = ((int)theme->staincolor2 >> 0) & 0xFF; + *vmpartspawner.particle_staintex = (float)theme->staintex; + *vmpartspawner.particle_stainalpha = (float)theme->stainalpha/256; + *vmpartspawner.particle_stainsize = (float)theme->stainsize; + *vmpartspawner.particle_delayspawn = theme->delayspawn; + *vmpartspawner.particle_delaycollision = theme->delaycollision; + *vmpartspawner.particle_angle = theme->angle; + *vmpartspawner.particle_spin = theme->spin; +} + +// QC globals -> particle theme +void VM_CL_ParticleThemeFromGlobals(vmparticletheme_t *theme) +{ + theme->typeindex = (unsigned short)*vmpartspawner.particle_type; + theme->blendmode = (pblend_t)(int)*vmpartspawner.particle_blendmode; + theme->orientation = (porientation_t)(int)*vmpartspawner.particle_orientation; + theme->color1 = ((int)vmpartspawner.particle_color1[0] << 16) + ((int)vmpartspawner.particle_color1[1] << 8) + ((int)vmpartspawner.particle_color1[2]); + theme->color2 = ((int)vmpartspawner.particle_color2[0] << 16) + ((int)vmpartspawner.particle_color2[1] << 8) + ((int)vmpartspawner.particle_color2[2]); + theme->tex = (int)*vmpartspawner.particle_tex; + theme->size = *vmpartspawner.particle_size; + theme->sizeincrease = *vmpartspawner.particle_sizeincrease; + theme->alpha = *vmpartspawner.particle_alpha*256; + theme->alphafade = *vmpartspawner.particle_alphafade*256; + theme->lifetime = *vmpartspawner.particle_time; + theme->gravity = *vmpartspawner.particle_gravity; + theme->bounce = *vmpartspawner.particle_bounce; + theme->airfriction = *vmpartspawner.particle_airfriction; + theme->liquidfriction = *vmpartspawner.particle_liquidfriction; + theme->originjitter = *vmpartspawner.particle_originjitter; + theme->velocityjitter = *vmpartspawner.particle_velocityjitter; + theme->qualityreduction = (*vmpartspawner.particle_qualityreduction) ? true : false; + theme->stretch = *vmpartspawner.particle_stretch; + theme->staincolor1 = ((int)vmpartspawner.particle_staincolor1[0])*65536 + (int)(vmpartspawner.particle_staincolor1[1])*256 + (int)(vmpartspawner.particle_staincolor1[2]); + theme->staincolor2 = (int)(vmpartspawner.particle_staincolor2[0])*65536 + (int)(vmpartspawner.particle_staincolor2[1])*256 + (int)(vmpartspawner.particle_staincolor2[2]); + theme->staintex =(int)*vmpartspawner.particle_staintex; + theme->stainalpha = *vmpartspawner.particle_stainalpha*256; + theme->stainsize = *vmpartspawner.particle_stainsize; + theme->delayspawn = *vmpartspawner.particle_delayspawn; + theme->delaycollision = *vmpartspawner.particle_delaycollision; + theme->angle = *vmpartspawner.particle_angle; + theme->spin = *vmpartspawner.particle_spin; +} + +// init particle spawner interface +// # float(float max_themes) initparticlespawner +void VM_CL_InitParticleSpawner (void) +{ + VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_InitParticleSpawner); + VM_InitParticleSpawner((int)PRVM_G_FLOAT(OFS_PARM0)); + vmpartspawner.themes[0].initialized = true; + VM_ResetParticleTheme(&vmpartspawner.themes[0]); + PRVM_G_FLOAT(OFS_RETURN) = (vmpartspawner.verified == true) ? 1 : 0; +} + +// void() resetparticle +void VM_CL_ResetParticle (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ResetParticle); + if (vmpartspawner.verified == false) + { + VM_Warning("VM_CL_ResetParticle: particle spawner not initialized\n"); + return; + } + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0]); +} + +// void(float themenum) particletheme +void VM_CL_ParticleTheme (void) +{ + int themenum; + + VM_SAFEPARMCOUNT(1, VM_CL_ParticleTheme); + if (vmpartspawner.verified == false) + { + VM_Warning("VM_CL_ParticleTheme: particle spawner not initialized\n"); + return; + } + themenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (themenum < 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning("VM_CL_ParticleTheme: bad theme number %i\n", themenum); + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0]); + return; + } + if (vmpartspawner.themes[themenum].initialized == false) + { + VM_Warning("VM_CL_ParticleTheme: theme #%i not exists\n", themenum); + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0]); + return; + } + // load particle theme into globals + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[themenum]); +} + +// float() saveparticletheme +// void(float themenum) updateparticletheme +void VM_CL_ParticleThemeSave (void) +{ + int themenum; + + VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_ParticleThemeSave); + if (vmpartspawner.verified == false) + { + VM_Warning("VM_CL_ParticleThemeSave: particle spawner not initialized\n"); + return; + } + // allocate new theme, save it and return + if (prog->argc < 1) + { + for (themenum = 0; themenum < vmpartspawner.max_themes; themenum++) + if (vmpartspawner.themes[themenum].initialized == false) + break; + if (themenum >= vmpartspawner.max_themes) + { + if (vmpartspawner.max_themes == 2048) + VM_Warning("VM_CL_ParticleThemeSave: no free theme slots\n"); + else + VM_Warning("VM_CL_ParticleThemeSave: no free theme slots, try initparticlespawner() with highter max_themes\n"); + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; + } + vmpartspawner.themes[themenum].initialized = true; + VM_CL_ParticleThemeFromGlobals(&vmpartspawner.themes[themenum]); + PRVM_G_FLOAT(OFS_RETURN) = themenum; + return; + } + // update existing theme + themenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (themenum < 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning("VM_CL_ParticleThemeSave: bad theme number %i\n", themenum); + return; + } + vmpartspawner.themes[themenum].initialized = true; + VM_CL_ParticleThemeFromGlobals(&vmpartspawner.themes[themenum]); +} + +// void(float themenum) freeparticletheme +void VM_CL_ParticleThemeFree (void) +{ + int themenum; + + VM_SAFEPARMCOUNT(1, VM_CL_ParticleThemeFree); + if (vmpartspawner.verified == false) + { + VM_Warning("VM_CL_ParticleThemeFree: particle spawner not initialized\n"); + return; + } + themenum = (int)PRVM_G_FLOAT(OFS_PARM0); + // check parms + if (themenum <= 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning("VM_CL_ParticleThemeFree: bad theme number %i\n", themenum); + return; + } + if (vmpartspawner.themes[themenum].initialized == false) + { + VM_Warning("VM_CL_ParticleThemeFree: theme #%i already freed\n", themenum); + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0]); + return; + } + // free theme + VM_ResetParticleTheme(&vmpartspawner.themes[themenum]); + vmpartspawner.themes[themenum].initialized = false; +} + +// float(vector org, vector dir, [float theme]) particle +// returns 0 if failed, 1 if succesful +void VM_CL_SpawnParticle (void) +{ + float *org, *dir; + vmparticletheme_t *theme; + particle_t *part; + int themenum; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_SpawnParticle2); + if (vmpartspawner.verified == false) + { + VM_Warning("VM_CL_SpawnParticle: particle spawner not initialized\n"); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + org = PRVM_G_VECTOR(OFS_PARM0); + dir = PRVM_G_VECTOR(OFS_PARM1); + + if (prog->argc < 3) // global-set particle + { + part = CL_NewParticle(org, (unsigned short)*vmpartspawner.particle_type, ((int)(vmpartspawner.particle_color1[0]) << 16) + ((int)(vmpartspawner.particle_color1[1]) << 8) + ((int)(vmpartspawner.particle_color1[2])), ((int)vmpartspawner.particle_color2[0] << 16) + ((int)vmpartspawner.particle_color2[1] << 8) + ((int)vmpartspawner.particle_color2[2]), (int)*vmpartspawner.particle_tex, *vmpartspawner.particle_size, *vmpartspawner.particle_sizeincrease, *vmpartspawner.particle_alpha*256, *vmpartspawner.particle_alphafade*256, *vmpartspawner.particle_gravity, *vmpartspawner.particle_bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], *vmpartspawner.particle_airfriction, *vmpartspawner.particle_liquidfriction, *vmpartspawner.particle_originjitter, *vmpartspawner.particle_velocityjitter, (*vmpartspawner.particle_qualityreduction) ? true : false, *vmpartspawner.particle_time, *vmpartspawner.particle_stretch, (pblend_t)(int)*vmpartspawner.particle_blendmode, (porientation_t)(int)*vmpartspawner.particle_orientation, (int)(vmpartspawner.particle_staincolor1[0])*65536 + (int)(vmpartspawner.particle_staincolor1[1])*256 + (int)(vmpartspawner.particle_staincolor1[2]), (int)(vmpartspawner.particle_staincolor2[0])*65536 + (int)(vmpartspawner.particle_staincolor2[1])*256 + (int)(vmpartspawner.particle_staincolor2[2]), (int)*vmpartspawner.particle_staintex, *vmpartspawner.particle_stainalpha*256, *vmpartspawner.particle_stainsize, *vmpartspawner.particle_angle, *vmpartspawner.particle_spin, NULL); + if (!part) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + if (*vmpartspawner.particle_delayspawn) + part->delayedspawn = cl.time + *vmpartspawner.particle_delayspawn; + //if (*vmpartspawner.particle_delaycollision) + // part->delayedcollisions = cl.time + *vmpartspawner.particle_delaycollision; + } + else // quick themed particle + { + themenum = (int)PRVM_G_FLOAT(OFS_PARM2); + if (themenum <= 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning("VM_CL_SpawnParticle: bad theme number %i\n", themenum); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + theme = &vmpartspawner.themes[themenum]; + part = CL_NewParticle(org, theme->typeindex, theme->color1, theme->color2, theme->tex, theme->size, theme->sizeincrease, theme->alpha, theme->alphafade, theme->gravity, theme->bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], theme->airfriction, theme->liquidfriction, theme->originjitter, theme->velocityjitter, theme->qualityreduction, theme->lifetime, theme->stretch, theme->blendmode, theme->orientation, theme->staincolor1, theme->staincolor2, theme->staintex, theme->stainalpha, theme->stainsize, theme->angle, theme->spin, NULL); + if (!part) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + if (theme->delayspawn) + part->delayedspawn = cl.time + theme->delayspawn; + //if (theme->delaycollision) + // part->delayedcollisions = cl.time + theme->delaycollision; + } + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +// float(vector org, vector dir, float spawndelay, float collisiondelay, [float theme]) delayedparticle +// returns 0 if failed, 1 if success +void VM_CL_SpawnParticleDelayed (void) +{ + float *org, *dir; + vmparticletheme_t *theme; + particle_t *part; + int themenum; + + VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_SpawnParticle2); + if (vmpartspawner.verified == false) + { + VM_Warning("VM_CL_SpawnParticle: particle spawner not initialized\n"); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + org = PRVM_G_VECTOR(OFS_PARM0); + dir = PRVM_G_VECTOR(OFS_PARM1); + if (prog->argc < 5) // global-set particle + part = CL_NewParticle(org, (unsigned short)*vmpartspawner.particle_type, ((int)vmpartspawner.particle_color1[0] << 16) + ((int)vmpartspawner.particle_color1[1] << 8) + ((int)vmpartspawner.particle_color1[2]), ((int)vmpartspawner.particle_color2[0] << 16) + ((int)vmpartspawner.particle_color2[1] << 8) + ((int)vmpartspawner.particle_color2[2]), (int)*vmpartspawner.particle_tex, *vmpartspawner.particle_size, *vmpartspawner.particle_sizeincrease, *vmpartspawner.particle_alpha*256, *vmpartspawner.particle_alphafade*256, *vmpartspawner.particle_gravity, *vmpartspawner.particle_bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], *vmpartspawner.particle_airfriction, *vmpartspawner.particle_liquidfriction, *vmpartspawner.particle_originjitter, *vmpartspawner.particle_velocityjitter, (*vmpartspawner.particle_qualityreduction) ? true : false, *vmpartspawner.particle_time, *vmpartspawner.particle_stretch, (pblend_t)(int)*vmpartspawner.particle_blendmode, (porientation_t)(int)*vmpartspawner.particle_orientation, ((int)vmpartspawner.particle_staincolor1[0] << 16) + ((int)vmpartspawner.particle_staincolor1[1] << 8) + ((int)vmpartspawner.particle_staincolor1[2]), ((int)vmpartspawner.particle_staincolor2[0] << 16) + ((int)vmpartspawner.particle_staincolor2[1] << 8) + ((int)vmpartspawner.particle_staincolor2[2]), (int)*vmpartspawner.particle_staintex, *vmpartspawner.particle_stainalpha*256, *vmpartspawner.particle_stainsize, *vmpartspawner.particle_angle, *vmpartspawner.particle_spin, NULL); + else // themed particle + { + themenum = (int)PRVM_G_FLOAT(OFS_PARM4); + if (themenum <= 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning("VM_CL_SpawnParticle: bad theme number %i\n", themenum); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + theme = &vmpartspawner.themes[themenum]; + part = CL_NewParticle(org, theme->typeindex, theme->color1, theme->color2, theme->tex, theme->size, theme->sizeincrease, theme->alpha, theme->alphafade, theme->gravity, theme->bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], theme->airfriction, theme->liquidfriction, theme->originjitter, theme->velocityjitter, theme->qualityreduction, theme->lifetime, theme->stretch, theme->blendmode, theme->orientation, theme->staincolor1, theme->staincolor2, theme->staintex, theme->stainalpha, theme->stainsize, theme->angle, theme->spin, NULL); + } + if (!part) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + part->delayedspawn = cl.time + PRVM_G_FLOAT(OFS_PARM2); + //part->delayedcollisions = cl.time + PRVM_G_FLOAT(OFS_PARM3); + PRVM_G_FLOAT(OFS_RETURN) = 0; +} + +//==================== +//CSQC engine entities query +//==================== + +// float(float entitynum, float whatfld) getentity; +// vector(float entitynum, float whatfld) getentityvec; +// querying engine-drawn entity +// VorteX: currently it's only tested with whatfld = 1..7 +void VM_CL_GetEntity (void) +{ + int entnum, fieldnum; + float org[3], v1[3], v2[3]; + VM_SAFEPARMCOUNT(2, VM_CL_GetEntityVec); + + entnum = PRVM_G_FLOAT(OFS_PARM0); + if (entnum < 0 || entnum >= cl.num_entities) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + fieldnum = PRVM_G_FLOAT(OFS_PARM1); + switch(fieldnum) + { + case 0: // active state + PRVM_G_FLOAT(OFS_RETURN) = cl.entities_active[entnum]; + break; + case 1: // origin + Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 2: // forward + Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, PRVM_G_VECTOR(OFS_RETURN), v1, v2, org); + break; + case 3: // right + Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, v1, PRVM_G_VECTOR(OFS_RETURN), v2, org); + break; + case 4: // up + Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, v1, v2, PRVM_G_VECTOR(OFS_RETURN), org); + break; + case 5: // scale + PRVM_G_FLOAT(OFS_RETURN) = Matrix4x4_ScaleFromMatrix(&cl.entities[entnum].render.matrix); + break; + case 6: // origin + v_forward, v_right, v_up + Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, PRVM_clientglobalvector(v_forward), PRVM_clientglobalvector(v_right), PRVM_clientglobalvector(v_up), PRVM_G_VECTOR(OFS_RETURN)); + break; + case 7: // alpha + PRVM_G_FLOAT(OFS_RETURN) = cl.entities[entnum].render.alpha; + break; + case 8: // colormor + VectorCopy(cl.entities[entnum].render.colormod, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 9: // pants colormod + VectorCopy(cl.entities[entnum].render.colormap_pantscolor, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 10: // shirt colormod + VectorCopy(cl.entities[entnum].render.colormap_shirtcolor, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 11: // skinnum + PRVM_G_FLOAT(OFS_RETURN) = cl.entities[entnum].render.skinnum; + break; + case 12: // mins + VectorCopy(cl.entities[entnum].render.mins, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 13: // maxs + VectorCopy(cl.entities[entnum].render.maxs, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 14: // absmin + Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, org); + VectorAdd(cl.entities[entnum].render.mins, org, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 15: // absmax + Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, org); + VectorAdd(cl.entities[entnum].render.maxs, org, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 16: // light + VectorMA(cl.entities[entnum].render.modellight_ambient, 0.5, cl.entities[entnum].render.modellight_diffuse, PRVM_G_VECTOR(OFS_RETURN)); + break; + default: + PRVM_G_FLOAT(OFS_RETURN) = 0; + break; + } +} + +//==================== +//QC POLYGON functions +//==================== + +#define VMPOLYGONS_MAXPOINTS 64 + +typedef struct vmpolygons_triangle_s +{ + rtexture_t *texture; + int drawflag; + qboolean hasalpha; + unsigned short elements[3]; +}vmpolygons_triangle_t; + +typedef struct vmpolygons_s +{ + mempool_t *pool; + qboolean initialized; + double progstarttime; + + int max_vertices; + int num_vertices; + float *data_vertex3f; + float *data_color4f; + float *data_texcoord2f; + + int max_triangles; + int num_triangles; + vmpolygons_triangle_t *data_triangles; + unsigned short *data_sortedelement3s; + + qboolean begin_active; + int begin_draw2d; + rtexture_t *begin_texture; + int begin_drawflag; + int begin_vertices; + float begin_vertex[VMPOLYGONS_MAXPOINTS][3]; + float begin_color[VMPOLYGONS_MAXPOINTS][4]; + float begin_texcoord[VMPOLYGONS_MAXPOINTS][2]; + qboolean begin_texture_hasalpha; +} vmpolygons_t; + +// FIXME: make VM_CL_R_Polygon functions use Debug_Polygon functions? +vmpolygons_t vmpolygons[PRVM_MAXPROGS]; + +//#304 void() renderscene (EXT_CSQC) +// moved that here to reset the polygons, +// resetting them earlier causes R_Mesh_Draw to be called with numvertices = 0 +// --blub +void VM_CL_R_RenderScene (void) +{ + double t = Sys_DoubleTime(); + vmpolygons_t* polys = vmpolygons + PRVM_GetProgNr(); + VM_SAFEPARMCOUNT(0, VM_CL_R_RenderScene); + + // we need to update any RENDER_VIEWMODEL entities at this point because + // csqc supplies its own view matrix + CL_UpdateViewEntities(); + // now draw stuff! + R_RenderView(); + + polys->num_vertices = polys->num_triangles = 0; + polys->progstarttime = prog->starttime; + + // callprofile fixing hack: do not include this time in what is counted for CSQC_UpdateView + prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= Sys_DoubleTime() - t; +} + +static void VM_ResizePolygons(vmpolygons_t *polys) +{ + float *oldvertex3f = polys->data_vertex3f; + float *oldcolor4f = polys->data_color4f; + float *oldtexcoord2f = polys->data_texcoord2f; + vmpolygons_triangle_t *oldtriangles = polys->data_triangles; + unsigned short *oldsortedelement3s = polys->data_sortedelement3s; + polys->max_vertices = min(polys->max_triangles*3, 65536); + polys->data_vertex3f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[3])); + polys->data_color4f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[4])); + polys->data_texcoord2f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[2])); + polys->data_triangles = (vmpolygons_triangle_t *)Mem_Alloc(polys->pool, polys->max_triangles*sizeof(vmpolygons_triangle_t)); + polys->data_sortedelement3s = (unsigned short *)Mem_Alloc(polys->pool, polys->max_triangles*sizeof(unsigned short[3])); + if (polys->num_vertices) + { + memcpy(polys->data_vertex3f, oldvertex3f, polys->num_vertices*sizeof(float[3])); + memcpy(polys->data_color4f, oldcolor4f, polys->num_vertices*sizeof(float[4])); + memcpy(polys->data_texcoord2f, oldtexcoord2f, polys->num_vertices*sizeof(float[2])); + } + if (polys->num_triangles) + { + memcpy(polys->data_triangles, oldtriangles, polys->num_triangles*sizeof(vmpolygons_triangle_t)); + memcpy(polys->data_sortedelement3s, oldsortedelement3s, polys->num_triangles*sizeof(unsigned short[3])); + } + if (oldvertex3f) + Mem_Free(oldvertex3f); + if (oldcolor4f) + Mem_Free(oldcolor4f); + if (oldtexcoord2f) + Mem_Free(oldtexcoord2f); + if (oldtriangles) + Mem_Free(oldtriangles); + if (oldsortedelement3s) + Mem_Free(oldsortedelement3s); +} + +static void VM_InitPolygons (vmpolygons_t* polys) +{ + memset(polys, 0, sizeof(*polys)); + polys->pool = Mem_AllocPool("VMPOLY", 0, NULL); + polys->max_triangles = 1024; + VM_ResizePolygons(polys); + polys->initialized = true; +} + +static void VM_DrawPolygonCallback (const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex; + vmpolygons_t* polys = vmpolygons + PRVM_GetProgNr(); + if(polys->progstarttime != prog->starttime) // from other progs? won't draw these (this can cause crashes!) + return; +// R_Mesh_ResetTextureState(); + R_EntityMatrix(&identitymatrix); + GL_CullFace(GL_NONE); + GL_DepthTest(true); // polys in 3D space shall always have depth test + GL_DepthRange(0, 1); + R_Mesh_PrepareVertices_Generic_Arrays(polys->num_vertices, polys->data_vertex3f, polys->data_color4f, polys->data_texcoord2f); + + for (surfacelistindex = 0;surfacelistindex < numsurfaces;) + { + int numtriangles = 0; + rtexture_t *tex = polys->data_triangles[surfacelist[surfacelistindex]].texture; + int drawflag = polys->data_triangles[surfacelist[surfacelistindex]].drawflag; + DrawQ_ProcessDrawFlag(drawflag, polys->data_triangles[surfacelist[surfacelistindex]].hasalpha); + R_SetupShader_Generic(tex, NULL, GL_MODULATE, 1, false); + numtriangles = 0; + for (;surfacelistindex < numsurfaces;surfacelistindex++) + { + if (polys->data_triangles[surfacelist[surfacelistindex]].texture != tex || polys->data_triangles[surfacelist[surfacelistindex]].drawflag != drawflag) + break; + VectorCopy(polys->data_triangles[surfacelist[surfacelistindex]].elements, polys->data_sortedelement3s + 3*numtriangles); + numtriangles++; + } + R_Mesh_Draw(0, polys->num_vertices, 0, numtriangles, NULL, NULL, 0, polys->data_sortedelement3s, NULL, 0); + } +} + +void VMPolygons_Store(vmpolygons_t *polys) +{ + qboolean hasalpha; + int i; + + // detect if we have alpha + hasalpha = polys->begin_texture_hasalpha; + for(i = 0; !hasalpha && (i < polys->begin_vertices); ++i) + if(polys->begin_color[i][3] < 1) + hasalpha = true; + + if (polys->begin_draw2d) + { + // draw the polygon as 2D immediately + drawqueuemesh_t mesh; + mesh.texture = polys->begin_texture; + mesh.num_vertices = polys->begin_vertices; + mesh.num_triangles = polys->begin_vertices-2; + mesh.data_element3i = polygonelement3i; + mesh.data_element3s = polygonelement3s; + mesh.data_vertex3f = polys->begin_vertex[0]; + mesh.data_color4f = polys->begin_color[0]; + mesh.data_texcoord2f = polys->begin_texcoord[0]; + DrawQ_Mesh(&mesh, polys->begin_drawflag, hasalpha); + } + else + { + // queue the polygon as 3D for sorted transparent rendering later + int i; + if (polys->max_triangles < polys->num_triangles + polys->begin_vertices-2) + { + while (polys->max_triangles < polys->num_triangles + polys->begin_vertices-2) + polys->max_triangles *= 2; + VM_ResizePolygons(polys); + } + if (polys->num_vertices + polys->begin_vertices <= polys->max_vertices) + { + // needle in a haystack! + // polys->num_vertices was used for copying where we actually want to copy begin_vertices + // that also caused it to not render the first polygon that is added + // --blub + memcpy(polys->data_vertex3f + polys->num_vertices * 3, polys->begin_vertex[0], polys->begin_vertices * sizeof(float[3])); + memcpy(polys->data_color4f + polys->num_vertices * 4, polys->begin_color[0], polys->begin_vertices * sizeof(float[4])); + memcpy(polys->data_texcoord2f + polys->num_vertices * 2, polys->begin_texcoord[0], polys->begin_vertices * sizeof(float[2])); + for (i = 0;i < polys->begin_vertices-2;i++) + { + polys->data_triangles[polys->num_triangles].texture = polys->begin_texture; + polys->data_triangles[polys->num_triangles].drawflag = polys->begin_drawflag; + polys->data_triangles[polys->num_triangles].elements[0] = polys->num_vertices; + polys->data_triangles[polys->num_triangles].elements[1] = polys->num_vertices + i+1; + polys->data_triangles[polys->num_triangles].elements[2] = polys->num_vertices + i+2; + polys->data_triangles[polys->num_triangles].hasalpha = hasalpha; + polys->num_triangles++; + } + polys->num_vertices += polys->begin_vertices; + } + } + polys->begin_active = false; +} + +// TODO: move this into the client code and clean-up everything else, too! [1/6/2008 Black] +// LordHavoc: agreed, this is a mess +void VM_CL_AddPolygonsToMeshQueue (void) +{ + int i; + vmpolygons_t* polys = vmpolygons + PRVM_GetProgNr(); + vec3_t center; + + // only add polygons of the currently active prog to the queue - if there is none, we're done + if( !prog ) + return; + + if (!polys->num_triangles) + return; + + for (i = 0;i < polys->num_triangles;i++) + { + VectorMAMAM(1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[0], 1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[1], 1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[2], center); + R_MeshQueue_AddTransparent(center, VM_DrawPolygonCallback, NULL, i, NULL); + } + + /*polys->num_triangles = 0; // now done after rendering the scene, + polys->num_vertices = 0; // otherwise it's not rendered at all and prints an error message --blub */ +} + +//void(string texturename, float flag[, float is2d]) R_BeginPolygon +void VM_CL_R_PolygonBegin (void) +{ + const char *picname; + skinframe_t *sf; + vmpolygons_t* polys = vmpolygons + PRVM_GetProgNr(); + int tf; + + // TODO instead of using skinframes here (which provides the benefit of + // better management of flags, and is more suited for 3D rendering), what + // about supporting Q3 shaders? + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_R_PolygonBegin); + + if (!polys->initialized) + VM_InitPolygons(polys); + if(polys->progstarttime != prog->starttime) + { + // from another progs? then reset the polys first (fixes crashes on map change, because that can make skinframe textures invalid) + polys->num_vertices = polys->num_triangles = 0; + polys->progstarttime = prog->starttime; + } + if (polys->begin_active) + { + VM_Warning("VM_CL_R_PolygonBegin: called twice without VM_CL_R_PolygonBegin after first\n"); + return; + } + picname = PRVM_G_STRING(OFS_PARM0); + + sf = NULL; + if(*picname) + { + tf = TEXF_ALPHA; + if((int)PRVM_G_FLOAT(OFS_PARM1) & DRAWFLAG_MIPMAP) + tf |= TEXF_MIPMAP; + + do + { + sf = R_SkinFrame_FindNextByName(sf, picname); + } + while(sf && sf->textureflags != tf); + + if(!sf || !sf->base) + sf = R_SkinFrame_LoadExternal(picname, tf, true); + + if(sf) + R_SkinFrame_MarkUsed(sf); + } + + polys->begin_texture = (sf && sf->base) ? sf->base : r_texture_white; + polys->begin_texture_hasalpha = (sf && sf->base) ? sf->hasalpha : false; + polys->begin_drawflag = (int)PRVM_G_FLOAT(OFS_PARM1) & DRAWFLAG_MASK; + polys->begin_vertices = 0; + polys->begin_active = true; + polys->begin_draw2d = (prog->argc >= 3 ? (int)PRVM_G_FLOAT(OFS_PARM2) : r_refdef.draw2dstage); +} + +//void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex +void VM_CL_R_PolygonVertex (void) +{ + vmpolygons_t* polys = vmpolygons + PRVM_GetProgNr(); + + VM_SAFEPARMCOUNT(4, VM_CL_R_PolygonVertex); + + if (!polys->begin_active) + { + VM_Warning("VM_CL_R_PolygonVertex: VM_CL_R_PolygonBegin wasn't called\n"); + return; + } + + if (polys->begin_vertices >= VMPOLYGONS_MAXPOINTS) + { + VM_Warning("VM_CL_R_PolygonVertex: may have %i vertices max\n", VMPOLYGONS_MAXPOINTS); + return; + } + + polys->begin_vertex[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM0)[0]; + polys->begin_vertex[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM0)[1]; + polys->begin_vertex[polys->begin_vertices][2] = PRVM_G_VECTOR(OFS_PARM0)[2]; + polys->begin_texcoord[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM1)[0]; + polys->begin_texcoord[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM1)[1]; + polys->begin_color[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM2)[0]; + polys->begin_color[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM2)[1]; + polys->begin_color[polys->begin_vertices][2] = PRVM_G_VECTOR(OFS_PARM2)[2]; + polys->begin_color[polys->begin_vertices][3] = PRVM_G_FLOAT(OFS_PARM3); + polys->begin_vertices++; +} + +//void() R_EndPolygon +void VM_CL_R_PolygonEnd (void) +{ + vmpolygons_t* polys = vmpolygons + PRVM_GetProgNr(); + + VM_SAFEPARMCOUNT(0, VM_CL_R_PolygonEnd); + if (!polys->begin_active) + { + VM_Warning("VM_CL_R_PolygonEnd: VM_CL_R_PolygonBegin wasn't called\n"); + return; + } + polys->begin_active = false; + if (polys->begin_vertices >= 3) + VMPolygons_Store(polys); + else + VM_Warning("VM_CL_R_PolygonEnd: %i vertices isn't a good choice\n", polys->begin_vertices); +} + +static vmpolygons_t debugPolys; + +void Debug_PolygonBegin(const char *picname, int drawflag) +{ + if(!debugPolys.initialized) + VM_InitPolygons(&debugPolys); + if(debugPolys.begin_active) + { + Con_Printf("Debug_PolygonBegin: called twice without Debug_PolygonEnd after first\n"); + return; + } + debugPolys.begin_texture = picname[0] ? Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT)->tex : r_texture_white; + debugPolys.begin_drawflag = drawflag; + debugPolys.begin_vertices = 0; + debugPolys.begin_active = true; +} + +void Debug_PolygonVertex(float x, float y, float z, float s, float t, float r, float g, float b, float a) +{ + if(!debugPolys.begin_active) + { + Con_Printf("Debug_PolygonVertex: Debug_PolygonBegin wasn't called\n"); + return; + } + + if(debugPolys.begin_vertices > VMPOLYGONS_MAXPOINTS) + { + Con_Printf("Debug_PolygonVertex: may have %i vertices max\n", VMPOLYGONS_MAXPOINTS); + return; + } + + debugPolys.begin_vertex[debugPolys.begin_vertices][0] = x; + debugPolys.begin_vertex[debugPolys.begin_vertices][1] = y; + debugPolys.begin_vertex[debugPolys.begin_vertices][2] = z; + debugPolys.begin_texcoord[debugPolys.begin_vertices][0] = s; + debugPolys.begin_texcoord[debugPolys.begin_vertices][1] = t; + debugPolys.begin_color[debugPolys.begin_vertices][0] = r; + debugPolys.begin_color[debugPolys.begin_vertices][1] = g; + debugPolys.begin_color[debugPolys.begin_vertices][2] = b; + debugPolys.begin_color[debugPolys.begin_vertices][3] = a; + debugPolys.begin_vertices++; +} + +void Debug_PolygonEnd(void) +{ + if (!debugPolys.begin_active) + { + Con_Printf("Debug_PolygonEnd: Debug_PolygonBegin wasn't called\n"); + return; + } + debugPolys.begin_active = false; + if (debugPolys.begin_vertices >= 3) + VMPolygons_Store(&debugPolys); + else + Con_Printf("Debug_PolygonEnd: %i vertices isn't a good choice\n", debugPolys.begin_vertices); +} + +/* +============= +CL_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +qboolean CL_CheckBottom (prvm_edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); + VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (!(CL_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) + goto realcheck; + } + + return true; // we got out easy + +realcheck: +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*sv_stepheight.value; + trace = CL_TraceLine(start, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), true, false, NULL, true, false); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = CL_TraceLine(start, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), true, false, NULL, true, false); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) + return false; + } + + return true; +} + +/* +============= +CL_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done and false is returned +============= +*/ +qboolean CL_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace) +{ + float dz; + vec3_t oldorg, neworg, end, traceendpos; + trace_t trace; + int i, svent; + prvm_edict_t *enemy; + +// try the move + VectorCopy (PRVM_clientedictvector(ent, origin), oldorg); + VectorAdd (PRVM_clientedictvector(ent, origin), move, neworg); + +// flying monsters don't step up + if ( (int)PRVM_clientedictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (PRVM_clientedictvector(ent, origin), move, neworg); + enemy = PRVM_PROG_TO_EDICT(PRVM_clientedictedict(ent, enemy)); + if (i == 0 && enemy != prog->edicts) + { + dz = PRVM_clientedictvector(ent, origin)[2] - PRVM_clientedictvector(PRVM_PROG_TO_EDICT(PRVM_clientedictedict(ent, enemy)), origin)[2]; + if (dz > 40) + neworg[2] -= 8; + if (dz < 30) + neworg[2] += 8; + } + trace = CL_TraceBox(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), PRVM_clientedictvector(ent, maxs), neworg, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true); + if (settrace) + CL_VM_SetTraceGlobals(&trace, svent); + + if (trace.fraction == 1) + { + VectorCopy(trace.endpos, traceendpos); + if (((int)PRVM_clientedictfloat(ent, flags) & FL_SWIM) && !(CL_PointSuperContents(traceendpos) & SUPERCONTENTS_LIQUIDSMASK)) + return false; // swim monster left water + + VectorCopy (traceendpos, PRVM_clientedictvector(ent, origin)); + if (relink) + CL_LinkEdict(ent); + return true; + } + + if (enemy == prog->edicts) + break; + } + + return false; + } + +// push down from a step height above the wished position + neworg[2] += sv_stepheight.value; + VectorCopy (neworg, end); + end[2] -= sv_stepheight.value*2; + + trace = CL_TraceBox(neworg, PRVM_clientedictvector(ent, mins), PRVM_clientedictvector(ent, maxs), end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true); + if (settrace) + CL_VM_SetTraceGlobals(&trace, svent); + + if (trace.startsolid) + { + neworg[2] -= sv_stepheight.value; + trace = CL_TraceBox(neworg, PRVM_clientedictvector(ent, mins), PRVM_clientedictvector(ent, maxs), end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true); + if (settrace) + CL_VM_SetTraceGlobals(&trace, svent); + if (trace.startsolid) + return false; + } + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) + { + VectorAdd (PRVM_clientedictvector(ent, origin), move, PRVM_clientedictvector(ent, origin)); + if (relink) + CL_LinkEdict(ent); + PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) & ~FL_ONGROUND; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, PRVM_clientedictvector(ent, origin)); + + if (!CL_CheckBottom (ent)) + { + if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + CL_LinkEdict(ent); + return true; + } + VectorCopy (oldorg, PRVM_clientedictvector(ent, origin)); + return false; + } + + if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) + PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) & ~FL_PARTIALGROUND; + + PRVM_clientedictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + +// the move is ok + if (relink) + CL_LinkEdict(ent); + return true; +} + +/* +=============== +VM_CL_walkmove + +float(float yaw, float dist[, settrace]) walkmove +=============== +*/ +static void VM_CL_walkmove (void) +{ + prvm_edict_t *ent; + float yaw, dist; + vec3_t move; + mfunction_t *oldf; + int oldself; + qboolean settrace; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_walkmove); + + // assume failure if it returns early + PRVM_G_FLOAT(OFS_RETURN) = 0; + + ent = PRVM_PROG_TO_EDICT(PRVM_clientglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning("walkmove: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("walkmove: can not modify free entity\n"); + return; + } + yaw = PRVM_G_FLOAT(OFS_PARM0); + dist = PRVM_G_FLOAT(OFS_PARM1); + settrace = prog->argc >= 3 && PRVM_G_FLOAT(OFS_PARM2); + + if ( !( (int)PRVM_clientedictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + return; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + +// save program state, because CL_movestep may call other progs + oldf = prog->xfunction; + oldself = PRVM_clientglobaledict(self); + + PRVM_G_FLOAT(OFS_RETURN) = CL_movestep(ent, move, true, false, settrace); + + +// restore program state + prog->xfunction = oldf; + PRVM_clientglobaledict(self) = oldself; +} + +/* +=============== +VM_CL_serverkey + +string(string key) serverkey +=============== +*/ +void VM_CL_serverkey(void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNT(1, VM_CL_serverkey); + InfoString_GetValue(cl.qw_serverinfo, PRVM_G_STRING(OFS_PARM0), string, sizeof(string)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); +} + +/* +================= +VM_CL_checkpvs + +Checks if an entity is in a point's PVS. +Should be fast but can be inexact. + +float checkpvs(vector viewpos, entity viewee) = #240; +================= +*/ +static void VM_CL_checkpvs (void) +{ + vec3_t viewpos; + prvm_edict_t *viewee; + vec3_t mi, ma; +#if 1 + unsigned char *pvs; +#else + int fatpvsbytes; + unsigned char fatpvs[MAX_MAP_LEAFS/8]; +#endif + + VM_SAFEPARMCOUNT(2, VM_SV_checkpvs); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), viewpos); + viewee = PRVM_G_EDICT(OFS_PARM1); + + if(viewee->priv.required->free) + { + VM_Warning("checkpvs: can not check free entity\n"); + PRVM_G_FLOAT(OFS_RETURN) = 4; + return; + } + + VectorAdd(PRVM_serveredictvector(viewee, origin), PRVM_serveredictvector(viewee, mins), mi); + VectorAdd(PRVM_serveredictvector(viewee, origin), PRVM_serveredictvector(viewee, maxs), ma); + +#if 1 + if(!sv.worldmodel->brush.GetPVS || !sv.worldmodel->brush.BoxTouchingPVS) + { + // no PVS support on this worldmodel... darn + PRVM_G_FLOAT(OFS_RETURN) = 3; + return; + } + pvs = sv.worldmodel->brush.GetPVS(sv.worldmodel, viewpos); + if(!pvs) + { + // viewpos isn't in any PVS... darn + PRVM_G_FLOAT(OFS_RETURN) = 2; + return; + } + PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, pvs, mi, ma); +#else + // using fat PVS like FTEQW does (slow) + if(!sv.worldmodel->brush.FatPVS || !sv.worldmodel->brush.BoxTouchingPVS) + { + // no PVS support on this worldmodel... darn + PRVM_G_FLOAT(OFS_RETURN) = 3; + return; + } + fatpvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, viewpos, 8, fatpvs, sizeof(fatpvs), false); + if(!fatpvsbytes) + { + // viewpos isn't in any PVS... darn + PRVM_G_FLOAT(OFS_RETURN) = 2; + return; + } + PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, fatpvs, mi, ma); +#endif +} + +// #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. +static void VM_CL_skel_create(void) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = CL_GetModelByIndex(modelindex); + skeleton_t *skeleton; + int i; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (!model || !model->num_bones) + return; + for (i = 0;i < MAX_EDICTS;i++) + if (!prog->skeletons[i]) + break; + if (i == MAX_EDICTS) + return; + prog->skeletons[i] = skeleton = (skeleton_t *)Mem_Alloc(cls.levelmempool, sizeof(skeleton_t) + model->num_bones * sizeof(matrix4x4_t)); + PRVM_G_FLOAT(OFS_RETURN) = i + 1; + skeleton->model = model; + skeleton->relativetransforms = (matrix4x4_t *)(skeleton+1); + // initialize to identity matrices + for (i = 0;i < skeleton->model->num_bones;i++) + skeleton->relativetransforms[i] = identitymatrix; +} + +// #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +static void VM_CL_skel_build(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + prvm_edict_t *ed = PRVM_G_EDICT(OFS_PARM1); + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM2); + float retainfrac = PRVM_G_FLOAT(OFS_PARM3); + int firstbone = PRVM_G_FLOAT(OFS_PARM4) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM5) - 1; + dp_model_t *model = CL_GetModelByIndex(modelindex); + float blendfrac; + int numblends; + int bonenum; + int blendindex; + framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + matrix4x4_t blendedmatrix; + matrix4x4_t matrix; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + firstbone = max(0, firstbone); + lastbone = min(lastbone, model->num_bones - 1); + lastbone = min(lastbone, skeleton->model->num_bones - 1); + VM_GenerateFrameGroupBlend(framegroupblend, ed); + VM_FrameBlendFromFrameGroupBlend(frameblend, framegroupblend, model); + blendfrac = 1.0f - retainfrac; + for (numblends = 0;numblends < MAX_FRAMEBLENDS && frameblend[numblends].lerp;numblends++) + frameblend[numblends].lerp *= blendfrac; + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + { + memset(&blendedmatrix, 0, sizeof(blendedmatrix)); + Matrix4x4_Accumulate(&blendedmatrix, &skeleton->relativetransforms[bonenum], retainfrac); + for (blendindex = 0;blendindex < numblends;blendindex++) + { + Matrix4x4_FromBonePose6s(&matrix, model->num_posescale, model->data_poses6s + 6 * (frameblend[blendindex].subframe * model->num_bones + bonenum)); + Matrix4x4_Accumulate(&blendedmatrix, &matrix, frameblend[blendindex].lerp); + } + skeleton->relativetransforms[bonenum] = blendedmatrix; + } + PRVM_G_FLOAT(OFS_RETURN) = skeletonindex + 1; +} + +// #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton +static void VM_CL_skel_get_numbones(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->num_bones; +} + +// #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) +static void VM_CL_skel_get_bonename(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + PRVM_G_INT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(skeleton->model->data_bones[bonenum].name); +} + +// #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +static void VM_CL_skel_get_boneparent(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->data_bones[bonenum].parent + 1; +} + +// #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex +static void VM_CL_skel_find_bone(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + const char *tagname = PRVM_G_STRING(OFS_PARM1); + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + PRVM_G_FLOAT(OFS_RETURN) = Mod_Alias_GetTagIndexForName(skeleton->model, 0, tagname); +} + +// #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +static void VM_CL_skel_get_bonerel(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + matrix4x4_t matrix; + vec3_t forward, left, up, origin; + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + VectorClear(PRVM_clientglobalvector(v_forward)); + VectorClear(PRVM_clientglobalvector(v_right)); + VectorClear(PRVM_clientglobalvector(v_up)); + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + matrix = skeleton->relativetransforms[bonenum]; + Matrix4x4_ToVectors(&matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); +} + +// #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +static void VM_CL_skel_get_boneabs(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + vec3_t forward, left, up, origin; + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + VectorClear(PRVM_clientglobalvector(v_forward)); + VectorClear(PRVM_clientglobalvector(v_right)); + VectorClear(PRVM_clientglobalvector(v_up)); + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + matrix = skeleton->relativetransforms[bonenum]; + // convert to absolute + while ((bonenum = skeleton->model->data_bones[bonenum].parent) >= 0) + { + temp = matrix; + Matrix4x4_Concat(&matrix, &skeleton->relativetransforms[bonenum], &temp); + } + Matrix4x4_ToVectors(&matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); +} + +// #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +static void VM_CL_skel_set_bone(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + skeleton->relativetransforms[bonenum] = matrix; +} + +// #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +static void VM_CL_skel_mul_bone(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + temp = skeleton->relativetransforms[bonenum]; + Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); +} + +// #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +static void VM_CL_skel_mul_bones(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int firstbone = PRVM_G_FLOAT(OFS_PARM1) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM2) - 1; + int bonenum; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + firstbone = max(0, firstbone); + lastbone = min(lastbone, skeleton->model->num_bones - 1); + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + { + temp = skeleton->relativetransforms[bonenum]; + Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); + } +} + +// #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +static void VM_CL_skel_copybones(void) +{ + int skeletonindexdst = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int skeletonindexsrc = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + int firstbone = PRVM_G_FLOAT(OFS_PARM2) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM3) - 1; + int bonenum; + skeleton_t *skeletondst; + skeleton_t *skeletonsrc; + if (skeletonindexdst < 0 || skeletonindexdst >= MAX_EDICTS || !(skeletondst = prog->skeletons[skeletonindexdst])) + return; + if (skeletonindexsrc < 0 || skeletonindexsrc >= MAX_EDICTS || !(skeletonsrc = prog->skeletons[skeletonindexsrc])) + return; + firstbone = max(0, firstbone); + lastbone = min(lastbone, skeletondst->model->num_bones - 1); + lastbone = min(lastbone, skeletonsrc->model->num_bones - 1); + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + skeletondst->relativetransforms[bonenum] = skeletonsrc->relativetransforms[bonenum]; +} + +// #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +static void VM_CL_skel_delete(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + Mem_Free(skeleton); + prog->skeletons[skeletonindex] = NULL; +} + +// #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found +static void VM_CL_frameforname(void) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = CL_GetModelByIndex(modelindex); + const char *name = PRVM_G_STRING(OFS_PARM1); + int i; + PRVM_G_FLOAT(OFS_RETURN) = -1; + if (!model || !model->animscenes) + return; + for (i = 0;i < model->numframes;i++) + { + if (!strcasecmp(model->animscenes[i].name, name)) + { + PRVM_G_FLOAT(OFS_RETURN) = i; + break; + } + } +} + +// #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +static void VM_CL_frameduration(void) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = CL_GetModelByIndex(modelindex); + int framenum = (int)PRVM_G_FLOAT(OFS_PARM1); + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes) + return; + if (model->animscenes[framenum].framerate) + PRVM_G_FLOAT(OFS_RETURN) = model->animscenes[framenum].framecount / model->animscenes[framenum].framerate; +} + +void VM_CL_RotateMoves(void) +{ + /* + * Obscure builtin used by GAME_XONOTIC. + * + * Edits the input history of cl_movement by rotating all move commands + * currently in the queue using the given transform. + * + * The vector passed is an "angles transform" as used by warpzonelib, i.e. + * v_angle-like (non-inverted) euler angles that perform the rotation + * of the space that is to be done. + * + * This is meant to be used as a fixangle replacement after passing + * through a warpzone/portal: the client is told about the warp transform, + * and calls this function in the same frame as the one on which the + * client's origin got changed by the serverside teleport. Then this code + * transforms the pre-warp input (which matches the empty space behind + * the warp plane) into post-warp input (which matches the target area + * of the warp). Also, at the same time, the client has to use + * R_SetView to adjust VF_CL_VIEWANGLES according to the same transform. + * + * This together allows warpzone motion to be perfectly predicted by + * the client! + * + * Furthermore, for perfect warpzone behaviour, the server side also + * has to detect input the client sent before it received the origin + * update, but after the warp occurred on the server, and has to adjust + * input appropriately. + */ + matrix4x4_t m; + vec3_t v = {0, 0, 0}; + vec3_t x, y, z; + VM_SAFEPARMCOUNT(1, VM_CL_RotateMoves); + AngleVectorsFLU(PRVM_G_VECTOR(OFS_PARM0), x, y, z); + Matrix4x4_FromVectors(&m, x, y, z, v); + CL_RotateMoves(&m); +} + +// #358 void(string cubemapname) loadcubemap +static void VM_CL_loadcubemap(void) +{ + const char *name; + + VM_SAFEPARMCOUNT(1, VM_CL_loadcubemap); + name = PRVM_G_STRING(OFS_PARM0); + R_GetCubemap(name); +} + +//============================================================================ + +// To create a almost working builtin file from this replace: +// "^NULL.*" with "" +// "^{.*//.*}:Wh\(.*\)" with "\1" +// "\:" with "//" +// "^.*//:Wh{\#:d*}:Wh{.*}" with "\2 = \1;" +// "\n\n+" with "\n\n" + +prvm_builtin_t vm_cl_builtins[] = { +NULL, // #0 NULL function (not callable) (QUAKE) +VM_CL_makevectors, // #1 void(vector ang) makevectors (QUAKE) +VM_CL_setorigin, // #2 void(entity e, vector o) setorigin (QUAKE) +VM_CL_setmodel, // #3 void(entity e, string m) setmodel (QUAKE) +VM_CL_setsize, // #4 void(entity e, vector min, vector max) setsize (QUAKE) +NULL, // #5 void(entity e, vector min, vector max) setabssize (QUAKE) +VM_break, // #6 void() break (QUAKE) +VM_random, // #7 float() random (QUAKE) +VM_CL_sound, // #8 void(entity e, float chan, string samp) sound (QUAKE) +VM_normalize, // #9 vector(vector v) normalize (QUAKE) +VM_error, // #10 void(string e) error (QUAKE) +VM_objerror, // #11 void(string e) objerror (QUAKE) +VM_vlen, // #12 float(vector v) vlen (QUAKE) +VM_vectoyaw, // #13 float(vector v) vectoyaw (QUAKE) +VM_CL_spawn, // #14 entity() spawn (QUAKE) +VM_remove, // #15 void(entity e) remove (QUAKE) +VM_CL_traceline, // #16 void(vector v1, vector v2, float tryents, entity ignoreentity) traceline (QUAKE) +NULL, // #17 entity() checkclient (QUAKE) +VM_find, // #18 entity(entity start, .string fld, string match) find (QUAKE) +VM_precache_sound, // #19 void(string s) precache_sound (QUAKE) +VM_CL_precache_model, // #20 void(string s) precache_model (QUAKE) +NULL, // #21 void(entity client, string s, ...) stuffcmd (QUAKE) +VM_CL_findradius, // #22 entity(vector org, float rad) findradius (QUAKE) +NULL, // #23 void(string s, ...) bprint (QUAKE) +NULL, // #24 void(entity client, string s, ...) sprint (QUAKE) +VM_dprint, // #25 void(string s, ...) dprint (QUAKE) +VM_ftos, // #26 string(float f) ftos (QUAKE) +VM_vtos, // #27 string(vector v) vtos (QUAKE) +VM_coredump, // #28 void() coredump (QUAKE) +VM_traceon, // #29 void() traceon (QUAKE) +VM_traceoff, // #30 void() traceoff (QUAKE) +VM_eprint, // #31 void(entity e) eprint (QUAKE) +VM_CL_walkmove, // #32 float(float yaw, float dist[, float settrace]) walkmove (QUAKE) +NULL, // #33 (QUAKE) +VM_CL_droptofloor, // #34 float() droptofloor (QUAKE) +VM_CL_lightstyle, // #35 void(float style, string value) lightstyle (QUAKE) +VM_rint, // #36 float(float v) rint (QUAKE) +VM_floor, // #37 float(float v) floor (QUAKE) +VM_ceil, // #38 float(float v) ceil (QUAKE) +NULL, // #39 (QUAKE) +VM_CL_checkbottom, // #40 float(entity e) checkbottom (QUAKE) +VM_CL_pointcontents, // #41 float(vector v) pointcontents (QUAKE) +NULL, // #42 (QUAKE) +VM_fabs, // #43 float(float f) fabs (QUAKE) +NULL, // #44 vector(entity e, float speed) aim (QUAKE) +VM_cvar, // #45 float(string s) cvar (QUAKE) +VM_localcmd, // #46 void(string s) localcmd (QUAKE) +VM_nextent, // #47 entity(entity e) nextent (QUAKE) +VM_CL_particle, // #48 void(vector o, vector d, float color, float count) particle (QUAKE) +VM_changeyaw, // #49 void() ChangeYaw (QUAKE) +NULL, // #50 (QUAKE) +VM_vectoangles, // #51 vector(vector v) vectoangles (QUAKE) +NULL, // #52 void(float to, float f) WriteByte (QUAKE) +NULL, // #53 void(float to, float f) WriteChar (QUAKE) +NULL, // #54 void(float to, float f) WriteShort (QUAKE) +NULL, // #55 void(float to, float f) WriteLong (QUAKE) +NULL, // #56 void(float to, float f) WriteCoord (QUAKE) +NULL, // #57 void(float to, float f) WriteAngle (QUAKE) +NULL, // #58 void(float to, string s) WriteString (QUAKE) +NULL, // #59 (QUAKE) +VM_sin, // #60 float(float f) sin (DP_QC_SINCOSSQRTPOW) +VM_cos, // #61 float(float f) cos (DP_QC_SINCOSSQRTPOW) +VM_sqrt, // #62 float(float f) sqrt (DP_QC_SINCOSSQRTPOW) +VM_changepitch, // #63 void(entity ent) changepitch (DP_QC_CHANGEPITCH) +VM_CL_tracetoss, // #64 void(entity e, entity ignore) tracetoss (DP_QC_TRACETOSS) +VM_etos, // #65 string(entity ent) etos (DP_QC_ETOS) +NULL, // #66 (QUAKE) +NULL, // #67 void(float step) movetogoal (QUAKE) +VM_precache_file, // #68 string(string s) precache_file (QUAKE) +VM_CL_makestatic, // #69 void(entity e) makestatic (QUAKE) +NULL, // #70 void(string s) changelevel (QUAKE) +NULL, // #71 (QUAKE) +VM_cvar_set, // #72 void(string var, string val) cvar_set (QUAKE) +NULL, // #73 void(entity client, strings) centerprint (QUAKE) +VM_CL_ambientsound, // #74 void(vector pos, string samp, float vol, float atten) ambientsound (QUAKE) +VM_CL_precache_model, // #75 string(string s) precache_model2 (QUAKE) +VM_precache_sound, // #76 string(string s) precache_sound2 (QUAKE) +VM_precache_file, // #77 string(string s) precache_file2 (QUAKE) +NULL, // #78 void(entity e) setspawnparms (QUAKE) +NULL, // #79 void(entity killer, entity killee) logfrag (QUAKEWORLD) +NULL, // #80 string(entity e, string keyname) infokey (QUAKEWORLD) +VM_stof, // #81 float(string s) stof (FRIK_FILE) +NULL, // #82 void(vector where, float set) multicast (QUAKEWORLD) +NULL, // #83 (QUAKE) +NULL, // #84 (QUAKE) +NULL, // #85 (QUAKE) +NULL, // #86 (QUAKE) +NULL, // #87 (QUAKE) +NULL, // #88 (QUAKE) +NULL, // #89 (QUAKE) +VM_CL_tracebox, // #90 void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox (DP_QC_TRACEBOX) +VM_randomvec, // #91 vector() randomvec (DP_QC_RANDOMVEC) +VM_CL_getlight, // #92 vector(vector org) getlight (DP_QC_GETLIGHT) +VM_registercvar, // #93 float(string name, string value) registercvar (DP_REGISTERCVAR) +VM_min, // #94 float(float a, floats) min (DP_QC_MINMAXBOUND) +VM_max, // #95 float(float a, floats) max (DP_QC_MINMAXBOUND) +VM_bound, // #96 float(float minimum, float val, float maximum) bound (DP_QC_MINMAXBOUND) +VM_pow, // #97 float(float f, float f) pow (DP_QC_SINCOSSQRTPOW) +VM_findfloat, // #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT) +VM_checkextension, // #99 float(string s) checkextension (the basis of the extension system) +// FrikaC and Telejano range #100-#199 +NULL, // #100 +NULL, // #101 +NULL, // #102 +NULL, // #103 +NULL, // #104 +NULL, // #105 +NULL, // #106 +NULL, // #107 +NULL, // #108 +NULL, // #109 +VM_fopen, // #110 float(string filename, float mode) fopen (FRIK_FILE) +VM_fclose, // #111 void(float fhandle) fclose (FRIK_FILE) +VM_fgets, // #112 string(float fhandle) fgets (FRIK_FILE) +VM_fputs, // #113 void(float fhandle, string s) fputs (FRIK_FILE) +VM_strlen, // #114 float(string s) strlen (FRIK_FILE) +VM_strcat, // #115 string(string s1, string s2, ...) strcat (FRIK_FILE) +VM_substring, // #116 string(string s, float start, float length) substring (FRIK_FILE) +VM_stov, // #117 vector(string) stov (FRIK_FILE) +VM_strzone, // #118 string(string s) strzone (FRIK_FILE) +VM_strunzone, // #119 void(string s) strunzone (FRIK_FILE) +NULL, // #120 +NULL, // #121 +NULL, // #122 +NULL, // #123 +NULL, // #124 +NULL, // #125 +NULL, // #126 +NULL, // #127 +NULL, // #128 +NULL, // #129 +NULL, // #130 +NULL, // #131 +NULL, // #132 +NULL, // #133 +NULL, // #134 +NULL, // #135 +NULL, // #136 +NULL, // #137 +NULL, // #138 +NULL, // #139 +NULL, // #140 +NULL, // #141 +NULL, // #142 +NULL, // #143 +NULL, // #144 +NULL, // #145 +NULL, // #146 +NULL, // #147 +NULL, // #148 +NULL, // #149 +NULL, // #150 +NULL, // #151 +NULL, // #152 +NULL, // #153 +NULL, // #154 +NULL, // #155 +NULL, // #156 +NULL, // #157 +NULL, // #158 +NULL, // #159 +NULL, // #160 +NULL, // #161 +NULL, // #162 +NULL, // #163 +NULL, // #164 +NULL, // #165 +NULL, // #166 +NULL, // #167 +NULL, // #168 +NULL, // #169 +NULL, // #170 +NULL, // #171 +NULL, // #172 +NULL, // #173 +NULL, // #174 +NULL, // #175 +NULL, // #176 +NULL, // #177 +NULL, // #178 +NULL, // #179 +NULL, // #180 +NULL, // #181 +NULL, // #182 +NULL, // #183 +NULL, // #184 +NULL, // #185 +NULL, // #186 +NULL, // #187 +NULL, // #188 +NULL, // #189 +NULL, // #190 +NULL, // #191 +NULL, // #192 +NULL, // #193 +NULL, // #194 +NULL, // #195 +NULL, // #196 +NULL, // #197 +NULL, // #198 +NULL, // #199 +// FTEQW range #200-#299 +NULL, // #200 +NULL, // #201 +NULL, // #202 +NULL, // #203 +NULL, // #204 +NULL, // #205 +NULL, // #206 +NULL, // #207 +NULL, // #208 +NULL, // #209 +NULL, // #210 +NULL, // #211 +NULL, // #212 +NULL, // #213 +NULL, // #214 +NULL, // #215 +NULL, // #216 +NULL, // #217 +VM_bitshift, // #218 float(float number, float quantity) bitshift (EXT_BITSHIFT) +NULL, // #219 +NULL, // #220 +VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) +VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) +VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) +VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) +VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) +VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) +VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) +VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) +VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) +VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) +NULL, // #231 +NULL, // #232 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) +NULL, // #233 +NULL, // #234 +NULL, // #235 +NULL, // #236 +NULL, // #237 +NULL, // #238 +NULL, // #239 +VM_CL_checkpvs, // #240 +NULL, // #241 +NULL, // #242 +NULL, // #243 +NULL, // #244 +NULL, // #245 +NULL, // #246 +NULL, // #247 +NULL, // #248 +NULL, // #249 +NULL, // #250 +NULL, // #251 +NULL, // #252 +NULL, // #253 +NULL, // #254 +NULL, // #255 +NULL, // #256 +NULL, // #257 +NULL, // #258 +NULL, // #259 +NULL, // #260 +NULL, // #261 +NULL, // #262 +VM_CL_skel_create, // #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. +VM_CL_skel_build, // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +VM_CL_skel_get_numbones, // #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton +VM_CL_skel_get_bonename, // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) +VM_CL_skel_get_boneparent, // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, -1 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +VM_CL_skel_find_bone, // #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex +VM_CL_skel_get_bonerel, // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +VM_CL_skel_get_boneabs, // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +VM_CL_skel_set_bone, // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +VM_CL_skel_mul_bone, // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +VM_CL_skel_mul_bones, // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +VM_CL_skel_copybones, // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +VM_CL_skel_delete, // #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +VM_CL_frameforname, // #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found +VM_CL_frameduration, // #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +NULL, // #278 +NULL, // #279 +NULL, // #280 +NULL, // #281 +NULL, // #282 +NULL, // #283 +NULL, // #284 +NULL, // #285 +NULL, // #286 +NULL, // #287 +NULL, // #288 +NULL, // #289 +NULL, // #290 +NULL, // #291 +NULL, // #292 +NULL, // #293 +NULL, // #294 +NULL, // #295 +NULL, // #296 +NULL, // #297 +NULL, // #298 +NULL, // #299 +// CSQC range #300-#399 +VM_CL_R_ClearScene, // #300 void() clearscene (EXT_CSQC) +VM_CL_R_AddEntities, // #301 void(float mask) addentities (EXT_CSQC) +VM_CL_R_AddEntity, // #302 void(entity ent) addentity (EXT_CSQC) +VM_CL_R_SetView, // #303 float(float property, ...) setproperty (EXT_CSQC) +VM_CL_R_RenderScene, // #304 void() renderscene (EXT_CSQC) +VM_CL_R_AddDynamicLight, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (EXT_CSQC) +VM_CL_R_PolygonBegin, // #306 void(string texturename, float flag, float is2d[NYI: , float lines]) R_BeginPolygon +VM_CL_R_PolygonVertex, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex +VM_CL_R_PolygonEnd, // #308 void() R_EndPolygon +NULL /* R_LoadWorldModel in menu VM, should stay unassigned in client*/, // #309 +VM_CL_unproject, // #310 vector (vector v) cs_unproject (EXT_CSQC) +VM_CL_project, // #311 vector (vector v) cs_project (EXT_CSQC) +NULL, // #312 +NULL, // #313 +NULL, // #314 +VM_drawline, // #315 void(float width, vector pos1, vector pos2, float flag) drawline (EXT_CSQC) +VM_iscachedpic, // #316 float(string name) iscachedpic (EXT_CSQC) +VM_precache_pic, // #317 string(string name, float trywad) precache_pic (EXT_CSQC) +VM_getimagesize, // #318 vector(string picname) draw_getimagesize (EXT_CSQC) +VM_freepic, // #319 void(string name) freepic (EXT_CSQC) +VM_drawcharacter, // #320 float(vector position, float character, vector scale, vector rgb, float alpha, float flag) drawcharacter (EXT_CSQC) +VM_drawstring, // #321 float(vector position, string text, vector scale, vector rgb, float alpha, float flag) drawstring (EXT_CSQC) +VM_drawpic, // #322 float(vector position, string pic, vector size, vector rgb, float alpha, float flag) drawpic (EXT_CSQC) +VM_drawfill, // #323 float(vector position, vector size, vector rgb, float alpha, float flag) drawfill (EXT_CSQC) +VM_drawsetcliparea, // #324 void(float x, float y, float width, float height) drawsetcliparea +VM_drawresetcliparea, // #325 void(void) drawresetcliparea +VM_drawcolorcodedstring, // #326 float drawcolorcodedstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) (EXT_CSQC) +VM_stringwidth, // #327 // FIXME is this okay? +VM_drawsubpic, // #328 // FIXME is this okay? +VM_drawrotpic, // #329 // FIXME is this okay? +VM_CL_getstatf, // #330 float(float stnum) getstatf (EXT_CSQC) +VM_CL_getstati, // #331 float(float stnum) getstati (EXT_CSQC) +VM_CL_getstats, // #332 string(float firststnum) getstats (EXT_CSQC) +VM_CL_setmodelindex, // #333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) +VM_CL_modelnameforindex, // #334 string(float mdlindex) modelnameforindex (EXT_CSQC) +VM_CL_particleeffectnum, // #335 float(string effectname) particleeffectnum (EXT_CSQC) +VM_CL_trailparticles, // #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) +VM_CL_pointparticles, // #337 void(float effectnum, vector origin [, vector dir, float count]) pointparticles (EXT_CSQC) +VM_centerprint, // #338 void(string s, ...) centerprint (EXT_CSQC) +VM_print, // #339 void(string s, ...) print (EXT_CSQC, DP_SV_PRINT) +VM_keynumtostring, // #340 string(float keynum) keynumtostring (EXT_CSQC) +VM_stringtokeynum, // #341 float(string keyname) stringtokeynum (EXT_CSQC) +VM_getkeybind, // #342 string(float keynum[, float bindmap]) getkeybind (EXT_CSQC) +VM_CL_setcursormode, // #343 void(float usecursor) setcursormode (EXT_CSQC) +VM_CL_getmousepos, // #344 vector() getmousepos (EXT_CSQC) +VM_CL_getinputstate, // #345 float(float framenum) getinputstate (EXT_CSQC) +VM_CL_setsensitivityscale, // #346 void(float sens) setsensitivityscale (EXT_CSQC) +VM_CL_runplayerphysics, // #347 void() runstandardplayerphysics (EXT_CSQC) +VM_CL_getplayerkey, // #348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) +VM_CL_isdemo, // #349 float() isdemo (EXT_CSQC) +VM_isserver, // #350 float() isserver (EXT_CSQC) +VM_CL_setlistener, // #351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) +VM_CL_registercmd, // #352 void(string cmdname) registercommand (EXT_CSQC) +VM_wasfreed, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too) +VM_CL_serverkey, // #354 string(string key) serverkey (EXT_CSQC) +VM_CL_videoplaying, // #355 +VM_findfont, // #356 float(string fontname) loadfont (DP_GFX_FONTS) +VM_loadfont, // #357 float(string fontname, string fontmaps, string sizes, float slot) loadfont (DP_GFX_FONTS) +VM_CL_loadcubemap, // #358 void(string cubemapname) loadcubemap (DP_GFX_) +NULL, // #359 +VM_CL_ReadByte, // #360 float() readbyte (EXT_CSQC) +VM_CL_ReadChar, // #361 float() readchar (EXT_CSQC) +VM_CL_ReadShort, // #362 float() readshort (EXT_CSQC) +VM_CL_ReadLong, // #363 float() readlong (EXT_CSQC) +VM_CL_ReadCoord, // #364 float() readcoord (EXT_CSQC) +VM_CL_ReadAngle, // #365 float() readangle (EXT_CSQC) +VM_CL_ReadString, // #366 string() readstring (EXT_CSQC) +VM_CL_ReadFloat, // #367 float() readfloat (EXT_CSQC) +NULL, // #368 +NULL, // #369 +NULL, // #370 +NULL, // #371 +NULL, // #372 +NULL, // #373 +NULL, // #374 +NULL, // #375 +NULL, // #376 +NULL, // #377 +NULL, // #378 +NULL, // #379 +NULL, // #380 +NULL, // #381 +NULL, // #382 +NULL, // #383 +NULL, // #384 +NULL, // #385 +NULL, // #386 +NULL, // #387 +NULL, // #388 +NULL, // #389 +NULL, // #390 +NULL, // #391 +NULL, // #392 +NULL, // #393 +NULL, // #394 +NULL, // #395 +NULL, // #396 +NULL, // #397 +NULL, // #398 +NULL, // #399 +// LordHavoc's range #400-#499 +VM_CL_copyentity, // #400 void(entity from, entity to) copyentity (DP_QC_COPYENTITY) +NULL, // #401 void(entity ent, float colors) setcolor (DP_QC_SETCOLOR) +VM_findchain, // #402 entity(.string fld, string match) findchain (DP_QC_FINDCHAIN) +VM_findchainfloat, // #403 entity(.float fld, float match) findchainfloat (DP_QC_FINDCHAINFLOAT) +VM_CL_effect, // #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) +VM_CL_te_blood, // #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) +VM_CL_te_bloodshower, // #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) +VM_CL_te_explosionrgb, // #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) +VM_CL_te_particlecube, // #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) +VM_CL_te_particlerain, // #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) +VM_CL_te_particlesnow, // #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) +VM_CL_te_spark, // #411 void(vector org, vector vel, float howmany) te_spark (DP_TE_SPARK) +VM_CL_te_gunshotquad, // #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) +VM_CL_te_spikequad, // #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) +VM_CL_te_superspikequad, // #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) +VM_CL_te_explosionquad, // #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) +VM_CL_te_smallflash, // #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) +VM_CL_te_customflash, // #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) +VM_CL_te_gunshot, // #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_spike, // #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_superspike, // #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_explosion, // #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_tarexplosion, // #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_wizspike, // #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_knightspike, // #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_lavasplash, // #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_teleport, // #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_explosion2, // #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_lightning1, // #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_lightning2, // #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_lightning3, // #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_beam, // #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) +VM_vectorvectors, // #432 void(vector dir) vectorvectors (DP_QC_VECTORVECTORS) +VM_CL_te_plasmaburn, // #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) +VM_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE) +VM_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE) +VM_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE) +VM_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE) +VM_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE) +VM_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE) +NULL, // #440 void(entity e, string s) clientcommand (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_tokenize, // #441 float(string s) tokenize (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_argv, // #442 string(float n) argv (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_CL_setattachment, // #443 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) +VM_search_begin, // #444 float(string pattern, float caseinsensitive, float quiet) search_begin (DP_QC_FS_SEARCH) +VM_search_end, // #445 void(float handle) search_end (DP_QC_FS_SEARCH) +VM_search_getsize, // #446 float(float handle) search_getsize (DP_QC_FS_SEARCH) +VM_search_getfilename, // #447 string(float handle, float num) search_getfilename (DP_QC_FS_SEARCH) +VM_cvar_string, // #448 string(string s) cvar_string (DP_QC_CVAR_STRING) +VM_findflags, // #449 entity(entity start, .float fld, float match) findflags (DP_QC_FINDFLAGS) +VM_findchainflags, // #450 entity(.float fld, float match) findchainflags (DP_QC_FINDCHAINFLAGS) +VM_CL_gettagindex, // #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) +VM_CL_gettaginfo, // #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) +NULL, // #453 void(entity clent) dropclient (DP_SV_DROPCLIENT) +NULL, // #454 entity() spawnclient (DP_SV_BOTCLIENT) +NULL, // #455 float(entity clent) clienttype (DP_SV_BOTCLIENT) +NULL, // #456 void(float to, string s) WriteUnterminatedString (DP_SV_WRITEUNTERMINATEDSTRING) +VM_CL_te_flamejet, // #457 void(vector org, vector vel, float howmany) te_flamejet (DP_TE_FLAMEJET) +NULL, // #458 +VM_ftoe, // #459 entity(float num) entitybyindex (DP_QC_EDICT_NUM) +VM_buf_create, // #460 float() buf_create (DP_QC_STRINGBUFFERS) +VM_buf_del, // #461 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) +VM_buf_getsize, // #462 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) +VM_buf_copy, // #463 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) +VM_buf_sort, // #464 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) +VM_buf_implode, // #465 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) +VM_bufstr_get, // #466 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) +VM_bufstr_set, // #467 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) +VM_bufstr_add, // #468 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) +VM_bufstr_free, // #469 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) +NULL, // #470 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) +VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) +VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) +VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) +VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) +VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) +VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) +VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_QC_STRINGCOLORFUNCTIONS) +VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) +VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) +VM_strtolower, // #480 string(string s) VM_strtolower (DP_QC_STRING_CASE_FUNCTIONS) +VM_strtoupper, // #481 string(string s) VM_strtoupper (DP_QC_STRING_CASE_FUNCTIONS) +VM_cvar_defstring, // #482 string(string s) cvar_defstring (DP_QC_CVAR_DEFSTRING) +VM_CL_pointsound, // #483 void(vector origin, string sample, float volume, float attenuation) pointsound (DP_SV_POINTSOUND) +VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) +VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) +VM_getsurfacepointattribute,// #486 vector(entity e, float s, float n, float a) getsurfacepointattribute +VM_gecko_create, // #487 float gecko_create( string name ) +VM_gecko_destroy, // #488 void gecko_destroy( string name ) +VM_gecko_navigate, // #489 void gecko_navigate( string name, string URI ) +VM_gecko_keyevent, // #490 float gecko_keyevent( string name, float key, float eventtype ) +VM_gecko_movemouse, // #491 void gecko_mousemove( string name, float x, float y ) +VM_gecko_resize, // #492 void gecko_resize( string name, float w, float h ) +VM_gecko_get_texture_extent, // #493 vector gecko_get_texture_extent( string name ) +VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) +VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) +VM_numentityfields, // #496 float() numentityfields = #496; (QP_QC_ENTITYDATA) +VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (DP_QC_ENTITYDATA) +VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) +VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) +VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) +VM_CL_ReadPicture, // #501 string() ReadPicture = #501; +VM_CL_boxparticles, // #502 void(float effectnum, entity own, vector origin_from, vector origin_to, vector dir_from, vector dir_to, float count) boxparticles (DP_CSQC_BOXPARTICLES) +VM_whichpack, // #503 string(string) whichpack = #503; +VM_CL_GetEntity, // #504 float(float entitynum, float fldnum) getentity = #504; vector(float entitynum, float fldnum) getentityvec = #504; +NULL, // #505 +NULL, // #506 +NULL, // #507 +NULL, // #508 +NULL, // #509 +VM_uri_escape, // #510 string(string in) uri_escape = #510; +VM_uri_unescape, // #511 string(string in) uri_unescape = #511; +VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) +VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) +VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) +VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) +VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) +VM_gettime, // #519 float(float timer) gettime = #519; (DP_QC_GETTIME) +VM_keynumtostring, // #520 string keynumtostring(float keynum) +VM_findkeysforcommand, // #521 string findkeysforcommand(string command[, float bindmap]) +VM_CL_InitParticleSpawner, // #522 void(float max_themes) initparticlespawner (DP_CSQC_SPAWNPARTICLE) +VM_CL_ResetParticle, // #523 void() resetparticle (DP_CSQC_SPAWNPARTICLE) +VM_CL_ParticleTheme, // #524 void(float theme) particletheme (DP_CSQC_SPAWNPARTICLE) +VM_CL_ParticleThemeSave, // #525 void() particlethemesave, void(float theme) particlethemeupdate (DP_CSQC_SPAWNPARTICLE) +VM_CL_ParticleThemeFree, // #526 void() particlethemefree (DP_CSQC_SPAWNPARTICLE) +VM_CL_SpawnParticle, // #527 float(vector org, vector vel, [float theme]) particle (DP_CSQC_SPAWNPARTICLE) +VM_CL_SpawnParticleDelayed, // #528 float(vector org, vector vel, float delay, float collisiondelay, [float theme]) delayedparticle (DP_CSQC_SPAWNPARTICLE) +VM_loadfromdata, // #529 +VM_loadfromfile, // #530 +VM_CL_setpause, // #531 float(float ispaused) setpause = #531 (DP_CSQC_SETPAUSE) +VM_log, // #532 +VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) +VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) +NULL, // #535 +NULL, // #536 +NULL, // #537 +NULL, // #538 +NULL, // #539 +VM_physics_enable, // #540 void(entity e, float physics_enabled) physics_enable = #540; (DP_PHYSICS_ODE) +VM_physics_addforce, // #541 void(entity e, vector force, vector relative_ofs) physics_addforce = #541; (DP_PHYSICS_ODE) +VM_physics_addtorque, // #542 void(entity e, vector torque) physics_addtorque = #542; (DP_PHYSICS_ODE) +NULL, // #543 +NULL, // #544 +NULL, // #545 +NULL, // #546 +NULL, // #547 +NULL, // #548 +NULL, // #549 +NULL, // #550 +NULL, // #551 +NULL, // #552 +NULL, // #553 +NULL, // #554 +NULL, // #555 +NULL, // #556 +NULL, // #557 +NULL, // #558 +NULL, // #559 +NULL, // #560 +NULL, // #561 +NULL, // #562 +NULL, // #563 +NULL, // #564 +NULL, // #565 +NULL, // #566 +NULL, // #567 +NULL, // #568 +NULL, // #569 +NULL, // #570 +NULL, // #571 +NULL, // #572 +NULL, // #573 +NULL, // #574 +NULL, // #575 +NULL, // #576 +NULL, // #577 +NULL, // #578 +NULL, // #579 +NULL, // #580 +NULL, // #581 +NULL, // #582 +NULL, // #583 +NULL, // #584 +NULL, // #585 +NULL, // #586 +NULL, // #587 +NULL, // #588 +NULL, // #589 +NULL, // #590 +NULL, // #591 +NULL, // #592 +NULL, // #593 +NULL, // #594 +NULL, // #595 +NULL, // #596 +NULL, // #597 +NULL, // #598 +NULL, // #599 +NULL, // #600 +NULL, // #601 +NULL, // #602 +NULL, // #603 +NULL, // #604 +VM_callfunction, // #605 +VM_writetofile, // #606 +VM_isfunction, // #607 +NULL, // #608 +NULL, // #609 +VM_findkeysforcommand, // #610 string findkeysforcommand(string command[, float bindmap]) +NULL, // #611 +NULL, // #612 +VM_parseentitydata, // #613 +NULL, // #614 +NULL, // #615 +NULL, // #616 +NULL, // #617 +NULL, // #618 +NULL, // #619 +NULL, // #620 +NULL, // #621 +NULL, // #622 +NULL, // #623 +VM_CL_getextresponse, // #624 string getextresponse(void) +NULL, // #625 +NULL, // #626 +VM_sprintf, // #627 string sprintf(string format, ...) +VM_getsurfacenumtriangles, // #628 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACETRIANGLE) +VM_getsurfacetriangle, // #629 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACETRIANGLE) +VM_setkeybind, // #630 float(float key, string bind[, float bindmap]) setkeybind +VM_getbindmaps, // #631 vector(void) getbindmap +VM_setbindmaps, // #632 float(vector bm) setbindmap +NULL, // #633 +NULL, // #634 +NULL, // #635 +NULL, // #636 +NULL, // #637 +VM_CL_RotateMoves, // #638 +NULL, // #639 +}; + +const int vm_cl_numbuiltins = sizeof(vm_cl_builtins) / sizeof(prvm_builtin_t); + +void VM_Polygons_Reset(void) +{ + vmpolygons_t* polys = vmpolygons + PRVM_GetProgNr(); + + // TODO: replace vm_polygons stuff with a more general debugging polygon system, and make vm_polygons functions use that system + if(polys->initialized) + { + Mem_FreePool(&polys->pool); + polys->initialized = false; + } +} + +void VM_CL_Cmd_Init(void) +{ + VM_Cmd_Init(); + VM_Polygons_Reset(); +} + +void VM_CL_Cmd_Reset(void) +{ + World_End(&cl.world); + VM_Cmd_Reset(); + VM_Polygons_Reset(); +} + + diff --git a/misc/source/darkplaces-src/clvm_cmds.h b/misc/source/darkplaces-src/clvm_cmds.h new file mode 100644 index 00000000..8835dae6 --- /dev/null +++ b/misc/source/darkplaces-src/clvm_cmds.h @@ -0,0 +1,33 @@ +#ifndef __CLVM_CMDS_H__ +#define __CLVM_CMDS_H__ + +int CL_GetPitchSign(prvm_edict_t *ent); +int CL_GetTagMatrix (matrix4x4_t *out, prvm_edict_t *ent, int tagindex); +void CL_GetEntityMatrix (prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix); + +/* These are VM built-ins that originate in the client-side programs support + but are reused by the other programs (usually the menu). */ + +void VM_CL_setmodel (void); +void VM_CL_precache_model (void); +void VM_CL_setorigin (void); + +void VM_CL_R_AddDynamicLight (void); +void VM_CL_R_ClearScene (void); +void VM_CL_R_AddEntities (void); +void VM_CL_R_AddEntity (void); +void VM_CL_R_SetView (void); +void VM_CL_R_RenderScene (void); +void VM_CL_R_LoadWorldModel (void); + +void VM_CL_R_PolygonBegin (void); +void VM_CL_R_PolygonVertex (void); +void VM_CL_R_PolygonEnd (void); +/* VMs exposing the polygon calls must call this on Init/Reset */ +void VM_Polygons_Reset(void); + +void VM_CL_setattachment(void); +void VM_CL_gettagindex(void); +void VM_CL_gettaginfo(void); + +#endif /* __CLVM_CMDS_H__ */ diff --git a/misc/source/darkplaces-src/cmd.c b/misc/source/darkplaces-src/cmd.c new file mode 100644 index 00000000..bbaca649 --- /dev/null +++ b/misc/source/darkplaces-src/cmd.c @@ -0,0 +1,1972 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cmd.c -- Quake script command processing module + +#include "quakedef.h" + +typedef struct cmdalias_s +{ + struct cmdalias_s *next; + char name[MAX_ALIAS_NAME]; + char *value; + qboolean initstate; // indicates this command existed at init + char *initialvalue; // backup copy of value at init +} cmdalias_t; + +static cmdalias_t *cmd_alias; + +static qboolean cmd_wait; + +static mempool_t *cmd_mempool; + +static char cmd_tokenizebuffer[CMD_TOKENIZELENGTH]; +static int cmd_tokenizebufferpos = 0; + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" +============ +*/ +static void Cmd_Wait_f (void) +{ + cmd_wait = true; +} + +typedef struct cmddeferred_s +{ + struct cmddeferred_s *next; + char *value; + double time; +} cmddeferred_t; + +static cmddeferred_t *cmd_deferred_list = NULL; + +/* +============ +Cmd_Defer_f + +Cause a command to be executed after a delay. +============ +*/ +static void Cmd_Defer_f (void) +{ + if(Cmd_Argc() == 1) + { + double time = Sys_DoubleTime(); + cmddeferred_t *next = cmd_deferred_list; + if(!next) + Con_Printf("No commands are pending.\n"); + while(next) + { + Con_Printf("-> In %9.2f: %s\n", next->time-time, next->value); + next = next->next; + } + } else if(Cmd_Argc() == 2 && !strcasecmp("clear", Cmd_Argv(1))) + { + while(cmd_deferred_list) + { + cmddeferred_t *cmd = cmd_deferred_list; + cmd_deferred_list = cmd->next; + Mem_Free(cmd->value); + Mem_Free(cmd); + } + } else if(Cmd_Argc() == 3) + { + const char *value = Cmd_Argv(2); + cmddeferred_t *defcmd = (cmddeferred_t*)Mem_Alloc(tempmempool, sizeof(*defcmd)); + size_t len = strlen(value); + + defcmd->time = Sys_DoubleTime() + atof(Cmd_Argv(1)); + defcmd->value = (char*)Mem_Alloc(tempmempool, len+1); + memcpy(defcmd->value, value, len+1); + defcmd->next = NULL; + + if(cmd_deferred_list) + { + cmddeferred_t *next = cmd_deferred_list; + while(next->next) + next = next->next; + next->next = defcmd; + } else + cmd_deferred_list = defcmd; + /* Stupid me... this changes the order... so commands with the same delay go blub :S + defcmd->next = cmd_deferred_list; + cmd_deferred_list = defcmd;*/ + } else { + Con_Printf("usage: defer \n" + " defer clear\n"); + return; + } +} + +/* +============ +Cmd_Centerprint_f + +Print something to the center of the screen using SCR_Centerprint +============ +*/ +static void Cmd_Centerprint_f (void) +{ + char msg[MAX_INPUTLINE]; + unsigned int i, c, p; + c = Cmd_Argc(); + if(c >= 2) + { + strlcpy(msg, Cmd_Argv(1), sizeof(msg)); + for(i = 2; i < c; ++i) + { + strlcat(msg, " ", sizeof(msg)); + strlcat(msg, Cmd_Argv(i), sizeof(msg)); + } + c = strlen(msg); + for(p = 0, i = 0; i < c; ++i) + { + if(msg[i] == '\\') + { + if(msg[i+1] == 'n') + msg[p++] = '\n'; + else if(msg[i+1] == '\\') + msg[p++] = '\\'; + else { + msg[p++] = '\\'; + msg[p++] = msg[i+1]; + } + ++i; + } else { + msg[p++] = msg[i]; + } + } + msg[p] = '\0'; + SCR_CenterPrint(msg); + } +} + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +static sizebuf_t cmd_text; +static unsigned char cmd_text_buf[CMDBUFSIZE]; + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer +============ +*/ +void Cbuf_AddText (const char *text) +{ + int l; + + l = (int)strlen (text); + + if (cmd_text.cursize + l >= cmd_text.maxsize) + { + Con_Print("Cbuf_AddText: overflow\n"); + return; + } + + SZ_Write (&cmd_text, (const unsigned char *)text, (int)strlen (text)); +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +FIXME: actually change the command buffer to do less copying +============ +*/ +void Cbuf_InsertText (const char *text) +{ + char *temp; + int templen; + + // copy off any commands still remaining in the exec buffer + templen = cmd_text.cursize; + if (templen) + { + temp = (char *)Mem_Alloc (tempmempool, templen); + memcpy (temp, cmd_text.data, templen); + SZ_Clear (&cmd_text); + } + else + temp = NULL; + + // add the entire text of the file + Cbuf_AddText (text); + + // add the copied off data + if (temp != NULL) + { + SZ_Write (&cmd_text, (const unsigned char *)temp, templen); + Mem_Free (temp); + } +} + +/* +============ +Cbuf_Execute_Deferred --blub +============ +*/ +void Cbuf_Execute_Deferred (void) +{ + cmddeferred_t *cmd, *prev; + double time = Sys_DoubleTime(); + prev = NULL; + cmd = cmd_deferred_list; + while(cmd) + { + if(cmd->time <= time) + { + Cbuf_AddText(cmd->value); + Cbuf_AddText(";\n"); + Mem_Free(cmd->value); + + if(prev) { + prev->next = cmd->next; + Mem_Free(cmd); + cmd = prev->next; + } else { + cmd_deferred_list = cmd->next; + Mem_Free(cmd); + cmd = cmd_deferred_list; + } + continue; + } + prev = cmd; + cmd = cmd->next; + } +} + +/* +============ +Cbuf_Execute +============ +*/ +static void Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ); +void Cbuf_Execute (void) +{ + int i; + char *text; + char line[MAX_INPUTLINE]; + char preprocessed[MAX_INPUTLINE]; + char *firstchar; + qboolean quotes; + char *comment; + + // LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes + cmd_tokenizebufferpos = 0; + + Cbuf_Execute_Deferred(); + while (cmd_text.cursize) + { +// find a \n or ; line break + text = (char *)cmd_text.data; + + quotes = false; + comment = NULL; + for (i=0 ; i < cmd_text.cursize ; i++) + { + if(!comment) + { + if (text[i] == '"') + quotes = !quotes; + + if(quotes) + { + // make sure i doesn't get > cursize which causes a negative + // size in memmove, which is fatal --blub + if (i < (cmd_text.cursize-1) && (text[i] == '\\' && (text[i+1] == '"' || text[i+1] == '\\'))) + i++; + } + else + { + if(text[i] == '/' && text[i + 1] == '/' && (i == 0 || ISWHITESPACE(text[i-1]))) + comment = &text[i]; + if(text[i] == ';') + break; // don't break if inside a quoted string or comment + } + } + + if (text[i] == '\r' || text[i] == '\n') + break; + } + + // better than CRASHING on overlong input lines that may SOMEHOW enter the buffer + if(i >= MAX_INPUTLINE) + { + Con_Printf("Warning: console input buffer had an overlong line. Ignored.\n"); + line[0] = 0; + } + else + { + memcpy (line, text, comment ? (comment - text) : i); + line[comment ? (comment - text) : i] = 0; + } + +// delete the text from the command buffer and move remaining commands down +// this is necessary because commands (exec, alias) can insert data at the +// beginning of the text buffer + + if (i == cmd_text.cursize) + cmd_text.cursize = 0; + else + { + i++; + cmd_text.cursize -= i; + memmove (cmd_text.data, text+i, cmd_text.cursize); + } + +// execute the command line + firstchar = line; + while(*firstchar && ISWHITESPACE(*firstchar)) + ++firstchar; + if( + (strncmp(firstchar, "alias", 5) || !ISWHITESPACE(firstchar[5])) + && + (strncmp(firstchar, "bind", 4) || !ISWHITESPACE(firstchar[4])) + && + (strncmp(firstchar, "in_bind", 7) || !ISWHITESPACE(firstchar[7])) + ) + { + Cmd_PreprocessString( line, preprocessed, sizeof(preprocessed), NULL ); + Cmd_ExecuteString (preprocessed, src_command); + } + else + { + Cmd_ExecuteString (line, src_command); + } + + if (cmd_wait) + { // skip out while text still remains in buffer, leaving it + // for next frame + cmd_wait = false; + break; + } + } +} + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + +/* +=============== +Cmd_StuffCmds_f + +Adds command line parameters as script statements +Commands lead with a +, and continue until a - or another + +quake +prog jctest.qp +cmd amlev1 +quake -nosound +cmd amlev1 +=============== +*/ +qboolean host_stuffcmdsrun = false; +void Cmd_StuffCmds_f (void) +{ + int i, j, l; + // this is for all commandline options combined (and is bounds checked) + char build[MAX_INPUTLINE]; + + if (Cmd_Argc () != 1) + { + Con_Print("stuffcmds : execute command line parameters\n"); + return; + } + + // no reason to run the commandline arguments twice + if (host_stuffcmdsrun) + return; + + host_stuffcmdsrun = true; + build[0] = 0; + l = 0; + for (i = 0;i < com_argc;i++) + { + if (com_argv[i] && com_argv[i][0] == '+' && (com_argv[i][1] < '0' || com_argv[i][1] > '9') && l + strlen(com_argv[i]) - 1 <= sizeof(build) - 1) + { + j = 1; + while (com_argv[i][j]) + build[l++] = com_argv[i][j++]; + i++; + for (;i < com_argc;i++) + { + if (!com_argv[i]) + continue; + if ((com_argv[i][0] == '+' || com_argv[i][0] == '-') && (com_argv[i][1] < '0' || com_argv[i][1] > '9')) + break; + if (l + strlen(com_argv[i]) + 4 > sizeof(build) - 1) + break; + build[l++] = ' '; + if (strchr(com_argv[i], ' ')) + build[l++] = '\"'; + for (j = 0;com_argv[i][j];j++) + build[l++] = com_argv[i][j]; + if (strchr(com_argv[i], ' ')) + build[l++] = '\"'; + } + build[l++] = '\n'; + i--; + } + } + // now terminate the combined string and prepend it to the command buffer + // we already reserved space for the terminator + build[l++] = 0; + Cbuf_InsertText (build); +} + +static void Cmd_Exec(const char *filename) +{ + char *f; + + if (!strcmp(filename, "config.cfg")) + { + filename = CONFIGFILENAME; + if (COM_CheckParm("-noconfig")) + return; // don't execute config.cfg + } + + f = (char *)FS_LoadFile (filename, tempmempool, false, NULL); + if (!f) + { + Con_Printf("couldn't exec %s\n",filename); + return; + } + Con_Printf("execing %s\n",filename); + + // if executing default.cfg for the first time, lock the cvar defaults + // it may seem backwards to insert this text BEFORE the default.cfg + // but Cbuf_InsertText inserts before, so this actually ends up after it. + if (strlen(filename) >= 11 && !strcmp(filename + strlen(filename) - 11, "default.cfg")) + Cbuf_InsertText("\ncvar_lockdefaults\n"); + + // insert newline after the text to make sure the last line is terminated (some text editors omit the trailing newline) + // (note: insertion order here is backwards from execution order, so this adds it after the text, by calling it before...) + Cbuf_InsertText ("\n"); + Cbuf_InsertText (f); + Mem_Free(f); + + // special defaults for specific games go here, these execute before default.cfg + // Nehahra pushable crates malfunction in some levels if this is on + // Nehahra NPC AI is confused by blowupfallenzombies + if (gamemode == GAME_NEHAHRA) + Cbuf_InsertText("\nsv_gameplayfix_upwardvelocityclearsongroundflag 0\nsv_gameplayfix_blowupfallenzombies 0\n\n"); + // hipnotic mission pack has issues in their 'friendly monster' ai, which seem to attempt to attack themselves for some reason when findradius() returns non-solid entities. + // hipnotic mission pack has issues with bobbing water entities 'jittering' between different heights on alternate frames at the default 0.0138889 ticrate, 0.02 avoids this issue + // hipnotic mission pack has issues in their proximity mine sticking code, which causes them to bounce off. + if (gamemode == GAME_HIPNOTIC) + Cbuf_InsertText("\nsv_gameplayfix_blowupfallenzombies 0\nsys_ticrate 0.02\nsv_gameplayfix_slidemoveprojectiles 0\n\n"); + // rogue mission pack has a guardian boss that does not wake up if findradius returns one of the entities around its spawn area + if (gamemode == GAME_ROGUE) + Cbuf_InsertText("\nsv_gameplayfix_findradiusdistancetobox 0\n\n"); + if (gamemode == GAME_NEXUIZ) + Cbuf_InsertText("\nsv_gameplayfix_q2airaccelerate 1\nsv_gameplayfix_stepmultipletimes 1\n\n"); + if (gamemode == GAME_TENEBRAE) + Cbuf_InsertText("\nr_shadow_gloss 2\nr_shadow_bumpscale_basetexture 4\n\n"); +} + +/* +=============== +Cmd_Exec_f +=============== +*/ +static void Cmd_Exec_f (void) +{ + fssearch_t *s; + int i; + + if (Cmd_Argc () != 2) + { + Con_Print("exec : execute a script file\n"); + return; + } + + s = FS_Search(Cmd_Argv(1), true, true); + if(!s || !s->numfilenames) + { + Con_Printf("couldn't exec %s\n",Cmd_Argv(1)); + return; + } + + for(i = 0; i < s->numfilenames; ++i) + Cmd_Exec(s->filenames[i]); + + FS_FreeSearch(s); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +static void Cmd_Echo_f (void) +{ + int i; + + for (i=1 ; i - toggles between 0 and 1\n toggle - toggles between 0 and \n toggle [string 1] [string 2]...[string n] - cycles through all strings\n"); + else + { // Correct Arguments Specified + // Acquire Potential CVar + cvar_t* cvCVar = Cvar_FindVar( Cmd_Argv(1) ); + + if(cvCVar != NULL) + { // Valid CVar + if(nNumArgs == 2) + { // Default Usage + if(cvCVar->integer) + Cvar_SetValueQuick(cvCVar, 0); + else + Cvar_SetValueQuick(cvCVar, 1); + } + else + if(nNumArgs == 3) + { // 0 and Specified Usage + if(cvCVar->integer == atoi(Cmd_Argv(2) ) ) + // CVar is Specified Value; // Reset to 0 + Cvar_SetValueQuick(cvCVar, 0); + else + if(cvCVar->integer == 0) + // CVar is 0; Specify Value + Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); + else + // CVar does not match; Reset to 0 + Cvar_SetValueQuick(cvCVar, 0); + } + else + { // Variable Values Specified + int nCnt; + int bFound = 0; + + for(nCnt = 2; nCnt < nNumArgs; nCnt++) + { // Cycle through Values + if( strcmp(cvCVar->string, Cmd_Argv(nCnt) ) == 0) + { // Current Value Located; Increment to Next + if( (nCnt + 1) == nNumArgs) + // Max Value Reached; Reset + Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); + else + // Next Value + Cvar_SetQuick(cvCVar, Cmd_Argv(nCnt + 1) ); + + // End Loop + nCnt = nNumArgs; + // Assign Found + bFound = 1; + } + } + if(!bFound) + // Value not Found; Reset to Original + Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); + } + + } + else + { // Invalid CVar + Con_Printf("ERROR : CVar '%s' not found\n", Cmd_Argv(1) ); + } + } +} + +/* +=============== +Cmd_Alias_f + +Creates a new command that executes a command string (possibly ; seperated) +=============== +*/ +static void Cmd_Alias_f (void) +{ + cmdalias_t *a; + char cmd[MAX_INPUTLINE]; + int i, c; + const char *s; + size_t alloclen; + + if (Cmd_Argc() == 1) + { + Con_Print("Current alias commands:\n"); + for (a = cmd_alias ; a ; a=a->next) + Con_Printf("%s : %s", a->name, a->value); + return; + } + + s = Cmd_Argv(1); + if (strlen(s) >= MAX_ALIAS_NAME) + { + Con_Print("Alias name is too long\n"); + return; + } + + // if the alias already exists, reuse it + for (a = cmd_alias ; a ; a=a->next) + { + if (!strcmp(s, a->name)) + { + Z_Free (a->value); + break; + } + } + + if (!a) + { + cmdalias_t *prev, *current; + + a = (cmdalias_t *)Z_Malloc (sizeof(cmdalias_t)); + strlcpy (a->name, s, sizeof (a->name)); + // insert it at the right alphanumeric position + for( prev = NULL, current = cmd_alias ; current && strcmp( current->name, a->name ) < 0 ; prev = current, current = current->next ) + ; + if( prev ) { + prev->next = a; + } else { + cmd_alias = a; + } + a->next = current; + } + + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + c = Cmd_Argc(); + for (i=2 ; i < c ; i++) + { + if (i != 2) + strlcat (cmd, " ", sizeof (cmd)); + strlcat (cmd, Cmd_Argv(i), sizeof (cmd)); + } + strlcat (cmd, "\n", sizeof (cmd)); + + alloclen = strlen (cmd) + 1; + if(alloclen >= 2) + cmd[alloclen - 2] = '\n'; // to make sure a newline is appended even if too long + a->value = (char *)Z_Malloc (alloclen); + memcpy (a->value, cmd, alloclen); +} + +/* +=============== +Cmd_UnAlias_f + +Remove existing aliases. +=============== +*/ +static void Cmd_UnAlias_f (void) +{ + cmdalias_t *a, *p; + int i; + const char *s; + + if(Cmd_Argc() == 1) + { + Con_Print("unalias: Usage: unalias alias1 [alias2 ...]\n"); + return; + } + + for(i = 1; i < Cmd_Argc(); ++i) + { + s = Cmd_Argv(i); + p = NULL; + for(a = cmd_alias; a; p = a, a = a->next) + { + if(!strcmp(s, a->name)) + { + if (a->initstate) // we can not remove init aliases + continue; + if(a == cmd_alias) + cmd_alias = a->next; + if(p) + p->next = a->next; + Z_Free(a->value); + Z_Free(a); + break; + } + } + if(!a) + Con_Printf("unalias: %s alias not found\n", s); + } +} + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + const char *name; + const char *description; + xcommand_t consolefunction; + xcommand_t clientfunction; + qboolean csqcfunc; + qboolean initstate; // indicates this command existed at init +} cmd_function_t; + +static int cmd_argc; +static const char *cmd_argv[MAX_ARGS]; +static const char *cmd_null_string = ""; +static const char *cmd_args; +cmd_source_t cmd_source; + + +static cmd_function_t *cmd_functions; // possible commands to execute + +static const char *Cmd_GetDirectCvarValue(const char *varname, cmdalias_t *alias, qboolean *is_multiple) +{ + cvar_t *cvar; + long argno; + char *endptr; + + if(is_multiple) + *is_multiple = false; + + if(!varname || !*varname) + return NULL; + + if(alias) + { + if(!strcmp(varname, "*")) + { + if(is_multiple) + *is_multiple = true; + return Cmd_Args(); + } + else if(!strcmp(varname, "#")) + { + return va("%d", Cmd_Argc()); + } + else if(varname[strlen(varname) - 1] == '-') + { + argno = strtol(varname, &endptr, 10); + if(endptr == varname + strlen(varname) - 1) + { + // whole string is a number, apart from the - + const char *p = Cmd_Args(); + for(; argno > 1; --argno) + if(!COM_ParseToken_Console(&p)) + break; + if(p) + { + if(is_multiple) + *is_multiple = true; + + // kill pre-argument whitespace + for (;*p && ISWHITESPACE(*p);p++) + ; + + return p; + } + } + } + else + { + argno = strtol(varname, &endptr, 10); + if(*endptr == 0) + { + // whole string is a number + // NOTE: we already made sure we don't have an empty cvar name! + if(argno >= 0 && argno < Cmd_Argc()) + return Cmd_Argv(argno); + } + } + } + + if((cvar = Cvar_FindVar(varname)) && !(cvar->flags & CVAR_PRIVATE)) + return cvar->string; + + return NULL; +} + +qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qboolean putquotes) +{ + qboolean quote_quot = !!strchr(quoteset, '"'); + qboolean quote_backslash = !!strchr(quoteset, '\\'); + qboolean quote_dollar = !!strchr(quoteset, '$'); + + if(putquotes) + { + if(outlen <= 2) + { + *out++ = 0; + return false; + } + *out++ = '"'; --outlen; + --outlen; + } + + while(*in) + { + if(*in == '"' && quote_quot) + { + if(outlen <= 2) + goto fail; + *out++ = '\\'; --outlen; + *out++ = '"'; --outlen; + } + else if(*in == '\\' && quote_backslash) + { + if(outlen <= 2) + goto fail; + *out++ = '\\'; --outlen; + *out++ = '\\'; --outlen; + } + else if(*in == '$' && quote_dollar) + { + if(outlen <= 2) + goto fail; + *out++ = '$'; --outlen; + *out++ = '$'; --outlen; + } + else + { + if(outlen <= 1) + goto fail; + *out++ = *in; --outlen; + } + ++in; + } + if(putquotes) + *out++ = '"'; + *out++ = 0; + return true; +fail: + if(putquotes) + *out++ = '"'; + *out++ = 0; + return false; +} + +static const char *Cmd_GetCvarValue(const char *var, size_t varlen, cmdalias_t *alias) +{ + static char varname[MAX_INPUTLINE]; + static char varval[MAX_INPUTLINE]; + const char *varstr; + char *varfunc; +static char asis[] = "asis"; // just to suppress const char warnings + + if(varlen >= MAX_INPUTLINE) + varlen = MAX_INPUTLINE - 1; + memcpy(varname, var, varlen); + varname[varlen] = 0; + varfunc = strchr(varname, ' '); + + if(varfunc) + { + *varfunc = 0; + ++varfunc; + } + + if(*var == 0) + { + // empty cvar name? + return NULL; + } + + varstr = NULL; + + if(varname[0] == '$') + varstr = Cmd_GetDirectCvarValue(Cmd_GetDirectCvarValue(varname + 1, alias, NULL), alias, NULL); + else + { + qboolean is_multiple = false; + // Exception: $* and $n- don't use the quoted form by default + varstr = Cmd_GetDirectCvarValue(varname, alias, &is_multiple); + if(is_multiple) + if(!varfunc) + varfunc = asis; + } + + if(!varstr) + { + if(alias) + Con_Printf("Warning: Could not expand $%s in alias %s\n", varname, alias->name); + else + Con_Printf("Warning: Could not expand $%s\n", varname); + return NULL; + } + + if(!varfunc || !strcmp(varfunc, "q")) // note: quoted form is default, use "asis" to override! + { + // quote it so it can be used inside double quotes + // we just need to replace " by \", and of course, double backslashes + Cmd_QuoteString(varval, sizeof(varval), varstr, "\"\\", false); + return varval; + } + else if(!strcmp(varfunc, "asis")) + { + return varstr; + } + else + Con_Printf("Unknown variable function %s\n", varfunc); + + return varstr; +} + +/* +Cmd_PreprocessString + +Preprocesses strings and replaces $*, $param#, $cvar accordingly. Also strips comments. +*/ +static void Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ) { + const char *in; + size_t eat, varlen; + unsigned outlen; + const char *val; + + // don't crash if there's no room in the outtext buffer + if( maxoutlen == 0 ) { + return; + } + maxoutlen--; // because of \0 + + in = intext; + outlen = 0; + + while( *in && outlen < maxoutlen ) { + if( *in == '$' ) { + // this is some kind of expansion, see what comes after the $ + in++; + + // The console does the following preprocessing: + // + // - $$ is transformed to a single dollar sign. + // - $var or ${var} are expanded to the contents of the named cvar, + // with quotation marks and backslashes quoted so it can safely + // be used inside quotation marks (and it should always be used + // that way) + // - ${var asis} inserts the cvar value as is, without doing this + // quoting + // - prefix the cvar name with a dollar sign to do indirection; + // for example, if $x has the value timelimit, ${$x} will return + // the value of $timelimit + // - when expanding an alias, the special variable name $* refers + // to all alias parameters, and a number refers to that numbered + // alias parameter, where the name of the alias is $0, the first + // parameter is $1 and so on; as a special case, $* inserts all + // parameters, without extra quoting, so one can use $* to just + // pass all parameters around. All parameters starting from $n + // can be referred to as $n- (so $* is equivalent to $1-). + // + // Note: when expanding an alias, cvar expansion is done in the SAME step + // as alias expansion so that alias parameters or cvar values containing + // dollar signs have no unwanted bad side effects. However, this needs to + // be accounted for when writing complex aliases. For example, + // alias foo "set x NEW; echo $x" + // actually expands to + // "set x NEW; echo OLD" + // and will print OLD! To work around this, use a second alias: + // alias foo "set x NEW; foo2" + // alias foo2 "echo $x" + // + // Also note: lines starting with alias are exempt from cvar expansion. + // If you want cvar expansion, write "alias" instead: + // + // set x 1 + // alias foo "echo $x" + // "alias" bar "echo $x" + // set x 2 + // + // foo will print 2, because the variable $x will be expanded when the alias + // gets expanded. bar will print 1, because the variable $x was expanded + // at definition time. foo can be equivalently defined as + // + // "alias" foo "echo $$x" + // + // because at definition time, $$ will get replaced to a single $. + + if( *in == '$' ) { + val = "$"; + eat = 1; + } else if(*in == '{') { + varlen = strcspn(in + 1, "}"); + if(in[varlen + 1] == '}') + { + val = Cmd_GetCvarValue(in + 1, varlen, alias); + eat = varlen + 2; + } + else + { + // ran out of data? + val = NULL; + eat = varlen + 1; + } + } else { + varlen = strspn(in, "#*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-"); + val = Cmd_GetCvarValue(in, varlen, alias); + eat = varlen; + } + if(val) + { + // insert the cvar value + while(*val && outlen < maxoutlen) + outtext[outlen++] = *val++; + in += eat; + } + else + { + // copy the unexpanded text + outtext[outlen++] = '$'; + while(eat && outlen < maxoutlen) + { + outtext[outlen++] = *in++; + --eat; + } + } + } + else + outtext[outlen++] = *in++; + } + outtext[outlen] = 0; +} + +/* +============ +Cmd_ExecuteAlias + +Called for aliases and fills in the alias into the cbuffer +============ +*/ +static void Cmd_ExecuteAlias (cmdalias_t *alias) +{ + static char buffer[ MAX_INPUTLINE ]; + static char buffer2[ MAX_INPUTLINE ]; + Cmd_PreprocessString( alias->value, buffer, sizeof(buffer) - 2, alias ); + // insert at start of command buffer, so that aliases execute in order + // (fixes bug introduced by Black on 20050705) + + // Note: Cbuf_PreprocessString will be called on this string AGAIN! So we + // have to make sure that no second variable expansion takes place, otherwise + // alias parameters containing dollar signs can have bad effects. + Cmd_QuoteString(buffer2, sizeof(buffer2), buffer, "$", false); + Cbuf_InsertText( buffer2 ); +} + +/* +======== +Cmd_List + + CmdList Added by EvilTypeGuy eviltypeguy@qeradiant.com + Thanks to Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ + +======== +*/ +static void Cmd_List_f (void) +{ + cmd_function_t *cmd; + const char *partial; + size_t len; + int count; + qboolean ispattern; + + if (Cmd_Argc() > 1) + { + partial = Cmd_Argv (1); + len = strlen(partial); + } + else + { + partial = NULL; + len = 0; + } + + ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); + + count = 0; + for (cmd = cmd_functions; cmd; cmd = cmd->next) + { + if (partial && (ispattern ? !matchpattern_with_separator(cmd->name, partial, false, "", false) : strncmp(partial, cmd->name, len))) + continue; + Con_Printf("%s : %s\n", cmd->name, cmd->description); + count++; + } + + if (len) + { + if(ispattern) + Con_Printf("%i Command%s matching \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); + else + Con_Printf("%i Command%s beginning with \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); + } + else + Con_Printf("%i Command%s\n\n", count, (count > 1) ? "s" : ""); +} + +static void Cmd_Apropos_f(void) +{ + cmd_function_t *cmd; + cvar_t *cvar; + cmdalias_t *alias; + const char *partial; + int count; + qboolean ispattern; + + if (Cmd_Argc() > 1) + partial = Cmd_Args(); + else + { + Con_Printf("usage: apropos \n"); + return; + } + + ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); + if(!ispattern) + partial = va("*%s*", partial); + + count = 0; + for (cvar = cvar_vars; cvar; cvar = cvar->next) + { + if (!matchpattern_with_separator(cvar->name, partial, true, "", false)) + if (!matchpattern_with_separator(cvar->description, partial, true, "", false)) + continue; + Con_Printf ("cvar ^3%s^7 is \"%s\" [\"%s\"] %s\n", cvar->name, cvar->string, cvar->defstring, cvar->description); + count++; + } + for (cmd = cmd_functions; cmd; cmd = cmd->next) + { + if (!matchpattern_with_separator(cmd->name, partial, true, "", false)) + if (!matchpattern_with_separator(cmd->description, partial, true, "", false)) + continue; + Con_Printf("command ^2%s^7: %s\n", cmd->name, cmd->description); + count++; + } + for (alias = cmd_alias; alias; alias = alias->next) + { + // procede here a bit differently as an alias value always got a final \n + if (!matchpattern_with_separator(alias->name, partial, true, "", false)) + if (!matchpattern_with_separator(alias->value, partial, true, "\n", false)) // when \n is as separator wildcards don't match it + continue; + Con_Printf("alias ^5%s^7: %s", alias->name, alias->value); // do not print an extra \n + count++; + } + Con_Printf("%i result%s\n\n", count, (count > 1) ? "s" : ""); +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) +{ + cmd_mempool = Mem_AllocPool("commands", 0, NULL); + // space for commands and script files + cmd_text.data = cmd_text_buf; + cmd_text.maxsize = sizeof(cmd_text_buf); + cmd_text.cursize = 0; +} + +void Cmd_Init_Commands (void) +{ +// +// register our commands +// + Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f, "execute commandline parameters (must be present in quake.rc script)"); + Cmd_AddCommand ("exec",Cmd_Exec_f, "execute a script file"); + Cmd_AddCommand ("echo",Cmd_Echo_f, "print a message to the console (useful in scripts)"); + Cmd_AddCommand ("alias",Cmd_Alias_f, "create a script function (parameters are passed in as $X (being X a number), $* for all parameters, $X- for all parameters starting from $X). Without arguments show the list of all alias"); + Cmd_AddCommand ("unalias",Cmd_UnAlias_f, "remove an alias"); + Cmd_AddCommand ("cmd", Cmd_ForwardToServer, "send a console commandline to the server (used by some mods)"); + Cmd_AddCommand ("wait", Cmd_Wait_f, "make script execution wait for next rendered frame"); + Cmd_AddCommand ("set", Cvar_Set_f, "create or change the value of a console variable"); + Cmd_AddCommand ("seta", Cvar_SetA_f, "create or change the value of a console variable that will be saved to config.cfg"); + Cmd_AddCommand ("unset", Cvar_Del_f, "delete a cvar (does not work for static ones like _cl_name, or read-only ones)"); +#ifdef FILLALLCVARSWITHRUBBISH + Cmd_AddCommand ("fillallcvarswithrubbish", Cvar_FillAll_f, "fill all cvars with a specified number of characters to provoke buffer overruns"); +#endif /* FILLALLCVARSWITHRUBBISH */ + + // 2000-01-09 CmdList, CvarList commands By Matthias "Maddes" Buecher + // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com + Cmd_AddCommand ("cmdlist", Cmd_List_f, "lists all console commands beginning with the specified prefix or matching the specified wildcard pattern"); + Cmd_AddCommand ("cvarlist", Cvar_List_f, "lists all console variables beginning with the specified prefix or matching the specified wildcard pattern"); + Cmd_AddCommand ("apropos", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description"); + + Cmd_AddCommand ("cvar_lockdefaults", Cvar_LockDefaults_f, "stores the current values of all cvars into their default values, only used once during startup after parsing default.cfg"); + Cmd_AddCommand ("cvar_resettodefaults_all", Cvar_ResetToDefaults_All_f, "sets all cvars to their locked default values"); + Cmd_AddCommand ("cvar_resettodefaults_nosaveonly", Cvar_ResetToDefaults_NoSaveOnly_f, "sets all non-saved cvars to their locked default values (variables that will not be saved to config.cfg)"); + Cmd_AddCommand ("cvar_resettodefaults_saveonly", Cvar_ResetToDefaults_SaveOnly_f, "sets all saved cvars to their locked default values (variables that will be saved to config.cfg)"); + + Cmd_AddCommand ("cprint", Cmd_Centerprint_f, "print something at the screen center"); + Cmd_AddCommand ("defer", Cmd_Defer_f, "execute a command in the future"); + + // DRESK - 5/14/06 + // Support Doom3-style Toggle Command + Cmd_AddCommand( "toggle", Cmd_Toggle_f, "toggles a console variable's values (use for more info)"); +} + +/* +============ +Cmd_Shutdown +============ +*/ +void Cmd_Shutdown(void) +{ + Mem_FreePool(&cmd_mempool); +} + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc (void) +{ + return cmd_argc; +} + +/* +============ +Cmd_Argv +============ +*/ +const char *Cmd_Argv (int arg) +{ + if (arg >= cmd_argc ) + return cmd_null_string; + return cmd_argv[arg]; +} + +/* +============ +Cmd_Args +============ +*/ +const char *Cmd_Args (void) +{ + return cmd_args; +} + + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +============ +*/ +// AK: This function should only be called from ExcuteString because the current design is a bit of an hack +static void Cmd_TokenizeString (const char *text) +{ + int l; + + cmd_argc = 0; + cmd_args = NULL; + + while (1) + { + // skip whitespace up to a /n + while (*text && ISWHITESPACE(*text) && *text != '\r' && *text != '\n') + text++; + + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + if (*text == '\n' || *text == '\r') + { + // a newline separates commands in the buffer + if (*text == '\r' && text[1] == '\n') + text++; + text++; + break; + } + + if (!*text) + return; + + if (cmd_argc == 1) + cmd_args = text; + + if (!COM_ParseToken_Console(&text)) + return; + + if (cmd_argc < MAX_ARGS) + { + l = (int)strlen(com_token) + 1; + if (cmd_tokenizebufferpos + l > CMD_TOKENIZELENGTH) + { + Con_Printf("Cmd_TokenizeString: ran out of %i character buffer space for command arguements\n", CMD_TOKENIZELENGTH); + break; + } + memcpy (cmd_tokenizebuffer + cmd_tokenizebufferpos, com_token, l); + cmd_argv[cmd_argc] = cmd_tokenizebuffer + cmd_tokenizebufferpos; + cmd_tokenizebufferpos += l; + cmd_argc++; + } + } +} + + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand_WithClientCommand (const char *cmd_name, xcommand_t consolefunction, xcommand_t clientfunction, const char *description) +{ + cmd_function_t *cmd; + cmd_function_t *prev, *current; + +// fail if the command is a variable name + if (Cvar_FindVar( cmd_name )) + { + Con_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name); + return; + } + +// fail if the command already exists + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (!strcmp (cmd_name, cmd->name)) + { + if (consolefunction || clientfunction) + { + Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name); + return; + } + else //[515]: csqc + { + cmd->csqcfunc = true; + return; + } + } + } + + cmd = (cmd_function_t *)Mem_Alloc(cmd_mempool, sizeof(cmd_function_t)); + cmd->name = cmd_name; + cmd->consolefunction = consolefunction; + cmd->clientfunction = clientfunction; + cmd->description = description; + if(!consolefunction && !clientfunction) //[515]: csqc + cmd->csqcfunc = true; + cmd->next = cmd_functions; + +// insert it at the right alphanumeric position + for( prev = NULL, current = cmd_functions ; current && strcmp( current->name, cmd->name ) < 0 ; prev = current, current = current->next ) + ; + if( prev ) { + prev->next = cmd; + } else { + cmd_functions = cmd; + } + cmd->next = current; +} + +void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *description) +{ + Cmd_AddCommand_WithClientCommand (cmd_name, function, NULL, description); +} + +/* +============ +Cmd_Exists +============ +*/ +qboolean Cmd_Exists (const char *cmd_name) +{ + cmd_function_t *cmd; + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + if (!strcmp (cmd_name,cmd->name)) + return true; + + return false; +} + + +/* +============ +Cmd_CompleteCommand +============ +*/ +const char *Cmd_CompleteCommand (const char *partial) +{ + cmd_function_t *cmd; + size_t len; + + len = strlen(partial); + + if (!len) + return NULL; + +// check functions + for (cmd = cmd_functions; cmd; cmd = cmd->next) + if (!strncasecmp(partial, cmd->name, len)) + return cmd->name; + + return NULL; +} + +/* + Cmd_CompleteCountPossible + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +int Cmd_CompleteCountPossible (const char *partial) +{ + cmd_function_t *cmd; + size_t len; + int h; + + h = 0; + len = strlen(partial); + + if (!len) + return 0; + + // Loop through the command list and count all partial matches + for (cmd = cmd_functions; cmd; cmd = cmd->next) + if (!strncasecmp(partial, cmd->name, len)) + h++; + + return h; +} + +/* + Cmd_CompleteBuildList + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +const char **Cmd_CompleteBuildList (const char *partial) +{ + cmd_function_t *cmd; + size_t len = 0; + size_t bpos = 0; + size_t sizeofbuf = (Cmd_CompleteCountPossible (partial) + 1) * sizeof (const char *); + const char **buf; + + len = strlen(partial); + buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); + // Loop through the alias list and print all matches + for (cmd = cmd_functions; cmd; cmd = cmd->next) + if (!strncasecmp(partial, cmd->name, len)) + buf[bpos++] = cmd->name; + + buf[bpos] = NULL; + return buf; +} + +// written by LordHavoc +void Cmd_CompleteCommandPrint (const char *partial) +{ + cmd_function_t *cmd; + size_t len = strlen(partial); + // Loop through the command list and print all matches + for (cmd = cmd_functions; cmd; cmd = cmd->next) + if (!strncasecmp(partial, cmd->name, len)) + Con_Printf("^2%s^7: %s\n", cmd->name, cmd->description); +} + +/* + Cmd_CompleteAlias + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +const char *Cmd_CompleteAlias (const char *partial) +{ + cmdalias_t *alias; + size_t len; + + len = strlen(partial); + + if (!len) + return NULL; + + // Check functions + for (alias = cmd_alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + return alias->name; + + return NULL; +} + +// written by LordHavoc +void Cmd_CompleteAliasPrint (const char *partial) +{ + cmdalias_t *alias; + size_t len = strlen(partial); + // Loop through the alias list and print all matches + for (alias = cmd_alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + Con_Printf("^5%s^7: %s", alias->name, alias->value); +} + + +/* + Cmd_CompleteAliasCountPossible + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +int Cmd_CompleteAliasCountPossible (const char *partial) +{ + cmdalias_t *alias; + size_t len; + int h; + + h = 0; + + len = strlen(partial); + + if (!len) + return 0; + + // Loop through the command list and count all partial matches + for (alias = cmd_alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + h++; + + return h; +} + +/* + Cmd_CompleteAliasBuildList + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +const char **Cmd_CompleteAliasBuildList (const char *partial) +{ + cmdalias_t *alias; + size_t len = 0; + size_t bpos = 0; + size_t sizeofbuf = (Cmd_CompleteAliasCountPossible (partial) + 1) * sizeof (const char *); + const char **buf; + + len = strlen(partial); + buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); + // Loop through the alias list and print all matches + for (alias = cmd_alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + buf[bpos++] = alias->name; + + buf[bpos] = NULL; + return buf; +} + +void Cmd_ClearCsqcFuncs (void) +{ + cmd_function_t *cmd; + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + cmd->csqcfunc = false; +} + +qboolean CL_VM_ConsoleCommand (const char *cmd); +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +FIXME: lookupnoadd the token to speed search? +============ +*/ +void Cmd_ExecuteString (const char *text, cmd_source_t src) +{ + int oldpos; + int found; + cmd_function_t *cmd; + cmdalias_t *a; + + oldpos = cmd_tokenizebufferpos; + cmd_source = src; + found = false; + + Cmd_TokenizeString (text); + +// execute the command line + if (!Cmd_Argc()) + { + cmd_tokenizebufferpos = oldpos; + return; // no tokens + } + +// check functions + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (!strcasecmp (cmd_argv[0],cmd->name)) + { + if (cmd->csqcfunc && CL_VM_ConsoleCommand (text)) //[515]: csqc + return; + switch (src) + { + case src_command: + if (cmd->consolefunction) + cmd->consolefunction (); + else if (cmd->clientfunction) + { + if (cls.state == ca_connected) + { + // forward remote commands to the server for execution + Cmd_ForwardToServer(); + } + else + Con_Printf("Can not send command \"%s\", not connected.\n", Cmd_Argv(0)); + } + else + Con_Printf("Command \"%s\" can not be executed\n", Cmd_Argv(0)); + found = true; + goto command_found; + case src_client: + if (cmd->clientfunction) + { + cmd->clientfunction (); + cmd_tokenizebufferpos = oldpos; + return; + } + break; + } + break; + } + } +command_found: + + // if it's a client command and no command was found, say so. + if (cmd_source == src_client) + { + Con_Printf("player \"%s\" tried to %s\n", host_client->name, text); + cmd_tokenizebufferpos = oldpos; + return; + } + +// check alias + for (a=cmd_alias ; a ; a=a->next) + { + if (!strcasecmp (cmd_argv[0], a->name)) + { + Cmd_ExecuteAlias(a); + cmd_tokenizebufferpos = oldpos; + return; + } + } + + if(found) // if the command was hooked and found, all is good + { + cmd_tokenizebufferpos = oldpos; + return; + } + +// check cvars + if (!Cvar_Command () && host_framecount > 0) + Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0)); + + cmd_tokenizebufferpos = oldpos; +} + + +/* +=================== +Cmd_ForwardStringToServer + +Sends an entire command string over to the server, unprocessed +=================== +*/ +void Cmd_ForwardStringToServer (const char *s) +{ + char temp[128]; + if (cls.state != ca_connected) + { + Con_Printf("Can't \"%s\", not connected\n", s); + return; + } + + if (!cls.netcon) + return; + + // LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my + // attention, it has been eradicated from here, its only (former) use in + // all of darkplaces. + if (cls.protocol == PROTOCOL_QUAKEWORLD) + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + else + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + if ((!strncmp(s, "say ", 4) || !strncmp(s, "say_team ", 9)) && cl_locs_enable.integer) + { + // say/say_team commands can replace % character codes with status info + while (*s) + { + if (*s == '%' && s[1]) + { + // handle proquake message macros + temp[0] = 0; + switch (s[1]) + { + case 'l': // current location + CL_Locs_FindLocationName(temp, sizeof(temp), cl.movement_origin); + break; + case 'h': // current health + dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_HEALTH]); + break; + case 'a': // current armor + dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ARMOR]); + break; + case 'x': // current rockets + dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ROCKETS]); + break; + case 'c': // current cells + dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_CELLS]); + break; + // silly proquake macros + case 'd': // loc at last death + CL_Locs_FindLocationName(temp, sizeof(temp), cl.lastdeathorigin); + break; + case 't': // current time + dpsnprintf(temp, sizeof(temp), "%.0f:%.0f", floor(cl.time / 60), cl.time - floor(cl.time / 60) * 60); + break; + case 'r': // rocket launcher status ("I have RL", "I need rockets", "I need RL") + if (!(cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER)) + dpsnprintf(temp, sizeof(temp), "I need RL"); + else if (!cl.stats[STAT_ROCKETS]) + dpsnprintf(temp, sizeof(temp), "I need rockets"); + else + dpsnprintf(temp, sizeof(temp), "I have RL"); + break; + case 'p': // powerup status (outputs "quad" "pent" and "eyes" according to status) + if (cl.stats[STAT_ITEMS] & IT_QUAD) + { + if (temp[0]) + strlcat(temp, " ", sizeof(temp)); + strlcat(temp, "quad", sizeof(temp)); + } + if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) + { + if (temp[0]) + strlcat(temp, " ", sizeof(temp)); + strlcat(temp, "pent", sizeof(temp)); + } + if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) + { + if (temp[0]) + strlcat(temp, " ", sizeof(temp)); + strlcat(temp, "eyes", sizeof(temp)); + } + break; + case 'w': // weapon status (outputs "SSG:NG:SNG:GL:RL:LG" with the text between : characters omitted if you lack the weapon) + if (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN) + strlcat(temp, "SSG", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_NAILGUN) + strlcat(temp, "NG", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN) + strlcat(temp, "SNG", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER) + strlcat(temp, "GL", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) + strlcat(temp, "RL", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_LIGHTNING) + strlcat(temp, "LG", sizeof(temp)); + break; + default: + // not a recognized macro, print it as-is... + temp[0] = s[0]; + temp[1] = s[1]; + temp[2] = 0; + break; + } + // write the resulting text + SZ_Write(&cls.netcon->message, (unsigned char *)temp, strlen(temp)); + s += 2; + continue; + } + MSG_WriteByte(&cls.netcon->message, *s); + s++; + } + MSG_WriteByte(&cls.netcon->message, 0); + } + else // any other command is passed on as-is + SZ_Write(&cls.netcon->message, (const unsigned char *)s, (int)strlen(s) + 1); +} + +/* +=================== +Cmd_ForwardToServer + +Sends the entire command line over to the server +=================== +*/ +void Cmd_ForwardToServer (void) +{ + const char *s; + if (!strcasecmp(Cmd_Argv(0), "cmd")) + { + // we want to strip off "cmd", so just send the args + s = Cmd_Argc() > 1 ? Cmd_Args() : ""; + } + else + { + // we need to keep the command name, so send Cmd_Argv(0), a space and then Cmd_Args() + s = va("%s %s", Cmd_Argv(0), Cmd_Argc() > 1 ? Cmd_Args() : ""); + } + // don't send an empty forward message if the user tries "cmd" by itself + if (!s || !*s) + return; + Cmd_ForwardStringToServer(s); +} + + +/* +================ +Cmd_CheckParm + +Returns the position (1 to argc-1) in the command's argument list +where the given parameter apears, or 0 if not present +================ +*/ + +int Cmd_CheckParm (const char *parm) +{ + int i; + + if (!parm) + { + Con_Printf ("Cmd_CheckParm: NULL"); + return 0; + } + + for (i = 1; i < Cmd_Argc (); i++) + if (!strcasecmp (parm, Cmd_Argv (i))) + return i; + + return 0; +} + + + +void Cmd_SaveInitState(void) +{ + cmd_function_t *f; + cmdalias_t *a; + for (f = cmd_functions;f;f = f->next) + f->initstate = true; + for (a = cmd_alias;a;a = a->next) + { + a->initstate = true; + a->initialvalue = Mem_strdup(zonemempool, a->value); + } + Cvar_SaveInitState(); +} + +void Cmd_RestoreInitState(void) +{ + cmd_function_t *f, **fp; + cmdalias_t *a, **ap; + for (fp = &cmd_functions;(f = *fp);) + { + if (f->initstate) + fp = &f->next; + else + { + // destroy this command, it didn't exist at init + Con_DPrintf("Cmd_RestoreInitState: Destroying command %s\n", f->name); + *fp = f->next; + Z_Free(f); + } + } + for (ap = &cmd_alias;(a = *ap);) + { + if (a->initstate) + { + // restore this alias, it existed at init + if (strcmp(a->value ? a->value : "", a->initialvalue ? a->initialvalue : "")) + { + Con_DPrintf("Cmd_RestoreInitState: Restoring alias %s\n", a->name); + if (a->value) + Z_Free(a->value); + a->value = Mem_strdup(zonemempool, a->initialvalue); + } + ap = &a->next; + } + else + { + // free this alias, it didn't exist at init... + Con_DPrintf("Cmd_RestoreInitState: Destroying alias %s\n", a->name); + *ap = a->next; + if (a->value) + Z_Free(a->value); + Z_Free(a); + } + } + Cvar_RestoreInitState(); +} diff --git a/misc/source/darkplaces-src/cmd.h b/misc/source/darkplaces-src/cmd.h new file mode 100644 index 00000000..cb96abaa --- /dev/null +++ b/misc/source/darkplaces-src/cmd.h @@ -0,0 +1,169 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// cmd.h -- Command buffer and command execution + +//=========================================================================== + +/* + +Any number of commands can be added in a frame, from several different sources. +Most commands come from either keybindings or console line input, but remote +servers can also send across commands and entire text files can be execed. + +The + command line options are also added to the command buffer. + +The game starts with a Cbuf_AddText ("exec quake.rc\n"); Cbuf_Execute (); + +*/ + +#ifndef CMD_H +#define CMD_H + +/// allocates an initial text buffer that will grow as needed +void Cbuf_Init (void); + +void Cmd_Init_Commands (void); + +void Cbuf_Shutdown (void); + +/*! as new commands are generated from the console or keybindings, + * the text is added to the end of the command buffer. + */ +void Cbuf_AddText (const char *text); + +/*! when a command wants to issue other commands immediately, the text is + * inserted at the beginning of the buffer, before any remaining unexecuted + * commands. + */ +void Cbuf_InsertText (const char *text); + +/*! Pulls off terminated lines of text from the command buffer and sends + * them through Cmd_ExecuteString. Stops when the buffer is empty. + * Normally called once per frame, but may be explicitly invoked. + * \note Do not call inside a command function! + */ +void Cbuf_Execute (void); + +//=========================================================================== + +/* + +Command execution takes a null terminated string, breaks it into tokens, +then searches for a command or variable that matches the first token. + +Commands can come from three sources, but the handler functions may choose +to dissallow the action or forward it to a remote server if the source is +not apropriate. + +*/ + +typedef void (*xcommand_t) (void); + +typedef enum +{ + src_client, ///< came in over a net connection as a clc_stringcmd + ///< host_client will be valid during this state. + src_command ///< from the command buffer +} cmd_source_t; + +extern cmd_source_t cmd_source; + +void Cmd_Init (void); +void Cmd_Shutdown (void); + +// called by Host_Init, this marks cvars, commands and aliases with their init values +void Cmd_SaveInitState (void); +// called by FS_GameDir_f, this restores cvars, commands and aliases to init values +void Cmd_RestoreInitState (void); + +void Cmd_AddCommand_WithClientCommand (const char *cmd_name, xcommand_t consolefunction, xcommand_t clientfunction, const char *description); +void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *description); +// called by the init functions of other parts of the program to +// register commands and functions to call for them. +// The cmd_name is referenced later, so it should not be in temp memory + +/// used by the cvar code to check for cvar / command name overlap +qboolean Cmd_Exists (const char *cmd_name); + +/// attempts to match a partial command for automatic command line completion +/// returns NULL if nothing fits +const char *Cmd_CompleteCommand (const char *partial); + +int Cmd_CompleteAliasCountPossible (const char *partial); + +const char **Cmd_CompleteAliasBuildList (const char *partial); + +int Cmd_CompleteCountPossible (const char *partial); + +const char **Cmd_CompleteBuildList (const char *partial); + +void Cmd_CompleteCommandPrint (const char *partial); + +const char *Cmd_CompleteAlias (const char *partial); + +void Cmd_CompleteAliasPrint (const char *partial); + +// Enhanced console completion by Fett erich@heintz.com + +// Added by EvilTypeGuy eviltypeguy@qeradiant.com + +int Cmd_Argc (void); +const char *Cmd_Argv (int arg); +const char *Cmd_Args (void); +// The functions that execute commands get their parameters with these +// functions. Cmd_Argv () will return an empty string, not a NULL +// if arg > argc, so string operations are always safe. + +/// Returns the position (1 to argc-1) in the command's argument list +/// where the given parameter apears, or 0 if not present +int Cmd_CheckParm (const char *parm); + +//void Cmd_TokenizeString (char *text); +// Takes a null terminated string. Does not need to be /n terminated. +// breaks the string up into arg tokens. + +/// Parses a single line of text into arguments and tries to execute it. +/// The text can come from the command buffer, a remote client, or stdin. +void Cmd_ExecuteString (const char *text, cmd_source_t src); + +/// adds the string as a clc_stringcmd to the client message. +/// (used when there is no reason to generate a local command to do it) +void Cmd_ForwardStringToServer (const char *s); + +/// adds the current command line as a clc_stringcmd to the client message. +/// 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 Cmd_ForwardToServer (void); + +/// used by command functions to send output to either the graphics console or +/// passed as a print message to the client +void Cmd_Print(const char *text); + +/// quotes a string so that it can be used as a command argument again; +/// quoteset is a string that contains one or more of ", \, $ and specifies +/// the characters to be quoted (you usually want to either pass "\"\\" or +/// "\"\\$"). Returns true on success, and false on overrun (in which case out +/// will contain a part of the quoted string). If putquotes is set, the +/// enclosing quote marks are also put. +qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qboolean putquotes); + +#endif + diff --git a/misc/source/darkplaces-src/collision.c b/misc/source/darkplaces-src/collision.c new file mode 100644 index 00000000..cf0d527f --- /dev/null +++ b/misc/source/darkplaces-src/collision.c @@ -0,0 +1,2178 @@ + +#include "quakedef.h" +#include "polygon.h" + +#define COLLISION_EDGEDIR_DOT_EPSILON (0.999f) +#define COLLISION_EDGECROSS_MINLENGTH2 (1.0f / 4194304.0f) +#define COLLISION_SNAPSCALE (32.0f) +#define COLLISION_SNAP (1.0f / COLLISION_SNAPSCALE) +#define COLLISION_SNAP2 (2.0f / COLLISION_SNAPSCALE) +#define COLLISION_PLANE_DIST_EPSILON (2.0f / COLLISION_SNAPSCALE) + +cvar_t collision_impactnudge = {0, "collision_impactnudge", "0.03125", "how much to back off from the impact"}; +cvar_t collision_startnudge = {0, "collision_startnudge", "0", "how much to bias collision trace start"}; +cvar_t collision_endnudge = {0, "collision_endnudge", "0", "how much to bias collision trace end"}; +cvar_t collision_enternudge = {0, "collision_enternudge", "0", "how much to bias collision entry fraction"}; +cvar_t collision_leavenudge = {0, "collision_leavenudge", "0", "how much to bias collision exit fraction"}; +cvar_t collision_prefernudgedfraction = {0, "collision_prefernudgedfraction", "1", "whether to sort collision events by nudged fraction (1) or real fraction (0)"}; +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +cvar_t collision_endposnudge = {0, "collision_endposnudge", "0", "workaround to fix trace_endpos sometimes being returned where it would be inside solid by making that collision hit (recommended: values like 1)"}; +#endif +cvar_t collision_debug_tracelineasbox = {0, "collision_debug_tracelineasbox", "0", "workaround for any bugs in Collision_TraceLineBrushFloat by using Collision_TraceBrushBrushFloat"}; +cvar_t collision_cache = {0, "collision_cache", "1", "store results of collision traces for next frame to reuse if possible (optimization)"}; + +mempool_t *collision_mempool; + +void Collision_Init (void) +{ + Cvar_RegisterVariable(&collision_impactnudge); + Cvar_RegisterVariable(&collision_startnudge); + Cvar_RegisterVariable(&collision_endnudge); + Cvar_RegisterVariable(&collision_enternudge); + Cvar_RegisterVariable(&collision_leavenudge); + Cvar_RegisterVariable(&collision_prefernudgedfraction); +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + Cvar_RegisterVariable(&collision_endposnudge); +#endif + Cvar_RegisterVariable(&collision_debug_tracelineasbox); + Cvar_RegisterVariable(&collision_cache); + collision_mempool = Mem_AllocPool("collision cache", 0, NULL); + Collision_Cache_Init(collision_mempool); +} + + + + + + + + + + + + + + +void Collision_PrintBrushAsQHull(colbrushf_t *brush, const char *name) +{ + int i; + Con_Printf("3 %s\n%i\n", name, brush->numpoints); + for (i = 0;i < brush->numpoints;i++) + Con_Printf("%f %f %f\n", brush->points[i].v[0], brush->points[i].v[1], brush->points[i].v[2]); + // FIXME: optimize! + Con_Printf("4\n%i\n", brush->numplanes); + for (i = 0;i < brush->numplanes;i++) + Con_Printf("%f %f %f %f\n", brush->planes[i].normal[0], brush->planes[i].normal[1], brush->planes[i].normal[2], brush->planes[i].dist); +} + +void Collision_ValidateBrush(colbrushf_t *brush) +{ + int j, k, pointsoffplanes, pointonplanes, pointswithinsufficientplanes, printbrush; + float d; + printbrush = false; + if (!brush->numpoints) + { + Con_Print("Collision_ValidateBrush: brush with no points!\n"); + printbrush = true; + } +#if 0 + // it's ok for a brush to have one point and no planes... + if (brush->numplanes == 0 && brush->numpoints != 1) + { + Con_Print("Collision_ValidateBrush: brush with no planes and more than one point!\n"); + printbrush = true; + } +#endif + if (brush->numplanes) + { + pointsoffplanes = 0; + pointswithinsufficientplanes = 0; + for (k = 0;k < brush->numplanes;k++) + if (DotProduct(brush->planes[k].normal, brush->planes[k].normal) < 0.0001f) + Con_Printf("Collision_ValidateBrush: plane #%i (%f %f %f %f) is degenerate\n", k, brush->planes[k].normal[0], brush->planes[k].normal[1], brush->planes[k].normal[2], brush->planes[k].dist); + for (j = 0;j < brush->numpoints;j++) + { + pointonplanes = 0; + for (k = 0;k < brush->numplanes;k++) + { + d = DotProduct(brush->points[j].v, brush->planes[k].normal) - brush->planes[k].dist; + if (d > COLLISION_PLANE_DIST_EPSILON) + { + Con_Printf("Collision_ValidateBrush: point #%i (%f %f %f) infront of plane #%i (%f %f %f %f)\n", j, brush->points[j].v[0], brush->points[j].v[1], brush->points[j].v[2], k, brush->planes[k].normal[0], brush->planes[k].normal[1], brush->planes[k].normal[2], brush->planes[k].dist); + printbrush = true; + } + if (fabs(d) > COLLISION_PLANE_DIST_EPSILON) + pointsoffplanes++; + else + pointonplanes++; + } + if (pointonplanes < 3) + pointswithinsufficientplanes++; + } + if (pointswithinsufficientplanes) + { + Con_Print("Collision_ValidateBrush: some points have insufficient planes, every point must be on at least 3 planes to form a corner.\n"); + printbrush = true; + } + if (pointsoffplanes == 0) // all points are on all planes + { + Con_Print("Collision_ValidateBrush: all points lie on all planes (degenerate, no brush volume!)\n"); + printbrush = true; + } + } + if (printbrush) + Collision_PrintBrushAsQHull(brush, "unnamed"); +} + +float nearestplanedist_float(const float *normal, const colpointf_t *points, int numpoints) +{ + float dist, bestdist; + if (!numpoints) + return 0; + bestdist = DotProduct(points->v, normal); + points++; + while(--numpoints) + { + dist = DotProduct(points->v, normal); + bestdist = min(bestdist, dist); + points++; + } + return bestdist; +} + +float furthestplanedist_float(const float *normal, const colpointf_t *points, int numpoints) +{ + float dist, bestdist; + if (!numpoints) + return 0; + bestdist = DotProduct(points->v, normal); + points++; + while(--numpoints) + { + dist = DotProduct(points->v, normal); + bestdist = max(bestdist, dist); + points++; + } + return bestdist; +} + +void Collision_CalcEdgeDirsForPolygonBrushFloat(colbrushf_t *brush) +{ + int i, j; + for (i = 0, j = brush->numpoints - 1;i < brush->numpoints;j = i, i++) + VectorSubtract(brush->points[i].v, brush->points[j].v, brush->edgedirs[j].v); +} + +colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalplanes, const colplanef_t *originalplanes, int supercontents, int q3surfaceflags, const texture_t *texture, int hasaabbplanes) +{ + // TODO: planesbuf could be replaced by a remapping table + int j, k, l, m, w, xyzflags; + int numpointsbuf = 0, maxpointsbuf = 256, numedgedirsbuf = 0, maxedgedirsbuf = 256, numplanesbuf = 0, maxplanesbuf = 256, numelementsbuf = 0, maxelementsbuf = 256; + int isaabb = true; + double maxdist; + colbrushf_t *brush; + colpointf_t pointsbuf[256]; + colpointf_t edgedirsbuf[256]; + colplanef_t planesbuf[256]; + int elementsbuf[1024]; + int polypointbuf[256]; + int pmaxpoints = 64; + int pnumpoints; + double p[2][3*64]; +#if 0 + // enable these if debugging to avoid seeing garbage in unused data- + memset(pointsbuf, 0, sizeof(pointsbuf)); + memset(edgedirsbuf, 0, sizeof(edgedirsbuf)); + memset(planesbuf, 0, sizeof(planesbuf)); + memset(elementsbuf, 0, sizeof(elementsbuf)); + memset(polypointbuf, 0, sizeof(polypointbuf)); + memset(p, 0, sizeof(p)); +#endif + + // check if there are too many planes and skip the brush + if (numoriginalplanes >= maxplanesbuf) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many planes for buffer\n"); + return NULL; + } + + // figure out how large a bounding box we need to properly compute this brush + maxdist = 0; + for (j = 0;j < numoriginalplanes;j++) + maxdist = max(maxdist, fabs(originalplanes[j].dist)); + // now make it large enough to enclose the entire brush, and round it off to a reasonable multiple of 1024 + maxdist = floor(maxdist * (4.0 / 1024.0) + 2) * 1024.0; + // construct a collision brush (points, planes, and renderable mesh) from + // a set of planes, this also optimizes out any unnecessary planes (ones + // whose polygon is clipped away by the other planes) + for (j = 0;j < numoriginalplanes;j++) + { + // add the new plane + VectorCopy(originalplanes[j].normal, planesbuf[numplanesbuf].normal); + planesbuf[numplanesbuf].dist = originalplanes[j].dist; + planesbuf[numplanesbuf].q3surfaceflags = originalplanes[j].q3surfaceflags; + planesbuf[numplanesbuf].texture = originalplanes[j].texture; + numplanesbuf++; + + // create a large polygon from the plane + w = 0; + PolygonD_QuadForPlane(p[w], originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist, maxdist); + pnumpoints = 4; + // clip it by all other planes + for (k = 0;k < numoriginalplanes && pnumpoints >= 3 && pnumpoints <= pmaxpoints;k++) + { + // skip the plane this polygon + // (nothing happens if it is processed, this is just an optimization) + if (k != j) + { + // we want to keep the inside of the brush plane so we flip + // the cutting plane + PolygonD_Divide(pnumpoints, p[w], -originalplanes[k].normal[0], -originalplanes[k].normal[1], -originalplanes[k].normal[2], -originalplanes[k].dist, COLLISION_PLANE_DIST_EPSILON, pmaxpoints, p[!w], &pnumpoints, 0, NULL, NULL, NULL); + w = !w; + } + } + + // if nothing is left, skip it + if (pnumpoints < 3) + { + //Con_DPrintf("Collision_NewBrushFromPlanes: warning: polygon for plane %f %f %f %f clipped away\n", originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist); + continue; + } + + for (k = 0;k < pnumpoints;k++) + { + int l, m; + m = 0; + for (l = 0;l < numoriginalplanes;l++) + if (fabs(DotProduct(&p[w][k*3], originalplanes[l].normal) - originalplanes[l].dist) < COLLISION_PLANE_DIST_EPSILON) + m++; + if (m < 3) + break; + } + if (k < pnumpoints) + { + Con_DPrintf("Collision_NewBrushFromPlanes: warning: polygon point does not lie on at least 3 planes\n"); + //return NULL; + } + + // check if there are too many polygon vertices for buffer + if (pnumpoints > pmaxpoints) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n"); + return NULL; + } + + // check if there are too many triangle elements for buffer + if (numelementsbuf + (pnumpoints - 2) * 3 > maxelementsbuf) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many triangle elements for buffer\n"); + return NULL; + } + + // add the unique points for this polygon + for (k = 0;k < pnumpoints;k++) + { + float v[3]; + // downgrade to float precision before comparing + VectorCopy(&p[w][k*3], v); + + // check if there is already a matching point (no duplicates) + for (m = 0;m < numpointsbuf;m++) + if (VectorDistance2(v, pointsbuf[m].v) < COLLISION_SNAP2) + break; + + // if there is no match, add a new one + if (m == numpointsbuf) + { + // check if there are too many and skip the brush + if (numpointsbuf >= maxpointsbuf) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n"); + return NULL; + } + // add the new one + VectorCopy(&p[w][k*3], pointsbuf[numpointsbuf].v); + numpointsbuf++; + } + + // store the index into a buffer + polypointbuf[k] = m; + } + + // add the triangles for the polygon + // (this particular code makes a triangle fan) + for (k = 0;k < pnumpoints - 2;k++) + { + elementsbuf[numelementsbuf++] = polypointbuf[0]; + elementsbuf[numelementsbuf++] = polypointbuf[k + 1]; + elementsbuf[numelementsbuf++] = polypointbuf[k + 2]; + } + + // add the unique edgedirs for this polygon + for (k = 0, l = pnumpoints-1;k < pnumpoints;l = k, k++) + { + float dir[3]; + // downgrade to float precision before comparing + VectorSubtract(&p[w][k*3], &p[w][l*3], dir); + VectorNormalize(dir); + + // check if there is already a matching edgedir (no duplicates) + for (m = 0;m < numedgedirsbuf;m++) + if (DotProduct(dir, edgedirsbuf[m].v) >= COLLISION_EDGEDIR_DOT_EPSILON) + break; + // skip this if there is + if (m < numedgedirsbuf) + continue; + + // try again with negated edgedir + VectorNegate(dir, dir); + // check if there is already a matching edgedir (no duplicates) + for (m = 0;m < numedgedirsbuf;m++) + if (DotProduct(dir, edgedirsbuf[m].v) >= COLLISION_EDGEDIR_DOT_EPSILON) + break; + // if there is no match, add a new one + if (m == numedgedirsbuf) + { + // check if there are too many and skip the brush + if (numedgedirsbuf >= maxedgedirsbuf) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many edgedirs for buffer\n"); + return NULL; + } + // add the new one + VectorCopy(dir, edgedirsbuf[numedgedirsbuf].v); + numedgedirsbuf++; + } + } + + // if any normal is not purely axial, it's not an axis-aligned box + if (isaabb && (originalplanes[j].normal[0] == 0) + (originalplanes[j].normal[1] == 0) + (originalplanes[j].normal[2] == 0) < 2) + isaabb = false; + } + + // if nothing is left, there's nothing to allocate + if (numplanesbuf < 4) + { + Con_DPrintf("Collision_NewBrushFromPlanes: failed to build collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf); + return NULL; + } + + // if no triangles or points could be constructed, then this routine failed but the brush is not discarded + if (numelementsbuf < 12 || numpointsbuf < 4) + Con_DPrintf("Collision_NewBrushFromPlanes: unable to rebuild triangles/points for collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf); + + // validate plane distances + for (j = 0;j < numplanesbuf;j++) + { + float d = furthestplanedist_float(planesbuf[j].normal, pointsbuf, numpointsbuf); + if (fabs(planesbuf[j].dist - d) > COLLISION_PLANE_DIST_EPSILON) + Con_DPrintf("plane %f %f %f %f mismatches dist %f\n", planesbuf[j].normal[0], planesbuf[j].normal[1], planesbuf[j].normal[2], planesbuf[j].dist, d); + } + + // allocate the brush and copy to it + brush = (colbrushf_t *)Mem_Alloc(mempool, sizeof(colbrushf_t) + sizeof(colpointf_t) * numpointsbuf + sizeof(colpointf_t) * numedgedirsbuf + sizeof(colplanef_t) * numplanesbuf + sizeof(int) * numelementsbuf); + brush->isaabb = isaabb; + brush->hasaabbplanes = hasaabbplanes; + brush->supercontents = supercontents; + brush->numplanes = numplanesbuf; + brush->numedgedirs = numedgedirsbuf; + brush->numpoints = numpointsbuf; + brush->numtriangles = numelementsbuf / 3; + brush->planes = (colplanef_t *)(brush + 1); + brush->points = (colpointf_t *)(brush->planes + brush->numplanes); + brush->edgedirs = (colpointf_t *)(brush->points + brush->numpoints); + brush->elements = (int *)(brush->points + brush->numpoints); + brush->q3surfaceflags = q3surfaceflags; + brush->texture = texture; + for (j = 0;j < brush->numpoints;j++) + { + brush->points[j].v[0] = pointsbuf[j].v[0]; + brush->points[j].v[1] = pointsbuf[j].v[1]; + brush->points[j].v[2] = pointsbuf[j].v[2]; + } + for (j = 0;j < brush->numedgedirs;j++) + { + brush->edgedirs[j].v[0] = edgedirsbuf[j].v[0]; + brush->edgedirs[j].v[1] = edgedirsbuf[j].v[1]; + brush->edgedirs[j].v[2] = edgedirsbuf[j].v[2]; + } + for (j = 0;j < brush->numplanes;j++) + { + brush->planes[j].normal[0] = planesbuf[j].normal[0]; + brush->planes[j].normal[1] = planesbuf[j].normal[1]; + brush->planes[j].normal[2] = planesbuf[j].normal[2]; + brush->planes[j].dist = planesbuf[j].dist; + brush->planes[j].q3surfaceflags = planesbuf[j].q3surfaceflags; + brush->planes[j].texture = planesbuf[j].texture; + } + for (j = 0;j < brush->numtriangles * 3;j++) + brush->elements[j] = elementsbuf[j]; + + xyzflags = 0; + VectorClear(brush->mins); + VectorClear(brush->maxs); + for (j = 0;j < min(6, numoriginalplanes);j++) + { + if (originalplanes[j].normal[0] == 1) {xyzflags |= 1;brush->maxs[0] = originalplanes[j].dist;} + else if (originalplanes[j].normal[0] == -1) {xyzflags |= 2;brush->mins[0] = -originalplanes[j].dist;} + else if (originalplanes[j].normal[1] == 1) {xyzflags |= 4;brush->maxs[1] = originalplanes[j].dist;} + else if (originalplanes[j].normal[1] == -1) {xyzflags |= 8;brush->mins[1] = -originalplanes[j].dist;} + else if (originalplanes[j].normal[2] == 1) {xyzflags |= 16;brush->maxs[2] = originalplanes[j].dist;} + else if (originalplanes[j].normal[2] == -1) {xyzflags |= 32;brush->mins[2] = -originalplanes[j].dist;} + } + // if not all xyzflags were set, then this is not a brush from q3map/q3map2, and needs reconstruction of the bounding box + // (this case works for any brush with valid points, but sometimes brushes are not reconstructed properly and hence the points are not valid, so this is reserved as a fallback case) + if (xyzflags != 63) + { + VectorCopy(brush->points[0].v, brush->mins); + VectorCopy(brush->points[0].v, brush->maxs); + for (j = 1;j < brush->numpoints;j++) + { + brush->mins[0] = min(brush->mins[0], brush->points[j].v[0]); + brush->mins[1] = min(brush->mins[1], brush->points[j].v[1]); + brush->mins[2] = min(brush->mins[2], brush->points[j].v[2]); + brush->maxs[0] = max(brush->maxs[0], brush->points[j].v[0]); + brush->maxs[1] = max(brush->maxs[1], brush->points[j].v[1]); + brush->maxs[2] = max(brush->maxs[2], brush->points[j].v[2]); + } + } + brush->mins[0] -= 1; + brush->mins[1] -= 1; + brush->mins[2] -= 1; + brush->maxs[0] += 1; + brush->maxs[1] += 1; + brush->maxs[2] += 1; + Collision_ValidateBrush(brush); + return brush; +} + + + +void Collision_CalcPlanesForPolygonBrushFloat(colbrushf_t *brush) +{ + int i; + float edge0[3], edge1[3], edge2[3], normal[3], dist, bestdist; + colpointf_t *p, *p2; + + // FIXME: these probably don't actually need to be normalized if the collision code does not care + if (brush->numpoints == 3) + { + // optimized triangle case + TriangleNormal(brush->points[0].v, brush->points[1].v, brush->points[2].v, brush->planes[0].normal); + if (DotProduct(brush->planes[0].normal, brush->planes[0].normal) < 0.0001f) + { + // there's no point in processing a degenerate triangle (GIGO - Garbage In, Garbage Out) + brush->numplanes = 0; + return; + } + else + { + brush->numplanes = 5; + brush->numedgedirs = 3; + VectorNormalize(brush->planes[0].normal); + brush->planes[0].dist = DotProduct(brush->points->v, brush->planes[0].normal); + VectorNegate(brush->planes[0].normal, brush->planes[1].normal); + brush->planes[1].dist = -brush->planes[0].dist; + VectorSubtract(brush->points[2].v, brush->points[0].v, edge0); + VectorSubtract(brush->points[0].v, brush->points[1].v, edge1); + VectorSubtract(brush->points[1].v, brush->points[2].v, edge2); + VectorCopy(edge0, brush->edgedirs[0].v); + VectorCopy(edge1, brush->edgedirs[1].v); + VectorCopy(edge2, brush->edgedirs[2].v); +#if 1 + { + float projectionnormal[3], projectionedge0[3], projectionedge1[3], projectionedge2[3]; + int i, best; + float dist, bestdist; + bestdist = fabs(brush->planes[0].normal[0]); + best = 0; + for (i = 1;i < 3;i++) + { + dist = fabs(brush->planes[0].normal[i]); + if (bestdist < dist) + { + bestdist = dist; + best = i; + } + } + VectorClear(projectionnormal); + if (brush->planes[0].normal[best] < 0) + projectionnormal[best] = -1; + else + projectionnormal[best] = 1; + VectorCopy(edge0, projectionedge0); + VectorCopy(edge1, projectionedge1); + VectorCopy(edge2, projectionedge2); + projectionedge0[best] = 0; + projectionedge1[best] = 0; + projectionedge2[best] = 0; + CrossProduct(projectionedge0, projectionnormal, brush->planes[2].normal); + CrossProduct(projectionedge1, projectionnormal, brush->planes[3].normal); + CrossProduct(projectionedge2, projectionnormal, brush->planes[4].normal); + } +#else + CrossProduct(edge0, brush->planes->normal, brush->planes[2].normal); + CrossProduct(edge1, brush->planes->normal, brush->planes[3].normal); + CrossProduct(edge2, brush->planes->normal, brush->planes[4].normal); +#endif + VectorNormalize(brush->planes[2].normal); + VectorNormalize(brush->planes[3].normal); + VectorNormalize(brush->planes[4].normal); + brush->planes[2].dist = DotProduct(brush->points[2].v, brush->planes[2].normal); + brush->planes[3].dist = DotProduct(brush->points[0].v, brush->planes[3].normal); + brush->planes[4].dist = DotProduct(brush->points[1].v, brush->planes[4].normal); + + if (developer_extra.integer) + { + // validation code +#if 0 + float temp[3]; + + VectorSubtract(brush->points[0].v, brush->points[1].v, edge0); + VectorSubtract(brush->points[2].v, brush->points[1].v, edge1); + CrossProduct(edge0, edge1, normal); + VectorNormalize(normal); + VectorSubtract(normal, brush->planes[0].normal, temp); + if (VectorLength(temp) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: TriangleNormal gave wrong answer (%f %f %f != correct answer %f %f %f)\n", brush->planes->normal[0], brush->planes->normal[1], brush->planes->normal[2], normal[0], normal[1], normal[2]); + if (fabs(DotProduct(brush->planes[1].normal, brush->planes[0].normal) - -1.0f) > 0.01f || fabs(brush->planes[1].dist - -brush->planes[0].dist) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: plane 1 (%f %f %f %f) is not opposite plane 0 (%f %f %f %f)\n", brush->planes[1].normal[0], brush->planes[1].normal[1], brush->planes[1].normal[2], brush->planes[1].dist, brush->planes[0].normal[0], brush->planes[0].normal[1], brush->planes[0].normal[2], brush->planes[0].dist); +#if 0 + if (fabs(DotProduct(brush->planes[2].normal, brush->planes[0].normal)) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: plane 2 (%f %f %f %f) is not perpendicular to plane 0 (%f %f %f %f)\n", brush->planes[2].normal[0], brush->planes[2].normal[1], brush->planes[2].normal[2], brush->planes[2].dist, brush->planes[0].normal[0], brush->planes[0].normal[1], brush->planes[0].normal[2], brush->planes[2].dist); + if (fabs(DotProduct(brush->planes[3].normal, brush->planes[0].normal)) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: plane 3 (%f %f %f %f) is not perpendicular to plane 0 (%f %f %f %f)\n", brush->planes[3].normal[0], brush->planes[3].normal[1], brush->planes[3].normal[2], brush->planes[3].dist, brush->planes[0].normal[0], brush->planes[0].normal[1], brush->planes[0].normal[2], brush->planes[3].dist); + if (fabs(DotProduct(brush->planes[4].normal, brush->planes[0].normal)) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: plane 4 (%f %f %f %f) is not perpendicular to plane 0 (%f %f %f %f)\n", brush->planes[4].normal[0], brush->planes[4].normal[1], brush->planes[4].normal[2], brush->planes[4].dist, brush->planes[0].normal[0], brush->planes[0].normal[1], brush->planes[0].normal[2], brush->planes[4].dist); + if (fabs(DotProduct(brush->planes[2].normal, edge0)) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: plane 2 (%f %f %f %f) is not perpendicular to edge 0 (%f %f %f to %f %f %f)\n", brush->planes[2].normal[0], brush->planes[2].normal[1], brush->planes[2].normal[2], brush->planes[2].dist, brush->points[2].v[0], brush->points[2].v[1], brush->points[2].v[2], brush->points[0].v[0], brush->points[0].v[1], brush->points[0].v[2]); + if (fabs(DotProduct(brush->planes[3].normal, edge1)) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: plane 3 (%f %f %f %f) is not perpendicular to edge 1 (%f %f %f to %f %f %f)\n", brush->planes[3].normal[0], brush->planes[3].normal[1], brush->planes[3].normal[2], brush->planes[3].dist, brush->points[0].v[0], brush->points[0].v[1], brush->points[0].v[2], brush->points[1].v[0], brush->points[1].v[1], brush->points[1].v[2]); + if (fabs(DotProduct(brush->planes[4].normal, edge2)) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: plane 4 (%f %f %f %f) is not perpendicular to edge 2 (%f %f %f to %f %f %f)\n", brush->planes[4].normal[0], brush->planes[4].normal[1], brush->planes[4].normal[2], brush->planes[4].dist, brush->points[1].v[0], brush->points[1].v[1], brush->points[1].v[2], brush->points[2].v[0], brush->points[2].v[1], brush->points[2].v[2]); +#endif +#endif + if (fabs(DotProduct(brush->points[0].v, brush->planes[0].normal) - brush->planes[0].dist) > 0.01f || fabs(DotProduct(brush->points[1].v, brush->planes[0].normal) - brush->planes[0].dist) > 0.01f || fabs(DotProduct(brush->points[2].v, brush->planes[0].normal) - brush->planes[0].dist) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: edges (%f %f %f to %f %f %f to %f %f %f) off front plane 0 (%f %f %f %f)\n", brush->points[0].v[0], brush->points[0].v[1], brush->points[0].v[2], brush->points[1].v[0], brush->points[1].v[1], brush->points[1].v[2], brush->points[2].v[0], brush->points[2].v[1], brush->points[2].v[2], brush->planes[0].normal[0], brush->planes[0].normal[1], brush->planes[0].normal[2], brush->planes[0].dist); + if (fabs(DotProduct(brush->points[0].v, brush->planes[1].normal) - brush->planes[1].dist) > 0.01f || fabs(DotProduct(brush->points[1].v, brush->planes[1].normal) - brush->planes[1].dist) > 0.01f || fabs(DotProduct(brush->points[2].v, brush->planes[1].normal) - brush->planes[1].dist) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: edges (%f %f %f to %f %f %f to %f %f %f) off back plane 1 (%f %f %f %f)\n", brush->points[0].v[0], brush->points[0].v[1], brush->points[0].v[2], brush->points[1].v[0], brush->points[1].v[1], brush->points[1].v[2], brush->points[2].v[0], brush->points[2].v[1], brush->points[2].v[2], brush->planes[1].normal[0], brush->planes[1].normal[1], brush->planes[1].normal[2], brush->planes[1].dist); + if (fabs(DotProduct(brush->points[2].v, brush->planes[2].normal) - brush->planes[2].dist) > 0.01f || fabs(DotProduct(brush->points[0].v, brush->planes[2].normal) - brush->planes[2].dist) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: edge 0 (%f %f %f to %f %f %f) off front plane 2 (%f %f %f %f)\n", brush->points[2].v[0], brush->points[2].v[1], brush->points[2].v[2], brush->points[0].v[0], brush->points[0].v[1], brush->points[0].v[2], brush->planes[2].normal[0], brush->planes[2].normal[1], brush->planes[2].normal[2], brush->planes[2].dist); + if (fabs(DotProduct(brush->points[0].v, brush->planes[3].normal) - brush->planes[3].dist) > 0.01f || fabs(DotProduct(brush->points[1].v, brush->planes[3].normal) - brush->planes[3].dist) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: edge 0 (%f %f %f to %f %f %f) off front plane 2 (%f %f %f %f)\n", brush->points[0].v[0], brush->points[0].v[1], brush->points[0].v[2], brush->points[1].v[0], brush->points[1].v[1], brush->points[1].v[2], brush->planes[3].normal[0], brush->planes[3].normal[1], brush->planes[3].normal[2], brush->planes[3].dist); + if (fabs(DotProduct(brush->points[1].v, brush->planes[4].normal) - brush->planes[4].dist) > 0.01f || fabs(DotProduct(brush->points[2].v, brush->planes[4].normal) - brush->planes[4].dist) > 0.01f) + Con_DPrintf("Collision_CalcPlanesForPolygonBrushFloat: edge 0 (%f %f %f to %f %f %f) off front plane 2 (%f %f %f %f)\n", brush->points[1].v[0], brush->points[1].v[1], brush->points[1].v[2], brush->points[2].v[0], brush->points[2].v[1], brush->points[2].v[2], brush->planes[4].normal[0], brush->planes[4].normal[1], brush->planes[4].normal[2], brush->planes[4].dist); + } + } + } + else + { + // choose best surface normal for polygon's plane + bestdist = 0; + for (i = 0, p = brush->points + 1;i < brush->numpoints - 2;i++, p++) + { + VectorSubtract(p[-1].v, p[0].v, edge0); + VectorSubtract(p[1].v, p[0].v, edge1); + CrossProduct(edge0, edge1, normal); + //TriangleNormal(p[-1].v, p[0].v, p[1].v, normal); + dist = DotProduct(normal, normal); + if (i == 0 || bestdist < dist) + { + bestdist = dist; + VectorCopy(normal, brush->planes->normal); + } + } + if (bestdist < 0.0001f) + { + // there's no point in processing a degenerate triangle (GIGO - Garbage In, Garbage Out) + brush->numplanes = 0; + return; + } + else + { + brush->numplanes = brush->numpoints + 2; + VectorNormalize(brush->planes->normal); + brush->planes->dist = DotProduct(brush->points->v, brush->planes->normal); + + // negate plane to create other side + VectorNegate(brush->planes[0].normal, brush->planes[1].normal); + brush->planes[1].dist = -brush->planes[0].dist; + for (i = 0, p = brush->points + (brush->numpoints - 1), p2 = brush->points;i < brush->numpoints;i++, p = p2, p2++) + { + VectorSubtract(p->v, p2->v, edge0); + CrossProduct(edge0, brush->planes->normal, brush->planes[i + 2].normal); + VectorNormalize(brush->planes[i + 2].normal); + brush->planes[i + 2].dist = DotProduct(p->v, brush->planes[i + 2].normal); + } + } + } + + if (developer_extra.integer) + { + // validity check - will be disabled later + Collision_ValidateBrush(brush); + for (i = 0;i < brush->numplanes;i++) + { + int j; + for (j = 0, p = brush->points;j < brush->numpoints;j++, p++) + if (DotProduct(p->v, brush->planes[i].normal) > brush->planes[i].dist + COLLISION_PLANE_DIST_EPSILON) + Con_DPrintf("Error in brush plane generation, plane %i\n", i); + } + } +} + +colbrushf_t *Collision_AllocBrushFromPermanentPolygonFloat(mempool_t *mempool, int numpoints, float *points, int supercontents, int q3surfaceflags, const texture_t *texture) +{ + colbrushf_t *brush; + brush = (colbrushf_t *)Mem_Alloc(mempool, sizeof(colbrushf_t) + sizeof(colplanef_t) * (numpoints + 2) + sizeof(colpointf_t) * numpoints); + brush->isaabb = false; + brush->hasaabbplanes = false; + brush->supercontents = supercontents; + brush->numpoints = numpoints; + brush->numedgedirs = numpoints; + brush->numplanes = numpoints + 2; + brush->planes = (colplanef_t *)(brush + 1); + brush->points = (colpointf_t *)points; + brush->edgedirs = (colpointf_t *)(brush->planes + brush->numplanes); + brush->q3surfaceflags = q3surfaceflags; + brush->texture = texture; + Sys_Error("Collision_AllocBrushFromPermanentPolygonFloat: FIXME: this code needs to be updated to generate a mesh..."); + return brush; +} + +// NOTE: start and end of each brush pair must have same numplanes/numpoints +void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *trace_start, const colbrushf_t *trace_end, const colbrushf_t *other_start, const colbrushf_t *other_end) +{ + int nplane, nplane2, nedge1, nedge2, hitq3surfaceflags = 0; + int tracenumedgedirs = trace_start->numedgedirs; + //int othernumedgedirs = other_start->numedgedirs; + int tracenumpoints = trace_start->numpoints; + int othernumpoints = other_start->numpoints; + int numplanes1 = other_start->numplanes; + int numplanes2 = numplanes1 + trace_start->numplanes; + int numplanes3 = numplanes2 + trace_start->numedgedirs * other_start->numedgedirs * 2; + vec_t enterfrac = -1, leavefrac = 1, startdist, enddist, ie, f, imove, enterfrac2 = -1; + vec4_t startplane; + vec4_t endplane; + vec4_t newimpactplane; + const texture_t *hittexture = NULL; + vec_t startdepth = 1; + vec3_t startdepthnormal; + + VectorClear(startdepthnormal); + Vector4Clear(newimpactplane); + + // fast case for AABB vs compiled brushes (which begin with AABB planes and also have precomputed bevels for AABB collisions) + if (trace_start->isaabb && other_start->hasaabbplanes) + numplanes3 = numplanes2 = numplanes1; + + // Separating Axis Theorem: + // if a supporting vector (plane normal) can be found that separates two + // objects, they are not colliding. + // + // Minkowski Sum: + // reduce the size of one object to a point while enlarging the other to + // represent the space that point can not occupy. + // + // try every plane we can construct between the two brushes and measure + // the distance between them. + for (nplane = 0;nplane < numplanes3;nplane++) + { + if (nplane < numplanes1) + { + nplane2 = nplane; + VectorCopy(other_start->planes[nplane2].normal, startplane); + VectorCopy(other_end->planes[nplane2].normal, endplane); + } + else if (nplane < numplanes2) + { + nplane2 = nplane - numplanes1; + VectorCopy(trace_start->planes[nplane2].normal, startplane); + VectorCopy(trace_end->planes[nplane2].normal, endplane); + } + else + { + // pick an edgedir from each brush and cross them + nplane2 = nplane - numplanes2; + nedge1 = nplane2 >> 1; + nedge2 = nedge1 / tracenumedgedirs; + nedge1 -= nedge2 * tracenumedgedirs; + if (nplane2 & 1) + { + CrossProduct(trace_start->edgedirs[nedge1].v, other_start->edgedirs[nedge2].v, startplane); + if (VectorLength2(startplane) < COLLISION_EDGECROSS_MINLENGTH2) + continue; // degenerate crossproduct + CrossProduct(trace_end->edgedirs[nedge1].v, other_end->edgedirs[nedge2].v, endplane); + if (VectorLength2(endplane) < COLLISION_EDGECROSS_MINLENGTH2) + continue; // degenerate crossproduct + } + else + { + CrossProduct(other_start->edgedirs[nedge2].v, trace_start->edgedirs[nedge1].v, startplane); + if (VectorLength2(startplane) < COLLISION_EDGECROSS_MINLENGTH2) + continue; // degenerate crossproduct + CrossProduct(other_end->edgedirs[nedge2].v, trace_end->edgedirs[nedge1].v, endplane); + if (VectorLength2(endplane) < COLLISION_EDGECROSS_MINLENGTH2) + continue; // degenerate crossproduct + } + VectorNormalize(startplane); + VectorNormalize(endplane); + } + startplane[3] = furthestplanedist_float(startplane, other_start->points, othernumpoints); + endplane[3] = furthestplanedist_float(startplane, other_end->points, othernumpoints); + startdist = nearestplanedist_float(startplane, trace_start->points, tracenumpoints) - startplane[3] - collision_startnudge.value; + enddist = nearestplanedist_float(endplane, trace_end->points, tracenumpoints) - endplane[3] - collision_endnudge.value; + //Con_Printf("%c%i: startdist = %f, enddist = %f, startdist / (startdist - enddist) = %f\n", nplane2 != nplane ? 'b' : 'a', nplane2, startdist, enddist, startdist / (startdist - enddist)); + + // aside from collisions, this is also used for error correction + if (startdist < collision_impactnudge.value && nplane < numplanes1 && (startdepth < startdist || startdepth == 1)) + { + startdepth = startdist; + VectorCopy(startplane, startdepthnormal); + } + + if (startdist > enddist) + { + // moving into brush + if (enddist >= collision_enternudge.value) + return; + if (startdist > 0) + { + // enter + imove = 1 / (startdist - enddist); + f = (startdist - collision_enternudge.value) * imove; + if (f < 0) + f = 0; + // check if this will reduce the collision time range + if (enterfrac < f) + { + // reduced collision time range + enterfrac = f; + // if the collision time range is now empty, no collision + if (enterfrac > leavefrac) + return; + // if the collision would be further away than the trace's + // existing collision data, we don't care about this + // collision + if (enterfrac > trace->realfraction) + return; + // calculate the nudged fraction and impact normal we'll + // need if we accept this collision later + enterfrac2 = (startdist - collision_impactnudge.value) * imove; + ie = 1.0f - enterfrac; + newimpactplane[0] = startplane[0] * ie + endplane[0] * enterfrac; + newimpactplane[1] = startplane[1] * ie + endplane[1] * enterfrac; + newimpactplane[2] = startplane[2] * ie + endplane[2] * enterfrac; + newimpactplane[3] = startplane[3] * ie + endplane[3] * enterfrac; + if (nplane < numplanes1) + { + // use the plane from other + nplane2 = nplane; + hitq3surfaceflags = other_start->planes[nplane2].q3surfaceflags; + hittexture = other_start->planes[nplane2].texture; + } + else if (nplane < numplanes2) + { + // use the plane from trace + nplane2 = nplane - numplanes1; + hitq3surfaceflags = trace_start->planes[nplane2].q3surfaceflags; + hittexture = trace_start->planes[nplane2].texture; + } + else + { + hitq3surfaceflags = other_start->q3surfaceflags; + hittexture = other_start->texture; + } + } + } + } + else + { + // moving out of brush + if (startdist > 0) + return; + if (enddist > 0) + { + // leave + f = (startdist + collision_leavenudge.value) / (startdist - enddist); + if (f > 1) + f = 1; + // check if this will reduce the collision time range + if (leavefrac > f) + { + // reduced collision time range + leavefrac = f; + // if the collision time range is now empty, no collision + if (enterfrac > leavefrac) + return; + } + } + } + } + + // at this point we know the trace overlaps the brush because it was not + // rejected at any point in the loop above + + // see if the trace started outside the brush or not + if (enterfrac > -1) + { + // started outside, and overlaps, therefore there is a collision here + // store out the impact information + if (trace->hitsupercontentsmask & other_start->supercontents) + { + trace->hitsupercontents = other_start->supercontents; + trace->hitq3surfaceflags = hitq3surfaceflags; + trace->hittexture = hittexture; + trace->realfraction = bound(0, enterfrac, 1); + trace->fraction = bound(0, enterfrac2, 1); + if (collision_prefernudgedfraction.integer) + trace->realfraction = trace->fraction; + VectorCopy(newimpactplane, trace->plane.normal); + trace->plane.dist = newimpactplane[3]; + } + } + else + { + // started inside, update startsolid and friends + trace->startsupercontents |= other_start->supercontents; + if (trace->hitsupercontentsmask & other_start->supercontents) + { + trace->startsolid = true; + if (leavefrac < 1) + trace->allsolid = true; + VectorCopy(newimpactplane, trace->plane.normal); + trace->plane.dist = newimpactplane[3]; + if (trace->startdepth > startdepth) + { + trace->startdepth = startdepth; + VectorCopy(startdepthnormal, trace->startdepthnormal); + } + } + } +} + +// NOTE: start and end of each brush pair must have same numplanes/numpoints +void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const colbrushf_t *other_start, const colbrushf_t *other_end) +{ + int nplane, hitq3surfaceflags = 0; + int numplanes = other_start->numplanes; + vec_t enterfrac = -1, leavefrac = 1, startdist, enddist, ie, f, imove, enterfrac2 = -1; + vec4_t startplane; + vec4_t endplane; + vec4_t newimpactplane; + const texture_t *hittexture = NULL; + vec_t startdepth = 1; + vec3_t startdepthnormal; + + if (collision_debug_tracelineasbox.integer) + { + colboxbrushf_t thisbrush_start, thisbrush_end; + Collision_BrushForBox(&thisbrush_start, linestart, linestart, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, lineend, lineend, 0, 0, NULL); + Collision_TraceBrushBrushFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, other_start, other_end); + return; + } + + VectorClear(startdepthnormal); + Vector4Clear(newimpactplane); + + // Separating Axis Theorem: + // if a supporting vector (plane normal) can be found that separates two + // objects, they are not colliding. + // + // Minkowski Sum: + // reduce the size of one object to a point while enlarging the other to + // represent the space that point can not occupy. + // + // try every plane we can construct between the two brushes and measure + // the distance between them. + for (nplane = 0;nplane < numplanes;nplane++) + { + VectorCopy(other_start->planes[nplane].normal, startplane); + startplane[3] = other_start->planes[nplane].dist; + VectorCopy(other_end->planes[nplane].normal, endplane); + endplane[3] = other_end->planes[nplane].dist; + startdist = DotProduct(linestart, startplane) - startplane[3] - collision_startnudge.value; + enddist = DotProduct(lineend, endplane) - endplane[3] - collision_endnudge.value; + //Con_Printf("%c%i: startdist = %f, enddist = %f, startdist / (startdist - enddist) = %f\n", nplane2 != nplane ? 'b' : 'a', nplane2, startdist, enddist, startdist / (startdist - enddist)); + + // aside from collisions, this is also used for error correction + if (startdist < collision_impactnudge.value && (startdepth < startdist || startdepth == 1)) + { + startdepth = startdist; + VectorCopy(startplane, startdepthnormal); + } + + if (startdist > enddist) + { + // moving into brush + if (enddist >= collision_enternudge.value) + return; + if (startdist > 0) + { + // enter + imove = 1 / (startdist - enddist); + f = (startdist - collision_enternudge.value) * imove; + if (f < 0) + f = 0; + // check if this will reduce the collision time range + if (enterfrac < f) + { + // reduced collision time range + enterfrac = f; + // if the collision time range is now empty, no collision + if (enterfrac > leavefrac) + return; + // if the collision would be further away than the trace's + // existing collision data, we don't care about this + // collision + if (enterfrac > trace->realfraction) + return; + // calculate the nudged fraction and impact normal we'll + // need if we accept this collision later + enterfrac2 = (startdist - collision_impactnudge.value) * imove; + ie = 1.0f - enterfrac; + newimpactplane[0] = startplane[0] * ie + endplane[0] * enterfrac; + newimpactplane[1] = startplane[1] * ie + endplane[1] * enterfrac; + newimpactplane[2] = startplane[2] * ie + endplane[2] * enterfrac; + newimpactplane[3] = startplane[3] * ie + endplane[3] * enterfrac; + hitq3surfaceflags = other_start->planes[nplane].q3surfaceflags; + hittexture = other_start->planes[nplane].texture; + } + } + } + else + { + // moving out of brush + if (startdist > 0) + return; + if (enddist > 0) + { + // leave + f = (startdist + collision_leavenudge.value) / (startdist - enddist); + if (f > 1) + f = 1; + // check if this will reduce the collision time range + if (leavefrac > f) + { + // reduced collision time range + leavefrac = f; + // if the collision time range is now empty, no collision + if (enterfrac > leavefrac) + return; + } + } + } + } + + // at this point we know the trace overlaps the brush because it was not + // rejected at any point in the loop above + + // see if the trace started outside the brush or not + if (enterfrac > -1) + { + // started outside, and overlaps, therefore there is a collision here + // store out the impact information + if (trace->hitsupercontentsmask & other_start->supercontents) + { + trace->hitsupercontents = other_start->supercontents; + trace->hitq3surfaceflags = hitq3surfaceflags; + trace->hittexture = hittexture; + trace->realfraction = bound(0, enterfrac, 1); + trace->fraction = bound(0, enterfrac2, 1); + if (collision_prefernudgedfraction.integer) + trace->realfraction = trace->fraction; + VectorCopy(newimpactplane, trace->plane.normal); + trace->plane.dist = newimpactplane[3]; + } + } + else + { + // started inside, update startsolid and friends + trace->startsupercontents |= other_start->supercontents; + if (trace->hitsupercontentsmask & other_start->supercontents) + { + trace->startsolid = true; + if (leavefrac < 1) + trace->allsolid = true; + VectorCopy(newimpactplane, trace->plane.normal); + trace->plane.dist = newimpactplane[3]; + if (trace->startdepth > startdepth) + { + trace->startdepth = startdepth; + VectorCopy(startdepthnormal, trace->startdepthnormal); + } + } + } +} + +qboolean Collision_PointInsideBrushFloat(const vec3_t point, const colbrushf_t *brush) +{ + int nplane; + const colplanef_t *plane; + + if (!BoxesOverlap(point, point, brush->mins, brush->maxs)) + return false; + for (nplane = 0, plane = brush->planes;nplane < brush->numplanes;nplane++, plane++) + if (DotProduct(plane->normal, point) > plane->dist) + return false; + return true; +} + +void Collision_TracePointBrushFloat(trace_t *trace, const vec3_t point, const colbrushf_t *thatbrush) +{ + if (!Collision_PointInsideBrushFloat(point, thatbrush)) + return; + + trace->startsupercontents |= thatbrush->supercontents; + if (trace->hitsupercontentsmask & thatbrush->supercontents) + { + trace->startsolid = true; + trace->allsolid = true; + } +} + +void Collision_SnapCopyPoints(int numpoints, const colpointf_t *in, colpointf_t *out, float fractionprecision, float invfractionprecision) +{ + int i; + for (i = 0;i < numpoints;i++) + { + out[i].v[0] = floor(in[i].v[0] * fractionprecision + 0.5f) * invfractionprecision; + out[i].v[1] = floor(in[i].v[1] * fractionprecision + 0.5f) * invfractionprecision; + out[i].v[2] = floor(in[i].v[2] * fractionprecision + 0.5f) * invfractionprecision; + } +} + +void Collision_TraceBrushTriangleMeshFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs) +{ + int i; + colpointf_t points[3]; + colpointf_t edgedirs[3]; + colplanef_t planes[5]; + colbrushf_t brush; + memset(&brush, 0, sizeof(brush)); + brush.isaabb = false; + brush.hasaabbplanes = false; + brush.numpoints = 3; + brush.numedgedirs = 3; + brush.numplanes = 5; + brush.points = points; + brush.edgedirs = edgedirs; + brush.planes = planes; + brush.supercontents = supercontents; + brush.q3surfaceflags = q3surfaceflags; + brush.texture = texture; + for (i = 0;i < brush.numplanes;i++) + { + brush.planes[i].q3surfaceflags = q3surfaceflags; + brush.planes[i].texture = texture; + } + if(stride > 0) + { + int k, cnt, tri; + cnt = (numtriangles + stride - 1) / stride; + for(i = 0; i < cnt; ++i) + { + if(BoxesOverlap(bbox6f + i * 6, bbox6f + i * 6 + 3, segmentmins, segmentmaxs)) + { + for(k = 0; k < stride; ++k) + { + tri = i * stride + k; + if(tri >= numtriangles) + break; + VectorCopy(vertex3f + element3i[tri * 3 + 0] * 3, points[0].v); + VectorCopy(vertex3f + element3i[tri * 3 + 1] * 3, points[1].v); + VectorCopy(vertex3f + element3i[tri * 3 + 2] * 3, points[2].v); + Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); + Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); + Collision_CalcPlanesForPolygonBrushFloat(&brush); + //Collision_PrintBrushAsQHull(&brush, "brush"); + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); + } + } + } + } + else if(stride == 0) + { + for (i = 0;i < numtriangles;i++, element3i += 3) + { + if (TriangleOverlapsBox(vertex3f + element3i[0]*3, vertex3f + element3i[1]*3, vertex3f + element3i[2]*3, segmentmins, segmentmaxs)) + { + VectorCopy(vertex3f + element3i[0] * 3, points[0].v); + VectorCopy(vertex3f + element3i[1] * 3, points[1].v); + VectorCopy(vertex3f + element3i[2] * 3, points[2].v); + Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); + Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); + Collision_CalcPlanesForPolygonBrushFloat(&brush); + //Collision_PrintBrushAsQHull(&brush, "brush"); + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); + } + } + } + else + { + for (i = 0;i < numtriangles;i++, element3i += 3) + { + VectorCopy(vertex3f + element3i[0] * 3, points[0].v); + VectorCopy(vertex3f + element3i[1] * 3, points[1].v); + VectorCopy(vertex3f + element3i[2] * 3, points[2].v); + Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); + Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); + Collision_CalcPlanesForPolygonBrushFloat(&brush); + //Collision_PrintBrushAsQHull(&brush, "brush"); + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); + } + } +} + +void Collision_TraceLineTriangleMeshFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs) +{ + int i; + // FIXME: snap vertices? + if(stride > 0) + { + int k, cnt, tri; + cnt = (numtriangles + stride - 1) / stride; + for(i = 0; i < cnt; ++i) + { + if(BoxesOverlap(bbox6f + i * 6, bbox6f + i * 6 + 3, segmentmins, segmentmaxs)) + { + for(k = 0; k < stride; ++k) + { + tri = i * stride + k; + if(tri >= numtriangles) + break; + Collision_TraceLineTriangleFloat(trace, linestart, lineend, vertex3f + element3i[tri * 3 + 0] * 3, vertex3f + element3i[tri * 3 + 1] * 3, vertex3f + element3i[tri * 3 + 2] * 3, supercontents, q3surfaceflags, texture); + } + } + } + } + else + { + for (i = 0;i < numtriangles;i++, element3i += 3) + Collision_TraceLineTriangleFloat(trace, linestart, lineend, vertex3f + element3i[0] * 3, vertex3f + element3i[1] * 3, vertex3f + element3i[2] * 3, supercontents, q3surfaceflags, texture); + } +} + +void Collision_TraceBrushTriangleFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const float *v0, const float *v1, const float *v2, int supercontents, int q3surfaceflags, const texture_t *texture) +{ + int i; + colpointf_t points[3]; + colpointf_t edgedirs[3]; + colplanef_t planes[5]; + colbrushf_t brush; + memset(&brush, 0, sizeof(brush)); + brush.isaabb = false; + brush.hasaabbplanes = false; + brush.numpoints = 3; + brush.numedgedirs = 3; + brush.numplanes = 5; + brush.points = points; + brush.edgedirs = edgedirs; + brush.planes = planes; + brush.supercontents = supercontents; + brush.q3surfaceflags = q3surfaceflags; + brush.texture = texture; + for (i = 0;i < brush.numplanes;i++) + { + brush.planes[i].q3surfaceflags = q3surfaceflags; + brush.planes[i].texture = texture; + } + VectorCopy(v0, points[0].v); + VectorCopy(v1, points[1].v); + VectorCopy(v2, points[2].v); + Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); + Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); + Collision_CalcPlanesForPolygonBrushFloat(&brush); + //Collision_PrintBrushAsQHull(&brush, "brush"); + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); +} + +void Collision_BrushForBox(colboxbrushf_t *boxbrush, const vec3_t mins, const vec3_t maxs, int supercontents, int q3surfaceflags, const texture_t *texture) +{ + int i; + memset(boxbrush, 0, sizeof(*boxbrush)); + boxbrush->brush.isaabb = true; + boxbrush->brush.hasaabbplanes = true; + boxbrush->brush.points = boxbrush->points; + boxbrush->brush.edgedirs = boxbrush->edgedirs; + boxbrush->brush.planes = boxbrush->planes; + boxbrush->brush.supercontents = supercontents; + boxbrush->brush.q3surfaceflags = q3surfaceflags; + boxbrush->brush.texture = texture; + if (VectorCompare(mins, maxs)) + { + // point brush + boxbrush->brush.numpoints = 1; + boxbrush->brush.numedgedirs = 0; + boxbrush->brush.numplanes = 0; + VectorCopy(mins, boxbrush->brush.points[0].v); + } + else + { + boxbrush->brush.numpoints = 8; + boxbrush->brush.numedgedirs = 3; + boxbrush->brush.numplanes = 6; + // there are 8 points on a box + // there are 3 edgedirs on a box (both signs are tested in collision) + // there are 6 planes on a box + VectorSet(boxbrush->brush.points[0].v, mins[0], mins[1], mins[2]); + VectorSet(boxbrush->brush.points[1].v, maxs[0], mins[1], mins[2]); + VectorSet(boxbrush->brush.points[2].v, mins[0], maxs[1], mins[2]); + VectorSet(boxbrush->brush.points[3].v, maxs[0], maxs[1], mins[2]); + VectorSet(boxbrush->brush.points[4].v, mins[0], mins[1], maxs[2]); + VectorSet(boxbrush->brush.points[5].v, maxs[0], mins[1], maxs[2]); + VectorSet(boxbrush->brush.points[6].v, mins[0], maxs[1], maxs[2]); + VectorSet(boxbrush->brush.points[7].v, maxs[0], maxs[1], maxs[2]); + VectorSet(boxbrush->brush.edgedirs[0].v, 1, 0, 0); + VectorSet(boxbrush->brush.edgedirs[1].v, 0, 1, 0); + VectorSet(boxbrush->brush.edgedirs[2].v, 0, 0, 1); + VectorSet(boxbrush->brush.planes[0].normal, -1, 0, 0);boxbrush->brush.planes[0].dist = -mins[0]; + VectorSet(boxbrush->brush.planes[1].normal, 1, 0, 0);boxbrush->brush.planes[1].dist = maxs[0]; + VectorSet(boxbrush->brush.planes[2].normal, 0, -1, 0);boxbrush->brush.planes[2].dist = -mins[1]; + VectorSet(boxbrush->brush.planes[3].normal, 0, 1, 0);boxbrush->brush.planes[3].dist = maxs[1]; + VectorSet(boxbrush->brush.planes[4].normal, 0, 0, -1);boxbrush->brush.planes[4].dist = -mins[2]; + VectorSet(boxbrush->brush.planes[5].normal, 0, 0, 1);boxbrush->brush.planes[5].dist = maxs[2]; + for (i = 0;i < 6;i++) + { + boxbrush->brush.planes[i].q3surfaceflags = q3surfaceflags; + boxbrush->brush.planes[i].texture = texture; + } + } + boxbrush->brush.supercontents = supercontents; + boxbrush->brush.q3surfaceflags = q3surfaceflags; + boxbrush->brush.texture = texture; + VectorSet(boxbrush->brush.mins, mins[0] - 1, mins[1] - 1, mins[2] - 1); + VectorSet(boxbrush->brush.maxs, maxs[0] + 1, maxs[1] + 1, maxs[2] + 1); + //Collision_ValidateBrush(&boxbrush->brush); +} + +void Collision_ClipTrace_BrushBox(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int supercontents, int q3surfaceflags, texture_t *texture) +{ + colboxbrushf_t boxbrush, thisbrush_start, thisbrush_end; + vec3_t startmins, startmaxs, endmins, endmaxs; + + // create brushes for the collision + VectorAdd(start, mins, startmins); + VectorAdd(start, maxs, startmaxs); + VectorAdd(end, mins, endmins); + VectorAdd(end, maxs, endmaxs); + Collision_BrushForBox(&boxbrush, cmins, cmaxs, supercontents, q3surfaceflags, texture); + Collision_BrushForBox(&thisbrush_start, startmins, startmaxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, endmins, endmaxs, 0, 0, NULL); + + memset(trace, 0, sizeof(trace_t)); + trace->hitsupercontentsmask = hitsupercontentsmask; + trace->fraction = 1; + trace->realfraction = 1; + trace->allsolid = true; + Collision_TraceBrushBrushFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, &boxbrush.brush, &boxbrush.brush); +} + +//pseudocode for detecting line/sphere overlap without calculating an impact point +//linesphereorigin = sphereorigin - linestart;linediff = lineend - linestart;linespherefrac = DotProduct(linesphereorigin, linediff) / DotProduct(linediff, linediff);return VectorLength2(linesphereorigin - bound(0, linespherefrac, 1) * linediff) >= sphereradius*sphereradius; + +// LordHavoc: currently unused, but tested +// note: this can be used for tracing a moving sphere vs a stationary sphere, +// by simply adding the moving sphere's radius to the sphereradius parameter, +// all the results are correct (impactpoint, impactnormal, and fraction) +float Collision_ClipTrace_Line_Sphere(double *linestart, double *lineend, double *sphereorigin, double sphereradius, double *impactpoint, double *impactnormal) +{ + double dir[3], scale, v[3], deviationdist, impactdist, linelength; + // make sure the impactpoint and impactnormal are valid even if there is + // no collision + VectorCopy(lineend, impactpoint); + VectorClear(impactnormal); + // calculate line direction + VectorSubtract(lineend, linestart, dir); + // normalize direction + linelength = VectorLength(dir); + if (linelength) + { + scale = 1.0 / linelength; + VectorScale(dir, scale, dir); + } + // this dotproduct calculates the distance along the line at which the + // sphere origin is (nearest point to the sphere origin on the line) + impactdist = DotProduct(sphereorigin, dir) - DotProduct(linestart, dir); + // calculate point on line at that distance, and subtract the + // sphereorigin from it, so we have a vector to measure for the distance + // of the line from the sphereorigin (deviation, how off-center it is) + VectorMA(linestart, impactdist, dir, v); + VectorSubtract(v, sphereorigin, v); + deviationdist = VectorLength2(v); + // if outside the radius, it's a miss for sure + // (we do this comparison using squared radius to avoid a sqrt) + if (deviationdist > sphereradius*sphereradius) + return 1; // miss (off to the side) + // nudge back to find the correct impact distance + impactdist -= sphereradius - deviationdist/sphereradius; + if (impactdist >= linelength) + return 1; // miss (not close enough) + if (impactdist < 0) + return 1; // miss (linestart is past or inside sphere) + // calculate new impactpoint + VectorMA(linestart, impactdist, dir, impactpoint); + // calculate impactnormal (surface normal at point of impact) + VectorSubtract(impactpoint, sphereorigin, impactnormal); + // normalize impactnormal + VectorNormalize(impactnormal); + // return fraction of movement distance + return impactdist / linelength; +} + +void Collision_TraceLineTriangleFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const float *point0, const float *point1, const float *point2, int supercontents, int q3surfaceflags, const texture_t *texture) +{ +#if 1 + // more optimized + float d1, d2, d, f, impact[3], edgenormal[3], faceplanenormal[3], faceplanedist, faceplanenormallength2, edge01[3], edge21[3], edge02[3]; + + // this function executes: + // 32 ops when line starts behind triangle + // 38 ops when line ends infront of triangle + // 43 ops when line fraction is already closer than this triangle + // 72 ops when line is outside edge 01 + // 92 ops when line is outside edge 21 + // 115 ops when line is outside edge 02 + // 123 ops when line impacts triangle and updates trace results + + // this code is designed for clockwise triangles, conversion to + // counterclockwise would require swapping some things around... + // it is easier to simply swap the point0 and point2 parameters to this + // function when calling it than it is to rewire the internals. + + // calculate the faceplanenormal of the triangle, this represents the front side + // 15 ops + VectorSubtract(point0, point1, edge01); + VectorSubtract(point2, point1, edge21); + CrossProduct(edge01, edge21, faceplanenormal); + // there's no point in processing a degenerate triangle (GIGO - Garbage In, Garbage Out) + // 6 ops + faceplanenormallength2 = DotProduct(faceplanenormal, faceplanenormal); + if (faceplanenormallength2 < 0.0001f) + return; + // calculate the distance + // 5 ops + faceplanedist = DotProduct(point0, faceplanenormal); + + // if start point is on the back side there is no collision + // (we don't care about traces going through the triangle the wrong way) + + // calculate the start distance + // 6 ops + d1 = DotProduct(faceplanenormal, linestart); + if (d1 <= faceplanedist) + return; + + // calculate the end distance + // 6 ops + d2 = DotProduct(faceplanenormal, lineend); + // if both are in front, there is no collision + if (d2 >= faceplanedist) + return; + + // from here on we know d1 is >= 0 and d2 is < 0 + // this means the line starts infront and ends behind, passing through it + + // calculate the recipricol of the distance delta, + // so we can use it multiple times cheaply (instead of division) + // 2 ops + d = 1.0f / (d1 - d2); + // calculate the impact fraction by taking the start distance (> 0) + // and subtracting the face plane distance (this is the distance of the + // triangle along that same normal) + // then multiply by the recipricol distance delta + // 2 ops + f = (d1 - faceplanedist) * d; + // skip out if this impact is further away than previous ones + // 1 ops + if (f > trace->realfraction) + return; + // calculate the perfect impact point for classification of insidedness + // 9 ops + impact[0] = linestart[0] + f * (lineend[0] - linestart[0]); + impact[1] = linestart[1] + f * (lineend[1] - linestart[1]); + impact[2] = linestart[2] + f * (lineend[2] - linestart[2]); + + // calculate the edge normal and reject if impact is outside triangle + // (an edge normal faces away from the triangle, to get the desired normal + // a crossproduct with the faceplanenormal is used, and because of the way + // the insidedness comparison is written it does not need to be normalized) + + // first use the two edges from the triangle plane math + // the other edge only gets calculated if the point survives that long + + // 20 ops + CrossProduct(edge01, faceplanenormal, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point1, edgenormal)) + return; + + // 20 ops + CrossProduct(faceplanenormal, edge21, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point2, edgenormal)) + return; + + // 23 ops + VectorSubtract(point0, point2, edge02); + CrossProduct(faceplanenormal, edge02, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point0, edgenormal)) + return; + + // 8 ops (rare) + + // store the new trace fraction + trace->realfraction = f; + + // calculate a nudged fraction to keep it out of the surface + // (the main fraction remains perfect) + trace->fraction = f - collision_impactnudge.value * d; + + if (collision_prefernudgedfraction.integer) + trace->realfraction = trace->fraction; + + // store the new trace plane (because collisions only happen from + // the front this is always simply the triangle normal, never flipped) + d = 1.0 / sqrt(faceplanenormallength2); + VectorScale(faceplanenormal, d, trace->plane.normal); + trace->plane.dist = faceplanedist * d; + + trace->hitsupercontents = supercontents; + trace->hitq3surfaceflags = q3surfaceflags; + trace->hittexture = texture; +#else + float d1, d2, d, f, fnudged, impact[3], edgenormal[3], faceplanenormal[3], faceplanedist, edge[3]; + + // this code is designed for clockwise triangles, conversion to + // counterclockwise would require swapping some things around... + // it is easier to simply swap the point0 and point2 parameters to this + // function when calling it than it is to rewire the internals. + + // calculate the unnormalized faceplanenormal of the triangle, + // this represents the front side + TriangleNormal(point0, point1, point2, faceplanenormal); + // there's no point in processing a degenerate triangle + // (GIGO - Garbage In, Garbage Out) + if (DotProduct(faceplanenormal, faceplanenormal) < 0.0001f) + return; + // calculate the unnormalized distance + faceplanedist = DotProduct(point0, faceplanenormal); + + // calculate the unnormalized start distance + d1 = DotProduct(faceplanenormal, linestart) - faceplanedist; + // if start point is on the back side there is no collision + // (we don't care about traces going through the triangle the wrong way) + if (d1 <= 0) + return; + + // calculate the unnormalized end distance + d2 = DotProduct(faceplanenormal, lineend) - faceplanedist; + // if both are in front, there is no collision + if (d2 >= 0) + return; + + // from here on we know d1 is >= 0 and d2 is < 0 + // this means the line starts infront and ends behind, passing through it + + // calculate the recipricol of the distance delta, + // so we can use it multiple times cheaply (instead of division) + d = 1.0f / (d1 - d2); + // calculate the impact fraction by taking the start distance (> 0) + // and subtracting the face plane distance (this is the distance of the + // triangle along that same normal) + // then multiply by the recipricol distance delta + f = d1 * d; + // skip out if this impact is further away than previous ones + if (f > trace->realfraction) + return; + // calculate the perfect impact point for classification of insidedness + impact[0] = linestart[0] + f * (lineend[0] - linestart[0]); + impact[1] = linestart[1] + f * (lineend[1] - linestart[1]); + impact[2] = linestart[2] + f * (lineend[2] - linestart[2]); + + // calculate the edge normal and reject if impact is outside triangle + // (an edge normal faces away from the triangle, to get the desired normal + // a crossproduct with the faceplanenormal is used, and because of the way + // the insidedness comparison is written it does not need to be normalized) + + VectorSubtract(point2, point0, edge); + CrossProduct(edge, faceplanenormal, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point0, edgenormal)) + return; + + VectorSubtract(point0, point1, edge); + CrossProduct(edge, faceplanenormal, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point1, edgenormal)) + return; + + VectorSubtract(point1, point2, edge); + CrossProduct(edge, faceplanenormal, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point2, edgenormal)) + return; + + // store the new trace fraction + trace->realfraction = bound(0, f, 1); + + // store the new trace plane (because collisions only happen from + // the front this is always simply the triangle normal, never flipped) + VectorNormalize(faceplanenormal); + VectorCopy(faceplanenormal, trace->plane.normal); + trace->plane.dist = DotProduct(point0, faceplanenormal); + + // calculate the normalized start and end distances + d1 = DotProduct(trace->plane.normal, linestart) - trace->plane.dist; + d2 = DotProduct(trace->plane.normal, lineend) - trace->plane.dist; + + // calculate a nudged fraction to keep it out of the surface + // (the main fraction remains perfect) + fnudged = (d1 - collision_impactnudge.value) / (d1 - d2); + trace->fraction = bound(0, fnudged, 1); + + // store the new trace endpos + // not needed, it's calculated later when the trace is finished + //trace->endpos[0] = linestart[0] + fnudged * (lineend[0] - linestart[0]); + //trace->endpos[1] = linestart[1] + fnudged * (lineend[1] - linestart[1]); + //trace->endpos[2] = linestart[2] + fnudged * (lineend[2] - linestart[2]); + trace->hitsupercontents = supercontents; + trace->hitq3surfaceflags = q3surfaceflags; + trace->hittexture = texture; +#endif +} + +typedef struct colbspnode_s +{ + mplane_t plane; + struct colbspnode_s *children[2]; + // the node is reallocated or split if max is reached + int numcolbrushf; + int maxcolbrushf; + colbrushf_t **colbrushflist; + //int numcolbrushd; + //int maxcolbrushd; + //colbrushd_t **colbrushdlist; +} +colbspnode_t; + +typedef struct colbsp_s +{ + mempool_t *mempool; + colbspnode_t *nodes; +} +colbsp_t; + +colbsp_t *Collision_CreateCollisionBSP(mempool_t *mempool) +{ + colbsp_t *bsp; + bsp = (colbsp_t *)Mem_Alloc(mempool, sizeof(colbsp_t)); + bsp->mempool = mempool; + bsp->nodes = (colbspnode_t *)Mem_Alloc(bsp->mempool, sizeof(colbspnode_t)); + return bsp; +} + +void Collision_FreeCollisionBSPNode(colbspnode_t *node) +{ + if (node->children[0]) + Collision_FreeCollisionBSPNode(node->children[0]); + if (node->children[1]) + Collision_FreeCollisionBSPNode(node->children[1]); + while (--node->numcolbrushf) + Mem_Free(node->colbrushflist[node->numcolbrushf]); + //while (--node->numcolbrushd) + // Mem_Free(node->colbrushdlist[node->numcolbrushd]); + Mem_Free(node); +} + +void Collision_FreeCollisionBSP(colbsp_t *bsp) +{ + Collision_FreeCollisionBSPNode(bsp->nodes); + Mem_Free(bsp); +} + +void Collision_BoundingBoxOfBrushTraceSegment(const colbrushf_t *start, const colbrushf_t *end, vec3_t mins, vec3_t maxs, float startfrac, float endfrac) +{ + int i; + colpointf_t *ps, *pe; + float tempstart[3], tempend[3]; + VectorLerp(start->points[0].v, startfrac, end->points[0].v, mins); + VectorCopy(mins, maxs); + for (i = 0, ps = start->points, pe = end->points;i < start->numpoints;i++, ps++, pe++) + { + VectorLerp(ps->v, startfrac, pe->v, tempstart); + VectorLerp(ps->v, endfrac, pe->v, tempend); + mins[0] = min(mins[0], min(tempstart[0], tempend[0])); + mins[1] = min(mins[1], min(tempstart[1], tempend[1])); + mins[2] = min(mins[2], min(tempstart[2], tempend[2])); + maxs[0] = min(maxs[0], min(tempstart[0], tempend[0])); + maxs[1] = min(maxs[1], min(tempstart[1], tempend[1])); + maxs[2] = min(maxs[2], min(tempstart[2], tempend[2])); + } + mins[0] -= 1; + mins[1] -= 1; + mins[2] -= 1; + maxs[0] += 1; + maxs[1] += 1; + maxs[2] += 1; +} + +//=========================================== + +void Collision_TranslateBrush(const vec3_t shift, colbrushf_t *brush) +{ + int i; + // now we can transform the data + for(i = 0; i < brush->numplanes; ++i) + { + brush->planes[i].dist += DotProduct(shift, brush->planes[i].normal); + } + for(i = 0; i < brush->numpoints; ++i) + { + VectorAdd(brush->points[i].v, shift, brush->points[i].v); + } + VectorAdd(brush->mins, shift, brush->mins); + VectorAdd(brush->maxs, shift, brush->maxs); +} + +void Collision_TransformBrush(const matrix4x4_t *matrix, colbrushf_t *brush) +{ + int i; + vec3_t v; + // we're breaking any AABB properties here... + brush->isaabb = false; + brush->hasaabbplanes = false; + // now we can transform the data + for(i = 0; i < brush->numplanes; ++i) + { + Matrix4x4_TransformPositivePlane(matrix, brush->planes[i].normal[0], brush->planes[i].normal[1], brush->planes[i].normal[2], brush->planes[i].dist, brush->planes[i].normal); + } + for(i = 0; i < brush->numedgedirs; ++i) + { + Matrix4x4_Transform(matrix, brush->edgedirs[i].v, v); + VectorCopy(v, brush->edgedirs[i].v); + } + for(i = 0; i < brush->numpoints; ++i) + { + Matrix4x4_Transform(matrix, brush->points[i].v, v); + VectorCopy(v, brush->points[i].v); + } + VectorCopy(brush->points[0].v, brush->mins); + VectorCopy(brush->points[0].v, brush->maxs); + for(i = 1; i < brush->numpoints; ++i) + { + if(brush->points[i].v[0] < brush->mins[0]) brush->mins[0] = brush->points[i].v[0]; + if(brush->points[i].v[1] < brush->mins[1]) brush->mins[1] = brush->points[i].v[1]; + if(brush->points[i].v[2] < brush->mins[2]) brush->mins[2] = brush->points[i].v[2]; + if(brush->points[i].v[0] > brush->maxs[0]) brush->maxs[0] = brush->points[i].v[0]; + if(brush->points[i].v[1] > brush->maxs[1]) brush->maxs[1] = brush->points[i].v[1]; + if(brush->points[i].v[2] > brush->maxs[2]) brush->maxs[2] = brush->points[i].v[2]; + } +} + +typedef struct collision_cachedtrace_parameters_s +{ + dp_model_t *model; + vec3_t end; + vec3_t start; + vec3_t mins; + vec3_t maxs; +// const frameblend_t *frameblend; +// const skeleton_t *skeleton; +// matrix4x4_t inversematrix; + int hitsupercontentsmask; + int type; // which type of query produced this cache entry + matrix4x4_t matrix; + vec3_t bodymins; + vec3_t bodymaxs; + int bodysupercontents; +} +collision_cachedtrace_parameters_t; + +typedef struct collision_cachedtrace_s +{ + qboolean valid; + collision_cachedtrace_parameters_t p; + trace_t result; +} +collision_cachedtrace_t; + +static mempool_t *collision_cachedtrace_mempool; +static collision_cachedtrace_t *collision_cachedtrace_array; +static int collision_cachedtrace_firstfree; +static int collision_cachedtrace_lastused; +static int collision_cachedtrace_max; +static int collision_cachedtrace_sequence; +static int collision_cachedtrace_hashsize; +static int *collision_cachedtrace_hash; +static unsigned int *collision_cachedtrace_arrayfullhashindex; +static unsigned int *collision_cachedtrace_arrayhashindex; +static unsigned int *collision_cachedtrace_arraynext; +static unsigned char *collision_cachedtrace_arrayused; +static qboolean collision_cachedtrace_rebuildhash; + +void Collision_Cache_Reset(qboolean resetlimits) +{ + if (collision_cachedtrace_hash) + Mem_Free(collision_cachedtrace_hash); + if (collision_cachedtrace_array) + Mem_Free(collision_cachedtrace_array); + if (collision_cachedtrace_arrayfullhashindex) + Mem_Free(collision_cachedtrace_arrayfullhashindex); + if (collision_cachedtrace_arrayhashindex) + Mem_Free(collision_cachedtrace_arrayhashindex); + if (collision_cachedtrace_arraynext) + Mem_Free(collision_cachedtrace_arraynext); + if (collision_cachedtrace_arrayused) + Mem_Free(collision_cachedtrace_arrayused); + if (resetlimits || !collision_cachedtrace_max) + collision_cachedtrace_max = collision_cache.integer ? 128 : 1; + collision_cachedtrace_firstfree = 1; + collision_cachedtrace_lastused = 0; + collision_cachedtrace_hashsize = collision_cachedtrace_max; + collision_cachedtrace_array = (collision_cachedtrace_t *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(collision_cachedtrace_t)); + collision_cachedtrace_hash = (int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_hashsize * sizeof(int)); + collision_cachedtrace_arrayfullhashindex = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); + collision_cachedtrace_arrayhashindex = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); + collision_cachedtrace_arraynext = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); + collision_cachedtrace_arrayused = (unsigned char *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned char)); + collision_cachedtrace_sequence = 1; + collision_cachedtrace_rebuildhash = false; +} + +void Collision_Cache_Init(mempool_t *mempool) +{ + collision_cachedtrace_mempool = mempool; + Collision_Cache_Reset(true); +} + +void Collision_Cache_RebuildHash(void) +{ + int index; + int range = collision_cachedtrace_lastused + 1; + int sequence = collision_cachedtrace_sequence; + int firstfree = collision_cachedtrace_max; + int lastused = 0; + int *hash = collision_cachedtrace_hash; + unsigned int hashindex; + unsigned int *arrayhashindex = collision_cachedtrace_arrayhashindex; + unsigned int *arraynext = collision_cachedtrace_arraynext; + collision_cachedtrace_rebuildhash = false; + memset(collision_cachedtrace_hash, 0, collision_cachedtrace_hashsize * sizeof(int)); + for (index = 1;index < range;index++) + { + if (collision_cachedtrace_arrayused[index] == sequence) + { + hashindex = arrayhashindex[index]; + arraynext[index] = hash[hashindex]; + hash[hashindex] = index; + lastused = index; + } + else + { + if (firstfree > index) + firstfree = index; + collision_cachedtrace_arrayused[index] = 0; + } + } + collision_cachedtrace_firstfree = firstfree; + collision_cachedtrace_lastused = lastused; +} + +void Collision_Cache_NewFrame(void) +{ + if (collision_cache.integer) + { + if (collision_cachedtrace_max < 128) + Collision_Cache_Reset(true); + } + else + { + if (collision_cachedtrace_max > 1) + Collision_Cache_Reset(true); + } + // rebuild hash if sequence would overflow byte, otherwise increment + if (collision_cachedtrace_sequence == 255) + { + Collision_Cache_RebuildHash(); + collision_cachedtrace_sequence = 1; + } + else + { + collision_cachedtrace_rebuildhash = true; + collision_cachedtrace_sequence++; + } +} + +static unsigned int Collision_Cache_HashIndexForArray(unsigned int *array, unsigned int size) +{ + unsigned int i; + unsigned int hashindex = 0; + // this is a super-cheesy checksum, designed only for speed + for (i = 0;i < size;i++) + hashindex += array[i] * (1 + i); + return hashindex; +} + +static collision_cachedtrace_t *Collision_Cache_Lookup(int type, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask) +{ + int hashindex = 0; + unsigned int fullhashindex; + int index = 0; + int range; + int sequence = collision_cachedtrace_sequence; + int *hash = collision_cachedtrace_hash; + unsigned int *arrayfullhashindex = collision_cachedtrace_arrayfullhashindex; + unsigned int *arraynext = collision_cachedtrace_arraynext; + collision_cachedtrace_t *cached = collision_cachedtrace_array + index; + collision_cachedtrace_parameters_t params; + // all non-cached traces use the same index + if ((frameblend && frameblend[0].lerp != 1) || (skeleton && skeleton->relativetransforms)) + r_refdef.stats.collisioncache_animated++; + else if (!collision_cache.integer) + r_refdef.stats.collisioncache_traced++; + else + { + // cached trace lookup + memset(¶ms, 0, sizeof(params)); + params.type = type; + params.model = model; + VectorCopy(bodymins, params.bodymins); + VectorCopy(bodymaxs, params.bodymaxs); + params.bodysupercontents = bodysupercontents; + VectorCopy(start, params.start); + VectorCopy(mins, params.mins); + VectorCopy(maxs, params.maxs); + VectorCopy(end, params.end); + params.hitsupercontentsmask = hitsupercontentsmask; + params.matrix = *matrix; + //params.inversematrix = *inversematrix; + fullhashindex = Collision_Cache_HashIndexForArray((unsigned int *)¶ms, sizeof(params) / sizeof(unsigned int)); + //fullhashindex = Collision_Cache_HashIndexForArray((unsigned int *)¶ms, 10); + hashindex = (int)(fullhashindex % (unsigned int)collision_cachedtrace_hashsize); + for (index = hash[hashindex];index;index = arraynext[index]) + { + if (arrayfullhashindex[index] != fullhashindex) + continue; + cached = collision_cachedtrace_array + index; + //if (memcmp(&cached->p, ¶ms, sizeof(params))) + if (cached->p.model != params.model + || cached->p.end[0] != params.end[0] + || cached->p.end[1] != params.end[1] + || cached->p.end[2] != params.end[2] + || cached->p.start[0] != params.start[0] + || cached->p.start[1] != params.start[1] + || cached->p.start[2] != params.start[2] + || cached->p.mins[0] != params.mins[0] + || cached->p.mins[1] != params.mins[1] + || cached->p.mins[2] != params.mins[2] + || cached->p.maxs[0] != params.maxs[0] + || cached->p.maxs[1] != params.maxs[1] + || cached->p.maxs[2] != params.maxs[2] + || cached->p.type != params.type + || cached->p.bodysupercontents != params.bodysupercontents + || cached->p.bodymins[0] != params.bodymins[0] + || cached->p.bodymins[1] != params.bodymins[1] + || cached->p.bodymins[2] != params.bodymins[2] + || cached->p.bodymaxs[0] != params.bodymaxs[0] + || cached->p.bodymaxs[1] != params.bodymaxs[1] + || cached->p.bodymaxs[2] != params.bodymaxs[2] + || cached->p.hitsupercontentsmask != params.hitsupercontentsmask + || cached->p.matrix.m[0][0] != params.matrix.m[0][0] + || cached->p.matrix.m[0][1] != params.matrix.m[0][1] + || cached->p.matrix.m[0][2] != params.matrix.m[0][2] + || cached->p.matrix.m[0][3] != params.matrix.m[0][3] + || cached->p.matrix.m[1][0] != params.matrix.m[1][0] + || cached->p.matrix.m[1][1] != params.matrix.m[1][1] + || cached->p.matrix.m[1][2] != params.matrix.m[1][2] + || cached->p.matrix.m[1][3] != params.matrix.m[1][3] + || cached->p.matrix.m[2][0] != params.matrix.m[2][0] + || cached->p.matrix.m[2][1] != params.matrix.m[2][1] + || cached->p.matrix.m[2][2] != params.matrix.m[2][2] + || cached->p.matrix.m[2][3] != params.matrix.m[2][3] + || cached->p.matrix.m[3][0] != params.matrix.m[3][0] + || cached->p.matrix.m[3][1] != params.matrix.m[3][1] + || cached->p.matrix.m[3][2] != params.matrix.m[3][2] + || cached->p.matrix.m[3][3] != params.matrix.m[3][3] + ) + continue; + // found a matching trace in the cache + r_refdef.stats.collisioncache_cached++; + cached->valid = true; + collision_cachedtrace_arrayused[index] = collision_cachedtrace_sequence; + return cached; + } + r_refdef.stats.collisioncache_traced++; + // find an unused cache entry + for (index = collision_cachedtrace_firstfree, range = collision_cachedtrace_max;index < range;index++) + if (collision_cachedtrace_arrayused[index] == 0) + break; + if (index == range) + { + // all claimed, but probably some are stale... + for (index = 1, range = collision_cachedtrace_max;index < range;index++) + if (collision_cachedtrace_arrayused[index] != sequence) + break; + if (index < range) + { + // found a stale one, rebuild the hash + Collision_Cache_RebuildHash(); + } + else + { + // we need to grow the cache + collision_cachedtrace_max *= 2; + Collision_Cache_Reset(false); + index = 1; + } + } + // link the new cache entry into the hash bucket + collision_cachedtrace_firstfree = index + 1; + if (collision_cachedtrace_lastused < index) + collision_cachedtrace_lastused = index; + cached = collision_cachedtrace_array + index; + collision_cachedtrace_arraynext[index] = collision_cachedtrace_hash[hashindex]; + collision_cachedtrace_hash[hashindex] = index; + collision_cachedtrace_arrayhashindex[index] = hashindex; + cached->valid = false; + cached->p = params; + collision_cachedtrace_arrayfullhashindex[index] = fullhashindex; + collision_cachedtrace_arrayused[index] = collision_cachedtrace_sequence; + } + return cached; +} + +void Collision_ClipToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask) +{ + float starttransformed[3], endtransformed[3]; + collision_cachedtrace_t *cached = Collision_Cache_Lookup(3, model, frameblend, skeleton, bodymins, bodymaxs, bodysupercontents, matrix, inversematrix, start, mins, maxs, end, hitsupercontentsmask); + if (cached->valid) + { + *trace = cached->result; + return; + } + + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + + Matrix4x4_Transform(inversematrix, start, starttransformed); + Matrix4x4_Transform(inversematrix, end, endtransformed); +#if COLLISIONPARANOID >= 3 + Con_Printf("trans(%f %f %f -> %f %f %f, %f %f %f -> %f %f %f)", start[0], start[1], start[2], starttransformed[0], starttransformed[1], starttransformed[2], end[0], end[1], end[2], endtransformed[0], endtransformed[1], endtransformed[2]); +#endif + + if (model && model->TraceBox) + { + if(model->TraceBrush && (inversematrix->m[0][1] || inversematrix->m[0][2] || inversematrix->m[1][0] || inversematrix->m[1][2] || inversematrix->m[2][0] || inversematrix->m[2][1])) + { + // we get here if TraceBrush exists, AND we have a rotation component (SOLID_BSP case) + // using starttransformed, endtransformed is WRONG in this case! + // should rather build a brush and trace using it + colboxbrushf_t thisbrush_start, thisbrush_end; + Collision_BrushForBox(&thisbrush_start, mins, maxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, mins, maxs, 0, 0, NULL); + Collision_TranslateBrush(start, &thisbrush_start.brush); + Collision_TranslateBrush(end, &thisbrush_end.brush); + Collision_TransformBrush(inversematrix, &thisbrush_start.brush); + Collision_TransformBrush(inversematrix, &thisbrush_end.brush); + //Collision_TranslateBrush(starttransformed, &thisbrush_start.brush); + //Collision_TranslateBrush(endtransformed, &thisbrush_end.brush); + model->TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask); + } + else // this is only approximate if rotated, quite useless + model->TraceBox(model, frameblend, skeleton, trace, starttransformed, mins, maxs, endtransformed, hitsupercontentsmask); + } + else // and this requires that the transformation matrix doesn't have angles components, like SV_TraceBox ensures; FIXME may get called if a model is SOLID_BSP but has no TraceBox function + Collision_ClipTrace_Box(trace, bodymins, bodymaxs, starttransformed, mins, maxs, endtransformed, hitsupercontentsmask, bodysupercontents, 0, NULL); + trace->fraction = bound(0, trace->fraction, 1); + trace->realfraction = bound(0, trace->realfraction, 1); + + VectorLerp(start, trace->fraction, end, trace->endpos); + // transform plane + // NOTE: this relies on plane.dist being directly after plane.normal + Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal); + + cached->result = *trace; +} + +void Collision_ClipToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontents) +{ + collision_cachedtrace_t *cached = Collision_Cache_Lookup(3, model, NULL, NULL, vec3_origin, vec3_origin, 0, &identitymatrix, &identitymatrix, start, mins, maxs, end, hitsupercontents); + if (cached->valid) + { + *trace = cached->result; + return; + } + + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + // ->TraceBox: TraceBrush not needed here, as worldmodel is never rotated + if (model && model->TraceBox) + model->TraceBox(model, NULL, NULL, trace, start, mins, maxs, end, hitsupercontents); + trace->fraction = bound(0, trace->fraction, 1); + trace->realfraction = bound(0, trace->realfraction, 1); + VectorLerp(start, trace->fraction, end, trace->endpos); + + cached->result = *trace; +} + +void Collision_ClipLineToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask, qboolean hitsurfaces) +{ + float starttransformed[3], endtransformed[3]; + collision_cachedtrace_t *cached = Collision_Cache_Lookup(2, model, frameblend, skeleton, bodymins, bodymaxs, bodysupercontents, matrix, inversematrix, start, vec3_origin, vec3_origin, end, hitsupercontentsmask); + if (cached->valid) + { + *trace = cached->result; + return; + } + + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + + Matrix4x4_Transform(inversematrix, start, starttransformed); + Matrix4x4_Transform(inversematrix, end, endtransformed); +#if COLLISIONPARANOID >= 3 + Con_Printf("trans(%f %f %f -> %f %f %f, %f %f %f -> %f %f %f)", start[0], start[1], start[2], starttransformed[0], starttransformed[1], starttransformed[2], end[0], end[1], end[2], endtransformed[0], endtransformed[1], endtransformed[2]); +#endif + + if (model && model->TraceLineAgainstSurfaces && hitsurfaces) + model->TraceLineAgainstSurfaces(model, frameblend, skeleton, trace, starttransformed, endtransformed, hitsupercontentsmask); + else if (model && model->TraceLine) + model->TraceLine(model, frameblend, skeleton, trace, starttransformed, endtransformed, hitsupercontentsmask); + else + Collision_ClipTrace_Box(trace, bodymins, bodymaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, hitsupercontentsmask, bodysupercontents, 0, NULL); + trace->fraction = bound(0, trace->fraction, 1); + trace->realfraction = bound(0, trace->realfraction, 1); + + VectorLerp(start, trace->fraction, end, trace->endpos); + // transform plane + // NOTE: this relies on plane.dist being directly after plane.normal + Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal); + + cached->result = *trace; +} + +void Collision_ClipLineToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontents, qboolean hitsurfaces) +{ + collision_cachedtrace_t *cached = Collision_Cache_Lookup(2, model, NULL, NULL, vec3_origin, vec3_origin, 0, &identitymatrix, &identitymatrix, start, vec3_origin, vec3_origin, end, hitsupercontents); + if (cached->valid) + { + *trace = cached->result; + return; + } + + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + if (model && model->TraceLineAgainstSurfaces && hitsurfaces) + model->TraceLineAgainstSurfaces(model, NULL, NULL, trace, start, end, hitsupercontents); + else if (model && model->TraceLine) + model->TraceLine(model, NULL, NULL, trace, start, end, hitsupercontents); + trace->fraction = bound(0, trace->fraction, 1); + trace->realfraction = bound(0, trace->realfraction, 1); + VectorLerp(start, trace->fraction, end, trace->endpos); + + cached->result = *trace; +} + +void Collision_ClipPointToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, int hitsupercontentsmask) +{ + float starttransformed[3]; + collision_cachedtrace_t *cached = Collision_Cache_Lookup(1, model, frameblend, skeleton, bodymins, bodymaxs, bodysupercontents, matrix, inversematrix, start, vec3_origin, vec3_origin, start, hitsupercontentsmask); + if (cached->valid) + { + *trace = cached->result; + return; + } + + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + + Matrix4x4_Transform(inversematrix, start, starttransformed); +#if COLLISIONPARANOID >= 3 + Con_Printf("trans(%f %f %f -> %f %f %f)", start[0], start[1], start[2], starttransformed[0], starttransformed[1], starttransformed[2]); +#endif + + if (model && model->TracePoint) + model->TracePoint(model, NULL, NULL, trace, starttransformed, hitsupercontentsmask); + else + Collision_ClipTrace_Point(trace, bodymins, bodymaxs, starttransformed, hitsupercontentsmask, bodysupercontents, 0, NULL); + + VectorCopy(start, trace->endpos); + // transform plane + // NOTE: this relies on plane.dist being directly after plane.normal + Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal); + + cached->result = *trace; +} + +void Collision_ClipPointToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, int hitsupercontents) +{ + collision_cachedtrace_t *cached = Collision_Cache_Lookup(1, model, NULL, NULL, vec3_origin, vec3_origin, 0, &identitymatrix, &identitymatrix, start, vec3_origin, vec3_origin, start, hitsupercontents); + if (cached->valid) + { + *trace = cached->result; + return; + } + + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + if (model && model->TracePoint) + model->TracePoint(model, NULL, NULL, trace, start, hitsupercontents); + VectorCopy(start, trace->endpos); + + cached->result = *trace; +} + +void Collision_CombineTraces(trace_t *cliptrace, const trace_t *trace, void *touch, qboolean isbmodel) +{ + // take the 'best' answers from the new trace and combine with existing data + if (trace->allsolid) + cliptrace->allsolid = true; + if (trace->startsolid) + { + if (isbmodel) + cliptrace->bmodelstartsolid = true; + cliptrace->startsolid = true; + if (cliptrace->realfraction == 1) + cliptrace->ent = touch; + if (cliptrace->startdepth > trace->startdepth) + { + cliptrace->startdepth = trace->startdepth; + VectorCopy(trace->startdepthnormal, cliptrace->startdepthnormal); + } + } + // don't set this except on the world, because it can easily confuse + // monsters underwater if there's a bmodel involved in the trace + // (inopen && inwater is how they check water visibility) + //if (trace->inopen) + // cliptrace->inopen = true; + if (trace->inwater) + cliptrace->inwater = true; + if ((trace->realfraction <= cliptrace->realfraction) && (VectorLength2(trace->plane.normal) > 0)) + { + cliptrace->fraction = trace->fraction; + cliptrace->realfraction = trace->realfraction; + VectorCopy(trace->endpos, cliptrace->endpos); + cliptrace->plane = trace->plane; + cliptrace->ent = touch; + cliptrace->hitsupercontents = trace->hitsupercontents; + cliptrace->hitq3surfaceflags = trace->hitq3surfaceflags; + cliptrace->hittexture = trace->hittexture; + } + cliptrace->startsupercontents |= trace->startsupercontents; +} + +void Collision_ShortenTrace(trace_t *trace, float shorten_factor, const vec3_t end) +{ + // now undo our moving end 1 qu farther... + trace->fraction = bound(trace->fraction, trace->fraction / shorten_factor - 1e-6, 1); // we subtract 1e-6 to guard for roundoff errors + trace->realfraction = bound(trace->realfraction, trace->realfraction / shorten_factor - 1e-6, 1); // we subtract 1e-6 to guard for roundoff errors + if(trace->fraction >= 1) // trace would NOT hit if not expanded! + { + trace->fraction = 1; + trace->realfraction = 1; + VectorCopy(end, trace->endpos); + memset(&trace->plane, 0, sizeof(trace->plane)); + trace->ent = NULL; + trace->hitsupercontentsmask = 0; + trace->hitsupercontents = 0; + trace->hitq3surfaceflags = 0; + trace->hittexture = NULL; + } +} diff --git a/misc/source/darkplaces-src/collision.h b/misc/source/darkplaces-src/collision.h new file mode 100644 index 00000000..919e9dcd --- /dev/null +++ b/misc/source/darkplaces-src/collision.h @@ -0,0 +1,186 @@ + +#ifndef COLLISION_H +#define COLLISION_H + +typedef struct plane_s +{ + vec3_t normal; + float dist; +} +plane_t; + +struct texture_s; +typedef struct trace_s +{ + // if true, the entire trace was in solid (see hitsupercontentsmask) + int allsolid; + // if true, the initial point was in solid (see hitsupercontentsmask) + int startsolid; + // this is set to true in world.c if startsolid was set in a trace against a SOLID_BSP entity, in other words this is true if the entity is stuck in a door or wall, but not if stuck in another normal entity + int bmodelstartsolid; + // if true, the trace passed through empty somewhere + // (set only by Q1BSP tracing) + int inopen; + // if true, the trace passed through water/slime/lava somewhere + // (set only by Q1BSP tracing) + int inwater; + // fraction of the total distance that was traveled before impact + // (1.0 = did not hit anything) + double fraction; + // like fraction but is not nudged away from the surface (better for + // comparisons between two trace structs, as only one nudge for the final + // result is ever needed) + double realfraction; + // final position of the trace (simply a point between start and end) + double endpos[3]; + // surface normal at impact (not really correct for edge collisions) + plane_t plane; + // entity the surface is on + // (not set by trace functions, only by physics) + void *ent; + // which SUPERCONTENTS bits to collide with, I.E. to consider solid + // (this also affects startsolid/allsolid) + int hitsupercontentsmask; + // the supercontents mask at the start point + int startsupercontents; + // the supercontents of the impacted surface + int hitsupercontents; + // the q3 surfaceflags of the impacted surface + int hitq3surfaceflags; + // the texture of the impacted surface + const struct texture_s *hittexture; + // initially false, set when the start leaf is found + // (set only by Q1BSP tracing and entity box tracing) + int startfound; + // if startsolid, contains the minimum penetration depth found in the + // trace, and the normal needed to push it out of that solid + double startdepth; + double startdepthnormal[3]; +} +trace_t; + +void Collision_Init(void); +void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture); +void Collision_ClipTrace_Point(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture); + +void Collision_Cache_Reset(qboolean resetlimits); +void Collision_Cache_Init(mempool_t *mempool); +void Collision_Cache_NewFrame(void); + +typedef struct colpointf_s +{ + vec3_t v; +} +colpointf_t; + +typedef struct colplanef_s +{ + const struct texture_s *texture; + int q3surfaceflags; + vec3_t normal; + vec_t dist; +} +colplanef_t; + +typedef struct colbrushf_s +{ + // culling box + vec3_t mins; + vec3_t maxs; + // used to avoid tracing against the same brush more than once per sweep + int markframe; + // the content flags of this brush + int supercontents; + // bounding planes (face planes) of this brush + int numplanes; + colplanef_t *planes; + // edge directions (normals) of this brush + int numedgedirs; + colpointf_t *edgedirs; + // points (corners) of this brush + int numpoints; + colpointf_t *points; + // renderable triangles representing this brush, using the points + int numtriangles; + int *elements; + // texture data for cases where an edgedir is used + const struct texture_s *texture; + int q3surfaceflags; + // optimized collisions for common cases + int isaabb; // indicates this is an axis aligned box + int hasaabbplanes; // indicates this has precomputed planes for AABB collisions +} +colbrushf_t; + +typedef struct colboxbrushf_s +{ + colpointf_t points[8]; + colpointf_t edgedirs[6]; + colplanef_t planes[6]; + colbrushf_t brush; +} +colboxbrushf_t; + +void Collision_CalcPlanesForPolygonBrushFloat(colbrushf_t *brush); +colbrushf_t *Collision_AllocBrushFromPermanentPolygonFloat(mempool_t *mempool, int numpoints, float *points, int supercontents, int q3surfaceflags, const texture_t *texture); +colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalplanes, const colplanef_t *originalplanes, int supercontents, int q3surfaceflags, const texture_t *texture, int hasaabbplanes); +void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const colbrushf_t *thatbrush_start, const colbrushf_t *thatbrush_end); +void Collision_TraceBrushTriangleMeshFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs); +void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const colbrushf_t *thatbrush_start, const colbrushf_t *thatbrush_end); +void Collision_TraceLineTriangleMeshFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs); +void Collision_TracePointBrushFloat(trace_t *trace, const vec3_t point, const colbrushf_t *thatbrush); +qboolean Collision_PointInsideBrushFloat(const vec3_t point, const colbrushf_t *brush); + +void Collision_BrushForBox(colboxbrushf_t *boxbrush, const vec3_t mins, const vec3_t maxs, int supercontents, int q3surfaceflags, const texture_t *texture); + +void Collision_BoundingBoxOfBrushTraceSegment(const colbrushf_t *start, const colbrushf_t *end, vec3_t mins, vec3_t maxs, float startfrac, float endfrac); + +float Collision_ClipTrace_Line_Sphere(double *linestart, double *lineend, double *sphereorigin, double sphereradius, double *impactpoint, double *impactnormal); +void Collision_TraceLineTriangleFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const float *point0, const float *point1, const float *point2, int supercontents, int q3surfaceflags, const texture_t *texture); +void Collision_TraceBrushTriangleFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const float *v0, const float *v1, const float *v2, int supercontents, int q3surfaceflags, const texture_t *texture); + +// traces a box move against a single entity +// mins and maxs are relative +// +// if the entire move stays in a single solid brush, trace.allsolid will be set +// +// if the starting point is in a solid, it will be allowed to move out to an +// open area, and trace.startsolid will be set +// +// type is one of the MOVE_ values such as MOVE_NOMONSTERS which skips box +// entities, only colliding with SOLID_BSP entities (doors, lifts) +// +// passedict is excluded from clipping checks +struct frameblend_s; +struct skeleton_s; +void Collision_ClipToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask); +void Collision_ClipLineToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask, qboolean hitsurfaces); +void Collision_ClipPointToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, int hitsupercontentsmask); +// like above but does not do a transform and does nothing if model is NULL +void Collision_ClipToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontents); +void Collision_ClipLineToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontents, qboolean hitsurfaces); +void Collision_ClipPointToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, int hitsupercontents); +// combines data from two traces: +// merges contents flags, startsolid, allsolid, inwater +// updates fraction, endpos, plane and surface info if new fraction is shorter +void Collision_CombineTraces(trace_t *cliptrace, const trace_t *trace, void *touch, qboolean isbmodel); + +// shorten a trace by the given factor +void Collision_ShortenTrace(trace_t *trace, float shorten_factor, const vec3_t end); + +// this enables rather large debugging spew! +// settings: +// 0 = no spew +// 1 = spew trace calls if something odd is happening +// 2 = spew trace calls always +// 3 = spew detailed trace flow (bsp tree recursion info) +#define COLLISIONPARANOID 0 + +// make every trace qu longer, and shorten the result, to work around a stupid bug somewhere +#define COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +extern cvar_t collision_endposnudge; +#endif + + +#endif diff --git a/misc/source/darkplaces-src/common.c b/misc/source/darkplaces-src/common.c new file mode 100644 index 00000000..69a7a5d8 --- /dev/null +++ b/misc/source/darkplaces-src/common.c @@ -0,0 +1,2334 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// common.c -- misc functions used in client and server + +#include +#include +#ifndef WIN32 +#include +#endif + +#include "quakedef.h" +#include "utf8lib.h" + +cvar_t registered = {0, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"}; +cvar_t cmdline = {0, "cmdline","0", "contains commandline the engine was launched with"}; + +char com_token[MAX_INPUTLINE]; +int com_argc; +const char **com_argv; +int com_selffd = -1; + +gamemode_t gamemode; +const char *gamename; +const char *gamedirname1; +const char *gamedirname2; +const char *gamescreenshotname; +const char *gameuserdirname; +char com_modname[MAX_OSPATH] = ""; + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + + +float BuffBigFloat (const unsigned char *buffer) +{ + union + { + float f; + unsigned int i; + } + u; + u.i = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; + return u.f; +} + +int BuffBigLong (const unsigned char *buffer) +{ + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; +} + +short BuffBigShort (const unsigned char *buffer) +{ + return (buffer[0] << 8) | buffer[1]; +} + +float BuffLittleFloat (const unsigned char *buffer) +{ + union + { + float f; + unsigned int i; + } + u; + u.i = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; + return u.f; +} + +int BuffLittleLong (const unsigned char *buffer) +{ + return (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; +} + +short BuffLittleShort (const unsigned char *buffer) +{ + return (buffer[1] << 8) | buffer[0]; +} + +void StoreBigLong (unsigned char *buffer, unsigned int i) +{ + buffer[0] = (i >> 24) & 0xFF; + buffer[1] = (i >> 16) & 0xFF; + buffer[2] = (i >> 8) & 0xFF; + buffer[3] = i & 0xFF; +} + +void StoreBigShort (unsigned char *buffer, unsigned short i) +{ + buffer[0] = (i >> 8) & 0xFF; + buffer[1] = i & 0xFF; +} + +void StoreLittleLong (unsigned char *buffer, unsigned int i) +{ + buffer[0] = i & 0xFF; + buffer[1] = (i >> 8) & 0xFF; + buffer[2] = (i >> 16) & 0xFF; + buffer[3] = (i >> 24) & 0xFF; +} + +void StoreLittleShort (unsigned char *buffer, unsigned short i) +{ + buffer[0] = i & 0xFF; + buffer[1] = (i >> 8) & 0xFF; +} + +/* +============================================================================ + + CRC FUNCTIONS + +============================================================================ +*/ + +// 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 + +static unsigned short crctable[256] = +{ + 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 +}; + +unsigned short CRC_Block(const unsigned char *data, size_t size) +{ + unsigned short crc = CRC_INIT_VALUE; + while (size--) + crc = (crc << 8) ^ crctable[(crc >> 8) ^ (*data++)]; + return crc ^ CRC_XOR_VALUE; +} + +unsigned short CRC_Block_CaseInsensitive(const unsigned char *data, size_t size) +{ + unsigned short crc = CRC_INIT_VALUE; + while (size--) + crc = (crc << 8) ^ crctable[(crc >> 8) ^ (tolower(*data++))]; + return crc ^ CRC_XOR_VALUE; +} + +// QuakeWorld +static unsigned char chktbl[1024 + 4] = +{ + 0x78,0xd2,0x94,0xe3,0x41,0xec,0xd6,0xd5,0xcb,0xfc,0xdb,0x8a,0x4b,0xcc,0x85,0x01, + 0x23,0xd2,0xe5,0xf2,0x29,0xa7,0x45,0x94,0x4a,0x62,0xe3,0xa5,0x6f,0x3f,0xe1,0x7a, + 0x64,0xed,0x5c,0x99,0x29,0x87,0xa8,0x78,0x59,0x0d,0xaa,0x0f,0x25,0x0a,0x5c,0x58, + 0xfb,0x00,0xa7,0xa8,0x8a,0x1d,0x86,0x80,0xc5,0x1f,0xd2,0x28,0x69,0x71,0x58,0xc3, + 0x51,0x90,0xe1,0xf8,0x6a,0xf3,0x8f,0xb0,0x68,0xdf,0x95,0x40,0x5c,0xe4,0x24,0x6b, + 0x29,0x19,0x71,0x3f,0x42,0x63,0x6c,0x48,0xe7,0xad,0xa8,0x4b,0x91,0x8f,0x42,0x36, + 0x34,0xe7,0x32,0x55,0x59,0x2d,0x36,0x38,0x38,0x59,0x9b,0x08,0x16,0x4d,0x8d,0xf8, + 0x0a,0xa4,0x52,0x01,0xbb,0x52,0xa9,0xfd,0x40,0x18,0x97,0x37,0xff,0xc9,0x82,0x27, + 0xb2,0x64,0x60,0xce,0x00,0xd9,0x04,0xf0,0x9e,0x99,0xbd,0xce,0x8f,0x90,0x4a,0xdd, + 0xe1,0xec,0x19,0x14,0xb1,0xfb,0xca,0x1e,0x98,0x0f,0xd4,0xcb,0x80,0xd6,0x05,0x63, + 0xfd,0xa0,0x74,0xa6,0x86,0xf6,0x19,0x98,0x76,0x27,0x68,0xf7,0xe9,0x09,0x9a,0xf2, + 0x2e,0x42,0xe1,0xbe,0x64,0x48,0x2a,0x74,0x30,0xbb,0x07,0xcc,0x1f,0xd4,0x91,0x9d, + 0xac,0x55,0x53,0x25,0xb9,0x64,0xf7,0x58,0x4c,0x34,0x16,0xbc,0xf6,0x12,0x2b,0x65, + 0x68,0x25,0x2e,0x29,0x1f,0xbb,0xb9,0xee,0x6d,0x0c,0x8e,0xbb,0xd2,0x5f,0x1d,0x8f, + 0xc1,0x39,0xf9,0x8d,0xc0,0x39,0x75,0xcf,0x25,0x17,0xbe,0x96,0xaf,0x98,0x9f,0x5f, + 0x65,0x15,0xc4,0x62,0xf8,0x55,0xfc,0xab,0x54,0xcf,0xdc,0x14,0x06,0xc8,0xfc,0x42, + 0xd3,0xf0,0xad,0x10,0x08,0xcd,0xd4,0x11,0xbb,0xca,0x67,0xc6,0x48,0x5f,0x9d,0x59, + 0xe3,0xe8,0x53,0x67,0x27,0x2d,0x34,0x9e,0x9e,0x24,0x29,0xdb,0x69,0x99,0x86,0xf9, + 0x20,0xb5,0xbb,0x5b,0xb0,0xf9,0xc3,0x67,0xad,0x1c,0x9c,0xf7,0xcc,0xef,0xce,0x69, + 0xe0,0x26,0x8f,0x79,0xbd,0xca,0x10,0x17,0xda,0xa9,0x88,0x57,0x9b,0x15,0x24,0xba, + 0x84,0xd0,0xeb,0x4d,0x14,0xf5,0xfc,0xe6,0x51,0x6c,0x6f,0x64,0x6b,0x73,0xec,0x85, + 0xf1,0x6f,0xe1,0x67,0x25,0x10,0x77,0x32,0x9e,0x85,0x6e,0x69,0xb1,0x83,0x00,0xe4, + 0x13,0xa4,0x45,0x34,0x3b,0x40,0xff,0x41,0x82,0x89,0x79,0x57,0xfd,0xd2,0x8e,0xe8, + 0xfc,0x1d,0x19,0x21,0x12,0x00,0xd7,0x66,0xe5,0xc7,0x10,0x1d,0xcb,0x75,0xe8,0xfa, + 0xb6,0xee,0x7b,0x2f,0x1a,0x25,0x24,0xb9,0x9f,0x1d,0x78,0xfb,0x84,0xd0,0x17,0x05, + 0x71,0xb3,0xc8,0x18,0xff,0x62,0xee,0xed,0x53,0xab,0x78,0xd3,0x65,0x2d,0xbb,0xc7, + 0xc1,0xe7,0x70,0xa2,0x43,0x2c,0x7c,0xc7,0x16,0x04,0xd2,0x45,0xd5,0x6b,0x6c,0x7a, + 0x5e,0xa1,0x50,0x2e,0x31,0x5b,0xcc,0xe8,0x65,0x8b,0x16,0x85,0xbf,0x82,0x83,0xfb, + 0xde,0x9f,0x36,0x48,0x32,0x79,0xd6,0x9b,0xfb,0x52,0x45,0xbf,0x43,0xf7,0x0b,0x0b, + 0x19,0x19,0x31,0xc3,0x85,0xec,0x1d,0x8c,0x20,0xf0,0x3a,0xfa,0x80,0x4d,0x2c,0x7d, + 0xac,0x60,0x09,0xc0,0x40,0xee,0xb9,0xeb,0x13,0x5b,0xe8,0x2b,0xb1,0x20,0xf0,0xce, + 0x4c,0xbd,0xc6,0x04,0x86,0x70,0xc6,0x33,0xc3,0x15,0x0f,0x65,0x19,0xfd,0xc2,0xd3, + + // map checksum goes here + 0x00,0x00,0x00,0x00 +}; + +// QuakeWorld +unsigned char COM_BlockSequenceCRCByteQW(unsigned char *base, int length, int sequence) +{ + unsigned char *p; + unsigned char chkb[60 + 4]; + + p = chktbl + (sequence % (sizeof(chktbl) - 8)); + + if (length > 60) + length = 60; + memcpy(chkb, base, length); + + chkb[length] = (sequence & 0xff) ^ p[0]; + chkb[length+1] = p[1]; + chkb[length+2] = ((sequence>>8) & 0xff) ^ p[2]; + chkb[length+3] = p[3]; + + return CRC_Block(chkb, length + 4) & 0xff; +} + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles byte ordering and avoids alignment errors +============================================================================== +*/ + +// +// writing functions +// + +void MSG_WriteChar (sizebuf_t *sb, int c) +{ + unsigned char *buf; + + buf = SZ_GetSpace (sb, 1); + buf[0] = c; +} + +void MSG_WriteByte (sizebuf_t *sb, int c) +{ + unsigned char *buf; + + buf = SZ_GetSpace (sb, 1); + buf[0] = c; +} + +void MSG_WriteShort (sizebuf_t *sb, int c) +{ + unsigned char *buf; + + buf = SZ_GetSpace (sb, 2); + buf[0] = c&0xff; + buf[1] = c>>8; +} + +void MSG_WriteLong (sizebuf_t *sb, int c) +{ + unsigned char *buf; + + buf = SZ_GetSpace (sb, 4); + buf[0] = c&0xff; + buf[1] = (c>>8)&0xff; + buf[2] = (c>>16)&0xff; + buf[3] = c>>24; +} + +void MSG_WriteFloat (sizebuf_t *sb, float f) +{ + union + { + float f; + int l; + } dat; + + + dat.f = f; + dat.l = LittleLong (dat.l); + + SZ_Write (sb, (unsigned char *)&dat.l, 4); +} + +void MSG_WriteString (sizebuf_t *sb, const char *s) +{ + if (!s || !*s) + MSG_WriteChar (sb, 0); + else + SZ_Write (sb, (unsigned char *)s, (int)strlen(s)+1); +} + +void MSG_WriteUnterminatedString (sizebuf_t *sb, const char *s) +{ + if (s && *s) + SZ_Write (sb, (unsigned char *)s, (int)strlen(s)); +} + +void MSG_WriteCoord13i (sizebuf_t *sb, float f) +{ + if (f >= 0) + MSG_WriteShort (sb, (int)(f * 8.0 + 0.5)); + else + MSG_WriteShort (sb, (int)(f * 8.0 - 0.5)); +} + +void MSG_WriteCoord16i (sizebuf_t *sb, float f) +{ + if (f >= 0) + MSG_WriteShort (sb, (int)(f + 0.5)); + else + MSG_WriteShort (sb, (int)(f - 0.5)); +} + +void MSG_WriteCoord32f (sizebuf_t *sb, float f) +{ + MSG_WriteFloat (sb, f); +} + +void MSG_WriteCoord (sizebuf_t *sb, float f, protocolversion_t protocol) +{ + if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD) + MSG_WriteCoord13i (sb, f); + else if (protocol == PROTOCOL_DARKPLACES1) + MSG_WriteCoord32f (sb, f); + else if (protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4) + MSG_WriteCoord16i (sb, f); + else + MSG_WriteCoord32f (sb, f); +} + +void MSG_WriteVector (sizebuf_t *sb, float *v, protocolversion_t protocol) +{ + MSG_WriteCoord (sb, v[0], protocol); + MSG_WriteCoord (sb, v[1], protocol); + MSG_WriteCoord (sb, v[2], protocol); +} + +// LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem +void MSG_WriteAngle8i (sizebuf_t *sb, float f) +{ + if (f >= 0) + MSG_WriteByte (sb, (int)(f*(256.0/360.0) + 0.5) & 255); + else + MSG_WriteByte (sb, (int)(f*(256.0/360.0) - 0.5) & 255); +} + +void MSG_WriteAngle16i (sizebuf_t *sb, float f) +{ + if (f >= 0) + MSG_WriteShort (sb, (int)(f*(65536.0/360.0) + 0.5) & 65535); + else + MSG_WriteShort (sb, (int)(f*(65536.0/360.0) - 0.5) & 65535); +} + +void MSG_WriteAngle32f (sizebuf_t *sb, float f) +{ + MSG_WriteFloat (sb, f); +} + +void MSG_WriteAngle (sizebuf_t *sb, float f, protocolversion_t protocol) +{ + if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD) + MSG_WriteAngle8i (sb, f); + else + MSG_WriteAngle16i (sb, f); +} + +// +// reading functions +// +int msg_readcount; +qboolean msg_badread; + +void MSG_BeginReading (void) +{ + msg_readcount = 0; + msg_badread = false; +} + +int MSG_ReadLittleShort (void) +{ + if (msg_readcount+2 > net_message.cursize) + { + msg_badread = true; + return -1; + } + msg_readcount += 2; + return (short)(net_message.data[msg_readcount-2] | (net_message.data[msg_readcount-1]<<8)); +} + +int MSG_ReadBigShort (void) +{ + if (msg_readcount+2 > net_message.cursize) + { + msg_badread = true; + return -1; + } + msg_readcount += 2; + return (short)((net_message.data[msg_readcount-2]<<8) + net_message.data[msg_readcount-1]); +} + +int MSG_ReadLittleLong (void) +{ + if (msg_readcount+4 > net_message.cursize) + { + msg_badread = true; + return -1; + } + msg_readcount += 4; + return net_message.data[msg_readcount-4] | (net_message.data[msg_readcount-3]<<8) | (net_message.data[msg_readcount-2]<<16) | (net_message.data[msg_readcount-1]<<24); +} + +int MSG_ReadBigLong (void) +{ + if (msg_readcount+4 > net_message.cursize) + { + msg_badread = true; + return -1; + } + msg_readcount += 4; + return (net_message.data[msg_readcount-4]<<24) + (net_message.data[msg_readcount-3]<<16) + (net_message.data[msg_readcount-2]<<8) + net_message.data[msg_readcount-1]; +} + +float MSG_ReadLittleFloat (void) +{ + union + { + float f; + int l; + } dat; + if (msg_readcount+4 > net_message.cursize) + { + msg_badread = true; + return -1; + } + msg_readcount += 4; + dat.l = net_message.data[msg_readcount-4] | (net_message.data[msg_readcount-3]<<8) | (net_message.data[msg_readcount-2]<<16) | (net_message.data[msg_readcount-1]<<24); + return dat.f; +} + +float MSG_ReadBigFloat (void) +{ + union + { + float f; + int l; + } dat; + if (msg_readcount+4 > net_message.cursize) + { + msg_badread = true; + return -1; + } + msg_readcount += 4; + dat.l = (net_message.data[msg_readcount-4]<<24) | (net_message.data[msg_readcount-3]<<16) | (net_message.data[msg_readcount-2]<<8) | net_message.data[msg_readcount-1]; + return dat.f; +} + +char *MSG_ReadString (void) +{ + static char string[MAX_INPUTLINE]; + const int maxstring = sizeof(string); + int l = 0,c; + // read string into buffer, but only store as many characters as will fit + while ((c = MSG_ReadByte()) > 0) + if (l < maxstring - 1) + string[l++] = c; + string[l] = 0; + return string; +} + +int MSG_ReadBytes (int numbytes, unsigned char *out) +{ + int l, c; + for (l = 0;l < numbytes && (c = MSG_ReadByte()) != -1;l++) + out[l] = c; + return l; +} + +float MSG_ReadCoord13i (void) +{ + return MSG_ReadLittleShort() * (1.0/8.0); +} + +float MSG_ReadCoord16i (void) +{ + return (signed short) MSG_ReadLittleShort(); +} + +float MSG_ReadCoord32f (void) +{ + return MSG_ReadLittleFloat(); +} + +float MSG_ReadCoord (protocolversion_t protocol) +{ + if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD) + return MSG_ReadCoord13i(); + else if (protocol == PROTOCOL_DARKPLACES1) + return MSG_ReadCoord32f(); + else if (protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4) + return MSG_ReadCoord16i(); + else + return MSG_ReadCoord32f(); +} + +void MSG_ReadVector (float *v, protocolversion_t protocol) +{ + v[0] = MSG_ReadCoord(protocol); + v[1] = MSG_ReadCoord(protocol); + v[2] = MSG_ReadCoord(protocol); +} + +// LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem +float MSG_ReadAngle8i (void) +{ + return (signed char) MSG_ReadByte () * (360.0/256.0); +} + +float MSG_ReadAngle16i (void) +{ + return (signed short)MSG_ReadShort () * (360.0/65536.0); +} + +float MSG_ReadAngle32f (void) +{ + return MSG_ReadFloat (); +} + +float MSG_ReadAngle (protocolversion_t protocol) +{ + if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD) + return MSG_ReadAngle8i (); + else + return MSG_ReadAngle16i (); +} + + +//=========================================================================== + +void SZ_Clear (sizebuf_t *buf) +{ + buf->cursize = 0; +} + +unsigned char *SZ_GetSpace (sizebuf_t *buf, int length) +{ + unsigned char *data; + + if (buf->cursize + length > buf->maxsize) + { + if (!buf->allowoverflow) + Host_Error ("SZ_GetSpace: overflow without allowoverflow set"); + + if (length > buf->maxsize) + Host_Error ("SZ_GetSpace: %i is > full buffer size", length); + + buf->overflowed = true; + Con_Print("SZ_GetSpace: overflow\n"); + SZ_Clear (buf); + } + + data = buf->data + buf->cursize; + buf->cursize += length; + + return data; +} + +void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length) +{ + memcpy (SZ_GetSpace(buf,length),data,length); +} + +// LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my +// attention, it has been eradicated from here, its only (former) use in +// all of darkplaces. + +static const char *hexchar = "0123456789ABCDEF"; +void Com_HexDumpToConsole(const unsigned char *data, int size) +{ + int i, j, n; + char text[1024]; + char *cur, *flushpointer; + const unsigned char *d; + cur = text; + flushpointer = text + 512; + for (i = 0;i < size;) + { + n = 16; + if (n > size - i) + n = size - i; + d = data + i; + // print offset + *cur++ = hexchar[(i >> 12) & 15]; + *cur++ = hexchar[(i >> 8) & 15]; + *cur++ = hexchar[(i >> 4) & 15]; + *cur++ = hexchar[(i >> 0) & 15]; + *cur++ = ':'; + // print hex + for (j = 0;j < 16;j++) + { + if (j < n) + { + *cur++ = hexchar[(d[j] >> 4) & 15]; + *cur++ = hexchar[(d[j] >> 0) & 15]; + } + else + { + *cur++ = ' '; + *cur++ = ' '; + } + if ((j & 3) == 3) + *cur++ = ' '; + } + // print text + for (j = 0;j < 16;j++) + { + if (j < n) + { + // color change prefix character has to be treated specially + if (d[j] == STRING_COLOR_TAG) + { + *cur++ = STRING_COLOR_TAG; + *cur++ = STRING_COLOR_TAG; + } + else if (d[j] >= (unsigned char) ' ') + *cur++ = d[j]; + else + *cur++ = '.'; + } + else + *cur++ = ' '; + } + *cur++ = '\n'; + i += n; + if (cur >= flushpointer || i >= size) + { + *cur++ = 0; + Con_Print(text); + cur = text; + } + } +} + +void SZ_HexDumpToConsole(const sizebuf_t *buf) +{ + Com_HexDumpToConsole(buf->data, buf->cursize); +} + + +//============================================================================ + +/* +============== +COM_Wordwrap + +Word wraps a string. The wordWidth function is guaranteed to be called exactly +once for each word in the string, so it may be stateful, no idea what that +would be good for any more. At the beginning of the string, it will be called +for the char 0 to initialize a clean state, and then once with the string " " +(a space) so the routine knows how long a space is. + +In case no single character fits into the given width, the wordWidth function +must return the width of exactly one character. + +Wrapped lines get the isContinuation flag set and are continuationWidth less wide. + +The sum of the return values of the processLine function will be returned. +============== +*/ +int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL) +{ + // Logic is as follows: + // + // For each word or whitespace: + // Newline found? Output current line, advance to next line. This is not a continuation. Continue. + // Space found? Always add it to the current line, no matter if it fits. + // Word found? Check if current line + current word fits. + // If it fits, append it. Continue. + // If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue. + + qboolean isContinuation = false; + float spaceWidth; + const char *startOfLine = string; + const char *cursor = string; + const char *end = string + length; + float spaceUsedInLine = 0; + float spaceUsedForWord; + int result = 0; + size_t wordLen; + size_t dummy; + + dummy = 0; + wordWidth(passthroughCW, NULL, &dummy, -1); + dummy = 1; + spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1); + + for(;;) + { + char ch = (cursor < end) ? *cursor : 0; + switch(ch) + { + case 0: // end of string + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + isContinuation = false; + goto out; + case '\n': // end of line + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + isContinuation = false; + ++cursor; + startOfLine = cursor; + break; + case ' ': // space + ++cursor; + spaceUsedInLine += spaceWidth; + break; + default: // word + wordLen = 1; + while(cursor + wordLen < end) + { + switch(cursor[wordLen]) + { + case 0: + case '\n': + case ' ': + goto out_inner; + default: + ++wordLen; + break; + } + } + out_inner: + spaceUsedForWord = wordWidth(passthroughCW, cursor, &wordLen, maxWidth - continuationWidth); // this may have reduced wordLen when it won't fit - but this is GOOD. TODO fix words that do fit in a non-continuation line + if(wordLen < 1) // cannot happen according to current spec of wordWidth + { + wordLen = 1; + spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself + } + if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine) + { + // we can simply append it + cursor += wordLen; + spaceUsedInLine += spaceUsedForWord; + } + else + { + // output current line + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + isContinuation = true; + startOfLine = cursor; + cursor += wordLen; + spaceUsedInLine = continuationWidth + spaceUsedForWord; + } + } + } + out: + + return result; + +/* + qboolean isContinuation = false; + float currentWordSpace = 0; + const char *currentWord = 0; + float minReserve = 0; + + float spaceUsedInLine = 0; + const char *currentLine = 0; + const char *currentLineEnd = 0; + float currentLineFinalWhitespace = 0; + const char *p; + + int result = 0; + minReserve = charWidth(passthroughCW, 0); + minReserve += charWidth(passthroughCW, ' '); + + if(maxWidth < continuationWidth + minReserve) + maxWidth = continuationWidth + minReserve; + + charWidth(passthroughCW, 0); + + for(p = string; p < string + length; ++p) + { + char c = *p; + float w = charWidth(passthroughCW, c); + + if(!currentWord) + { + currentWord = p; + currentWordSpace = 0; + } + + if(!currentLine) + { + currentLine = p; + spaceUsedInLine = isContinuation ? continuationWidth : 0; + currentLineEnd = 0; + } + + if(c == ' ') + { + // 1. I can add the word AND a space - then just append it. + if(spaceUsedInLine + currentWordSpace + w <= maxWidth) + { + currentLineEnd = p; // note: space not included here + currentLineFinalWhitespace = w; + spaceUsedInLine += currentWordSpace + w; + } + // 2. I can just add the word - then append it, output current line and go to next one. + else if(spaceUsedInLine + currentWordSpace <= maxWidth) + { + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + currentLine = 0; + isContinuation = true; + } + // 3. Otherwise, output current line and go to next one, where I can add the word. + else if(continuationWidth + currentWordSpace + w <= maxWidth) + { + if(currentLineEnd) + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + currentLine = currentWord; + spaceUsedInLine = continuationWidth + currentWordSpace + w; + currentLineEnd = p; + currentLineFinalWhitespace = w; + isContinuation = true; + } + // 4. We can't even do that? Then output both current and next word as new lines. + else + { + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + currentLine = 0; + isContinuation = true; + } + currentWord = 0; + } + else if(c == '\n') + { + // 1. I can add the word - then do it. + if(spaceUsedInLine + currentWordSpace <= maxWidth) + { + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + } + // 2. Otherwise, output current line, next one and make tabula rasa. + else + { + if(currentLineEnd) + { + processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + } + currentWord = 0; + currentLine = 0; + isContinuation = false; + } + else + { + currentWordSpace += w; + if( + spaceUsedInLine + currentWordSpace > maxWidth // can't join this line... + && + continuationWidth + currentWordSpace > maxWidth // can't join any other line... + ) + { + // this word cannot join ANY line... + // so output the current line... + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + + // then this word's beginning... + if(isContinuation) + { + // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces + float pieceWidth = maxWidth - continuationWidth; + const char *pos = currentWord; + currentWordSpace = 0; + + // reset the char width function to a state where no kerning occurs (start of word) + charWidth(passthroughCW, ' '); + while(pos <= p) + { + float w = charWidth(passthroughCW, *pos); + if(currentWordSpace + w > pieceWidth) // this piece won't fit any more + { + // print everything until it + result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true); + // go to here + currentWord = pos; + currentWordSpace = 0; + } + currentWordSpace += w; + ++pos; + } + // now we have a currentWord that fits... set up its next line + // currentWordSpace has been set + // currentWord has been set + spaceUsedInLine = continuationWidth; + currentLine = currentWord; + currentLineEnd = 0; + isContinuation = true; + } + else + { + // we have a guarantee that it will fix (see if clause) + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation); + + // and use the rest of this word as new start of a line + currentWordSpace = w; + currentWord = p; + spaceUsedInLine = continuationWidth; + currentLine = p; + currentLineEnd = 0; + isContinuation = true; + } + } + } + } + + if(!currentWord) + { + currentWord = p; + currentWordSpace = 0; + } + + if(currentLine) // Same procedure as \n + { + // Can I append the current word? + if(spaceUsedInLine + currentWordSpace <= maxWidth) + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + else + { + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + } + } + + return result; +*/ +} + +/* +============== +COM_ParseToken_Simple + +Parse a token out of a string +============== +*/ +int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash) +{ + int len; + int c; + const char *data = *datapointer; + + len = 0; + com_token[0] = 0; + + if (!data) + { + *datapointer = NULL; + return false; + } + +// skip whitespace +skipwhite: + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) + { + if (*data == 0) + { + // end of file + *datapointer = NULL; + return false; + } + } + + // handle Windows line ending + if (data[0] == '\r' && data[1] == '\n') + data++; + + if (data[0] == '/' && data[1] == '/') + { + // comment + while (*data && *data != '\n' && *data != '\r') + data++; + goto skipwhite; + } + else if (data[0] == '/' && data[1] == '*') + { + // comment + data++; + while (*data && (data[0] != '*' || data[1] != '/')) + data++; + if (*data) + data++; + if (*data) + data++; + goto skipwhite; + } + else if (*data == '\"') + { + // quoted string + for (data++;*data && *data != '\"';data++) + { + c = *data; + if (*data == '\\' && parsebackslash) + { + data++; + c = *data; + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + } + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = c; + } + com_token[len] = 0; + if (*data == '\"') + data++; + *datapointer = data; + return true; + } + else if (*data == '\r') + { + // translate Mac line ending to UNIX + com_token[len++] = '\n';data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else if (*data == '\n') + { + // single character + com_token[len++] = *data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else + { + // regular word + for (;!ISWHITESPACE(*data);data++) + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + com_token[len] = 0; + *datapointer = data; + return true; + } +} + +/* +============== +COM_ParseToken_QuakeC + +Parse a token out of a string +============== +*/ +int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline) +{ + int len; + int c; + const char *data = *datapointer; + + len = 0; + com_token[0] = 0; + + if (!data) + { + *datapointer = NULL; + return false; + } + +// skip whitespace +skipwhite: + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) + { + if (*data == 0) + { + // end of file + *datapointer = NULL; + return false; + } + } + + // handle Windows line ending + if (data[0] == '\r' && data[1] == '\n') + data++; + + if (data[0] == '/' && data[1] == '/') + { + // comment + while (*data && *data != '\n' && *data != '\r') + data++; + goto skipwhite; + } + else if (data[0] == '/' && data[1] == '*') + { + // comment + data++; + while (*data && (data[0] != '*' || data[1] != '/')) + data++; + if (*data) + data++; + if (*data) + data++; + goto skipwhite; + } + else if (*data == '\"' || *data == '\'') + { + // quoted string + char quote = *data; + for (data++;*data && *data != quote;data++) + { + c = *data; + if (*data == '\\') + { + data++; + c = *data; + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + } + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = c; + } + com_token[len] = 0; + if (*data == quote) + data++; + *datapointer = data; + return true; + } + else if (*data == '\r') + { + // translate Mac line ending to UNIX + com_token[len++] = '\n';data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';') + { + // single character + com_token[len++] = *data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else + { + // regular word + for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++) + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + com_token[len] = 0; + *datapointer = data; + return true; + } +} + +/* +============== +COM_ParseToken_VM_Tokenize + +Parse a token out of a string +============== +*/ +int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline) +{ + int len; + int c; + const char *data = *datapointer; + + len = 0; + com_token[0] = 0; + + if (!data) + { + *datapointer = NULL; + return false; + } + +// skip whitespace +skipwhite: + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) + { + if (*data == 0) + { + // end of file + *datapointer = NULL; + return false; + } + } + + // handle Windows line ending + if (data[0] == '\r' && data[1] == '\n') + data++; + + if (data[0] == '/' && data[1] == '/') + { + // comment + while (*data && *data != '\n' && *data != '\r') + data++; + goto skipwhite; + } + else if (data[0] == '/' && data[1] == '*') + { + // comment + data++; + while (*data && (data[0] != '*' || data[1] != '/')) + data++; + if (*data) + data++; + if (*data) + data++; + goto skipwhite; + } + else if (*data == '\"' || *data == '\'') + { + char quote = *data; + // quoted string + for (data++;*data && *data != quote;data++) + { + c = *data; + if (*data == '\\') + { + data++; + c = *data; + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + } + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = c; + } + com_token[len] = 0; + if (*data == quote) + data++; + *datapointer = data; + return true; + } + else if (*data == '\r') + { + // translate Mac line ending to UNIX + com_token[len++] = '\n';data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';') + { + // single character + com_token[len++] = *data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else + { + // regular word + for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++) + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + com_token[len] = 0; + *datapointer = data; + return true; + } +} + +/* +============== +COM_ParseToken_Console + +Parse a token out of a string, behaving like the qwcl console +============== +*/ +int COM_ParseToken_Console(const char **datapointer) +{ + int len; + const char *data = *datapointer; + + len = 0; + com_token[0] = 0; + + if (!data) + { + *datapointer = NULL; + return false; + } + +// skip whitespace +skipwhite: + for (;ISWHITESPACE(*data);data++) + { + if (*data == 0) + { + // end of file + *datapointer = NULL; + return false; + } + } + + if (*data == '/' && data[1] == '/') + { + // comment + while (*data && *data != '\n' && *data != '\r') + data++; + goto skipwhite; + } + else if (*data == '\"') + { + // quoted string + for (data++;*data && *data != '\"';data++) + { + // allow escaped " and \ case + if (*data == '\\' && (data[1] == '\"' || data[1] == '\\')) + data++; + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + } + com_token[len] = 0; + if (*data == '\"') + data++; + *datapointer = data; + } + else + { + // regular word + for (;!ISWHITESPACE(*data);data++) + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + com_token[len] = 0; + *datapointer = data; + } + + return true; +} + + +/* +================ +COM_CheckParm + +Returns the position (1 to argc-1) in the program's argument list +where the given parameter apears, or 0 if not present +================ +*/ +int COM_CheckParm (const char *parm) +{ + int i; + + for (i=1 ; i= 0 && gamemode != gamemode_info[index].mode) + COM_SetGameType(index); +} + +static void COM_SetGameType(int index) +{ + int i, t; + if (index < 0 || index >= (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]))) + index = 0; + gamemode = gamemode_info[index].mode; + gamename = gamemode_info[index].gamename; + gamedirname1 = gamemode_info[index].gamedirname1; + gamedirname2 = gamemode_info[index].gamedirname2; + gamescreenshotname = gamemode_info[index].gamescreenshotname; + gameuserdirname = gamemode_info[index].gameuserdirname; + + if (gamemode == com_startupgamemode) + { + if((t = COM_CheckParm("-customgamename")) && t + 1 < com_argc) + gamename = com_argv[t+1]; + if((t = COM_CheckParm("-customgamedirname1")) && t + 1 < com_argc) + gamedirname1 = com_argv[t+1]; + if((t = COM_CheckParm("-customgamedirname2")) && t + 1 < com_argc) + gamedirname2 = *com_argv[t+1] ? com_argv[t+1] : NULL; + if((t = COM_CheckParm("-customgamescreenshotname")) && t + 1 < com_argc) + gamescreenshotname = com_argv[t+1]; + if((t = COM_CheckParm("-customgameuserdirname")) && t + 1 < com_argc) + gameuserdirname = com_argv[t+1]; + } + + if (gamedirname2 && gamedirname2[0]) + Con_Printf("Game is %s using base gamedirs %s %s", gamename, gamedirname1, gamedirname2); + else + Con_Printf("Game is %s using base gamedir %s", gamename, gamedirname1); + for (i = 0;i < fs_numgamedirs;i++) + { + if (i == 0) + Con_Printf(", with mod gamedirs"); + Con_Printf(" %s", fs_gamedirs[i]); + } + Con_Printf("\n"); +} + + +/* +================ +COM_Init +================ +*/ +void COM_Init_Commands (void) +{ + int i, j, n; + char com_cmdline[MAX_INPUTLINE]; + + Cvar_RegisterVariable (®istered); + Cvar_RegisterVariable (&cmdline); + + // reconstitute the command line for the cmdline externally visible cvar + n = 0; + for (j = 0;(j < MAX_NUM_ARGVS) && (j < com_argc);j++) + { + i = 0; + if (strstr(com_argv[j], " ")) + { + // arg contains whitespace, store quotes around it + com_cmdline[n++] = '\"'; + while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i]) + com_cmdline[n++] = com_argv[j][i++]; + com_cmdline[n++] = '\"'; + } + else + { + while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i]) + com_cmdline[n++] = com_argv[j][i++]; + } + if (n < ((int)sizeof(com_cmdline) - 1)) + com_cmdline[n++] = ' '; + else + break; + } + com_cmdline[n] = 0; + Cvar_Set ("cmdline", com_cmdline); +} + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +char *va(const char *format, ...) +{ + va_list argptr; + // LordHavoc: now cycles through 8 buffers to avoid problems in most cases + static char string[8][1024], *s; + static int stringindex = 0; + + s = string[stringindex]; + stringindex = (stringindex + 1) & 7; + va_start (argptr, format); + dpvsnprintf (s, sizeof (string[0]), format,argptr); + va_end (argptr); + + return s; +} + + +//====================================== + +// snprintf and vsnprintf are NOT portable. Use their DP counterparts instead + +#undef snprintf +#undef vsnprintf + +#ifdef WIN32 +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#endif + + +int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...) +{ + va_list args; + int result; + + va_start (args, format); + result = dpvsnprintf (buffer, buffersize, format, args); + va_end (args); + + return result; +} + + +int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args) +{ + int result; + +#if _MSC_VER >= 1400 + result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args); +#else + result = vsnprintf (buffer, buffersize, format, args); +#endif + if (result < 0 || (size_t)result >= buffersize) + { + buffer[buffersize - 1] = '\0'; + return -1; + } + + return result; +} + + +//====================================== + +void COM_ToLowerString (const char *in, char *out, size_t size_out) +{ + if (size_out == 0) + return; + + if(utf8_enable.integer) + { + while(*in && size_out > 1) + { + int n; + Uchar ch = u8_getchar_utf8_enabled(in, &in); + ch = u8_tolower(ch); + n = u8_fromchar(ch, out, size_out); + if(n <= 0) + break; + out += n; + size_out -= n; + } + return; + } + + while (*in && size_out > 1) + { + if (*in >= 'A' && *in <= 'Z') + *out++ = *in++ + 'a' - 'A'; + else + *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +void COM_ToUpperString (const char *in, char *out, size_t size_out) +{ + if (size_out == 0) + return; + + if(utf8_enable.integer) + { + while(*in && size_out > 1) + { + int n; + Uchar ch = u8_getchar_utf8_enabled(in, &in); + ch = u8_toupper(ch); + n = u8_fromchar(ch, out, size_out); + if(n <= 0) + break; + out += n; + size_out -= n; + } + return; + } + + while (*in && size_out > 1) + { + if (*in >= 'a' && *in <= 'z') + *out++ = *in++ + 'A' - 'a'; + else + *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +int COM_StringBeginsWith(const char *s, const char *match) +{ + for (;*s && *match;s++, match++) + if (*s != *match) + return false; + return true; +} + +int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix) +{ + int argc, commentprefixlength; + char *tokenbufend; + const char *l; + argc = 0; + tokenbufend = tokenbuf + tokenbufsize; + l = *text; + commentprefixlength = 0; + if (commentprefix) + commentprefixlength = (int)strlen(commentprefix); + while (*l && *l != '\n' && *l != '\r') + { + if (!ISWHITESPACE(*l)) + { + if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength)) + { + while (*l && *l != '\n' && *l != '\r') + l++; + break; + } + if (argc >= maxargc) + return -1; + argv[argc++] = tokenbuf; + if (*l == '"') + { + l++; + while (*l && *l != '"') + { + if (tokenbuf >= tokenbufend) + return -1; + *tokenbuf++ = *l++; + } + if (*l == '"') + l++; + } + else + { + while (!ISWHITESPACE(*l)) + { + if (tokenbuf >= tokenbufend) + return -1; + *tokenbuf++ = *l++; + } + } + if (tokenbuf >= tokenbufend) + return -1; + *tokenbuf++ = 0; + } + else + l++; + } + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + if (*l == '\r') + l++; + if (*l == '\n') + l++; + *text = l; + return argc; +} + +/* +============ +COM_StringLengthNoColors + +calculates the visible width of a color coded string. + +*valid is filled with TRUE if the string is a valid colored string (that is, if +it does not end with an unfinished color code). If it gets filled with FALSE, a +fix would be adding a STRING_COLOR_TAG at the end of the string. + +valid can be set to NULL if the caller doesn't care. + +For size_s, specify the maximum number of characters from s to use, or 0 to use +all characters until the zero terminator. +============ +*/ +size_t +COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid) +{ + const char *end = size_s ? (s + size_s) : NULL; + size_t len = 0; + for(;;) + { + switch((s == end) ? 0 : *s) + { + case 0: + if(valid) + *valid = TRUE; + return len; + case STRING_COLOR_TAG: + ++s; + switch((s == end) ? 0 : *s) + { + case STRING_COLOR_RGB_TAG_CHAR: + if (s+1 != end && isxdigit(s[1]) && + s+2 != end && isxdigit(s[2]) && + s+3 != end && isxdigit(s[3]) ) + { + s+=3; + break; + } + ++len; // STRING_COLOR_TAG + ++len; // STRING_COLOR_RGB_TAG_CHAR + break; + case 0: // ends with unfinished color code! + ++len; + if(valid) + *valid = FALSE; + return len; + case STRING_COLOR_TAG: // escaped ^ + ++len; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': // color code + break; + default: // not a color code + ++len; // STRING_COLOR_TAG + ++len; // the character + break; + } + break; + default: + ++len; + break; + } + ++s; + } + // never get here +} + +/* +============ +COM_StringDecolorize + +removes color codes from a string. + +If escape_carets is true, the resulting string will be safe for printing. If +escape_carets is false, the function will just strip color codes (for logging +for example). + +If the output buffer size did not suffice for converting, the function returns +FALSE. Generally, if escape_carets is false, the output buffer needs +strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2 +bytes. In any case, the function makes sure that the resulting string is +zero terminated. + +For size_in, specify the maximum number of characters from in to use, or 0 to use +all characters until the zero terminator. +============ +*/ +qboolean +COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets) +{ +#define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return FALSE; } } while(0) + const char *end = size_in ? (in + size_in) : NULL; + if(size_out < 1) + return FALSE; + for(;;) + { + switch((in == end) ? 0 : *in) + { + case 0: + *out++ = 0; + return TRUE; + case STRING_COLOR_TAG: + ++in; + switch((in == end) ? 0 : *in) + { + case STRING_COLOR_RGB_TAG_CHAR: + if (in+1 != end && isxdigit(in[1]) && + in+2 != end && isxdigit(in[2]) && + in+3 != end && isxdigit(in[3]) ) + { + in+=3; + break; + } + APPEND(STRING_COLOR_TAG); + if(escape_carets) + APPEND(STRING_COLOR_TAG); + APPEND(STRING_COLOR_RGB_TAG_CHAR); + break; + case 0: // ends with unfinished color code! + APPEND(STRING_COLOR_TAG); + // finish the code by appending another caret when escaping + if(escape_carets) + APPEND(STRING_COLOR_TAG); + *out++ = 0; + return TRUE; + case STRING_COLOR_TAG: // escaped ^ + APPEND(STRING_COLOR_TAG); + // append a ^ twice when escaping + if(escape_carets) + APPEND(STRING_COLOR_TAG); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': // color code + break; + default: // not a color code + APPEND(STRING_COLOR_TAG); + APPEND(*in); + break; + } + break; + default: + APPEND(*in); + break; + } + ++in; + } + // never get here +#undef APPEND +} + +// written by Elric, thanks Elric! +char *SearchInfostring(const char *infostring, const char *key) +{ + static char value [MAX_INPUTLINE]; + char crt_key [MAX_INPUTLINE]; + size_t value_ind, key_ind; + char c; + + if (*infostring++ != '\\') + return NULL; + + value_ind = 0; + for (;;) + { + key_ind = 0; + + // Get the key name + for (;;) + { + c = *infostring++; + + if (c == '\0') + return NULL; + if (c == '\\' || key_ind == sizeof (crt_key) - 1) + { + crt_key[key_ind] = '\0'; + break; + } + + crt_key[key_ind++] = c; + } + + // If it's the key we are looking for, save it in "value" + if (!strcmp(crt_key, key)) + { + for (;;) + { + c = *infostring++; + + if (c == '\0' || c == '\\' || value_ind == sizeof (value) - 1) + { + value[value_ind] = '\0'; + return value; + } + + value[value_ind++] = c; + } + } + + // Else, skip the value + for (;;) + { + c = *infostring++; + + if (c == '\0') + return NULL; + if (c == '\\') + break; + } + } +} + +void InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength) +{ + int pos = 0, j; + size_t keylength; + if (!key) + key = ""; + keylength = strlen(key); + if (valuelength < 1 || !value) + { + Con_Printf("InfoString_GetValue: no room in value\n"); + return; + } + value[0] = 0; + if (strchr(key, '\\')) + { + Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key); + return; + } + if (strchr(key, '\"')) + { + Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key); + return; + } + if (!key[0]) + { + Con_Printf("InfoString_GetValue: can not look up a key with no name\n"); + return; + } + while (buffer[pos] == '\\') + { + if (!memcmp(buffer + pos+1, key, keylength)) + { + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + pos++; + for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++) + value[j] = buffer[pos+j]; + value[j] = 0; + return; + } + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + } + // if we reach this point the key was not found +} + +void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value) +{ + int pos = 0, pos2; + size_t keylength; + if (!key) + key = ""; + if (!value) + value = ""; + keylength = strlen(key); + if (strchr(key, '\\') || strchr(value, '\\')) + { + Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \\ which is not possible to store in an infostring\n", key, value); + return; + } + if (strchr(key, '\"') || strchr(value, '\"')) + { + Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \" which is not allowed in an infostring\n", key, value); + return; + } + if (!key[0]) + { + Con_Printf("InfoString_SetValue: can not set a key with no name\n"); + return; + } + while (buffer[pos] == '\\') + { + if (!memcmp(buffer + pos+1, key, keylength)) + break; + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + } + // if we found the key, find the end of it because we will be replacing it + pos2 = pos; + if (buffer[pos] == '\\') + { + for (pos2++;buffer[pos2] && buffer[pos2] != '\\';pos2++); + for (pos2++;buffer[pos2] && buffer[pos2] != '\\';pos2++); + } + if (bufferlength <= pos + 1 + strlen(key) + 1 + strlen(value) + strlen(buffer + pos2)) + { + Con_Printf("InfoString_SetValue: no room for \"%s\" \"%s\" in infostring\n", key, value); + return; + } + if (value && value[0]) + { + // set the key/value and append the remaining text + char tempbuffer[4096]; + strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer)); + dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer); + } + else + { + // just remove the key from the text + strlcpy(buffer + pos, buffer + pos2, bufferlength - pos); + } +} + +void InfoString_Print(char *buffer) +{ + int i; + char key[2048]; + char value[2048]; + while (*buffer) + { + if (*buffer != '\\') + { + Con_Printf("InfoString_Print: corrupt string\n"); + return; + } + for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++) + if (i < (int)sizeof(key)-1) + key[i++] = *buffer; + key[i] = 0; + if (*buffer != '\\') + { + Con_Printf("InfoString_Print: corrupt string\n"); + return; + } + for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++) + if (i < (int)sizeof(value)-1) + value[i++] = *buffer; + value[i] = 0; + // empty value is an error case + Con_Printf("%20s %s\n", key, value[0] ? value : "NO VALUE"); + } +} + +//======================================================== +// strlcat and strlcpy, from OpenBSD + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, 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. + */ + +/* $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $ */ +/* $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $ */ + + +#ifndef HAVE_STRLCAT +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} +#endif // #ifndef HAVE_STRLCAT + + +#ifndef HAVE_STRLCPY +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +#endif // #ifndef HAVE_STRLCPY + +void FindFraction(double val, int *num, int *denom, int denomMax) +{ + int i; + double bestdiff; + // initialize + bestdiff = fabs(val); + *num = 0; + *denom = 1; + + for(i = 1; i <= denomMax; ++i) + { + int inum = (int) floor(0.5 + val * i); + double diff = fabs(val - inum / (double)i); + if(diff < bestdiff) + { + bestdiff = diff; + *num = inum; + *denom = i; + } + } +} + +// decodes an XPM from C syntax +char **XPM_DecodeString(const char *in) +{ + static char *tokens[257]; + static char lines[257][512]; + size_t line = 0; + + // skip until "{" token + while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{")); + + // now, read in succession: string, comma-or-} + while(COM_ParseToken_QuakeC(&in, false)) + { + tokens[line] = lines[line]; + strlcpy(lines[line++], com_token, sizeof(lines[0])); + if(!COM_ParseToken_QuakeC(&in, false)) + return NULL; + if(!strcmp(com_token, "}")) + break; + if(strcmp(com_token, ",")) + return NULL; + if(line >= sizeof(tokens) / sizeof(tokens[0])) + return NULL; + } + + return tokens; +} + +static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes) +{ + unsigned char i0 = (bytes > 0) ? in[0] : 0; + unsigned char i1 = (bytes > 1) ? in[1] : 0; + unsigned char i2 = (bytes > 2) ? in[2] : 0; + unsigned char o0 = base64[i0 >> 2]; + unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077]; + unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077]; + unsigned char o3 = base64[i2 & 077]; + out[0] = (bytes > 0) ? o0 : '?'; + out[1] = (bytes > 0) ? o1 : '?'; + out[2] = (bytes > 1) ? o2 : '='; + out[3] = (bytes > 2) ? o3 : '='; +} + +size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen) +{ + size_t blocks, i; + // expand the out-buffer + blocks = (buflen + 2) / 3; + if(blocks*4 > outbuflen) + return 0; + for(i = blocks; i > 0; ) + { + --i; + base64_3to4(buf + 3*i, buf + 4*i, buflen - 3*i); + } + return blocks * 4; +} diff --git a/misc/source/darkplaces-src/common.h b/misc/source/darkplaces-src/common.h new file mode 100644 index 00000000..bd715ed6 --- /dev/null +++ b/misc/source/darkplaces-src/common.h @@ -0,0 +1,377 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef COMMON_H +#define COMMON_H + + +/// MSVC has a different name for several standard functions +#ifdef WIN32 +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +#endif + +// Create our own define for Mac OS X +#if defined(__APPLE__) && defined(__MACH__) +# define MACOSX +#endif + +#ifdef SUNOS +#include ///< Needed for FNDELAY +#endif + +//============================================================================ + +typedef struct sizebuf_s +{ + qboolean allowoverflow; ///< if false, do a Sys_Error + qboolean overflowed; ///< set to true if the buffer size failed + unsigned char *data; + int maxsize; + int cursize; +} sizebuf_t; + +void SZ_Clear (sizebuf_t *buf); +unsigned char *SZ_GetSpace (sizebuf_t *buf, int length); +void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length); +void SZ_HexDumpToConsole(const sizebuf_t *buf); + +void Com_HexDumpToConsole(const unsigned char *data, int size); + +unsigned short CRC_Block(const unsigned char *data, size_t size); +unsigned short CRC_Block_CaseInsensitive(const unsigned char *data, size_t size); // for hash lookup functions that use strcasecmp for comparison + +unsigned char COM_BlockSequenceCRCByteQW(unsigned char *base, int length, int sequence); + +// these are actually md4sum (mdfour.c) +unsigned Com_BlockChecksum (void *buffer, int length); +void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf); + + +//============================================================================ +// Endianess handling +//============================================================================ + +// check mem_bigendian if you need to know the system byte order + +/*! \name Byte order functions. + * @{ + */ + +// unaligned memory access crashes on some platform, so always read bytes... +#define BigShort(l) BuffBigShort((unsigned char *)&(l)) +#define LittleShort(l) BuffLittleShort((unsigned char *)&(l)) +#define BigLong(l) BuffBigLong((unsigned char *)&(l)) +#define LittleLong(l) BuffLittleLong((unsigned char *)&(l)) +#define BigFloat(l) BuffBigFloat((unsigned char *)&(l)) +#define LittleFloat(l) BuffLittleFloat((unsigned char *)&(l)) + +/// Extract a big endian 32bit float from the given \p buffer. +float BuffBigFloat (const unsigned char *buffer); + +/// Extract a big endian 32bit int from the given \p buffer. +int BuffBigLong (const unsigned char *buffer); + +/// Extract a big endian 16bit short from the given \p buffer. +short BuffBigShort (const unsigned char *buffer); + +/// Extract a little endian 32bit float from the given \p buffer. +float BuffLittleFloat (const unsigned char *buffer); + +/// Extract a little endian 32bit int from the given \p buffer. +int BuffLittleLong (const unsigned char *buffer); + +/// Extract a little endian 16bit short from the given \p buffer. +short BuffLittleShort (const unsigned char *buffer); + +/// Encode a big endian 32bit int to the given \p buffer +void StoreBigLong (unsigned char *buffer, unsigned int i); + +/// Encode a big endian 16bit int to the given \p buffer +void StoreBigShort (unsigned char *buffer, unsigned short i); + +/// Encode a little endian 32bit int to the given \p buffer +void StoreLittleLong (unsigned char *buffer, unsigned int i); + +/// Encode a little endian 16bit int to the given \p buffer +void StoreLittleShort (unsigned char *buffer, unsigned short i); +//@} + +//============================================================================ + +// these versions are purely for internal use, never sent in network protocol +// (use Protocol_EnumForNumber and Protocol_NumberToEnum to convert) +typedef enum protocolversion_e +{ + PROTOCOL_UNKNOWN, + PROTOCOL_DARKPLACES7, ///< added QuakeWorld-style movement protocol to allow more consistent prediction + PROTOCOL_DARKPLACES6, ///< various changes + PROTOCOL_DARKPLACES5, ///< uses EntityFrame5 entity snapshot encoder/decoder which is based on a Tribes networking article at http://www.garagegames.com/articles/networking1/ + PROTOCOL_DARKPLACES4, ///< various changes + PROTOCOL_DARKPLACES3, ///< uses EntityFrame4 entity snapshot encoder/decoder which is broken, this attempted to do partial snapshot updates on a QuakeWorld-like protocol, but it is broken and impossible to fix + PROTOCOL_DARKPLACES2, ///< various changes + PROTOCOL_DARKPLACES1, ///< uses EntityFrame entity snapshot encoder/decoder which is a QuakeWorld-like entity snapshot delta compression method + PROTOCOL_QUAKEDP, ///< darkplaces extended quake protocol (used by TomazQuake and others), backwards compatible as long as no extended features are used + PROTOCOL_NEHAHRAMOVIE, ///< Nehahra movie protocol, a big nasty hack dating back to early days of the Quake Standards Group (but only ever used by neh_gl.exe), this is potentially backwards compatible with quake protocol as long as no extended features are used (but in actuality the neh_gl.exe which wrote this protocol ALWAYS wrote the extended information) + PROTOCOL_QUAKE, ///< quake (aka netquake/normalquake/nq) protocol + PROTOCOL_QUAKEWORLD, ///< quakeworld protocol + PROTOCOL_NEHAHRABJP, ///< same as QUAKEDP but with 16bit modelindex + PROTOCOL_NEHAHRABJP2, ///< same as NEHAHRABJP but with 16bit soundindex + PROTOCOL_NEHAHRABJP3 ///< same as NEHAHRABJP2 but with some changes +} +protocolversion_t; + +/*! \name Message IO functions. + * Handles byte ordering and avoids alignment errors + * @{ + */ + +void MSG_WriteChar (sizebuf_t *sb, int c); +void MSG_WriteByte (sizebuf_t *sb, int c); +void MSG_WriteShort (sizebuf_t *sb, int c); +void MSG_WriteLong (sizebuf_t *sb, int c); +void MSG_WriteFloat (sizebuf_t *sb, float f); +void MSG_WriteString (sizebuf_t *sb, const char *s); +void MSG_WriteUnterminatedString (sizebuf_t *sb, const char *s); +void MSG_WriteAngle8i (sizebuf_t *sb, float f); +void MSG_WriteAngle16i (sizebuf_t *sb, float f); +void MSG_WriteAngle32f (sizebuf_t *sb, float f); +void MSG_WriteCoord13i (sizebuf_t *sb, float f); +void MSG_WriteCoord16i (sizebuf_t *sb, float f); +void MSG_WriteCoord32f (sizebuf_t *sb, float f); +void MSG_WriteCoord (sizebuf_t *sb, float f, protocolversion_t protocol); +void MSG_WriteVector (sizebuf_t *sb, float *v, protocolversion_t protocol); +void MSG_WriteAngle (sizebuf_t *sb, float f, protocolversion_t protocol); + +extern int msg_readcount; +extern qboolean msg_badread; // set if a read goes beyond end of message + +void MSG_BeginReading (void); +int MSG_ReadLittleShort (void); +int MSG_ReadBigShort (void); +int MSG_ReadLittleLong (void); +int MSG_ReadBigLong (void); +float MSG_ReadLittleFloat (void); +float MSG_ReadBigFloat (void); +char *MSG_ReadString (void); +int MSG_ReadBytes (int numbytes, unsigned char *out); + +#define MSG_ReadChar() (msg_readcount >= net_message.cursize ? (msg_badread = true, -1) : (signed char)net_message.data[msg_readcount++]) +#define MSG_ReadByte() (msg_readcount >= net_message.cursize ? (msg_badread = true, -1) : (unsigned char)net_message.data[msg_readcount++]) +#define MSG_ReadShort MSG_ReadLittleShort +#define MSG_ReadLong MSG_ReadLittleLong +#define MSG_ReadFloat MSG_ReadLittleFloat + +float MSG_ReadAngle8i (void); +float MSG_ReadAngle16i (void); +float MSG_ReadAngle32f (void); +float MSG_ReadCoord13i (void); +float MSG_ReadCoord16i (void); +float MSG_ReadCoord32f (void); +float MSG_ReadCoord (protocolversion_t protocol); +void MSG_ReadVector (float *v, protocolversion_t protocol); +float MSG_ReadAngle (protocolversion_t protocol); +//@} +//============================================================================ + +typedef float (*COM_WordWidthFunc_t) (void *passthrough, const char *w, size_t *length, float maxWidth); // length is updated to the longest fitting string into maxWidth; if maxWidth < 0, all characters are used and length is used as is +typedef int (*COM_LineProcessorFunc) (void *passthrough, const char *line, size_t length, float width, qboolean isContination); +int COM_Wordwrap(const char *string, size_t length, float continuationSize, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL); + +extern char com_token[MAX_INPUTLINE]; + +int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash); +int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline); +int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline); +int COM_ParseToken_Console(const char **datapointer); + +extern int com_argc; +extern const char **com_argv; +extern int com_selffd; + +int COM_CheckParm (const char *parm); +void COM_Init (void); +void COM_Shutdown (void); +void COM_InitGameType (void); + +char *va(const char *format, ...) DP_FUNC_PRINTF(1); +// does a varargs printf into a temp buffer + + +// snprintf and vsnprintf are NOT portable. Use their DP counterparts instead +#ifdef snprintf +# undef snprintf +#endif +#define snprintf DO_NOT_USE_SNPRINTF__USE_DPSNPRINTF +#ifdef vsnprintf +# undef vsnprintf +#endif +#define vsnprintf DO_NOT_USE_VSNPRINTF__USE_DPVSNPRINTF + +// dpsnprintf and dpvsnprintf +// return the number of printed characters, excluding the final '\0' +// or return -1 if the buffer isn't big enough to contain the entire string. +// buffer is ALWAYS null-terminated +extern int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...) DP_FUNC_PRINTF(3); +extern int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args); + +// A bunch of functions are forbidden for security reasons (and also to please MSVS 2005, for some of them) +// LordHavoc: added #undef lines here to avoid warnings in Linux +#undef strcat +#define strcat DO_NOT_USE_STRCAT__USE_STRLCAT_OR_MEMCPY +#undef strncat +#define strncat DO_NOT_USE_STRNCAT__USE_STRLCAT_OR_MEMCPY +#undef strcpy +#define strcpy DO_NOT_USE_STRCPY__USE_STRLCPY_OR_MEMCPY +#undef strncpy +#define strncpy DO_NOT_USE_STRNCPY__USE_STRLCPY_OR_MEMCPY +//#undef sprintf +//#define sprintf DO_NOT_USE_SPRINTF__USE_DPSNPRINTF + + +//============================================================================ + +extern struct cvar_s registered; +extern struct cvar_s cmdline; + +typedef enum userdirmode_e +{ + USERDIRMODE_NOHOME, // basedir only + USERDIRMODE_HOME, // Windows basedir, general POSIX (~/.) + USERDIRMODE_MYGAMES, // pre-Vista (My Documents/My Games/), general POSIX (~/.) + USERDIRMODE_SAVEDGAMES, // Vista (%USERPROFILE%/Saved Games/), OSX (~/Library/Application Support/), Linux (~/.config) + USERDIRMODE_COUNT +} +userdirmode_t; + +typedef enum gamemode_e +{ + GAME_NORMAL, + GAME_HIPNOTIC, + GAME_ROGUE, + GAME_NEHAHRA, + GAME_NEXUIZ, + GAME_XONOTIC, + GAME_TRANSFUSION, + GAME_GOODVSBAD2, + GAME_TEU, + GAME_BATTLEMECH, + GAME_ZYMOTIC, + GAME_SETHERAL, + GAME_SOM, + GAME_TENEBRAE, // full of evil hackery + GAME_NEOTERIC, + GAME_OPENQUARTZ, //this game sucks + GAME_PRYDON, + GAME_DELUXEQUAKE, + GAME_THEHUNTED, + GAME_DEFEATINDETAIL2, + GAME_DARSANA, + GAME_CONTAGIONTHEORY, + GAME_EDU2P, + GAME_PROPHECY, + GAME_BLOODOMNICIDE, + GAME_STEELSTORM, // added by motorsep + GAME_STRAPBOMB, // added by motorsep for Urre + GAME_MOONHELM, + GAME_COUNT +} +gamemode_t; + +extern gamemode_t gamemode; +extern const char *gamename; +extern const char *gamedirname1; +extern const char *gamedirname2; +extern const char *gamescreenshotname; +extern const char *gameuserdirname; +extern char com_modname[MAX_OSPATH]; + +void COM_ChangeGameTypeForGameDirs(void); + +void COM_ToLowerString (const char *in, char *out, size_t size_out); +void COM_ToUpperString (const char *in, char *out, size_t size_out); +int COM_StringBeginsWith(const char *s, const char *match); + +int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix); + +size_t COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); +qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets); +void COM_ToLowerString (const char *in, char *out, size_t size_out); +void COM_ToUpperString (const char *in, char *out, size_t size_out); + +typedef struct stringlist_s +{ + /// maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +int matchpattern(const char *in, const char *pattern, int caseinsensitive); +int matchpattern_with_separator(const char *in, const char *pattern, int caseinsensitive, const char *separators, qboolean wildcard_least_one); +void stringlistinit(stringlist_t *list); +void stringlistfreecontents(stringlist_t *list); +void stringlistappend(stringlist_t *list, const char *text); +void stringlistsort(stringlist_t *list, qboolean uniq); +void listdirectory(stringlist_t *list, const char *basepath, const char *path); + +char *SearchInfostring(const char *infostring, const char *key); + +void InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength); +void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value); +void InfoString_Print(char *buffer); + +// strlcat and strlcpy, from OpenBSD +// Most (all?) BSDs already have them +#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(MACOSX) +# define HAVE_STRLCAT 1 +# define HAVE_STRLCPY 1 +#endif + +#ifndef HAVE_STRLCAT +/*! + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t strlcat(char *dst, const char *src, size_t siz); +#endif // #ifndef HAVE_STRLCAT + +#ifndef HAVE_STRLCPY +/*! + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t strlcpy(char *dst, const char *src, size_t siz); + +#endif // #ifndef HAVE_STRLCPY + +void FindFraction(double val, int *num, int *denom, int denomMax); + +// decodes XPM file to XPM array (as if #include'd) +char **XPM_DecodeString(const char *in); + +size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen); + +#endif + diff --git a/misc/source/darkplaces-src/conproc.c b/misc/source/darkplaces-src/conproc.c new file mode 100644 index 00000000..ccbb849c --- /dev/null +++ b/misc/source/darkplaces-src/conproc.c @@ -0,0 +1,365 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// conproc.c + +#include "quakedef.h" + +#include +#include +#include "conproc.h" + +HANDLE heventDone; +HANDLE hfileBuffer; +HANDLE heventChildSend; +HANDLE heventParentSend; +HANDLE hStdout; +HANDLE hStdin; + +DWORD RequestProc (DWORD dwNichts); +LPVOID GetMappedBuffer (HANDLE hfileBuffer); +void ReleaseMappedBuffer (LPVOID pBuffer); +BOOL GetScreenBufferLines (int *piLines); +BOOL SetScreenBufferLines (int iLines); +BOOL ReadText (LPTSTR pszText, int iBeginLine, int iEndLine); +BOOL WriteText (LPCTSTR szText); +int CharToCode (int c); +BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy); + + +void InitConProc (HANDLE hFile, HANDLE heventParent, HANDLE heventChild) +{ + DWORD dwID; + +// ignore if we don't have all the events. + if (!hFile || !heventParent || !heventChild) + return; + + hfileBuffer = hFile; + heventParentSend = heventParent; + heventChildSend = heventChild; + +// so we'll know when to go away. + heventDone = CreateEvent (NULL, false, false, NULL); + + if (!heventDone) + { + Con_Print("Couldn't create heventDone\n"); + return; + } + + if (!CreateThread (NULL, + 0, + (LPTHREAD_START_ROUTINE) RequestProc, + 0, + 0, + &dwID)) + { + CloseHandle (heventDone); + Con_Print("Couldn't create QHOST thread\n"); + return; + } + +// save off the input/output handles. + hStdout = GetStdHandle (STD_OUTPUT_HANDLE); + hStdin = GetStdHandle (STD_INPUT_HANDLE); + +// force 80 character width, at least 25 character height + SetConsoleCXCY (hStdout, 80, 25); +} + + +void DeinitConProc (void) +{ + if (heventDone) + SetEvent (heventDone); +} + + +DWORD RequestProc (DWORD dwNichts) +{ + int *pBuffer; + DWORD dwRet; + HANDLE heventWait[2]; + int iBeginLine, iEndLine; + + heventWait[0] = heventParentSend; + heventWait[1] = heventDone; + + while (1) + { + dwRet = WaitForMultipleObjects (2, heventWait, false, INFINITE); + + // heventDone fired, so we're exiting. + if (dwRet == WAIT_OBJECT_0 + 1) + break; + + pBuffer = (int *) GetMappedBuffer (hfileBuffer); + + // hfileBuffer is invalid. Just leave. + if (!pBuffer) + { + Con_Print("Invalid hfileBuffer\n"); + break; + } + + switch (pBuffer[0]) + { + case CCOM_WRITE_TEXT: + // Param1 : Text + pBuffer[0] = WriteText ((LPCTSTR) (pBuffer + 1)); + break; + + case CCOM_GET_TEXT: + // Param1 : Begin line + // Param2 : End line + iBeginLine = pBuffer[1]; + iEndLine = pBuffer[2]; + pBuffer[0] = ReadText ((LPTSTR) (pBuffer + 1), iBeginLine, + iEndLine); + break; + + case CCOM_GET_SCR_LINES: + // No params + pBuffer[0] = GetScreenBufferLines (&pBuffer[1]); + break; + + case CCOM_SET_SCR_LINES: + // Param1 : Number of lines + pBuffer[0] = SetScreenBufferLines (pBuffer[1]); + break; + } + + ReleaseMappedBuffer (pBuffer); + SetEvent (heventChildSend); + } + + return 0; +} + + +LPVOID GetMappedBuffer (HANDLE hfileBuffer) +{ + LPVOID pBuffer; + + pBuffer = MapViewOfFile (hfileBuffer, + FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); + + return pBuffer; +} + + +void ReleaseMappedBuffer (LPVOID pBuffer) +{ + UnmapViewOfFile (pBuffer); +} + + +BOOL GetScreenBufferLines (int *piLines) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + BOOL bRet; + + bRet = GetConsoleScreenBufferInfo (hStdout, &info); + + if (bRet) + *piLines = info.dwSize.Y; + + return bRet; +} + + +BOOL SetScreenBufferLines (int iLines) +{ + + return SetConsoleCXCY (hStdout, 80, iLines); +} + + +BOOL ReadText (LPTSTR pszText, int iBeginLine, int iEndLine) +{ + COORD coord; + DWORD dwRead; + BOOL bRet; + + coord.X = 0; + coord.Y = iBeginLine; + + bRet = ReadConsoleOutputCharacter( + hStdout, + pszText, + 80 * (iEndLine - iBeginLine + 1), + coord, + &dwRead); + + // Make sure it's null terminated. + if (bRet) + pszText[dwRead] = '\0'; + + return bRet; +} + + +BOOL WriteText (LPCTSTR szText) +{ + DWORD dwWritten; + INPUT_RECORD rec; + char upper, *sz; + + sz = (LPTSTR) szText; + + while (*sz) + { + // 13 is the code for a carriage return (\n) instead of 10. + if (*sz == 10) + *sz = 13; + + upper = toupper(*sz); + + rec.EventType = KEY_EVENT; + rec.Event.KeyEvent.bKeyDown = true; + rec.Event.KeyEvent.wRepeatCount = 1; + rec.Event.KeyEvent.wVirtualKeyCode = upper; + rec.Event.KeyEvent.wVirtualScanCode = CharToCode (*sz); + rec.Event.KeyEvent.uChar.AsciiChar = *sz; + rec.Event.KeyEvent.uChar.UnicodeChar = *sz; + rec.Event.KeyEvent.dwControlKeyState = isupper(*sz) ? 0x80 : 0x0; + + WriteConsoleInput( + hStdin, + &rec, + 1, + &dwWritten); + + rec.Event.KeyEvent.bKeyDown = false; + + WriteConsoleInput( + hStdin, + &rec, + 1, + &dwWritten); + + sz++; + } + + return true; +} + + +int CharToCode (int c) +{ + char upper; + + upper = toupper(c); + + switch (c) + { + case 13: + return 28; + + default: + break; + } + + if (isalpha(c)) + return (30 + upper - 65); + + if (isdigit(c)) + return (1 + upper - 47); + + return c; +} + + +BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + COORD coordMax; + + coordMax = GetLargestConsoleWindowSize(hStdout); + + if (cy > coordMax.Y) + cy = coordMax.Y; + + if (cx > coordMax.X) + cx = coordMax.X; + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return false; + +// height + info.srWindow.Left = 0; + info.srWindow.Right = info.dwSize.X - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = cy - 1; + + if (cy < info.dwSize.Y) + { + if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) + return false; + + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return false; + } + else if (cy > info.dwSize.Y) + { + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return false; + + if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) + return false; + } + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return false; + +// width + info.srWindow.Left = 0; + info.srWindow.Right = cx - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = info.dwSize.Y - 1; + + if (cx < info.dwSize.X) + { + if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) + return false; + + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return false; + } + else if (cx > info.dwSize.X) + { + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return false; + + if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) + return false; + } + + return true; +} + diff --git a/misc/source/darkplaces-src/conproc.h b/misc/source/darkplaces-src/conproc.h new file mode 100644 index 00000000..8fe112a1 --- /dev/null +++ b/misc/source/darkplaces-src/conproc.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// conproc.h + +#ifndef CONPROC_H +#define CONPROC_H + +#define CCOM_WRITE_TEXT 0x2 +// Param1 : Text + +#define CCOM_GET_TEXT 0x3 +// Param1 : Begin line +// Param2 : End line + +#define CCOM_GET_SCR_LINES 0x4 +// No params + +#define CCOM_SET_SCR_LINES 0x5 +// Param1 : Number of lines + +void InitConProc (HANDLE hFile, HANDLE heventParent, HANDLE heventChild); +void DeinitConProc (void); + +#endif + diff --git a/misc/source/darkplaces-src/console.c b/misc/source/darkplaces-src/console.c new file mode 100644 index 00000000..bb9c277d --- /dev/null +++ b/misc/source/darkplaces-src/console.c @@ -0,0 +1,2971 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// console.c + +#if !defined(WIN32) || defined(__MINGW32__) +# include +#endif +#include + +#include "quakedef.h" + +// for u8_encodech +#include "ft2.h" + +float con_cursorspeed = 4; + +// lines up from bottom to display +int con_backscroll; + +conbuffer_t con; + +#define CON_LINES(i) CONBUFFER_LINES(&con, i) +#define CON_LINES_LAST CONBUFFER_LINES_LAST(&con) +#define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con) + +cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"}; +cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"}; +cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"}; + +cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"}; +cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"}; +cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"}; +cvar_t con_chatrect = {CVAR_SAVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"}; +cvar_t con_chatrect_x = {CVAR_SAVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"}; +cvar_t con_chatrect_y = {CVAR_SAVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"}; +cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"}; +cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"}; +cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"}; +cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"}; +cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"}; + + +cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"}; +#ifdef WIN32 +cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"}; +#else +cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"}; +#endif + + +cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"}; +cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: " + "0: add nothing after completion. " + "1: add the last color after completion. " + "2: add a quote when starting a quote instead of the color. " + "4: will replace 1, will force color, even after a quote. " + "8: ignore non-alphanumerics. " + "16: ignore spaces. "}; +#define NICKS_ADD_COLOR 1 +#define NICKS_ADD_QUOTE 2 +#define NICKS_FORCE_COLOR 4 +#define NICKS_ALPHANUMERICS_ONLY 8 +#define NICKS_NO_SPACES 16 + +cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"}; +cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"}; +cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"}; + +int con_linewidth; +int con_vislines; + +qboolean con_initialized; + +// used for server replies to rcon command +lhnetsocket_t *rcon_redirect_sock = NULL; +lhnetaddress_t *rcon_redirect_dest = NULL; +int rcon_redirect_bufferpos = 0; +char rcon_redirect_buffer[1400]; +qboolean rcon_redirect_proquakeprotocol = false; + +// generic functions for console buffers + +void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool) +{ + buf->active = true; + buf->textsize = textsize; + buf->text = (char *) Mem_Alloc(mempool, textsize); + buf->maxlines = maxlines; + buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines)); + buf->lines_first = 0; + buf->lines_count = 0; +} + +/* +================ +ConBuffer_Clear +================ +*/ +void ConBuffer_Clear (conbuffer_t *buf) +{ + buf->lines_count = 0; +} + +/* +================ +ConBuffer_Shutdown +================ +*/ +void ConBuffer_Shutdown(conbuffer_t *buf) +{ + buf->active = false; + if (buf->text) + Mem_Free(buf->text); + if (buf->lines) + Mem_Free(buf->lines); + buf->text = NULL; + buf->lines = NULL; +} + +/* +================ +ConBuffer_FixTimes + +Notifies the console code about the current time +(and shifts back times of other entries when the time +went backwards) +================ +*/ +void ConBuffer_FixTimes(conbuffer_t *buf) +{ + int i; + if(buf->lines_count >= 1) + { + double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime; + if(diff < 0) + { + for(i = 0; i < buf->lines_count; ++i) + CONBUFFER_LINES(buf, i).addtime += diff; + } + } +} + +/* +================ +ConBuffer_DeleteLine + +Deletes the first line from the console history. +================ +*/ +void ConBuffer_DeleteLine(conbuffer_t *buf) +{ + if(buf->lines_count == 0) + return; + --buf->lines_count; + buf->lines_first = (buf->lines_first + 1) % buf->maxlines; +} + +/* +================ +ConBuffer_DeleteLastLine + +Deletes the last line from the console history. +================ +*/ +void ConBuffer_DeleteLastLine(conbuffer_t *buf) +{ + if(buf->lines_count == 0) + return; + --buf->lines_count; +} + +/* +================ +ConBuffer_BytesLeft + +Checks if there is space for a line of the given length, and if yes, returns a +pointer to the start of such a space, and NULL otherwise. +================ +*/ +static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len) +{ + if(len > buf->textsize) + return NULL; + if(buf->lines_count == 0) + return buf->text; + else + { + char *firstline_start = buf->lines[buf->lines_first].start; + char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len; + // the buffer is cyclic, so we first have two cases... + if(firstline_start < lastline_onepastend) // buffer is contiguous + { + // put at end? + if(len <= buf->text + buf->textsize - lastline_onepastend) + return lastline_onepastend; + // put at beginning? + else if(len <= firstline_start - buf->text) + return buf->text; + else + return NULL; + } + else // buffer has a contiguous hole + { + if(len <= firstline_start - lastline_onepastend) + return lastline_onepastend; + else + return NULL; + } + } +} + +/* +================ +ConBuffer_AddLine + +Appends a given string as a new line to the console. +================ +*/ +void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask) +{ + char *putpos; + con_lineinfo_t *p; + + // developer_memory 1 during shutdown prints while conbuffer_t is being freed + if (!buf->active) + return; + + ConBuffer_FixTimes(buf); + + if(len >= buf->textsize) + { + // line too large? + // only display end of line. + line += len - buf->textsize + 1; + len = buf->textsize - 1; + } + while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines) + ConBuffer_DeleteLine(buf); + memcpy(putpos, line, len); + putpos[len] = 0; + ++buf->lines_count; + + //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST); + + p = &CONBUFFER_LINES_LAST(buf); + p->start = putpos; + p->len = len; + p->addtime = cl.time; + p->mask = mask; + p->height = -1; // calculate when needed +} + +int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start) +{ + int i; + if(start == -1) + start = buf->lines_count; + for(i = start - 1; i >= 0; --i) + { + con_lineinfo_t *l = &CONBUFFER_LINES(buf, i); + + if((l->mask & mask_must) != mask_must) + continue; + if(l->mask & mask_mustnot) + continue; + + return i; + } + + return -1; +} + +int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start) +{ + int i; + for(i = start + 1; i < buf->lines_count; ++i) + { + con_lineinfo_t *l = &CONBUFFER_LINES(buf, i); + + if((l->mask & mask_must) != mask_must) + continue; + if(l->mask & mask_mustnot) + continue; + + return i; + } + + return -1; +} + +const char *ConBuffer_GetLine(conbuffer_t *buf, int i) +{ + static char copybuf[MAX_INPUTLINE]; + con_lineinfo_t *l = &CONBUFFER_LINES(buf, i); + size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1; + strlcpy(copybuf, l->start, sz); + return copybuf; +} + +/* +============================================================================== + +LOGGING + +============================================================================== +*/ + +/// \name Logging +//@{ +cvar_t log_file = {0, "log_file","", "filename to log messages to"}; +cvar_t log_dest_udp = {0, "log_dest_udp","", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"}; +char log_dest_buffer[1400]; // UDP packet +size_t log_dest_buffer_pos; +unsigned int log_dest_buffer_appending; +char crt_log_file [MAX_OSPATH] = ""; +qfile_t* logfile = NULL; + +unsigned char* logqueue = NULL; +size_t logq_ind = 0; +size_t logq_size = 0; + +void Log_ConPrint (const char *msg); +//@} +/* +==================== +Log_DestBuffer_Init +==================== +*/ +static void Log_DestBuffer_Init(void) +{ + memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print + log_dest_buffer_pos = 5; +} + +/* +==================== +Log_DestBuffer_Flush +==================== +*/ +void Log_DestBuffer_Flush(void) +{ + lhnetaddress_t log_dest_addr; + lhnetsocket_t *log_dest_socket; + const char *s = log_dest_udp.string; + qboolean have_opened_temp_sockets = false; + if(s) if(log_dest_buffer_pos > 5) + { + ++log_dest_buffer_appending; + log_dest_buffer[log_dest_buffer_pos++] = 0; + + if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one + { + have_opened_temp_sockets = true; + NetConn_OpenServerPorts(true); + } + + while(COM_ParseToken_Console(&s)) + if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000)) + { + log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr); + if(!log_dest_socket) + log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr); + if(log_dest_socket) + NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr); + } + + if(have_opened_temp_sockets) + NetConn_CloseServerPorts(); + --log_dest_buffer_appending; + } + log_dest_buffer_pos = 0; +} + +/* +==================== +Log_Timestamp +==================== +*/ +const char* Log_Timestamp (const char *desc) +{ + static char timestamp [128]; + time_t crt_time; +#if _MSC_VER >= 1400 + struct tm crt_tm; +#else + struct tm *crt_tm; +#endif + char timestring [64]; + + // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993"); + time (&crt_time); +#if _MSC_VER >= 1400 + localtime_s (&crt_tm, &crt_time); + strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm); +#else + crt_tm = localtime (&crt_time); + strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm); +#endif + + if (desc != NULL) + dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring); + else + dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring); + + return timestamp; +} + + +/* +==================== +Log_Open +==================== +*/ +void Log_Open (void) +{ + if (logfile != NULL || log_file.string[0] == '\0') + return; + + logfile = FS_OpenRealFile(log_file.string, "a", false); + if (logfile != NULL) + { + strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file)); + FS_Print (logfile, Log_Timestamp ("Log started")); + } +} + + +/* +==================== +Log_Close +==================== +*/ +void Log_Close (void) +{ + if (logfile == NULL) + return; + + FS_Print (logfile, Log_Timestamp ("Log stopped")); + FS_Print (logfile, "\n"); + FS_Close (logfile); + + logfile = NULL; + crt_log_file[0] = '\0'; +} + + +/* +==================== +Log_Start +==================== +*/ +void Log_Start (void) +{ + size_t pos; + size_t n; + Log_Open (); + + // Dump the contents of the log queue into the log file and free it + if (logqueue != NULL) + { + unsigned char *temp = logqueue; + logqueue = NULL; + if(logq_ind != 0) + { + if (logfile != NULL) + FS_Write (logfile, temp, logq_ind); + if(*log_dest_udp.string) + { + for(pos = 0; pos < logq_ind; ) + { + if(log_dest_buffer_pos == 0) + Log_DestBuffer_Init(); + n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos); + memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n); + log_dest_buffer_pos += n; + Log_DestBuffer_Flush(); + pos += n; + } + } + } + Mem_Free (temp); + logq_ind = 0; + logq_size = 0; + } +} + + +/* +================ +Log_ConPrint +================ +*/ +void Log_ConPrint (const char *msg) +{ + static qboolean inprogress = false; + + // don't allow feedback loops with memory error reports + if (inprogress) + return; + inprogress = true; + + // Until the host is completely initialized, we maintain a log queue + // to store the messages, since the log can't be started before + if (logqueue != NULL) + { + size_t remain = logq_size - logq_ind; + size_t len = strlen (msg); + + // If we need to enlarge the log queue + if (len > remain) + { + size_t factor = ((logq_ind + len) / logq_size) + 1; + unsigned char* newqueue; + + logq_size *= factor; + newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size); + memcpy (newqueue, logqueue, logq_ind); + Mem_Free (logqueue); + logqueue = newqueue; + remain = logq_size - logq_ind; + } + memcpy (&logqueue[logq_ind], msg, len); + logq_ind += len; + + inprogress = false; + return; + } + + // Check if log_file has changed + if (strcmp (crt_log_file, log_file.string) != 0) + { + Log_Close (); + Log_Open (); + } + + // If a log file is available + if (logfile != NULL) + FS_Print (logfile, msg); + + inprogress = false; +} + + +/* +================ +Log_Printf +================ +*/ +void Log_Printf (const char *logfilename, const char *fmt, ...) +{ + qfile_t *file; + + file = FS_OpenRealFile(logfilename, "a", true); + if (file != NULL) + { + va_list argptr; + + va_start (argptr, fmt); + FS_VPrintf (file, fmt, argptr); + va_end (argptr); + + FS_Close (file); + } +} + + +/* +============================================================================== + +CONSOLE + +============================================================================== +*/ + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f (void) +{ + // toggle the 'user wants console' bit + key_consoleactive ^= KEY_CONSOLEACTIVE_USER; + Con_ClearNotify(); +} + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify (void) +{ + int i; + for(i = 0; i < CON_LINES_COUNT; ++i) + CON_LINES(i).mask |= CON_MASK_HIDENOTIFY; +} + + +/* +================ +Con_MessageMode_f +================ +*/ +void Con_MessageMode_f (void) +{ + key_dest = key_message; + chat_mode = 0; // "say" + chat_bufferlen = 0; + chat_buffer[0] = 0; +} + + +/* +================ +Con_MessageMode2_f +================ +*/ +void Con_MessageMode2_f (void) +{ + key_dest = key_message; + chat_mode = 1; // "say_team" + chat_bufferlen = 0; + chat_buffer[0] = 0; +} + +/* +================ +Con_CommandMode_f +================ +*/ +void Con_CommandMode_f (void) +{ + key_dest = key_message; + if(Cmd_Argc() > 1) + { + dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args()); + chat_bufferlen = strlen(chat_buffer); + } + chat_mode = -1; // command +} + +/* +================ +Con_CheckResize +================ +*/ +void Con_CheckResize (void) +{ + int i, width; + float f; + + f = bound(1, con_textsize.value, 128); + if(f != con_textsize.value) + Cvar_SetValueQuick(&con_textsize, f); + width = (int)floor(vid_conwidth.value / con_textsize.value); + width = bound(1, width, con.textsize/4); + // FIXME uses con in a non abstracted way + + if (width == con_linewidth) + return; + + con_linewidth = width; + + for(i = 0; i < CON_LINES_COUNT; ++i) + CON_LINES(i).height = -1; // recalculate when next needed + + Con_ClearNotify(); + con_backscroll = 0; +} + +//[515]: the simplest command ever +//LordHavoc: not so simple after I made it print usage... +static void Con_Maps_f (void) +{ + if (Cmd_Argc() > 2) + { + Con_Printf("usage: maps [mapnameprefix]\n"); + return; + } + else if (Cmd_Argc() == 2) + GetMapList(Cmd_Argv(1), NULL, 0); + else + GetMapList("", NULL, 0); +} + +void Con_ConDump_f (void) +{ + int i; + qfile_t *file; + if (Cmd_Argc() != 2) + { + Con_Printf("usage: condump \n"); + return; + } + file = FS_OpenRealFile(Cmd_Argv(1), "w", false); + if (!file) + { + Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1)); + return; + } + for(i = 0; i < CON_LINES_COUNT; ++i) + { + FS_Write(file, CON_LINES(i).start, CON_LINES(i).len); + FS_Write(file, "\n", 1); + } + FS_Close(file); +} + +void Con_Clear_f (void) +{ + ConBuffer_Clear(&con); +} + +/* +================ +Con_Init +================ +*/ +void Con_Init (void) +{ + con_linewidth = 80; + ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool); + + // Allocate a log queue, this will be freed after configs are parsed + logq_size = MAX_INPUTLINE; + logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size); + logq_ind = 0; + + Cvar_RegisterVariable (&sys_colortranslation); + Cvar_RegisterVariable (&sys_specialcharactertranslation); + + Cvar_RegisterVariable (&log_file); + Cvar_RegisterVariable (&log_dest_udp); + + // support for the classic Quake option +// COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file + if (COM_CheckParm ("-condebug") != 0) + Cvar_SetQuick (&log_file, "qconsole.log"); + + // register our cvars + Cvar_RegisterVariable (&con_chat); + Cvar_RegisterVariable (&con_chatpos); + Cvar_RegisterVariable (&con_chatrect_x); + Cvar_RegisterVariable (&con_chatrect_y); + Cvar_RegisterVariable (&con_chatrect); + Cvar_RegisterVariable (&con_chatsize); + Cvar_RegisterVariable (&con_chattime); + Cvar_RegisterVariable (&con_chatwidth); + Cvar_RegisterVariable (&con_notify); + Cvar_RegisterVariable (&con_notifyalign); + Cvar_RegisterVariable (&con_notifysize); + Cvar_RegisterVariable (&con_notifytime); + Cvar_RegisterVariable (&con_textsize); + Cvar_RegisterVariable (&con_chatsound); + + // --blub + Cvar_RegisterVariable (&con_nickcompletion); + Cvar_RegisterVariable (&con_nickcompletion_flags); + + Cvar_RegisterVariable (&con_completion_playdemo); // *.dem + Cvar_RegisterVariable (&con_completion_timedemo); // *.dem + Cvar_RegisterVariable (&con_completion_exec); // *.cfg + + // register our commands + Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console"); + Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone"); + Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team"); + Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command"); + Cmd_AddCommand ("clear", Con_Clear_f, "clear console history"); + Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps"); + Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)"); + + con_initialized = true; + Con_DPrint("Console initialized.\n"); +} + +void Con_Shutdown (void) +{ + ConBuffer_Shutdown(&con); +} + +/* +================ +Con_PrintToHistory + +Handles cursor positioning, line wrapping, etc +All console printing must go through this in order to be displayed +If no console is visible, the notify window will pop up. +================ +*/ +void Con_PrintToHistory(const char *txt, int mask) +{ + // process: + // \n goes to next line + // \r deletes current line and makes a new one + + static int cr_pending = 0; + static char buf[CON_TEXTSIZE]; + static int bufpos = 0; + + if(!con.text) // FIXME uses a non-abstracted property of con + return; + + for(; *txt; ++txt) + { + if(cr_pending) + { + ConBuffer_DeleteLastLine(&con); + cr_pending = 0; + } + switch(*txt) + { + case 0: + break; + case '\r': + ConBuffer_AddLine(&con, buf, bufpos, mask); + bufpos = 0; + cr_pending = 1; + break; + case '\n': + ConBuffer_AddLine(&con, buf, bufpos, mask); + bufpos = 0; + break; + default: + buf[bufpos++] = *txt; + if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con + { + ConBuffer_AddLine(&con, buf, bufpos, mask); + bufpos = 0; + } + break; + } + } +} + +/*! The translation table between the graphical font and plain ASCII --KB */ +static char qfont_table[256] = { + '\0', '#', '#', '#', '#', '.', '#', '#', + '#', 9, 10, '#', ' ', 13, '.', '.', + '[', ']', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '.', '<', '=', '>', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', '<', + + '<', '=', '>', '#', '#', '.', '#', '#', + '#', '#', ' ', '#', ' ', '>', '.', '.', + '[', ']', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '.', '<', '=', '>', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', '<' +}; + +void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol) +{ + rcon_redirect_sock = sock; + rcon_redirect_dest = dest; + rcon_redirect_proquakeprotocol = proquakeprotocol; + if (rcon_redirect_proquakeprotocol) + { + // reserve space for the packet header + rcon_redirect_buffer[0] = 0; + rcon_redirect_buffer[1] = 0; + rcon_redirect_buffer[2] = 0; + rcon_redirect_buffer[3] = 0; + // this is a reply to a CCREQ_RCON + rcon_redirect_buffer[4] = (char)CCREP_RCON; + } + else + memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print + rcon_redirect_bufferpos = 5; +} + +void Con_Rcon_Redirect_Flush(void) +{ + rcon_redirect_buffer[rcon_redirect_bufferpos] = 0; + if (rcon_redirect_proquakeprotocol) + { + // update the length in the packet header + StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK)); + } + NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest); + memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print + rcon_redirect_bufferpos = 5; + rcon_redirect_proquakeprotocol = false; +} + +void Con_Rcon_Redirect_End(void) +{ + Con_Rcon_Redirect_Flush(); + rcon_redirect_dest = NULL; + rcon_redirect_sock = NULL; +} + +void Con_Rcon_Redirect_Abort(void) +{ + rcon_redirect_dest = NULL; + rcon_redirect_sock = NULL; +} + +/* +================ +Con_Rcon_AddChar +================ +*/ +/// Adds a character to the rcon buffer. +void Con_Rcon_AddChar(int c) +{ + if(log_dest_buffer_appending) + return; + ++log_dest_buffer_appending; + + // if this print is in response to an rcon command, add the character + // to the rcon redirect buffer + + if (rcon_redirect_dest) + { + rcon_redirect_buffer[rcon_redirect_bufferpos++] = c; + if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1) + Con_Rcon_Redirect_Flush(); + } + else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way + { + if(log_dest_buffer_pos == 0) + Log_DestBuffer_Init(); + log_dest_buffer[log_dest_buffer_pos++] = c; + if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero + Log_DestBuffer_Flush(); + } + else + log_dest_buffer_pos = 0; + + --log_dest_buffer_appending; +} + +/** + * Convert an RGB color to its nearest quake color. + * I'll cheat on this a bit by translating the colors to HSV first, + * S and V decide if it's black or white, otherwise, H will decide the + * actual color. + * @param _r Red (0-255) + * @param _g Green (0-255) + * @param _b Blue (0-255) + * @return A quake color character. + */ +static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b) +{ + float r = ((float)_r)/255.0; + float g = ((float)_g)/255.0; + float b = ((float)_b)/255.0; + float min = min(r, min(g, b)); + float max = max(r, max(g, b)); + + int h; ///< Hue angle [0,360] + float s; ///< Saturation [0,1] + float v = max; ///< In HSV v == max [0,1] + + if(max == min) + s = 0; + else + s = 1.0 - (min/max); + + // Saturation threshold. We now say 0.2 is the minimum value for a color! + if(s < 0.2) + { + // If the value is less than half, return a black color code. + // Otherwise return a white one. + if(v < 0.5) + return '0'; + return '7'; + } + + // Let's get the hue angle to define some colors: + if(max == min) + h = 0; + else if(max == r) + h = (int)(60.0 * (g-b)/(max-min))%360; + else if(max == g) + h = (int)(60.0 * (b-r)/(max-min) + 120); + else // if(max == b) redundant check + h = (int)(60.0 * (r-g)/(max-min) + 240); + + if(h < 36) // *red* to orange + return '1'; + else if(h < 80) // orange over *yellow* to evilish-bright-green + return '3'; + else if(h < 150) // evilish-bright-green over *green* to ugly bright blue + return '2'; + else if(h < 200) // ugly bright blue over *bright blue* to darkish blue + return '5'; + else if(h < 270) // darkish blue over *dark blue* to cool purple + return '4'; + else if(h < 330) // cool purple over *purple* to ugly swiny red + return '6'; + else // ugly red to red closes the circly + return '1'; +} + +/* +================ +Con_MaskPrint +================ +*/ +extern cvar_t timestamps; +extern cvar_t timeformat; +extern qboolean sys_nostdout; +void Con_MaskPrint(int additionalmask, const char *msg) +{ + static int mask = 0; + static int index = 0; + static char line[MAX_INPUTLINE]; + + for (;*msg;msg++) + { + Con_Rcon_AddChar(*msg); + // if this is the beginning of a new line, print timestamp + if (index == 0) + { + const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : ""; + // reset the color + // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7! + line[index++] = STRING_COLOR_TAG; + // assert( STRING_COLOR_DEFAULT < 10 ) + line[index++] = STRING_COLOR_DEFAULT + '0'; + // special color codes for chat messages must always come first + // for Con_PrintToHistory to work properly + if (*msg == 1 || *msg == 2) + { + // play talk wav + if (*msg == 1) + { + if (con_chatsound.value) + { + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + if(msg[1] == '\r' && cl.foundtalk2wav) + S_LocalSound ("sound/misc/talk2.wav"); + else + S_LocalSound ("sound/misc/talk.wav"); + } + else + { + if (msg[1] == '(' && cl.foundtalk2wav) + S_LocalSound ("sound/misc/talk2.wav"); + else + S_LocalSound ("sound/misc/talk.wav"); + } + } + mask = CON_MASK_CHAT; + } + line[index++] = STRING_COLOR_TAG; + line[index++] = '3'; + msg++; + Con_Rcon_AddChar(*msg); + } + // store timestamp + for (;*timestamp;index++, timestamp++) + if (index < (int)sizeof(line) - 2) + line[index] = *timestamp; + // add the mask + mask |= additionalmask; + } + // append the character + line[index++] = *msg; + // if this is a newline character, we have a complete line to print + if (*msg == '\n' || index >= (int)sizeof(line) / 2) + { + // terminate the line + line[index] = 0; + // send to log file + Log_ConPrint(line); + // send to scrollable buffer + if (con_initialized && cls.state != ca_dedicated) + { + Con_PrintToHistory(line, mask); + } + // send to terminal or dedicated server window + if (!sys_nostdout) + if (developer.integer || !(mask & CON_MASK_DEVELOPER)) + { + if(sys_specialcharactertranslation.integer) + { + char *p; + const char *q; + p = line; + while(*p) + { + int ch = u8_getchar(p, &q); + if(ch >= 0xE000 && ch <= 0xE0FF) + { + *p = qfont_table[ch - 0xE000]; + if(q > p+1) + memmove(p+1, q, strlen(q)+1); + p = p + 1; + } + else + p = p + (q - p); + } + } + + if(sys_colortranslation.integer == 1) // ANSI + { + static char printline[MAX_INPUTLINE * 4 + 3]; + // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end + // a newline can transform into four bytes, but then prevents the three extra bytes from appearing + int lastcolor = 0; + const char *in; + char *out; + int color; + for(in = line, out = printline; *in; ++in) + { + switch(*in) + { + case STRING_COLOR_TAG: + if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) ) + { + char r = tolower(in[2]); + char g = tolower(in[3]); + char b = tolower(in[4]); + // it's a hex digit already, so the else part needs no check --blub + if(isdigit(r)) r -= '0'; + else r -= 87; + if(isdigit(g)) g -= '0'; + else g -= 87; + if(isdigit(b)) b -= '0'; + else b -= 87; + + color = Sys_Con_NearestColor(r * 17, g * 17, b * 17); + in += 3; // 3 only, the switch down there does the fourth + } + else + color = in[1]; + + switch(color) + { + case STRING_COLOR_TAG: + ++in; + *out++ = STRING_COLOR_TAG; + break; + case '0': + case '7': + // normal color + ++in; + if(lastcolor == 0) break; else lastcolor = 0; + *out++ = 0x1B; *out++ = '['; *out++ = 'm'; + break; + case '1': + // light red + ++in; + if(lastcolor == 1) break; else lastcolor = 1; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm'; + break; + case '2': + // light green + ++in; + if(lastcolor == 2) break; else lastcolor = 2; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm'; + break; + case '3': + // yellow + ++in; + if(lastcolor == 3) break; else lastcolor = 3; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm'; + break; + case '4': + // light blue + ++in; + if(lastcolor == 4) break; else lastcolor = 4; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm'; + break; + case '5': + // light cyan + ++in; + if(lastcolor == 5) break; else lastcolor = 5; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm'; + break; + case '6': + // light magenta + ++in; + if(lastcolor == 6) break; else lastcolor = 6; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm'; + break; + // 7 handled above + case '8': + case '9': + // bold normal color + ++in; + if(lastcolor == 8) break; else lastcolor = 8; + *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm'; + break; + default: + *out++ = STRING_COLOR_TAG; + break; + } + break; + case '\n': + if(lastcolor != 0) + { + *out++ = 0x1B; *out++ = '['; *out++ = 'm'; + lastcolor = 0; + } + *out++ = *in; + break; + default: + *out++ = *in; + break; + } + } + if(lastcolor != 0) + { + *out++ = 0x1B; + *out++ = '['; + *out++ = 'm'; + } + *out++ = 0; + Sys_PrintToTerminal(printline); + } + else if(sys_colortranslation.integer == 2) // Quake + { + Sys_PrintToTerminal(line); + } + else // strip + { + static char printline[MAX_INPUTLINE]; // it can only get shorter here + const char *in; + char *out; + for(in = line, out = printline; *in; ++in) + { + switch(*in) + { + case STRING_COLOR_TAG: + switch(in[1]) + { + case STRING_COLOR_RGB_TAG_CHAR: + if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) ) + { + in+=4; + break; + } + *out++ = STRING_COLOR_TAG; + *out++ = STRING_COLOR_RGB_TAG_CHAR; + ++in; + break; + case STRING_COLOR_TAG: + ++in; + *out++ = STRING_COLOR_TAG; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ++in; + break; + default: + *out++ = STRING_COLOR_TAG; + break; + } + break; + default: + *out++ = *in; + break; + } + } + *out++ = 0; + Sys_PrintToTerminal(printline); + } + } + // empty the line buffer + index = 0; + mask = 0; + } + } +} + +/* +================ +Con_MaskPrintf +================ +*/ +void Con_MaskPrintf(int mask, const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Con_MaskPrint(mask, msg); +} + +/* +================ +Con_Print +================ +*/ +void Con_Print(const char *msg) +{ + Con_MaskPrint(CON_MASK_PRINT, msg); +} + +/* +================ +Con_Printf +================ +*/ +void Con_Printf(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Con_MaskPrint(CON_MASK_PRINT, msg); +} + +/* +================ +Con_DPrint +================ +*/ +void Con_DPrint(const char *msg) +{ + if(developer.integer < 0) // at 0, we still add to the buffer but hide + return; + + Con_MaskPrint(CON_MASK_DEVELOPER, msg); +} + +/* +================ +Con_DPrintf +================ +*/ +void Con_DPrintf(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + if(developer.integer < 0) // at 0, we still add to the buffer but hide + return; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Con_MaskPrint(CON_MASK_DEVELOPER, msg); +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + +/* +================ +Con_DrawInput + +The input line scrolls horizontally if typing goes beyond the right edge + +Modified by EvilTypeGuy eviltypeguy@qeradiant.com +================ +*/ +extern cvar_t r_font_disable_freetype; +void Con_DrawInput (void) +{ + int y; + int i; + char editlinecopy[MAX_INPUTLINE+1], *text; + float x, xo; + size_t len_out; + int col_out; + + if (!key_consoleactive) + return; // don't draw anything + + strlcpy(editlinecopy, key_line, sizeof(editlinecopy)); + text = editlinecopy; + + // Advanced Console Editing by Radix radix@planetquake.com + // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com + // use strlen of edit_line instead of key_linepos to allow editing + // of early characters w/o erasing + + y = (int)strlen(text); + + // append enoug nul-bytes to cover the utf8-versions of the cursor too + for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i) + text[i] = 0; + + // add the cursor frame + if (r_font_disable_freetype.integer) + { + // this code is freetype incompatible! + if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible + { + if (!utf8_enable.integer) + text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right + else if (y + 3 < (int)sizeof(editlinecopy)-1) + { + int ofs = u8_bytelen(text + key_linepos, 1); + size_t len; + const char *curbuf; + curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len); + + if (curbuf) + { + memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len); + memcpy(text + key_linepos, curbuf, len); + } + } else + text[key_linepos] = '-' + ('+' - '-') * key_insert; + } + } + +// text[key_linepos + 1] = 0; + + len_out = key_linepos; + col_out = -1; + xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000); + x = vid_conwidth.value * 0.95 - xo; // scroll + if(x >= 0) + x = 0; + + // draw it + DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE ); + + // add a cursor on top of this (when using freetype) + if (!r_font_disable_freetype.integer) + { + if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible + { + if (!utf8_enable.integer) + { + text[0] = 11 + 130 * key_insert; // either solid or triangle facing right + text[1] = 0; + } + else + { + size_t len; + const char *curbuf; + curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len); + memcpy(text, curbuf, len); + text[len] = 0; + } + DrawQ_String(x + xo, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, FONT_CONSOLE); + } + } + + // remove cursor +// key_line[key_linepos] = 0; +} + +typedef struct +{ + dp_font_t *font; + float alignment; // 0 = left, 0.5 = center, 1 = right + float fontsize; + float x; + float y; + float width; + float ymin, ymax; + const char *continuationString; + + // PRIVATE: + int colorindex; // init to -1 +} +con_text_info_t; + +float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth) +{ + con_text_info_t *ti = (con_text_info_t *) passthrough; + if(w == NULL) + { + ti->colorindex = -1; + return ti->fontsize * ti->font->maxwidth; + } + if(maxWidth >= 0) + return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char + else if(maxWidth == -1) + return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font); + else + { + printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth); + // Note: this is NOT a Con_Printf, as it could print recursively + return 0; + } +} + +int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) +{ + (void) passthrough; + (void) line; + (void) length; + (void) width; + (void) isContinuation; + return 1; +} + +int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) +{ + con_text_info_t *ti = (con_text_info_t *) passthrough; + + if(ti->y < ti->ymin - 0.001) + (void) 0; + else if(ti->y > ti->ymax - ti->fontsize + 0.001) + (void) 0; + else + { + int x = (int) (ti->x + (ti->width - width) * ti->alignment); + if(isContinuation && *ti->continuationString) + x = (int) DrawQ_String(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font); + if(length > 0) + DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font); + } + + ti->y += ti->fontsize; + return 1; +} + +int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString) +{ + int i; + int lines = 0; + int maxlines = (int) floor(height / fontsize + 0.01f); + int startidx; + int nskip = 0; + int continuationWidth = 0; + size_t l; + double t = cl.time; // saved so it won't change + con_text_info_t ti; + + ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY; + ti.fontsize = fontsize; + ti.alignment = alignment_x; + ti.width = width; + ti.ymin = y; + ti.ymax = y + height; + ti.continuationString = continuationString; + + l = 0; + Con_WordWidthFunc(&ti, NULL, &l, -1); + l = strlen(continuationString); + continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1); + + // first find the first line to draw by backwards iterating and word wrapping to find their length... + startidx = CON_LINES_COUNT; + for(i = CON_LINES_COUNT - 1; i >= 0; --i) + { + con_lineinfo_t *l = &CON_LINES(i); + int mylines; + + if((l->mask & mask_must) != mask_must) + continue; + if(l->mask & mask_mustnot) + continue; + if(maxage && (l->addtime < t - maxage)) + continue; + + // WE FOUND ONE! + // Calculate its actual height... + mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti); + if(lines + mylines >= maxlines) + { + nskip = lines + mylines - maxlines; + lines = maxlines; + startidx = i; + break; + } + lines += mylines; + startidx = i; + } + + // then center according to the calculated amount of lines... + ti.x = x; + ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize; + + // then actually draw + for(i = startidx; i < CON_LINES_COUNT; ++i) + { + con_lineinfo_t *l = &CON_LINES(i); + + if((l->mask & mask_must) != mask_must) + continue; + if(l->mask & mask_mustnot) + continue; + if(maxage && (l->addtime < t - maxage)) + continue; + + COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti); + } + + return lines; +} + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify (void) +{ + float x, v, xr; + float chatstart, notifystart, inputsize, height; + float align; + char temptext[MAX_INPUTLINE]; + int numChatlines; + int chatpos; + + ConBuffer_FixTimes(&con); + + numChatlines = con_chat.integer; + + chatpos = con_chatpos.integer; + + if (con_notify.integer < 0) + Cvar_SetValueQuick(&con_notify, 0); + if (gamemode == GAME_TRANSFUSION) + v = 8; // vertical offset + else + v = 0; + + // GAME_NEXUIZ: center, otherwise left justify + align = con_notifyalign.value; + if(!*con_notifyalign.string) // empty string, evaluated to 0 above + { + if(gamemode == GAME_NEXUIZ) + align = 0.5; + } + + if(numChatlines || !con_chatrect.integer) + { + if(chatpos == 0) + { + // first chat, input line, then notify + chatstart = v; + notifystart = v + (numChatlines + 1) * con_chatsize.value; + } + else if(chatpos > 0) + { + // first notify, then (chatpos-1) empty lines, then chat, then input + notifystart = v; + chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value; + } + else // if(chatpos < 0) + { + // first notify, then much space, then chat, then input, then -chatpos-1 empty lines + notifystart = v; + chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value; + } + } + else + { + // just notify and input + notifystart = v; + chatstart = 0; // shut off gcc warning + } + + v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, ""); + + if(con_chatrect.integer) + { + x = con_chatrect_x.value * vid_conwidth.value; + v = con_chatrect_y.value * vid_conheight.value; + } + else + { + x = 0; + if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong + v = chatstart; + } + height = numChatlines * con_chatsize.value; + + if(numChatlines) + { + Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, con_chatsize.value, 0.0, 1.0, (utf8_enable.integer ? "^3\xee\x80\x8c\xee\x80\x8c\xee\x80\x8c " : "^3\014\014\014 ")); // 015 is ·> character in conchars.tga + v += height; + } + if (key_dest == key_message) + { + //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on } + int colorindex = -1; + const char *cursor; + cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL); + + // LordHavoc: speedup, and other improvements + if (chat_mode < 0) + dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor); + else if(chat_mode) + dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor); + else + dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor); + + // FIXME word wrap + inputsize = (numChatlines ? con_chatsize : con_notifysize).value; + xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT); + x = min(xr, x); + DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT); + } +} + +/* +================ +Con_LineHeight + +Returns the height of a given console line; calculates it if necessary. +================ +*/ +int Con_LineHeight(int lineno) +{ + con_lineinfo_t *li = &CON_LINES(lineno); + if(li->height == -1) + { + float width = vid_conwidth.value; + con_text_info_t ti; + con_lineinfo_t *li = &CON_LINES(lineno); + ti.fontsize = con_textsize.value; + ti.font = FONT_CONSOLE; + li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL); + } + return li->height; +} + +/* +================ +Con_DrawConsoleLine + +Draws a line of the console; returns its height in lines. +If alpha is 0, the line is not drawn, but still wrapped and its height +returned. +================ +*/ +int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax) +{ + float width = vid_conwidth.value; + con_text_info_t ti; + con_lineinfo_t *li = &CON_LINES(lineno); + + if((li->mask & mask_must) != mask_must) + return 0; + if((li->mask & mask_mustnot) != 0) + return 0; + + ti.continuationString = ""; + ti.alignment = 0; + ti.fontsize = con_textsize.value; + ti.font = FONT_CONSOLE; + ti.x = 0; + ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize; + ti.ymin = ymin; + ti.ymax = ymax; + ti.width = width; + + return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti); +} + +/* +================ +Con_LastVisibleLine + +Calculates the last visible line index and how much to show of it based on +con_backscroll. +================ +*/ +static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast) +{ + int lines_seen = 0; + int i; + + if(con_backscroll < 0) + con_backscroll = 0; + + *last = 0; + + // now count until we saw con_backscroll actual lines + for(i = CON_LINES_COUNT - 1; i >= 0; --i) + if((CON_LINES(i).mask & mask_must) == mask_must) + if((CON_LINES(i).mask & mask_mustnot) == 0) + { + int h = Con_LineHeight(i); + + // line is the last visible line? + *last = i; + if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll) + { + *limitlast = lines_seen + h - con_backscroll; + return; + } + + lines_seen += h; + } + + // if we get here, no line was on screen - scroll so that one line is + // visible then. + con_backscroll = lines_seen - 1; + *limitlast = 1; +} + +/* +================ +Con_DrawConsole + +Draws the console with the solid background +The typing input line at the bottom should only be drawn if typing is allowed +================ +*/ +void Con_DrawConsole (int lines) +{ + float alpha, alpha0; + double sx, sy; + int mask_must = 0; + int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER; + cachepic_t *conbackpic; + + if (lines <= 0) + return; + + if (con_backscroll < 0) + con_backscroll = 0; + + con_vislines = lines; + + r_draw2d_force = true; + +// draw the background + alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game + if((alpha = alpha0 * scr_conalphafactor.value) > 0) + { + sx = scr_conscroll_x.value; + sy = scr_conscroll_y.value; + conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL; + sx *= realtime; sy *= realtime; + sx -= floor(sx); sy -= floor(sy); + if (conbackpic && conbackpic->tex != r_texture_notexture) + DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, + 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0); + else + DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0); + } + if((alpha = alpha0 * scr_conalpha2factor.value) > 0) + { + sx = scr_conscroll2_x.value; + sy = scr_conscroll2_y.value; + conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0); + sx *= realtime; sy *= realtime; + sx -= floor(sx); sy -= floor(sy); + if(conbackpic && conbackpic->tex != r_texture_notexture) + DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, + 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0); + } + if((alpha = alpha0 * scr_conalpha3factor.value) > 0) + { + sx = scr_conscroll3_x.value; + sy = scr_conscroll3_y.value; + conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0); + sx *= realtime; sy *= realtime; + sx -= floor(sx); sy -= floor(sy); + if(conbackpic && conbackpic->tex != r_texture_notexture) + DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, + 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0); + } + DrawQ_String(vid_conwidth.integer - DrawQ_TextWidth(engineversion, 0, con_textsize.value, con_textsize.value, false, FONT_CONSOLE), lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE); + +// draw the text +#if 0 + { + int i; + int count = CON_LINES_COUNT; + float ymax = con_vislines - 2 * con_textsize.value; + float y = ymax + con_textsize.value * con_backscroll; + for (i = 0;i < count && y >= 0;i++) + y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value; + // fix any excessive scrollback for the next frame + if (i >= count && y >= 0) + { + con_backscroll -= (int)(y / con_textsize.value); + if (con_backscroll < 0) + con_backscroll = 0; + } + } +#else + if(CON_LINES_COUNT > 0) + { + int i, last, limitlast; + float y; + float ymax = con_vislines - 2 * con_textsize.value; + Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast); + //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast); + y = ymax - con_textsize.value; + + if(limitlast) + y += (CON_LINES(last).height - limitlast) * con_textsize.value; + i = last; + + for(;;) + { + y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value; + if(i == 0) + break; // top of console buffer + if(y < 0) + break; // top of console window + limitlast = 0; + --i; + } + } +#endif + +// draw the input prompt, user text, and cursor if desired + Con_DrawInput (); + + r_draw2d_force = false; +} + +/* +GetMapList + +Made by [515] +Prints not only map filename, but also +its format (q1/q2/q3/hl) and even its message +*/ +//[515]: here is an ugly hack.. two gotos... oh my... *but it works* +//LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing +//LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto +//LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups... +qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength) +{ + fssearch_t *t; + char message[1024]; + int i, k, max, p, o, min; + unsigned char *len; + qfile_t *f; + unsigned char buf[1024]; + + dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s); + t = FS_Search(message, 1, true); + if(!t) + return false; + if (t->numfilenames > 1) + Con_Printf("^1 %i maps found :\n", t->numfilenames); + len = (unsigned char *)Z_Malloc(t->numfilenames); + min = 666; + for(max=i=0;inumfilenames;i++) + { + k = (int)strlen(t->filenames[i]); + k -= 9; + if(max < k) + max = k; + else + if(min > k) + min = k; + len[i] = k; + } + o = (int)strlen(s); + for(i=0;inumfilenames;i++) + { + int lumpofs = 0, lumplen = 0; + char *entities = NULL; + const char *data = NULL; + char keyname[64]; + char entfilename[MAX_QPATH]; + strlcpy(message, "^1**ERROR**^7", sizeof(message)); + p = 0; + f = FS_OpenVirtualFile(t->filenames[i], true); + if(f) + { + memset(buf, 0, 1024); + FS_Read(f, buf, 1024); + if (!memcmp(buf, "IBSP", 4)) + { + p = LittleLong(((int *)buf)[1]); + if (p == Q3BSPVERSION) + { + q3dheader_t *header = (q3dheader_t *)buf; + lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs); + lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen); + } + else if (p == Q2BSPVERSION) + { + q2dheader_t *header = (q2dheader_t *)buf; + lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs); + lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen); + } + } + else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30) + { + dheader_t *header = (dheader_t *)buf; + lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs); + lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen); + } + else + p = 0; + strlcpy(entfilename, t->filenames[i], sizeof(entfilename)); + memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5); + entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL); + if (!entities && lumplen >= 10) + { + FS_Seek(f, lumpofs, SEEK_SET); + entities = (char *)Z_Malloc(lumplen + 1); + FS_Read(f, entities, lumplen); + } + if (entities) + { + // if there are entities to parse, a missing message key just + // means there is no title, so clear the message string now + message[0] = 0; + data = entities; + for (;;) + { + int l; + if (!COM_ParseToken_Simple(&data, false, false)) + break; + if (com_token[0] == '{') + continue; + if (com_token[0] == '}') + break; + // skip leading whitespace + for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++); + for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++) + keyname[l] = com_token[k+l]; + keyname[l] = 0; + if (!COM_ParseToken_Simple(&data, false, false)) + break; + if (developer_extra.integer) + Con_DPrintf("key: %s %s\n", keyname, com_token); + if (!strcmp(keyname, "message")) + { + // get the message contents + strlcpy(message, com_token, sizeof(message)); + break; + } + } + } + } + if (entities) + Z_Free(entities); + if(f) + FS_Close(f); + *(t->filenames[i]+len[i]+5) = 0; + switch(p) + { + case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break; + case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break; + case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break; + case 30: strlcpy((char *)buf, "HL", sizeof(buf));break; + default: strlcpy((char *)buf, "??", sizeof(buf));break; + } + Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message); + } + Con_Print("\n"); + for(p=o;pfilenames[0]+5+p); + if(k == 0) + goto endcomplete; + for(i=1;inumfilenames;i++) + if(*(t->filenames[i]+5+p) != k) + goto endcomplete; + } +endcomplete: + if(p > o && completedname && completednamebufferlength > 0) + { + memset(completedname, 0, completednamebufferlength); + memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1)); + } + Z_Free(len); + FS_FreeSearch(t); + return p > o; +} + +/* + Con_DisplayList + + New function for tab-completion system + Added by EvilTypeGuy + MEGA Thanks to Taniwha + +*/ +void Con_DisplayList(const char **list) +{ + int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4); + const char **walk = list; + + while (*walk) { + len = (int)strlen(*walk); + if (len > maxlen) + maxlen = len; + walk++; + } + maxlen += 1; + + while (*list) { + len = (int)strlen(*list); + if (pos + maxlen >= width) { + Con_Print("\n"); + pos = 0; + } + + Con_Print(*list); + for (i = 0; i < (maxlen - len); i++) + Con_Print(" "); + + pos += maxlen; + list++; + } + + if (pos) + Con_Print("\n\n"); +} + +/* + SanitizeString strips color tags from the string in + and writes the result on string out +*/ +void SanitizeString(char *in, char *out) +{ + while(*in) + { + if(*in == STRING_COLOR_TAG) + { + ++in; + if(!*in) + { + out[0] = STRING_COLOR_TAG; + out[1] = 0; + return; + } + else if (*in >= '0' && *in <= '9') // ^[0-9] found + { + ++in; + if(!*in) + { + *out = 0; + return; + } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9] + continue; + } + else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found + { + if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) ) + { + in+=4; + if (!*in) + { + *out = 0; + return; + } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb + continue; + } + else in--; + } + else if (*in != STRING_COLOR_TAG) + --in; + } + *out = qfont_table[*(unsigned char*)in]; + ++in; + ++out; + } + *out = 0; +} + +// Now it becomes TRICKY :D --blub +static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that +static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches. +// means: when somebody uses a cvar's name as his name, we won't ever get his colors in there... +static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys +static int Nicks_matchpos; + +// co against <<:BLASTER:>> is true!? +int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len) +{ + while(a_len) + { + if(tolower(*a) == tolower(*b)) + { + if(*a == 0) + return 0; + --a_len; + ++a; + ++b; + continue; + } + if(!*a) + return -1; + if(!*b) + return 1; + if(*a == ' ') + return (*a < *b) ? -1 : 1; + if(*b == ' ') + ++b; + else + return (*a < *b) ? -1 : 1; + } + return 0; +} +int Nicks_strncasecmp(char *a, char *b, unsigned int a_len) +{ + char space_char; + if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)) + { + if(con_nickcompletion_flags.integer & NICKS_NO_SPACES) + return Nicks_strncasecmp_nospaces(a, b, a_len); + return strncasecmp(a, b, a_len); + } + + space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; + + // ignore non alphanumerics of B + // if A contains a non-alphanumeric, B must contain it as well though! + while(a_len) + { + qboolean alnum_a, alnum_b; + + if(tolower(*a) == tolower(*b)) + { + if(*a == 0) // end of both strings, they're equal + return 0; + --a_len; + ++a; + ++b; + continue; + } + // not equal, end of one string? + if(!*a) + return -1; + if(!*b) + return 1; + // ignore non alphanumerics + alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char); + alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char); + if(!alnum_a) // b must contain this + return (*a < *b) ? -1 : 1; + if(!alnum_b) + ++b; + // otherwise, both are alnum, they're just not equal, return the appropriate number + else + return (*a < *b) ? -1 : 1; + } + return 0; +} + + +/* Nicks_CompleteCountPossible + + Count the number of possible nicks to complete + */ +int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon) +{ + char name[128]; + int i, p; + int match; + int spos; + int count = 0; + + if(!con_nickcompletion.integer) + return 0; + + // changed that to 1 + if(!line[0])// || !line[1]) // we want at least... 2 written characters + return 0; + + for(i = 0; i < cl.maxclients; ++i) + { + p = i; + if(!cl.scores[p].name[0]) + continue; + + SanitizeString(cl.scores[p].name, name); + //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name); + + if(!name[0]) + continue; + + match = -1; + spos = pos - 1; // no need for a minimum of characters :) + + while(spos >= 0) + { + if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'') + { + if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start + !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start + { + --spos; + continue; + } + } + if(isCon && spos == 0) + break; + if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0) + match = spos; + --spos; + } + if(match < 0) + continue; + //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name); + strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count])); + + // the sanitized list + strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count])); + if(!count) + { + Nicks_matchpos = match; + } + + Nicks_offset[count] = s - (&line[match]); + //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]); + + ++count; + } + return count; +} + +void Cmd_CompleteNicksPrint(int count) +{ + int i; + for(i = 0; i < count; ++i) + Con_Printf("%s\n", Nicks_list[i]); +} + +void Nicks_CutMatchesNormal(int count) +{ + // cut match 0 down to the longest possible completion + int i; + unsigned int c, l; + c = strlen(Nicks_sanlist[0]) - 1; + for(i = 1; i < count; ++i) + { + l = strlen(Nicks_sanlist[i]) - 1; + if(l < c) + c = l; + + for(l = 0; l <= c; ++l) + if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l])) + { + c = l-1; + break; + } + } + Nicks_sanlist[0][c+1] = 0; + //Con_Printf("List0: %s\n", Nicks_sanlist[0]); +} + +unsigned int Nicks_strcleanlen(const char *s) +{ + unsigned int l = 0; + while(*s) + { + if( (*s >= 'a' && *s <= 'z') || + (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9') || + *s == ' ') + ++l; + ++s; + } + return l; +} + +void Nicks_CutMatchesAlphaNumeric(int count) +{ + // cut match 0 down to the longest possible completion + int i; + unsigned int c, l; + char tempstr[sizeof(Nicks_sanlist[0])]; + char *a, *b; + char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces + + c = strlen(Nicks_sanlist[0]); + for(i = 0, l = 0; i < (int)c; ++i) + { + if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') || + (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') || + (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED + { + tempstr[l++] = Nicks_sanlist[0][i]; + } + } + tempstr[l] = 0; + + for(i = 1; i < count; ++i) + { + a = tempstr; + b = Nicks_sanlist[i]; + while(1) + { + if(!*a) + break; + if(!*b) + { + *a = 0; + break; + } + if(tolower(*a) == tolower(*b)) + { + ++a; + ++b; + continue; + } + if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char) + { + // b is alnum, so cut + *a = 0; + break; + } + ++b; + } + } + // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit + Nicks_CutMatchesNormal(count); + //if(!Nicks_sanlist[0][0]) + if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr)) + { + // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there + strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr)); + } +} + +void Nicks_CutMatchesNoSpaces(int count) +{ + // cut match 0 down to the longest possible completion + int i; + unsigned int c, l; + char tempstr[sizeof(Nicks_sanlist[0])]; + char *a, *b; + + c = strlen(Nicks_sanlist[0]); + for(i = 0, l = 0; i < (int)c; ++i) + { + if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied + { + tempstr[l++] = Nicks_sanlist[0][i]; + } + } + tempstr[l] = 0; + + for(i = 1; i < count; ++i) + { + a = tempstr; + b = Nicks_sanlist[i]; + while(1) + { + if(!*a) + break; + if(!*b) + { + *a = 0; + break; + } + if(tolower(*a) == tolower(*b)) + { + ++a; + ++b; + continue; + } + if(*b != ' ') + { + *a = 0; + break; + } + ++b; + } + } + // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit + Nicks_CutMatchesNormal(count); + //if(!Nicks_sanlist[0][0]) + //Con_Printf("TS: %s\n", tempstr); + if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr)) + { + // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there + strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr)); + } +} + +void Nicks_CutMatches(int count) +{ + if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY) + Nicks_CutMatchesAlphaNumeric(count); + else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES) + Nicks_CutMatchesNoSpaces(count); + else + Nicks_CutMatchesNormal(count); +} + +const char **Nicks_CompleteBuildList(int count) +{ + const char **buf; + int bpos = 0; + // the list is freed by Con_CompleteCommandLine, so create a char** + buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *)); + + for(; bpos < count; ++bpos) + buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos]; + + Nicks_CutMatches(count); + + buf[bpos] = NULL; + return buf; +} + +/* + Nicks_AddLastColor + Restores the previous used color, after the autocompleted name. +*/ +int Nicks_AddLastColor(char *buffer, int pos) +{ + qboolean quote_added = false; + int match; + int color = STRING_COLOR_DEFAULT + '0'; + char r = 0, g = 0, b = 0; + + if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"') + { + // we'll have to add a quote :) + buffer[pos++] = '\"'; + quote_added = true; + } + + if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR) + { + // add color when no quote was added, or when flags &4? + // find last color + for(match = Nicks_matchpos-1; match >= 0; --match) + { + if(buffer[match] == STRING_COLOR_TAG) + { + if( isdigit(buffer[match+1]) ) + { + color = buffer[match+1]; + break; + } + else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR) + { + if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) ) + { + r = buffer[match+2]; + g = buffer[match+3]; + b = buffer[match+4]; + color = -1; + break; + } + } + } + } + if(!quote_added) + { + if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4 + pos -= 2; + else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR + && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) ) + pos -= 5; + } + buffer[pos++] = STRING_COLOR_TAG; + if (color == -1) + { + buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR; + buffer[pos++] = r; + buffer[pos++] = g; + buffer[pos++] = b; + } + else + buffer[pos++] = color; + } + return pos; +} + +int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos) +{ + int n; + /*if(!con_nickcompletion.integer) + return; is tested in Nicks_CompletionCountPossible */ + n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false); + if(n == 1) + { + size_t len; + char *msg; + + msg = Nicks_list[0]; + len = min(size - Nicks_matchpos - 3, strlen(msg)); + memcpy(&buffer[Nicks_matchpos], msg, len); + if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0 + len = Nicks_AddLastColor(buffer, Nicks_matchpos+len); + buffer[len++] = ' '; + buffer[len] = 0; + return len; + } else if(n > 1) + { + int len; + char *msg; + Con_Printf("\n%i possible nicks:\n", n); + Cmd_CompleteNicksPrint(n); + + Nicks_CutMatches(n); + + msg = Nicks_sanlist[0]; + len = min(size - Nicks_matchpos, strlen(msg)); + memcpy(&buffer[Nicks_matchpos], msg, len); + buffer[Nicks_matchpos + len] = 0; + //pos += len; + return Nicks_matchpos + len; + } + return pos; +} + + +/* + Con_CompleteCommandLine + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + Enhanced to tab-complete map names by [515] + +*/ +void Con_CompleteCommandLine (void) +{ + const char *cmd = ""; + char *s; + const char **list[4] = {0, 0, 0, 0}; + char s2[512]; + char command[512]; + int c, v, a, i, cmd_len, pos, k; + int n; // nicks --blub + const char *space, *patterns; + + //find what we want to complete + pos = key_linepos; + while(--pos) + { + k = key_line[pos]; + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + } + pos++; + + s = key_line + pos; + strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor + key_line[key_linepos] = 0; //hide them + + space = strchr(key_line + 1, ' '); + if(space && pos == (space - key_line) + 1) + { + strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line))); + + patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this? + if(patterns && !*patterns) + patterns = NULL; // get rid of the empty string + + if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map"))) + { + //maps search + char t[MAX_QPATH]; + if (GetMapList(s, t, sizeof(t))) + { + // first move the cursor + key_linepos += (int)strlen(t) - (int)strlen(s); + + // and now do the actual work + *s = 0; + strlcat(key_line, t, MAX_INPUTLINE); + strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor + + // and fix the cursor + if(key_linepos > (int) strlen(key_line)) + key_linepos = (int) strlen(key_line); + } + return; + } + else + { + if(patterns) + { + char t[MAX_QPATH]; + stringlist_t resultbuf, dirbuf; + + // Usage: + // // store completion patterns (space separated) for command foo in con_completion_foo + // set con_completion_foo "foodata/*.foodefault *.foo" + // foo + // + // Note: patterns with slash are always treated as absolute + // patterns; patterns without slash search in the innermost + // directory the user specified. There is no way to "complete into" + // a directory as of now, as directories seem to be unknown to the + // FS subsystem. + // + // Examples: + // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm" + // set con_completion_playdemo "*.dem" + // set con_completion_play "*.wav *.ogg" + // + // TODO somehow add support for directories; these shall complete + // to their name + an appended slash. + + stringlistinit(&resultbuf); + stringlistinit(&dirbuf); + while(COM_ParseToken_Simple(&patterns, false, false)) + { + fssearch_t *search; + if(strchr(com_token, '/')) + { + search = FS_Search(com_token, true, true); + } + else + { + const char *slash = strrchr(s, '/'); + if(slash) + { + strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash + strlcat(t, com_token, sizeof(t)); + search = FS_Search(t, true, true); + } + else + search = FS_Search(com_token, true, true); + } + if(search) + { + for(i = 0; i < search->numfilenames; ++i) + if(!strncmp(search->filenames[i], s, strlen(s))) + if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE) + stringlistappend(&resultbuf, search->filenames[i]); + FS_FreeSearch(search); + } + } + + // In any case, add directory names + { + fssearch_t *search; + const char *slash = strrchr(s, '/'); + if(slash) + { + strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash + strlcat(t, "*", sizeof(t)); + search = FS_Search(t, true, true); + } + else + search = FS_Search("*", true, true); + if(search) + { + for(i = 0; i < search->numfilenames; ++i) + if(!strncmp(search->filenames[i], s, strlen(s))) + if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY) + stringlistappend(&dirbuf, search->filenames[i]); + FS_FreeSearch(search); + } + } + + if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0) + { + const char *p, *q; + unsigned int matchchars; + if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1) + { + dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]); + } + else + if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0) + { + dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]); + } + else + { + stringlistsort(&resultbuf, true); // dirbuf is already sorted + Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings); + for(i = 0; i < dirbuf.numstrings; ++i) + { + Con_Printf("^4%s^7/\n", dirbuf.strings[i]); + } + for(i = 0; i < resultbuf.numstrings; ++i) + { + Con_Printf("%s\n", resultbuf.strings[i]); + } + matchchars = sizeof(t) - 1; + if(resultbuf.numstrings > 0) + { + p = resultbuf.strings[0]; + q = resultbuf.strings[resultbuf.numstrings - 1]; + for(; *p && *p == *q; ++p, ++q); + matchchars = (unsigned int)(p - resultbuf.strings[0]); + } + if(dirbuf.numstrings > 0) + { + p = dirbuf.strings[0]; + q = dirbuf.strings[dirbuf.numstrings - 1]; + for(; *p && *p == *q; ++p, ++q); + matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0])); + } + // now p points to the first non-equal character, or to the end + // of resultbuf.strings[0]. We want to append the characters + // from resultbuf.strings[0] to (not including) p as these are + // the unique prefix + strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t))); + } + + // first move the cursor + key_linepos += (int)strlen(t) - (int)strlen(s); + + // and now do the actual work + *s = 0; + strlcat(key_line, t, MAX_INPUTLINE); + strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor + + // and fix the cursor + if(key_linepos > (int) strlen(key_line)) + key_linepos = (int) strlen(key_line); + } + stringlistfreecontents(&resultbuf); + stringlistfreecontents(&dirbuf); + + return; // bail out, when we complete for a command that wants a file name + } + } + } + + // Count number of possible matches and print them + c = Cmd_CompleteCountPossible(s); + if (c) + { + Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":"); + Cmd_CompleteCommandPrint(s); + } + v = Cvar_CompleteCountPossible(s); + if (v) + { + Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":"); + Cvar_CompleteCvarPrint(s); + } + a = Cmd_CompleteAliasCountPossible(s); + if (a) + { + Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":"); + Cmd_CompleteAliasPrint(s); + } + n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true); + if (n) + { + Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":"); + Cmd_CompleteNicksPrint(n); + } + + if (!(c + v + a + n)) // No possible matches + { + if(s2[0]) + strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos); + return; + } + + if (c) + cmd = *(list[0] = Cmd_CompleteBuildList(s)); + if (v) + cmd = *(list[1] = Cvar_CompleteBuildList(s)); + if (a) + cmd = *(list[2] = Cmd_CompleteAliasBuildList(s)); + if (n) + cmd = *(list[3] = Nicks_CompleteBuildList(n)); + + for (cmd_len = (int)strlen(s);;cmd_len++) + { + const char **l; + for (i = 0; i < 3; i++) + if (list[i]) + for (l = list[i];*l;l++) + if ((*l)[cmd_len] != cmd[cmd_len]) + goto done; + // all possible matches share this character, so we continue... + if (!cmd[cmd_len]) + { + // if all matches ended at the same position, stop + // (this means there is only one match) + break; + } + } +done: + + // prevent a buffer overrun by limiting cmd_len according to remaining space + cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos); + if (cmd) + { + key_linepos = pos; + memcpy(&key_line[key_linepos], cmd, cmd_len); + key_linepos += cmd_len; + // if there is only one match, add a space after it + if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1) + { + if(n) + { // was a nick, might have an offset, and needs colors ;) --blub + key_linepos = pos - Nicks_offset[0]; + cmd_len = strlen(Nicks_list[0]); + cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos); + + memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len); + key_linepos += cmd_len; + if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0 + key_linepos = Nicks_AddLastColor(key_line, key_linepos); + } + key_line[key_linepos++] = ' '; + } + } + + // use strlcat to avoid a buffer overrun + key_line[key_linepos] = 0; + strlcat(key_line, s2, sizeof(key_line)); + + // free the command, cvar, and alias lists + for (i = 0; i < 4; i++) + if (list[i]) + Mem_Free((void *)list[i]); +} + diff --git a/misc/source/darkplaces-src/console.h b/misc/source/darkplaces-src/console.h new file mode 100644 index 00000000..8b2d7680 --- /dev/null +++ b/misc/source/darkplaces-src/console.h @@ -0,0 +1,149 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef CONSOLE_H +#define CONSOLE_H + +// +// console +// +extern int con_totallines; +extern int con_backscroll; +extern qboolean con_initialized; + +void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol); +void Con_Rcon_Redirect_End(void); +void Con_Rcon_Redirect_Abort(void); + +/// If the line width has changed, reformat the buffer. +void Con_CheckResize (void); +void Con_Init (void); +void Con_Init_Commands (void); +void Con_Shutdown (void); +void Con_DrawConsole (int lines); + +/// Prints to a chosen console target +void Con_MaskPrint(int mask, const char *msg); + +// Prints to a chosen console target +void Con_MaskPrintf(int mask, const char *fmt, ...) DP_FUNC_PRINTF(2); + +/// Prints to all appropriate console targets, and adds timestamps +void Con_Print(const char *txt); + +/// Prints to all appropriate console targets. +void Con_Printf(const char *fmt, ...) DP_FUNC_PRINTF(1); + +/// A Con_Print that only shows up if the "developer" cvar is set. +void Con_DPrint(const char *msg); + +/// A Con_Printf that only shows up if the "developer" cvar is set +void Con_DPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); +void Con_Clear_f (void); +void Con_DrawNotify (void); + +/// Clear all notify lines. +void Con_ClearNotify (void); +void Con_ToggleConsole_f (void); + +qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength); + +/// wrapper function to attempt to either complete the command line +/// or to list possible matches grouped by type +/// (i.e. will display possible variables, aliases, commands +/// that match what they've typed so far) +void Con_CompleteCommandLine(void); + +/// Generic libs/util/console.c function to display a list +/// formatted in columns on the console +void Con_DisplayList(const char **list); + + +/*! \name log + * @{ + */ +void Log_Init (void); +void Log_Close (void); +void Log_Start (void); +void Log_DestBuffer_Flush (void); ///< call this once per frame to send out replies to rcon streaming clients + +void Log_Printf(const char *logfilename, const char *fmt, ...) DP_FUNC_PRINTF(2); +//@} + +// CON_MASK_PRINT is the default (Con_Print/Con_Printf) +// CON_MASK_DEVELOPER is used by Con_DPrint/Con_DPrintf +#define CON_MASK_HIDENOTIFY 128 +#define CON_MASK_CHAT 1 +#define CON_MASK_INPUT 2 +#define CON_MASK_DEVELOPER 4 +#define CON_MASK_PRINT 8 + +typedef struct con_lineinfo_s +{ + char *start; + size_t len; + int mask; + + /// used only by console.c + double addtime; + int height; ///< recalculated line height when needed (-1 to unset) +} +con_lineinfo_t; + +typedef struct conbuffer_s +{ + qboolean active; + int textsize; + char *text; + int maxlines; + con_lineinfo_t *lines; + int lines_first; + int lines_count; ///< cyclic buffer +} +conbuffer_t; + +#define CONBUFFER_LINES(buf, i) (buf)->lines[((buf)->lines_first + (i)) % (buf)->maxlines] +#define CONBUFFER_LINES_COUNT(buf) ((buf)->lines_count) +#define CONBUFFER_LINES_LAST(buf) CONBUFFER_LINES(buf, CONBUFFER_LINES_COUNT(buf) - 1) + +void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool); +void ConBuffer_Clear (conbuffer_t *buf); +void ConBuffer_Shutdown(conbuffer_t *buf); + +/*! Notifies the console code about the current time + * (and shifts back times of other entries when the time + * went backwards) + */ +void ConBuffer_FixTimes(conbuffer_t *buf); + +/// Deletes the first line from the console history. +void ConBuffer_DeleteLine(conbuffer_t *buf); + +/// Deletes the last line from the console history. +void ConBuffer_DeleteLastLine(conbuffer_t *buf); + +/// Appends a given string as a new line to the console. +void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask); +int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start); +int ConBuffer_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start); +const char *ConBuffer_GetLine(conbuffer_t *buf, int i); + +#endif + diff --git a/misc/source/darkplaces-src/crypto-keygen-standalone-brute.sh b/misc/source/darkplaces-src/crypto-keygen-standalone-brute.sh new file mode 100644 index 00000000..a081b068 --- /dev/null +++ b/misc/source/darkplaces-src/crypto-keygen-standalone-brute.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +outfile=$1; shift +hosts=$1; shift + +on() +{ + case "$1" in + localhost) + shift + exec "$@" + ;; + *) + exec ssh "$@" + ;; + esac +} + +pids= +mainpid=$$ +trap 'kill $pids' EXIT +trap 'exit 1' INT USR1 + +n=0 +for h in $hosts; do + nn=`on "$h" cat /proc/cpuinfo | grep -c '^processor[ :]'` + n=$(($nn + $n)) +done + +rm -f bruteforce-* +i=0 +for h in $hosts; do + nn=`on "$h" cat /proc/cpuinfo | grep -c '^processor[ :]'` + ii=$(($nn + $i)) + while [ $i -lt $ii ]; do + i=$(($i+1)) + ( + on "$h" ./crypto-keygen-standalone -n $n -o /dev/stdout "$@" > bruteforce-$i & + pid=$! + trap 'kill $pid' TERM + wait + if [ -s "bruteforce-$i" ]; then + trap - TERM + mv "bruteforce-$i" "$outfile" + kill -USR1 $mainpid + else + rm -f "bruteforce-$i" + fi + ) & + pids="$pids $!" + done +done +wait diff --git a/misc/source/darkplaces-src/crypto-keygen-standalone.c b/misc/source/darkplaces-src/crypto-keygen-standalone.c new file mode 100644 index 00000000..d9206915 --- /dev/null +++ b/misc/source/darkplaces-src/crypto-keygen-standalone.c @@ -0,0 +1,773 @@ +#define _GNU_SOURCE + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// BEGIN stuff shared with crypto.c +#define FOURCC_D0PK (('d' << 0) | ('0' << 8) | ('p' << 16) | ('k' << 24)) +#define FOURCC_D0SK (('d' << 0) | ('0' << 8) | ('s' << 16) | ('k' << 24)) +#define FOURCC_D0PI (('d' << 0) | ('0' << 8) | ('p' << 16) | ('i' << 24)) +#define FOURCC_D0SI (('d' << 0) | ('0' << 8) | ('s' << 16) | ('i' << 24)) +#define FOURCC_D0IQ (('d' << 0) | ('0' << 8) | ('i' << 16) | ('q' << 24)) +#define FOURCC_D0IR (('d' << 0) | ('0' << 8) | ('i' << 16) | ('r' << 24)) +#define FOURCC_D0ER (('d' << 0) | ('0' << 8) | ('e' << 16) | ('r' << 24)) +#define FOURCC_D0IC (('d' << 0) | ('0' << 8) | ('i' << 16) | ('c' << 24)) + +static unsigned long Crypto_LittleLong(const char *data) +{ + return + ((unsigned char) data[0]) | + (((unsigned char) data[1]) << 8) | + (((unsigned char) data[2]) << 16) | + (((unsigned char) data[3]) << 24); +} + +static void Crypto_UnLittleLong(char *data, unsigned long l) +{ + data[0] = l & 0xFF; + data[1] = (l >> 8) & 0xFF; + data[2] = (l >> 16) & 0xFF; + data[3] = (l >> 24) & 0xFF; +} + +static size_t Crypto_ParsePack(const char *buf, size_t len, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + if(Crypto_LittleLong(buf) != header) + return 0; + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 > len) + return 0; + lumpsize[i] = Crypto_LittleLong(&buf[pos]); + pos += 4; + if(pos + lumpsize[i] > len) + return 0; + lumps[i] = &buf[pos]; + pos += lumpsize[i]; + } + return pos; +} + +static size_t Crypto_UnParsePack(char *buf, size_t len, unsigned long header, const char *const *lumps, const size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + Crypto_UnLittleLong(buf, header); + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 + lumpsize[i] > len) + return 0; + Crypto_UnLittleLong(&buf[pos], lumpsize[i]); + pos += 4; + memcpy(&buf[pos], lumps[i], lumpsize[i]); + pos += lumpsize[i]; + } + return pos; +} + +void file2buf(const char *fn, char **data, size_t *datasize) +{ + FILE *f; + *data = NULL; + *datasize = 0; + size_t n = 0, dn = 0; + if(!strncmp(fn, "/dev/fd/", 8)) + f = fdopen(atoi(fn + 8), "rb"); + else + f = fopen(fn, "rb"); + if(!f) + { + return; + } + for(;;) + { + *data = realloc(*data, *datasize += 8192); + if(!*data) + { + *datasize = 0; + fclose(f); + return; + } + dn = fread(*data + n, 1, *datasize - n, f); + if(!dn) + break; + n += dn; + } + fclose(f); + *datasize = n; +} + +int buf2file(const char *fn, const char *data, size_t n) +{ + FILE *f; + if(!strncmp(fn, "/dev/fd/", 8)) + f = fdopen(atoi(fn + 8), "wb"); + else + f = fopen(fn, "wb"); + if(!f) + return 0; + n = fwrite(data, n, 1, f); + if(fclose(f) || !n) + return 0; + return 1; +} + +void file2lumps(const char *fn, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) +{ + char *buf; + size_t n; + file2buf(fn, &buf, &n); + if(!buf) + { + fprintf(stderr, "could not open %s\n", fn); + exit(1); + } + if(!Crypto_ParsePack(buf, n, header, lumps, lumpsize, nlumps)) + { + fprintf(stderr, "could not parse %s as %c%c%c%c (%d lumps expected)\n", fn, (int) header & 0xFF, (int) (header >> 8) & 0xFF, (int) (header >> 16) & 0xFF, (int) (header >> 24) & 0xFF, (int) nlumps); + free(buf); + exit(1); + } + free(buf); +} + +mode_t umask_save; +void lumps2file(const char *fn, unsigned long header, const char *const *lumps, size_t *lumpsize, size_t nlumps, D0_BOOL private) +{ + char buf[65536]; + size_t n; + if(private) + umask(umask_save | 0077); + else + umask(umask_save); + if(!(n = Crypto_UnParsePack(buf, sizeof(buf), header, lumps, lumpsize, nlumps))) + { + fprintf(stderr, "could not unparse for %s\n", fn); + exit(1); + } + if(!buf2file(fn, buf, n)) + { + fprintf(stderr, "could not write %s\n", fn); + exit(1); + } +} + +void USAGE(const char *me) +{ + printf("Usage:\n" + "%s [-F] [-b bits] [-n progress-denominator] [-x prefix] [-X infix] [-C] -o private.d0sk\n" + "%s -P private.d0sk -o public.d0pk\n" + "%s [-n progress-denominator] [-x prefix] [-X infix] [-C] -p public.d0pk -o idkey-unsigned.d0si\n" + "%s -p public.d0pk -I idkey-unsigned.d0si -o request.d0iq -O camouflage.d0ic\n" + "%s -P private.d0sk -j request.d0iq -o response.d0ir\n" + "%s -p public.d0pk -I idkey-unsigned.d0si -c camouflage.d0ic -J response.d0ir -o idkey.d0si\n" + "%s -P private.d0sk -I idkey-unsigned.d0si -o idkey.d0si\n" + "%s -I idkey.d0si -o id.d0pi\n" + "%s -p public.d0pk\n" + "%s -P private.d0sk\n" + "%s -p public.d0pk -i id.d0pi\n" + "%s -p public.d0pk -I idkey.d0si\n" + "%s -0 -p public.d0pk -I idkey.d0si\n" + "%s -0 -p public.d0pk\n" + "%s -p public.d0pk -I idkey.d0si -d file-to-sign.dat -o file-signed.dat\n" + "%s -p public.d0pk -s file-signed.dat -o file-content.dat [-O id.d0pi]\n" + "%s -p public.d0pk -I idkey.d0si -d file-to-sign.dat -O signature.dat\n" + "%s -p public.d0pk -d file-to-sign.dat -s signature.dat [-O id.d0pi]\n", + me, me, me, me, me, me, me, me, me, me, me, me, me, me, me, me, me, me + ); +} + +unsigned int seconds; +unsigned int generated; +unsigned int ntasks = 1; +double generated_offset; +double guesscount; +double guessfactor; +void print_generated(int signo) +{ + (void) signo; + ++seconds; + if(generated >= 1000000000) + { + generated_offset += generated; + generated = 0; + } + fprintf(stderr, "Generated: %.0f (about %.0f, %.1f/s, about %.2f hours for %.0f)\n", + // nasty and dishonest hack: + // we are adjusting the values "back", so the total count is + // divided by guessfactor (as the check function is called + // guessfactor as often as it would be if no fastreject were + // done) + // so the values indicate the relative speed of fastreject vs + // normal! + (generated + generated_offset) / guessfactor, + (generated + generated_offset) * ntasks / guessfactor, + (generated + generated_offset) * ntasks / (guessfactor * seconds), + guesscount * ((guessfactor * seconds) / (generated + generated_offset) / ntasks) / 3600.0, + guesscount); + alarm(1); +} + +#define CHECK(x) if(!(x)) { fprintf(stderr, "error exit: error returned by %s\n", #x); exit(2); } + +const char *prefix = NULL, *infix = NULL; +size_t prefixlen = 0; +int ignorecase; +typedef D0_BOOL (*fingerprint_func) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +D0_BOOL fastreject(const d0_blind_id_t *ctx, void *pass) +{ + static char fp64[513]; size_t fp64size = 512; + CHECK(((fingerprint_func) pass)(ctx, fp64, &fp64size)); + ++generated; + if(ignorecase) + { + if(prefixlen) + if(strncasecmp(fp64, prefix, prefixlen)) + return 1; + if(infix) + { + fp64[fp64size] = 0; + if(!strcasestr(fp64, infix)) + return 1; + } + } + else + { + if(prefixlen) + if(memcmp(fp64, prefix, prefixlen)) + return 1; + if(infix) + { + fp64[fp64size] = 0; + if(!strstr(fp64, infix)) + return 1; + } + } + return 0; +} + +int main(int argc, char **argv) +{ + int opt; + size_t lumpsize[2]; + const char *lumps[2]; + char *databuf_in; size_t databufsize_in; + char *databuf_out; size_t databufsize_out; + char *databuf_sig; size_t databufsize_sig; + char lumps_w0[65536]; + char lumps_w1[65536]; + const char *pubkeyfile = NULL, *privkeyfile = NULL, *pubidfile = NULL, *prividfile = NULL, *idreqfile = NULL, *idresfile = NULL, *outfile = NULL, *outfile2 = NULL, *camouflagefile = NULL, *datafile = NULL, *sigfile = NULL; + char fp64[513]; size_t fp64size = 512; + int mask = 0; + int bits = 1024; + int i; + D0_BOOL do_fastreject = 1; + d0_blind_id_t *ctx; + if(!d0_blind_id_INITIALIZE()) + { + fprintf(stderr, "could not initialize\n"); + exit(1); + } + + umask_save = umask(0022); + + ctx = d0_blind_id_new(); + while((opt = getopt(argc, argv, "d:s:p:P:i:I:j:J:o:O:c:b:x:X:y:Fn:C0")) != -1) + { + switch(opt) + { + case 'C': + ignorecase = 1; + break; + case 'n': + ntasks = atoi(optarg); + break; + case 'b': + bits = atoi(optarg); + break; + case 'p': // d0pk = + pubkeyfile = optarg; + mask |= 1; + break; + case 'P': // d0sk = + privkeyfile = optarg; + mask |= 2; + break; + case 'i': // d0pi = + pubidfile = optarg; + mask |= 4; + break; + case 'I': // d0si = + prividfile = optarg; + mask |= 8; + break; + case 'j': // d0iq = + idreqfile = optarg; + mask |= 0x10; + break; + case 'J': // d0ir = + idresfile = optarg; + mask |= 0x20; + break; + case 'o': + outfile = optarg; + mask |= 0x40; + break; + case 'O': + outfile2 = optarg; + mask |= 0x80; + break; + case 'c': + camouflagefile = optarg; + mask |= 0x100; + break; + case 'x': + prefix = optarg; + prefixlen = strlen(prefix); + break; + case '0': + // test mode + mask |= 0x200; + break; + case 'd': + datafile = optarg; + mask |= 0x400; + break; + case 's': + sigfile = optarg; + mask |= 0x800; + break; + case 'X': + infix = optarg; + break; + case 'F': + do_fastreject = 0; + break; + default: + USAGE(*argv); + return 1; + } + } + + // fastreject is a slight slowdown when rejecting nothing at all + if(!infix && !prefixlen) + do_fastreject = 0; + + guesscount = pow(64.0, prefixlen); + if(infix) + guesscount /= (1 - pow(1 - pow(1/64.0, strlen(infix)), 44 - prefixlen - strlen(infix))); + // 44 chars; prefix is assumed to not match the infix (although it theoretically could) + // 43'th char however is always '=' and does not count + if(ignorecase) + { + if(infix) + for(i = 0; infix[i]; ++i) + if(toupper(infix[i]) != tolower(infix[i])) + guesscount /= 2; + for(i = 0; i < (int)prefixlen; ++i) + if(toupper(prefix[i]) != tolower(prefix[i])) + guesscount /= 2; + } + + if(do_fastreject) + { + // fastreject: reject function gets called about log(2^bits) times more often + guessfactor = bits * log(2) / 2; + // so guess function gets called guesscount * guessfactor times, and it tests as many valid keys as guesscount + } + + if(mask & 1) + { + file2lumps(pubkeyfile, FOURCC_D0PK, lumps, lumpsize, 2); + if(!d0_blind_id_read_public_key(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode public key\n"); + exit(1); + } + if(!d0_blind_id_read_private_id_modulus(ctx, lumps[1], lumpsize[1])) + { + fprintf(stderr, "could not decode modulus\n"); + exit(1); + } + } + else if(mask & 2) + { + file2lumps(privkeyfile, FOURCC_D0SK, lumps, lumpsize, 2); + if(!d0_blind_id_read_private_key(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode private key\n"); + exit(1); + } + if(!d0_blind_id_read_private_id_modulus(ctx, lumps[1], lumpsize[1])) + { + fprintf(stderr, "could not decode modulus\n"); + exit(1); + } + } + + if(mask & 4) + { + file2lumps(pubidfile, FOURCC_D0PI, lumps, lumpsize, 1); + if(!d0_blind_id_read_public_id(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode public ID\n"); + exit(1); + } + } + if(mask & 8) + { + file2lumps(prividfile, FOURCC_D0SI, lumps, lumpsize, 1); + if(!d0_blind_id_read_private_id(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode private ID\n"); + exit(1); + } + } + + if(mask & 0x10) + { + file2lumps(idreqfile, FOURCC_D0IQ, lumps, lumpsize, 1); + lumpsize[1] = sizeof(lumps_w1); + lumps[1] = lumps_w1; + if(!d0_blind_id_answer_private_id_request(ctx, lumps[0], lumpsize[0], lumps_w1, &lumpsize[1])) + { + fprintf(stderr, "could not answer private ID request\n"); + exit(1); + } + } + else if((mask & 0x120) == 0x120) + { + file2lumps(camouflagefile, FOURCC_D0IC, lumps, lumpsize, 1); + if(!d0_blind_id_read_private_id_request_camouflage(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not decode camouflage\n"); + exit(1); + } + + file2lumps(idresfile, FOURCC_D0IR, lumps, lumpsize, 1); + if(!d0_blind_id_finish_private_id_request(ctx, lumps[0], lumpsize[0])) + { + fprintf(stderr, "could not finish private ID request\n"); + exit(1); + } + } + + if(mask & 0x400) + { + file2buf(datafile, &databuf_in, &databufsize_in); + if(!databuf_in) + { + fprintf(stderr, "could not decode data\n"); + exit(1); + } + } + + if(mask & 0x800) + { + file2buf(sigfile, &databuf_sig, &databufsize_sig); + if(!databuf_sig) + { + fprintf(stderr, "could not decode signature\n"); + exit(1); + } + } + + switch(mask) + { + // modes of operation: + case 0x40: + // nothing -> private key file (incl modulus), print fingerprint + generated = 0; + generated_offset = 0; + seconds = 0; + signal(SIGALRM, print_generated); + alarm(1); + if(do_fastreject) + { + CHECK(d0_blind_id_generate_private_key_fastreject(ctx, bits, fastreject, d0_blind_id_fingerprint64_public_key)); + } + else + { + guessfactor = 1; // no fastreject here + do + { + CHECK(d0_blind_id_generate_private_key(ctx, bits)); + } + while(fastreject(ctx, d0_blind_id_fingerprint64_public_key)); + } + alarm(0); + signal(SIGALRM, NULL); + CHECK(d0_blind_id_generate_private_id_modulus(ctx)); + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + lumps[1] = lumps_w1; + lumpsize[1] = sizeof(lumps_w1); + CHECK(d0_blind_id_write_private_key(ctx, lumps_w0, &lumpsize[0])); + CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); + lumps2file(outfile, FOURCC_D0SK, lumps, lumpsize, 2, 1); + break; + case 0x42: + // private key file -> public key file (incl modulus) + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + lumps[1] = lumps_w1; + lumpsize[1] = sizeof(lumps_w1); + CHECK(d0_blind_id_write_public_key(ctx, lumps_w0, &lumpsize[0])); + CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); + lumps2file(outfile, FOURCC_D0PK, lumps, lumpsize, 2, 0); + break; + case 0x41: + // public key file -> unsigned private ID file + generated = 0; + generated_offset = 0; + seconds = 0; + signal(SIGALRM, print_generated); + alarm(1); + guessfactor = 1; // no fastreject here + do + { + CHECK(d0_blind_id_generate_private_id_start(ctx)); + } + while(fastreject(ctx, d0_blind_id_fingerprint64_public_id)); + alarm(0); + signal(SIGALRM, 0); + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_private_id(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); + break; + case 0xC9: + // public key file, unsigned private ID file -> ID request file and camouflage file + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_generate_private_id_request(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0IQ, lumps, lumpsize, 1, 0); + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_private_id_request_camouflage(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile2, FOURCC_D0IC, lumps, lumpsize, 1, 1); + break; + case 0x52: + // private key file, ID request file -> ID response file + lumps2file(outfile, FOURCC_D0IR, lumps+1, lumpsize+1, 1, 0); + break; + case 0x169: + // public key file, ID response file, private ID file -> signed private ID file + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_private_id(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); + break; + case 0x4A: + // private key file, private ID file -> signed private ID file + { + char buf[65536]; size_t bufsize; + char buf2[65536]; size_t buf2size; + D0_BOOL status; + d0_blind_id_t *ctx2 = d0_blind_id_new(); + CHECK(d0_blind_id_copy(ctx2, ctx)); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)); + buf2size = sizeof(buf2); + CHECK(d0_blind_id_authenticate_with_private_id_challenge(ctx2, 1, 1, buf, bufsize, buf2, &buf2size, &status)); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)); + buf2size = sizeof(buf2); + CHECK(d0_blind_id_authenticate_with_private_id_verify(ctx2, buf, bufsize, buf2, &buf2size, &status)); + CHECK(status == 0); + CHECK(d0_blind_id_authenticate_with_private_id_generate_missing_signature(ctx2)); + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_private_id(ctx2, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); + } + break; + case 0x48: + // private ID file -> public ID file + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + CHECK(d0_blind_id_write_public_id(ctx, lumps_w0, &lumpsize[0])); + lumps2file(outfile, FOURCC_D0PI, lumps, lumpsize, 1, 0); + break; + case 0x01: + case 0x02: + // public/private key file -> fingerprint + CHECK(d0_blind_id_fingerprint64_public_key(ctx, fp64, &fp64size)); + printf("%.*s\n", (int)fp64size, fp64); + break; + case 0x05: + case 0x09: + // public/private ID file -> fingerprint + CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); + printf("%.*s\n", (int)fp64size, fp64); + break; + case 0x449: + // public key, private ID, data -> signed data + databufsize_out = databufsize_in + 8192; + databuf_out = malloc(databufsize_out); + CHECK(d0_blind_id_sign_with_private_id_sign(ctx, 1, 0, databuf_in, databufsize_in, databuf_out, &databufsize_out)); + buf2file(outfile, databuf_out, databufsize_out); + break; + case 0x489: + // public key, private ID, data -> signature + databufsize_out = databufsize_in + 8192; + databuf_out = malloc(databufsize_out); + CHECK(d0_blind_id_sign_with_private_id_sign_detached(ctx, 1, 0, databuf_in, databufsize_in, databuf_out, &databufsize_out)); + buf2file(outfile2, databuf_out, databufsize_out); + break; + case 0x841: + case 0x8C1: + // public key, signed data -> data, optional public ID + { + D0_BOOL status; + databufsize_out = databufsize_sig; + databuf_out = malloc(databufsize_out); + CHECK(d0_blind_id_sign_with_private_id_verify(ctx, 1, 0, databuf_sig, databufsize_sig, databuf_out, &databufsize_out, &status)); + CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); + printf("%d\n", (int)status); + printf("%.*s\n", (int)fp64size, fp64); + buf2file(outfile, databuf_out, databufsize_out); + + if(outfile2) + { + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + lumps[1] = lumps_w1; + lumpsize[1] = sizeof(lumps_w1); + CHECK(d0_blind_id_write_public_key(ctx, lumps_w0, &lumpsize[0])); + CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); + lumps2file(outfile2, FOURCC_D0PK, lumps, lumpsize, 2, 0); + } + } + break; + case 0xC01: + case 0xC81: + // public key, signature, signed data -> optional public ID + { + D0_BOOL status; + CHECK(d0_blind_id_sign_with_private_id_verify_detached(ctx, 1, 0, databuf_sig, databufsize_sig, databuf_in, databufsize_in, &status)); + CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); + printf("%d\n", (int)status); + printf("%.*s\n", (int)fp64size, fp64); + + if(outfile2) + { + lumps[0] = lumps_w0; + lumpsize[0] = sizeof(lumps_w0); + lumps[1] = lumps_w1; + lumpsize[1] = sizeof(lumps_w1); + CHECK(d0_blind_id_write_public_key(ctx, lumps_w0, &lumpsize[0])); + CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); + lumps2file(outfile2, FOURCC_D0PK, lumps, lumpsize, 2, 0); + } + } + break; +/* + case 0x09: + // public key, private ID file -> test whether key is properly signed + { + char buf[65536]; size_t bufsize; + char buf2[65536]; size_t buf2size; + D0_BOOL status; + d0_blind_id_t *ctx2 = d0_blind_id_new(); + CHECK(d0_blind_id_copy(ctx2, ctx)); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)); + buf2size = sizeof(buf2); + CHECK(d0_blind_id_authenticate_with_private_id_challenge(ctx2, 1, 1, buf, bufsize, buf2, &buf2size, &status)); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)); + buf2size = sizeof(buf2); + CHECK(d0_blind_id_authenticate_with_private_id_verify(ctx2, buf, bufsize, buf2, &buf2size, &status)); + if(status) + printf("OK\n"); + else + printf("EPIC FAIL\n"); + } + break; +*/ + case 0x209: + // protocol client + { + char hexbuf[131073]; + const char hex[] = "0123456789abcdef"; + char buf[65536]; size_t bufsize; + char buf2[65536]; size_t buf2size; + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)); + for(i = 0; i < (int)bufsize; ++i) + sprintf(&hexbuf[2*i], "%02x", (unsigned char)buf[i]); + printf("%s\n", hexbuf); + fgets(hexbuf, sizeof(hexbuf), stdin); + buf2size = strlen(hexbuf) / 2; + for(i = 0; i < (int)buf2size; ++i) + buf2[i] = ((strchr(hex, hexbuf[2*i]) - hex) << 4) | (strchr(hex, hexbuf[2*i+1]) - hex); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)); + for(i = 0; i < (int)bufsize; ++i) + sprintf(&hexbuf[2*i], "%02x", (unsigned char)buf[i]); + printf("%s\n", hexbuf); + } + break; + case 0x201: + // protocol server + { + char hexbuf[131073]; + const char hex[] = "0123456789abcdef"; + char buf[65536]; size_t bufsize; + char buf2[65536]; size_t buf2size; + D0_BOOL status; + fgets(hexbuf, sizeof(hexbuf), stdin); + buf2size = strlen(hexbuf) / 2; + for(i = 0; i < (int)buf2size; ++i) + buf2[i] = ((strchr(hex, hexbuf[2*i]) - hex) << 4) | (strchr(hex, hexbuf[2*i+1]) - hex); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_challenge(ctx, 1, 1, buf2, buf2size, buf, &bufsize, &status)); + for(i = 0; i < (int)bufsize; ++i) + sprintf(&hexbuf[2*i], "%02x", (unsigned char)buf[i]); + printf("%s\n", hexbuf); + fgets(hexbuf, sizeof(hexbuf), stdin); + buf2size = strlen(hexbuf) / 2; + for(i = 0; i < (int)buf2size; ++i) + buf2[i] = ((strchr(hex, hexbuf[2*i]) - hex) << 4) | (strchr(hex, hexbuf[2*i+1]) - hex); + bufsize = sizeof(buf); + CHECK(d0_blind_id_authenticate_with_private_id_verify(ctx, buf2, buf2size, buf, &bufsize, &status)); + printf("verify status: %d\n", status); + + CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); + printf("%.*s\n", (int)fp64size, fp64); + } + break; + default: + USAGE(*argv); + exit(1); + break; + } + d0_blind_id_SHUTDOWN(); + return 0; +} diff --git a/misc/source/darkplaces-src/crypto.c b/misc/source/darkplaces-src/crypto.c new file mode 100644 index 00000000..6a40df4c --- /dev/null +++ b/misc/source/darkplaces-src/crypto.c @@ -0,0 +1,2401 @@ +// TODO key loading, generating, saving +#include "quakedef.h" +#include "crypto.h" +#include "common.h" + +#include "hmac.h" +#include "libcurl.h" + +cvar_t crypto_developer = {CVAR_SAVE, "crypto_developer", "0", "print extra info about crypto handshake"}; +cvar_t crypto_servercpupercent = {CVAR_SAVE, "crypto_servercpupercent", "10", "allowed crypto CPU load in percent for server operation (0 = no limit, faster)"}; +cvar_t crypto_servercpumaxtime = {CVAR_SAVE, "crypto_servercpumaxtime", "0.01", "maximum allowed crypto CPU time per frame (0 = no limit)"}; +cvar_t crypto_servercpudebug = {CVAR_SAVE, "crypto_servercpudebug", "0", "print statistics about time usage by crypto"}; +static double crypto_servercpu_accumulator = 0; +static double crypto_servercpu_lastrealtime = 0; +cvar_t crypto_aeslevel = {CVAR_SAVE, "crypto_aeslevel", "1", "whether to support AES encryption in authenticated connections (0 = no, 1 = supported, 2 = requested, 3 = required)"}; +int crypto_keyfp_recommended_length; +static const char *crypto_idstring = NULL; +static char crypto_idstring_buf[512]; + +#define PROTOCOL_D0_BLIND_ID FOURCC_D0PK +#define PROTOCOL_VLEN (('v' << 0) | ('l' << 8) | ('e' << 16) | ('n' << 24)) + +// BEGIN stuff shared with crypto-keygen-standalone +#define FOURCC_D0PK (('d' << 0) | ('0' << 8) | ('p' << 16) | ('k' << 24)) +#define FOURCC_D0SK (('d' << 0) | ('0' << 8) | ('s' << 16) | ('k' << 24)) +#define FOURCC_D0PI (('d' << 0) | ('0' << 8) | ('p' << 16) | ('i' << 24)) +#define FOURCC_D0SI (('d' << 0) | ('0' << 8) | ('s' << 16) | ('i' << 24)) +#define FOURCC_D0IQ (('d' << 0) | ('0' << 8) | ('i' << 16) | ('q' << 24)) +#define FOURCC_D0IR (('d' << 0) | ('0' << 8) | ('i' << 16) | ('r' << 24)) +#define FOURCC_D0ER (('d' << 0) | ('0' << 8) | ('e' << 16) | ('r' << 24)) +#define FOURCC_D0IC (('d' << 0) | ('0' << 8) | ('i' << 16) | ('c' << 24)) + +static unsigned long Crypto_LittleLong(const char *data) +{ + return + ((unsigned char) data[0]) | + (((unsigned char) data[1]) << 8) | + (((unsigned char) data[2]) << 16) | + (((unsigned char) data[3]) << 24); +} + +static void Crypto_UnLittleLong(char *data, unsigned long l) +{ + data[0] = l & 0xFF; + data[1] = (l >> 8) & 0xFF; + data[2] = (l >> 16) & 0xFF; + data[3] = (l >> 24) & 0xFF; +} + +static size_t Crypto_ParsePack(const char *buf, size_t len, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + if(Crypto_LittleLong(buf) != header) + return 0; + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 > len) + return 0; + lumpsize[i] = Crypto_LittleLong(&buf[pos]); + pos += 4; + if(pos + lumpsize[i] > len) + return 0; + lumps[i] = &buf[pos]; + pos += lumpsize[i]; + } + return pos; +} + +static size_t Crypto_UnParsePack(char *buf, size_t len, unsigned long header, const char *const *lumps, const size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + Crypto_UnLittleLong(buf, header); + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 + lumpsize[i] > len) + return 0; + Crypto_UnLittleLong(&buf[pos], lumpsize[i]); + pos += 4; + memcpy(&buf[pos], lumps[i], lumpsize[i]); + pos += lumpsize[i]; + } + return pos; +} +// END stuff shared with xonotic-keygen + +#define USE_AES + +#ifdef CRYPTO_STATIC + +#include + +#define d0_blind_id_dll 1 +#define Crypto_OpenLibrary() true +#define Crypto_CloseLibrary() + +#define qd0_blind_id_new d0_blind_id_new +#define qd0_blind_id_free d0_blind_id_free +//#define qd0_blind_id_clear d0_blind_id_clear +#define qd0_blind_id_copy d0_blind_id_copy +//#define qd0_blind_id_generate_private_key d0_blind_id_generate_private_key +//#define qd0_blind_id_generate_private_key_fastreject d0_blind_id_generate_private_key_fastreject +//#define qd0_blind_id_read_private_key d0_blind_id_read_private_key +#define qd0_blind_id_read_public_key d0_blind_id_read_public_key +//#define qd0_blind_id_write_private_key d0_blind_id_write_private_key +//#define qd0_blind_id_write_public_key d0_blind_id_write_public_key +#define qd0_blind_id_fingerprint64_public_key d0_blind_id_fingerprint64_public_key +//#define qd0_blind_id_generate_private_id_modulus d0_blind_id_generate_private_id_modulus +#define qd0_blind_id_read_private_id_modulus d0_blind_id_read_private_id_modulus +//#define qd0_blind_id_write_private_id_modulus d0_blind_id_write_private_id_modulus +#define qd0_blind_id_generate_private_id_start d0_blind_id_generate_private_id_start +#define qd0_blind_id_generate_private_id_request d0_blind_id_generate_private_id_request +//#define qd0_blind_id_answer_private_id_request d0_blind_id_answer_private_id_request +#define qd0_blind_id_finish_private_id_request d0_blind_id_finish_private_id_request +//#define qd0_blind_id_read_private_id_request_camouflage d0_blind_id_read_private_id_request_camouflage +//#define qd0_blind_id_write_private_id_request_camouflage d0_blind_id_write_private_id_request_camouflage +#define qd0_blind_id_read_private_id d0_blind_id_read_private_id +//#define qd0_blind_id_read_public_id d0_blind_id_read_public_id +#define qd0_blind_id_write_private_id d0_blind_id_write_private_id +//#define qd0_blind_id_write_public_id d0_blind_id_write_public_id +#define qd0_blind_id_authenticate_with_private_id_start d0_blind_id_authenticate_with_private_id_start +#define qd0_blind_id_authenticate_with_private_id_challenge d0_blind_id_authenticate_with_private_id_challenge +#define qd0_blind_id_authenticate_with_private_id_response d0_blind_id_authenticate_with_private_id_response +#define qd0_blind_id_authenticate_with_private_id_verify d0_blind_id_authenticate_with_private_id_verify +#define qd0_blind_id_fingerprint64_public_id d0_blind_id_fingerprint64_public_id +#define qd0_blind_id_sessionkey_public_id d0_blind_id_sessionkey_public_id +#define qd0_blind_id_INITIALIZE d0_blind_id_INITIALIZE +#define qd0_blind_id_SHUTDOWN d0_blind_id_SHUTDOWN +#define qd0_blind_id_util_sha256 d0_blind_id_util_sha256 +#define qd0_blind_id_sign_with_private_id_sign d0_blind_id_sign_with_private_id_sign +#define qd0_blind_id_sign_with_private_id_sign_detached d0_blind_id_sign_with_private_id_sign_detached + +#else + +// d0_blind_id interface +#define D0_EXPORT +#ifdef __GNUC__ +#define D0_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define D0_WARN_UNUSED_RESULT +#endif +#define D0_BOOL int + +typedef struct d0_blind_id_s d0_blind_id_t; +typedef D0_BOOL (*d0_fastreject_function) (const d0_blind_id_t *ctx, void *pass); +static D0_EXPORT D0_WARN_UNUSED_RESULT d0_blind_id_t *(*qd0_blind_id_new) (void); +static D0_EXPORT void (*qd0_blind_id_free) (d0_blind_id_t *a); +//static D0_EXPORT void (*qd0_blind_id_clear) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_copy) (d0_blind_id_t *ctx, const d0_blind_id_t *src); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_key) (d0_blind_id_t *ctx, int k); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_key_fastreject) (d0_blind_id_t *ctx, int k, d0_fastreject_function reject, void *pass); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_key) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_public_key) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_public_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_fingerprint64_public_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_modulus) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id_modulus) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id_modulus) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_start) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_request) (d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_answer_private_id_request) (const d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_finish_private_id_request) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id_request_camouflage) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id_request_camouflage) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_public_id) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_start) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_challenge) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL recv_modulus, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen, D0_BOOL *status); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_response) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_verify) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *msg, size_t *msglen, D0_BOOL *status); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_fingerprint64_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sessionkey_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); // can only be done after successful key exchange, this performs a modpow; key length is limited by SHA_DIGESTSIZE for now; also ONLY valid after successful d0_blind_id_authenticate_with_private_id_verify/d0_blind_id_fingerprint64_public_id +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_INITIALIZE) (void); +static D0_EXPORT void (*qd0_blind_id_SHUTDOWN) (void); +static D0_EXPORT void (*qd0_blind_id_util_sha256) (char *out, const char *in, size_t n); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sign_with_private_id_sign) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sign_with_private_id_sign_detached) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); +static dllfunction_t d0_blind_id_funcs[] = +{ + {"d0_blind_id_new", (void **) &qd0_blind_id_new}, + {"d0_blind_id_free", (void **) &qd0_blind_id_free}, + //{"d0_blind_id_clear", (void **) &qd0_blind_id_clear}, + {"d0_blind_id_copy", (void **) &qd0_blind_id_copy}, + //{"d0_blind_id_generate_private_key", (void **) &qd0_blind_id_generate_private_key}, + //{"d0_blind_id_generate_private_key_fastreject", (void **) &qd0_blind_id_generate_private_key_fastreject}, + //{"d0_blind_id_read_private_key", (void **) &qd0_blind_id_read_private_key}, + {"d0_blind_id_read_public_key", (void **) &qd0_blind_id_read_public_key}, + //{"d0_blind_id_write_private_key", (void **) &qd0_blind_id_write_private_key}, + //{"d0_blind_id_write_public_key", (void **) &qd0_blind_id_write_public_key}, + {"d0_blind_id_fingerprint64_public_key", (void **) &qd0_blind_id_fingerprint64_public_key}, + //{"d0_blind_id_generate_private_id_modulus", (void **) &qd0_blind_id_generate_private_id_modulus}, + {"d0_blind_id_read_private_id_modulus", (void **) &qd0_blind_id_read_private_id_modulus}, + //{"d0_blind_id_write_private_id_modulus", (void **) &qd0_blind_id_write_private_id_modulus}, + {"d0_blind_id_generate_private_id_start", (void **) &qd0_blind_id_generate_private_id_start}, + {"d0_blind_id_generate_private_id_request", (void **) &qd0_blind_id_generate_private_id_request}, + //{"d0_blind_id_answer_private_id_request", (void **) &qd0_blind_id_answer_private_id_request}, + {"d0_blind_id_finish_private_id_request", (void **) &qd0_blind_id_finish_private_id_request}, + //{"d0_blind_id_read_private_id_request_camouflage", (void **) &qd0_blind_id_read_private_id_request_camouflage}, + //{"d0_blind_id_write_private_id_request_camouflage", (void **) &qd0_blind_id_write_private_id_request_camouflage}, + {"d0_blind_id_read_private_id", (void **) &qd0_blind_id_read_private_id}, + //{"d0_blind_id_read_public_id", (void **) &qd0_blind_id_read_public_id}, + {"d0_blind_id_write_private_id", (void **) &qd0_blind_id_write_private_id}, + //{"d0_blind_id_write_public_id", (void **) &qd0_blind_id_write_public_id}, + {"d0_blind_id_authenticate_with_private_id_start", (void **) &qd0_blind_id_authenticate_with_private_id_start}, + {"d0_blind_id_authenticate_with_private_id_challenge", (void **) &qd0_blind_id_authenticate_with_private_id_challenge}, + {"d0_blind_id_authenticate_with_private_id_response", (void **) &qd0_blind_id_authenticate_with_private_id_response}, + {"d0_blind_id_authenticate_with_private_id_verify", (void **) &qd0_blind_id_authenticate_with_private_id_verify}, + {"d0_blind_id_fingerprint64_public_id", (void **) &qd0_blind_id_fingerprint64_public_id}, + {"d0_blind_id_sessionkey_public_id", (void **) &qd0_blind_id_sessionkey_public_id}, + {"d0_blind_id_INITIALIZE", (void **) &qd0_blind_id_INITIALIZE}, + {"d0_blind_id_SHUTDOWN", (void **) &qd0_blind_id_SHUTDOWN}, + {"d0_blind_id_util_sha256", (void **) &qd0_blind_id_util_sha256}, + {"d0_blind_id_sign_with_private_id_sign", (void **) &qd0_blind_id_sign_with_private_id_sign}, + {"d0_blind_id_sign_with_private_id_sign_detached", (void **) &qd0_blind_id_sign_with_private_id_sign_detached}, + {NULL, NULL} +}; +// end of d0_blind_id interface + +static dllhandle_t d0_blind_id_dll = NULL; +static qboolean Crypto_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libd0_blind_id-0.dll", +#elif defined(MACOSX) + "libd0_blind_id.0.dylib", +#else + "libd0_blind_id.so.0", + "libd0_blind_id.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (d0_blind_id_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &d0_blind_id_dll, d0_blind_id_funcs); +} + +static void Crypto_CloseLibrary (void) +{ + Sys_UnloadLibrary (&d0_blind_id_dll); +} + +#endif + +#ifdef CRYPTO_RIJNDAEL_STATIC + +#include + +#define d0_rijndael_dll 1 +#define Crypto_Rijndael_OpenLibrary() true +#define Crypto_Rijndael_CloseLibrary() + +#define qd0_rijndael_setup_encrypt d0_rijndael_setup_encrypt +#define qd0_rijndael_setup_decrypt d0_rijndael_setup_decrypt +#define qd0_rijndael_encrypt d0_rijndael_encrypt +#define qd0_rijndael_decrypt d0_rijndael_decrypt + +#else + +// no need to do the #define dance here, as the upper part declares out macros either way + +D0_EXPORT int (*qd0_rijndael_setup_encrypt) (unsigned long *rk, const unsigned char *key, + int keybits); +D0_EXPORT int (*qd0_rijndael_setup_decrypt) (unsigned long *rk, const unsigned char *key, + int keybits); +D0_EXPORT void (*qd0_rijndael_encrypt) (const unsigned long *rk, int nrounds, + const unsigned char plaintext[16], unsigned char ciphertext[16]); +D0_EXPORT void (*qd0_rijndael_decrypt) (const unsigned long *rk, int nrounds, + const unsigned char ciphertext[16], unsigned char plaintext[16]); +#define D0_RIJNDAEL_KEYLENGTH(keybits) ((keybits)/8) +#define D0_RIJNDAEL_RKLENGTH(keybits) ((keybits)/8+28) +#define D0_RIJNDAEL_NROUNDS(keybits) ((keybits)/32+6) +static dllfunction_t d0_rijndael_funcs[] = +{ + {"d0_rijndael_setup_decrypt", (void **) &qd0_rijndael_setup_decrypt}, + {"d0_rijndael_setup_encrypt", (void **) &qd0_rijndael_setup_encrypt}, + {"d0_rijndael_decrypt", (void **) &qd0_rijndael_decrypt}, + {"d0_rijndael_encrypt", (void **) &qd0_rijndael_encrypt}, + {NULL, NULL} +}; +// end of d0_blind_id interface + +static dllhandle_t d0_rijndael_dll = NULL; +static qboolean Crypto_Rijndael_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libd0_rijndael-0.dll", +#elif defined(MACOSX) + "libd0_rijndael.0.dylib", +#else + "libd0_rijndael.so.0", + "libd0_rijndael.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (d0_rijndael_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &d0_rijndael_dll, d0_rijndael_funcs); +} + +static void Crypto_Rijndael_CloseLibrary (void) +{ + Sys_UnloadLibrary (&d0_rijndael_dll); +} + +#endif + +// various helpers +void sha256(unsigned char *out, const unsigned char *in, int n) +{ + qd0_blind_id_util_sha256((char *) out, (const char *) in, n); +} + +static size_t Crypto_LoadFile(const char *path, char *buf, size_t nmax) +{ + qfile_t *f = NULL; + fs_offset_t n; + if(*fs_userdir) + f = FS_SysOpen(va("%s%s", fs_userdir, path), "rb", false); + if(!f) + f = FS_SysOpen(va("%s%s", fs_basedir, path), "rb", false); + if(!f) + return 0; + n = FS_Read(f, buf, nmax); + if(n < 0) + n = 0; + FS_Close(f); + return (size_t) n; +} + +static qboolean PutWithNul(char **data, size_t *len, const char *str) +{ + // invariant: data points to insertion point + size_t l = strlen(str); + if(l >= *len) + return false; + memcpy(*data, str, l+1); + *data += l+1; + *len -= l+1; + return true; +} + +static const char *GetUntilNul(const char **data, size_t *len) +{ + // invariant: data points to next character to take + const char *data_save = *data; + size_t n; + const char *p; + + if(!*data) + return NULL; + + if(!*len) + { + *data = NULL; + return NULL; + } + + p = (const char *) memchr(*data, 0, *len); + if(!p) // no terminating NUL + { + *data = NULL; + *len = 0; + return NULL; + } + else + { + n = (p - *data) + 1; + *len -= n; + *data += n; + if(*len == 0) + *data = NULL; + return (const char *) data_save; + } + *data = NULL; + return NULL; +} + +// d0pk reading +static d0_blind_id_t *Crypto_ReadPublicKey(char *buf, size_t len) +{ + d0_blind_id_t *pk = NULL; + const char *p[2]; + size_t l[2]; + if(Crypto_ParsePack(buf, len, FOURCC_D0PK, p, l, 2)) + { + pk = qd0_blind_id_new(); + if(pk) + if(qd0_blind_id_read_public_key(pk, p[0], l[0])) + if(qd0_blind_id_read_private_id_modulus(pk, p[1], l[1])) + return pk; + } + if(pk) + qd0_blind_id_free(pk); + return NULL; +} + +// d0si reading +static qboolean Crypto_AddPrivateKey(d0_blind_id_t *pk, char *buf, size_t len) +{ + const char *p[1]; + size_t l[1]; + if(Crypto_ParsePack(buf, len, FOURCC_D0SI, p, l, 1)) + { + if(qd0_blind_id_read_private_id(pk, p[0], l[0])) + return true; + } + return false; +} + +#define MAX_PUBKEYS 16 +static d0_blind_id_t *pubkeys[MAX_PUBKEYS]; +static char pubkeys_fp64[MAX_PUBKEYS][FP64_SIZE+1]; +static qboolean pubkeys_havepriv[MAX_PUBKEYS]; +static char pubkeys_priv_fp64[MAX_PUBKEYS][FP64_SIZE+1]; +static char challenge_append[1400]; +static size_t challenge_append_length; + +static int keygen_i = -1; +static char keygen_buf[8192]; + +#define MAX_CRYPTOCONNECTS 16 +#define CRYPTOCONNECT_NONE 0 +#define CRYPTOCONNECT_PRECONNECT 1 +#define CRYPTOCONNECT_CONNECT 2 +#define CRYPTOCONNECT_RECONNECT 3 +#define CRYPTOCONNECT_DUPLICATE 4 +typedef struct server_cryptoconnect_s +{ + double lasttime; + lhnetaddress_t address; + crypto_t crypto; + int next_step; +} +server_cryptoconnect_t; +static server_cryptoconnect_t cryptoconnects[MAX_CRYPTOCONNECTS]; + +static int cdata_id = 0; +typedef struct +{ + d0_blind_id_t *id; + int s, c; + int next_step; + char challenge[2048]; + char wantserver_idfp[FP64_SIZE+1]; + qboolean wantserver_aes; + int cdata_id; +} +crypto_data_t; + +// crypto specific helpers +#define CDATA ((crypto_data_t *) crypto->data) +#define MAKE_CDATA if(!crypto->data) crypto->data = Z_Malloc(sizeof(crypto_data_t)) +#define CLEAR_CDATA if(crypto->data) { if(CDATA->id) qd0_blind_id_free(CDATA->id); Z_Free(crypto->data); } crypto->data = NULL + +static crypto_t *Crypto_ServerFindInstance(lhnetaddress_t *peeraddress, qboolean allow_create) +{ + crypto_t *crypto; + int i, best; + + if(!d0_blind_id_dll) + return NULL; // no support + + for(i = 0; i < MAX_CRYPTOCONNECTS; ++i) + if(LHNETADDRESS_Compare(peeraddress, &cryptoconnects[i].address)) + break; + if(i < MAX_CRYPTOCONNECTS && (allow_create || cryptoconnects[i].crypto.data)) + { + crypto = &cryptoconnects[i].crypto; + cryptoconnects[i].lasttime = realtime; + return crypto; + } + if(!allow_create) + return NULL; + best = 0; + for(i = 1; i < MAX_CRYPTOCONNECTS; ++i) + if(cryptoconnects[i].lasttime < cryptoconnects[best].lasttime) + best = i; + crypto = &cryptoconnects[best].crypto; + cryptoconnects[best].lasttime = realtime; + memcpy(&cryptoconnects[best].address, peeraddress, sizeof(cryptoconnects[best].address)); + CLEAR_CDATA; + return crypto; +} + +qboolean Crypto_ServerFinishInstance(crypto_t *out, crypto_t *crypto) +{ + // no check needed here (returned pointers are only used in prefilled fields) + if(!crypto || !crypto->authenticated) + { + Con_Printf("Passed an invalid crypto connect instance\n"); + memset(out, 0, sizeof(*out)); + return false; + } + CLEAR_CDATA; + memcpy(out, crypto, sizeof(*out)); + memset(crypto, 0, sizeof(crypto)); + return true; +} + +crypto_t *Crypto_ServerGetInstance(lhnetaddress_t *peeraddress) +{ + // no check needed here (returned pointers are only used in prefilled fields) + return Crypto_ServerFindInstance(peeraddress, false); +} + +typedef struct crypto_storedhostkey_s +{ + struct crypto_storedhostkey_s *next; + lhnetaddress_t addr; + int keyid; + char idfp[FP64_SIZE+1]; + int aeslevel; +} +crypto_storedhostkey_t; +static crypto_storedhostkey_t *crypto_storedhostkey_hashtable[CRYPTO_HOSTKEY_HASHSIZE]; + +static void Crypto_InitHostKeys(void) +{ + int i; + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + crypto_storedhostkey_hashtable[i] = NULL; +} + +static void Crypto_ClearHostKeys(void) +{ + int i; + crypto_storedhostkey_t *hk, *hkn; + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + { + for(hk = crypto_storedhostkey_hashtable[i]; hk; hk = hkn) + { + hkn = hk->next; + Z_Free(hk); + } + crypto_storedhostkey_hashtable[i] = NULL; + } +} + +static qboolean Crypto_ClearHostKey(lhnetaddress_t *peeraddress) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t **hkp; + qboolean found = false; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hkp = &crypto_storedhostkey_hashtable[hashindex]; *hkp && LHNETADDRESS_Compare(&((*hkp)->addr), peeraddress); hkp = &((*hkp)->next)); + + if(*hkp) + { + crypto_storedhostkey_t *hk = *hkp; + *hkp = hk->next; + Z_Free(hk); + found = true; + } + + return found; +} + +static void Crypto_StoreHostKey(lhnetaddress_t *peeraddress, const char *keystring, qboolean complain) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t *hk; + int keyid; + char idfp[FP64_SIZE+1]; + int aeslevel; + + if(!d0_blind_id_dll) + return; + + // syntax of keystring: + // aeslevel id@key id@key ... + + if(!*keystring) + return; + aeslevel = bound(0, *keystring - '0', 3); + while(*keystring && *keystring != ' ') + ++keystring; + + keyid = -1; + while(*keystring && keyid < 0) + { + // id@key + const char *idstart, *idend, *keystart, *keyend; + ++keystring; // skip the space + idstart = keystring; + while(*keystring && *keystring != ' ' && *keystring != '@') + ++keystring; + idend = keystring; + if(!*keystring) + break; + ++keystring; + keystart = keystring; + while(*keystring && *keystring != ' ') + ++keystring; + keyend = keystring; + + if(idend - idstart == FP64_SIZE && keyend - keystart == FP64_SIZE) + { + for(keyid = 0; keyid < MAX_PUBKEYS; ++keyid) + if(pubkeys[keyid]) + if(!memcmp(pubkeys_fp64[keyid], keystart, FP64_SIZE)) + { + memcpy(idfp, idstart, FP64_SIZE); + idfp[FP64_SIZE] = 0; + break; + } + if(keyid >= MAX_PUBKEYS) + keyid = -1; + } + } + + if(keyid < 0) + return; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hk = crypto_storedhostkey_hashtable[hashindex]; hk && LHNETADDRESS_Compare(&hk->addr, peeraddress); hk = hk->next); + + if(hk) + { + if(complain) + { + if(hk->keyid != keyid || memcmp(hk->idfp, idfp, FP64_SIZE+1)) + Con_Printf("Server %s tried to change the host key to a value not in the host cache. Connecting to it will fail. To accept the new host key, do crypto_hostkey_clear %s\n", buf, buf); + if(hk->aeslevel > aeslevel) + Con_Printf("Server %s tried to reduce encryption status, not accepted. Connecting to it will fail. To accept, do crypto_hostkey_clear %s\n", buf, buf); + } + hk->aeslevel = max(aeslevel, hk->aeslevel); + return; + } + + // great, we did NOT have it yet + hk = (crypto_storedhostkey_t *) Z_Malloc(sizeof(*hk)); + memcpy(&hk->addr, peeraddress, sizeof(hk->addr)); + hk->keyid = keyid; + memcpy(hk->idfp, idfp, FP64_SIZE+1); + hk->next = crypto_storedhostkey_hashtable[hashindex]; + hk->aeslevel = aeslevel; + crypto_storedhostkey_hashtable[hashindex] = hk; +} + +qboolean Crypto_RetrieveHostKey(lhnetaddress_t *peeraddress, int *keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, int *aeslevel) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t *hk; + + if(!d0_blind_id_dll) + return false; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hk = crypto_storedhostkey_hashtable[hashindex]; hk && LHNETADDRESS_Compare(&hk->addr, peeraddress); hk = hk->next); + + if(!hk) + return false; + + if(keyid) + *keyid = hk->keyid; + if(keyfp) + strlcpy(keyfp, pubkeys_fp64[hk->keyid], keyfplen); + if(idfp) + strlcpy(idfp, hk->idfp, idfplen); + if(aeslevel) + *aeslevel = hk->aeslevel; + + return true; +} +int Crypto_RetrieveLocalKey(int keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen) // return value: -1 if more to come, +1 if valid, 0 if end of list +{ + if(keyid < 0 || keyid >= MAX_PUBKEYS) + return 0; + if(keyfp) + *keyfp = 0; + if(idfp) + *idfp = 0; + if(!pubkeys[keyid]) + return -1; + if(keyfp) + strlcpy(keyfp, pubkeys_fp64[keyid], keyfplen); + if(idfp) + if(pubkeys_havepriv[keyid]) + strlcpy(idfp, pubkeys_priv_fp64[keyid], keyfplen); + return 1; +} +// end + +// init/shutdown code +static void Crypto_BuildChallengeAppend(void) +{ + char *p, *lengthptr, *startptr; + size_t n; + int i; + p = challenge_append; + n = sizeof(challenge_append); + Crypto_UnLittleLong(p, PROTOCOL_VLEN); + p += 4; + n -= 4; + lengthptr = p; + Crypto_UnLittleLong(p, 0); + p += 4; + n -= 4; + Crypto_UnLittleLong(p, PROTOCOL_D0_BLIND_ID); + p += 4; + n -= 4; + startptr = p; + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys_havepriv[i]) + PutWithNul(&p, &n, pubkeys_fp64[i]); + PutWithNul(&p, &n, ""); + for(i = 0; i < MAX_PUBKEYS; ++i) + if(!pubkeys_havepriv[i] && pubkeys[i]) + PutWithNul(&p, &n, pubkeys_fp64[i]); + Crypto_UnLittleLong(lengthptr, p - startptr); + challenge_append_length = p - challenge_append; +} + +static void Crypto_LoadKeys(void) +{ + char buf[8192]; + size_t len, len2; + int i; + + // load keys + // note: we are just a CLIENT + // so we load: + // PUBLIC KEYS to accept (including modulus) + // PRIVATE KEY of user + + crypto_idstring = NULL; + dpsnprintf(crypto_idstring_buf, sizeof(crypto_idstring_buf), "%d", d0_rijndael_dll ? crypto_aeslevel.integer : 0); + for(i = 0; i < MAX_PUBKEYS; ++i) + { + memset(pubkeys_fp64[i], 0, sizeof(pubkeys_fp64[i])); + memset(pubkeys_priv_fp64[i], 0, sizeof(pubkeys_fp64[i])); + pubkeys_havepriv[i] = false; + len = Crypto_LoadFile(va("key_%d.d0pk", i), buf, sizeof(buf)); + if((pubkeys[i] = Crypto_ReadPublicKey(buf, len))) + { + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_key(pubkeys[i], pubkeys_fp64[i], &len2)) // keeps final NUL + { + Con_Printf("Loaded public key key_%d.d0pk (fingerprint: %s)\n", i, pubkeys_fp64[i]); + len = Crypto_LoadFile(va("key_%d.d0si", i), buf, sizeof(buf)); + if(len) + { + if(Crypto_AddPrivateKey(pubkeys[i], buf, len)) + { + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_id(pubkeys[i], pubkeys_priv_fp64[i], &len2)) // keeps final NUL + { + Con_Printf("Loaded private ID key_%d.d0si for key_%d.d0pk (fingerprint: %s)\n", i, i, pubkeys_priv_fp64[i]); + pubkeys_havepriv[i] = true; + strlcat(crypto_idstring_buf, va(" %s@%s", pubkeys_priv_fp64[i], pubkeys_fp64[i]), sizeof(crypto_idstring_buf)); + } + else + { + // can't really happen + // but nothing leaked here + } + } + } + } + else + { + // can't really happen + qd0_blind_id_free(pubkeys[i]); + pubkeys[i] = NULL; + } + } + } + crypto_idstring = crypto_idstring_buf; + + keygen_i = -1; + Crypto_BuildChallengeAppend(); + + // find a good prefix length for all the keys we know (yes, algorithm is not perfect yet, may yield too long prefix length) + crypto_keyfp_recommended_length = 0; + memset(buf+256, 0, MAX_PUBKEYS + MAX_PUBKEYS); + while(crypto_keyfp_recommended_length < FP64_SIZE) + { + memset(buf, 0, 256); + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + ++buf[(unsigned char) pubkeys_fp64[i][crypto_keyfp_recommended_length]]; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + ++buf[(unsigned char) pubkeys_priv_fp64[i][crypto_keyfp_recommended_length]]; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + if(buf[(unsigned char) pubkeys_fp64[i][crypto_keyfp_recommended_length]] < 2) + buf[256 + i] = 1; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + if(buf[(unsigned char) pubkeys_priv_fp64[i][crypto_keyfp_recommended_length]] < 2) + buf[256 + MAX_PUBKEYS + i] = 1; + } + ++crypto_keyfp_recommended_length; + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + break; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + break; + } + if(i >= MAX_PUBKEYS) + break; + } + if(crypto_keyfp_recommended_length < 7) + crypto_keyfp_recommended_length = 7; +} + +static void Crypto_UnloadKeys(void) +{ + int i; + keygen_i = -1; + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + qd0_blind_id_free(pubkeys[i]); + pubkeys[i] = NULL; + pubkeys_havepriv[i] = false; + memset(pubkeys_fp64[i], 0, sizeof(pubkeys_fp64[i])); + memset(pubkeys_priv_fp64[i], 0, sizeof(pubkeys_fp64[i])); + challenge_append_length = 0; + } + crypto_idstring = NULL; +} + +void Crypto_Shutdown(void) +{ + crypto_t *crypto; + int i; + + Crypto_Rijndael_CloseLibrary(); + + if(d0_blind_id_dll) + { + // free memory + for(i = 0; i < MAX_CRYPTOCONNECTS; ++i) + { + crypto = &cryptoconnects[i].crypto; + CLEAR_CDATA; + } + memset(cryptoconnects, 0, sizeof(cryptoconnects)); + crypto = &cls.crypto; + CLEAR_CDATA; + + Crypto_UnloadKeys(); + + qd0_blind_id_SHUTDOWN(); + + Crypto_CloseLibrary(); + } +} + +void Crypto_Init(void) +{ + if(!Crypto_OpenLibrary()) + return; + + if(!qd0_blind_id_INITIALIZE()) + { + Crypto_Rijndael_CloseLibrary(); + Crypto_CloseLibrary(); + Con_Printf("libd0_blind_id initialization FAILED, cryptography support has been disabled\n"); + return; + } + + Crypto_Rijndael_OpenLibrary(); // if this fails, it's uncritical + + Crypto_InitHostKeys(); + Crypto_LoadKeys(); +} +// end + +// keygen code +static void Crypto_KeyGen_Finished(int code, size_t length_received, unsigned char *buffer, void *cbdata) +{ + const char *p[1]; + size_t l[1]; + static char buf[8192]; + static char buf2[8192]; + size_t bufsize, buf2size; + qfile_t *f = NULL; + d0_blind_id_t *ctx, *ctx2; + D0_BOOL status; + size_t len2; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + keygen_i = -1; + return; + } + + if(keygen_i >= MAX_PUBKEYS || !pubkeys[keygen_i]) + { + Con_Printf("overflow of keygen_i\n"); + keygen_i = -1; + return; + } + if(keygen_i < 0) + { + Con_Printf("Unexpected response from keygen server:\n"); + Com_HexDumpToConsole(buffer, length_received); + return; + } + if(!Crypto_ParsePack((const char *) buffer, length_received, FOURCC_D0IR, p, l, 1)) + { + if(length_received >= 5 && Crypto_LittleLong((const char *) buffer) == FOURCC_D0ER) + { + Con_Printf("Error response from keygen server: %.*s\n", (int)(length_received - 5), buffer + 5); + } + else + { + Con_Printf("Invalid response from keygen server:\n"); + Com_HexDumpToConsole(buffer, length_received); + } + keygen_i = -1; + return; + } + if(!qd0_blind_id_finish_private_id_request(pubkeys[keygen_i], p[0], l[0])) + { + Con_Printf("d0_blind_id_finish_private_id_request failed\n"); + keygen_i = -1; + return; + } + + // verify the key we just got (just in case) + ctx = qd0_blind_id_new(); + if(!ctx) + { + Con_Printf("d0_blind_id_new failed\n"); + keygen_i = -1; + return; + } + ctx2 = qd0_blind_id_new(); + if(!ctx2) + { + Con_Printf("d0_blind_id_new failed\n"); + qd0_blind_id_free(ctx); + keygen_i = -1; + return; + } + if(!qd0_blind_id_copy(ctx, pubkeys[keygen_i])) + { + Con_Printf("d0_blind_id_copy failed\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + if(!qd0_blind_id_copy(ctx2, pubkeys[keygen_i])) + { + Con_Printf("d0_blind_id_copy failed\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + bufsize = sizeof(buf); + if(!qd0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)) + { + Con_Printf("d0_blind_id_authenticate_with_private_id_start failed\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + buf2size = sizeof(buf2); + if(!qd0_blind_id_authenticate_with_private_id_challenge(ctx2, 1, 1, buf, bufsize, buf2, &buf2size, &status) || !status) + { + Con_Printf("d0_blind_id_authenticate_with_private_id_challenge failed (server does not have the requested private key)\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + bufsize = sizeof(buf); + if(!qd0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)) + { + Con_Printf("d0_blind_id_authenticate_with_private_id_response failed\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + buf2size = sizeof(buf2); + if(!qd0_blind_id_authenticate_with_private_id_verify(ctx2, buf, bufsize, buf2, &buf2size, &status) || !status) + { + Con_Printf("d0_blind_id_authenticate_with_private_id_verify failed (server does not have the requested private key)\n"); + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + keygen_i = -1; + return; + } + qd0_blind_id_free(ctx); + qd0_blind_id_free(ctx2); + + // we have a valid key now! + // make the rest of crypto.c know that + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_id(pubkeys[keygen_i], pubkeys_priv_fp64[keygen_i], &len2)) // keeps final NUL + { + Con_Printf("Received private ID key_%d.d0pk (fingerprint: %s)\n", keygen_i, pubkeys_priv_fp64[keygen_i]); + pubkeys_havepriv[keygen_i] = true; + strlcat(crypto_idstring_buf, va(" %s@%s", pubkeys_priv_fp64[keygen_i], pubkeys_fp64[keygen_i]), sizeof(crypto_idstring_buf)); + crypto_idstring = crypto_idstring_buf; + Crypto_BuildChallengeAppend(); + } + // write the key to disk + p[0] = buf; + l[0] = sizeof(buf); + if(!qd0_blind_id_write_private_id(pubkeys[keygen_i], buf, &l[0])) + { + Con_Printf("d0_blind_id_write_private_id failed\n"); + keygen_i = -1; + return; + } + if(!(buf2size = Crypto_UnParsePack(buf2, sizeof(buf2), FOURCC_D0SI, p, l, 1))) + { + Con_Printf("Crypto_UnParsePack failed\n"); + keygen_i = -1; + return; + } + + if(*fs_userdir) + { + FS_CreatePath(va("%skey_%d.d0si", fs_userdir, keygen_i)); + f = FS_SysOpen(va("%skey_%d.d0si", fs_userdir, keygen_i), "wb", false); + } + if(!f) + { + FS_CreatePath(va("%skey_%d.d0si", fs_basedir, keygen_i)); + f = FS_SysOpen(va("%skey_%d.d0si", fs_basedir, keygen_i), "wb", false); + } + if(!f) + { + Con_Printf("Cannot open key_%d.d0si\n", keygen_i); + keygen_i = -1; + return; + } + FS_Write(f, buf2, buf2size); + FS_Close(f); + + Con_Printf("Saved to key_%d.d0si\n", keygen_i); + keygen_i = -1; +} + +static void Crypto_KeyGen_f(void) +{ + int i; + const char *p[1]; + size_t l[1]; + static char buf[8192]; + static char buf2[8192]; + size_t buf2l, buf2pos; + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + if(Cmd_Argc() != 3) + { + Con_Printf("usage:\n%s id url\n", Cmd_Argv(0)); + return; + } + i = atoi(Cmd_Argv(1)); + if(!pubkeys[i]) + { + Con_Printf("there is no public key %d\n", i); + return; + } + if(pubkeys_havepriv[i]) + { + Con_Printf("there is already a private key for %d\n", i); + return; + } + if(keygen_i >= 0) + { + Con_Printf("there is already a keygen run on the way\n"); + return; + } + keygen_i = i; + if(!qd0_blind_id_generate_private_id_start(pubkeys[keygen_i])) + { + Con_Printf("d0_blind_id_start failed\n"); + keygen_i = -1; + return; + } + p[0] = buf; + l[0] = sizeof(buf); + if(!qd0_blind_id_generate_private_id_request(pubkeys[keygen_i], buf, &l[0])) + { + Con_Printf("d0_blind_id_generate_private_id_request failed\n"); + keygen_i = -1; + return; + } + buf2pos = strlen(Cmd_Argv(2)); + memcpy(buf2, Cmd_Argv(2), buf2pos); + if(!(buf2l = Crypto_UnParsePack(buf2 + buf2pos, sizeof(buf2) - buf2pos - 1, FOURCC_D0IQ, p, l, 1))) + { + Con_Printf("Crypto_UnParsePack failed\n"); + keygen_i = -1; + return; + } + if(!(buf2l = base64_encode((unsigned char *) (buf2 + buf2pos), buf2l, sizeof(buf2) - buf2pos - 1))) + { + Con_Printf("base64_encode failed\n"); + keygen_i = -1; + return; + } + buf2l += buf2pos; + buf[buf2l] = 0; + if(!Curl_Begin_ToMemory(buf2, 0, (unsigned char *) keygen_buf, sizeof(keygen_buf), Crypto_KeyGen_Finished, NULL)) + { + Con_Printf("curl failed\n"); + keygen_i = -1; + return; + } + Con_Printf("key generation in progress\n"); +} +// end + +// console commands +static void Crypto_Reload_f(void) +{ + Crypto_ClearHostKeys(); + Crypto_UnloadKeys(); + Crypto_LoadKeys(); +} + +static void Crypto_Keys_f(void) +{ + int i; + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + { + Con_Printf("%2d: public key key_%d.d0pk (fingerprint: %s)\n", i, i, pubkeys_fp64[i]); + if(pubkeys_havepriv[i]) + Con_Printf(" private ID key_%d.d0si (fingerprint: %s)\n", i, pubkeys_priv_fp64[i]); + } + } +} + +static void Crypto_HostKeys_f(void) +{ + int i; + crypto_storedhostkey_t *hk; + char buf[128]; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + { + for(hk = crypto_storedhostkey_hashtable[i]; hk; hk = hk->next) + { + LHNETADDRESS_ToString(&hk->addr, buf, sizeof(buf), 1); + Con_Printf("%d %s@%.*s %s\n", + hk->aeslevel, + hk->idfp, + crypto_keyfp_recommended_length, pubkeys_fp64[hk->keyid], + buf); + } + } +} + +static void Crypto_HostKey_Clear_f(void) +{ + lhnetaddress_t addr; + int i; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + + for(i = 1; i < Cmd_Argc(); ++i) + { + LHNETADDRESS_FromString(&addr, Cmd_Argv(i), 26000); + if(Crypto_ClearHostKey(&addr)) + { + Con_Printf("cleared host key for %s\n", Cmd_Argv(i)); + } + } +} + +void Crypto_Init_Commands(void) +{ + if(d0_blind_id_dll) + { + Cmd_AddCommand("crypto_reload", Crypto_Reload_f, "reloads cryptographic keys"); + Cmd_AddCommand("crypto_keygen", Crypto_KeyGen_f, "generates and saves a cryptographic key"); + Cmd_AddCommand("crypto_keys", Crypto_Keys_f, "lists the loaded keys"); + Cmd_AddCommand("crypto_hostkeys", Crypto_HostKeys_f, "lists the cached host keys"); + Cmd_AddCommand("crypto_hostkey_clear", Crypto_HostKey_Clear_f, "clears a cached host key"); + Cvar_RegisterVariable(&crypto_developer); + if(d0_rijndael_dll) + Cvar_RegisterVariable(&crypto_aeslevel); + else + crypto_aeslevel.integer = 0; // make sure + Cvar_RegisterVariable(&crypto_servercpupercent); + Cvar_RegisterVariable(&crypto_servercpumaxtime); + Cvar_RegisterVariable(&crypto_servercpudebug); + } +} +// end + +// AES encryption +static void aescpy(unsigned char *key, const unsigned char *iv, unsigned char *dst, const unsigned char *src, size_t len) +{ + const unsigned char *xorpos = iv; + unsigned char xorbuf[16]; + unsigned long rk[D0_RIJNDAEL_RKLENGTH(DHKEY_SIZE * 8)]; + size_t i; + qd0_rijndael_setup_encrypt(rk, key, DHKEY_SIZE * 8); + while(len > 16) + { + for(i = 0; i < 16; ++i) + xorbuf[i] = src[i] ^ xorpos[i]; + qd0_rijndael_encrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), xorbuf, dst); + xorpos = dst; + len -= 16; + src += 16; + dst += 16; + } + if(len > 0) + { + for(i = 0; i < len; ++i) + xorbuf[i] = src[i] ^ xorpos[i]; + for(; i < 16; ++i) + xorbuf[i] = xorpos[i]; + qd0_rijndael_encrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), xorbuf, dst); + } +} +static void seacpy(unsigned char *key, const unsigned char *iv, unsigned char *dst, const unsigned char *src, size_t len) +{ + const unsigned char *xorpos = iv; + unsigned char xorbuf[16]; + unsigned long rk[D0_RIJNDAEL_RKLENGTH(DHKEY_SIZE * 8)]; + size_t i; + qd0_rijndael_setup_decrypt(rk, key, DHKEY_SIZE * 8); + while(len > 16) + { + qd0_rijndael_decrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), src, xorbuf); + for(i = 0; i < 16; ++i) + dst[i] = xorbuf[i] ^ xorpos[i]; + xorpos = src; + len -= 16; + src += 16; + dst += 16; + } + if(len > 0) + { + qd0_rijndael_decrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), src, xorbuf); + for(i = 0; i < len; ++i) + dst[i] = xorbuf[i] ^ xorpos[i]; + } +} + +// NOTE: we MUST avoid the following begins of the packet: +// 1. 0xFF, 0xFF, 0xFF, 0xFF +// 2. 0x80, 0x00, length/256, length%256 +// this luckily does NOT affect AES mode, where the first byte always is in the range from 0x00 to 0x0F +const void *Crypto_EncryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len) +{ + unsigned char h[32]; + int i; + if(crypto->authenticated) + { + if(crypto->use_aes) + { + // AES packet = 1 byte length overhead, 15 bytes from HMAC-SHA-256, data, 0..15 bytes padding + // 15 bytes HMAC-SHA-256 (112bit) suffice as the attacker can't do more than forge a random-looking packet + // HMAC is needed to not leak information about packet content + if(developer_networking.integer) + { + Con_Print("To be encrypted:\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + } + if(len_src + 32 > len || !HMAC_SHA256_32BYTES(h, (const unsigned char *) data_src, len_src, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("Crypto_EncryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = ((len_src + 15) / 16) * 16 + 16; // add 16 for HMAC, then round to 16-size for AES + ((unsigned char *) data_dst)[0] = *len_dst - len_src; + memcpy(((unsigned char *) data_dst)+1, h, 15); + aescpy(crypto->dhkey, (const unsigned char *) data_dst, ((unsigned char *) data_dst) + 16, (const unsigned char *) data_src, len_src); + // IV dst src len + } + else + { + // HMAC packet = 16 bytes HMAC-SHA-256 (truncated to 128 bits), data + if(len_src + 16 > len || !HMAC_SHA256_32BYTES(h, (const unsigned char *) data_src, len_src, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("Crypto_EncryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src + 16; + memcpy(data_dst, h, 16); + memcpy(((unsigned char *) data_dst) + 16, (unsigned char *) data_src, len_src); + + // handle the "avoid" conditions: + i = BuffBigLong((unsigned char *) data_dst); + if( + (i == (int)0xFFFFFFFF) // avoid QW control packet + || + (i == (int)0x80000000 + (int)*len_dst) // avoid NQ control packet + ) + *(unsigned char *)data_dst ^= 0x80; // this will ALWAYS fix it + } + return data_dst; + } + else + { + *len_dst = len_src; + return data_src; + } +} + +const void *Crypto_DecryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len) +{ + unsigned char h[32]; + int i; + + // silently handle non-crypto packets + i = BuffBigLong((unsigned char *) data_src); + if( + (i == (int)0xFFFFFFFF) // avoid QW control packet + || + (i == (int)0x80000000 + (int)len_src) // avoid NQ control packet + ) + return NULL; + + if(crypto->authenticated) + { + if(crypto->use_aes) + { + if(len_src < 16 || ((len_src - 16) % 16)) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src - ((unsigned char *) data_src)[0]; + if(len < *len_dst || *len_dst > len_src - 16) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d->%d bytes out)\n", (int) len_src, (int) *len_dst, (int) len); + return NULL; + } + seacpy(crypto->dhkey, (unsigned char *) data_src, (unsigned char *) data_dst, ((const unsigned char *) data_src) + 16, *len_dst); + // IV dst src len + if(!HMAC_SHA256_32BYTES(h, (const unsigned char *) data_dst, *len_dst, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("HMAC fail\n"); + return NULL; + } + if(memcmp(((const unsigned char *) data_src)+1, h, 15)) // ignore first byte, used for length + { + Con_Printf("HMAC mismatch\n"); + return NULL; + } + if(developer_networking.integer) + { + Con_Print("Decrypted:\n"); + Com_HexDumpToConsole((const unsigned char *) data_dst, *len_dst); + } + return data_dst; // no need to copy + } + else + { + if(len_src < 16) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src - 16; + if(len < *len_dst) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d->%d bytes out)\n", (int) len_src, (int) *len_dst, (int) len); + return NULL; + } + //memcpy(data_dst, data_src + 16, *len_dst); + if(!HMAC_SHA256_32BYTES(h, ((const unsigned char *) data_src) + 16, *len_dst, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("HMAC fail\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + return NULL; + } + + if(memcmp((const unsigned char *) data_src, h, 16)) // ignore first byte, used for length + { + // undo the "avoid conditions" + if( + (i == (int)0x7FFFFFFF) // avoided QW control packet + || + (i == (int)0x00000000 + (int)len_src) // avoided NQ control packet + ) + { + // do the avoidance on the hash too + h[0] ^= 0x80; + if(memcmp((const unsigned char *) data_src, h, 16)) // ignore first byte, used for length + { + Con_Printf("HMAC mismatch\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + return NULL; + } + } + else + { + Con_Printf("HMAC mismatch\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + return NULL; + } + } + return ((const unsigned char *) data_src) + 16; // no need to copy, so data_dst is not used + } + } + else + { + *len_dst = len_src; + return data_src; + } +} +// end + +const char *Crypto_GetInfoResponseDataString(void) +{ + crypto_idstring_buf[0] = '0' + crypto_aeslevel.integer; + return crypto_idstring; +} + +// network protocol +qboolean Crypto_ServerAppendToChallenge(const char *data_in, size_t len_in, char *data_out, size_t *len_out, size_t maxlen_out) +{ + // cheap op, all is precomputed + if(!d0_blind_id_dll) + return false; // no support + // append challenge + if(maxlen_out <= *len_out + challenge_append_length) + return false; + memcpy(data_out + *len_out, challenge_append, challenge_append_length); + *len_out += challenge_append_length; + return false; +} + +static int Crypto_ServerError(char *data_out, size_t *len_out, const char *msg, const char *msg_client) +{ + if(!msg_client) + msg_client = msg; + Con_DPrintf("rejecting client: %s\n", msg); + if(*msg_client) + dpsnprintf(data_out, *len_out, "reject %s", msg_client); + *len_out = strlen(data_out); + return CRYPTO_DISCARD; +} + +static int Crypto_SoftServerError(char *data_out, size_t *len_out, const char *msg) +{ + *len_out = 0; + Con_DPrintf("%s\n", msg); + return CRYPTO_DISCARD; +} + +static int Crypto_ServerParsePacket_Internal(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + // if "connect": reject if in the middle of crypto handshake + crypto_t *crypto = NULL; + char *data_out_p = data_out; + const char *string = data_in; + int aeslevel; + D0_BOOL aes; + D0_BOOL status; + + if(!d0_blind_id_dll) + return CRYPTO_NOMATCH; // no support + + if (len_in > 8 && !memcmp(string, "connect\\", 8) && d0_rijndael_dll && crypto_aeslevel.integer >= 3) + { + const char *s; + int i; + // sorry, we have to verify the challenge here to not reflect network spam + + if (!(s = SearchInfostring(string + 4, "challenge"))) + return CRYPTO_NOMATCH; // will be later accepted if encryption was set up + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) // challenge mismatch is silent + return CRYPTO_DISCARD; // pre-challenge: rather be silent + + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto || !crypto->authenticated) + return Crypto_ServerError(data_out, len_out, "This server requires authentication and encryption to be supported by your client", NULL); + } + else if(len_in > 5 && !memcmp(string, "d0pk\\", 5) && ((LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP) || sv_public.integer > -3)) + { + const char *cnt, *s, *p; + int id; + int clientid = -1, serverid = -1; + cnt = SearchInfostring(string + 4, "id"); + id = (cnt ? atoi(cnt) : -1); + cnt = SearchInfostring(string + 4, "cnt"); + if(!cnt) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + GetUntilNul(&data_in, &len_in); + if(!data_in) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + if(!strcmp(cnt, "0")) + { + int i; + if (!(s = SearchInfostring(string + 4, "challenge"))) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) // challenge mismatch is silent + return CRYPTO_DISCARD; // pre-challenge: rather be silent + + if (!(s = SearchInfostring(string + 4, "aeslevel"))) + aeslevel = 0; // not supported + else + aeslevel = bound(0, atoi(s), 3); + switch(bound(0, d0_rijndael_dll ? crypto_aeslevel.integer : 0, 3)) + { + default: // dummy, never happens, but to make gcc happy... + case 0: + if(aeslevel >= 3) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL); + aes = false; + break; + case 1: + aes = (aeslevel >= 2); + break; + case 2: + aes = (aeslevel >= 1); + break; + case 3: + if(aeslevel <= 0) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL); + aes = true; + break; + } + + p = GetUntilNul(&data_in, &len_in); + if(p && *p) + { + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + if(pubkeys_havepriv[i]) + if(serverid < 0) + serverid = i; + } + if(serverid < 0) + return Crypto_ServerError(data_out, len_out, "Invalid server key", NULL); + } + p = GetUntilNul(&data_in, &len_in); + if(p && *p) + { + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + if(clientid < 0) + clientid = i; + } + if(clientid < 0) + return Crypto_ServerError(data_out, len_out, "Invalid client key", NULL); + } + + crypto = Crypto_ServerFindInstance(peeraddress, true); + if(!crypto) + return Crypto_ServerError(data_out, len_out, "Could not create a crypto connect instance", NULL); + MAKE_CDATA; + CDATA->cdata_id = id; + CDATA->s = serverid; + CDATA->c = clientid; + memset(crypto->dhkey, 0, sizeof(crypto->dhkey)); + CDATA->challenge[0] = 0; + crypto->client_keyfp[0] = 0; + crypto->client_idfp[0] = 0; + crypto->server_keyfp[0] = 0; + crypto->server_idfp[0] = 0; + crypto->use_aes = aes != 0; + + if(CDATA->s >= 0) + { + // I am the server, and my key is ok... so let's set server_keyfp and server_idfp + strlcpy(crypto->server_keyfp, pubkeys_fp64[CDATA->s], sizeof(crypto->server_keyfp)); + strlcpy(crypto->server_idfp, pubkeys_priv_fp64[CDATA->s], sizeof(crypto->server_idfp)); + + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_new failed", "Internal error"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->s])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\1\\id\\%d\\aes\\%d", CDATA->cdata_id, crypto->use_aes)); + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed", "Internal error"); + } + CDATA->next_step = 2; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(CDATA->c >= 0) + { + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_new failed", "Internal error"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\5\\id\\%d\\aes\\%d", CDATA->cdata_id, crypto->use_aes)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed", "Internal error"); + } + CDATA->next_step = 6; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "Missing client and server key", NULL); + } + } + else if(!strcmp(cnt, "2")) + { + size_t fpbuflen; + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 2) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\3\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_response(CDATA->id, data_in, len_in, data_out_p, len_out)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_response failed", "Internal error"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) crypto->dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed", "Internal error"); + } + if(CDATA->c >= 0) + { + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + CDATA->next_step = 4; + } + else + { + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + } + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "4")) + { + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 4) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\5\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed", "Internal error"); + } + CDATA->next_step = 6; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "6")) + { + static char msgbuf[32]; + size_t msgbuflen = sizeof(msgbuf); + size_t fpbuflen; + int i; + unsigned char dhkey[DHKEY_SIZE]; + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 6) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + if(!qd0_blind_id_authenticate_with_private_id_verify(CDATA->id, data_in, len_in, msgbuf, &msgbuflen, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_verify failed (authentication error)", "Authentication error"); + } + if(status) + strlcpy(crypto->client_keyfp, pubkeys_fp64[CDATA->c], sizeof(crypto->client_keyfp)); + else + crypto->client_keyfp[0] = 0; + memset(crypto->client_idfp, 0, sizeof(crypto->client_idfp)); + fpbuflen = FP64_SIZE; + if(!qd0_blind_id_fingerprint64_public_id(CDATA->id, crypto->client_idfp, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_fingerprint64_public_id failed", "Internal error"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed", "Internal error"); + } + // XOR the two DH keys together to make one + for(i = 0; i < DHKEY_SIZE; ++i) + crypto->dhkey[i] ^= dhkey[i]; + + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + // send a challenge-less challenge + PutWithNul(&data_out_p, len_out, "challenge "); + *len_out = data_out_p - data_out; + --*len_out; // remove NUL terminator + return CRYPTO_MATCH; + } + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + } + return CRYPTO_NOMATCH; +} + +int Crypto_ServerParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + int ret; + double t = 0; + static double complain_time = 0; + const char *cnt; + qboolean do_time = false; + qboolean do_reject = false; + if(crypto_servercpupercent.value > 0 || crypto_servercpumaxtime.value > 0) + if(len_in > 5 && !memcmp(data_in, "d0pk\\", 5)) + { + do_time = true; + cnt = SearchInfostring(data_in + 4, "cnt"); + if(cnt) + if(!strcmp(cnt, "0")) + do_reject = true; + } + if(do_time) + { + // check if we may perform crypto... + if(crypto_servercpupercent.value > 0) + { + crypto_servercpu_accumulator += (realtime - crypto_servercpu_lastrealtime) * crypto_servercpupercent.value * 0.01; + if(crypto_servercpumaxtime.value) + if(crypto_servercpu_accumulator > crypto_servercpumaxtime.value) + crypto_servercpu_accumulator = crypto_servercpumaxtime.value; + } + else + { + if(crypto_servercpumaxtime.value > 0) + if(realtime != crypto_servercpu_lastrealtime) + crypto_servercpu_accumulator = crypto_servercpumaxtime.value; + } + crypto_servercpu_lastrealtime = realtime; + if(do_reject && crypto_servercpu_accumulator < 0) + { + if(realtime > complain_time + 5) + Con_Printf("crypto: cannot perform requested crypto operations; denial service attack or crypto_servercpupercent/crypto_servercpumaxtime are too low\n"); + *len_out = 0; + return CRYPTO_DISCARD; + } + t = Sys_DoubleTime(); + } + ret = Crypto_ServerParsePacket_Internal(data_in, len_in, data_out, len_out, peeraddress); + if(do_time) + { + t = Sys_DoubleTime() - t; + if(crypto_servercpudebug.integer) + Con_Printf("crypto: accumulator was %.1f ms, used %.1f ms for crypto, ", crypto_servercpu_accumulator * 1000, t * 1000); + crypto_servercpu_accumulator -= t; + if(crypto_servercpudebug.integer) + Con_Printf("is %.1f ms\n", crypto_servercpu_accumulator * 1000); + } + return ret; +} + +static int Crypto_ClientError(char *data_out, size_t *len_out, const char *msg) +{ + dpsnprintf(data_out, *len_out, "reject %s", msg); + *len_out = strlen(data_out); + return CRYPTO_REPLACE; +} + +static int Crypto_SoftClientError(char *data_out, size_t *len_out, const char *msg) +{ + *len_out = 0; + Con_Printf("%s\n", msg); + return CRYPTO_DISCARD; +} + +int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + crypto_t *crypto = &cls.crypto; + const char *string = data_in; + const char *s; + D0_BOOL aes; + char *data_out_p = data_out; + D0_BOOL status; + + if(!d0_blind_id_dll) + return CRYPTO_NOMATCH; // no support + + // if "challenge": verify challenge, and discard message, send next crypto protocol message instead + // otherwise, just handle actual protocol messages + + if (len_in == 6 && !memcmp(string, "accept", 6) && cls.connect_trying && d0_rijndael_dll) + { + int wantserverid = -1; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL); + if(!crypto || !crypto->authenticated) + { + if(wantserverid >= 0) + return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); + if(crypto_aeslevel.integer >= 3) + return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); + } + return CRYPTO_NOMATCH; + } + else if (len_in >= 1 && string[0] == 'j' && cls.connect_trying && d0_rijndael_dll && crypto_aeslevel.integer >= 3) + { + int wantserverid = -1; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL); + if(!crypto || !crypto->authenticated) + { + if(wantserverid >= 0) + return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); + if(crypto_aeslevel.integer >= 3) + return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); + } + return CRYPTO_NOMATCH; + } + else if (len_in >= 13 && !memcmp(string, "infoResponse\x0A", 13)) + { + s = SearchInfostring(string + 13, "d0_blind_id"); + if(s) + Crypto_StoreHostKey(peeraddress, s, true); + return CRYPTO_NOMATCH; + } + else if (len_in >= 15 && !memcmp(string, "statusResponse\x0A", 15)) + { + char save = 0; + const char *p; + p = strchr(string + 15, '\n'); + if(p) + { + save = *p; + * (char *) p = 0; // cut off the string there + } + s = SearchInfostring(string + 15, "d0_blind_id"); + if(s) + Crypto_StoreHostKey(peeraddress, s, true); + if(p) + { + * (char *) p = save; + // invoking those nasal demons again (do not run this on the DS9k) + } + return CRYPTO_NOMATCH; + } + else if(len_in > 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) + { + const char *vlen_blind_id_ptr = NULL; + size_t len_blind_id_ptr = 0; + unsigned long k, v; + const char *challenge = data_in + 10; + const char *p; + int i; + int clientid = -1, serverid = -1, wantserverid = -1; + qboolean server_can_auth = true; + char wantserver_idfp[FP64_SIZE+1]; + int wantserver_aeslevel; + + // if we have a stored host key for the server, assume serverid to already be selected! + // (the loop will refuse to overwrite this one then) + wantserver_idfp[0] = 0; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, wantserver_idfp, sizeof(wantserver_idfp), &wantserver_aeslevel); + // requirement: wantserver_idfp is a full ID if wantserverid set + + // if we leave, we have to consider the connection + // unauthenticated; NOTE: this may be faked by a clever + // attacker to force an unauthenticated connection; so we have + // a safeguard check in place when encryption is required too + // in place, or when authentication is required by the server + crypto->authenticated = false; + + GetUntilNul(&data_in, &len_in); + if(!data_in) + return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present") : + (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + + // FTEQW extension protocol + while(len_in >= 8) + { + k = Crypto_LittleLong(data_in); + v = Crypto_LittleLong(data_in + 4); + data_in += 8; + len_in -= 8; + switch(k) + { + case PROTOCOL_VLEN: + if(len_in >= 4 + v) + { + k = Crypto_LittleLong(data_in); + data_in += 4; + len_in -= 4; + switch(k) + { + case PROTOCOL_D0_BLIND_ID: + vlen_blind_id_ptr = data_in; + len_blind_id_ptr = v; + break; + } + data_in += v; + len_in -= v; + } + break; + default: + break; + } + } + + if(!vlen_blind_id_ptr) + return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though authentication is required") : + (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + + data_in = vlen_blind_id_ptr; + len_in = len_blind_id_ptr; + + // parse fingerprints + // once we found a fingerprint we can auth to (ANY), select it as clientfp + // once we found a fingerprint in the first list that we know, select it as serverfp + + for(;;) + { + p = GetUntilNul(&data_in, &len_in); + if(!p) + break; + if(!*p) + { + if(!server_can_auth) + break; // other protocol message may follow + server_can_auth = false; + if(clientid >= 0) + break; + continue; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + { + if(pubkeys_havepriv[i]) + if(clientid < 0) + clientid = i; + if(server_can_auth) + if(serverid < 0) + if(wantserverid < 0 || i == wantserverid) + serverid = i; + } + } + if(clientid >= 0 && serverid >= 0) + break; + } + + // if stored host key is not found: + if(wantserverid >= 0 && serverid < 0) + return Crypto_ClientError(data_out, len_out, "Server CA does not match stored host key, refusing to connect"); + + if(serverid >= 0 || clientid >= 0) + { + // TODO at this point, fill clientside crypto struct! + MAKE_CDATA; + CDATA->cdata_id = ++cdata_id; + CDATA->s = serverid; + CDATA->c = clientid; + memset(crypto->dhkey, 0, sizeof(crypto->dhkey)); + strlcpy(CDATA->challenge, challenge, sizeof(CDATA->challenge)); + crypto->client_keyfp[0] = 0; + crypto->client_idfp[0] = 0; + crypto->server_keyfp[0] = 0; + crypto->server_idfp[0] = 0; + memcpy(CDATA->wantserver_idfp, wantserver_idfp, sizeof(crypto->server_idfp)); + + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + switch(bound(0, d0_rijndael_dll ? crypto_aeslevel.integer : 0, 3)) + { + default: // dummy, never happens, but to make gcc happy... + case 0: + if(wantserver_aeslevel >= 3) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL); + CDATA->wantserver_aes = false; + break; + case 1: + CDATA->wantserver_aes = (wantserver_aeslevel >= 2); + break; + case 2: + CDATA->wantserver_aes = (wantserver_aeslevel >= 1); + break; + case 3: + if(wantserver_aeslevel <= 0) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL); + CDATA->wantserver_aes = true; + break; + } + + // build outgoing message + // append regular stuff + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\0\\id\\%d\\aeslevel\\%d\\challenge\\%s", CDATA->cdata_id, d0_rijndael_dll ? crypto_aeslevel.integer : 0, challenge)); + PutWithNul(&data_out_p, len_out, serverid >= 0 ? pubkeys_fp64[serverid] : ""); + PutWithNul(&data_out_p, len_out, clientid >= 0 ? pubkeys_fp64[clientid] : ""); + + if(clientid >= 0) + { + // I am the client, and my key is ok... so let's set client_keyfp and client_idfp + strlcpy(crypto->client_keyfp, pubkeys_fp64[CDATA->c], sizeof(crypto->client_keyfp)); + strlcpy(crypto->client_idfp, pubkeys_priv_fp64[CDATA->c], sizeof(crypto->client_idfp)); + } + + if(serverid >= 0) + { + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_new failed"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->s])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + CDATA->next_step = 1; + *len_out = data_out_p - data_out; + } + else if(clientid >= 0) + { + // skip over server auth, perform client auth only + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_new failed"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed"); + } + CDATA->next_step = 5; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + } + else + *len_out = data_out_p - data_out; + + return CRYPTO_DISCARD; + } + else + { + if(wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(wantserver_aeslevel >= 3) + return Crypto_ClientError(data_out, len_out, "Server insists on encryption, but neither can authenticate to the other"); + return (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + } + } + else if(len_in > 5 && !memcmp(string, "d0pk\\", 5) && cls.connect_trying) + { + const char *cnt; + int id; + cnt = SearchInfostring(string + 4, "id"); + id = (cnt ? atoi(cnt) : -1); + cnt = SearchInfostring(string + 4, "cnt"); + if(!cnt) + return Crypto_ClientError(data_out, len_out, "d0pk\\ message without cnt"); + GetUntilNul(&data_in, &len_in); + if(!data_in) + return Crypto_ClientError(data_out, len_out, "d0pk\\ message without attachment"); + + if(!strcmp(cnt, "1")) + { + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 1) + return Crypto_SoftClientError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if((s = SearchInfostring(string + 4, "aes"))) + aes = atoi(s); + else + aes = false; + // we CANNOT toggle the AES status any more! + // as the server already decided + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(!aes && CDATA->wantserver_aes) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Stored host key requires encryption, but server did not enable encryption"); + } + if(aes && (!d0_rijndael_dll || crypto_aeslevel.integer <= 0)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on encryption too hard"); + } + if(!aes && (d0_rijndael_dll && crypto_aeslevel.integer >= 3)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on plaintext too hard"); + } + crypto->use_aes = aes != 0; + + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\2\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed"); + } + CDATA->next_step = 3; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "3")) + { + static char msgbuf[32]; + size_t msgbuflen = sizeof(msgbuf); + size_t fpbuflen; + + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 3) + return Crypto_SoftClientError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if(!qd0_blind_id_authenticate_with_private_id_verify(CDATA->id, data_in, len_in, msgbuf, &msgbuflen, &status)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_verify failed (server authentication error)"); + } + if(status) + strlcpy(crypto->server_keyfp, pubkeys_fp64[CDATA->s], sizeof(crypto->server_keyfp)); + else + crypto->server_keyfp[0] = 0; + memset(crypto->server_idfp, 0, sizeof(crypto->server_idfp)); + fpbuflen = FP64_SIZE; + if(!qd0_blind_id_fingerprint64_public_id(CDATA->id, crypto->server_idfp, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_fingerprint64_public_id failed"); + } + if(CDATA->wantserver_idfp[0]) + if(memcmp(CDATA->wantserver_idfp, crypto->server_idfp, sizeof(crypto->server_idfp))) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server ID does not match stored host key, refusing to connect"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) crypto->dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed"); + } + + // cache the server key + Crypto_StoreHostKey(&cls.connect_address, va("%d %s@%s", crypto->use_aes ? 1 : 0, crypto->server_idfp, pubkeys_fp64[CDATA->s]), false); + + if(CDATA->c >= 0) + { + // client will auth next + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\4\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed"); + } + CDATA->next_step = 5; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else + { + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + // assume we got the empty challenge to finish the protocol + PutWithNul(&data_out_p, len_out, "challenge "); + *len_out = data_out_p - data_out; + --*len_out; // remove NUL terminator + return CRYPTO_REPLACE; + } + } + else if(!strcmp(cnt, "5")) + { + size_t fpbuflen; + unsigned char dhkey[DHKEY_SIZE]; + int i; + + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va("Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 5) + return Crypto_SoftClientError(data_out, len_out, va("Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if(CDATA->s < 0) // only if server didn't auth + { + if((s = SearchInfostring(string + 4, "aes"))) + aes = atoi(s); + else + aes = false; + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(!aes && CDATA->wantserver_aes) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Stored host key requires encryption, but server did not enable encryption"); + } + if(aes && (!d0_rijndael_dll || crypto_aeslevel.integer <= 0)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on encryption too hard"); + } + if(!aes && (d0_rijndael_dll && crypto_aeslevel.integer >= 3)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on plaintext too hard"); + } + crypto->use_aes = aes != 0; + } + + PutWithNul(&data_out_p, len_out, va("d0pk\\cnt\\6\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_response(CDATA->id, data_in, len_in, data_out_p, len_out)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_response failed"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed"); + } + // XOR the two DH keys together to make one + for(i = 0; i < DHKEY_SIZE; ++i) + crypto->dhkey[i] ^= dhkey[i]; + // session key is FINISHED! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + return Crypto_SoftClientError(data_out, len_out, "Got unknown d0_blind_id message from server"); + } + + return CRYPTO_NOMATCH; +} + +size_t Crypto_SignData(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size) +{ + if(keyid < 0 || keyid >= MAX_PUBKEYS) + return 0; + if(!pubkeys_havepriv[keyid]) + return 0; + if(qd0_blind_id_sign_with_private_id_sign(pubkeys[keyid], true, false, (const char *)data, datasize, (char *)signed_data, &signed_size)) + return signed_size; + return 0; +} + +size_t Crypto_SignDataDetached(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size) +{ + if(keyid < 0 || keyid >= MAX_PUBKEYS) + return 0; + if(!pubkeys_havepriv[keyid]) + return 0; + if(qd0_blind_id_sign_with_private_id_sign_detached(pubkeys[keyid], true, false, (const char *)data, datasize, (char *)signed_data, &signed_size)) + return signed_size; + return 0; +} diff --git a/misc/source/darkplaces-src/crypto.h b/misc/source/darkplaces-src/crypto.h new file mode 100644 index 00000000..507c0a06 --- /dev/null +++ b/misc/source/darkplaces-src/crypto.h @@ -0,0 +1,155 @@ +#ifndef CRYPTO_H +#define CRYPTO_H + +extern cvar_t crypto_developer; +extern cvar_t crypto_aeslevel; +#define ENCRYPTION_REQUIRED (crypto_aeslevel.integer >= 3) + +extern int crypto_keyfp_recommended_length; // applies to LOCAL IDs, and to ALL keys + +#define CRYPTO_HEADERSIZE 31 +// AES case causes 16 to 31 bytes overhead +// SHA256 case causes 16 bytes overhead as we truncate to 128bit + +#include "lhnet.h" + +#define FP64_SIZE 44 +#define DHKEY_SIZE 16 + +typedef struct +{ + unsigned char dhkey[DHKEY_SIZE]; // shared key, not NUL terminated + char client_idfp[FP64_SIZE+1]; + char client_keyfp[FP64_SIZE+1]; // NULL if signature fail + char server_idfp[FP64_SIZE+1]; + char server_keyfp[FP64_SIZE+1]; // NULL if signature fail + qboolean authenticated; + qboolean use_aes; + void *data; +} +crypto_t; + +void Crypto_Init(void); +void Crypto_Init_Commands(void); +void Crypto_Shutdown(void); +const void *Crypto_EncryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len); +const void *Crypto_DecryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len); +#define CRYPTO_NOMATCH 0 // process as usual (packet was not used) +#define CRYPTO_MATCH 1 // process as usual (packet was used) +#define CRYPTO_DISCARD 2 // discard this packet +#define CRYPTO_REPLACE 3 // make the buffer the current packet +int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress); +int Crypto_ServerParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress); + +// if len_out is nonzero, the packet is to be sent to the client + +qboolean Crypto_ServerAppendToChallenge(const char *data_in, size_t len_in, char *data_out, size_t *len_out, size_t maxlen); +crypto_t *Crypto_ServerGetInstance(lhnetaddress_t *peeraddress); +qboolean Crypto_ServerFinishInstance(crypto_t *out, crypto_t *in); // also clears allocated memory +const char *Crypto_GetInfoResponseDataString(void); + +// retrieves a host key for an address (can be exposed to menuqc, or used by the engine to look up stored keys e.g. for server bookmarking) +// pointers may be NULL +qboolean Crypto_RetrieveHostKey(lhnetaddress_t *peeraddress, int *keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, int *aeslevel); +int Crypto_RetrieveLocalKey(int keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen); // return value: -1 if more to come, +1 if valid, 0 if end of list + +size_t Crypto_SignData(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size); +size_t Crypto_SignDataDetached(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size); + +// netconn protocol: +// non-crypto: +// getchallenge > +// < challenge +// connect > +// < accept (or: reject) +// crypto: +// getchallenge > +// < challenge SP NUL vlen d0pk NUL NUL +// +// IF serverfp: +// d0pk\cnt\0\challenge\\aeslevel\ NUL NUL +// > +// check if client would get accepted; if not, do "reject" now +// require non-control packets to be encrypted require non-control packets to be encrypted +// do not send anything yet do not send anything yet +// RESET to serverfp RESET to serverfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// < d0pk\cnt\1\aes\ NUL *startdata* +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// d0pk\cnt\2 NUL *challengedata* > +// d0_blind_id_authenticate_with_private_id_response() = 0 +// < d0pk\cnt\3 NUL *responsedata* +// d0_blind_id_authenticate_with_private_id_verify() = 1 +// store server's fingerprint NOW +// d0_blind_id_sessionkey_public_id() = 1 d0_blind_id_sessionkey_public_id() = 1 +// +// IF clientfp AND NOT serverfp: +// RESET to clientfp RESET to clientfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// d0pk\cnt\0\challenge\\aeslevel\ NUL NUL NUL *startdata* +// > +// check if client would get accepted; if not, do "reject" now +// require non-control packets to be encrypted require non-control packets to be encrypted +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// < d0pk\cnt\5\aes\ NUL *challengedata* +// +// IF clientfp AND serverfp: +// RESET to clientfp RESET to clientfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// d0pk\cnt\4 NUL *startdata* > +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// < d0pk\cnt\5 NUL *challengedata* +// +// IF clientfp: +// d0_blind_id_authenticate_with_private_id_response() = 0 +// d0pk\cnt\6 NUL *responsedata* > +// d0_blind_id_authenticate_with_private_id_verify() = 1 +// store client's fingerprint NOW +// d0_blind_id_sessionkey_public_id() = 1 d0_blind_id_sessionkey_public_id() = 1 +// note: the ... is the "connect" message, except without the challenge. Reinterpret as regular connect message on server side +// +// enforce encrypted transmission (key is XOR of the two DH keys) +// +// IF clientfp: +// < challenge (mere sync message) +// +// connect\... > +// < accept (ALWAYS accept if connection is encrypted, ignore challenge as it had been checked before) +// +// commence with ingame protocol + +// in short: +// server: +// getchallenge NUL d0_blind_id: reply with challenge with added fingerprints +// cnt=0: IF server will auth, cnt=1, ELSE cnt=5 +// cnt=2: cnt=3 +// cnt=4: cnt=5 +// cnt=6: send "challenge" +// client: +// challenge with added fingerprints: cnt=0; if client will auth but not server, append client auth start +// cnt=1: cnt=2 +// cnt=3: IF client will auth, cnt=4, ELSE rewrite as "challenge" +// cnt=5: cnt=6, server will continue by sending "challenge" (let's avoid sending two packets as response to one) +// other change: +// accept empty "challenge", and challenge-less connect in case crypto protocol has executed and finished +// statusResponse and infoResponse get an added d0_blind_id key that lists +// the keys the server can auth with and to in key@ca SPACE key@ca notation +// any d0pk\ message has an appended "id" parameter; messages with an unexpected "id" are ignored to prevent errors from multiple concurrent auth runs + + +// comparison to OTR: +// - encryption: yes +// - authentication: yes +// - deniability: no (attacker requires the temporary session key to prove you +// have sent a specific message, the private key itself does not suffice), no +// measures are taken to provide forgeability to even provide deniability +// against an attacker who knows the temporary session key, as using CTR mode +// for the encryption - which, together with deriving the MAC key from the +// encryption key, and MACing the ciphertexts instead of the plaintexts, +// would provide forgeability and thus deniability - requires longer +// encrypted packets and deniability was not a goal of this, as we may e.g. +// reserve the right to capture packet dumps + extra state info to prove a +// client/server has sent specific packets to prove cheating) +// - perfect forward secrecy: yes (session key is derived via DH key exchange) + +#endif diff --git a/misc/source/darkplaces-src/csprogs.c b/misc/source/darkplaces-src/csprogs.c new file mode 100644 index 00000000..55bf6bf1 --- /dev/null +++ b/misc/source/darkplaces-src/csprogs.c @@ -0,0 +1,1198 @@ +#include "quakedef.h" +#include "progsvm.h" +#include "clprogdefs.h" +#include "csprogs.h" +#include "cl_collision.h" +#include "snd_main.h" +#include "clvm_cmds.h" +#include "prvm_cmds.h" + +//============================================================================ +// Client prog handling +//[515]: omg !!! optimize it ! a lot of hacks here and there also :P + +#define CSQC_RETURNVAL prog->globals.generic[OFS_RETURN] +#define CSQC_BEGIN csqc_tmpprog=prog;prog=0;PRVM_SetProg(PRVM_CLIENTPROG); +#define CSQC_END prog=csqc_tmpprog; + +static prvm_prog_t *csqc_tmpprog; + +void CL_VM_PreventInformationLeaks(void) +{ + if(!cl.csqc_loaded) + return; + CSQC_BEGIN + VM_ClearTraceGlobals(); + PRVM_clientglobalfloat(trace_networkentity) = 0; + CSQC_END +} + +//[515]: these are required funcs +static const char *cl_required_func[] = +{ + "CSQC_Init", + "CSQC_InputEvent", + "CSQC_UpdateView", + "CSQC_ConsoleCommand", +}; + +static int cl_numrequiredfunc = sizeof(cl_required_func) / sizeof(char*); + +#define CL_REQFIELDS (sizeof(cl_reqfields) / sizeof(prvm_required_field_t)) + +prvm_required_field_t cl_reqfields[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) {ev_float, #x }, +#define PRVM_DECLARE_clientfieldvector(x) {ev_vector, #x }, +#define PRVM_DECLARE_clientfieldstring(x) {ev_string, #x }, +#define PRVM_DECLARE_clientfieldedict(x) {ev_entity, #x }, +#define PRVM_DECLARE_clientfieldfunction(x) {ev_function, #x }, +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +#define CL_REQGLOBALS (sizeof(cl_reqglobals) / sizeof(prvm_required_field_t)) + +prvm_required_field_t cl_reqglobals[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_clientglobalvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_clientglobalstring(x) {ev_string, #x}, +#define PRVM_DECLARE_clientglobaledict(x) {ev_entity, #x}, +#define PRVM_DECLARE_clientglobalfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +void CL_VM_Error (const char *format, ...) DP_FUNC_PRINTF(1); +void CL_VM_Error (const char *format, ...) //[515]: hope it will be never executed =) +{ + char errorstring[4096]; + va_list argptr; + + va_start (argptr, format); + dpvsnprintf (errorstring, sizeof(errorstring), format, argptr); + va_end (argptr); +// Con_Printf( "CL_VM_Error: %s\n", errorstring ); + + PRVM_Crash(); + cl.csqc_loaded = false; + + Cvar_SetValueQuick(&csqc_progcrc, -1); + Cvar_SetValueQuick(&csqc_progsize, -1); + +// Host_AbortCurrentFrame(); //[515]: hmmm... if server says it needs csqc then client MUST disconnect + Host_Error("CL_VM_Error: %s", errorstring); +} +void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin) +{ + if(cl.csqc_loaded) + { + CSQC_BEGIN + PRVM_clientglobalfloat(dmg_take) = dmg_take; + PRVM_clientglobalfloat(dmg_save) = dmg_save; + VectorCopy(dmg_origin, PRVM_clientglobalvector(dmg_origin)); + CSQC_END + } +} + +void CSQC_UpdateNetworkTimes(double newtime, double oldtime) +{ + if(!cl.csqc_loaded) + return; + CSQC_BEGIN + PRVM_clientglobalfloat(servertime) = newtime; + PRVM_clientglobalfloat(serverprevtime) = oldtime; + PRVM_clientglobalfloat(serverdeltatime) = newtime - oldtime; + CSQC_END +} + +//[515]: set globals before calling R_UpdateView, WEIRD CRAP +void CSQC_R_RecalcView (void); +static void CSQC_SetGlobals (void) +{ + CSQC_BEGIN + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobalfloat(frametime) = max(0, cl.time - cl.oldtime); + PRVM_clientglobalfloat(servercommandframe) = cls.servermovesequence; + PRVM_clientglobalfloat(clientcommandframe) = cl.movecmd[0].sequence; + VectorCopy(cl.viewangles, PRVM_clientglobalvector(input_angles)); + // // FIXME: this actually belongs into getinputstate().. [12/17/2007 Black] + PRVM_clientglobalfloat(input_buttons) = cl.movecmd[0].buttons; + VectorSet(PRVM_clientglobalvector(input_movevalues), cl.movecmd[0].forwardmove, cl.movecmd[0].sidemove, cl.movecmd[0].upmove); + VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin); + VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles); + + // LordHavoc: Spike says not to do this, but without pmove_org the + // CSQC is useless as it can't alter the view origin without + // completely replacing it + Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, PRVM_clientglobalvector(pmove_org)); + VectorCopy(cl.movement_velocity, PRVM_clientglobalvector(pmove_vel)); + PRVM_clientglobalfloat(pmove_onground) = cl.onground; + PRVM_clientglobalfloat(pmove_inwater) = cl.inwater; + + VectorCopy(cl.viewangles, PRVM_clientglobalvector(view_angles)); + VectorCopy(cl.punchangle, PRVM_clientglobalvector(view_punchangle)); + VectorCopy(cl.punchvector, PRVM_clientglobalvector(view_punchvector)); + PRVM_clientglobalfloat(maxclients) = cl.maxclients; + + CSQC_R_RecalcView(); + CSQC_END +} + +void CSQC_Predraw (prvm_edict_t *ed) +{ + int b; + if(!PRVM_clientedictfunction(ed, predraw)) + return; + b = PRVM_clientglobaledict(self); + PRVM_clientglobaledict(self) = PRVM_EDICT_TO_PROG(ed); + PRVM_ExecuteProgram(PRVM_clientedictfunction(ed, predraw), "CSQC_Predraw: NULL function\n"); + PRVM_clientglobaledict(self) = b; +} + +void CSQC_Think (prvm_edict_t *ed) +{ + int b; + if(PRVM_clientedictfunction(ed, think)) + if(PRVM_clientedictfloat(ed, nextthink) && PRVM_clientedictfloat(ed, nextthink) <= PRVM_clientglobalfloat(time)) + { + PRVM_clientedictfloat(ed, nextthink) = 0; + b = PRVM_clientglobaledict(self); + PRVM_clientglobaledict(self) = PRVM_EDICT_TO_PROG(ed); + PRVM_ExecuteProgram(PRVM_clientedictfunction(ed, think), "CSQC_Think: NULL function\n"); + PRVM_clientglobaledict(self) = b; + } +} + +extern cvar_t cl_noplayershadow; +extern cvar_t r_equalize_entities_fullbright; +qboolean CSQC_AddRenderEdict(prvm_edict_t *ed, int edictnum) +{ + int renderflags; + int c; + float scale; + entity_render_t *entrender; + dp_model_t *model; + + model = CL_GetModelFromEdict(ed); + if (!model) + return false; + + if (edictnum) + { + if (r_refdef.scene.numentities >= r_refdef.scene.maxentities) + return false; + entrender = cl.csqcrenderentities + edictnum; + r_refdef.scene.entities[r_refdef.scene.numentities++] = entrender; + entrender->entitynumber = edictnum + MAX_EDICTS; + //entrender->shadertime = 0; // shadertime was set by spawn() + entrender->flags = 0; + entrender->alpha = 1; + entrender->scale = 1; + VectorSet(entrender->colormod, 1, 1, 1); + VectorSet(entrender->glowmod, 1, 1, 1); + entrender->allowdecals = true; + } + else + { + entrender = CL_NewTempEntity(0); + if (!entrender) + return false; + } + + entrender->userwavefunc_param[0] = PRVM_clientedictfloat(ed, userwavefunc_param0); + entrender->userwavefunc_param[1] = PRVM_clientedictfloat(ed, userwavefunc_param1); + entrender->userwavefunc_param[2] = PRVM_clientedictfloat(ed, userwavefunc_param2); + entrender->userwavefunc_param[3] = PRVM_clientedictfloat(ed, userwavefunc_param3); + + entrender->model = model; + entrender->skinnum = (int)PRVM_clientedictfloat(ed, skin); + entrender->effects |= entrender->model->effects; + renderflags = (int)PRVM_clientedictfloat(ed, renderflags); + entrender->alpha = PRVM_clientedictfloat(ed, alpha); + entrender->scale = scale = PRVM_clientedictfloat(ed, scale); + VectorCopy(PRVM_clientedictvector(ed, colormod), entrender->colormod); + VectorCopy(PRVM_clientedictvector(ed, glowmod), entrender->glowmod); + if(PRVM_clientedictfloat(ed, effects)) entrender->effects |= (int)PRVM_clientedictfloat(ed, effects); + if (!entrender->alpha) + entrender->alpha = 1.0f; + if (!entrender->scale) + entrender->scale = scale = 1.0f; + if (!VectorLength2(entrender->colormod)) + VectorSet(entrender->colormod, 1, 1, 1); + if (!VectorLength2(entrender->glowmod)) + VectorSet(entrender->glowmod, 1, 1, 1); + + // LordHavoc: use the CL_GetTagMatrix function on self to ensure consistent behavior (duplicate code would be bad) + CL_GetTagMatrix(&entrender->matrix, ed, 0); + + // set up the animation data + VM_GenerateFrameGroupBlend(ed->priv.server->framegroupblend, ed); + VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(ed, model, ed->priv.server->frameblend); + if (PRVM_clientedictfloat(ed, shadertime)) // hack for csprogs.dat files that do not set shadertime, leaves the value at entity spawn time + entrender->shadertime = PRVM_clientedictfloat(ed, shadertime); + + // transparent offset + if (renderflags & RF_USETRANSPARENTOFFSET) + entrender->transparent_offset = PRVM_clientglobalfloat(transparent_offset); + + if(renderflags) + { + if(renderflags & RF_VIEWMODEL) entrender->flags |= RENDER_VIEWMODEL | RENDER_NODEPTHTEST; + if(renderflags & RF_EXTERNALMODEL)entrender->flags |= RENDER_EXTERIORMODEL; + if(renderflags & RF_NOCULL) entrender->flags |= RENDER_NOCULL; + if(renderflags & RF_DEPTHHACK) entrender->flags |= RENDER_NODEPTHTEST; + if(renderflags & RF_ADDITIVE) entrender->flags |= RENDER_ADDITIVE; + } + + c = (int)PRVM_clientedictfloat(ed, colormap); + if (c <= 0) + CL_SetEntityColormapColors(entrender, -1); + else if (c <= cl.maxclients && cl.scores != NULL) + CL_SetEntityColormapColors(entrender, cl.scores[c-1].colors); + else + CL_SetEntityColormapColors(entrender, c); + + entrender->flags &= ~(RENDER_SHADOW | RENDER_LIGHT | RENDER_NOSELFSHADOW); + // either fullbright or lit + if(!r_fullbright.integer) + { + if (!(entrender->effects & EF_FULLBRIGHT) && !(renderflags & RF_FULLBRIGHT)) + entrender->flags |= RENDER_LIGHT; + else if(r_equalize_entities_fullbright.integer) + entrender->flags |= RENDER_LIGHT | RENDER_EQUALIZE; + } + // hide player shadow during intermission or nehahra movie + if (!(entrender->effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) + && (entrender->alpha >= 1) + && !(renderflags & RF_NOSHADOW) + && !(entrender->flags & RENDER_VIEWMODEL) + && (!(entrender->flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer))) + entrender->flags |= RENDER_SHADOW; + if (entrender->flags & RENDER_VIEWMODEL) + entrender->flags |= RENDER_NOSELFSHADOW; + if (entrender->effects & EF_NOSELFSHADOW) + entrender->flags |= RENDER_NOSELFSHADOW; + if (entrender->effects & EF_NODEPTHTEST) + entrender->flags |= RENDER_NODEPTHTEST; + if (entrender->effects & EF_ADDITIVE) + entrender->flags |= RENDER_ADDITIVE; + if (entrender->effects & EF_DOUBLESIDED) + entrender->flags |= RENDER_DOUBLESIDED; + + // make the other useful stuff + memcpy(entrender->framegroupblend, ed->priv.server->framegroupblend, sizeof(ed->priv.server->framegroupblend)); + CL_UpdateRenderEntity(entrender); + // override animation data with full control + memcpy(entrender->frameblend, ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend)); + if (ed->priv.server->skeleton.relativetransforms) + entrender->skeleton = &ed->priv.server->skeleton; + else + entrender->skeleton = NULL; + + return true; +} + +qboolean CL_VM_InputEvent (qboolean down, int key, int ascii) +{ + qboolean r; + + if(!cl.csqc_loaded) + return false; + + CSQC_BEGIN + if (!PRVM_clientfunction(CSQC_InputEvent)) + r = false; + else + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + PRVM_G_FLOAT(OFS_PARM0) = !down; // 0 is down, 1 is up + PRVM_G_FLOAT(OFS_PARM1) = key; + PRVM_G_FLOAT(OFS_PARM2) = ascii; + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_InputEvent), "QC function CSQC_InputEvent is missing"); + r = CSQC_RETURNVAL != 0; + } + CSQC_END + return r; +} + +qboolean CL_VM_UpdateView (void) +{ + vec3_t emptyvector; + emptyvector[0] = 0; + emptyvector[1] = 0; + emptyvector[2] = 0; +// vec3_t oldangles; + if(!cl.csqc_loaded) + return false; + R_TimeReport("pre-UpdateView"); + CSQC_BEGIN + //VectorCopy(cl.viewangles, oldangles); + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + CSQC_SetGlobals(); + // clear renderable entity and light lists to prevent crashes if the + // CSQC_UpdateView function does not call R_ClearScene as it should + r_refdef.scene.numentities = 0; + r_refdef.scene.numlights = 0; + // pass in width and height as parameters (EXT_CSQC_1) + PRVM_G_FLOAT(OFS_PARM0) = vid.width; + PRVM_G_FLOAT(OFS_PARM1) = vid.height; + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_UpdateView), "QC function CSQC_UpdateView is missing"); + //VectorCopy(oldangles, cl.viewangles); + // Dresk : Reset Dmg Globals Here + CL_VM_UpdateDmgGlobals(0, 0, emptyvector); + CSQC_END + R_TimeReport("UpdateView"); + return true; +} + +extern sizebuf_t vm_tempstringsbuf; +qboolean CL_VM_ConsoleCommand (const char *cmd) +{ + int restorevm_tempstringsbuf_cursize; + qboolean r = false; + if(!cl.csqc_loaded) + return false; + CSQC_BEGIN + if (PRVM_clientfunction(CSQC_ConsoleCommand)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(cmd); + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_ConsoleCommand), "QC function CSQC_ConsoleCommand is missing"); + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + r = CSQC_RETURNVAL != 0; + } + CSQC_END + return r; +} + +qboolean CL_VM_Parse_TempEntity (void) +{ + int t; + qboolean r = false; + if(!cl.csqc_loaded) + return false; + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Parse_TempEntity)) + { + t = msg_readcount; + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Parse_TempEntity), "QC function CSQC_Parse_TempEntity is missing"); + r = CSQC_RETURNVAL != 0; + if(!r) + { + msg_readcount = t; + msg_badread = false; + } + } + CSQC_END + return r; +} + +void CL_VM_Parse_StuffCmd (const char *msg) +{ + int restorevm_tempstringsbuf_cursize; + if(msg[0] == 'c') + if(msg[1] == 's') + if(msg[2] == 'q') + if(msg[3] == 'c') + { + // if this is setting a csqc variable, deprotect csqc_progcrc + // temporarily so that it can be set by the cvar command, + // and then reprotect it afterwards + int crcflags = csqc_progcrc.flags; + int sizeflags = csqc_progcrc.flags; + csqc_progcrc.flags &= ~CVAR_READONLY; + csqc_progsize.flags &= ~CVAR_READONLY; + Cmd_ExecuteString (msg, src_command); + csqc_progcrc.flags = crcflags; + csqc_progsize.flags = sizeflags; + return; + } + + if(cls.demoplayback) + if(!strncmp(msg, "curl --clear_autodownload\ncurl --pak --forthismap --as ", 55)) + { + // special handling for map download commands + // run these commands IMMEDIATELY, instead of waiting for a client frame + // that way, there is no black screen when playing back demos + // I know this is a really ugly hack, but I can't think of any better way + // FIXME find the actual CAUSE of this, and make demo playback WAIT + // until all maps are loaded, then remove this hack + + char buf[MAX_INPUTLINE]; + const char *p, *q; + size_t l; + + p = msg; + + for(;;) + { + q = strchr(p, '\n'); + if(q) + l = q - p; + else + l = strlen(p); + if(l > sizeof(buf) - 1) + l = sizeof(buf) - 1; + strlcpy(buf, p, l + 1); // strlcpy needs a + 1 as it includes the newline! + + Cmd_ExecuteString(buf, src_command); + + p += l; + if(*p == '\n') + ++p; // skip the newline and continue + else + break; // end of string or overflow + } + Cmd_ExecuteString("curl --clear_autodownload", src_command); // don't inhibit CSQC loading + return; + } + + if(!cl.csqc_loaded) + { + Cbuf_AddText(msg); + return; + } + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Parse_StuffCmd)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg); + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Parse_StuffCmd), "QC function CSQC_Parse_StuffCmd is missing"); + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + } + else + Cbuf_AddText(msg); + CSQC_END +} + +static void CL_VM_Parse_Print (const char *msg) +{ + int restorevm_tempstringsbuf_cursize; + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg); + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Parse_Print), "QC function CSQC_Parse_Print is missing"); + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; +} + +void CSQC_AddPrintText (const char *msg) +{ + size_t i; + if(!cl.csqc_loaded) + { + Con_Print(msg); + return; + } + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Parse_Print)) + { + // FIXME: is this bugged? + i = strlen(msg)-1; + if(msg[i] != '\n' && msg[i] != '\r') + { + if(strlen(cl.csqc_printtextbuf)+i >= MAX_INPUTLINE) + { + CL_VM_Parse_Print(cl.csqc_printtextbuf); + cl.csqc_printtextbuf[0] = 0; + } + else + strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE); + return; + } + strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE); + CL_VM_Parse_Print(cl.csqc_printtextbuf); + cl.csqc_printtextbuf[0] = 0; + } + else + Con_Print(msg); + CSQC_END +} + +void CL_VM_Parse_CenterPrint (const char *msg) +{ + int restorevm_tempstringsbuf_cursize; + if(!cl.csqc_loaded) + { + SCR_CenterPrint(msg); + return; + } + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Parse_CenterPrint)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg); + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Parse_CenterPrint), "QC function CSQC_Parse_CenterPrint is missing"); + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + } + else + SCR_CenterPrint(msg); + CSQC_END +} + +void CL_VM_UpdateIntermissionState (int intermission) +{ + if(cl.csqc_loaded) + { + CSQC_BEGIN + PRVM_clientglobalfloat(intermission) = intermission; + CSQC_END + } +} +void CL_VM_UpdateShowingScoresState (int showingscores) +{ + if(cl.csqc_loaded) + { + CSQC_BEGIN + PRVM_clientglobalfloat(sb_showscores) = showingscores; + CSQC_END + } +} +qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos) +{ + qboolean r = false; + if(cl.csqc_loaded) + { + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Event_Sound)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + PRVM_G_FLOAT(OFS_PARM0) = ent; + PRVM_G_FLOAT(OFS_PARM1) = CHAN_ENGINE2USER(channel); + PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(cl.sound_name[sound_num] ); + PRVM_G_FLOAT(OFS_PARM3) = volume; + PRVM_G_FLOAT(OFS_PARM4) = attenuation; + VectorCopy(pos, PRVM_G_VECTOR(OFS_PARM5) ); + PRVM_G_FLOAT(OFS_PARM6) = 0; // pitch shift not supported yet + PRVM_G_FLOAT(OFS_PARM7) = 0; // flags - none can come in at this point yet + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Event_Sound), "QC function CSQC_Event_Sound is missing"); + r = CSQC_RETURNVAL != 0; + } + CSQC_END + } + + return r; +} +void CL_VM_UpdateCoopDeathmatchGlobals (int gametype) +{ + // Avoid global names for clean(er) coding + int localcoop; + int localdeathmatch; + + if(cl.csqc_loaded) + { + if(gametype == GAME_COOP) + { + localcoop = 1; + localdeathmatch = 0; + } + else + if(gametype == GAME_DEATHMATCH) + { + localcoop = 0; + localdeathmatch = 1; + } + else + { + // How did the ServerInfo send an unknown gametype? + // Better just assign the globals as 0... + localcoop = 0; + localdeathmatch = 0; + } + CSQC_BEGIN + PRVM_clientglobalfloat(coop) = localcoop; + PRVM_clientglobalfloat(deathmatch) = localdeathmatch; + CSQC_END + } +} +float CL_VM_Event (float event) //[515]: needed ? I'd say "YES", but don't know for what :D +{ + float r = 0; + if(!cl.csqc_loaded) + return 0; + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Event)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + PRVM_G_FLOAT(OFS_PARM0) = event; + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Event), "QC function CSQC_Event is missing"); + r = CSQC_RETURNVAL; + } + CSQC_END + return r; +} + +void CSQC_ReadEntities (void) +{ + unsigned short entnum, oldself, realentnum; + if(!cl.csqc_loaded) + { + Host_Error ("CSQC_ReadEntities: CSQC is not loaded"); + return; + } + + CSQC_BEGIN + PRVM_clientglobalfloat(time) = cl.time; + oldself = PRVM_clientglobaledict(self); + while(1) + { + entnum = MSG_ReadShort(); + if(!entnum || msg_badread) + break; + realentnum = entnum & 0x7FFF; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum]; + if(entnum & 0x8000) + { + if(PRVM_clientglobaledict(self)) + { + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Ent_Remove), "QC function CSQC_Ent_Remove is missing"); + cl.csqc_server2csqcentitynumber[realentnum] = 0; + } + else + { + // LordHavoc: removing an entity that is already gone on + // the csqc side is possible for legitimate reasons (such + // as a repeat of the remove message), so no warning is + // needed + //Con_Printf("Bad csqc_server2csqcentitynumber map\n"); //[515]: never happens ? + } + } + else + { + if(!PRVM_clientglobaledict(self)) + { + if(!PRVM_clientfunction(CSQC_Ent_Spawn)) + { + prvm_edict_t *ed; + ed = PRVM_ED_Alloc(); + PRVM_clientedictfloat(ed, entnum) = realentnum; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT_TO_PROG(ed); + } + else + { + // entity( float entnum ) CSQC_Ent_Spawn; + // the qc function should set entnum, too (this way it also can return world [2/1/2008 Andreas] + PRVM_G_FLOAT(OFS_PARM0) = (float) realentnum; + // make sure no one gets wrong ideas + PRVM_clientglobaledict(self) = 0; + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Ent_Spawn), "QC function CSQC_Ent_Spawn is missing"); + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT( PRVM_G_INT( OFS_RETURN ) ); + } + PRVM_G_FLOAT(OFS_PARM0) = 1; + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Ent_Update), "QC function CSQC_Ent_Update is missing"); + } + else { + PRVM_G_FLOAT(OFS_PARM0) = 0; + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Ent_Update), "QC function CSQC_Ent_Update is missing"); + } + } + } + PRVM_clientglobaledict(self) = oldself; + CSQC_END +} + +void CL_VM_CB_BeginIncreaseEdicts(void) +{ + // links don't survive the transition, so unlink everything + World_UnlinkAll(&cl.world); +} + +void CL_VM_CB_EndIncreaseEdicts(void) +{ + int i; + prvm_edict_t *ent; + + // link every entity except world + for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++) + if (!ent->priv.server->free) + CL_LinkEdict(ent); +} + +void CL_VM_CB_InitEdict(prvm_edict_t *e) +{ + int edictnum = PRVM_NUM_FOR_EDICT(e); + entity_render_t *entrender; + CL_ExpandCSQCRenderEntities(edictnum); + entrender = cl.csqcrenderentities + edictnum; + e->priv.server->move = false; // don't move on first frame + memset(entrender, 0, sizeof(*entrender)); + entrender->shadertime = cl.time; +} + +extern void R_DecalSystem_Reset(decalsystem_t *decalsystem); + +void CL_VM_CB_FreeEdict(prvm_edict_t *ed) +{ + entity_render_t *entrender = cl.csqcrenderentities + PRVM_NUM_FOR_EDICT(ed); + R_DecalSystem_Reset(&entrender->decalsystem); + memset(entrender, 0, sizeof(*entrender)); + World_UnlinkEdict(ed); + memset(ed->fields.vp, 0, prog->entityfields * 4); + VM_RemoveEdictSkeleton(ed); + World_Physics_RemoveFromEntity(&cl.world, ed); + World_Physics_RemoveJointFromEntity(&cl.world, ed); +} + +void CL_VM_CB_CountEdicts(void) +{ + int i; + prvm_edict_t *ent; + int active = 0, models = 0, solid = 0; + + for (i=0 ; inum_edicts ; i++) + { + ent = PRVM_EDICT_NUM(i); + if (ent->priv.server->free) + continue; + active++; + if (PRVM_clientedictfloat(ent, solid)) + solid++; + if (PRVM_clientedictstring(ent, model)) + models++; + } + + Con_Printf("num_edicts:%3i\n", prog->num_edicts); + Con_Printf("active :%3i\n", active); + Con_Printf("view :%3i\n", models); + Con_Printf("touch :%3i\n", solid); +} + +qboolean CL_VM_CB_LoadEdict(prvm_edict_t *ent) +{ + return true; +} + +void Cmd_ClearCsqcFuncs (void); + +// returns true if the packet is valid, false if end of file is reached +// used for dumping the CSQC download into demo files +qboolean MakeDownloadPacket(const char *filename, unsigned char *data, size_t len, int crc, int cnt, sizebuf_t *buf, int protocol) +{ + int packetsize = buf->maxsize - 7; // byte short long + int npackets = (len + packetsize - 1) / (packetsize); + + if(protocol == PROTOCOL_QUAKEWORLD) + return false; // CSQC can't run in QW anyway + + SZ_Clear(buf); + if(cnt == 0) + { + MSG_WriteByte(buf, svc_stufftext); + MSG_WriteString(buf, va("\ncl_downloadbegin %lu %s\n", (unsigned long)len, filename)); + return true; + } + else if(cnt >= 1 && cnt <= npackets) + { + unsigned long thispacketoffset = (cnt - 1) * packetsize; + int thispacketsize = len - thispacketoffset; + if(thispacketsize > packetsize) + thispacketsize = packetsize; + + MSG_WriteByte(buf, svc_downloaddata); + MSG_WriteLong(buf, thispacketoffset); + MSG_WriteShort(buf, thispacketsize); + SZ_Write(buf, data + thispacketoffset, thispacketsize); + + return true; + } + else if(cnt == npackets + 1) + { + MSG_WriteByte(buf, svc_stufftext); + MSG_WriteString(buf, va("\ncl_downloadfinished %lu %d\n", (unsigned long)len, crc)); + return true; + } + return false; +} + +void CL_VM_Init (void) +{ + const char* csprogsfn; + unsigned char *csprogsdata; + fs_offset_t csprogsdatasize; + int csprogsdatacrc, requiredcrc; + int requiredsize; + + // reset csqc_progcrc after reading it, so that changing servers doesn't + // expect csqc on the next server + requiredcrc = csqc_progcrc.integer; + requiredsize = csqc_progsize.integer; + Cvar_SetValueQuick(&csqc_progcrc, -1); + Cvar_SetValueQuick(&csqc_progsize, -1); + + // if the server is not requesting a csprogs, then we're done here + if (requiredcrc < 0) + return; + + // see if the requested csprogs.dat file matches the requested crc + csprogsdatacrc = -1; + csprogsfn = va("dlcache/%s.%i.%i", csqc_progname.string, requiredsize, requiredcrc); + csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize); + if (!csprogsdata) + { + csprogsfn = csqc_progname.string; + csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize); + } + if (csprogsdata) + { + csprogsdatacrc = CRC_Block(csprogsdata, (size_t)csprogsdatasize); + if (csprogsdatacrc != requiredcrc || csprogsdatasize != requiredsize) + { + if (cls.demoplayback) + { + Con_Printf("^1Warning: Your %s is not the same version as the demo was recorded with (CRC/size are %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize); + // Mem_Free(csprogsdata); + // return; + // We WANT to continue here, and play the demo with different csprogs! + // After all, this is just a warning. Sure things may go wrong from here. + } + else + { + Mem_Free(csprogsdata); + Con_Printf("^1Your %s is not the same version as the server (CRC is %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize); + CL_Disconnect(); + return; + } + } + } + else + { + if (requiredcrc >= 0) + { + if (cls.demoplayback) + Con_Printf("CL_VM_Init: demo requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string); + else + Con_Printf("CL_VM_Init: server requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string); + CL_Disconnect(); + } + return; + } + + PRVM_Begin; + PRVM_InitProg(PRVM_CLIENTPROG); + + // allocate the mempools + prog->progs_mempool = Mem_AllocPool(csqc_progname.string, 0, NULL); + prog->edictprivate_size = 0; // no private struct used + prog->name = CL_NAME; + prog->num_edicts = 1; + prog->max_edicts = 512; + prog->limit_edicts = CL_MAX_EDICTS; + prog->reserved_edicts = 0; + prog->edictprivate_size = sizeof(edict_engineprivate_t); + // TODO: add a shared extension string #define and add real support for csqc extension strings [12/5/2007 Black] + prog->extensionstring = vm_sv_extensions; + prog->builtins = vm_cl_builtins; + prog->numbuiltins = vm_cl_numbuiltins; + prog->begin_increase_edicts = CL_VM_CB_BeginIncreaseEdicts; + prog->end_increase_edicts = CL_VM_CB_EndIncreaseEdicts; + prog->init_edict = CL_VM_CB_InitEdict; + prog->free_edict = CL_VM_CB_FreeEdict; + prog->count_edicts = CL_VM_CB_CountEdicts; + prog->load_edict = CL_VM_CB_LoadEdict; + prog->init_cmd = VM_CL_Cmd_Init; + prog->reset_cmd = VM_CL_Cmd_Reset; + prog->error_cmd = CL_VM_Error; + prog->ExecuteProgram = CLVM_ExecuteProgram; + + PRVM_LoadProgs(csprogsfn, cl_numrequiredfunc, cl_required_func, CL_REQFIELDS, cl_reqfields, CL_REQGLOBALS, cl_reqglobals); + + if (!prog->loaded) + { + CL_VM_Error("CSQC %s ^2failed to load\n", csprogsfn); + if(!sv.active) + CL_Disconnect(); + Mem_Free(csprogsdata); + return; + } + + Con_DPrintf("CSQC %s ^5loaded (crc=%i, size=%i)\n", csprogsfn, csprogsdatacrc, (int)csprogsdatasize); + + if(cls.demorecording) + { + if(cls.demo_lastcsprogssize != csprogsdatasize || cls.demo_lastcsprogscrc != csprogsdatacrc) + { + int i; + static char buf[NET_MAXMESSAGE]; + sizebuf_t sb; + unsigned char *demobuf; fs_offset_t demofilesize; + + sb.data = (unsigned char *) buf; + sb.maxsize = sizeof(buf); + i = 0; + + CL_CutDemo(&demobuf, &demofilesize); + while(MakeDownloadPacket(csqc_progname.string, csprogsdata, (size_t)csprogsdatasize, csprogsdatacrc, i++, &sb, cls.protocol)) + CL_WriteDemoMessage(&sb); + CL_PasteDemo(&demobuf, &demofilesize); + + cls.demo_lastcsprogssize = csprogsdatasize; + cls.demo_lastcsprogscrc = csprogsdatacrc; + } + } + Mem_Free(csprogsdata); + + // check if OP_STATE animation is possible in this dat file + if (prog->fieldoffsets.nextthink >= 0 && prog->fieldoffsets.frame >= 0 && prog->fieldoffsets.think >= 0 && prog->globaloffsets.self >= 0) + prog->flag |= PRVM_OP_STATE; + + // set time + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = 0; + + PRVM_clientglobalstring(mapname) = PRVM_SetEngineString(cl.worldname); + PRVM_clientglobalfloat(player_localentnum) = cl.playerentity; + + // set map description (use world entity 0) + PRVM_clientedictstring(prog->edicts, message) = PRVM_SetEngineString(cl.worldmessage); + VectorCopy(cl.world.mins, PRVM_clientedictvector(prog->edicts, mins)); + VectorCopy(cl.world.maxs, PRVM_clientedictvector(prog->edicts, maxs)); + + // call the prog init + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Init), "QC function CSQC_Init is missing"); + + PRVM_End; + cl.csqc_loaded = true; + + cl.csqc_vidvars.drawcrosshair = false; + cl.csqc_vidvars.drawenginesbar = false; + + // Update Coop and Deathmatch Globals (at this point the client knows them from ServerInfo) + CL_VM_UpdateCoopDeathmatchGlobals(cl.gametype); +} + +void CL_VM_ShutDown (void) +{ + Cmd_ClearCsqcFuncs(); + //Cvar_SetValueQuick(&csqc_progcrc, -1); + //Cvar_SetValueQuick(&csqc_progsize, -1); + if(!cl.csqc_loaded) + return; + CSQC_BEGIN + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = 0; + if (PRVM_clientfunction(CSQC_Shutdown)) + PRVM_ExecuteProgram(PRVM_clientfunction(CSQC_Shutdown), "QC function CSQC_Shutdown is missing"); + PRVM_ResetProg(); + CSQC_END + Con_DPrint("CSQC ^1unloaded\n"); + cl.csqc_loaded = false; +} + +qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out) +{ + prvm_edict_t *ed; + dp_model_t *mod; + matrix4x4_t matrix; + qboolean r = 0; + + CSQC_BEGIN; + + // FIXME consider attachments here! + + ed = PRVM_EDICT_NUM(entnum - MAX_EDICTS); + + if(!ed->priv.required->free) + { + mod = CL_GetModelFromEdict(ed); + VectorCopy(PRVM_clientedictvector(ed, origin), out); + if(CL_GetTagMatrix (&matrix, ed, 0) == 0) + Matrix4x4_OriginFromMatrix(&matrix, out); + if (mod && mod->soundfromcenter) + VectorMAMAM(1.0f, out, 0.5f, mod->normalmins, 0.5f, mod->normalmaxs, out); + r = 1; + } + + CSQC_END; + + return r; +} + +qboolean CL_VM_TransformView(int entnum, matrix4x4_t *viewmatrix, mplane_t *clipplane, vec3_t visorigin) +{ + qboolean ret = false; + prvm_edict_t *ed; + vec3_t forward, left, up, origin, ang; + matrix4x4_t mat, matq; + + CSQC_BEGIN + ed = PRVM_EDICT_NUM(entnum); + // camera: + // camera_transform + if(PRVM_clientedictfunction(ed, camera_transform)) + { + ret = true; + if(viewmatrix || clipplane || visorigin) + { + Matrix4x4_ToVectors(viewmatrix, forward, left, up, origin); + AnglesFromVectors(ang, forward, up, false); + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = entnum; + VectorCopy(origin, PRVM_G_VECTOR(OFS_PARM0)); + VectorCopy(ang, PRVM_G_VECTOR(OFS_PARM1)); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorScale(left, -1, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_clientglobalvector(trace_endpos)); + PRVM_ExecuteProgram(PRVM_clientedictfunction(ed, camera_transform), "QC function e.camera_transform is missing"); + VectorCopy(PRVM_G_VECTOR(OFS_RETURN), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorScale(PRVM_clientglobalvector(v_right), -1, left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + VectorCopy(PRVM_clientglobalvector(trace_endpos), visorigin); + Matrix4x4_Invert_Full(&mat, viewmatrix); + Matrix4x4_FromVectors(viewmatrix, forward, left, up, origin); + Matrix4x4_Concat(&matq, viewmatrix, &mat); + Matrix4x4_TransformPositivePlane(&matq, clipplane->normal[0], clipplane->normal[1], clipplane->normal[2], clipplane->dist, &clipplane->normal[0]); + } + } + CSQC_END + + return ret; +} diff --git a/misc/source/darkplaces-src/csprogs.h b/misc/source/darkplaces-src/csprogs.h new file mode 100644 index 00000000..bb0a3fb8 --- /dev/null +++ b/misc/source/darkplaces-src/csprogs.h @@ -0,0 +1,69 @@ +#ifndef CSPROGS_H +#define CSPROGS_H + +#define CL_NAME "client" + +// LordHavoc: changed to match MAX_EDICTS +#define CL_MAX_EDICTS MAX_EDICTS + +#define ENTMASK_ENGINE 1 +#define ENTMASK_ENGINEVIEWMODELS 2 +#define ENTMASK_NORMAL 4 + +#define VF_MIN 1 //(vector) +#define VF_MIN_X 2 //(float) +#define VF_MIN_Y 3 //(float) +#define VF_SIZE 4 //(vector) (viewport size) +#define VF_SIZE_X 5 //(float) +#define VF_SIZE_Y 6 //(float) +#define VF_VIEWPORT 7 //(vector, vector) +#define VF_FOV 8 //(vector) +#define VF_FOVX 9 //(float) +#define VF_FOVY 10 //(float) +#define VF_ORIGIN 11 //(vector) +#define VF_ORIGIN_X 12 //(float) +#define VF_ORIGIN_Y 13 //(float) +#define VF_ORIGIN_Z 14 //(float) +#define VF_ANGLES 15 //(vector) +#define VF_ANGLES_X 16 //(float) +#define VF_ANGLES_Y 17 //(float) +#define VF_ANGLES_Z 18 //(float) + +#define VF_DRAWWORLD 19 //(float) //actually world model and sky +#define VF_DRAWENGINESBAR 20 //(float) +#define VF_DRAWCROSSHAIR 21 //(float) + +#define VF_CL_VIEWANGLES 33 //(vector) //sweet thing for RPGs/... +#define VF_CL_VIEWANGLES_X 34 //(float) +#define VF_CL_VIEWANGLES_Y 35 //(float) +#define VF_CL_VIEWANGLES_Z 36 //(float) + +#define VF_PERSPECTIVE 200 //(float) +#define VF_CLEARSCREEN 201 //(float) + +#define RF_VIEWMODEL 1 // The entity is never drawn in mirrors. In engines with realtime lighting, it casts no shadows. +#define RF_EXTERNALMODEL 2 // The entity is appears in mirrors but not in the normal view. It does still cast shadows in engines with realtime lighting. +#define RF_DEPTHHACK 4 // The entity appears closer to the view than normal, either by scaling it wierdly or by just using a depthrange. This will usually be found in conjunction with RF_VIEWMODEL +#define RF_ADDITIVE 8 // Add the entity acording to it's alpha values instead of the normal blend +#define RF_USEAXIS 16 // When set, the entity will use the v_forward, v_right and v_up globals instead of it's angles field for orientation. Angles will be ignored compleatly. + // Note that to use this properly, you'll NEED to use the predraw function to set the globals. +//#define RF_DOUBLESIDED 32 +#define RF_USETRANSPARENTOFFSET 64 // Allows QC to customize origin used for transparent sorting via transparent_origin global, helps to fix transparent sorting bugs on a very large entities +#define RF_NOCULL 128 // do not cull this entity using r_cullentities, for large outdoor entities (asteroids on the sky. etc) + +#define RF_FULLBRIGHT 256 +#define RF_NOSHADOW 512 + +extern cvar_t csqc_progname; //[515]: csqc crc check and right csprogs name according to progs.dat +extern cvar_t csqc_progcrc; +extern cvar_t csqc_progsize; + +void CL_VM_PreventInformationLeaks(void); + +qboolean MakeDownloadPacket(const char *filename, unsigned char *data, size_t len, int crc, int cnt, sizebuf_t *buf, int protocol); + +qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out); + +qboolean CL_VM_TransformView(int entnum, matrix4x4_t *viewmatrix, mplane_t *clipplane, vec3_t visorigin); + +#endif diff --git a/misc/source/darkplaces-src/curves.c b/misc/source/darkplaces-src/curves.c new file mode 100644 index 00000000..17d1b942 --- /dev/null +++ b/misc/source/darkplaces-src/curves.c @@ -0,0 +1,440 @@ + +/* +this code written by Forest Hale, on 2004-10-17, and placed into public domain +this implements Quadratic BSpline surfaces as seen in Quake3 by id Software + +a small rant on misuse of the name 'bezier': many people seem to think that +bezier is a generic term for splines, but it is not, it is a term for a +specific type of bspline (4 control points, cubic bspline), bsplines are the +generalization of the bezier spline to support dimensions other than cubic. + +example equations for 1-5 control point bsplines being sampled as t=0...1 +1: flat (0th dimension) +o = a +2: linear (1st dimension) +o = a * (1 - t) + b * t +3: quadratic bspline (2nd dimension) +o = a * (1 - t) * (1 - t) + 2 * b * (1 - t) * t + c * t * t +4: cubic (bezier) bspline (3rd dimension) +o = a * (1 - t) * (1 - t) * (1 - t) + 3 * b * (1 - t) * (1 - t) * t + 3 * c * (1 - t) * t * t + d * t * t * t +5: quartic bspline (4th dimension) +o = a * (1 - t) * (1 - t) * (1 - t) * (1 - t) + 4 * b * (1 - t) * (1 - t) * (1 - t) * t + 6 * c * (1 - t) * (1 - t) * t * t + 4 * d * (1 - t) * t * t * t + e * t * t * t * t + +arbitrary dimension bspline +double factorial(int n) +{ + int i; + double f; + f = 1; + for (i = 1;i < n;i++) + f = f * i; + return f; +} +double bsplinesample(int dimensions, double t, double *param) +{ + double o = 0; + for (i = 0;i < dimensions + 1;i++) + o += param[i] * factorial(dimensions)/(factorial(i)*factorial(dimensions-i)) * pow(t, i) * pow(1 - t, dimensions - i); + return o; +} +*/ + +#include "quakedef.h" +#include "mathlib.h" + +#include +#include "curves.h" + +// Calculate number of resulting vertex rows/columns by given patch size and tesselation factor +// tess=0 means that we reduce detalization of base 3x3 patches by removing middle row and column of vertices +// "DimForTess" is "DIMension FOR TESSelation factor" +// NB: tess=0 actually means that tess must be 0.5, but obviously it can't because it is of int type. (so "a*tess"-like code is replaced by "a/2" if tess=0) +int Q3PatchDimForTess(int size, int tess) +{ + if (tess > 0) + return (size - 1) * tess + 1; + else if (tess == 0) + return (size - 1) / 2 + 1; + else + return 0; // Maybe warn about wrong tess here? +} + +// usage: +// to expand a 5x5 patch to 21x21 vertices (4x4 tesselation), one might use this call: +// Q3PatchSubdivideFloat(3, sizeof(float[3]), outvertices, 5, 5, sizeof(float[3]), patchvertices, 4, 4); +void Q3PatchTesselateFloat(int numcomponents, int outputstride, float *outputvertices, int patchwidth, int patchheight, int inputstride, float *patchvertices, int tesselationwidth, int tesselationheight) +{ + int k, l, x, y, component, outputwidth = Q3PatchDimForTess(patchwidth, tesselationwidth); + float px, py, *v, a, b, c, *cp[3][3], temp[3][64]; + int xmax = max(1, 2*tesselationwidth); + int ymax = max(1, 2*tesselationheight); + + // iterate over the individual 3x3 quadratic spline surfaces one at a time + // expanding them to fill the output array (with some overlap to ensure + // the edges are filled) + for (k = 0;k < patchheight-1;k += 2) + { + for (l = 0;l < patchwidth-1;l += 2) + { + // set up control point pointers for quicker lookup later + for (y = 0;y < 3;y++) + for (x = 0;x < 3;x++) + cp[y][x] = (float *)((unsigned char *)patchvertices + ((k+y)*patchwidth+(l+x)) * inputstride); + // for each row... + for (y = 0;y <= ymax;y++) + { + // calculate control points for this row by collapsing the 3 + // rows of control points to one row using py + py = (float)y / (float)ymax; + // calculate quadratic spline weights for py + a = ((1.0f - py) * (1.0f - py)); + b = ((1.0f - py) * (2.0f * py)); + c = (( py) * ( py)); + for (component = 0;component < numcomponents;component++) + { + temp[0][component] = cp[0][0][component] * a + cp[1][0][component] * b + cp[2][0][component] * c; + temp[1][component] = cp[0][1][component] * a + cp[1][1][component] * b + cp[2][1][component] * c; + temp[2][component] = cp[0][2][component] * a + cp[1][2][component] * b + cp[2][2][component] * c; + } + // fetch a pointer to the beginning of the output vertex row + v = (float *)((unsigned char *)outputvertices + ((k * ymax / 2 + y) * outputwidth + l * xmax / 2) * outputstride); + // for each column of the row... + for (x = 0;x <= xmax;x++) + { + // calculate point based on the row control points + px = (float)x / (float)xmax; + // calculate quadratic spline weights for px + // (could be precalculated) + a = ((1.0f - px) * (1.0f - px)); + b = ((1.0f - px) * (2.0f * px)); + c = (( px) * ( px)); + for (component = 0;component < numcomponents;component++) + v[component] = temp[0][component] * a + temp[1][component] * b + temp[2][component] * c; + // advance to next output vertex using outputstride + // (the next vertex may not be directly following this + // one, as this may be part of a larger structure) + v = (float *)((unsigned char *)v + outputstride); + } + } + } + } +#if 0 + // enable this if you want results printed out + printf("vertices[%i][%i] =\n{\n", (patchheight-1)*tesselationheight+1, (patchwidth-1)*tesselationwidth+1); + for (y = 0;y < (patchheight-1)*tesselationheight+1;y++) + { + for (x = 0;x < (patchwidth-1)*tesselationwidth+1;x++) + { + printf("("); + for (component = 0;component < numcomponents;component++) + printf("%f ", outputvertices[(y*((patchwidth-1)*tesselationwidth+1)+x)*numcomponents+component]); + printf(") "); + } + printf("\n"); + } + printf("}\n"); +#endif +} + +static int Q3PatchTesselation(float largestsquared3xcurvearea, float tolerance) +{ + float f; + // f is actually a squared 2x curve area... so the formula had to be adjusted to give roughly the same subdivisions + f = pow(largestsquared3xcurvearea / 64.0f, 0.25f) / tolerance; + //if(f < 0.25) // VERY flat patches + if(f < 0.0001) // TOTALLY flat patches + return 0; + else if(f < 2) + return 1; + else + return (int) floor(log(f) / log(2.0f)) + 1; + // this is always at least 2 + // maps [0.25..0.5[ to -1 (actually, 1 is returned) + // maps [0.5..1[ to 0 (actually, 1 is returned) + // maps [1..2[ to 1 + // maps [2..4[ to 2 + // maps [4..8[ to 4 +} + +float Squared3xCurveArea(const float *a, const float *control, const float *b, int components) +{ +#if 0 + // mimicing the old behaviour with the new code... + + float deviation; + float quartercurvearea = 0; + int c; + for (c = 0;c < components;c++) + { + deviation = control[c] * 0.5f - a[c] * 0.25f - b[c] * 0.25f; + quartercurvearea += deviation*deviation; + } + + // But as the new code now works on the squared 2x curve area, let's scale the value + return quartercurvearea * quartercurvearea * 64.0; + +#else + // ideally, we'd like the area between the spline a->control->b and the line a->b. + // but as this is hard to calculate, let's calculate an upper bound of it: + // the area of the triangle a->control->b->a. + // + // one can prove that the area of a quadratic spline = 2/3 * the area of + // the triangle of its control points! + // to do it, first prove it for the spline through (0,0), (1,1), (2,0) + // (which is a parabola) and then note that moving the control point + // left/right is just shearing and keeps the area of both the spline and + // the triangle invariant. + // + // why are we going for the spline area anyway? + // we know that: + // + // the area between the spline and the line a->b is a measure of the + // error of approximation of the spline by the line. + // + // also, on circle-like or parabola-like curves, you easily get that the + // double amount of line approximation segments reduces the error to its quarter + // (also, easy to prove for splines by doing it for one specific one, and using + // affine transforms to get all other splines) + // + // so... + // + // let's calculate the area! but we have to avoid the cross product, as + // components is not necessarily 3 + // + // the area of a triangle spanned by vectors a and b is + // + // 0.5 * |a| |b| sin gamma + // + // now, cos gamma is + // + // a.b / (|a| |b|) + // + // so the area is + // + // 0.5 * sqrt(|a|^2 |b|^2 - (a.b)^2) + int c; + float aa = 0, bb = 0, ab = 0; + for (c = 0;c < components;c++) + { + float xa = a[c] - control[c]; + float xb = b[c] - control[c]; + aa += xa * xa; + ab += xa * xb; + bb += xb * xb; + } + // area is 0.5 * sqrt(aa*bb - ab*ab) + // 2x TRIANGLE area is sqrt(aa*bb - ab*ab) + // 3x CURVE area is sqrt(aa*bb - ab*ab) + return aa * bb - ab * ab; +#endif +} + +// returns how much tesselation of each segment is needed to remain under tolerance +int Q3PatchTesselationOnX(int patchwidth, int patchheight, int components, const float *in, float tolerance) +{ + int x, y; + const float *patch; + float squared3xcurvearea, largestsquared3xcurvearea; + largestsquared3xcurvearea = 0; + for (y = 0;y < patchheight;y++) + { + for (x = 0;x < patchwidth-1;x += 2) + { + patch = in + ((y * patchwidth) + x) * components; + squared3xcurvearea = Squared3xCurveArea(&patch[0], &patch[components], &patch[2*components], components); + if (largestsquared3xcurvearea < squared3xcurvearea) + largestsquared3xcurvearea = squared3xcurvearea; + } + } + return Q3PatchTesselation(largestsquared3xcurvearea, tolerance); +} + +// returns how much tesselation of each segment is needed to remain under tolerance +int Q3PatchTesselationOnY(int patchwidth, int patchheight, int components, const float *in, float tolerance) +{ + int x, y; + const float *patch; + float squared3xcurvearea, largestsquared3xcurvearea; + largestsquared3xcurvearea = 0; + for (y = 0;y < patchheight-1;y += 2) + { + for (x = 0;x < patchwidth;x++) + { + patch = in + ((y * patchwidth) + x) * components; + squared3xcurvearea = Squared3xCurveArea(&patch[0], &patch[patchwidth*components], &patch[2*patchwidth*components], components); + if (largestsquared3xcurvearea < squared3xcurvearea) + largestsquared3xcurvearea = squared3xcurvearea; + } + } + return Q3PatchTesselation(largestsquared3xcurvearea, tolerance); +} + +// Find an equal vertex in array. Check only vertices with odd X and Y +static int FindEqualOddVertexInArray(int numcomponents, float *vertex, float *vertices, int width, int height) +{ + int x, y, j; + for (y=0; y 0.05) + // div0: this is notably smaller than the smallest radiant grid + // but large enough so we don't need to get scared of roundoff + // errors + { + found = false; + break; + } + if(found) + return y*width+x; + vertices += numcomponents*2; + } + vertices += numcomponents*(width-1); + } + return -1; +} + +#define SIDE_INVALID -1 +#define SIDE_X 0 +#define SIDE_Y 1 + +static int GetSide(int p1, int p2, int width, int height, int *pointdist) +{ + int x1 = p1 % width, y1 = p1 / width; + int x2 = p2 % width, y2 = p2 / width; + if (p1 < 0 || p2 < 0) + return SIDE_INVALID; + if (x1 == x2) + { + if (y1 != y2) + { + *pointdist = abs(y2 - y1); + return SIDE_Y; + } + else + return SIDE_INVALID; + } + else if (y1 == y2) + { + *pointdist = abs(x2 - x1); + return SIDE_X; + } + else + return SIDE_INVALID; +} + +// Increase tesselation of one of two touching patches to make a seamless connection between them +// Returns 0 in case if patches were not modified, otherwise 1 +int Q3PatchAdjustTesselation(int numcomponents, patchinfo_t *patch1, float *patchvertices1, patchinfo_t *patch2, float *patchvertices2) +{ + // what we are doing here is: + // we take for each corner of one patch + // and check if the other patch contains that corner + // once we have a pair of such matches + + struct {int id1,id2;} commonverts[8]; + int i, j, k, side1, side2, *tess1, *tess2; + int dist1 = 0, dist2 = 0; + qboolean modified = false; + + // Potential paired vertices (corners of the first patch) + commonverts[0].id1 = 0; + commonverts[1].id1 = patch1->xsize-1; + commonverts[2].id1 = patch1->xsize*(patch1->ysize-1); + commonverts[3].id1 = patch1->xsize*patch1->ysize-1; + for (i=0;i<4;++i) + commonverts[i].id2 = FindEqualOddVertexInArray(numcomponents, patchvertices1+numcomponents*commonverts[i].id1, patchvertices2, patch2->xsize, patch2->ysize); + + // Corners of the second patch + commonverts[4].id2 = 0; + commonverts[5].id2 = patch2->xsize-1; + commonverts[6].id2 = patch2->xsize*(patch2->ysize-1); + commonverts[7].id2 = patch2->xsize*patch2->ysize-1; + for (i=4;i<8;++i) + commonverts[i].id1 = FindEqualOddVertexInArray(numcomponents, patchvertices2+numcomponents*commonverts[i].id2, patchvertices1, patch1->xsize, patch1->ysize); + + for (i=0;i<8;++i) + for (j=i+1;j<8;++j) + { + side1 = GetSide(commonverts[i].id1,commonverts[j].id1,patch1->xsize,patch1->ysize,&dist1); + side2 = GetSide(commonverts[i].id2,commonverts[j].id2,patch2->xsize,patch2->ysize,&dist2); + + if (side1 == SIDE_INVALID || side2 == SIDE_INVALID) + continue; + + if(dist1 != dist2) + { + // no patch welding if the resolutions mismatch + continue; + } + + // Update every lod level + for (k=0;klods[k].xtess : &patch1->lods[k].ytess; + tess2 = side2 == SIDE_X ? &patch2->lods[k].xtess : &patch2->lods[k].ytess; + if (*tess1 != *tess2) + { + if (*tess1 < *tess2) + *tess1 = *tess2; + else + *tess2 = *tess1; + modified = true; + } + } + } + + return modified; +} + +#undef SIDE_INVALID +#undef SIDE_X +#undef SIDE_Y + +// calculates elements for a grid of vertices +// (such as those produced by Q3PatchTesselate) +// (note: width and height are the actual vertex size, this produces +// (width-1)*(height-1)*2 triangles, 3 elements each) +void Q3PatchTriangleElements(int *elements, int width, int height, int firstvertex) +{ + int x, y, row0, row1; + for (y = 0;y < height - 1;y++) + { + if(y % 2) + { + // swap the triangle order in odd rows as optimization for collision stride + row0 = firstvertex + (y + 0) * width + width - 2; + row1 = firstvertex + (y + 1) * width + width - 2; + for (x = 0;x < width - 1;x++) + { + *elements++ = row1; + *elements++ = row1 + 1; + *elements++ = row0 + 1; + *elements++ = row0; + *elements++ = row1; + *elements++ = row0 + 1; + row0--; + row1--; + } + } + else + { + row0 = firstvertex + (y + 0) * width; + row1 = firstvertex + (y + 1) * width; + for (x = 0;x < width - 1;x++) + { + *elements++ = row0; + *elements++ = row1; + *elements++ = row0 + 1; + *elements++ = row1; + *elements++ = row1 + 1; + *elements++ = row0 + 1; + row0++; + row1++; + } + } + } +} + diff --git a/misc/source/darkplaces-src/curves.h b/misc/source/darkplaces-src/curves.h new file mode 100644 index 00000000..6555a5a0 --- /dev/null +++ b/misc/source/darkplaces-src/curves.h @@ -0,0 +1,39 @@ + +#ifndef CURVES_H +#define CURVES_H + +#define PATCH_LODS_NUM 2 +#define PATCH_LOD_COLLISION 0 +#define PATCH_LOD_VISUAL 1 + +typedef struct patchinfo_s +{ + int xsize, ysize; + struct { + int xtess, ytess; + } lods[PATCH_LODS_NUM]; +} patchinfo_t; + +// Calculate number of resulting vertex rows/columns by given patch size and tesselation factor +// When tess=0 it means that we reduce detalization of base 3x3 patches by removing middle row and column +// "DimForTess" is "DIMension FOR TESSelation factor" +int Q3PatchDimForTess(int size, int tess); + +// usage: +// to expand a 5x5 patch to 21x21 vertices (4x4 tesselation), one might use this call: +// Q3PatchSubdivideFloat(3, sizeof(float[3]), outvertices, 5, 5, sizeof(float[3]), patchvertices, 4, 4); +void Q3PatchTesselateFloat(int numcomponents, int outputstride, float *outputvertices, int patchwidth, int patchheight, int inputstride, float *patchvertices, int tesselationwidth, int tesselationheight); +// returns how much tesselation of each segment is needed to remain under tolerance +int Q3PatchTesselationOnX(int patchwidth, int patchheight, int components, const float *in, float tolerance); +// returns how much tesselation of each segment is needed to remain under tolerance +int Q3PatchTesselationOnY(int patchwidth, int patchheight, int components, const float *in, float tolerance); +// calculates elements for a grid of vertices +// (such as those produced by Q3PatchTesselate) +// (note: width and height are the actual vertex size, this produces +// (width-1)*(height-1)*2 triangles, 3 elements each) +void Q3PatchTriangleElements(int *elements, int width, int height, int firstvertex); + +int Q3PatchAdjustTesselation(int numcomponents, patchinfo_t *patch1, float *patchvertices1, patchinfo_t *patch2, float *patchvertices2); + +#endif + diff --git a/misc/source/darkplaces-src/cvar.c b/misc/source/darkplaces-src/cvar.c new file mode 100644 index 00000000..3dee0406 --- /dev/null +++ b/misc/source/darkplaces-src/cvar.c @@ -0,0 +1,1033 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cvar.c -- dynamic variable tracking + +#include "quakedef.h" + +const char *cvar_dummy_description = "custom cvar"; + +cvar_t *cvar_vars = NULL; +cvar_t *cvar_hashtable[CVAR_HASHSIZE]; +const char *cvar_null_string = ""; + +/* +============ +Cvar_FindVar +============ +*/ +cvar_t *Cvar_FindVar (const char *var_name) +{ + int hashindex; + cvar_t *var; + + // use hash lookup to minimize search time + hashindex = CRC_Block((const unsigned char *)var_name, strlen(var_name)) % CVAR_HASHSIZE; + for (var = cvar_hashtable[hashindex];var;var = var->nextonhashchain) + if (!strcmp (var_name, var->name)) + return var; + + return NULL; +} + +cvar_t *Cvar_FindVarAfter (const char *prev_var_name, int neededflags) +{ + cvar_t *var; + + if (*prev_var_name) + { + var = Cvar_FindVar (prev_var_name); + if (!var) + return NULL; + var = var->next; + } + else + var = cvar_vars; + + // search for the next cvar matching the needed flags + while (var) + { + if ((var->flags & neededflags) || !neededflags) + break; + var = var->next; + } + return var; +} + +cvar_t *Cvar_FindVarLink (const char *var_name, cvar_t **parent, cvar_t ***link, cvar_t **prev_alpha) +{ + int hashindex; + cvar_t *var; + + // use hash lookup to minimize search time + hashindex = CRC_Block((const unsigned char *)var_name, strlen(var_name)); + if(parent) *parent = NULL; + if(prev_alpha) *prev_alpha = NULL; + if(link) *link = &cvar_hashtable[hashindex]; + for (var = cvar_hashtable[hashindex];var;var = var->nextonhashchain) + { + if (!strcmp (var_name, var->name)) + { + if(!prev_alpha || var == cvar_vars) + return var; + + *prev_alpha = cvar_vars; + // if prev_alpha happens to become NULL then there has been some inconsistency elsewhere + // already - should I still insert '*prev_alpha &&' in the loop? + while((*prev_alpha)->next != var) + *prev_alpha = (*prev_alpha)->next; + return var; + } + if(parent) *parent = var; + } + + return NULL; +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValueOr (const char *var_name, float def) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return def; + return atof (var->string); +} + +float Cvar_VariableValue (const char *var_name) +{ + return Cvar_VariableValueOr(var_name, 0); +} + +/* +============ +Cvar_VariableString +============ +*/ +const char *Cvar_VariableStringOr (const char *var_name, const char *def) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return def; + return var->string; +} + +const char *Cvar_VariableString (const char *var_name) +{ + return Cvar_VariableStringOr(var_name, cvar_null_string); +} + +/* +============ +Cvar_VariableDefString +============ +*/ +const char *Cvar_VariableDefString (const char *var_name) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return cvar_null_string; + return var->defstring; +} + +/* +============ +Cvar_VariableDescription +============ +*/ +const char *Cvar_VariableDescription (const char *var_name) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return cvar_null_string; + return var->description; +} + + +/* +============ +Cvar_CompleteVariable +============ +*/ +const char *Cvar_CompleteVariable (const char *partial) +{ + cvar_t *cvar; + size_t len; + + len = strlen(partial); + + if (!len) + return NULL; + +// check functions + for (cvar=cvar_vars ; cvar ; cvar=cvar->next) + if (!strncasecmp (partial,cvar->name, len)) + return cvar->name; + + return NULL; +} + + +/* + CVar_CompleteCountPossible + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + +*/ +int Cvar_CompleteCountPossible (const char *partial) +{ + cvar_t *cvar; + size_t len; + int h; + + h = 0; + len = strlen(partial); + + if (!len) + return 0; + + // Loop through the cvars and count all possible matches + for (cvar = cvar_vars; cvar; cvar = cvar->next) + if (!strncasecmp(partial, cvar->name, len)) + h++; + + return h; +} + +/* + CVar_CompleteBuildList + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +const char **Cvar_CompleteBuildList (const char *partial) +{ + const cvar_t *cvar; + size_t len = 0; + size_t bpos = 0; + size_t sizeofbuf = (Cvar_CompleteCountPossible (partial) + 1) * sizeof (const char *); + const char **buf; + + len = strlen(partial); + buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); + // Loop through the alias list and print all matches + for (cvar = cvar_vars; cvar; cvar = cvar->next) + if (!strncasecmp(partial, cvar->name, len)) + buf[bpos++] = cvar->name; + + buf[bpos] = NULL; + return buf; +} + +// written by LordHavoc +void Cvar_CompleteCvarPrint (const char *partial) +{ + cvar_t *cvar; + size_t len = strlen(partial); + // Loop through the command list and print all matches + for (cvar = cvar_vars; cvar; cvar = cvar->next) + if (!strncasecmp(partial, cvar->name, len)) + Con_Printf ("^3%s^7 is \"%s\" [\"%s\"] %s\n", cvar->name, cvar->string, cvar->defstring, cvar->description); +} + +// we assume that prog is already set to the target progs +static void Cvar_UpdateAutoCvar(cvar_t *var) +{ + int i; + if(!prog) + Host_Error("Cvar_UpdateAutoCvar: no prog set"); + i = PRVM_GetProgNr(); + if(var->globaldefindex_progid[i] == prog->id) + { + // MUST BE SYNCED WITH prvm_edict.c PRVM_LoadProgs + int j; + const char *s; + vec3_t v; + switch(prog->globaldefs[var->globaldefindex[i]].type & ~DEF_SAVEGLOBAL) + { + case ev_float: + PRVM_GLOBALFIELDFLOAT(prog->globaldefs[var->globaldefindex[i]].ofs) = var->value; + break; + case ev_vector: + s = var->string; + VectorClear(v); + for (j = 0;j < 3;j++) + { + while (*s && ISWHITESPACE(*s)) + s++; + if (!*s) + break; + v[j] = atof(s); + while (!ISWHITESPACE(*s)) + s++; + if (!*s) + break; + } + VectorCopy(v, PRVM_GLOBALFIELDVECTOR(prog->globaldefs[var->globaldefindex[i]].ofs)); + break; + case ev_string: + PRVM_ChangeEngineString(var->globaldefindex_stringno[i], var->string); + PRVM_GLOBALFIELDSTRING(prog->globaldefs[var->globaldefindex[i]].ofs) = var->globaldefindex_stringno[i]; + break; + } + } +} + +// called after loading a savegame +void Cvar_UpdateAllAutoCvars(void) +{ + cvar_t *var; + for (var = cvar_vars ; var ; var = var->next) + Cvar_UpdateAutoCvar(var); +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_SetQuick_Internal (cvar_t *var, const char *value) +{ + qboolean changed; + size_t valuelen; + prvm_prog_t *tmpprog; + int i; + + changed = strcmp(var->string, value) != 0; + // LordHavoc: don't reallocate when there is no change + if (!changed) + return; + + // LordHavoc: don't reallocate when the buffer is the same size + valuelen = strlen(value); + if (!var->string || strlen(var->string) != valuelen) + { + Z_Free ((char *)var->string); // free the old value string + + var->string = (char *)Z_Malloc (valuelen + 1); + } + memcpy ((char *)var->string, value, valuelen + 1); + var->value = atof (var->string); + var->integer = (int) var->value; + if ((var->flags & CVAR_NOTIFY) && changed && sv.active) + SV_BroadcastPrintf("\"%s\" changed to \"%s\"\n", var->name, var->string); +#if 0 + // TODO: add infostring support to the server? + if ((var->flags & CVAR_SERVERINFO) && changed && sv.active) + { + InfoString_SetValue(svs.serverinfo, sizeof(svs.serverinfo), var->name, var->string); + if (sv.active) + { + MSG_WriteByte (&sv.reliable_datagram, svc_serverinfostring); + MSG_WriteString (&sv.reliable_datagram, var->name); + MSG_WriteString (&sv.reliable_datagram, var->string); + } + } +#endif + if ((var->flags & CVAR_USERINFO) && cls.state != ca_dedicated) + CL_SetInfo(var->name, var->string, true, false, false, false); + else if ((var->flags & CVAR_NQUSERINFOHACK) && cls.state != ca_dedicated) + { + // update the cls.userinfo to have proper values for the + // silly nq config variables. + // + // this is done when these variables are changed rather than at + // connect time because if the user or code checks the userinfo and it + // holds weird values it may cause confusion... + if (!strcmp(var->name, "_cl_color")) + { + int top = (var->integer >> 4) & 15, bottom = var->integer & 15; + CL_SetInfo("topcolor", va("%i", top), true, false, false, false); + CL_SetInfo("bottomcolor", va("%i", bottom), true, false, false, false); + if (cls.protocol != PROTOCOL_QUAKEWORLD && cls.netcon) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("color %i %i", top, bottom)); + } + } + else if (!strcmp(var->name, "_cl_rate")) + CL_SetInfo("rate", va("%i", var->integer), true, false, false, false); + else if (!strcmp(var->name, "_cl_playerskin")) + CL_SetInfo("playerskin", var->string, true, false, false, false); + else if (!strcmp(var->name, "_cl_playermodel")) + CL_SetInfo("playermodel", var->string, true, false, false, false); + else if (!strcmp(var->name, "_cl_name")) + CL_SetInfo("name", var->string, true, false, false, false); + else if (!strcmp(var->name, "rcon_secure")) + { + // whenever rcon_secure is changed to 0, clear rcon_password for + // security reasons (prevents a send-rcon-password-as-plaintext + // attack based on NQ protocol session takeover and svc_stufftext) + if(var->integer <= 0) + Cvar_Set("rcon_password", ""); + } + else if (!strcmp(var->name, "net_slist_favorites")) + NetConn_UpdateFavorites(); + } + + tmpprog = prog; + for(i = 0; i < PRVM_MAXPROGS; ++i) + { + if(PRVM_ProgLoaded(i)) + { + PRVM_SetProg(i); + Cvar_UpdateAutoCvar(var); + } + } + prog = tmpprog; +} + +void Cvar_SetQuick (cvar_t *var, const char *value) +{ + if (var == NULL) + { + Con_Print("Cvar_SetQuick: var == NULL\n"); + return; + } + + if (developer_extra.integer) + Con_DPrintf("Cvar_SetQuick({\"%s\", \"%s\", %i, \"%s\"}, \"%s\");\n", var->name, var->string, var->flags, var->defstring, value); + + Cvar_SetQuick_Internal(var, value); +} + +void Cvar_Set (const char *var_name, const char *value) +{ + cvar_t *var; + var = Cvar_FindVar (var_name); + if (var == NULL) + { + Con_Printf("Cvar_Set: variable %s not found\n", var_name); + return; + } + Cvar_SetQuick(var, value); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValueQuick(cvar_t *var, float value) +{ + char val[MAX_INPUTLINE]; + + if ((float)((int)value) == value) + dpsnprintf(val, sizeof(val), "%i", (int)value); + else + dpsnprintf(val, sizeof(val), "%f", value); + Cvar_SetQuick(var, val); +} + +void Cvar_SetValue(const char *var_name, float value) +{ + char val[MAX_INPUTLINE]; + + if ((float)((int)value) == value) + dpsnprintf(val, sizeof(val), "%i", (int)value); + else + dpsnprintf(val, sizeof(val), "%f", value); + Cvar_Set(var_name, val); +} + +/* +============ +Cvar_RegisterVariable + +Adds a freestanding variable to the variable list. +============ +*/ +void Cvar_RegisterVariable (cvar_t *variable) +{ + int hashindex; + cvar_t *current, *next, *cvar; + char *oldstr; + size_t alloclen; + + if (developer_extra.integer) + Con_DPrintf("Cvar_RegisterVariable({\"%s\", \"%s\", %i});\n", variable->name, variable->string, variable->flags); + +// first check to see if it has already been defined + cvar = Cvar_FindVar (variable->name); + if (cvar) + { + if (cvar->flags & CVAR_ALLOCATED) + { + if (developer_extra.integer) + Con_DPrintf("... replacing existing allocated cvar {\"%s\", \"%s\", %i}\n", cvar->name, cvar->string, cvar->flags); + // fixed variables replace allocated ones + // (because the engine directly accesses fixed variables) + // NOTE: this isn't actually used currently + // (all cvars are registered before config parsing) + variable->flags |= (cvar->flags & ~CVAR_ALLOCATED); + // cvar->string is now owned by variable instead + variable->string = cvar->string; + variable->defstring = cvar->defstring; + variable->value = atof (variable->string); + variable->integer = (int) variable->value; + // replace cvar with this one... + variable->next = cvar->next; + if (cvar_vars == cvar) + { + // head of the list is easy to change + cvar_vars = variable; + } + else + { + // otherwise find it somewhere in the list + for (current = cvar_vars;current->next != cvar;current = current->next) + ; + current->next = variable; + } + + // get rid of old allocated cvar + // (but not cvar->string and cvar->defstring, because we kept those) + Z_Free((char *)cvar->name); + Z_Free(cvar); + } + else + Con_DPrintf("Can't register variable %s, already defined\n", variable->name); + return; + } + +// check for overlap with a command + if (Cmd_Exists (variable->name)) + { + Con_Printf("Cvar_RegisterVariable: %s is a command\n", variable->name); + return; + } + +// copy the value off, because future sets will Z_Free it + oldstr = (char *)variable->string; + alloclen = strlen(variable->string) + 1; + variable->string = (char *)Z_Malloc (alloclen); + memcpy ((char *)variable->string, oldstr, alloclen); + variable->defstring = (char *)Z_Malloc (alloclen); + memcpy ((char *)variable->defstring, oldstr, alloclen); + variable->value = atof (variable->string); + variable->integer = (int) variable->value; + +// link the variable in +// alphanumerical order + for( current = NULL, next = cvar_vars ; next && strcmp( next->name, variable->name ) < 0 ; current = next, next = next->next ) + ; + if( current ) { + current->next = variable; + } else { + cvar_vars = variable; + } + variable->next = next; + + // link to head of list in this hash table index + hashindex = CRC_Block((const unsigned char *)variable->name, strlen(variable->name)) % CVAR_HASHSIZE; + variable->nextonhashchain = cvar_hashtable[hashindex]; + cvar_hashtable[hashindex] = variable; +} + +/* +============ +Cvar_Get + +Adds a newly allocated variable to the variable list or sets its value. +============ +*/ +cvar_t *Cvar_Get (const char *name, const char *value, int flags, const char *newdescription) +{ + int hashindex; + cvar_t *current, *next, *cvar; + + if (developer_extra.integer) + Con_DPrintf("Cvar_Get(\"%s\", \"%s\", %i);\n", name, value, flags); + +// first check to see if it has already been defined + cvar = Cvar_FindVar (name); + if (cvar) + { + cvar->flags |= flags; + Cvar_SetQuick_Internal (cvar, value); + if(newdescription && (cvar->flags & CVAR_ALLOCATED)) + { + if(cvar->description != cvar_dummy_description) + Z_Free((char *)cvar->description); + + if(*newdescription) + cvar->description = (char *)Mem_strdup(zonemempool, newdescription); + else + cvar->description = cvar_dummy_description; + } + return cvar; + } + +// check for pure evil + if (!*name) + { + Con_Printf("Cvar_Get: invalid variable name\n"); + return NULL; + } + +// check for overlap with a command + if (Cmd_Exists (name)) + { + Con_Printf("Cvar_Get: %s is a command\n", name); + return NULL; + } + +// allocate a new cvar, cvar name, and cvar string +// TODO: factorize the following code with the one at the end of Cvar_RegisterVariable() +// FIXME: these never get Z_Free'd + cvar = (cvar_t *)Z_Malloc(sizeof(cvar_t)); + cvar->flags = flags | CVAR_ALLOCATED; + cvar->name = (char *)Mem_strdup(zonemempool, name); + cvar->string = (char *)Mem_strdup(zonemempool, value); + cvar->defstring = (char *)Mem_strdup(zonemempool, value); + cvar->value = atof (cvar->string); + cvar->integer = (int) cvar->value; + + if(newdescription && *newdescription) + cvar->description = (char *)Mem_strdup(zonemempool, newdescription); + else + cvar->description = cvar_dummy_description; // actually checked by VM_cvar_type + +// link the variable in +// alphanumerical order + for( current = NULL, next = cvar_vars ; next && strcmp( next->name, cvar->name ) < 0 ; current = next, next = next->next ) + ; + if( current ) + current->next = cvar; + else + cvar_vars = cvar; + cvar->next = next; + + // link to head of list in this hash table index + hashindex = CRC_Block((const unsigned char *)cvar->name, strlen(cvar->name)) % CVAR_HASHSIZE; + cvar->nextonhashchain = cvar_hashtable[hashindex]; + cvar_hashtable[hashindex] = cvar; + + return cvar; +} + + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +qboolean Cvar_Command (void) +{ + cvar_t *v; + +// check variables + v = Cvar_FindVar (Cmd_Argv(0)); + if (!v) + return false; + +// perform a variable print or set + if (Cmd_Argc() == 1) + { + Con_Printf("\"%s\" is \"%s\" [\"%s\"]\n", v->name, ((v->flags & CVAR_PRIVATE) ? "********"/*hunter2*/ : v->string), v->defstring); + return true; + } + + if (developer_extra.integer) + Con_DPrint("Cvar_Command: "); + + if (v->flags & CVAR_READONLY) + { + Con_Printf("%s is read-only\n", v->name); + return true; + } + Cvar_Set (v->name, Cmd_Argv(1)); + if (developer_extra.integer) + Con_DPrint("\n"); + return true; +} + + +void Cvar_UnlockDefaults (void) +{ + cvar_t *var; + // unlock the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + var->flags &= ~CVAR_DEFAULTSET; +} + + +void Cvar_LockDefaults_f (void) +{ + cvar_t *var; + // lock in the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + { + if (!(var->flags & CVAR_DEFAULTSET)) + { + size_t alloclen; + + //Con_Printf("locking cvar %s (%s -> %s)\n", var->name, var->string, var->defstring); + var->flags |= CVAR_DEFAULTSET; + Z_Free((char *)var->defstring); + alloclen = strlen(var->string) + 1; + var->defstring = (char *)Z_Malloc(alloclen); + memcpy((char *)var->defstring, var->string, alloclen); + } + } +} + +void Cvar_SaveInitState(void) +{ + cvar_t *c; + for (c = cvar_vars;c;c = c->next) + { + c->initstate = true; + c->initflags = c->flags; + c->initdefstring = Mem_strdup(zonemempool, c->defstring); + c->initstring = Mem_strdup(zonemempool, c->string); + c->initvalue = c->value; + c->initinteger = c->integer; + VectorCopy(c->vector, c->initvector); + } +} + +void Cvar_RestoreInitState(void) +{ + int hashindex; + cvar_t *c, **cp; + cvar_t *c2, **cp2; + for (cp = &cvar_vars;(c = *cp);) + { + if (c->initstate) + { + // restore this cvar, it existed at init + if (((c->flags ^ c->initflags) & CVAR_MAXFLAGSVAL) + || strcmp(c->defstring ? c->defstring : "", c->initdefstring ? c->initdefstring : "") + || strcmp(c->string ? c->string : "", c->initstring ? c->initstring : "")) + { + Con_DPrintf("Cvar_RestoreInitState: Restoring cvar \"%s\"\n", c->name); + if (c->defstring) + Z_Free((char *)c->defstring); + c->defstring = Mem_strdup(zonemempool, c->initdefstring); + if (c->string) + Z_Free((char *)c->string); + c->string = Mem_strdup(zonemempool, c->initstring); + } + c->flags = c->initflags; + c->value = c->initvalue; + c->integer = c->initinteger; + VectorCopy(c->initvector, c->vector); + cp = &c->next; + } + else + { + if (!(c->flags & CVAR_ALLOCATED)) + { + Con_DPrintf("Cvar_RestoreInitState: Unable to destroy cvar \"%s\", it was registered after init!\n", c->name); + continue; + } + // remove this cvar, it did not exist at init + Con_DPrintf("Cvar_RestoreInitState: Destroying cvar \"%s\"\n", c->name); + // unlink struct from hash + hashindex = CRC_Block((const unsigned char *)c->name, strlen(c->name)) % CVAR_HASHSIZE; + for (cp2 = &cvar_hashtable[hashindex];(c2 = *cp2);) + { + if (c2 == c) + { + *cp2 = c2->nextonhashchain; + break; + } + else + cp2 = &c2->nextonhashchain; + } + // unlink struct from main list + *cp = c->next; + // free strings + if (c->defstring) + Z_Free((char *)c->defstring); + if (c->string) + Z_Free((char *)c->string); + if (c->description && c->description != cvar_dummy_description) + Z_Free((char *)c->description); + // free struct + Z_Free(c); + } + } +} + +void Cvar_ResetToDefaults_All_f (void) +{ + cvar_t *var; + // restore the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + if((var->flags & CVAR_NORESETTODEFAULTS) == 0) + Cvar_SetQuick(var, var->defstring); +} + + +void Cvar_ResetToDefaults_NoSaveOnly_f (void) +{ + cvar_t *var; + // restore the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + if ((var->flags & (CVAR_NORESETTODEFAULTS | CVAR_SAVE)) == 0) + Cvar_SetQuick(var, var->defstring); +} + + +void Cvar_ResetToDefaults_SaveOnly_f (void) +{ + cvar_t *var; + // restore the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + if ((var->flags & (CVAR_NORESETTODEFAULTS | CVAR_SAVE)) == CVAR_SAVE) + Cvar_SetQuick(var, var->defstring); +} + + +/* +============ +Cvar_WriteVariables + +Writes lines containing "set variable value" for all variables +with the archive flag set to true. +============ +*/ +void Cvar_WriteVariables (qfile_t *f) +{ + cvar_t *var; + char buf1[MAX_INPUTLINE], buf2[MAX_INPUTLINE]; + + // don't save cvars that match their default value + for (var = cvar_vars ; var ; var = var->next) + if ((var->flags & CVAR_SAVE) && (strcmp(var->string, var->defstring) || !(var->flags & CVAR_DEFAULTSET))) + { + Cmd_QuoteString(buf1, sizeof(buf1), var->name, "\"\\$", false); + Cmd_QuoteString(buf2, sizeof(buf2), var->string, "\"\\$", false); + FS_Printf(f, "%s\"%s\" \"%s\"\n", var->flags & CVAR_ALLOCATED ? "seta " : "", buf1, buf2); + } +} + + +// Added by EvilTypeGuy eviltypeguy@qeradiant.com +// 2000-01-09 CvarList command By Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ +/* +========= +Cvar_List +========= +*/ +void Cvar_List_f (void) +{ + cvar_t *cvar; + const char *partial; + size_t len; + int count; + qboolean ispattern; + + if (Cmd_Argc() > 1) + { + partial = Cmd_Argv (1); + len = strlen(partial); + } + else + { + partial = NULL; + len = 0; + } + + ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); + + count = 0; + for (cvar = cvar_vars; cvar; cvar = cvar->next) + { + if (len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp (partial,cvar->name,len))) + continue; + + Con_Printf("%s is \"%s\" [\"%s\"] %s\n", cvar->name, ((cvar->flags & CVAR_PRIVATE) ? "********"/*hunter2*/ : cvar->string), cvar->defstring, cvar->description); + count++; + } + + if (len) + { + if(ispattern) + Con_Printf("%i cvar%s matching \"%s\"\n", count, (count > 1) ? "s" : "", partial); + else + Con_Printf("%i cvar%s beginning with \"%s\"\n", count, (count > 1) ? "s" : "", partial); + } + else + Con_Printf("%i cvar(s)\n", count); +} +// 2000-01-09 CvarList command by Maddes + +void Cvar_Set_f (void) +{ + cvar_t *cvar; + + // make sure it's the right number of parameters + if (Cmd_Argc() < 3) + { + Con_Printf("Set: wrong number of parameters, usage: set []\n"); + return; + } + + // check if it's read-only + cvar = Cvar_FindVar(Cmd_Argv(1)); + if (cvar && cvar->flags & CVAR_READONLY) + { + Con_Printf("Set: %s is read-only\n", cvar->name); + return; + } + + if (developer_extra.integer) + Con_DPrint("Set: "); + + // all looks ok, create/modify the cvar + Cvar_Get(Cmd_Argv(1), Cmd_Argv(2), 0, Cmd_Argc() > 3 ? Cmd_Argv(3) : NULL); +} + +void Cvar_SetA_f (void) +{ + cvar_t *cvar; + + // make sure it's the right number of parameters + if (Cmd_Argc() < 3) + { + Con_Printf("SetA: wrong number of parameters, usage: seta []\n"); + return; + } + + // check if it's read-only + cvar = Cvar_FindVar(Cmd_Argv(1)); + if (cvar && cvar->flags & CVAR_READONLY) + { + Con_Printf("SetA: %s is read-only\n", cvar->name); + return; + } + + if (developer_extra.integer) + Con_DPrint("SetA: "); + + // all looks ok, create/modify the cvar + Cvar_Get(Cmd_Argv(1), Cmd_Argv(2), CVAR_SAVE, Cmd_Argc() > 3 ? Cmd_Argv(3) : NULL); +} + +void Cvar_Del_f (void) +{ + int i; + cvar_t *cvar, *parent, **link, *prev; + + if(Cmd_Argc() < 2) + { + Con_Printf("Del: wrong number of parameters, useage: unset [ ...]\n"); + return; + } + for(i = 1; i < Cmd_Argc(); ++i) + { + cvar = Cvar_FindVarLink(Cmd_Argv(i), &parent, &link, &prev); + if(!cvar) + { + Con_Printf("Del: %s is not defined\n", Cmd_Argv(i)); + continue; + } + if(cvar->flags & CVAR_READONLY) + { + Con_Printf("Del: %s is read-only\n", cvar->name); + continue; + } + if(!(cvar->flags & CVAR_ALLOCATED)) + { + Con_Printf("Del: %s is static and cannot be deleted\n", cvar->name); + continue; + } + if(cvar == cvar_vars) + { + cvar_vars = cvar->next; + } + else + { + // in this case, prev must be set, otherwise there has been some inconsistensy + // elsewhere already... should I still check for prev != NULL? + prev->next = cvar->next; + } + + if(parent) + parent->nextonhashchain = cvar->nextonhashchain; + else if(link) + *link = cvar->nextonhashchain; + + if(cvar->description != cvar_dummy_description) + Z_Free((char *)cvar->description); + + Z_Free((char *)cvar->name); + Z_Free((char *)cvar->string); + Z_Free((char *)cvar->defstring); + Z_Free(cvar); + } +} + +#ifdef FILLALLCVARSWITHRUBBISH +void Cvar_FillAll_f() +{ + char *buf, *p, *q; + int n, i; + cvar_t *var; + qboolean verify; + if(Cmd_Argc() != 2) + { + Con_Printf("Usage: %s length to plant rubbish\n", Cmd_Argv(0)); + Con_Printf("Usage: %s -length to verify that the rubbish is still there\n", Cmd_Argv(0)); + return; + } + n = atoi(Cmd_Argv(1)); + verify = (n < 0); + if(verify) + n = -n; + buf = Z_Malloc(n + 1); + buf[n] = 0; + for(var = cvar_vars; var; var = var->next) + { + for(i = 0, p = buf, q = var->name; i < n; ++i) + { + *p++ = *q++; + if(!*q) + q = var->name; + } + if(verify && strcmp(var->string, buf)) + { + Con_Printf("\n%s does not contain the right rubbish, either this is the first run or a possible overrun was detected, or something changed it intentionally; it DOES contain: %s\n", var->name, var->string); + } + Cvar_SetQuick(var, buf); + } + Z_Free(buf); +} +#endif /* FILLALLCVARSWITHRUBBISH */ diff --git a/misc/source/darkplaces-src/cvar.h b/misc/source/darkplaces-src/cvar.h new file mode 100644 index 00000000..4fd177d6 --- /dev/null +++ b/misc/source/darkplaces-src/cvar.h @@ -0,0 +1,245 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cvar.h + +/* + +cvar_t variables are used to hold scalar or string variables that can be changed or displayed at the console or prog code as well as accessed directly +in C code. + +it is sufficient to initialize a cvar_t with just the first two fields, or +you can add a ,true flag for variables that you want saved to the configuration +file when the game is quit: + +cvar_t r_draworder = {"r_draworder","1"}; +cvar_t scr_screensize = {"screensize","1",true}; + +Cvars must be registered before use, or they will have a 0 value instead of the float interpretation of the string. Generally, all cvar_t declarations should be registered in the apropriate init function before any console commands are executed: +Cvar_RegisterVariable (&host_framerate); + + +C code usually just references a cvar in place: +if ( r_draworder.value ) + +It could optionally ask for the value to be looked up for a string name: +if (Cvar_VariableValue ("r_draworder")) + +Interpreted prog code can access cvars with the cvar(name) or +cvar_set (name, value) internal functions: +teamplay = cvar("teamplay"); +cvar_set ("registered", "1"); + +The user can access cvars from the console in two ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. +*/ + +#ifndef CVAR_H +#define CVAR_H + +// cvar flags + +#define CVAR_SAVE 1 +#define CVAR_NOTIFY 2 +#define CVAR_READONLY 4 +#define CVAR_SERVERINFO 8 +#define CVAR_USERINFO 16 +// CVAR_PRIVATE means do not $ expand or sendcvar this cvar under any circumstances (rcon_password uses this) +#define CVAR_PRIVATE 32 +// this means that this cvar should update a userinfo key but the name does not correspond directly to the userinfo key to update, and may require additional conversion ("_cl_color" for example should update "topcolor" and "bottomcolor") +#define CVAR_NQUSERINFOHACK 64 +// used to determine if flags is valid +#define CVAR_NORESETTODEFAULTS 128 +// for engine-owned cvars that must not be reset on gametype switch (e.g. scr_screenshot_name, which otherwise isn't set to the mod name properly) +#define CVAR_MAXFLAGSVAL 255 +// for internal use only! +#define CVAR_DEFAULTSET (1<<30) +#define CVAR_ALLOCATED (1<<31) + +/* +// type of a cvar for menu purposes +#define CVARMENUTYPE_FLOAT 1 +#define CVARMENUTYPE_INTEGER 2 +#define CVARMENUTYPE_SLIDER 3 +#define CVARMENUTYPE_BOOL 4 +#define CVARMENUTYPE_STRING 5 +#define CVARMENUTYPE_OPTION 6 + +// which menu to put a cvar in +#define CVARMENU_GRAPHICS 1 +#define CVARMENU_SOUND 2 +#define CVARMENU_INPUT 3 +#define CVARMENU_NETWORK 4 +#define CVARMENU_SERVER 5 + +#define MAX_CVAROPTIONS 16 + +typedef struct cvaroption_s +{ + int value; + const char *name; +} +cvaroption_t; + +typedef struct menucvar_s +{ + int type; + float valuemin, valuemax, valuestep; + int numoptions; + cvaroption_t optionlist[MAX_CVAROPTIONS]; +} +menucvar_t; +*/ + +typedef struct cvar_s +{ + int flags; + + const char *name; + + const char *string; + const char *description; + int integer; + float value; + float vector[3]; + + const char *defstring; + + // values at init (for Cvar_RestoreInitState) + qboolean initstate; // indicates this existed at init + int initflags; + const char *initstring; + const char *initdescription; + int initinteger; + float initvalue; + float initvector[3]; + const char *initdefstring; + + unsigned int globaldefindex_progid[3]; + int globaldefindex[3]; + int globaldefindex_stringno[3]; + + //menucvar_t menuinfo; + struct cvar_s *next; + struct cvar_s *nextonhashchain; +} cvar_t; + +/* +void Cvar_MenuSlider(cvar_t *variable, int menu, float slider_min, float slider_max, float slider_step); +void Cvar_MenuBool(cvar_t *variable, int menu, const char *name_false, const char *name_true); +void Cvar_MenuFloat(cvar_t *variable, int menu, float range_min, float range_max); +void Cvar_MenuInteger(cvar_t *variable, int menu, int range_min, int range_max); +void Cvar_MenuString(cvar_t *variable, int menu); +void Cvar_MenuOption(cvar_t *variable, int menu, int value[16], const char *name[16]); +*/ + +/// registers a cvar that already has the name, string, and optionally the +/// archive elements set. +void Cvar_RegisterVariable (cvar_t *variable); + +/// equivelant to " " typed at the console +void Cvar_Set (const char *var_name, const char *value); + +/// expands value to a string and calls Cvar_Set +void Cvar_SetValue (const char *var_name, float value); + +void Cvar_SetQuick (cvar_t *var, const char *value); +void Cvar_SetValueQuick (cvar_t *var, float value); + +float Cvar_VariableValueOr (const char *var_name, float def); +// returns def if not defined + +float Cvar_VariableValue (const char *var_name); +// returns 0 if not defined or non numeric + +const char *Cvar_VariableStringOr (const char *var_name, const char *def); +// returns def if not defined + +const char *Cvar_VariableString (const char *var_name); +// returns an empty string if not defined + +const char *Cvar_VariableDefString (const char *var_name); +// returns an empty string if not defined + +const char *Cvar_VariableDescription (const char *var_name); +// returns an empty string if not defined + +const char *Cvar_CompleteVariable (const char *partial); +// attempts to match a partial variable name for command line completion +// returns NULL if nothing fits + +void Cvar_CompleteCvarPrint (const char *partial); + +qboolean Cvar_Command (void); +// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known +// command. Returns true if the command was a variable reference that +// was handled. (print or change) + +void Cvar_SaveInitState(void); +void Cvar_RestoreInitState(void); + +void Cvar_UnlockDefaults (void); +void Cvar_LockDefaults_f (void); +void Cvar_ResetToDefaults_All_f (void); +void Cvar_ResetToDefaults_NoSaveOnly_f (void); +void Cvar_ResetToDefaults_SaveOnly_f (void); + +void Cvar_WriteVariables (qfile_t *f); +// Writes lines containing "set variable value" for all variables +// with the archive flag set to true. + +cvar_t *Cvar_FindVar (const char *var_name); +cvar_t *Cvar_FindVarAfter (const char *prev_var_name, int neededflags); + +int Cvar_CompleteCountPossible (const char *partial); +const char **Cvar_CompleteBuildList (const char *partial); +// Added by EvilTypeGuy - functions for tab completion system +// Thanks to Fett erich@heintz.com +// Thanks to taniwha + +/// Prints a list of Cvars including a count of them to the user console +/// Referenced in cmd.c in Cmd_Init hence it's inclusion here. +/// Added by EvilTypeGuy eviltypeguy@qeradiant.com +/// Thanks to Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ +void Cvar_List_f (void); + +void Cvar_Set_f (void); +void Cvar_SetA_f (void); +void Cvar_Del_f (void); +// commands to create new cvars (or set existing ones) +// seta creates an archived cvar (saved to config) + +/// allocates a cvar by name and returns its address, +/// or merely sets its value if it already exists. +cvar_t *Cvar_Get (const char *name, const char *value, int flags, const char *newdescription); + +extern const char *cvar_dummy_description; // ALWAYS the same pointer +extern cvar_t *cvar_vars; // used to list all cvars + +void Cvar_UpdateAllAutoCvars(void); // updates ALL autocvars of the active prog to the cvar values (savegame loading) + +#ifdef FILLALLCVARSWITHRUBBISH +void Cvar_FillAll_f(); +#endif /* FILLALLCVARSWITHRUBBISH */ + +#endif + diff --git a/misc/source/darkplaces-src/darkplaces-dedicated.dev b/misc/source/darkplaces-src/darkplaces-dedicated.dev new file mode 100644 index 00000000..c6391082 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces-dedicated.dev @@ -0,0 +1,1699 @@ +[Project] +FileName=darkplaces-dedicated.dev +Name=DarkPlaces +UnitCount=165 +Type=1 +Ver=1 +ObjFiles= +Includes= +Libs= +PrivateResource=darkplaces-dedicated_private.rc +ResourceIncludes= +MakeIncludes= +Compiler=-Wall -O2 -fno-strict-aliasing -ffast-math -funroll-loops -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES_@@_ +CppCompiler= +Linker=-lwinmm -lws2_32_@@_ +IsCpp=0 +Icon=darkplaces.ico +ExeOutput= +ObjectOutput= +OverrideOutput=1 +OverrideOutputName=darkplaces-dedicated.exe +HostApplication= +Folders="Header Files","Source Files" +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=1 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000100 + +[Unit1] +FileName=dpvsimpledecode.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=cdaudio.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=cl_collision.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=cl_screen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=cl_video.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=client.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=clprogdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=cmd.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=collision.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=common.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=console.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=curves.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=cvar.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=fs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=gl_backend.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=polygon.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=glquake.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=image.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=input.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=jpeg.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=keys.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=lhnet.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=mathlib.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=matrixlib.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=menu.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=meshqueue.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=model_alias.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=model_brush.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=model_shared.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=model_sprite.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=model_zymotic.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=modelgen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=mprogdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=netconn.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=palette.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=portals.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=pr_comp.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=progdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=progs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=progsvm.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=protocol.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=prvm_execprogram.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=qtypes.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=quakedef.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit47] +FileName=r_lerpanim.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit48] +FileName=r_modules.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit49] +FileName=r_shadow.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit50] +FileName=r_textures.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit51] +FileName=render.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit52] +FileName=resource.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit53] +FileName=sbar.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit54] +FileName=screen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit55] +FileName=server.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit56] +FileName=sound.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit57] +FileName=spritegn.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit58] +FileName=sys.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit59] +FileName=vid.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit60] +FileName=wad.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit62] +FileName=zone.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit63] +FileName=zone.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit64] +FileName=cd_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit65] +FileName=cd_null.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit66] +FileName=cl_collision.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit67] +FileName=cl_demo.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit68] +FileName=cl_input.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit69] +FileName=cl_main.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit70] +FileName=cl_parse.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit71] +FileName=cl_particles.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit72] +FileName=cl_screen.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit73] +FileName=cl_video.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit74] +FileName=cmd.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit75] +FileName=collision.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit76] +FileName=common.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit77] +FileName=console.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit78] +FileName=polygon.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit79] +FileName=curves.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit80] +FileName=cvar.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit81] +FileName=dpvsimpledecode.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit82] +FileName=filematch.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit83] +FileName=fractalnoise.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit84] +FileName=fs.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit85] +FileName=gl_backend.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit86] +FileName=gl_draw.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit87] +FileName=gl_rmain.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit88] +FileName=gl_rsurf.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit89] +FileName=gl_textures.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit91] +FileName=host_cmd.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit92] +FileName=image.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit93] +FileName=jpeg.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit94] +FileName=keys.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit95] +FileName=lhnet.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit96] +FileName=mathlib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit97] +FileName=matrixlib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit98] +FileName=menu.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit99] +FileName=meshqueue.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit100] +FileName=model_alias.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit101] +FileName=model_brush.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit102] +FileName=model_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit103] +FileName=model_sprite.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit104] +FileName=netconn.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit105] +FileName=palette.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit106] +FileName=portals.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit107] +FileName=protocol.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit108] +FileName=prvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit109] +FileName=prvm_edict.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit110] +FileName=prvm_exec.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit111] +FileName=builddate.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit113] +FileName=r_lerpanim.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit114] +FileName=r_lightning.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit115] +FileName=r_modules.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit116] +FileName=r_shadow.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit118] +FileName=r_sprites.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit119] +FileName=sbar.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit120] +FileName=snd_null.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit121] +FileName=sv_main.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit122] +FileName=sv_move.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit123] +FileName=sv_phys.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit124] +FileName=sv_user.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit125] +FileName=sys_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit126] +FileName=sys_linux.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit127] +FileName=vid_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit128] +FileName=vid_null.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit133] +FileName=image_png.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit134] +FileName=lhfont.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit135] +FileName=mdfour.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit136] +FileName=model_dpmodel.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit137] +FileName=model_psk.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit139] +FileName=csprogs.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit140] +FileName=mdfour.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit141] +FileName=image_png.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit143] +FileName=wad.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit145] +FileName=cl_dyntexture.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit148] +FileName=cl_gecko.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit149] +FileName=clvm_cmds.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit150] +FileName=libcurl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit151] +FileName=libcurl.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit152] +FileName=sv_demo.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit153] +FileName=sv_demo.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit154] +FileName=svbsp.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit155] +FileName=svbsp.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit156] +FileName=timing.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit157] +FileName=hmac.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit112] +FileName=r_explosion.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=1 +Minor=0 +Release=0 +Build=0 +LanguageID=1033 +CharsetID=1252 +CompanyName=Forest Hale Digital Services +FileVersion=1.0 +FileDescription=DarkPlaces Game Engine +InternalName=darkplaces.exe +LegalCopyright=id Software, Forest Hale, and contributors +LegalTrademarks= +OriginalFilename=darkplaces.exe +ProductName=DarkPlaces +ProductVersion=1.0 +AutoIncBuildNr=0 + +[Unit90] +FileName=host.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit138] +FileName=clvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=bspfile.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=draw.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit61] +FileName=world.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit117] +FileName=r_sky.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit158] +FileName=hmac.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit159] +FileName=cap_avi.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit160] +FileName=cap_avi.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit161] +FileName=cap_ogg.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit162] +FileName=cap_ogg.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit163] +FileName=utf8lib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit164] +FileName=ft2.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit165] +FileName=bih.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit129] +FileName=svvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit130] +FileName=mvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit131] +FileName=prvm_cmds.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit132] +FileName=csprogs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit144] +FileName=world.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit146] +FileName=cl_dyntexture.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit147] +FileName=cl_gecko.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit142] +FileName=view.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/misc/source/darkplaces-src/darkplaces-dedicated.dsp b/misc/source/darkplaces-src/darkplaces-dedicated.dsp new file mode 100644 index 00000000..09e5d6a6 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces-dedicated.dsp @@ -0,0 +1,741 @@ +# Microsoft Developer Studio Project File - Name="darkplaces-dedicated" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=darkplaces-dedicated - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "darkplaces-dedicated.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "darkplaces-dedicated.mak" CFG="darkplaces-dedicated - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "darkplaces-dedicated - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "darkplaces-dedicated - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "darkplaces-dedicated - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release-Dedicated" +# PROP Intermediate_Dir "Release-Dedicated" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /Ox /Ot /Og /Oi /Op /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40c /d "NDEBUG" +# ADD RSC /l 0x40c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 ws2_32.lib winmm.lib user32.lib gdi32.lib /nologo /subsystem:console /LARGEADDRESSAWARE /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "darkplaces-dedicated - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug-Dedicated" +# PROP Intermediate_Dir "Debug-Dedicated" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40c /d "_DEBUG" +# ADD RSC /l 0x40c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ws2_32.lib winmm.lib user32.lib gdi32.lib /nologo /subsystem:console /LARGEADDRESSAWARE /debug /machine:I386 /out:"Debug-Dedicated/darkplaces-dedicated-debug.exe" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "darkplaces-dedicated - Win32 Release" +# Name "darkplaces-dedicated - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\builddate.c +# End Source File +# Begin Source File + +SOURCE=.\cd_null.c +# End Source File +# Begin Source File + +SOURCE=.\cd_shared.c +# End Source File +# Begin Source File + +SOURCE=.\cl_collision.c +# End Source File +# Begin Source File + +SOURCE=.\cl_demo.c +# End Source File +# Begin Source File + +SOURCE=.\cl_dyntexture.h +# End Source File +# Begin Source File + +SOURCE=.\cl_gecko.h +# End Source File +# Begin Source File + +SOURCE=.\cl_input.c +# End Source File +# Begin Source File + +SOURCE=.\cl_main.c +# End Source File +# Begin Source File + +SOURCE=.\cl_parse.c +# End Source File +# Begin Source File + +SOURCE=.\cl_particles.c +# End Source File +# Begin Source File + +SOURCE=.\cl_screen.c +# End Source File +# Begin Source File + +SOURCE=.\cl_video.c +# End Source File +# Begin Source File + +SOURCE=.\clvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\cmd.c +# End Source File +# Begin Source File + +SOURCE=.\collision.c +# End Source File +# Begin Source File + +SOURCE=.\common.c +# End Source File +# Begin Source File + +SOURCE=.\console.c +# End Source File +# Begin Source File + +SOURCE=.\csprogs.c +# End Source File +# Begin Source File + +SOURCE=.\curves.c +# End Source File +# Begin Source File + +SOURCE=.\cvar.c +# End Source File +# Begin Source File + +SOURCE=.\darkplaces.rc +# End Source File +# Begin Source File + +SOURCE=.\dpvsimpledecode.c +# End Source File +# Begin Source File + +SOURCE=.\filematch.c +# End Source File +# Begin Source File + +SOURCE=.\fractalnoise.c +# End Source File +# Begin Source File + +SOURCE=.\fs.c +# End Source File +# Begin Source File + +SOURCE=.\gl_backend.c +# End Source File +# Begin Source File + +SOURCE=.\gl_draw.c +# End Source File +# Begin Source File + +SOURCE=.\gl_rmain.c +# End Source File +# Begin Source File + +SOURCE=.\gl_rsurf.c +# End Source File +# Begin Source File + +SOURCE=.\gl_textures.c +# End Source File +# Begin Source File + +SOURCE=.\host.c +# End Source File +# Begin Source File + +SOURCE=.\host_cmd.c +# End Source File +# Begin Source File + +SOURCE=.\image.c +# End Source File +# Begin Source File + +SOURCE=.\image_png.c +# End Source File +# Begin Source File + +SOURCE=.\jpeg.c +# End Source File +# Begin Source File + +SOURCE=.\keys.c +# End Source File +# Begin Source File + +SOURCE=.\lhnet.c +# End Source File +# Begin Source File + +SOURCE=.\libcurl.c +# End Source File +# Begin Source File + +SOURCE=.\mathlib.c +# End Source File +# Begin Source File + +SOURCE=.\matrixlib.c +# End Source File +# Begin Source File + +SOURCE=.\mdfour.c +# End Source File +# Begin Source File + +SOURCE=.\menu.c +# End Source File +# Begin Source File + +SOURCE=.\meshqueue.c +# End Source File +# Begin Source File + +SOURCE=.\model_alias.c +# End Source File +# Begin Source File + +SOURCE=.\model_brush.c +# End Source File +# Begin Source File + +SOURCE=.\model_shared.c +# End Source File +# Begin Source File + +SOURCE=.\model_sprite.c +# End Source File +# Begin Source File + +SOURCE=.\mvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\netconn.c +# End Source File +# Begin Source File + +SOURCE=.\palette.c +# End Source File +# Begin Source File + +SOURCE=.\polygon.c +# End Source File +# Begin Source File + +SOURCE=.\portals.c +# End Source File +# Begin Source File + +SOURCE=.\protocol.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_edict.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_exec.c +# End Source File +# Begin Source File + +SOURCE=.\r_explosion.c +# End Source File +# Begin Source File + +SOURCE=.\r_lerpanim.c +# End Source File +# Begin Source File + +SOURCE=.\r_lightning.c +# End Source File +# Begin Source File + +SOURCE=.\r_modules.c +# End Source File +# Begin Source File + +SOURCE=.\r_shadow.c +# End Source File +# Begin Source File + +SOURCE=.\r_sky.c +# End Source File +# Begin Source File + +SOURCE=.\r_sprites.c +# End Source File +# Begin Source File + +SOURCE=.\sbar.c +# End Source File +# Begin Source File + +SOURCE=.\snd_null.c +# End Source File +# Begin Source File + +SOURCE=.\sv_demo.c +# End Source File +# Begin Source File + +SOURCE=.\sv_main.c +# End Source File +# Begin Source File + +SOURCE=.\sv_move.c +# End Source File +# Begin Source File + +SOURCE=.\sv_phys.c +# End Source File +# Begin Source File + +SOURCE=.\sv_user.c +# End Source File +# Begin Source File + +SOURCE=.\svbsp.c +# End Source File +# Begin Source File + +SOURCE=.\svvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\sys_linux.c +# End Source File +# Begin Source File + +SOURCE=.\sys_shared.c +# End Source File +# Begin Source File + +SOURCE=.\vid_null.c +# End Source File +# Begin Source File + +SOURCE=.\vid_shared.c +# End Source File +# Begin Source File + +SOURCE=.\view.c +# End Source File +# Begin Source File + +SOURCE=.\wad.c +# End Source File +# Begin Source File + +SOURCE=.\world.c +# End Source File +# Begin Source File + +SOURCE=.\zone.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\bspfile.h +# End Source File +# Begin Source File + +SOURCE=.\cdaudio.h +# End Source File +# Begin Source File + +SOURCE=.\cl_collision.h +# End Source File +# Begin Source File + +SOURCE=.\cl_dyntexture.h +# End Source File +# Begin Source File + +SOURCE=.\cl_gecko.h +# End Source File +# Begin Source File + +SOURCE=.\cl_screen.h +# End Source File +# Begin Source File + +SOURCE=.\cl_video.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\clprogdefs.h +# End Source File +# Begin Source File + +SOURCE=.\cmd.h +# End Source File +# Begin Source File + +SOURCE=.\collision.h +# End Source File +# Begin Source File + +SOURCE=.\common.h +# End Source File +# Begin Source File + +SOURCE=.\console.h +# End Source File +# Begin Source File + +SOURCE=.\csprogs.h +# End Source File +# Begin Source File + +SOURCE=.\curves.h +# End Source File +# Begin Source File + +SOURCE=.\cvar.h +# End Source File +# Begin Source File + +SOURCE=.\dpvsimpledecode.h +# End Source File +# Begin Source File + +SOURCE=.\draw.h +# End Source File +# Begin Source File + +SOURCE=.\fs.h +# End Source File +# Begin Source File + +SOURCE=.\gl_backend.h +# End Source File +# Begin Source File + +SOURCE=.\glquake.h +# End Source File +# Begin Source File + +SOURCE=.\image.h +# End Source File +# Begin Source File + +SOURCE=.\image_png.h +# End Source File +# Begin Source File + +SOURCE=.\input.h +# End Source File +# Begin Source File + +SOURCE=.\jpeg.h +# End Source File +# Begin Source File + +SOURCE=.\keys.h +# End Source File +# Begin Source File + +SOURCE=.\lhfont.h +# End Source File +# Begin Source File + +SOURCE=.\lhnet.h +# End Source File +# Begin Source File + +SOURCE=.\libcurl.h +# End Source File +# Begin Source File + +SOURCE=.\mathlib.h +# End Source File +# Begin Source File + +SOURCE=.\matrixlib.h +# End Source File +# Begin Source File + +SOURCE=.\mdfour.h +# End Source File +# Begin Source File + +SOURCE=.\menu.h +# End Source File +# Begin Source File + +SOURCE=.\meshqueue.h +# End Source File +# Begin Source File + +SOURCE=.\model_alias.h +# End Source File +# Begin Source File + +SOURCE=.\model_brush.h +# End Source File +# Begin Source File + +SOURCE=.\model_dpmodel.h +# End Source File +# Begin Source File + +SOURCE=.\model_psk.h +# End Source File +# Begin Source File + +SOURCE=.\model_shared.h +# End Source File +# Begin Source File + +SOURCE=.\model_sprite.h +# End Source File +# Begin Source File + +SOURCE=.\model_zymotic.h +# End Source File +# Begin Source File + +SOURCE=.\modelgen.h +# End Source File +# Begin Source File + +SOURCE=.\mprogdefs.h +# End Source File +# Begin Source File + +SOURCE=.\netconn.h +# End Source File +# Begin Source File + +SOURCE=.\palette.h +# End Source File +# Begin Source File + +SOURCE=.\polygon.h +# End Source File +# Begin Source File + +SOURCE=.\portals.h +# End Source File +# Begin Source File + +SOURCE=.\pr_comp.h +# End Source File +# Begin Source File + +SOURCE=.\pr_execprogram.h +# End Source File +# Begin Source File + +SOURCE=.\progdefs.h +# End Source File +# Begin Source File + +SOURCE=.\progs.h +# End Source File +# Begin Source File + +SOURCE=.\progsvm.h +# End Source File +# Begin Source File + +SOURCE=.\protocol.h +# End Source File +# Begin Source File + +SOURCE=.\prvm_cmds.h +# End Source File +# Begin Source File + +SOURCE=.\prvm_execprogram.h +# End Source File +# Begin Source File + +SOURCE=.\qtypes.h +# End Source File +# Begin Source File + +SOURCE=.\quakedef.h +# End Source File +# Begin Source File + +SOURCE=.\r_lerpanim.h +# End Source File +# Begin Source File + +SOURCE=.\r_modules.h +# End Source File +# Begin Source File + +SOURCE=.\r_shadow.h +# End Source File +# Begin Source File + +SOURCE=.\r_textures.h +# End Source File +# Begin Source File + +SOURCE=.\render.h +# End Source File +# Begin Source File + +SOURCE=.\sbar.h +# End Source File +# Begin Source File + +SOURCE=.\screen.h +# End Source File +# Begin Source File + +SOURCE=.\server.h +# End Source File +# Begin Source File + +SOURCE=.\snd_main.h +# End Source File +# Begin Source File + +SOURCE=.\sound.h +# End Source File +# Begin Source File + +SOURCE=.\spritegn.h +# End Source File +# Begin Source File + +SOURCE=.\sv_demo.h +# End Source File +# Begin Source File + +SOURCE=.\svbsp.h +# End Source File +# Begin Source File + +SOURCE=.\sys.h +# End Source File +# Begin Source File + +SOURCE=.\vid.h +# End Source File +# Begin Source File + +SOURCE=.\wad.h +# End Source File +# Begin Source File + +SOURCE=.\world.h +# End Source File +# Begin Source File + +SOURCE=.\zone.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\darkplaces.ico +# End Source File +# Begin Source File + +SOURCE=.\darkplaces.rc +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# End Group +# End Target +# End Project diff --git a/misc/source/darkplaces-src/darkplaces-dedicated.vcproj b/misc/source/darkplaces-src/darkplaces-dedicated.vcproj new file mode 100644 index 00000000..3910650f --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces-dedicated.vcproj @@ -0,0 +1,1095 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/source/darkplaces-src/darkplaces-sdl.dev b/misc/source/darkplaces-src/darkplaces-sdl.dev new file mode 100644 index 00000000..4ead20d9 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces-sdl.dev @@ -0,0 +1,1979 @@ +[Project] +FileName=darkplaces-sdl.dev +Name=DarkPlaces +UnitCount=183 +Type=0 +Ver=1 +ObjFiles= +Includes=C:\Dev-Cpp\SDL\include;C:\Dev-Cpp\SDL\include\SDL +Libs=C:\Dev-Cpp\SDL\lib +PrivateResource=darkplaces_private.rc +ResourceIncludes= +MakeIncludes= +Compiler=-Wall -O2 -fno-strict-aliasing -ffast-math -funroll-loops -D_GNU_SOURCE=1 -Dmain=SDL_main -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES_@@__@@_ +CppCompiler= +Linker=-lwinmm -lws2_32 -luser32 -lgdi32 -lcomctl32 -Wl,--large-address-aware -lmingw32 -lSDLmain -lSDL -mwindows_@@__@@_ +IsCpp=0 +Icon= +ExeOutput= +ObjectOutput= +OverrideOutput=1 +OverrideOutputName=darkplaces-sdl.exe +HostApplication= +Folders="Header Files","Source Files" +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=1 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000001100000000100 + +[Unit1] +FileName=dpvsimpledecode.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=cdaudio.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=cl_collision.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=cl_screen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=cl_video.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=client.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=clprogdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=cmd.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=collision.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=common.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=conproc.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=console.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=snd_main.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=curves.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=cvar.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=bspfile.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=draw.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=fs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=gl_backend.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=polygon.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=glquake.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=image.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=input.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=jpeg.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=keys.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=lhnet.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=mathlib.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=matrixlib.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=menu.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=meshqueue.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=model_alias.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=model_brush.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=model_shared.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=model_sprite.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=model_zymotic.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=modelgen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=mprogdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=netconn.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=palette.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=portals.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=pr_comp.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=progdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=progs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=progsvm.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=protocol.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=prvm_execprogram.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit47] +FileName=qtypes.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit48] +FileName=quakedef.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit49] +FileName=r_lerpanim.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit50] +FileName=r_modules.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit51] +FileName=r_shadow.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit52] +FileName=r_textures.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit53] +FileName=render.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit54] +FileName=resource.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit55] +FileName=sbar.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit56] +FileName=screen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit57] +FileName=server.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit58] +FileName=snd_ogg.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit59] +FileName=snd_wav.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit60] +FileName=sound.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit61] +FileName=spritegn.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit62] +FileName=sys.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit63] +FileName=vid.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit64] +FileName=wad.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit65] +FileName=world.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit66] +FileName=zone.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit67] +FileName=zone.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit68] +FileName=cd_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit70] +FileName=cl_demo.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit71] +FileName=cl_input.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit72] +FileName=cl_main.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit73] +FileName=cl_parse.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit74] +FileName=cl_particles.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit75] +FileName=cl_screen.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit76] +FileName=cl_video.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit77] +FileName=cmd.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit78] +FileName=collision.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit79] +FileName=common.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit80] +FileName=conproc.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit81] +FileName=console.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit82] +FileName=polygon.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit83] +FileName=curves.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit84] +FileName=cvar.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit85] +FileName=dpvsimpledecode.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit86] +FileName=filematch.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit87] +FileName=fractalnoise.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit88] +FileName=fs.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit89] +FileName=gl_backend.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit90] +FileName=gl_draw.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit91] +FileName=gl_rmain.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit92] +FileName=gl_rsurf.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit93] +FileName=gl_textures.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit94] +FileName=host.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit95] +FileName=host_cmd.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit96] +FileName=image.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit97] +FileName=jpeg.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit98] +FileName=keys.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit99] +FileName=lhnet.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit100] +FileName=mathlib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit101] +FileName=matrixlib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit102] +FileName=menu.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit103] +FileName=meshqueue.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit104] +FileName=model_alias.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit105] +FileName=model_brush.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit106] +FileName=model_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit107] +FileName=model_sprite.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit108] +FileName=netconn.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit109] +FileName=palette.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit110] +FileName=portals.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit111] +FileName=protocol.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit112] +FileName=prvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit113] +FileName=prvm_edict.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit114] +FileName=prvm_exec.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit115] +FileName=builddate.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit116] +FileName=r_explosion.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit118] +FileName=r_lightning.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit119] +FileName=r_modules.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit120] +FileName=r_shadow.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit121] +FileName=r_sky.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit123] +FileName=sbar.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit124] +FileName=snd_main.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit125] +FileName=snd_mem.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit126] +FileName=snd_mix.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit127] +FileName=snd_ogg.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit128] +FileName=snd_wav.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit130] +FileName=sv_move.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit131] +FileName=sv_phys.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit132] +FileName=sv_user.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit133] +FileName=sys_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit136] +FileName=wad.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit137] +FileName=world.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit138] +FileName=svvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit139] +FileName=mvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit140] +FileName=prvm_cmds.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit141] +FileName=csprogs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit142] +FileName=image_png.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit143] +FileName=lhfont.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit145] +FileName=model_dpmodel.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit146] +FileName=model_psk.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit147] +FileName=csprogs.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit148] +FileName=image_png.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit149] +FileName=mdfour.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit150] +FileName=libcurl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit151] +FileName=libcurl.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit152] +FileName=clvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit153] +FileName=svbsp.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit154] +FileName=svbsp.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit155] +FileName=sv_demo.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit156] +FileName=sv_demo.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit157] +FileName=snd_modplug.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit117] +FileName=r_lerpanim.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=1 +Minor=0 +Release=0 +Build=0 +LanguageID=1033 +CharsetID=1252 +CompanyName=Forest Hale Digital Services +FileVersion=1.0 +FileDescription=DarkPlaces Game Engine +InternalName=darkplaces.exe +LegalCopyright=id Software, Forest Hale, and contributors +LegalTrademarks= +OriginalFilename=darkplaces.exe +ProductName=DarkPlaces +ProductVersion=1.0 +AutoIncBuildNr=0 + +[Unit122] +FileName=r_sprites.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit144] +FileName=mdfour.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit158] +FileName=snd_modplug.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit159] +FileName=cl_gecko.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit160] +FileName=cl_gecko.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit161] +FileName=cl_dyntexture.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit162] +FileName=cl_dyntexture.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit163] +FileName=sys_sdl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit164] +FileName=vid_sdl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit165] +FileName=cd_sdl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit166] +FileName=snd_sdl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit69] +FileName=cl_collision.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit129] +FileName=sv_main.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit134] +FileName=vid_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit135] +FileName=view.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit167] +FileName=cap_ogg.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit168] +FileName=cap_ogg.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit169] +FileName=cap_avi.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit170] +FileName=cap_avi.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit175] +FileName=hmac.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit179] +FileName=ft2_fontdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit181] +FileName=utf8lib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit182] +FileName=bih.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit185] +FileName=timing.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit186] +FileName=vid_agl_mackeys.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit187] +FileName=vid_wgl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit188] +FileName=clvm_cmds.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit189] +FileName=darkplaces_private.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit190] +FileName=SDLMain.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit191] +FileName=snd_3dras.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit192] +FileName=snd_3dras_typedefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit193] +FileName=timing.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit194] +FileName=vid_agl_mackeys.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit174] +FileName=hmac.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit178] +FileName=ft2.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit180] +FileName=ft2.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit177] +FileName=ft2_defs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit172] +FileName=SDLMain.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit173] +FileName=timing.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit171] +FileName=clvm_cmds.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit176] +FileName=utf8lib.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit183] +FileName=bih.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/misc/source/darkplaces-src/darkplaces-sdl.dsp b/misc/source/darkplaces-src/darkplaces-sdl.dsp new file mode 100644 index 00000000..69425882 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces-sdl.dsp @@ -0,0 +1,777 @@ +# Microsoft Developer Studio Project File - Name="darkplaces-sdl" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=darkplaces-sdl - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "darkplaces-sdl.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "darkplaces-sdl.mak" CFG="darkplaces-sdl - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "darkplaces-sdl - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "darkplaces-sdl - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "darkplaces-sdl - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release-SDL" +# PROP Intermediate_Dir "Release-SDL" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /Ox /Ot /Og /Oi /Op /I "SDL/include" /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40c /d "NDEBUG" +# ADD RSC /l 0x40c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 ws2_32.lib winmm.lib sdl.lib sdlmain.lib user32.lib /nologo /subsystem:console /LARGEADDRESSAWARE /machine:I386 /libpath:"SDL/lib" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "darkplaces-sdl - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug-SDL" +# PROP Intermediate_Dir "Debug-SDL" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "SDL/include" /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40c /d "_DEBUG" +# ADD RSC /l 0x40c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ws2_32.lib winmm.lib sdl.lib sdlmain.lib user32.lib /nologo /subsystem:console /LARGEADDRESSAWARE /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"Debug-SDL/darkplaces-sdl-debug.exe" /pdbtype:sept /libpath:"SDL/lib" +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "darkplaces-sdl - Win32 Release" +# Name "darkplaces-sdl - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\builddate.c +# End Source File +# Begin Source File + +SOURCE=.\cd_sdl.c +# End Source File +# Begin Source File + +SOURCE=.\cd_shared.c +# End Source File +# Begin Source File + +SOURCE=.\cl_collision.c +# End Source File +# Begin Source File + +SOURCE=.\cl_demo.c +# End Source File +# Begin Source File + +SOURCE=.\cl_dyntexture.c +# End Source File +# Begin Source File + +SOURCE=.\cl_gecko.c +# End Source File +# Begin Source File + +SOURCE=.\cl_input.c +# End Source File +# Begin Source File + +SOURCE=.\cl_main.c +# End Source File +# Begin Source File + +SOURCE=.\cl_parse.c +# End Source File +# Begin Source File + +SOURCE=.\cl_particles.c +# End Source File +# Begin Source File + +SOURCE=.\cl_screen.c +# End Source File +# Begin Source File + +SOURCE=.\cl_video.c +# End Source File +# Begin Source File + +SOURCE=.\clvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\cmd.c +# End Source File +# Begin Source File + +SOURCE=.\collision.c +# End Source File +# Begin Source File + +SOURCE=.\common.c +# End Source File +# Begin Source File + +SOURCE=.\console.c +# End Source File +# Begin Source File + +SOURCE=.\csprogs.c +# End Source File +# Begin Source File + +SOURCE=.\curves.c +# End Source File +# Begin Source File + +SOURCE=.\cvar.c +# End Source File +# Begin Source File + +SOURCE=.\dpvsimpledecode.c +# End Source File +# Begin Source File + +SOURCE=.\filematch.c +# End Source File +# Begin Source File + +SOURCE=.\fractalnoise.c +# End Source File +# Begin Source File + +SOURCE=.\fs.c +# End Source File +# Begin Source File + +SOURCE=.\gl_backend.c +# End Source File +# Begin Source File + +SOURCE=.\gl_draw.c +# End Source File +# Begin Source File + +SOURCE=.\gl_rmain.c +# End Source File +# Begin Source File + +SOURCE=.\gl_rsurf.c +# End Source File +# Begin Source File + +SOURCE=.\gl_textures.c +# End Source File +# Begin Source File + +SOURCE=.\host.c +# End Source File +# Begin Source File + +SOURCE=.\host_cmd.c +# End Source File +# Begin Source File + +SOURCE=.\image.c +# End Source File +# Begin Source File + +SOURCE=.\image_png.c +# End Source File +# Begin Source File + +SOURCE=.\jpeg.c +# End Source File +# Begin Source File + +SOURCE=.\keys.c +# End Source File +# Begin Source File + +SOURCE=.\lhnet.c +# End Source File +# Begin Source File + +SOURCE=.\libcurl.c +# End Source File +# Begin Source File + +SOURCE=.\mathlib.c +# End Source File +# Begin Source File + +SOURCE=.\matrixlib.c +# End Source File +# Begin Source File + +SOURCE=.\mdfour.c +# End Source File +# Begin Source File + +SOURCE=.\menu.c +# End Source File +# Begin Source File + +SOURCE=.\meshqueue.c +# End Source File +# Begin Source File + +SOURCE=.\model_alias.c +# End Source File +# Begin Source File + +SOURCE=.\model_brush.c +# End Source File +# Begin Source File + +SOURCE=.\model_shared.c +# End Source File +# Begin Source File + +SOURCE=.\model_sprite.c +# End Source File +# Begin Source File + +SOURCE=.\mvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\netconn.c +# End Source File +# Begin Source File + +SOURCE=.\palette.c +# End Source File +# Begin Source File + +SOURCE=.\polygon.c +# End Source File +# Begin Source File + +SOURCE=.\portals.c +# End Source File +# Begin Source File + +SOURCE=.\protocol.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_edict.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_exec.c +# End Source File +# Begin Source File + +SOURCE=.\r_explosion.c +# End Source File +# Begin Source File + +SOURCE=.\r_lerpanim.c +# End Source File +# Begin Source File + +SOURCE=.\r_lightning.c +# End Source File +# Begin Source File + +SOURCE=.\r_modules.c +# End Source File +# Begin Source File + +SOURCE=.\r_shadow.c +# End Source File +# Begin Source File + +SOURCE=.\r_sky.c +# End Source File +# Begin Source File + +SOURCE=.\r_sprites.c +# End Source File +# Begin Source File + +SOURCE=.\sbar.c +# End Source File +# Begin Source File + +SOURCE=.\snd_main.c +# End Source File +# Begin Source File + +SOURCE=.\snd_mem.c +# End Source File +# Begin Source File + +SOURCE=.\snd_mix.c +# End Source File +# Begin Source File + +SOURCE=.\snd_modplug.c +# End Source File +# Begin Source File + +SOURCE=.\snd_ogg.c +# End Source File +# Begin Source File + +SOURCE=.\snd_sdl.c +# End Source File +# Begin Source File + +SOURCE=.\snd_wav.c +# End Source File +# Begin Source File + +SOURCE=.\sv_demo.c +# End Source File +# Begin Source File + +SOURCE=.\sv_main.c +# End Source File +# Begin Source File + +SOURCE=.\sv_move.c +# End Source File +# Begin Source File + +SOURCE=.\sv_phys.c +# End Source File +# Begin Source File + +SOURCE=.\sv_user.c +# End Source File +# Begin Source File + +SOURCE=.\svbsp.c +# End Source File +# Begin Source File + +SOURCE=.\svvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\sys_sdl.c +# End Source File +# Begin Source File + +SOURCE=.\sys_shared.c +# End Source File +# Begin Source File + +SOURCE=.\vid_sdl.c +# End Source File +# Begin Source File + +SOURCE=.\vid_shared.c +# End Source File +# Begin Source File + +SOURCE=.\view.c +# End Source File +# Begin Source File + +SOURCE=.\wad.c +# End Source File +# Begin Source File + +SOURCE=.\world.c +# End Source File +# Begin Source File + +SOURCE=.\zone.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\bspfile.h +# End Source File +# Begin Source File + +SOURCE=.\cdaudio.h +# End Source File +# Begin Source File + +SOURCE=.\cl_collision.h +# End Source File +# Begin Source File + +SOURCE=.\cl_dyntexture.h +# End Source File +# Begin Source File + +SOURCE=.\cl_gecko.h +# End Source File +# Begin Source File + +SOURCE=.\cl_screen.h +# End Source File +# Begin Source File + +SOURCE=.\cl_video.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\clprogdefs.h +# End Source File +# Begin Source File + +SOURCE=.\cmd.h +# End Source File +# Begin Source File + +SOURCE=.\collision.h +# End Source File +# Begin Source File + +SOURCE=.\common.h +# End Source File +# Begin Source File + +SOURCE=.\conproc.h +# End Source File +# Begin Source File + +SOURCE=.\console.h +# End Source File +# Begin Source File + +SOURCE=.\csprogs.h +# End Source File +# Begin Source File + +SOURCE=.\curves.h +# End Source File +# Begin Source File + +SOURCE=.\cvar.h +# End Source File +# Begin Source File + +SOURCE=.\dpvsimpledecode.h +# End Source File +# Begin Source File + +SOURCE=.\draw.h +# End Source File +# Begin Source File + +SOURCE=.\fs.h +# End Source File +# Begin Source File + +SOURCE=.\gl_backend.h +# End Source File +# Begin Source File + +SOURCE=.\glquake.h +# End Source File +# Begin Source File + +SOURCE=.\image.h +# End Source File +# Begin Source File + +SOURCE=.\image_png.h +# End Source File +# Begin Source File + +SOURCE=.\input.h +# End Source File +# Begin Source File + +SOURCE=.\jpeg.h +# End Source File +# Begin Source File + +SOURCE=.\keys.h +# End Source File +# Begin Source File + +SOURCE=.\lhfont.h +# End Source File +# Begin Source File + +SOURCE=.\lhnet.h +# End Source File +# Begin Source File + +SOURCE=.\libcurl.h +# End Source File +# Begin Source File + +SOURCE=.\mathlib.h +# End Source File +# Begin Source File + +SOURCE=.\matrixlib.h +# End Source File +# Begin Source File + +SOURCE=.\mdfour.h +# End Source File +# Begin Source File + +SOURCE=.\menu.h +# End Source File +# Begin Source File + +SOURCE=.\meshqueue.h +# End Source File +# Begin Source File + +SOURCE=.\model_alias.h +# End Source File +# Begin Source File + +SOURCE=.\model_brush.h +# End Source File +# Begin Source File + +SOURCE=.\model_dpmodel.h +# End Source File +# Begin Source File + +SOURCE=.\model_psk.h +# End Source File +# Begin Source File + +SOURCE=.\model_shared.h +# End Source File +# Begin Source File + +SOURCE=.\model_sprite.h +# End Source File +# Begin Source File + +SOURCE=.\model_zymotic.h +# End Source File +# Begin Source File + +SOURCE=.\modelgen.h +# End Source File +# Begin Source File + +SOURCE=.\mprogdefs.h +# End Source File +# Begin Source File + +SOURCE=.\netconn.h +# End Source File +# Begin Source File + +SOURCE=.\palette.h +# End Source File +# Begin Source File + +SOURCE=.\polygon.h +# End Source File +# Begin Source File + +SOURCE=.\portals.h +# End Source File +# Begin Source File + +SOURCE=.\pr_comp.h +# End Source File +# Begin Source File + +SOURCE=.\pr_execprogram.h +# End Source File +# Begin Source File + +SOURCE=.\progdefs.h +# End Source File +# Begin Source File + +SOURCE=.\progs.h +# End Source File +# Begin Source File + +SOURCE=.\progsvm.h +# End Source File +# Begin Source File + +SOURCE=.\protocol.h +# End Source File +# Begin Source File + +SOURCE=.\prvm_cmds.h +# End Source File +# Begin Source File + +SOURCE=.\prvm_execprogram.h +# End Source File +# Begin Source File + +SOURCE=.\qtypes.h +# End Source File +# Begin Source File + +SOURCE=.\quakedef.h +# End Source File +# Begin Source File + +SOURCE=.\r_lerpanim.h +# End Source File +# Begin Source File + +SOURCE=.\r_modules.h +# End Source File +# Begin Source File + +SOURCE=.\r_shadow.h +# End Source File +# Begin Source File + +SOURCE=.\r_textures.h +# End Source File +# Begin Source File + +SOURCE=.\render.h +# End Source File +# Begin Source File + +SOURCE=.\sbar.h +# End Source File +# Begin Source File + +SOURCE=.\screen.h +# End Source File +# Begin Source File + +SOURCE=.\server.h +# End Source File +# Begin Source File + +SOURCE=.\snd_main.h +# End Source File +# Begin Source File + +SOURCE=.\snd_modplug.h +# End Source File +# Begin Source File + +SOURCE=.\snd_ogg.h +# End Source File +# Begin Source File + +SOURCE=.\snd_wav.h +# End Source File +# Begin Source File + +SOURCE=.\sound.h +# End Source File +# Begin Source File + +SOURCE=.\spritegn.h +# End Source File +# Begin Source File + +SOURCE=.\sv_demo.h +# End Source File +# Begin Source File + +SOURCE=.\svbsp.h +# End Source File +# Begin Source File + +SOURCE=.\sys.h +# End Source File +# Begin Source File + +SOURCE=.\vid.h +# End Source File +# Begin Source File + +SOURCE=.\wad.h +# End Source File +# Begin Source File + +SOURCE=.\world.h +# End Source File +# Begin Source File + +SOURCE=.\zone.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\darkplaces.ico +# End Source File +# Begin Source File + +SOURCE=.\darkplaces.rc +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# End Group +# End Target +# End Project diff --git a/misc/source/darkplaces-src/darkplaces-sdl.vcproj b/misc/source/darkplaces-src/darkplaces-sdl.vcproj new file mode 100644 index 00000000..123c8384 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces-sdl.vcproj @@ -0,0 +1,1125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/source/darkplaces-src/darkplaces-wgl.vcproj b/misc/source/darkplaces-src/darkplaces-wgl.vcproj new file mode 100644 index 00000000..fd905ff2 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces-wgl.vcproj @@ -0,0 +1,1131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/source/darkplaces-src/darkplaces.dev b/misc/source/darkplaces-src/darkplaces.dev new file mode 100644 index 00000000..87ca69ea --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces.dev @@ -0,0 +1,1799 @@ +[Project] +FileName=darkplaces.dev +Name=DarkPlaces +UnitCount=175 +Type=0 +Ver=1 +ObjFiles= +Includes= +Libs= +PrivateResource=darkplaces_private.rc +ResourceIncludes= +MakeIncludes= +Compiler=-Wall -O2 -fno-strict-aliasing -ffast-math -funroll-loops -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES_@@_ +CppCompiler= +Linker=-lwinmm -lws2_32 -luser32 -lgdi32 -ldxguid -ldinput -lcomctl32 -Wl,--large-address-aware_@@_ +IsCpp=0 +Icon=darkplaces.ico +ExeOutput= +ObjectOutput= +OverrideOutput=1 +OverrideOutputName=darkplaces.exe +HostApplication= +Folders="Header Files","Source Files" +CommandLine= +UseCustomMakefile=0 +CustomMakefile= +IncludeVersionInfo=1 +SupportXPThemes=0 +CompilerSet=0 +CompilerSettings=0000000000000000000100 + +[Unit1] +FileName=dpvsimpledecode.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit2] +FileName=cdaudio.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit3] +FileName=cl_collision.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit4] +FileName=cl_screen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit5] +FileName=cl_video.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit6] +FileName=client.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit7] +FileName=clprogdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit8] +FileName=cmd.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit9] +FileName=collision.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit10] +FileName=common.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit11] +FileName=conproc.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit12] +FileName=console.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit13] +FileName=snd_main.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit14] +FileName=curves.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit15] +FileName=cvar.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit16] +FileName=bspfile.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit17] +FileName=draw.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit18] +FileName=fs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit19] +FileName=gl_backend.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit20] +FileName=polygon.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit21] +FileName=glquake.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit22] +FileName=image.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit23] +FileName=input.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit24] +FileName=jpeg.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit25] +FileName=keys.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit26] +FileName=lhnet.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit27] +FileName=mathlib.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit28] +FileName=matrixlib.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit29] +FileName=menu.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit30] +FileName=meshqueue.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit31] +FileName=model_alias.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit32] +FileName=model_brush.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit33] +FileName=model_shared.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit34] +FileName=model_sprite.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit35] +FileName=model_zymotic.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit36] +FileName=modelgen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit37] +FileName=mprogdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit38] +FileName=netconn.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit39] +FileName=palette.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit40] +FileName=portals.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit41] +FileName=pr_comp.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit42] +FileName=progdefs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit43] +FileName=progs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit44] +FileName=progsvm.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit45] +FileName=protocol.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit46] +FileName=prvm_execprogram.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit47] +FileName=qtypes.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit48] +FileName=quakedef.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit49] +FileName=r_lerpanim.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit50] +FileName=r_modules.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit51] +FileName=r_shadow.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit52] +FileName=r_textures.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit53] +FileName=render.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit54] +FileName=resource.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit55] +FileName=sbar.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit56] +FileName=screen.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit57] +FileName=server.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit58] +FileName=snd_ogg.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit59] +FileName=snd_wav.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit60] +FileName=sound.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit61] +FileName=spritegn.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit62] +FileName=sys.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit63] +FileName=vid.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit64] +FileName=wad.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit65] +FileName=world.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit66] +FileName=zone.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit67] +FileName=zone.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit68] +FileName=cd_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit69] +FileName=cd_win.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit70] +FileName=cl_collision.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit71] +FileName=cl_demo.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit72] +FileName=cl_input.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit73] +FileName=cl_main.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit74] +FileName=cl_parse.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit75] +FileName=cl_particles.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit76] +FileName=cl_screen.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit77] +FileName=cl_video.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit78] +FileName=cmd.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit79] +FileName=collision.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit80] +FileName=common.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit81] +FileName=conproc.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit82] +FileName=console.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit83] +FileName=polygon.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit84] +FileName=curves.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit85] +FileName=cvar.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit86] +FileName=dpvsimpledecode.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit87] +FileName=filematch.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit88] +FileName=fractalnoise.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit89] +FileName=fs.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit90] +FileName=gl_backend.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit91] +FileName=gl_draw.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit92] +FileName=gl_rmain.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit93] +FileName=gl_rsurf.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit94] +FileName=gl_textures.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit95] +FileName=host.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit96] +FileName=host_cmd.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit97] +FileName=image.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit98] +FileName=jpeg.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit99] +FileName=keys.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit100] +FileName=lhnet.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit101] +FileName=mathlib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit102] +FileName=matrixlib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit103] +FileName=menu.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit104] +FileName=meshqueue.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit105] +FileName=model_alias.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit106] +FileName=model_brush.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit107] +FileName=model_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit108] +FileName=model_sprite.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit109] +FileName=netconn.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit110] +FileName=palette.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit111] +FileName=portals.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit112] +FileName=protocol.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit113] +FileName=prvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit114] +FileName=prvm_edict.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit115] +FileName=prvm_exec.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit116] +FileName=builddate.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit118] +FileName=r_lerpanim.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit119] +FileName=r_lightning.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit120] +FileName=r_modules.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit121] +FileName=r_shadow.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit123] +FileName=r_sprites.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit124] +FileName=sbar.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit125] +FileName=snd_main.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit126] +FileName=snd_mem.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit127] +FileName=snd_mix.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit128] +FileName=snd_ogg.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit129] +FileName=snd_wav.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit130] +FileName=snd_win.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit131] +FileName=sv_main.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit132] +FileName=sv_move.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit133] +FileName=sv_phys.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit134] +FileName=sv_user.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit135] +FileName=sys_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit136] +FileName=sys_win.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit137] +FileName=vid_shared.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit138] +FileName=vid_wgl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit139] +FileName=view.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit140] +FileName=wad.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit141] +FileName=world.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit142] +FileName=svvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit143] +FileName=mvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit145] +FileName=csprogs.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit146] +FileName=image_png.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit147] +FileName=lhfont.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit148] +FileName=mdfour.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit149] +FileName=model_dpmodel.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit150] +FileName=model_psk.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit151] +FileName=csprogs.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit152] +FileName=image_png.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit153] +FileName=mdfour.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit154] +FileName=libcurl.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit155] +FileName=libcurl.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit156] +FileName=clvm_cmds.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit157] +FileName=svbsp.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit117] +FileName=r_explosion.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[VersionInfo] +Major=1 +Minor=0 +Release=0 +Build=0 +LanguageID=1033 +CharsetID=1252 +CompanyName=Forest Hale Digital Services +FileVersion=1.0 +FileDescription=DarkPlaces Game Engine +InternalName=darkplaces.exe +LegalCopyright=id Software, Forest Hale, and contributors +LegalTrademarks= +OriginalFilename=darkplaces.exe +ProductName=DarkPlaces +ProductVersion=1.0 +AutoIncBuildNr=0 + +[Unit122] +FileName=r_sky.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit144] +FileName=prvm_cmds.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit158] +FileName=svbsp.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit159] +FileName=sv_demo.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit160] +FileName=sv_demo.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit161] +FileName=snd_modplug.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit162] +FileName=snd_modplug.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit163] +FileName=cl_gecko.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit164] +FileName=cl_gecko.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit165] +FileName=cl_dyntexture.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit166] +FileName=cl_dyntexture.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit167] +FileName=hmac.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit168] +FileName=hmac.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit169] +FileName=cap_avi.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit170] +FileName=cap_avi.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit171] +FileName=cap_ogg.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit172] +FileName=cap_ogg.h +CompileCpp=0 +Folder=Header Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit173] +FileName=utf8lib.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit174] +FileName=ft2.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + +[Unit175] +FileName=bih.c +CompileCpp=0 +Folder=Source Files +Compile=1 +Link=1 +Priority=1000 +OverrideBuildCmd=0 +BuildCmd= + diff --git a/misc/source/darkplaces-src/darkplaces.dsp b/misc/source/darkplaces-src/darkplaces.dsp new file mode 100644 index 00000000..672e25cb --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces.dsp @@ -0,0 +1,781 @@ +# Microsoft Developer Studio Project File - Name="darkplaces" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=darkplaces - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "darkplaces.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "darkplaces.mak" CFG="darkplaces - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "darkplaces - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "darkplaces - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "darkplaces - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /Ox /Ot /Og /Oi /Op /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40c /d "NDEBUG" +# ADD RSC /l 0x40c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 ws2_32.lib winmm.lib user32.lib gdi32.lib comctl32.lib /nologo /subsystem:windows /LARGEADDRESSAWARE /machine:I386 +# SUBTRACT LINK32 /nodefaultlib + +!ELSEIF "$(CFG)" == "darkplaces - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40c /d "_DEBUG" +# ADD RSC /l 0x40c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 ws2_32.lib winmm.lib user32.lib gdi32.lib comctl32.lib /nologo /subsystem:windows /LARGEADDRESSAWARE /debug /machine:I386 /out:"Debug/darkplaces-debug.exe" /pdbtype:sept +# SUBTRACT LINK32 /nodefaultlib + +!ENDIF + +# Begin Target + +# Name "darkplaces - Win32 Release" +# Name "darkplaces - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\builddate.c +# End Source File +# Begin Source File + +SOURCE=.\cd_shared.c +# End Source File +# Begin Source File + +SOURCE=.\cd_win.c +# End Source File +# Begin Source File + +SOURCE=.\cl_collision.c +# End Source File +# Begin Source File + +SOURCE=.\cl_demo.c +# End Source File +# Begin Source File + +SOURCE=.\cl_dyntexture.c +# End Source File +# Begin Source File + +SOURCE=.\cl_gecko.c +# End Source File +# Begin Source File + +SOURCE=.\cl_input.c +# End Source File +# Begin Source File + +SOURCE=.\cl_main.c +# End Source File +# Begin Source File + +SOURCE=.\cl_parse.c +# End Source File +# Begin Source File + +SOURCE=.\cl_particles.c +# End Source File +# Begin Source File + +SOURCE=.\cl_screen.c +# End Source File +# Begin Source File + +SOURCE=.\cl_video.c +# End Source File +# Begin Source File + +SOURCE=.\clvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\cmd.c +# End Source File +# Begin Source File + +SOURCE=.\collision.c +# End Source File +# Begin Source File + +SOURCE=.\common.c +# End Source File +# Begin Source File + +SOURCE=.\conproc.c +# End Source File +# Begin Source File + +SOURCE=.\console.c +# End Source File +# Begin Source File + +SOURCE=.\csprogs.c +# End Source File +# Begin Source File + +SOURCE=.\curves.c +# End Source File +# Begin Source File + +SOURCE=.\cvar.c +# End Source File +# Begin Source File + +SOURCE=.\dpvsimpledecode.c +# End Source File +# Begin Source File + +SOURCE=.\filematch.c +# End Source File +# Begin Source File + +SOURCE=.\fractalnoise.c +# End Source File +# Begin Source File + +SOURCE=.\fs.c +# End Source File +# Begin Source File + +SOURCE=.\gl_backend.c +# End Source File +# Begin Source File + +SOURCE=.\gl_draw.c +# End Source File +# Begin Source File + +SOURCE=.\gl_rmain.c +# End Source File +# Begin Source File + +SOURCE=.\gl_rsurf.c +# End Source File +# Begin Source File + +SOURCE=.\gl_textures.c +# End Source File +# Begin Source File + +SOURCE=.\host.c +# End Source File +# Begin Source File + +SOURCE=.\host_cmd.c +# End Source File +# Begin Source File + +SOURCE=.\image.c +# End Source File +# Begin Source File + +SOURCE=.\image_png.c +# End Source File +# Begin Source File + +SOURCE=.\jpeg.c +# End Source File +# Begin Source File + +SOURCE=.\keys.c +# End Source File +# Begin Source File + +SOURCE=.\lhnet.c +# End Source File +# Begin Source File + +SOURCE=.\libcurl.c +# End Source File +# Begin Source File + +SOURCE=.\mathlib.c +# End Source File +# Begin Source File + +SOURCE=.\matrixlib.c +# End Source File +# Begin Source File + +SOURCE=.\mdfour.c +# End Source File +# Begin Source File + +SOURCE=.\menu.c +# End Source File +# Begin Source File + +SOURCE=.\meshqueue.c +# End Source File +# Begin Source File + +SOURCE=.\model_alias.c +# End Source File +# Begin Source File + +SOURCE=.\model_brush.c +# End Source File +# Begin Source File + +SOURCE=.\model_shared.c +# End Source File +# Begin Source File + +SOURCE=.\model_sprite.c +# End Source File +# Begin Source File + +SOURCE=.\mvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\netconn.c +# End Source File +# Begin Source File + +SOURCE=.\palette.c +# End Source File +# Begin Source File + +SOURCE=.\polygon.c +# End Source File +# Begin Source File + +SOURCE=.\portals.c +# End Source File +# Begin Source File + +SOURCE=.\protocol.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_edict.c +# End Source File +# Begin Source File + +SOURCE=.\prvm_exec.c +# End Source File +# Begin Source File + +SOURCE=.\r_explosion.c +# End Source File +# Begin Source File + +SOURCE=.\r_lerpanim.c +# End Source File +# Begin Source File + +SOURCE=.\r_lightning.c +# End Source File +# Begin Source File + +SOURCE=.\r_modules.c +# End Source File +# Begin Source File + +SOURCE=.\r_shadow.c +# End Source File +# Begin Source File + +SOURCE=.\r_sky.c +# End Source File +# Begin Source File + +SOURCE=.\r_sprites.c +# End Source File +# Begin Source File + +SOURCE=.\sbar.c +# End Source File +# Begin Source File + +SOURCE=.\snd_main.c +# End Source File +# Begin Source File + +SOURCE=.\snd_mem.c +# End Source File +# Begin Source File + +SOURCE=.\snd_mix.c +# End Source File +# Begin Source File + +SOURCE=.\snd_modplug.c +# End Source File +# Begin Source File + +SOURCE=.\snd_ogg.c +# End Source File +# Begin Source File + +SOURCE=.\snd_wav.c +# End Source File +# Begin Source File + +SOURCE=.\snd_win.c +# End Source File +# Begin Source File + +SOURCE=.\sv_demo.c +# End Source File +# Begin Source File + +SOURCE=.\sv_main.c +# End Source File +# Begin Source File + +SOURCE=.\sv_move.c +# End Source File +# Begin Source File + +SOURCE=.\sv_phys.c +# End Source File +# Begin Source File + +SOURCE=.\sv_user.c +# End Source File +# Begin Source File + +SOURCE=.\svbsp.c +# End Source File +# Begin Source File + +SOURCE=.\svvm_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\sys_shared.c +# End Source File +# Begin Source File + +SOURCE=.\sys_win.c +# End Source File +# Begin Source File + +SOURCE=.\vid_shared.c +# End Source File +# Begin Source File + +SOURCE=.\vid_wgl.c +# End Source File +# Begin Source File + +SOURCE=.\view.c +# End Source File +# Begin Source File + +SOURCE=.\wad.c +# End Source File +# Begin Source File + +SOURCE=.\world.c +# End Source File +# Begin Source File + +SOURCE=.\zone.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\bspfile.h +# End Source File +# Begin Source File + +SOURCE=.\cdaudio.h +# End Source File +# Begin Source File + +SOURCE=.\cl_collision.h +# End Source File +# Begin Source File + +SOURCE=.\cl_dyntexture.h +# End Source File +# Begin Source File + +SOURCE=.\cl_gecko.h +# End Source File +# Begin Source File + +SOURCE=.\cl_screen.h +# End Source File +# Begin Source File + +SOURCE=.\cl_video.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\clprogdefs.h +# End Source File +# Begin Source File + +SOURCE=.\cmd.h +# End Source File +# Begin Source File + +SOURCE=.\collision.h +# End Source File +# Begin Source File + +SOURCE=.\common.h +# End Source File +# Begin Source File + +SOURCE=.\conproc.h +# End Source File +# Begin Source File + +SOURCE=.\console.h +# End Source File +# Begin Source File + +SOURCE=.\csprogs.h +# End Source File +# Begin Source File + +SOURCE=.\curves.h +# End Source File +# Begin Source File + +SOURCE=.\cvar.h +# End Source File +# Begin Source File + +SOURCE=.\dpvsimpledecode.h +# End Source File +# Begin Source File + +SOURCE=.\draw.h +# End Source File +# Begin Source File + +SOURCE=.\fs.h +# End Source File +# Begin Source File + +SOURCE=.\gl_backend.h +# End Source File +# Begin Source File + +SOURCE=.\glquake.h +# End Source File +# Begin Source File + +SOURCE=.\image.h +# End Source File +# Begin Source File + +SOURCE=.\image_png.h +# End Source File +# Begin Source File + +SOURCE=.\input.h +# End Source File +# Begin Source File + +SOURCE=.\jpeg.h +# End Source File +# Begin Source File + +SOURCE=.\keys.h +# End Source File +# Begin Source File + +SOURCE=.\lhfont.h +# End Source File +# Begin Source File + +SOURCE=.\lhnet.h +# End Source File +# Begin Source File + +SOURCE=.\libcurl.h +# End Source File +# Begin Source File + +SOURCE=.\mathlib.h +# End Source File +# Begin Source File + +SOURCE=.\matrixlib.h +# End Source File +# Begin Source File + +SOURCE=.\mdfour.h +# End Source File +# Begin Source File + +SOURCE=.\menu.h +# End Source File +# Begin Source File + +SOURCE=.\meshqueue.h +# End Source File +# Begin Source File + +SOURCE=.\model_alias.h +# End Source File +# Begin Source File + +SOURCE=.\model_brush.h +# End Source File +# Begin Source File + +SOURCE=.\model_dpmodel.h +# End Source File +# Begin Source File + +SOURCE=.\model_psk.h +# End Source File +# Begin Source File + +SOURCE=.\model_shared.h +# End Source File +# Begin Source File + +SOURCE=.\model_sprite.h +# End Source File +# Begin Source File + +SOURCE=.\model_zymotic.h +# End Source File +# Begin Source File + +SOURCE=.\modelgen.h +# End Source File +# Begin Source File + +SOURCE=.\mprogdefs.h +# End Source File +# Begin Source File + +SOURCE=.\netconn.h +# End Source File +# Begin Source File + +SOURCE=.\palette.h +# End Source File +# Begin Source File + +SOURCE=.\polygon.h +# End Source File +# Begin Source File + +SOURCE=.\portals.h +# End Source File +# Begin Source File + +SOURCE=.\pr_comp.h +# End Source File +# Begin Source File + +SOURCE=.\pr_execprogram.h +# End Source File +# Begin Source File + +SOURCE=.\progdefs.h +# End Source File +# Begin Source File + +SOURCE=.\progs.h +# End Source File +# Begin Source File + +SOURCE=.\progsvm.h +# End Source File +# Begin Source File + +SOURCE=.\protocol.h +# End Source File +# Begin Source File + +SOURCE=.\prvm_cmds.h +# End Source File +# Begin Source File + +SOURCE=.\prvm_execprogram.h +# End Source File +# Begin Source File + +SOURCE=.\qtypes.h +# End Source File +# Begin Source File + +SOURCE=.\quakedef.h +# End Source File +# Begin Source File + +SOURCE=.\r_lerpanim.h +# End Source File +# Begin Source File + +SOURCE=.\r_modules.h +# End Source File +# Begin Source File + +SOURCE=.\r_shadow.h +# End Source File +# Begin Source File + +SOURCE=.\r_textures.h +# End Source File +# Begin Source File + +SOURCE=.\render.h +# End Source File +# Begin Source File + +SOURCE=.\sbar.h +# End Source File +# Begin Source File + +SOURCE=.\screen.h +# End Source File +# Begin Source File + +SOURCE=.\server.h +# End Source File +# Begin Source File + +SOURCE=.\snd_main.h +# End Source File +# Begin Source File + +SOURCE=.\snd_modplug.h +# End Source File +# Begin Source File + +SOURCE=.\snd_ogg.h +# End Source File +# Begin Source File + +SOURCE=.\snd_wav.h +# End Source File +# Begin Source File + +SOURCE=.\sound.h +# End Source File +# Begin Source File + +SOURCE=.\spritegn.h +# End Source File +# Begin Source File + +SOURCE=.\sv_demo.h +# End Source File +# Begin Source File + +SOURCE=.\svbsp.h +# End Source File +# Begin Source File + +SOURCE=.\sys.h +# End Source File +# Begin Source File + +SOURCE=.\vid.h +# End Source File +# Begin Source File + +SOURCE=.\wad.h +# End Source File +# Begin Source File + +SOURCE=.\world.h +# End Source File +# Begin Source File + +SOURCE=.\zone.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\darkplaces.ico +# End Source File +# Begin Source File + +SOURCE=.\darkplaces.rc +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# End Group +# End Target +# End Project diff --git a/misc/source/darkplaces-src/darkplaces.dsw b/misc/source/darkplaces-src/darkplaces.dsw new file mode 100644 index 00000000..4289b243 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces.dsw @@ -0,0 +1,53 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "darkplaces"=".\darkplaces.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "dedicated"=".\darkplaces-dedicated.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "sdl"=".\darkplaces-sdl.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/misc/source/darkplaces-src/darkplaces.ico b/misc/source/darkplaces-src/darkplaces.ico new file mode 100644 index 00000000..99ec6999 Binary files /dev/null and b/misc/source/darkplaces-src/darkplaces.ico differ diff --git a/misc/source/darkplaces-src/darkplaces.rc b/misc/source/darkplaces-src/darkplaces.rc new file mode 100644 index 00000000..fe2c6b9e --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces.rc @@ -0,0 +1,25 @@ +#include // include for version info constants + +A ICON MOVEABLE PURE LOADONCALL DISCARDABLE "darkplaces.ico" + +1 VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +FILETYPE VFT_APP +{ + BLOCK "StringFileInfo" + { + BLOCK "040904E4" + { + VALUE "CompanyName", "Forest Hale Digital Services" + VALUE "FileVersion", "1.0" + VALUE "FileDescription", "DarkPlaces Game Engine" + VALUE "InternalName", "darkplaces.exe" + VALUE "LegalCopyright", "id Software, Forest Hale, and contributors" + VALUE "LegalTrademarks", "" + VALUE "OriginalFilename", "darkplaces.exe" + VALUE "ProductName", "DarkPlaces" + VALUE "ProductVersion", "1.0" + } + } +} diff --git a/misc/source/darkplaces-src/darkplaces.sln b/misc/source/darkplaces-src/darkplaces.sln new file mode 100644 index 00000000..716aa52d --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-wgl", "darkplaces-wgl.vcproj", "{6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl", "darkplaces-sdl.vcproj", "{7470C8B3-FCA7-42D3-9909-5F9E735C7C51}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-dedicated", "darkplaces-dedicated.vcproj", "{389AE334-D907-4069-90B3-F0551B3EFDE9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.ActiveCfg = Debug|Win32 + {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.Build.0 = Debug|Win32 + {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.ActiveCfg = Debug|x64 + {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.Build.0 = Debug|x64 + {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.ActiveCfg = Release|Win32 + {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.Build.0 = Release|Win32 + {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.ActiveCfg = Release|x64 + {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.Build.0 = Release|x64 + {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.ActiveCfg = Debug|Win32 + {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.Build.0 = Debug|Win32 + {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.ActiveCfg = Debug|x64 + {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.Build.0 = Debug|x64 + {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.ActiveCfg = Release|Win32 + {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.Build.0 = Release|Win32 + {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.ActiveCfg = Release|x64 + {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.Build.0 = Release|x64 + {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.ActiveCfg = Debug|Win32 + {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.Build.0 = Debug|Win32 + {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.ActiveCfg = Debug|x64 + {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.Build.0 = Debug|x64 + {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.ActiveCfg = Release|Win32 + {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.Build.0 = Release|Win32 + {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.ActiveCfg = Release|x64 + {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/misc/source/darkplaces-src/darkplaces.txt b/misc/source/darkplaces-src/darkplaces.txt new file mode 100644 index 00000000..4e050738 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces.txt @@ -0,0 +1,1388 @@ +DarkPlaces engine readme : updated 20070311: + +About the DarkPlaces glquake engine: +DarkPlaces engine was started because I was unsatisfied with the other engines available soon after the quake source release (which did little more than add some flashy effects), and craved modding features for my DarkPlaces mod, and wanted some real enhancements to the online gaming experience as well. + +DarkPlaces engine is the result, I hope everyone likes it. + +I am not very good at writing documentation, so this readme is organized as a feature list, with information on each feature, I hope it is still adequate documentation. + +If you have any suggestions for features to document in detail in the readme or any other questions/comments/bugreports/suggestions/etc, send me an email with the address lordhavoc ghdigital com (add @ and . characters as appropriate) + + + + +Input Tips: +If mouse movement is jerky but framerate is high, try typing "gl_finish 1" (without quotes) into the console (makes cpu wait for gpu before ending frame, which gives lousy input drivers a chance to catch up). + + + +Graphics Tips: +Visit the Color Control submenu of Options, it's near the top, fiddle with gamma (or grey level if using the color levels mode) until the grey box surrounding the white/black dither pattern matches up with the grey you see while looking at the dither from a distance, this will calibrate quake to look approximately as id Software intended, and ensure everyone sees it the same. Note: Different resolutions may be different intensities depending on monitor. Note2: ATI Radeon Catalyst 3.10 drivers seem to have a weird gamma limiting 'feature' which rejects gamma settings it doesn't like, feel free to complain to ATI about this if it gets in your way (it probably will). + +Visit the Effects Options submenu of Options, and check out the options. + + + +Networking tips: +Visit the Player Setup submenu of the Multiplayer menu to configure your network speed (as well as the usual settings from quake like name and colors). + +To host a server behind a router/firewall, simply set up a port forward on the UDP port you are running the server on (default is 26000), to forward incoming UDP packets on that port to the server, then people can connect. + +To make your server show up on the server browser (in the Join Game menu), either set sv_public 1 in the console, or use the multiplayer new game menu and check the Public server checkbox. + + + +Supported games: +Quake : -quake, this is active by default, gamedirs: id1 +Quake: Scourge of Armagon : -hipnotic or hipnotic in executable name or path, gamedirs: hipnotic, id1 +Quake: Dissolution of Eternity : -rogue or rogue in executable name or path, gamedirs: rogue, id1 +Nexuiz : -nexuiz or nexuiz in executable name or path, gamedirs: data +Nehahra : -nehahra or nehahra in executable name or path, gamedirs: nehahra, id1 +GoodVsBad2 : -goodvsbad2 or gvb2 in executable name or path, gamedirs: rts +BattleMech : -battlemech or battlemech in executable name or path, gamedirs: base +PrydonGate : -prydon or prydon in executable name or path, gamedirs: prydon +These games are considered officially supported, if any problems are seen, please make sure you are running the latest version of the game and engine, if you are, please report the problem. + + + +Graphics features: +Redesigned effects including smoke, blood, bubbles and explosions. +Better looking dynamic lights. +External texture support (see Replacement Content section below) +Realtime bumpmapped lighting/shadowing support (r_shadow_realtime_world cvar) with many options. (note: very slow if you do not have .rtlights files installed, be sure to get some from dpmod or search on the web for rtlights files) +.rtlights file support (improves performance/appearance of realtime lighting) +.rtlights file editing (see r_editlights_help in game) +Alpha blended sprites (instead of glquake's masked sprites). +Interpolated entity movement and animations (both models and sprites). +Overbright and fullbright support on walls and models (like winquake). +Colormapping support on any q1 model (like winquake). +Fog (set with "fog density red green blue" command) +Skybox (loadsky "mtnsun_" will load "env/mtnsun_ft.tga" and so on). +Sky rendering improved (no more glquake distortion). +Sky polygons obscure geometry just like in winquake. +Color calibration menu to ensure a proper Quake experience. +Improved model lighting (directional shading). +No messy .ms2 model mesh files (no glquake dir anymore either). +New improved crosshair (team color coded). +Improved image loading (smoother menus and such). +Ability to disable particle effects (cl_particles* cvars). +Decals (cl_decals cvar to enable). +Stainmaps (cl_stainmap cvar to enable). +Sorted transparent stuff to render better. +Improved multitexture support (r_textureunits (1-4 supported), needs gl_combine 1 because of overbright) +Improved chase cam (chase_active 1 no longer goes into walls) +More configurable console background (scr_conalpha and scr_conbrightness) +Optional fullbrights (r_fullbrights 0/1 followed by r_restart) +Dynamic Farclip (no distance limits in huge maps) +Improved gl_flashblend (now renders a corona instead of an ugly blob) +DynamicLight coronas (more realism) +Transparent statusbar (sbar_alpha) that does not block your view as much. +No 8bit texture uploads (fixes 'green' walls in the distance). +Fixed view blends (glquake was quite broken). +JPEG texture support using libjpeg (Thanks Elric) +Video Options, Color Control, and Effects Options menus added, and more options. +.dlit file support (produced by hmap2 -light) for fast per-pixel lighting without shadowing. +pointfile command is improved (for leak finding in maps when you have a .pnt file from a failed qbsp compile) +configurable particle effects (effectinfo.txt, can be reloaded at any time by cl_particles_reloadeffects command for quick testing) +fixed envmap command (makes a skybox of the current scene) + + + +Sound features: +Ogg and wav file overrides for cd tracks (example: sound/cdtracks/track01.ogg or .wav) (Thanks Elric) +Streaming ogg sounds to save memory (Ogg sounds over a certain size are streamed automatically) (Thanks Elric) +Ogg Vorbis sound support (all .wav sounds look for .ogg if the .wav is missing, useful for making mods smaller, particularly useful for cd tracks) (Thanks Elric) +Stereo sound file support (useful for cd tracks) +7.1 surround sound mixing support (snd_channels cvar selects how many to use, default 2 for stereo) + + + +Client features: +showtime cvar. +showdate cvar. +-benchmark option to run automated timedemo benchmarks (-benchmark demo1 does +timedemo demo1 and quits immediately when finished) +timedemo automatically puts results in gamedir/benchmark.log +Slightly improved aiming on quake servers (does not support proquake aiming). +-sndspeed samplerate (default: 44100, quake used 11025) +snd_swapstereo cvar (for people with backwards SB16 sound cards) +Saves video settings to config and restores them properly +Ability to change video settings during game (video options menu or vid_* cvars) +showfps cvar. +Sends 20fps network packets to improve modem play instead of one per frame. (sys_ticrate controls network framerate) +Allow skin colormaps 14 and 15 (freaky :) +Longer chat messages. +No more 72fps limit, cl_maxfps lets you decide. +Support for more mouse buttons (mouse1-mouse16, mwheelup/mwheeldown are aliases to mouse4 and mouse5). +Server browser for public (sv_public 1) darkplaces servers as well as quakeworld servers. +log_file cvar to log console messages to a file. +condump command to dump recent console history to a file. +PK3 archive support with compression support using zlib (Thanks Elric) +maps command lists installed maps +tab completion of map names on map/changelevel commands +tab completion of rcon commands +.ent file replacement allows you to modify sky and fog settings on a per-map basis (use sv_saveentfile command in singleplayer and then edit the worldspawn entity in a text editor, for example setting "sky" "mtnsun_" to load the skybox mtnsun_ in your favorite maps, or "fog" "0.03 0.2 0.2 0.2" to put fog in the level) +Switchable bindmaps (in_bind command allows you to bind keys in one of 8 bindmaps, 0-7, in_bindmap command allows you to select two active bindmaps to use at once, ones missing in the first are checked in the second) +Options menu "Reset to Defaults" option works better than in Quake +cvarlist and cmdlist commands +improved tab completion of commands and cvars, with default value and description listed +curl command (downloads a URL to a pk3 archive and loads it) +fs_rescan command (allows you to load a newly installed pak/pk3 archive without quitting the game) +saveconfig command (allows you to save settings before quitting the game, mostly useful when debugging the engine if you expect it to crash) +gamedir command to change current mod (only works while disconnected). +QuakeWorld support. +ProQuake message macros (%l location, %d last death location, %h health, %a armor, %x rockets, %c cells, %t current time, %r rocket launcher status (I need RL, I need rockets, I have RL), %p powerup status (quad pent ring), %w weapon status (SSG:NG:SNG:GL:RL:LG). +Support for ProQuake .loc files (locs/e1m1.loc or maps/e1m1.loc) +Support for QIZMO .loc files (maps/e1m1.loc) +Ingame editing of .loc files using locs_* commands (locs_save saves a new .loc file to maps directory) +bestweapon command (takes a number sequence like bestweapon 87654321, digits corresponding to weapons, first ones are preferred over last ones, only uses weapons that have 1 ammo or more) +ls and dir commands to list files in the Quake virtual filesystem (useful to find files inside paks) +toggle command allows you to use a single bind to toggle a cvar between 0 and 1 +slowmo cvar allows you to pause/slow down/speed up demo playback (try using these binds for example: bind f1 "slowmo 0";bind f2 "slowmo 0.1";bind f3 "slowmo 1") +AVI video recording using builtin I420 codec (bind f4 "toggle cl_capturevideo"), with automatic creation of sequentially numbered avi files for each recording session. (WARNING: HUGE files, make sure you have several gigabytes of disk space available! and it is only recommended during demo playback! You will probably want to reencode these videos using VirtualDub, mencoder, or other utilities before posting them on a website) +ping display in scoreboard, even on Quake servers (on DarkPlaces servers it also shows packet loss) + + + +Server features: +Allows clients to connect through firewalls (automatic feature) +Works behind firewalls unlike NetQuake (must port forward UDP packets on the relevant port from the firewall to the server, as with any game) +More accurate movement and precise aiming. +255 player support. +sv_cheats cvar controls cheats (no longer based on deathmatch). +slowmo cvar controls game speed. +No crash with the buggy 'teleport train' in shub's pit. +Allow skin colormaps 14 and 15 (freaky :) +sys_ticrate applies to listen (client) servers as well as dedicated. +sv_public cvar to advertise to master server. +log_file cvar to log console messages to a file. +condump command to dump recent console history to a file. +PK3 archive support with compression support using zlib (Thanks Elric) +Option to prevent wallhacks from working (sv_cullentities_trace 1). +Selectable protocol (sv_protocolname QUAKE for example allows quake clients to play, default is sv_protocolname DP7 or a later protocol) +Automatic file downloads to DarkPlaces clients. +Ability to send URLs to DarkPlaces clients to download pk3 archives needed to play on this server. +rcon support for remote administration by trusted clients with matching rcon_password, or external quakeworld rcon tools +prvm_edictset, prvm_global, prvm_globals, and prvm_globalset commands aid in QuakeC debugging +prvm_printfunction function prints out the QuakeC assembly opcodes of a function, can be useful if you can't decompile the progs.dat file +prvm_profile command gives call count and estimated builtin-function cost +sys_colortranslation cvar controls processing of Quake3-style ^ color codes in terminal output, default is white text on windows and ANSI color output on Linux/Mac OSX +sys_specialcharactertranslation controls processing of special Quake characters to make colored names more readable in terminal output + + + +Modding features: +HalfLife map support (place your HalfLife wads in quake/id1/textures/ or quake/MODDIR/textures/ as the maps need them) +Larger q1 and hl map size of +-32768 units. +Colored lighting (.lit support) for q1 maps. +Q3 map support (no shaders though), with no limits. +Q2 and Q3 model support, with greatly increased limits (256 skins, 65536 frames, 65536 vertices, 65536 triangles). (Note: md2 player models are not supported because they have no skin list) +Optimized QuakeC interpreter so mods run faster. +Bounds checking QuakeC interpreter so mods can't do naughty things with memory. +Warnings for many common QuakeC errors. +Unprecached models are now a warning (does not kill the server anymore). +External texture support (see dpextensions.qc DP_GFX_EXTERNALTEXTURES). +Fog ("fog" key in worldspawn, same parameters as fog command). +.spr32 and halflife .spr sprites supported. (Use Krimzon's tool to make spr32, and lhfire can render directly to spr32, or just use replacement textures on .spr). +Skybox ("sky" key in worldspawn, works like loadsky and quake2). +Stereo wav sounds supported. +Ogg Vorbis sounds supported. (Thanks Elric) +ATTN_NONE sounds are no longer directional (good for music). +play2 sound testing command (ATTN_NONE variant of play). +r_texturestats and memstats and memlist commands to give memory use info. +Lighting on sprites (put ! anywhere in sprite filename to enable). +More r_speeds info (now a transparent overlay instead of spewing to console). +Supports rotating bmodels (use avelocity, and keep in mind the bmodel needs the "origin" key set to rotate (read up on hipnotic rotation support in your qbsp docs, or choose another qbsp if yours does not support this feature), or in q3 maps an origin brush works). +More sound channels. +More dynamic lights (32 changed to 256). +More precached models and sounds (256 changed to 4096). +Many more features documented in dpextensions.qc. (bullet tracing on models, qc player input, etc) + + + +Replacing Content: +Formats supported: tga (recommended), png (loads very slowly), jpg (loads slowly), pcx, wal, lmp + +Usually you want to put replacement content in either id1/ or another directory such as pretty/ inside your quake directory, in DarkPlaces you can run multiple -game options at once (such as -game ctf -game pretty -game dpmod to have texture overrides from pretty, maps from ctf, and gameplay from dpmod) or multiple gamedirs specified with the gamedir console command (gamedir ctf pretty dpmod). + +All texture layers are optional except diffuse (the others are NOT loaded without it) + +Replacing skins: +progs/player.mdl_0.tga - diffuse +progs/player.mdl_0_norm.tga - normalmap (can have alpha channel with bumpmap height for offsetmapping/reliefmapping) +progs/player.mdl_0_bump.tga - bumpmap (not loaded if normalmap is present) +progs/player.mdl_0_glow.tga - glow map (use _luma if tenebrae compatibility is a concern) +progs/player.mdl_0_luma.tga - alternate tenebrae-compatible name for glow map (use one or the other) +progs/player.mdl_0_pants.tga - pants image, greyscale and does not cover the same pixels as the diffuse texture (this is additive blended ('Screen' mode in photoshop) ontop of the diffuse texture with a color tint according to your pants color) +progs/player.mdl_0_shirt.tga - shirt image, same type as pants + +Replacing textures in specific maps: +textures/e1m1/ecop1_6.tga +textures/e1m1/ecop1_6_norm.tga +textures/e1m1/ecop1_6_bump.tga +textures/e1m1/ecop1_6_glow.tga +textures/e1m1/ecop1_6_luma.tga +textures/e1m1/ecop1_6_pants.tga - pants and shirt layers are possible on bmodel entities with quakec modifications to set their .colormap field +textures/e1m1/ecop1_6_shirt.tga + +Replacing textures in all maps: +textures/quake.tga +textures/quake_norm.tga +textures/quake_bump.tga +textures/quake_glow.tga +textures/quake_luma.tga +textures/quake_pants.tga +textures/quake_shirt.tga + +Replacing hud and menu pictures: +gfx/conchars.tga + +Replacing models: +same as in Quake, you can replace a model with exactly the same file name (including file extension), so for example an md3 player model has to be renamed progs/player.mdl, and a small box of shells in md3 format has to be renamed maps/b_shell0.bsp + +How to make .skin files for multiple skins on a Quake3 (md3) or DarkPlacesModel (dpm) model: +These files use the same format as the ones in Quake3 (except being named modelname_0.skin, modelname_1.skin, and so on), they specify what texture to use on each part of the md3 (or zym or dpm or psk) model, their contents look like the following... +torso,progs/player_default.tga says that the model part named "torso" should use the image progs/player_default.tga +gun,progs/player_default.tga says that the model part named "gun" should use the image progs/player_default.tga +muzzleflash,progs/player_default_muzzleflash.tga says that the model part named "muzzleflash" should use the image progs/player_default_muzzleflash.tga - this is useful for transparent skin areas which should be kept separate from opaque skins +tag_head, says that the first tag is named "tag_head" - this is only useful for QuakeC mods using segmented player models so that they can look up/down without their legs rotating, don't worry about it as a user +tag_torso, second tag name +tag_weapon, third tag name + +How to install a soundtrack in ogg format +These files must be in ogg or wav format, and numbers begin at 002 if you wish to replace (or install) the Quake cd music - since track 001 was the Quake data track.

+quake/id1/sound/cdtracks/track002.ogg replacement track for "cd loop 2" +quake/id1/sound/cdtracks/track003.ogg replacement track for "cd loop 3" + +Example list of filenames: +quake/id1/progs/player.mdl replaces the player model) +quake/id1/progs/player.mdl_0.skin text file that specifies textures to use on an md3 model) +quake/id1/progs/player_default.tga texture referenced by the .skin, make sure that any special parts of this are black, like pants should be black here otherwise you get pink pants when you use a red color ingame) +quake/id1/progs/player_default_pants.tga white pants area of the skin, this is colored by the engine according to your color settings, additive blended (which is called "Screen" mode in Photoshop if you wish to preview the layers)) +quake/id1/progs/player_default_shirt.tga white shirt area of the skin, similar to pants described above) +quake/id1/progs/player_default_norm.tga normalmap texture for player_default, alpha channel can contain a heightmap for offsetmapping (r_glsl_offsetmapping 1 in console) to use, alternatively you can use _bump.tga instead of this which is only a heightmap and the engine will generate the normalmap for you) +quake/id1/progs/player_default_gloss.tga glossmap (shiny areas) for player_default) +quake/id1/progs/player_default_glow.tga glowmap (glowing stuff) for player_default, this is fullbrights and such, be sure the corresponding pixels are black in the player_default.tga, because just like pants/shirt this is additive blended) +quake/id1/textures/quake.tga replaces the quake logo on the arch in start.bsp) +quake/id1/textures/quake_norm.tga same as for a player) +quake/id1/textures/quake_gloss.tga same as for a player) +quake/id1/textures/#water1.tga replaces *water1 texture in the maps, # is used instead of * in filenames) +quake/id1/gfx/conchars.tga replacement font image, this was in gfx.wad in quake) +quake/id1/gfx/conback.tga replacement console background, just like in quake) +quake/id1/gfx/mainmenu.tga replacement main menu image, just like in quake) +quake/id1/maps/b_bh25.bsp replacement for normal health pack, for example this could be an md3 model instead) +quake/id1/sound/cdtracks/track002.ogg replacement track for "cd loop 2" +quake/id1/sound/cdtracks/track003.ogg replacement track for "cd loop 3" + + + +Commandline options as of 2007-03-11: +BSD GLX: -gl_driver drivername selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it +BSD GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) +BSD GLX: -novideosync disables GLX_SGI_swap_control +BSD Sound: -cddev devicepath chooses which CD drive to use +Client: -benchmark demoname runs a timedemo and quits, results of any timedemo can be found in gamedir/benchmark.log (for example id1/benchmark.log) +Client: -demo demoname runs a playdemo and quits +Client: -forceqmenu disables menu.dat (same as +forceqmenu 1) +Client: -particles number changes maximum number of particles at once, default 32768 +Client: -texbrightness number sets the quake palette brightness (brightness of black), allowing you to make quake textures brighter/darker, not recommended +Client: -texcontrast number sets the quake palette contrast, allowing you to make quake textures brighter/darker, not recommended +Client: -texgamma number sets the quake palette gamma, allowing you to make quake textures brighter/darker, not recommended +Client: -useqmenu causes the first time you open the menu to use the quake menu, then reverts to menu.dat (if forceqmenu is 0) +Console: -condebug logs console messages to qconsole.log, see also log_file +Console: -developer enables warnings and other notices (RECOMMENDED for mod developers) +Console: -nostdout disables text output to the terminal the game was launched from +Filesystem: -basedir path chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1) +GL: -noanisotropy disables GL_EXT_texture_filter_anisotropic (allows higher quality texturing) +GL: -nocombine disables GL_ARB_texture_env_combine or GL_EXT_texture_env_combine (required for bumpmapping and faster map rendering) +GL: -nocubemap disables GL_ARB_texture_cube_map (required for bumpmapping) +GL: -nocva disables GL_EXT_compiled_vertex_array (renders faster) +GL: -nodot3 disables GL_ARB_texture_env_dot3 (required for bumpmapping) +GL: -nodrawrangeelements disables GL_EXT_draw_range_elements (renders faster) +GL: -noedgeclamp disables GL_EXT_texture_edge_clamp or GL_SGIS_texture_edge_clamp (recommended, some cards do not support the other texture clamp method) +GL: -nofragmentshader disables GL_ARB_fragment_shader (allows pixel shader effects, can improve per pixel lighting performance and capabilities) +GL: -nomtex disables GL_ARB_multitexture (required for faster map rendering) +GL: -noseparatestencil disables use of OpenGL2.0 glStencilOpSeparate and GL_ATI_separate_stencil extensions (which accelerate shadow rendering) +GL: -noshaderobjects disables GL_ARB_shader_objects (required for vertex shader and fragment shader) +GL: -noshadinglanguage100 disables GL_ARB_shading_language_100 (required for vertex shader and fragment shader) +GL: -nostenciltwoside disables GL_EXT_stencil_two_side (which accelerate shadow rendering) +GL: -notexture3d disables GL_EXT_texture3D (required for spherical lights, otherwise they render as a column) +GL: -novertexshader disables GL_ARB_vertex_shader (allows vertex shader effects) +Game: -battlemech runs the multiplayer topdown deathmatch game BattleMech +Game: -contagiontheory runs the game Contagion Theory +Game: -darsana runs the game Darsana +Game: -did2 runs the game Defeat In Detail 2 +Game: -goodvsbad2 runs the psychadelic RTS FPS game Good Vs Bad 2 +Game: -hipnotic runs Quake mission pack 1: The Scourge of Armagon +Game: -nehahra runs The Seal of Nehahra movie and game +Game: -neoteric runs the game Neoteric +Game: -netherworld runs the game Netherworld: Dark Master +Game: -nexuiz runs the multiplayer game Nexuiz +Game: -openquartz runs the game OpenQuartz, a standalone GPL replacement of the quake content +Game: -prydon runs the topdown point and click action-RPG Prydon Gate +Game: -quake runs the game Quake (default) +Game: -rogue runs Quake mission pack 2: The Dissolution of Eternity +Game: -setheral runs the multiplayer game Setheral +Game: -som runs the multiplayer game Son Of Man +Game: -tenebrae runs the graphics test mod known as Tenebrae (some features not implemented) +Game: -teu runs The Evil Unleashed (this option is obsolete as they are not using darkplaces) +Game: -thehunted runs the game The Hunted +Game: -transfusion runs Transfusion (the recreation of Blood in Quake) +Game: -zymotic runs the singleplayer game Zymotic +Input: -nomouse disables mouse support (see also vid_mouse cvar) +Input: -nomouse disables mouse support (see also vid_mouse cvar) +Linux ALSA Sound: -sndpcm devicename selects which pcm device to us, default is "default" +Linux GLX: -gl_driver drivername selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it +Linux GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) +Linux GLX: -novideosync disables GLX_SGI_swap_control +Linux Sound: -cddev devicepath chooses which CD drive to use +MacOSX GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) +MacOSX GLX: -novideosync disables GLX_SGI_swap_control +SDL GL: -gl_driver drivername selects a GL driver library, default is whatever SDL recommends, useful only for 3dfxogl.dll/3dfxvgl.dll or fxmesa or similar, if you don't know what this is for, you don't need it +Server: -dedicated [playerlimit] starts a dedicated server (with a command console), default playerlimit is 8 +Server: -ip ipaddress sets the ip address of this machine for purposes of networking (default 0.0.0.0 also known as INADDR_ANY), use only if you have multiple network adapters and need to choose one specifically. +Server: -listen [playerlimit] starts a multiplayer server with graphical client, like singleplayer but other players can connect, default playerlimit is 8 +Server: -port portnumber sets the port to use for a server (default 26000, the same port as QUAKE itself), useful if you host multiple servers on your machine +Sound: -nocdaudio disables CD audio support +Sound: -nosound disables sound (including CD audio) +Sound: -novorbis disables ogg vorbis sound support +Sound: -simsound runs sound mixing but with no output +Sound: -sndbits bits chooses 8 bit or 16 bit sound output +Sound: -sndmono sets sound output to mono +Sound: -sndquad sets sound output to 4 channel surround +Sound: -sndspeed hz chooses sound output rate (supported values are 48000, 44100, 32000, 24000, 22050, 16000, 11025 (quake), 8000) +Sound: -sndstereo sets sound output to stereo +Video: -bpp bits performs +vid_bitsperpixel bits (example -bpp 32 or -bpp 16) +Video: -fullscreen performs +vid_fullscreen 1 +Video: -height pixels performs +vid_height pixels and also +vid_width pixels*4/3 if only -height is specified (example: -height 768 sets 1024x768 mode) +Video: -width pixels performs +vid_width pixels and also +vid_height pixels*3/4 if only -width is specified (example: -width 1024 sets 1024x768 mode) +Video: -window performs +vid_fullscreen 0 +Windows DirectSound: -primarysound locks the sound hardware for exclusive use +Windows DirectSound: -snoforceformat uses the format that DirectSound returns, rather than forcing it +Windows GDI Input: -noforcemaccel disables setting of mouse acceleration (not used with -dinput, windows only) +Windows GDI Input: -noforcemparms disables setting of mouse parameters (not used with -dinput, windows only) +Windows GDI Input: -noforcemspd disables setting of mouse speed (not used with -dinput, windows only) +Windows Input: -dinput enables DirectInput for mouse/joystick input +Windows Input: -nojoy disables joystick support, may be a small speed increase +Windows Sound: -wavonly uses wave sound instead of DirectSound +Windows WGL: -gl_driver drivername selects a GL driver library, default is opengl32.dll, useful only for 3dfxogl.dll or 3dfxvgl.dll, if you don't know what this is for, you don't need it +Windows WGL: -novideosync disables WGL_EXT_swap_control + + + +Full Console Variable List as of 2007-03-11: +_cl_color 0 internal storage cvar for current player colors (changed by color command) +_cl_name player internal storage cvar for current player name (changed by name command) +_cl_playermodel internal storage cvar for current player model in Nexuiz (changed by playermodel command) +_cl_playerskin internal storage cvar for current player skin in Nexuiz (changed by playerskin command) +_cl_pmodel 0 internal storage cvar for current player model number in nehahra (changed by pmodel command) +_cl_rate 10000 internal storage cvar for current rate (changed by rate command) +_snd_mixahead 0.1 how much sound to mix ahead of time +ambient_fade 100 rate of volume fading when moving from one environment to another +ambient_level 0.3 volume of environment noises (water and wind) +bgmvolume 1 volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg) +cdaudioinitialized 0 indicates if CD Audio system is active +chase_active 0 enables chase cam +chase_back 48 chase cam distance from the player +chase_stevie 0 chase cam view from above (used only by GoodVsBad2) +chase_up 24 chase cam distance from the player +cl_anglespeedkey 1.5 how much +speed multiplies keyboard turning speed +cl_autodemo 0 records every game played, using the date/time and map name to name the demo file +cl_autodemo_nameformat %Y-%m-%d_%H-%M The format of the cl_autodemo filename, followed by the map name +cl_backspeed 400 backward movement speed +cl_beams_instantaimhack 1 makes your lightning gun aiming update instantly +cl_beams_lightatend 0 make a light at the end of the beam +cl_beams_polygons 1 use beam polygons instead of models +cl_beams_quakepositionhack 1 makes your lightning gun appear to fire from your waist (as in Quake and QuakeWorld) +cl_bob 0.02 view bobbing amount +cl_bobcycle 0.6 view bobbing speed +cl_bob2 0 sideways view bobbing amount +cl_bob2cycle 0.6 sideways view bobbing speed +cl_bob2smooth 0.05 how fast the view goes back when you stop touching the ground +cl_bobfall 0 how much the view swings down when falling (influenced by the speed you hit the ground with) +cl_bobfallcycle 3 speed of the bobfall swing +cl_bobfallspeed 200 necessary amount of speed for bob-falling to occur +cl_bobmodel 1 enables gun bobbing +cl_bobmodel_side 0.05 gun bobbing sideways sway amount +cl_bobmodel_speed 7 gun bobbing speed +cl_bobmodel_up 0.02 gun bobbing upward movement amount +cl_leanmodel 0 enables gun leaning +cl_leanmodel_side_speed 0.7 gun leaning sideways speed +cl_leanmodel_side_limit 35 gun leaning sideways limit +cl_leanmodel_side_highpass1 30 gun leaning sideways pre-highpass in 1/s +cl_leanmodel_side_highpass 3 gun leaning sideways highpass in 1/s +cl_leanmodel_side_lowpass 20 gun leaning sideways lowpass in 1/s +cl_leanmodel_up_speed 0.65 gun leaning upward speed +cl_leanmodel_up_limit 50 gun leaning upward limit +cl_leanmodel_up_highpass1 5 gun leaning upward pre-highpass in 1/s +cl_leanmodel_up_highpass 15 gun leaning upward highpass in 1/s +cl_leanmodel_up_lowpass 20 gun leaning upward lowpass in 1/s +cl_followmodel 0 enables gun following +cl_followmodel_side_speed 0.25 gun following sideways speed +cl_followmodel_side_limit 6 gun following sideways limit +cl_followmodel_side_highpass1 30 gun following sideways pre-highpass in 1/s +cl_followmodel_side_highpass 5 gun following sideways highpass in 1/s +cl_followmodel_side_lowpass 10 gun following sideways lowpass in 1/s +cl_followmodel_up_speed 0.5 gun following upward speed +cl_followmodel_up_limit 5 gun following upward limit +cl_followmodel_up_highpass1 60 gun following upward pre-highpass in 1/s +cl_followmodel_up_highpass 2 gun following upward highpass in 1/s +cl_followmodel_up_lowpass 10 gun following upward lowpass in 1/s +cl_bobup 0.5 view bobbing adjustment that makes the up or down swing of the bob last longer +cl_capturevideo 0 enables saving of video to a .avi file using uncompressed I420 colorspace and PCM audio, note that scr_screenshot_gammaboost affects the brightness of the output) +cl_capturevideo_fps 30 how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful) +cl_capturevideo_number 1 number to append to video filename, incremented each time a capture begins +cl_capturevideo_realtime 0 causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over 1 second +cl_curl_enabled 0 whether client's download support is enabled +cl_curl_maxdownloads 1 maximum number of concurrent HTTP/FTP downloads +cl_curl_maxspeed 100 maximum download speed (KiB/s) +cl_deathnoviewmodel 1 hides gun model when dead +cl_deathscoreboard 1 shows scoreboard (+showscores) while dead +cl_decals 0 enables decals (bullet holes, blood, etc) +cl_decals_fadetime 20 how long decals take to fade away +cl_decals_time 0 how long before decals start to fade away +cl_dlights_decaybrightness 1 reduces brightness of light flashes over time +cl_dlights_decayradius 1 reduces size of light flashes over time +cl_explosions_alpha_end 0 end alpha of an explosion shell (just before it disappears) +cl_explosions_alpha_start 1.5 starting alpha of an explosion shell +cl_explosions_lifetime 0.5 how long an explosion shell lasts +cl_explosions_size_end 128 ending alpha of an explosion shell (just before it disappears) +cl_explosions_size_start 16 starting size of an explosion shell +cl_forwardspeed 400 forward movement speed +cl_gravity 800 how much gravity to apply in client physics (should match sv_gravity) +cl_itembobheight 0 how much items bob up and down (try 8) +cl_itembobspeed 0.5 how frequently items bob up and down +cl_joinbeforedownloadsfinish 1 if non-zero the game will begin after the map is loaded before other downloads finish +cl_maxfps 1000 maximum fps cap, if game is running faster than this it will wait before running another frame (useful to make cpu time available to other programs) +cl_movement 0 enables clientside prediction of your player movement +cl_movement_accelerate 10 how fast you accelerate (should match sv_accelerate) +cl_movement_airaccel_qw 1 ratio of QW-style air control as opposed to simple acceleration (should match sv_airaccel_qw) +cl_movement_airaccel_sideways_friction 0 anti-sideways movement stabilization (should match sv_airaccel_sideways_friction) +cl_movement_airaccelerate -1 how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead +cl_movement_edgefriction 2 how much to slow down when you may be about to fall off a ledge (should match edgefriction) +cl_movement_friction 4 how fast you slow down (should match sv_friction) +cl_movement_jumpvelocity 270 how fast you move upward when you begin a jump (should match the quakec code) +cl_movement_maxairspeed 30 how fast you can move while in the air (should match sv_maxairspeed) +cl_movement_maxspeed 320 how fast you can move (should match sv_maxspeed) +cl_movement_stepheight 18 how tall a step you can step in one instant (should match sv_stepheight) +cl_movement_stopspeed 100 speed below which you will be slowed rapidly to a stop rather than sliding endlessly (should match sv_stopspeed) +cl_movement_wateraccelerate -1 how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead +cl_movement_waterfriction -1 how fast you slow down (should match sv_friction), if less than 0 the cl_movement_friction variable is used instead +cl_movespeedkey 2.0 how much +speed multiplies keyboard movement speed +cl_netinputpacketlosstolerance 4 how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server) +cl_netinputpacketspersecond 50 how many input packets to send to server each second +cl_netlocalping 0 lags local loopback connection by this much ping time (useful to play more fairly on your own server with people with higher pings) +cl_netpacketloss 0 drops this percentage of packets (incoming and outgoing), useful for testing network protocol robustness (effects failing to start, sounds failing to play, etc) +cl_nettimesyncmode 2 selects method of time synchronization in client with regard to server packets, values are: 0 = no sync, 1 = exact sync (reset timing each packet), 2 = loose sync (reset timing only if it is out of bounds), 3 = tight sync and bounding +cl_nodelta 0 disables delta compression of non-player entities in QW network protocol +cl_nolerp 0 network update smoothing +cl_noplayershadow 0 hide player shadow +cl_particles 1 enables particle effects +cl_particles_blood 1 enables blood effects +cl_particles_blood_alpha 0.5 opacity of blood +cl_particles_blood_bloodhack 1 make certain quake particle() calls create blood effects instead +cl_particles_bubbles 1 enables bubbles (used by multiple effects) +cl_particles_bulletimpacts 1 enables bulletimpact effects +cl_particles_explosions_shell 0 enables polygonal shell from explosions +cl_particles_explosions_smokes 0 enables smoke from explosions +cl_particles_explosions_sparks 1 enables sparks from explosions +cl_particles_quake 0 makes particle effects look mostly like the ones in Quake +cl_particles_quality 1 multiplies number of particles and reduces their alpha +cl_particles_size 1 multiplies particle size +cl_particles_smoke 1 enables smoke (used by multiple effects) +cl_particles_smoke_alpha 0.5 smoke brightness +cl_particles_smoke_alphafade 0.55 brightness fade per second +cl_particles_sparks 1 enables sparks (used by multiple effects) +cl_pitchspeed 150 keyboard pitch turning speed +cl_port 0 forces client to use chosen port number if not 0 +cl_prydoncursor 0 enables a mouse pointer which is able to click on entities in the world, useful for point and click mods, see PRYDON_CLIENTCURSOR extension in dpextensions.qc +cl_rollangle 2.0 how much to tilt the view when strafing +cl_rollspeed 200 how much strafing is necessary to tilt the view +cl_serverextension_download 0 indicates whether the server supports the download command +cl_shownet 0 1 = print packet size, 2 = print packet message list +cl_sidespeed 350 strafe movement speed +cl_slowmo 1 speed of game time (should match slowmo) +cl_sound_hknighthit hknight/hit.wav sound to play during TE_KNIGHTSPIKE (empty cvar disables sound) +cl_sound_r_exp3 weapons/r_exp3.wav sound to play during TE_EXPLOSION and related effects (empty cvar disables sound) +cl_sound_ric1 weapons/ric1.wav sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound) +cl_sound_ric2 weapons/ric2.wav sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound) +cl_sound_ric3 weapons/ric3.wav sound to play with 10% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound) +cl_sound_tink1 weapons/tink1.wav sound to play with 80% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound) +cl_sound_wizardhit wizard/hit.wav sound to play during TE_WIZSPIKE (empty cvar disables sound) +cl_stainmaps 1 stains lightmaps, much faster than decals but blurred +cl_stainmaps_clearonload 1 clear stainmaps on map restart +cl_stairsmoothspeed 160 how fast your view moves upward/downward when running up/down stairs +cl_upspeed 400 vertical movement speed (while swimming or flying) +cl_viewmodel_scale 1 changes size of gun model, lower values prevent poking into walls but cause strange artifacts on lighting and especially r_stereo/vid_stereobuffer options where the size of the gun becomes visible +cl_yawspeed 140 keyboard yaw turning speed +cmdline 0 contains commandline the engine was launched with +collision_endnudge 0 how much to bias collision trace end +collision_enternudge 0 how much to bias collision entry fraction +collision_impactnudge 0.03125 how much to back off from the impact +collision_leavenudge 0 how much to bias collision exit fraction +collision_prefernudgedfraction 1 whether to sort collision events by nudged fraction (1) or real fraction (0) +collision_startnudge 0 how much to bias collision trace start +con_closeontoggleconsole 1 allows toggleconsole binds to close the console as well +con_chat 0 how many chat lines to show in a dedicated chat area +con_chatpos 0 where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top) +con_chatsize 8 chat text size in virtual 2D pixels +con_chattime 30 how long chat lines last, in seconds +con_chatwidth 1.0 relative chat window width +con_notify 4 how many notify lines to show (0-32) +con_notifyalign 3 how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default) +con_notifysize 8 notify text size in virtual 2D pixels +con_notifytime 3 how long notify lines last, in seconds +con_textsize 8 console text size in virtual 2D pixels +coop 0 coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch) +crosshair 0 selects crosshair to use (0 is none) +crosshair_color_alpha 1 how opaque the crosshair should be +crosshair_color_blue 0 customizable crosshair color +crosshair_color_green 0 customizable crosshair color +crosshair_color_red 1 customizable crosshair color +crosshair_size 1 adjusts size of the crosshair on the screen +csqc_progcrc -1 CRC of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1 +csqc_progname csprogs.dat name of csprogs.dat file to load +csqc_progsize -1 file size of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1 +cutscene 1 enables cutscenes in nehahra, can be used by other mods +deathmatch 0 deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons) +demo_nehahra 0 reads all quake demos as nehahra movie protocol +developer 0 prints additional debugging messages and information (recommended for modders and level designers) +developer_entityparsing 0 prints detailed network entities information each time a packet is received +developer_memory 0 prints debugging information about memory allocations +developer_memorydebug 0 enables memory corruption checks (very slow) +developer_networkentities 0 prints received entities, value is 0-4 (higher for more info) +developer_networking 0 prints all received and sent packets (recommended only for debugging) +developer_texturelogging 0 produces a textures.log file containing names of skins and map textures the engine tried to load +edgefriction 2 how much you slow down when nearing a ledge you might fall off +forceqmenu 0 enables the quake menu instead of the quakec menu.dat (if present) +fov 90 field of vision, 1-170 degrees, default 90, some players use 110-130 +fraglimit 0 ends level if this many frags is reached by any player +freelook 1 mouse controls pitch instead of forward/back +gamecfg 0 unused cvar in quake, can be used by mods +gameversion 0 version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible +gl_combine 1 faster rendering by using GL_ARB_texture_env_combine extension (part of OpenGL 1.3 and above) +gl_dither 1 enables OpenGL dithering (16bit looks bad with this off) +gl_ext_separatetencil 1 make use of OpenGL 2.0 glStencilOpSeparate or GL_ATI_separate_stencil extension +gl_ext_stenciltwoside 1 make use of GL_EXT_stenciltwoside extension (NVIDIA only) +gl_finish 0 make the cpu wait for the graphics processor at the end of each rendered frame (can help with strange input or video lag problems on some machines) +gl_flashblend 0 render bright coronas for dynamic lights instead of actual lighting, fast but ugly +gl_fogblue 0.3 nehahra fog color blue value (for Nehahra compatibility only) +gl_fogdensity 0.25 nehahra fog density (recommend values below 0.1) (for Nehahra compatibility only) +gl_fogenable 0 nehahra fog enable (for Nehahra compatibility only) +gl_fogend 0 nehahra fog end distance (for Nehahra compatibility only) +gl_foggreen 0.3 nehahra fog color green value (for Nehahra compatibility only) +gl_fogred 0.3 nehahra fog color red value (for Nehahra compatibility only) +gl_fogstart 0 nehahra fog start distance (for Nehahra compatibility only) +gl_lightmaps 0 draws only lightmaps, no texture (for level designers) +gl_lockarrays 0 enables use of glLockArraysEXT, may cause glitches with some broken drivers, and may be slower than normal +gl_lockarrays_minimumvertices 1 minimum number of vertices required for use of glLockArraysEXT, setting this too low may reduce performance +gl_max_size 2048 maximum allowed texture size, can be used to reduce video memory usage, note: this is automatically reduced to match video card capabilities (such as 256 on 3Dfx cards before Voodoo4/5) +gl_mesh_drawrangeelements 1 use glDrawRangeElements function if available instead of glDrawElements (for performance comparisons or bug testing) +gl_mesh_testarrayelement 0 use glBegin(GL_TRIANGLES);glArrayElement();glEnd(); primitives instead of glDrawElements (useful to test for driver bugs with glDrawElements) +gl_mesh_testmanualfeeding 0 use glBegin(GL_TRIANGLES);glTexCoord2f();glVertex3f();glEnd(); primitives instead of glDrawElements (useful to test for driver bugs with glDrawElements) +gl_paranoid 0 enables OpenGL error checking and other tests +gl_picmip 0 reduces resolution of textures by powers of 2, for example 1 will halve width/height, reducing texture memory usage by 75% +gl_polyblend 1 tints view while underwater, hurt, etc +gl_printcheckerror 0 prints all OpenGL error checks, useful to identify location of driver crashes +gl_texture_anisotropy 1 anisotropic filtering quality (if supported by hardware), 1 sample (no anisotropy) and 8 sample (8 tap anisotropy) are recommended values +halflifebsp 0 indicates the current map is hlbsp format (useful to know because of different bounding box sizes) +host_framerate 0 locks frame timing to this value in seconds, 0.05 is 20fps for example, note that this can easily run too fast, use cl_maxfps if you want to limit your framerate instead, or sys_ticrate to limit server speed +host_speeds 0 reports how much time is used in server/graphics/sound +hostname UNNAMED server message to show in server browser +in_pitch_max 90 how far upward you can aim (quake used 80 +in_pitch_min -90 how far downward you can aim (quake used -70 +joy_axisforward 1 which joystick axis to query for forward/backward movement +joy_axispitch 3 which joystick axis to query for looking up/down +joy_axisroll -1 which joystick axis to query for tilting head right/left +joy_axisside 0 which joystick axis to query for right/left movement +joy_axisup -1 which joystick axis to query for up/down movement +joy_axisyaw 2 which joystick axis to query for looking right/left +joy_deadzoneforward 0 deadzone tolerance, suggested values are in the range 0 to 0.01 +joy_deadzonepitch 0 deadzone tolerance, suggested values are in the range 0 to 0.01 +joy_deadzoneroll 0 deadzone tolerance, suggested values are in the range 0 to 0.01 +joy_deadzoneside 0 deadzone tolerance, suggested values are in the range 0 to 0.01 +joy_deadzoneup 0 deadzone tolerance, suggested values are in the range 0 to 0.01 +joy_deadzoneyaw 0 deadzone tolerance, suggested values are in the range 0 to 0.01 +joy_detected 0 number of joysticks detected by engine +joy_enable 1 enables joystick support +joy_index 0 selects which joystick to use if you have multiple +joy_sensitivityforward -1 movement multiplier +joy_sensitivitypitch 1 movement multiplier +joy_sensitivityroll 1 movement multiplier +joy_sensitivityside 1 movement multiplier +joy_sensitivityup 1 movement multiplier +joy_sensitivityyaw -1 movement multiplier +joyadvanced 0 use more than 2 axis joysticks (configuring this is very technical) +joyadvaxisr 0 axis mapping for joyadvanced 1 mode +joyadvaxisu 0 axis mapping for joyadvanced 1 mode +joyadvaxisv 0 axis mapping for joyadvanced 1 mode +joyadvaxisx 0 axis mapping for joyadvanced 1 mode +joyadvaxisy 0 axis mapping for joyadvanced 1 mode +joyadvaxisz 0 axis mapping for joyadvanced 1 mode +joyforwardsensitivity -1.0 how fast the joystick moves forward +joyforwardthreshold 0.15 minimum joystick movement necessary to move forward +joyname joystick name of joystick to use (informational only, used only by joyadvanced 1 mode) +joypitchsensitivity 1.0 how fast the joystick looks up/down +joypitchthreshold 0.15 minimum joystick movement necessary to look up/down +joysidesensitivity -1.0 how fast the joystick moves sideways (strafing) +joysidethreshold 0.15 minimum joystick movement necessary to move sideways (strafing) +joystick 0 enables joysticks +joywwhack1 0.0 special hack for wingman warrior +joywwhack2 0.0 special hack for wingman warrior +joyyawsensitivity -1.0 how fast the joystick turns left/right +joyyawthreshold 0.15 minimum joystick movement necessary to turn left/right +locs_enable 1 enables replacement of certain % codes in chat messages: %l (location), %d (last death location), %h (health), %a (armor), %x (rockets), %c (cells), %r (rocket launcher status), %p (powerup status), %w (weapon status), %t (current time in level) +locs_show 0 shows defined locations for editing purposes +log_file filename to log messages to +lookspring 0 returns pitch to level with the floor when no longer holding a pitch key +lookstrafe 0 move instead of turning +m_filter 0 smoothes mouse movement, less responsive but smoother aiming +m_forward 1 mouse forward speed multiplier +m_pitch 0.022 mouse pitch speed multiplier +m_side 0.8 mouse side speed multiplier +m_yaw 0.022 mouse yaw speed multiplier +mcbsp 0 indicates the current map is mcbsp format (useful to know because of different bounding box sizes) +menu_options_colorcontrol_correctionvalue 0.5 intensity value that matches up to white/black dither pattern, should be 0.5 for linear color +mod_q3bsp_curves_collisions 1 enables collisions with curves (SLOW) +mod_q3bsp_debugtracebrush 0 selects different tracebrush bsp recursion algorithms (for debugging purposes only) +mod_q3bsp_lightmapmergepower 4 merges the quake3 128x128 lightmap textures into larger lightmap group textures to speed up rendering, 1 = 256x256, 2 = 512x512, 3 = 1024x1024, 4 = 2048x2048, 5 = 4096x4096, ... +mod_q3bsp_optimizedtraceline 1 whether to use optimized traceline code for line traces (as opposed to tracebox code) +nehx00 0 nehahra data storage cvar (used in singleplayer) +nehx01 0 nehahra data storage cvar (used in singleplayer) +nehx02 0 nehahra data storage cvar (used in singleplayer) +nehx03 0 nehahra data storage cvar (used in singleplayer) +nehx04 0 nehahra data storage cvar (used in singleplayer) +nehx05 0 nehahra data storage cvar (used in singleplayer) +nehx06 0 nehahra data storage cvar (used in singleplayer) +nehx07 0 nehahra data storage cvar (used in singleplayer) +nehx08 0 nehahra data storage cvar (used in singleplayer) +nehx09 0 nehahra data storage cvar (used in singleplayer) +nehx10 0 nehahra data storage cvar (used in singleplayer) +nehx11 0 nehahra data storage cvar (used in singleplayer) +nehx12 0 nehahra data storage cvar (used in singleplayer) +nehx13 0 nehahra data storage cvar (used in singleplayer) +nehx14 0 nehahra data storage cvar (used in singleplayer) +nehx15 0 nehahra data storage cvar (used in singleplayer) +nehx16 0 nehahra data storage cvar (used in singleplayer) +nehx17 0 nehahra data storage cvar (used in singleplayer) +nehx18 0 nehahra data storage cvar (used in singleplayer) +nehx19 0 nehahra data storage cvar (used in singleplayer) +net_address 0.0.0.0 network address to open ports on +net_address_ipv6 [0:0:0:0:0:0:0:0] network address to open ipv6 ports on +net_connectfloodblockingtimeout 5 when a connection packet is received, it will block all future connect packets from that IP address for this many seconds (cuts down on connect floods) +net_connecttimeout 10 after requesting a connection, the client must reply within this many seconds or be dropped (cuts down on connect floods) +net_messagetimeout 300 drops players who have not sent any packets for this many seconds +net_slist_maxtries 3 how many times to ask the same server for information (more times gives better ping reports but takes longer) +net_slist_queriesperframe 4 maximum number of server information requests to send each rendered frame (guards against low framerates causing problems) +net_slist_queriespersecond 20 how many server information requests to send per second +net_slist_timeout 4 how long to listen for a server information response before giving up +noaim 1 QW option to disable vertical autoaim +noexit 0 kills anyone attempting to use an exit +nomonsters 0 unused cvar in quake, can be used by mods +nosound 0 disables sound +pausable 1 allow players to pause or not +port 26000 server port for players to connect to +pr_checkextension 1 indicates to QuakeC that the standard quakec extensions system is available (if 0, quakec should not attempt to use extensions) +prvm_boundscheck 1 enables detection of out of bounds memory access in the QuakeC code being run (in other words, prevents really exceedingly bad QuakeC code from doing nasty things to your computer) +prvm_statementprofiling 0 counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled) +prvm_traceqc 0 prints every QuakeC statement as it is executed (only for really thorough debugging!) +qport 0 identification key for playing on qw servers (allows you to maintain a connection to a quakeworld server even if your port changes) +r_ambient 0 brightens map, value is 0-128 +r_batchmode 1 selects method of rendering multiple surfaces with one driver call (values are 0, 1, 2, etc...) +r_bloom 0 enables bloom effect (makes bright pixels affect neighboring pixels) +r_bloom_blur 4 how large the glow is +r_bloom_brighten 2 how bright the glow is, after subtract/power +r_bloom_colorexponent 1 how exagerated the glow is +r_bloom_colorscale 1 how bright the glow is +r_bloom_colorsubtract 0.125 reduces bloom colors by a certain amount +r_bloom_resolution 320 what resolution to perform the bloom effect at (independent of screen resolution) +r_coronas 1 brightness of corona flare effects around certain lights, 0 disables corona effects +r_cullentities_trace 1 probabistically cull invisible entities +r_cullentities_trace_delay 1 number of seconds until the entity gets actually culled +r_cullentities_trace_enlarge 0 box enlargement for entity culling +r_cullentities_trace_samples 2 number of samples to test for entity culling +r_draweffects 1 renders temporary sprite effects +r_drawentities 1 draw entities (doors, players, projectiles, etc) +r_drawexplosions 1 enables rendering of explosion shells (see also cl_particles_explosions_shell) +r_drawparticles 1 enables drawing of particles +r_drawportals 0 shows portals (separating polygons) in world interior in quake1 maps +r_drawviewmodel 1 draw your weapon model +r_dynamic 1 enables dynamic lights (rocket glow and such) +r_editlights 0 enables .rtlights file editing mode +r_editlights_cursordistance 1024 maximum distance of cursor from eye +r_editlights_cursorgrid 4 snaps cursor to this grid size +r_editlights_cursorpushback 0 how far to pull the cursor back toward the eye +r_editlights_cursorpushoff 4 how far to push the cursor off the impacted surface +r_editlights_quakelightsizescale 1 changes size of light entities loaded from a map +r_explosionclip 1 enables collision detection for explosion shell (so that it flattens against walls and floors) +r_fullbright 0 makes map very bright and renders faster +r_fullbrights 1 enables glowing pixels in quake textures (changes need r_restart to take effect) +r_glsl 1 enables use of OpenGL 2.0 pixel shaders for lighting +r_glsl_deluxemapping 1 use per pixel lighting on deluxemap-compiled q3bsp maps (or a value of 2 forces deluxemap shading even without deluxemaps) +r_glsl_offsetmapping 0 offset mapping effect (also known as parallax mapping or virtual displacement mapping) +r_glsl_offsetmapping_reliefmapping 0 relief mapping effect (higher quality) +r_glsl_offsetmapping_scale 0.04 how deep the offset mapping effect is +r_hdr 0 enables High Dynamic Range bloom effect (higher quality version of r_bloom) +r_hdr_glowintensity 1 how bright light emitting textures should appear +r_hdr_range 4 how much dynamic range to render bloom with (equivilant to multiplying r_bloom_brighten by this value and dividing r_bloom_colorscale by this value) +r_hdr_scenebrightness 1 global rendering brightness +r_lerpimages 1 bilinear filters images when scaling them up to power of 2 size (mode 1), looks better than glquake (mode 0) +r_lerpmodels 1 enables animation smoothing on models +r_lerpsprites 1 enables animation smoothing on sprites +r_letterbox 0 reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes) +r_lightmaprgba 1 whether to use RGBA (32bit) or RGB (24bit) lightmaps +r_lightningbeam_color_blue 1 color of the lightning beam effect +r_lightningbeam_color_green 1 color of the lightning beam effect +r_lightningbeam_color_red 1 color of the lightning beam effect +r_lightningbeam_qmbtexture 0 load the qmb textures/particles/lightning.pcx texture instead of generating one, can look better +r_lightningbeam_repeatdistance 128 how far to stretch the texture along the lightning beam effect +r_lightningbeam_scroll 5 speed of texture scrolling on the lightning beam effect +r_lightningbeam_thickness 4 thickness of the lightning beam effect +r_lockpvs 0 disables pvs switching, allows you to walk around and inspect what is visible from a given location in the map (anything not visible from your current location will not be drawn) +r_lockvisibility 0 disables visibility updates, allows you to walk around and inspect what is visible from a given viewpoint in the map (anything offscreen at the moment this is enabled will not be drawn) +r_mipskins 0 mipmaps skins (so they become blurrier in the distance), disabled by default because it tends to blur with strange border colors from the skin +r_mipsprites 1 mipmaps skins (so they become blurrier in the distance), unlike skins the sprites do not have strange border colors +r_nearclip 1 distance from camera of nearclip plane +r_nosurftextures 0 pretends there was no texture lump found in the q1bsp/hlbsp loading (useful for debugging this rare case) +r_novis 0 draws whole level, see also sv_cullentities_pvs 0 +r_precachetextures 1 0 = never upload textures until used, 1 = upload most textures before use (exceptions: rarely used skin colormap layers), 2 = upload all textures before use (can increase texture memory usage significantly) +r_q3bsp_renderskydepth 0 draws sky depth masking in q3 maps (as in q1 maps), this means for example that sky polygons can hide other things +r_qb1sp_skymasking 1 allows sky polygons in quake1 maps to obscure other geometry +r_render 1 enables rendering calls (you want this on!) +r_shadow_bumpscale_basetexture 0 generate fake bumpmaps from diffuse textures at this bumpyness, try 4 to match tenebrae, higher values increase depth, requires r_restart to take effect +r_shadow_bumpscale_bumpmap 4 what magnitude to interpret _bump.tga textures as, higher values increase depth, requires r_restart to take effect +r_shadow_culltriangles 1 performs more expensive tests to remove unnecessary triangles of lit surfaces +r_shadow_debuglight -1 renders only one light, for level design purposes or debugging +r_shadow_frontsidecasting 1 whether to cast shadows from illuminated triangles (front side of model) or unlit triangles (back side of model) +r_shadow_gloss 1 0 disables gloss (specularity) rendering, 1 uses gloss if textures are found, 2 forces a flat metallic specular effect on everything without textures (similar to tenebrae) +r_shadow_gloss2intensity 0.125 how bright the forced flat gloss should look if r_shadow_gloss is 2 +r_shadow_glossexponent 32 how 'sharp' the gloss should appear (specular power) +r_shadow_glossintensity 1 how bright textured glossmaps should look if r_shadow_gloss is 1 or 2 +r_shadow_lightattenuationpower 0.5 changes attenuation texture generation (does not affect r_glsl lighting) +r_shadow_lightattenuationscale 1 changes attenuation texture generation (does not affect r_glsl lighting) +r_shadow_lightintensityscale 1 renders all world lights brighter or darker +r_shadow_lightradiusscale 1 renders all world lights larger or smaller +r_shadow_portallight 1 use portal culling to exactly determine lit triangles when compiling world lights +r_shadow_projectdistance 1000000 how far to cast shadows +r_shadow_realtime_dlight 1 enables rendering of dynamic lights such as explosions and rocket light +r_shadow_realtime_dlight_portalculling 0 enables portal optimization on dynamic lights (slow!) +r_shadow_realtime_dlight_shadows 1 enables rendering of shadows from dynamic lights +r_shadow_realtime_dlight_svbspculling 0 enables svbsp optimization on dynamic lights (very slow!) +r_shadow_realtime_world 0 enables rendering of full world lighting (whether loaded from the map, or a .rtlights file, or a .ent file, or a .lights file produced by hlight) +r_shadow_realtime_world_compile 1 enables compilation of world lights for higher performance rendering +r_shadow_realtime_world_compileportalculling 1 enables portal-based culling optimization during compilation +r_shadow_realtime_world_compileshadow 1 enables compilation of shadows from world lights for higher performance rendering +r_shadow_realtime_world_compilesvbsp 1 enables svbsp optimization during compilation +r_shadow_realtime_world_lightmaps 0 brightness to render lightmaps when using full world lighting, try 0.5 for a tenebrae-like appearance +r_shadow_realtime_world_shadows 1 enables rendering of shadows from world lights +r_shadow_scissor 1 use scissor optimization of light rendering (restricts rendering to the portion of the screen affected by the light) +r_shadow_polygonfactor 0 how much to enlarge shadow volume polygons when rendering (should be 0!) +r_shadow_polygonoffset 1 how much to push shadow volumes into the distance when rendering, to reduce chances of zfighting artifacts (should not be less than 0) +r_shadow_texture3d 1 use 3D voxel textures for spherical attenuation rather than cylindrical (does not affect r_glsl lighting) +r_shadows 0 casts fake stencil shadows from models onto the world (rtlights are unaffected by this) +r_shadows_throwdistance 500 how far to cast shadows from models +r_showcollisionbrushes 0 draws collision brushes in quake3 maps (mode 1), mode 2 disables rendering of world (trippy!) +r_showcollisionbrushes_polygonfactor -1 expands outward the brush polygons a little bit, used to make collision brushes appear infront of walls +r_showcollisionbrushes_polygonoffset 0 nudges brush polygon depth in hardware depth units, used to make collision brushes appear infront of walls +r_showdisabledepthtest 0 disables depth testing on r_show* cvars, allowing you to see what hidden geometry the graphics card is processing +r_showlighting 0 shows areas lit by lights, useful for finding out why some areas of a map render slowly (bright orange = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful +r_shownormals 0 shows per-vertex surface normals and tangent vectors for bumpmapped lighting +r_showshadowvolumes 0 shows areas shadowed by lights, useful for finding out why some areas of a map render slowly (bright blue = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful +r_showsurfaces 0 1 shows surfaces as different colors, or a value of 2 shows triangle draw order (for analyzing whether meshes are optimized for vertex cache) +r_showtris 0 shows triangle outlines, value controls brightness (can be above 1) +r_skeletal_debugbone -1 development cvar for testing skeletal model code +r_skeletal_debugbonecomponent 3 development cvar for testing skeletal model code +r_skeletal_debugbonevalue 100 development cvar for testing skeletal model code +r_skeletal_debugtranslatex 1 development cvar for testing skeletal model code +r_skeletal_debugtranslatey 1 development cvar for testing skeletal model code +r_skeletal_debugtranslatez 1 development cvar for testing skeletal model code +r_sky 1 enables sky rendering (black otherwise) +r_skyscroll1 1 speed at which upper clouds layer scrolls in quake sky +r_skyscroll2 2 speed at which lower clouds layer scrolls in quake sky +r_smoothnormals_areaweighting 1 uses significantly faster (and supposedly higher quality) area-weighted vertex normals and tangent vectors rather than summing normalized triangle normals and tangents +r_speeds 0 displays rendering statistics and per-subsystem timings +r_stereo_redblue 0 red/blue anaglyph stereo glasses (note: most of these glasses are actually red/cyan, try that one too) +r_stereo_redcyan 0 red/cyan anaglyph stereo glasses, the kind given away at drive-in movies like Creature From The Black Lagoon In 3D +r_stereo_redgreen 0 red/green anaglyph stereo glasses (for those who don't mind yellow) +r_stereo_separation 4 separation of eyes in the world (try negative values too) +r_stereo_sidebyside 0 side by side views (for those who can't afford glasses but can afford eye strain) +r_subdivide_size 128 how large water polygons should be (smaller values produce more polygons which give better warping effects) +r_subdivisions_collision_maxtess 1024 maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing) +r_subdivisions_collision_maxvertices 4225 maximum vertices allowed per subdivided curve +r_subdivisions_collision_mintess 1 minimum number of subdivisions (values above 1 will smooth curves that don't need it) +r_subdivisions_collision_tolerance 15 maximum error tolerance on curve subdivision for collision purposes (usually a larger error tolerance than for rendering) +r_subdivisions_maxtess 1024 maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing) +r_subdivisions_maxvertices 65536 maximum vertices allowed per subdivided curve +r_subdivisions_mintess 1 minimum number of subdivisions (values above 1 will smooth curves that don't need it) +r_subdivisions_tolerance 4 maximum error tolerance on curve subdivision for rendering purposes (in other words, the curves will be given as many polygons as necessary to represent curves at this quality) +r_test 0 internal development use only, leave it alone (usually does nothing anyway) +r_textshadow 0 draws a shadow on all text to improve readability +r_textureunits 32 number of hardware texture units reported by driver (note: setting this to 1 turns off gl_combine) +r_useportalculling 1 use advanced portal culling visibility method to improve performance over just Potentially Visible Set, provides an even more significant speed improvement in unvised maps +r_wateralpha 1 opacity of water polygons +r_waterscroll 1 makes water scroll around, value controls how much +r_waterwarp 1 warp view while underwater +rcon_address server address to send rcon commands to (when not connected to a server) +rcon_password password to authenticate rcon commands +registered 0 indicates if this is running registered quake (whether gfx/pop.lmp was found) +samelevel 0 repeats same level if level ends (due to timelimit or someone hitting an exit) +saved1 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods +saved2 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods +saved3 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods +saved4 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods +savedgamecfg 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods +sbar_alpha_bg 0.4 opacity value of the statusbar background image +sbar_alpha_fg 1 opacity value of the statusbar weapon/item icons and numbers +scr_centertime 2 how long centerprint messages show +scr_conalpha 1 opacity of console background +scr_conbrightness 1 brightness of console background (0 = black, 1 = image) +scr_conforcewhiledisconnected 1 forces fullscreen console while disconnected +scr_menuforcewhiledisconnected 0 forces menu while disconnected +scr_printspeed 8 speed of intermission printing (episode end texts) +scr_refresh 1 allows you to completely shut off rendering for benchmarking purposes +scr_screenshot_gammaboost 1 gamma correction on saved screenshots and videos, 1.0 saves unmodified images +scr_screenshot_jpeg 1 save jpeg instead of targa +scr_screenshot_jpeg_quality 0.9 image quality of saved jpeg +scr_screenshot_name dp prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running) +scr_stipple 0 interlacing-like stippling of the display +scr_zoomwindow 0 displays a zoomed in overlay window +scr_zoomwindow_fov 20 fov of zoom window +scr_zoomwindow_viewsizex 20 horizontal viewsize of zoom window +scr_zoomwindow_viewsizey 20 vertical viewsize of zoom window +scratch1 0 unused cvar in quake, can be used by mods +scratch2 0 unused cvar in quake, can be used by mods +scratch3 0 unused cvar in quake, can be used by mods +scratch4 0 unused cvar in quake, can be used by mods +sensitivity 3 mouse speed multiplier +showbrand 0 shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered) +showdate 0 shows current date (useful on screenshots) +showdate_format %Y-%m-%d format string for date +showfps 0 shows your rendered fps (frames per second) +showpause 1 show pause icon when game is paused +showram 1 show ram icon if low on surface cache memory (not used) +showspeed 0 shows your current speed (qu per second); number selects unit: 1 = qups, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots +showtime 0 shows current time of day (useful on screenshots) +showtime_format %H:%M:%S format string for time of day +showturtle 0 show turtle icon when framerate is too low (not used) +skill 1 difficulty level of game, affects monster layouts in levels, 0 = easy, 1 = normal, 2 = hard, 3 = nightmare (same layout as hard but monsters fire twice) +skin QW player skin name (example: base) +slowmo 1.0 controls game speed, 0.5 is half speed, 2 is double speed +snd_channellayout 0 channel layout. Can be 0 (auto - snd_restart needed), 1 (standard layout), or 2 (ALSA layout) +snd_channels 2 number of channels for the sound ouput (2 for stereo; up to 8 supported for 3D sound) +snd_initialized 0 indicates the sound subsystem is active +snd_noextraupdate 0 disables extra sound mixer calls that are meant to reduce the chance of sound breakup at very low framerates +snd_precache 1 loads sounds before they are used +snd_show 0 shows some statistics about sound mixing +snd_soundradius 1000 radius of weapon sounds and other standard sound effects (monster idle noises are half this radius and flickering light noises are one third of this radius) +snd_speed 48000 sound output frequency, in hertz +snd_staticvolume 1 volume of ambient sound effects (such as swampy sounds at the start of e1m2) +snd_streaming 1 enables keeping compressed ogg sound files compressed, decompressing them only as needed, otherwise they will be decompressed completely at load (may use a lot of memory) +snd_swapstereo 0 swaps left/right speakers for old ISA soundblaster cards +snd_width 2 sound output precision, in bytes (1 and 2 supported) +sv_accelerate 10 rate at which a player accelerates to sv_maxspeed +sv_adminnick nick name to use for admin messages instead of host name +sv_aim 2 maximum cosine angle for quake's vertical autoaim, a value above 1 completely disables the autoaim, quake used 0.93 +sv_airaccelerate -1 rate at which a player accelerates to sv_maxairspeed while in the air, if less than 0 the sv_accelerate variable is used instead +sv_allowdownloads 1 whether to allow clients to download files from the server (does not affect http downloads) +sv_allowdownloads_archive 0 whether to allow downloads of archives (pak/pk3) +sv_allowdownloads_config 0 whether to allow downloads of config files (cfg) +sv_allowdownloads_dlcache 0 whether to allow downloads of dlcache files (dlcache/) +sv_allowdownloads_inarchive 0 whether to allow downloads from archives (pak/pk3) +sv_areagrid_mingridsize 64 minimum areagrid cell size, smaller values work better for lots of small objects, higher values for large objects +sv_cheats 0 enables cheat commands in any game, and cheat impulses in dpmod +sv_clmovement_enable 1 whether to allow clients to use cl_movement prediction, which can cause choppy movement on the server which may annoy other players +sv_clmovement_minping 0 if client ping is below this time in milliseconds, then their ability to use cl_movement prediction is disabled for a while (as they don't need it) +sv_clmovement_minping_disabletime 1000 when client falls below minping, disable their prediction for this many milliseconds (should be at least 1000 or else their prediction may turn on/off frequently) +sv_clmovement_inputtimeout 0.2 when a client does not send input for this many seconds, force them to move anyway (unlike QuakeWorld) +sv_cullentities_nevercullbmodels 0 if enabled the clients are always notified of moving doors and lifts and other submodels of world (warning: eats a lot of network bandwidth on some levels!) +sv_cullentities_pvs 1 fast but loose culling of hidden entities +sv_cullentities_stats 0 displays stats on network entities culled by various methods for each client +sv_cullentities_trace 0 somewhat slow but very tight culling of hidden entities, minimizes network traffic and makes wallhack cheats useless +sv_cullentities_trace_delay 1 number of seconds until the entity gets actually culled +sv_cullentities_trace_enlarge 0 box enlargement for entity culling +sv_cullentities_trace_prediction 1 also trace from the predicted player position +sv_cullentities_trace_samples 1 number of samples to test for entity culling +sv_cullentities_trace_samples_extra 2 number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight +sv_curl_defaulturl default autodownload source URL +sv_curl_serverpackages list of required files for the clients, separated by spaces +sv_debugmove 0 disables collision detection optimizations for debugging purposes +sv_echobprint 1 prints gamecode bprint() calls to server console +sv_entpatch 1 enables loading of .ent files to override entities in the bsp (for example Threewave CTF server pack contains .ent patch files enabling play of CTF on id1 maps) +sv_fixedframeratesingleplayer 0 allows you to use server-style timing system in singleplayer (don't run faster than sys_ticrate) +sv_freezenonclients 0 freezes time, except for players, allowing you to walk around and take screenshots of explosions +sv_friction 4 how fast you slow down +sv_gameplayfix_blowupfallenzombies 1 causes findradius to detect SOLID_NOT entities such as zombies and corpses on the floor, allowing splash damage to apply to them +sv_gameplayfix_droptofloorstartsolid 1 prevents items and monsters that start in a solid area from falling out of the level (makes droptofloor treat trace_startsolid as an acceptable outcome) +sv_gameplayfix_findradiusdistancetobox 1 causes findradius to check the distance to the corner of a box rather than the center of the box, makes findradius detect bmodels such as very large doors that would otherwise be unaffected by splash damage +sv_gameplayfix_grenadebouncedownslopes 1 prevents MOVETYPE_BOUNCE (grenades) from getting stuck when fired down a downward sloping surface +sv_gameplayfix_noairborncorpse 1 causes entities (corpses) sitting ontop of moving entities (players) to fall when the moving entity (player) is no longer supporting them +sv_gameplayfix_qwplayerphysics 1 changes water jumping to make it easier to get out of water, and prevents friction on landing when bunnyhopping +sv_gameplayfix_setmodelrealbox 1 fixes a bug in Quake that made setmodel always set the entity box to ('-16 -16 -16', '16 16 16') rather than properly checking the model box, breaks some poorly coded mods +sv_gameplayfix_stepdown 0 attempts to step down stairs, not just up them (prevents the familiar thud..thud..thud.. when running down stairs and slopes) +sv_gameplayfix_stepwhilejumping 1 applies step-up onto a ledge even while airborn, useful if you would otherwise just-miss the floor when running across small areas with gaps (for instance running across the moving platforms in dm2, or jumping to the megahealth and red armor in dm2 rather than using the bridge) +sv_gameplayfix_swiminbmodels 1 causes pointcontents (used to determine if you are in a liquid) to check bmodel entities as well as the world model, so you can swim around in (possibly moving) water bmodel entities +sv_gameplayfix_upwardvelocityclearsongroundflag 1 prevents monsters, items, and most other objects from being stuck to the floor when pushed around by damage, and other situations in mods +sv_gravity 800 how fast you fall (512 = roughly earth gravity) +sv_heartbeatperiod 120 how often to send heartbeat in seconds (only used if sv_public is 1) +sv_idealpitchscale 0.8 how much to look up/down slopes and stairs when not using freelook +sv_jumpstep 0 whether you can step up while jumping (sv_gameplayfix_stepwhilejumping must also be 1) +sv_master1 user-chosen master server 1 +sv_master2 user-chosen master server 2 +sv_master3 user-chosen master server 3 +sv_master4 user-chosen master server 4 +sv_maxairspeed 30 maximum speed a player can accelerate to when airborn (note that it is possible to completely stop by moving the opposite direction) +sv_maxrate 10000 upper limit on client rate cvar, should reflect your network connection quality +sv_maxspeed 320 maximum speed a player can accelerate to when on ground (can be exceeded by tricks) +sv_maxvelocity 2000 universal speed limit on all entities +sv_newflymove 0 enables simpler/buggier player physics (not recommended) +sv_nostep 0 prevents MOVETYPE_STEP entities (monsters) from moving +sv_playerphysicsqc 1 enables QuakeC function to override player physics +sv_progs progs.dat selects which quakec progs.dat file to run +sv_protocolname DP7 selects network protocol to host for (values include QUAKE, QUAKEDP, NEHAHRAMOVIE, DP1 and up) +sv_public 0 1: advertises this server on the master server (so that players can find it in the server browser); 0: allow direct queries only; -1: do not respond to direct queries; -2: do not allow anyone to connect +sv_qwmaster1 user-chosen qwmaster server 1 +sv_qwmaster2 user-chosen qwmaster server 2 +sv_qwmaster3 user-chosen qwmaster server 3 +sv_qwmaster4 user-chosen qwmaster server 4 +sv_random_seed random seed; when set, on every map start this random seed is used to initialize the random number generator. Don't touch it unless for benchmarking or debugging +sv_ratelimitlocalplayer 0 whether to apply rate limiting to the local player in a listen server (only useful for testing) +sv_sound_land demon/dland2.wav sound to play when MOVETYPE_STEP entity hits the ground at high speed (empty cvar disables the sound) +sv_sound_watersplash misc/h2ohit1.wav sound to play when MOVETYPE_FLY/TOSS/BOUNCE/STEP entity enters or leaves water (empty cvar disables the sound) +sv_stepheight 18 how high you can step up (TW_SV_STEPCONTROL extension) +sv_stopspeed 100 how fast you come to a complete stop +sv_wallfriction 1 how much you slow down when sliding along a wall +sv_wateraccelerate -1 rate at which a player accelerates to sv_maxspeed while in the air, if less than 0 the sv_accelerate variable is used instead +sv_waterfriction -1 how fast you slow down, if less than 0 the sv_friction variable is used instead +sys_colortranslation 0 terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation) +sys_colortranslation 1 terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation) +sys_specialcharactertranslation 1 terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output) +sys_ticrate 0.05 how long a server frame is in seconds, 0.05 is 20fps server rate, 0.1 is 10fps (can not be set higher than 0.1), 0 runs as many server frames as possible (makes games against bots a little smoother, overwhelms network players) +sys_usetimegettime 1 use windows timeGetTime function (which has issues on some motherboards) for timing rather than QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) +team none QW team (4 character limit, example: blue) +teamplay 0 teamplay mode, values depend on mod but typically 0 = no teams, 1 = no team damage no self damage, 2 = team damage and self damage, some mods support 3 = no team damage but can damage self +temp1 0 general cvar for mods to use, in stock id1 this selects which death animation to use on players (0 = random death, other values select specific death scenes) +timeformat [%Y-%m-%d %H:%M:%S] time format to use on timestamped console messages +timelimit 0 ends level at this time (in minutes) +timestamps 0 prints timestamps on console messages +v_brightness 0 brightness of black, useful for monitors that are too dark +v_centermove 0.15 how long before the view begins to center itself (if freelook/+mlook/+jlook/+klook are off) +v_centerspeed 500 how fast the view centers itself +v_color_black_b 0 desired color of black +v_color_black_g 0 desired color of black +v_color_black_r 0 desired color of black +v_color_enable 0 enables black-grey-white color correction curve controls +v_color_grey_b 0.5 desired color of grey +v_color_grey_g 0.5 desired color of grey +v_color_grey_r 0.5 desired color of grey +v_color_white_b 1 desired color of white +v_color_white_g 1 desired color of white +v_color_white_r 1 desired color of white +v_contrast 1 brightness of white (values above 1 give a brighter image with increased color saturation, unlike v_gamma) +v_deathtilt 1 whether to use sideways view when dead +v_deathtiltangle 80 what roll angle to use when tilting the view while dead +v_gamma 1 inverse gamma correction value, a brightness effect that does not affect white or black, and tends to make the image grey and dull +v_hwgamma 1 enables use of hardware gamma correction ramps if available (note: does not work very well on Windows2000 and above), values are 0 = off, 1 = attempt to use hardware gamma, 2 = use hardware gamma whether it works or not +v_idlescale 0 how much of the quake 'drunken view' effect to use +v_ipitch_cycle 1 v_idlescale pitch speed +v_ipitch_level 0.3 v_idlescale pitch amount +v_iroll_cycle 0.5 v_idlescale roll speed +v_iroll_level 0.1 v_idlescale roll amount +v_iyaw_cycle 2 v_idlescale yaw speed +v_iyaw_level 0.3 v_idlescale yaw amount +v_kickpitch 0.6 how much a view kick from damage pitches your view +v_kickroll 0.6 how much a view kick from damage rolls your view +v_kicktime 0.5 how long a view kick from damage lasts +v_psycho 0 easter egg (does not work on Windows2000 or above) +vid_bitsperpixel 32 how many bits per pixel to render at (32 or 16, 32 is recommended) +vid_conheight 480 virtual height of 2D graphics system +vid_conwidth 640 virtual width of 2D graphics system +vid_dgamouse 1 make use of DGA mouse input +vid_fullscreen 1 use fullscreen (1) or windowed (0) +vid_grabkeyboard 1 whether to grab the keyboard when mouse is active (prevents use of volume control keys, music player keys, etc on some keyboards) +vid_hardwaregammasupported 1 indicates whether hardware gamma is supported (updated by attempts to set hardware gamma ramps) +vid_height 480 resolution +vid_minheight 0 minimum vid_height that is acceptable (to be set in default.cfg in mods) +vid_minwidth 0 minimum vid_width that is acceptable (to be set in default.cfg in mods) +vid_mouse 1 whether to use the mouse in windowed mode (fullscreen always does) +vid_pixelheight 1 adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example) +vid_refreshrate 60 refresh rate to use, in hz (higher values flicker less, if supported by your monitor) +vid_stereobuffer 0 enables 'quad-buffered' stereo rendering for stereo shutterglasses, HMD (head mounted display) devices, or polarized stereo LCDs, if supported by your drivers +vid_vsync 0 sync to vertical blank, prevents 'tearing' (seeing part of one frame and part of another on the screen at the same time), automatically disabled when doing timedemo benchmarks +vid_width 640 resolution +viewsize 100 how large the view should be, 110 disables inventory bar, 120 disables status bar +volume 0.7 volume of sound effects + + + +Full console command list as of 2007-03-11: ++attack begin firing ++back move backward ++button3 activate button3 (behavior depends on mod) ++button4 activate button4 (behavior depends on mod) ++button5 activate button5 (behavior depends on mod) ++button6 activate button6 (behavior depends on mod) ++button7 activate button7 (behavior depends on mod) ++button8 activate button8 (behavior depends on mod) ++button9 activate button9 (behavior depends on mod) ++button10 activate button10 (behavior depends on mod) ++button11 activate button11 (behavior depends on mod) ++button12 activate button12 (behavior depends on mod) ++button13 activate button13 (behavior depends on mod) ++button14 activate button14 (behavior depends on mod) ++button15 activate button15 (behavior depends on mod) ++button16 activate button16 (behavior depends on mod) ++forward move forward ++jump jump ++klook activate keyboard looking mode, do not recenter view ++left turn left ++lookdown look downward ++lookup look upward ++mlook activate mouse looking mode, do not recenter view ++movedown swim downward ++moveleft strafe left ++moveright strafe right ++moveup swim upward ++right turn right ++showscores show scoreboard ++speed activate run mode (faster movement and turning) ++strafe activate strafing mode (move instead of turn) ++use use something (may be used by some mods) +-attack stop firing +-back stop moving backward +-button3 deactivate button3 +-button4 deactivate button4 +-button5 deactivate button5 +-button6 deactivate button6 +-button7 deactivate button7 +-button8 deactivate button8 +-button9 deactivate button9 +-button10 deactivate button10 +-button11 deactivate button11 +-button12 deactivate button12 +-button13 deactivate button13 +-button14 deactivate button14 +-button15 deactivate button15 +-button16 deactivate button16 +-forward stop moving forward +-jump end jump (so you can jump again) +-klook deactivate keyboard looking mode +-left stop turning left +-lookdown stop looking downward +-lookup stop looking upward +-mlook deactivate mouse looking mode +-movedown stop swimming downward +-moveleft stop strafing left +-moveright stop strafing right +-moveup stop swimming upward +-right stop turning right +-showscores hide scoreboard +-speed deactivate run mode +-strafe deactivate strafing mode +-use stop using something +alias create a script function (parameters are passed in as $1 through $9, and $* for all parameters) +begin signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received) +bestweapon send an impulse number to server to select the first usable weapon out of several (example: 8 7 6 5 4 3 2 1) +bf briefly flashes a bright color tint on view (used when items are picked up) +bind binds a command to the specified key in bindmap 0 +bottomcolor QW command to set bottom color without changing top color +cd execute a CD drive command (cd on/off/reset/remap/close/play/loop/stop/pause/resume/eject/info) - use cd by itself for usage +cddrive select an SDL-detected CD drive by number +centerview gradually recenter view (stop looking up/down) +changelevel change to another level, bringing along all connected clients +changing sent by qw servers to tell client to wait for level change +cycleweapon send an impulse number to server to select the next usable weapon out of several, or the first if you are not holding any (example: 8 7 3) +cl_areastats prints statistics on entity culling during collision traces +cl_begindownloads used internally by darkplaces client while connecting (causes loading of models and sounds or triggers downloads for missing ones) +cl_downloadbegin (networking) informs client of download file information, client replies with sv_startsoundload to begin the transfer +cl_downloadfinished signals that a download has finished and provides the client with file size and crc to check its integrity +cl_particles_reloadeffects reloads effectinfo.txt +clear clear console history +cmd send a console commandline to the server (used by some mods) +cmdlist lists all console commands beginning with the specified prefix +color change your player shirt and pants colors +condump output console history to a file (see also log_file) +connect connect to a server by IP address or hostname +curl download data from an URL and add to search path +cvar_lockdefaults stores the current values of all cvars into their default values, only used once during startup after parsing default.cfg +cvar_resettodefaults_all sets all cvars to their locked default values +cvar_resettodefaults_nosaveonly sets all non-saved cvars to their locked default values (variables that will not be saved to config.cfg) +cvar_resettodefaults_saveonly sets all saved cvars to their locked default values (variables that will be saved to config.cfg) +cvarlist lists all console variables beginning with the specified prefix +demos restart looping demos defined by the last startdemos command +dir list files in searchpath matching an * filename pattern, one per line +disconnect disconnect from server (or disconnect all clients if running a server) +download downloads a specified file from the server +echo print a message to the console (useful in scripts) +entities print information on network entities known to client +envmap render a cubemap (skybox) of the current scene +exec execute a script file +fly fly mode (flight) +fog set global fog parameters (density red green blue mindist maxdist) +force_centerview recenters view (stops looking up/down) +fs_rescan rescans filesystem for new pack archives and any other changes +fullinfo allows client to modify their userinfo +fullserverinfo internal use only, sent by server to client to update client's local copy of serverinfo string +gamedir changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf) +give alter inventory +gl_texturemode set texture filtering mode (GL_NEAREST, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, etc) +god god mode (invulnerability) +heartbeat send a heartbeat to the master server (updates your server information) +help open the help menu +impulse send an impulse number to server (select weapon, use item, etc) +in_bind binds a command to the specified key in the selected bindmap +in_bindmap selects active foreground and background (used only if a key is not bound in the foreground) bindmaps for typing +in_unbind removes command on the specified key in the selected bindmap +joyadvancedupdate applies current joyadv* cvar settings to the joystick driver +kick kick a player off the server by number or name +kill die instantly +load load a saved game file +loadconfig reset everything and reload configs +loadsky load a skybox by basename (for example loadsky mtnsun_ loads mtnsun_ft.tga and so on) +locs_add add a point or box location (usage: x y z[ x y z] \ +locs_clear remove all loc points/boxes +locs_reload reload .loc file for this map +locs_removenearest remove the nearest point or box (note: you need to be very near a box to remove it) +locs_save save .loc file for this map containing currently defined points and boxes +ls list files in searchpath matching an * filename pattern, multiple per line +map kick everyone off the server and start a new level +maps list information about available maps +maxplayers sets limit on how many players (or bots) may be connected to the server at once +memlist prints memory pool information (or if used as memlist 5 lists individual allocations of 5K or larger, 0 lists all allocations) +memstats prints memory system statistics +menu_credits open the credits menu +menu_fallback switch to engine menu (unload menu.dat) +menu_keys open the key binding menu +menu_load open the loadgame menu +menu_main open the main menu +menu_multiplayer open the multiplayer menu +menu_options open the options menu +menu_options_colorcontrol open the color control menu +menu_options_effects open the effects options menu +menu_options_graphics open the graphics options menu +menu_quit open the quit menu +menu_reset open the reset to defaults menu +menu_restart restart menu system (reloads menu.dat +menu_save open the savegame menu +menu_setup open the player setup menu +menu_singleplayer open the singleplayer menu +menu_transfusion_episode open the transfusion episode select menu +menu_transfusion_skill open the transfusion skill select menu +menu_video open the video options menu +messagemode input a chat message to say to everyone +messagemode2 input a chat message to say to only your team +modellist prints a list of loaded models +modelprecache load a model +name change your player name +net_slist query dp master servers and print all server information +net_slistqw query qw master servers and print all server information +net_stats print network statistics +nextul sends next fragment of current upload buffer (screenshot for example) +noclip noclip mode (flight without collisions, move through walls) +notarget notarget mode (monsters do not see you) +packet send a packet to the specified address:port containing a text string +path print searchpath (game directories and archives) +pause pause the game (if the server allows pausing) +pausedemo pause demo playback (can also safely pause demo recording if using QUAKE, QUAKEDP or NEHAHRAMOVIE protocol, useful for making movies) +ping print ping times of all players on the server +pingplreport command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers) +pings command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers) +play play a sound at your current location (not heard by anyone else) +play2 play a sound globally throughout the level (not heard by anyone else) +playdemo watch a demo file +playermodel change your player model +playerskin change your player skin number +playvideo play a .dpv video file +playvol play a sound at the specified volume level at your current location (not heard by anyone else) +pmodel change your player model choice (Nehahra specific) +pointfile display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level) +prespawn signon 1 (client acknowledges that server information has been received) +prvm_edict print all data about an entity number in the selected VM (server, client, menu) +prvm_edictcount prints number of active entities in the selected VM (server, client, menu) +prvm_edicts set a property on an entity number in the selected VM (server, client, menu) +prvm_edictset changes value of a specified property of a specified entity in the selected VM (server, client, menu) +prvm_fields prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu) +prvm_global prints value of a specified global variable in the selected VM (server, client, menu) +prvm_globals prints all global variables in the selected VM (server, client, menu) +prvm_globalset sets value of a specified global variable in the selected VM (server, client, menu) +prvm_printfunction prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, menu) +prvm_profile prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu) +quit quit the game +r_editlights_clear removes all world lights (let there be darkness!) +r_editlights_copyinfo store a copy of all properties (except origin) of the selected light +r_editlights_edit changes a property on the selected light +r_editlights_editall changes a property on ALL lights at once (tip: use radiusscale and colorscale to alter these properties) +r_editlights_help prints documentation on console commands and variables in rtlight editing system +r_editlights_importlightentitiesfrommap load lights from .ent file or map entities (ignoring .rtlights or .lights file) +r_editlights_importlightsfile load lights from .lights file (ignoring .rtlights or .ent files and map entities) +r_editlights_pasteinfo apply the stored properties onto the selected light (making it exactly identical except for origin) +r_editlights_reload reloads rtlights file (or imports from .lights file or .ent file or the map itself) +r_editlights_remove remove selected light +r_editlights_save save .rtlights file for current level +r_editlights_spawn creates a light with default properties (let there be light!) +r_editlights_togglecorona toggle on/off the corona option on the selected light +r_editlights_toggleshadow toggle on/off the shadow option on the selected light +r_glsl_restart unloads GLSL shaders, they will then be reloaded as needed +r_listmaptextures list all textures used by the current map +r_replacemaptexture override a map texture for testing purposes +r_restart restarts renderer +r_shadow_help prints documentation on console commands and variables used by realtime lighting and shadowing system +r_texturestats print information about all loaded textures and some statistics +rate change your network connection speed +rcon sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's) +reconnect reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server) +record record a demo +restart restart current level +save save the game to a file +saveconfig save settings to config.cfg immediately (also automatic when quitting) +say send a chat message to everyone on the server +say_team send a chat message to your team on the server +screenshot takes a screenshot of the next rendered frame +sendcvar sends the value of a cvar to the server as a sentcvar command, for use by QuakeC +set create or change the value of a console variable +seta create or change the value of a console variable that will be saved to config.cfg +setinfo modifies your userinfo +sizedown decrease view size (decreases viewsize cvar) +sizeup increase view size (increases viewsize cvar) +skins downloads missing qw skins from server +snd_unloadallsounds unload all sound files +snd_restart restart sound system +soundinfo print sound system information (such as channels and speed) +soundlist list loaded sounds +spawn signon 2 (client has sent player information, and is asking server to send scoreboard rankings) +startdemos start playing back the selected demos sequentially (used at end of startup script) +status print server status information +stop stop recording or playing a demo +stopdemo stop playing or recording demo (like stop command) and return to looping demos +stopdownload terminates a download +stopsound silence +stopul aborts current upload (screenshot for example) +stopvideo stop playing a .dpv video file +stuffcmds execute commandline parameters (must be present in quake.rc script) +sv_areastats prints statistics on entity culling during collision traces +sv_saveentfile save map entities to .ent file (to allow external editing) +sv_startdownload begins sending a file to the client (network protocol use only) +tell send a chat message to only one person on the server +timedemo play back a demo as fast as possible and save statistics to benchmark.log +timerefresh turn quickly and print rendering statistcs +toggle toggles a console variable's values (use for more info) +toggleconsole opens or closes the console +togglemenu opens or closes menu +topcolor QW command to set top color without changing bottom color +unbind removes a command on the specified key in bindmap 0 +unbindall removes all commands from all keys in all bindmaps (leaving only shift-escape and escape) +user prints additional information about a player number or name on the scoreboard +users prints additional information about all players on the scoreboard +v_cshift sets tint color of view +version print engine version +vid_restart restarts video system (closes and reopens the window, restarts renderer) +viewframe change animation frame of viewthing entity in current level +viewmodel change model of viewthing entity in current level +viewnext change to next animation frame of viewthing entity in current level +viewprev change to previous animation frame of viewthing entity in current level +wait make script execution wait for next rendered frame +which accepts a file name as argument and reports where the file is taken from + + +How to install Quake on Windows: +All that DarkPlaces needs from the Quake CD is pak files (be sure not to copy opengl32.dll from the Quake CD, it will not work with DarkPlaces!), with this in mind, all you need to do is make a Quake directory, extract the darkplaces engine zip to that directory, then make a quake/id1 directory, and put the pak0.pak and pak1.pak from your Quake CD into the quake/id1 directory, then all should be well. + +How to deal with a DOS Quake CD on Windows: +Try to use the DOS Quake installer if you can, use DOSBox if necessary to run the installer, then copy the pak0.pak and pak1.pak to your id1 directory in the darkplaces installation. ( http://dosbox.sourceforge.net ) + +How to deal with a WinQuake CD on Windows: +Copy the D:\Data\id1\pak0.pak and pak1.pak to your id1 directory. + +How to deal with a Linux Quake CD on Windows: +Find an archiver (perhaps 7zip or winrar) that can extract files from rpm archives, locate the pak files and copy them to your id1 directory. + + + +How to install Quake on Linux: +All that DarkPlaces needs from the Quake CD is pak files, with this in mind, all you need to do is make a ~/quake directory, extract the darkplaces engine zip to that directory, then make a quake/id1 directory, and put the pak0.pak and pak1.pak from your Quake CD into the quake/id1 directory, then all should be well, you will probably also want to make a ~/bin/darkplaces script containing the following text: +#!/bin/sh +cd ~/quake +./darkplaces-sdl $* +Then do chmod +x ~/bin/darkplaces + +For more information on Quake installation on Linux see http://www.tldp.org/HOWTO/Quake-HOWTO.html" (the Linux Quake How To) + +How to deal with a DOS Quake CD on Linux: +cat /media/cdrom/resource.001 /media/cdrom/resource.002 >quake.lha +unlha x quake.lha +If you can't get unlha or lha for your distribution, try using DOSBox to run the Quake installer. + +How to deal with a WinQuake CD on Linux: +Copy the /media/cdrom/data/id1/pak*.pak to your id1 directory. + +How to deal with a Linux Quake CD on Linux: +mkdir temp +cd temp +# in the following line replace quake.rpm with a correct rpm filename +cat /media/cdrom/quake.rpm | rpm2cpio | cpio -i +Now you should have a mess of subdirectories, locate the pak files and copy to your id1 directory. +Alternatively if you have an rpm-based distribution you could install the rpm, but it is easier to maintain your ~/quake directory than /usr/share/games/quake so you may want to copy the id1/pak*.pak from there and uninstall the rpm. + + + +How to install Quake on Mac OS X: +All that DarkPlaces needs from the Quake CD is pak files, with this in mind, make a folder named Quake, drag the Darkplaces.app into this Quake folder, make a folder inside the Quake folder (alongside Darkplaces) named id1, and put the pak0.pak and pak1.pak from your Quake CD into the quake/id1 directory, then all should be well, simply run the Darkplaces app + +How to deal with a DOS Quake CD on Mac OS X: +Unknown. Linux solution may work if you can get hold of lha, otherwise use DOSBox to run the Quake installer. + +How to deal with a WinQuake CD on Mac OS X: +Find the data folder on the cdrom and copy the data/id1/pak*.pak files to your id1 folder. + +How to deal with a Linux Quake CD on Mac OS X: +Unknown. If you can get hold of rpm2cpio and cpio you should be able to follow the Linux method. + + +Shader parameters for DP's own features: +- dp_reflect + Makes surfaces of this shader reflective with r_water. The reflection is + alpha blended on the texture with the given alpha, and modulated by the given + color. distort is used in conjunction with the normalmap to simulate a + nonplanar water surface. +- dp_refract + Makes surfaces of this shader refractive with r_water. The refraction + replaces the transparency of the texture. distort is used in conjunction with + the normalmap to simulate a nonplanar water surface. +- dp_water + This combines the effects of dp_reflect and dp_refract to simulate a water + surface. However, the refraction and the reflection are mixed using a Fresnel + equation that makes the amount of reflection slide from reflectmin when + looking parallel to the water to reflectmax when looking directly into the + water. The result of this reflection/refraction mix is then layered BELOW the + texture of the shader, so basically, it "fills up" the alpha values of the + water. The alpha value is a multiplicator for the alpha value on the texture + - set this to a small value like 0.1 to emphasize the reflection and make + the water transparent; but if r_water is 0, alpha isn't used, so the water can + be very visible then too. +- tcmod page + The texture is shifted by 1/ every seconds, and by 1/ + every * seconds. It is some sort of animmap replacement that keeps + all animation frames in a single texture. + To use it, make a texture with the frames aligned in a grid like this: + 1 2 3 4 + 5 6 7 8 + then align it in Radiant so only one of the animation frames can be seen on + the surface, and specify "tcmod page 4 2 0.1". DP will then display the frames + in order and the cycle will repeat every 0.8 seconds. +- dppolygonoffset + This surface gets glPolygonOffset(factor, offset); useful for decals + + +Thanks to: +Tomaz for adding features, fixing many bugs, and being generally helpful. +Andreas 'Black' Kirsch for much work on the QuakeC VM (menu.dat, someday clprogs.dat) and other contributions. +Mathieu 'Elric' Olivier for much work on the sound engine (especially the Ogg vorbis support) +MoALTz for some bugfixes and cleanups +Joseph Caporale for adding 5 mouse button support. +KGB|romi for his contributions to the Quake community, including his rtlights project and many suggestions, his id1 romi_rtlights.pk3 is included in darkplaces mod releases. +Zombie for making great levels and general DarkPlaces publicity. +FrikaC for FrikQCC and FrikBot and general community support. +Transfusion Project for recreating Blood in the world of Quake. +de-we for the great icons. +|Rain| for running my favorite anynet IRC server and his bot feh (which although a bit antisocial never seems to grow tired of being my calculator). +VorteX for the DP_QC_GETTAGINFO extension. +Ludwig Nussel for the ~/.games/darkplaces/ user directory support on non-Windows platforms (allowing games to be installed in a non-writable system location as is the standard on UNIX but still save configs to the user's home directory). diff --git a/misc/source/darkplaces-src/darkplaces.xpm b/misc/source/darkplaces-src/darkplaces.xpm new file mode 100644 index 00000000..befce123 --- /dev/null +++ b/misc/source/darkplaces-src/darkplaces.xpm @@ -0,0 +1,142 @@ +/* XPM */ +static char * darkplaces_xpm[] = { +"48 48 91 1", +" c None", +". c #020300", +"+ c #120303", +"@ c #180202", +"# c #200301", +"$ c #340100", +"% c #410000", +"& c #2D0704", +"* c #11120F", +"= c #4F0701", +"- c #3F0F00", +"; c #251615", +"> c #780000", +", c #2D1514", +"' c #1D1C1A", +") c #680F01", +"! c #431A10", +"~ c #3B221D", +"{ c #2B2725", +"] c #601B02", +"^ c #9D0A00", +"/ c #55220A", +"( c #3D2B25", +"_ c #53291D", +": c #4E2E1B", +"< c #333432", +"[ c #4C2F26", +"} c #812301", +"| c #3F332F", +"1 c #6B321A", +"2 c #3F3F3D", +"3 c #7E320E", +"4 c #563B35", +"5 c #4B3E39", +"6 c #533F39", +"7 c #6D3A2F", +"8 c #943508", +"9 c #514541", +"0 c #654132", +"a c #474845", +"b c #6D442B", +"c c #7C4328", +"d c #504F4D", +"e c #674A43", +"f c #644F47", +"g c #5E514B", +"h c #B64005", +"i c #91472F", +"j c #575654", +"k c #AD4A11", +"l c #82533C", +"m c #5F5E5B", +"n c #745C54", +"o c #6C5E5A", +"p c #A25329", +"q c #825C54", +"r c #676664", +"s c #816A62", +"t c #70706E", +"u c #986951", +"v c #7D706C", +"w c #C66935", +"x c #7E7C79", +"y c #FA6400", +"z c #937B73", +"A c #9E796A", +"B c #B97557", +"C c #A67B5D", +"D c #878581", +"E c #8F8D8B", +"F c #AF8A7D", +"G c #A58D83", +"H c #9C8F8C", +"I c #979693", +"J c #B79590", +"K c #A59F9C", +"L c #DD9472", +"M c #B39D97", +"N c #EB9462", +"O c #C1A391", +"P c #D19F8D", +"Q c #D1A184", +"R c #B6B0AD", +"S c #C8C2BF", +"T c #DDC0BC", +"U c #E1C2B0", +"V c #F8BE99", +"W c #DDD3CE", +"X c #EFE4E0", +"Y c #FEE4C7", +"Z c #FDFEFA", +" ", +" ID ", +" tvr ", +" 59aI ", +" 4v2t ", +" 4I2r ", +" E[Xaj ", +" z7Xda RHrqsE ", +" f2XjaD Itj93][zH ", +" 92Wd2x Ixja2,pQDI ", +" 92Xx2t Irjb&FIx ", +" 5aZx5r Dj{,ORx ", +" xsja,eXt2r t'+XIt ", +" Mu/+|<<;[Xr2r j!AXjt ", +" Kq3=[222a,5Xt2r 5;SSdI ", +" H;0A5222j ~oZr2t t+CWjr ", +" z37(2<2ax float(string key) stringtokeynum = #341; +// string(float keynum) keynumtostring = #340; +//description: key bind setting/getting including support for switchable +//bindmaps. + +//DP_CRYPTO +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: (CSQC) +float(string url, float id, string content_type, string delim, float buf, float keyid) crypto_uri_postbuf = #513; +//description: +//use -1 as buffer handle to justs end delim as postdata diff --git a/misc/source/darkplaces-src/dpdefs/dpextensions.qc b/misc/source/darkplaces-src/dpdefs/dpextensions.qc new file mode 100644 index 00000000..1aba127a --- /dev/null +++ b/misc/source/darkplaces-src/dpdefs/dpextensions.qc @@ -0,0 +1,2443 @@ + +//DarkPlaces supported extension list, draft version 1.04 + +//things that don't have extensions yet: +.float disableclientprediction; + +//definitions that id Software left out: +//these are passed as the 'nomonsters' parameter to traceline/tracebox (yes really this was supported in all quake engines, nomonsters is misnamed) +float MOVE_NORMAL = 0; // same as FALSE +float MOVE_NOMONSTERS = 1; // same as TRUE +float MOVE_MISSILE = 2; // save as movement with .movetype == MOVETYPE_FLYMISSILE + +//checkextension function +//idea: expected by almost everyone +//darkplaces implementation: LordHavoc +float(string s) checkextension = #99; +//description: +//check if (cvar("pr_checkextension")) before calling this, this is the only +//guaranteed extension to be present in the extension system, it allows you +//to check if an extension is available, by name, to check for an extension +//use code like this: +//// (it is recommended this code be placed in worldspawn or a worldspawn called function somewhere) +//if (cvar("pr_checkextension")) +//if (checkextension("DP_SV_SETCOLOR")) +// ext_setcolor = TRUE; +//from then on you can check ext_setcolor to know if that extension is available + +//BX_WAL_SUPPORT +//idea: id Software +//darkplaces implementation: LordHavoc +//description: +//indicates the engine supports .wal textures for filenames in the textures/ directory +//(note: DarkPlaces has supported this since 2001 or 2002, but did not advertise it as an extension, then I noticed Betwix was advertising it and added the extension accordingly) + +//DP_BUTTONCHAT +//idea: Vermeulen +//darkplaces implementation: LordHavoc +//field definitions: +.float buttonchat; +//description: +//true if the player is currently chatting (in messagemode, menus or console) + +//DP_BUTTONUSE +//idea: id Software +//darkplaces implementation: LordHavoc +//field definitions: +.float buttonuse; +//client console commands: +//+use +//-use +//description: +//made +use and -use commands work, they now control the .buttonuse field (.button1 was used by many mods for other purposes). + +//DP_CL_LOADSKY +//idea: Nehahra, LordHavoc +//darkplaces implementation: LordHavoc +//client console commands: +//"loadsky" (parameters: "basename", example: "mtnsun_" would load "mtnsun_up.tga" and "mtnsun_rt.tga" and similar names, use "" to revert to quake sky, note: this is the same as Quake2 skybox naming) +//description: +//sets global skybox for the map for this client (can be stuffed to a client by QC), does not hurt much to repeatedly execute this command, please don't use this in mods if it can be avoided (only if changing skybox is REALLY needed, otherwise please use DP_GFX_SKYBOX). + +//DP_CON_SET +//idea: id Software +//darkplaces implementation: LordHavoc +//description: +//indicates this engine supports the "set" console command which creates or sets a non-archived cvar (not saved to config.cfg on exit), it is recommended that set and seta commands be placed in default.cfg for mod-specific cvars. + +//DP_CON_SETA +//idea: id Software +//darkplaces implementation: LordHavoc +//description: +//indicates this engine supports the "seta" console command which creates or sets an archived cvar (saved to config.cfg on exit), it is recommended that set and seta commands be placed in default.cfg for mod-specific cvars. + +//DP_CON_ALIASPARAMETERS +//idea: many +//darkplaces implementation: Black +//description: +//indicates this engine supports aliases containing $1 through $9 parameter macros (which when called will expand to the parameters passed to the alias, for example alias test "say $2 $1", then you can type test hi there and it will execute say there hi), as well as $0 (name of the alias) and $* (all parameters $1 onward). + +//DP_CON_EXPANDCVAR +//idea: many, PHP +//darkplaces implementation: Black +//description: +//indicates this engine supports console commandlines containing $cvarname which will expand to the contents of that cvar as a parameter, for instance say my fov is $fov, will say "my fov is 90", or similar. + +//DP_CON_STARTMAP +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//adds two engine-called aliases named startmap_sp and startmap_dm which are called when the engine tries to start a singleplayer game from the menu (startmap_sp) or the -listen or -dedicated options are used or the engine is a dedicated server (uses startmap_dm), these allow a mod or game to specify their own map instead of start, and also distinguish between singleplayer and -listen/-dedicated, also these need not be a simple "map start" command, they can do other things if desired, startmap_sp and startmap_dm both default to "map start". + +//DP_EF_ADDITIVE +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//effects bit: +float EF_ADDITIVE = 32; +//description: +//additive blending when this object is rendered + +//DP_EF_BLUE +//idea: id Software +//darkplaces implementation: LordHavoc +//effects bit: +float EF_BLUE = 64; +//description: +//entity emits blue light (used for quad) + +//DP_EF_DOUBLESIDED +//idea: LordHavoc +//darkplaces implementation: [515] and LordHavoc +//effects bit: +float EF_DOUBLESIDED = 32768; +//description: +//render entity as double sided (backfaces are visible, I.E. you see the 'interior' of the model, rather than just the front), can be occasionally useful on transparent stuff. + +//DP_EF_FLAME +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//effects bit: +float EF_FLAME = 1024; +//description: +//entity is on fire + +//DP_EF_FULLBRIGHT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//effects bit: +float EF_FULLBRIGHT = 512; +//description: +//entity is always brightly lit + +//DP_EF_NODEPTHTEST +//idea: Supa +//darkplaces implementation: LordHavoc +//effects bit: +float EF_NODEPTHTEST = 8192; +//description: +//makes entity show up to client even through walls, useful with EF_ADDITIVE for special indicators like where team bases are in a map, so that people don't get lost + +//DP_EF_NODRAW +//idea: id Software +//darkplaces implementation: LordHavoc +//effects bit: +float EF_NODRAW = 16; +//description: +//prevents server from sending entity to client (forced invisible, even if it would have been a light source or other such things) + +//DP_EF_NOGUNBOB +//idea: Chris Page, Dresk +//darkplaces implementation: LordHAvoc +//effects bit: +float EF_NOGUNBOB = 256; +//description: +//this has different meanings depending on the entity it is used on: +//player entity - prevents gun bobbing on player.viewmodel +//viewmodelforclient entity - prevents gun bobbing on an entity attached to the player's view +//other entities - no effect +//uses: +//disabling gun bobbing on a diving mask or other model used as a .viewmodel. +//disabling gun bobbing on view-relative models meant to be part of the heads up display. (note: if fov is changed these entities may be off-screen, or too near the center of the screen, so use fov 90 in this case) + +//DP_EF_NOSHADOW +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//effects bit: +float EF_NOSHADOW = 4096; +//description: +//realtime lights will not cast shadows from this entity (but can still illuminate it) + +//DP_EF_RED +//idea: id Software +//darkplaces implementation: LordHavoc +//effects bit: +float EF_RED = 128; +//description: +//entity emits red light (used for invulnerability) + +//DP_EF_RESTARTANIM_BIT +//idea: id software +//darkplaces implementation: divVerent +//effects bit: +float EF_RESTARTANIM_BIT = 1048576; +//description: +//when toggled, the current animation is restarted. Useful for weapon animation. +//to toggle this bit in QC, you can do: +// self.effects += (EF_RESTARTANIM_BIT - 2 * (self.effects & EF_RESTARTANIM_BIT)); + +//DP_EF_STARDUST +//idea: MythWorks Inc +//darkplaces implementation: LordHavoc +//effects bit: +float EF_STARDUST = 2048; +//description: +//entity emits bouncing sparkles in every direction + +//DP_EF_TELEPORT_BIT +//idea: id software +//darkplaces implementation: divVerent +//effects bit: +float EF_TELEPORT_BIT = 2097152; +//description: +//when toggled, interpolation of the entity is skipped for one frame. Useful for teleporting. +//to toggle this bit in QC, you can do: +// self.effects += (EF_TELEPORT_BIT - 2 * (self.effects & EF_TELEPORT_BIT)); + +//DP_ENT_ALPHA +//idea: Nehahra +//darkplaces implementation: LordHavoc +//fields: +.float alpha; +//description: +//controls opacity of the entity, 0.0 is forced to be 1.0 (otherwise everything would be invisible), use -1 if you want to make something invisible, 1.0 is solid (like normal). + +//DP_ENT_COLORMOD +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definition: +.vector colormod; +//description: +//controls color of the entity, '0 0 0', is forced to be '1 1 1' (otherwise everything would be black), used for tinting objects, for instance using '1 0.6 0.4' on an ogre would give you an orange ogre (order is red green blue), note the colors can go up to '8 8 8' (8x as bright as normal). + +//DP_ENT_CUSTOMCOLORMAP +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//if .colormap is set to 1024 + pants + shirt * 16, those colors will be used for colormapping the entity, rather than looking up a colormap by player number. + +/* +//NOTE: no longer supported by darkplaces because all entities are delta compressed now +//DP_ENT_DELTACOMPRESS // no longer supported +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//effects bit: +float EF_DELTA = 8388608; +//description: +//(obsolete) applies delta compression to the network updates of the entity, making updates smaller, this might cause some unreliable behavior in packet loss situations, so it should only be used on numerous (nails/plasma shots/etc) or unimportant objects (gibs/shell casings/bullet holes/etc). +*/ + +//DP_ENT_EXTERIORMODELTOCLIENT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//fields: +.entity exteriormodeltoclient; +//description: +//the entity is visible to all clients with one exception: if the specified client is using first person view (not using chase_active) the entity will not be shown. Also if tag attachments are supported any entities attached to the player entity will not be drawn in first person. + +//DP_ENT_GLOW +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.float glow_color; +.float glow_size; +.float glow_trail; +//description: +//customizable glowing light effect on the entity, glow_color is a paletted (8bit) color in the range 0-255 (note: 0 and 254 are white), glow_size is 0 or higher (up to the engine what limit to cap it to, darkplaces imposes a 1020 limit), if glow_trail is true it will leave a trail of particles of the same color as the light. + +//DP_ENT_GLOWMOD +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definition: +.vector glowmod; +//description: +//controls color of the entity's glow texture (fullbrights), '0 0 0', is forced to be '1 1 1' (otherwise everything would be black), used for tinting objects, see colormod (same color restrictions apply). + +//DP_ENT_LOWPRECISION +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//effects bit: +float EF_LOWPRECISION = 4194304; +//description: +//uses low quality origin coordinates, reducing network traffic compared to the default high precision, intended for numerous objects (projectiles/gibs/bullet holes/etc). + +//DP_ENT_SCALE +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.float scale; +//description: +//controls rendering scale of the object, 0 is forced to be 1, darkplaces uses 1/16th accuracy and a limit of 15.9375, can be used to make an object larger or smaller. + +//DP_ENT_TRAILEFFECTNUM +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.float traileffectnum; +//description: +//use a custom effectinfo.txt effect on this entity, assign it like this: +//self.traileffectnum = particleeffectnum("mycustomeffect"); +//this will do both the dlight and particle trail as described in the effect, basically equivalent to trailparticles() in CSQC but performed on a server entity. + +//DP_ENT_VIEWMODEL +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.entity viewmodelforclient; +//description: +//this is a very special capability, attachs the entity to the view of the client specified, origin and angles become relative to the view of that client, all effects can be used (multiple skins on a weapon model etc)... the entity is not visible to any other client. + +//DP_GFX_EXTERNALTEXTURES +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//loads external textures found in various directories (tenebrae compatible)... +/* +in all examples .tga is merely the base texture, it can be any of these: +.tga (base texture) +_glow.tga (fullbrights or other glowing overlay stuff, NOTE: this is done using additive blend, not alpha) +_pants.tga (pants overlay for colormapping on models, this should be shades of grey (it is tinted by pants color) and black wherever the base texture is not black, as this is an additive blend) +_shirt.tga (same idea as pants, but for shirt color) +_diffuse.tga (this may be used instead of base texture for per pixel lighting) +_gloss.tga (specular texture for per pixel lighting, note this can be in color (tenebrae only supports greyscale)) +_norm.tga (normalmap texture for per pixel lighting) +_bump.tga (bumpmap, converted to normalmap at load time, supported only for reasons of tenebrae compatibility) +_luma.tga (same as _glow but supported only for reasons of tenebrae compatibility) + +due to glquake's incomplete Targa(r) loader, this section describes +required Targa(r) features support: +types: +type 1 (uncompressed 8bit paletted with 24bit/32bit palette) +type 2 (uncompressed 24bit/32bit true color, glquake supported this) +type 3 (uncompressed 8bit greyscale) +type 9 (RLE compressed 8bit paletted with 24bit/32bit palette) +type 10 (RLE compressed 24bit/32bit true color, glquake supported this) +type 11 (RLE compressed 8bit greyscale) +attribute bit 0x20 (Origin At Top Left, top to bottom, left to right) + +image formats guaranteed to be supported: tga, pcx, lmp +image formats that are optional: png, jpg + +mdl/spr/spr32 examples: +skins are named _A (A being a number) and skingroups are named like _A_B +these act as suffixes on the model name... +example names for skin _2_1 of model "progs/armor.mdl": +game/override/progs/armor.mdl_2_1.tga +game/textures/progs/armor.mdl_2_1.tga +game/progs/armor.mdl_2_1.tga +example names for skin _0 of the model "progs/armor.mdl": +game/override/progs/armor.mdl_0.tga +game/textures/progs/armor.mdl_0.tga +game/progs/armor.mdl_0.tga +note that there can be more skins files (of the _0 naming) than the mdl +contains, this is only useful to save space in the .mdl file if classic quake +compatibility is not a concern. + +bsp/md2/md3 examples: +example names for the texture "quake" of model "maps/start.bsp": +game/override/quake.tga +game/textures/quake.tga +game/quake.tga + +sbar/menu/console textures: for example the texture "conchars" (console font) in gfx.wad +game/override/gfx/conchars.tga +game/textures/gfx/conchars.tga +game/gfx/conchars.tga +*/ + +//DP_GFX_EXTERNALTEXTURES_PERMAPTEXTURES +//idea: Fuh? +//darkplaces implementation: LordHavoc +//description: +//Q1BSP and HLBSP map loading loads external textures found in textures// as well as textures/. +//Where mapname is the bsp filename minus the extension (typically .bsp) and minus maps/ if it is in maps/ (any other path is not removed) +//example: +//maps/e1m1.bsp uses textures in the directory textures/e1m1/ and falls back to textures/ +//maps/b_batt0.bsp uses textures in the directory textures/b_batt0.bsp and falls back to textures/ +//as a more extreme example: +//progs/something/blah.bsp uses textures in the directory textures/progs/something/blah/ and falls back to textures/ + +//DP_GFX_FOG +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//worldspawn fields: +//"fog" (parameters: "density red green blue", example: "0.1 0.3 0.3 0.3") +//description: +//global fog for the map, can not be changed by QC + +//DP_GFX_QUAKE3MODELTAGS +//idea: id Software +//darkplaces implementation: LordHavoc +//field definitions: +.entity tag_entity; // entity this is attached to (call setattachment to set this) +.float tag_index; // which tag on that entity (0 is relative to the entity, > 0 is an index into the tags on the model if it has any) (call setattachment to set this) +//builtin definitions: +void(entity e, entity tagentity, string tagname) setattachment = #443; // attachs e to a tag on tagentity (note: use "" to attach to entity origin/angles instead of a tag) +//description: +//allows entities to be visually attached to model tags (which follow animations perfectly) on other entities, for example attaching a weapon to a player's hand, or upper body attached to lower body, allowing it to change angles and frame separately (note: origin and angles are relative to the tag, use '0 0 0' for both if you want it to follow exactly, this is similar to viewmodelforclient's behavior). +//note 2: if the tag is not found, it defaults to "" (attach to origin/angles of entity) +//note 3: attaching to world turns off attachment +//note 4: the entity that this is attached to must be visible for this to work +//note 5: if an entity is attached to the player entity it will not be drawn in first person. + +//DP_GFX_SKINFILES +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//alias models (mdl, md2, md3) can have .skin files to replace conventional texture naming, these have a naming format such as: +//progs/test.md3_0.skin +//progs/test.md3_1.skin +//... +// +//these files contain replace commands (replace meshname shadername), example: +//replace "helmet" "progs/test/helmet1.tga" // this is a mesh shader replacement +//replace "teamstripes" "progs/test/redstripes.tga" +//replace "visor" "common/nodraw" // this makes the visor mesh invisible +////it is not possible to rename tags using this format +// +//Or the Quake3 syntax (100% compatible with Quake3's .skin files): +//helmet,progs/test/helmet1.tga // this is a mesh shader replacement +//teamstripes,progs/test/redstripes.tga +//visor,common/nodraw // this makes the visor mesh invisible +//tag_camera, // this defines that the first tag in the model is called tag_camera +//tag_test, // this defines that the second tag in the model is called tag_test +// +//any names that are not replaced are automatically show up as a grey checkerboard to indicate the error status, and "common/nodraw" is a special case that is invisible. +//this feature is intended to allow multiple skin sets on md3 models (which otherwise only have one skin set). +//other commands might be added someday but it is not expected. + +//DP_GFX_SKYBOX +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//worldspawn fields: +//"sky" (parameters: "basename", example: "mtnsun_" would load "mtnsun_up.tga" and "mtnsun_rt.tga" and similar names, note: "sky" is also used the same way by Quake2) +//description: +//global skybox for the map, can not be changed by QC + +//DP_UTF8 +//idea: Blub\0, divVerent +//darkplaces implementation: Blub\0 +//cvar definitions: +// utf8_enable: enable utf8 encoding +//description: utf8 characters are allowed inside cvars, protocol strings, files, progs strings, etc., +//and count as 1 char for string functions like strlen, substring, etc. +// note: utf8_enable is run-time cvar, could be changed during execution +// note: beware that str2chr() could return value bigger than 255 once utf8 is enabled + +//DP_HALFLIFE_MAP +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//simply indicates that the engine supports HalfLife maps (BSP version 30, NOT the QER RGBA ones which are also version 30). + +//DP_HALFLIFE_MAP_CVAR +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//cvars: +//halflifebsp 0/1 +//description: +//engine sets this cvar when loading a map to indicate if it is halflife format or not. + +//DP_HALFLIFE_SPRITE +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//simply indicates that the engine supports HalfLife sprites. + +//DP_INPUTBUTTONS +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.float button3; +.float button4; +.float button5; +.float button6; +.float button7; +.float button8; +.float button9; +.float button10; +.float button11; +.float button12; +.float button13; +.float button14; +.float button15; +.float button16; +//description: +//set to the state of the +button3, +button4, +button5, +button6, +button7, and +button8 buttons from the client, this does not involve protocol changes (the extra 6 button bits were simply not used). +//the exact mapping of protocol button bits on the server is: +//self.button0 = (bits & 1) != 0; +///* button1 is skipped because mods abuse it as a variable, and accordingly it has no bit */ +//self.button2 = (bits & 2) != 0; +//self.button3 = (bits & 4) != 0; +//self.button4 = (bits & 8) != 0; +//self.button5 = (bits & 16) != 0; +//self.button6 = (bits & 32) != 0; +//self.button7 = (bits & 64) != 0; +//self.button8 = (bits & 128) != 0; + +// DP_LIGHTSTYLE_STATICVALUE +// idea: VorteX +// darkplaces implementation: VorteX +// description: allows alternative 'static' lightstyle syntax : "=value" +// examples: "=0.5", "=2.0", "=2.75" +// could be used to control switchable lights or making styled lights with brightness > 2 +// Warning: this extension is experimental. It safely works in CSQC, but SVQC use is limited by the fact +// that other engines (which do not support this extension) could connect to a game and misunderstand this kind of lightstyle syntax + +//DP_LITSPRITES +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//indicates this engine supports lighting on sprites, any sprite with ! in its filename (both on disk and in the qc) will be lit rather than having forced EF_FULLBRIGHT (EF_FULLBRIGHT on the entity can still force these sprites to not be lit). + +//DP_LITSUPPORT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//indicates this engine loads .lit files for any quake1 format .bsp files it loads to enhance maps with colored lighting. +//implementation description: these files begin with the header QLIT followed by version number 1 (as little endian 32bit), the rest of the file is a replacement lightmaps lump, except being 3x as large as the lightmaps lump of the map it matches up with (and yes the between-lightmap padding is expanded 3x to keep this consistent), so the lightmap offset in each surface is simply multiplied by 3 during loading to properly index the lit data, and the lit file is loaded instead of the lightmap lump, other renderer changes are needed to display these of course... see the litsupport.zip sample code (almost a tutorial) at http://icculus.org/twilight/darkplaces for more information. + +//DP_MONSTERWALK +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//MOVETYPE_WALK is permitted on non-clients, so bots can move smoothly, run off ledges, etc, just like a real player. + +//DP_MOVETYPEBOUNCEMISSILE +//idea: id Software +//darkplaces implementation: id Software +//movetype definitions: +//float MOVETYPE_BOUNCEMISSILE = 11; // already in defs.qc +//description: +//MOVETYPE_BOUNCE but without gravity, and with full reflection (no speed loss like grenades have), in other words - bouncing laser bolts. + +//DP_NULL_MODEL +//idea: Chris +//darkplaces implementation: divVerent +//definitions: +//string dp_null_model = "null"; +//description: +//setmodel(e, "null"); makes an entity invisible, have a zero bbox, but +//networked. useful for shared CSQC entities. + +//DP_MOVETYPEFOLLOW +//idea: id Software, LordHavoc (redesigned) +//darkplaces implementation: LordHavoc +//movetype definitions: +float MOVETYPE_FOLLOW = 12; +//description: +//MOVETYPE_FOLLOW implemented, this uses existing entity fields in unusual ways: +//aiment - the entity this is attached to. +//punchangle - the original angles when the follow began. +//view_ofs - the relative origin (note that this is un-rotated by punchangle, and that is actually the only purpose of punchangle). +//v_angle - the relative angles. +//here's an example of how you would set a bullet hole sprite to follow a bmodel it was created on, even if the bmodel rotates: +//hole.movetype = MOVETYPE_FOLLOW; // make the hole follow +//hole.solid = SOLID_NOT; // MOVETYPE_FOLLOW is always non-solid +//hole.aiment = bmodel; // make the hole follow bmodel +//hole.punchangle = bmodel.angles; // the original angles of bmodel +//hole.view_ofs = hole.origin - bmodel.origin; // relative origin +//hole.v_angle = hole.angles - bmodel.angles; // relative angles + +//DP_QC_ASINACOSATANATAN2TAN +//idea: Urre +//darkplaces implementation: LordHavoc +//constant definitions: +float DEG2RAD = 0.0174532925199432957692369076848861271344287188854172545609719144; +float RAD2DEG = 57.2957795130823208767981548141051703324054724665643215491602438612; +float PI = 3.1415926535897932384626433832795028841971693993751058209749445923; +//builtin definitions: +float(float s) asin = #471; // returns angle in radians for a given sin() value, the result is in the range -PI*0.5 to PI*0.5 +float(float c) acos = #472; // returns angle in radians for a given cos() value, the result is in the range 0 to PI +float(float t) atan = #473; // returns angle in radians for a given tan() value, the result is in the range -PI*0.5 to PI*0.5 +float(float c, float s) atan2 = #474; // returns angle in radians for a given cos() and sin() value pair, the result is in the range -PI to PI (this is identical to vectoyaw except it returns radians rather than degrees) +float(float a) tan = #475; // returns tangent value (which is simply sin(a)/cos(a)) for the given angle in radians, the result is in the range -infinity to +infinity +//description: +//useful math functions for analyzing vectors, note that these all use angles in radians (just like the cos/sin functions) not degrees unlike makevectors/vectoyaw/vectoangles, so be sure to do the appropriate conversions (multiply by DEG2RAD or RAD2DEG as needed). +//note: atan2 can take unnormalized vectors (just like vectoyaw), and the function was included only for completeness (more often you want vectoyaw or vectoangles), atan2(v_x,v_y) * RAD2DEG gives the same result as vectoyaw(v) + +//DP_QC_AUTOCVARS +//idea: divVerent +//darkplaces implementation: divVerent +//description: +//allows QC variables to be bound to cvars +//(works for float, string, vector types) +//example: +// float autocvar_developer; +// float autocvar_registered; +// string autocvar__cl_name; +//NOTE: copying a string-typed autocvar to another variable/field, and then +//changing the cvar or returning from progs is UNDEFINED. Writing to autocvar +//globals is UNDEFINED. Accessing autocvar globals after cvar_set()ing that +//cvar in the same frame is IMPLEMENTATION DEFINED (an implementation may +//either yield the previous, or the current, value). Whether autocvar globals, +//after restoring a savegame, have the cvar's current value, or the original +//value at time of saving, is UNDEFINED. Restoring a savegame however must not +//restore the cvar values themselves. +//In case the cvar does NOT exist, then it is automatically created with the +//value of the autocvar initializer, if given. This is possible with e.g. +//frikqcc and fteqcc the following way: +// var float autocvar_whatever = 42; +//If no initializer is given, the cvar will be initialized to a string +//equivalent to the NULL value of the given data type, that is, the empty +//string, 0, or '0 0 0'. However, when automatic cvar creation took place, a +//warning is printed to the game console. +//NOTE: to prevent an ambiguity with float names for vector types, autocvar +//names MUST NOT end with _x, _y or _z! + +//DP_QC_CHANGEPITCH +//idea: id Software +//darkplaces implementation: id Software +//field definitions: +.float idealpitch; +.float pitch_speed; +//builtin definitions: +void(entity ent) changepitch = #63; +//description: +//equivalent to changeyaw, ent is normally self. (this was a Q2 builtin) + +//DP_QC_COPYENTITY +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(entity from, entity to) copyentity = #400; +//description: +//copies all data in the entity to another entity. + +//DP_QC_CVAR_DEFSTRING +//idea: id Software (Doom3), LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +string(string s) cvar_defstring = #482; +//description: +//returns the default value of a cvar, as a tempstring. + +//DP_QC_CVAR_DESCRIPTION +//idea: divVerent +//DarkPlaces implementation: divVerent +//builtin definitions: +string(string name) cvar_description = #518; +//description: +//returns the description of a cvar + +//DP_QC_CVAR_STRING +//idea: VorteX +//DarkPlaces implementation: VorteX, LordHavoc +//builtin definitions: +string(string s) cvar_string = #448; +//description: +//returns the value of a cvar, as a tempstring. + +//DP_QC_CVAR_TYPE +//idea: divVerent +//DarkPlaces implementation: divVerent +//builtin definitions: +float(string name) cvar_type = #495; +float CVAR_TYPEFLAG_EXISTS = 1; +float CVAR_TYPEFLAG_SAVED = 2; +float CVAR_TYPEFLAG_PRIVATE = 4; +float CVAR_TYPEFLAG_ENGINE = 8; +float CVAR_TYPEFLAG_HASDESCRIPTION = 16; +float CVAR_TYPEFLAG_READONLY = 32; + +//DP_QC_EDICT_NUM +//idea: 515 +//DarkPlaces implementation: LordHavoc +//builtin definitions: +entity(float entnum) edict_num = #459; +float(entity ent) wasfreed = #353; // same as in EXT_CSQC extension +//description: +//edict_num returns the entity corresponding to a given number, this works even for freed entities, but you should call wasfreed(ent) to see if is currently active. +//wasfreed returns whether an entity slot is currently free (entities that have never spawned are free, entities that have had remove called on them are also free). + +//DP_QC_ENTITYDATA +//idea: KrimZon +//darkplaces implementation: KrimZon +//builtin definitions: +float() numentityfields = #496; +string(float fieldnum) entityfieldname = #497; +float(float fieldnum) entityfieldtype = #498; +string(float fieldnum, entity ent) getentityfieldstring = #499; +float(float fieldnum, entity ent, string s) putentityfieldstring = #500; +//constants: +//Returned by entityfieldtype +float FIELD_STRING = 1; +float FIELD_FLOAT = 2; +float FIELD_VECTOR = 3; +float FIELD_ENTITY = 4; +float FIELD_FUNCTION = 6; +//description: +//Versatile functions intended for storing data from specific entities between level changes, but can be customized for some kind of partial savegame. +//WARNING: .entity fields cannot be saved and restored between map loads as they will leave dangling pointers. +//numentityfields returns the number of entity fields. NOT offsets. Vectors comprise 4 fields: v, v_x, v_y and v_z. +//entityfieldname returns the name as a string, eg. "origin" or "classname" or whatever. +//entityfieldtype returns a value that the constants represent, but the field may be of another type in more exotic progs.dat formats or compilers. +//getentityfieldstring returns data as would be written to a savegame, eg... "0.05" (float), "0 0 1" (vector), or "Hello World!" (string). Function names can also be returned. +//putentityfieldstring puts the data returned by getentityfieldstring back into the entity. + +//DP_QC_ENTITYSTRING +void(string s) loadfromdata = #529; +void(string s) loadfromfile = #530; +void(string s) callfunction = #605; +void(float fh, entity e) writetofile = #606; +float(string s) isfunction = #607; +void(entity e, string s) parseentitydata = #608; + +//DP_QC_ETOS +//idea: id Software +//darkplaces implementation: id Software +//builtin definitions: +string(entity ent) etos = #65; +//description: +//prints "entity 1" or similar into a string. (this was a Q2 builtin) + +//DP_QC_EXTRESPONSEPACKET +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: +string(void) getextresponse = #624; +//description: +//returns a string of the form "\"ipaddress:port\" data...", or the NULL string +//if no packet was found. Packets can be queued into the client/server by +//sending a packet starting with "\xFF\xFF\xFF\xFFextResponse " to the +//listening port. + +//DP_QC_FINDCHAIN +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +entity(.string fld, string match) findchain = #402; +//description: +//similar to find() but returns a chain of entities like findradius. + +//DP_QC_FINDCHAIN_TOFIELD +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: +entity(.string fld, float match, .entity tofield) findradius_tofield = #22; +entity(.string fld, string match, .entity tofield) findchain_tofield = #402; +entity(.string fld, float match, .entity tofield) findchainflags_tofield = #450; +entity(.string fld, float match, .entity tofield) findchainfloat_tofield = #403; +//description: +//similar to findchain() etc, but stores the chain into .tofield instead of .chain +//actually, the .entity tofield is an optional field of the the existing findchain* functions + +//DP_QC_FINDCHAINFLAGS +//idea: Sajt +//darkplaces implementation: LordHavoc +//builtin definitions: +entity(.float fld, float match) findchainflags = #450; +//description: +//similar to findflags() but returns a chain of entities like findradius. + +//DP_QC_FINDCHAINFLOAT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +entity(.entity fld, entity match) findchainentity = #403; +entity(.float fld, float match) findchainfloat = #403; +//description: +//similar to findentity()/findfloat() but returns a chain of entities like findradius. + +//DP_QC_FINDFLAGS +//idea: Sajt +//darkplaces implementation: LordHavoc +//builtin definitions: +entity(entity start, .float fld, float match) findflags = #449; +//description: +//finds an entity with the specified flag set in the field, similar to find() + +//DP_QC_FINDFLOAT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +entity(entity start, .entity fld, entity match) findentity = #98; +entity(entity start, .float fld, float match) findfloat = #98; +//description: +//finds an entity or float field value, similar to find(), but for entity and float fields. + +//DP_QC_FS_SEARCH +//idea: Black +//darkplaces implementation: Black +//builtin definitions: +float(string pattern, float caseinsensitive, float quiet) search_begin = #444; +void(float handle) search_end = #445; +float(float handle) search_getsize = #446; +string(float handle, float num) search_getfilename = #447; +//description: +//search_begin performs a filename search with the specified pattern (for example "maps/*.bsp") and stores the results in a search slot (minimum of 128 supported by any engine with this extension), the other functions take this returned search slot number, be sure to search_free when done (they are also freed on progs reload). +//search_end frees a search slot (also done at progs reload). +//search_getsize returns how many filenames were found. +//search_getfilename returns a filename from the search. + +//DP_QC_GETLIGHT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +vector(vector org) getlight = #92; +//description: +//returns the lighting at the requested location (in color), 0-255 range (can exceed 255). + +//DP_QC_GETSURFACE +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +float(entity e, float s) getsurfacenumpoints = #434; +vector(entity e, float s, float n) getsurfacepoint = #435; +vector(entity e, float s) getsurfacenormal = #436; +string(entity e, float s) getsurfacetexture = #437; +float(entity e, vector p) getsurfacenearpoint = #438; +vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; +//description: +//functions to query surface information. + +//DP_QC_GETSURFACEPOINTATTRIBUTE +//idea: BlackHC +//darkplaces implementation: BlackHC +// constants +float SPA_POSITION = 0; +float SPA_S_AXIS = 1; +float SPA_T_AXIS = 2; +float SPA_R_AXIS = 3; // same as SPA_NORMAL +float SPA_TEXCOORDS0 = 4; +float SPA_LIGHTMAP0_TEXCOORDS = 5; +float SPA_LIGHTMAP0_COLOR = 6; +//builtin definitions: +vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; + +//description: +//function to query extended information about a point on a certain surface + +//DP_QC_GETSURFACETRIANGLE +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: +float(entity e, float s) getsurfacenumtriangles = #628; +vector(entity e, float s, float n) getsurfacetriangle = #629; +//description: +//function to query triangles of a surface + +//DP_QC_GETTAGINFO +//idea: VorteX, LordHavoc +//DarkPlaces implementation: VorteX +//builtin definitions: +float(entity ent, string tagname) gettagindex = #451; +vector(entity ent, float tagindex) gettaginfo = #452; +//description: +//gettagindex returns the number of a tag on an entity, this number is the same as set by setattachment (in the .tag_index field), allowing the qc to save a little cpu time by keeping the number around if it wishes (this could already be done by calling setattachment and saving off the tag_index). +//gettaginfo returns the origin of the tag in worldspace and sets v_forward, v_right, and v_up to the current orientation of the tag in worldspace, this automatically resolves all dependencies (attachments, including viewmodelforclient), this means you could fire a shot from a tag on a gun entity attached to the view for example. + +//DP_QC_GETTAGINFO_BONEPROPERTIES +//idea: daemon +//DarkPlaces implementation: divVerent +//global definitions: +float gettaginfo_parent; +string gettaginfo_name; +vector gettaginfo_offset; +vector gettaginfo_forward; +vector gettaginfo_right; +vector gettaginfo_up; +//description: +//when this extension is present, gettaginfo fills in some globals with info about the bone that had been queried +//gettaginfo_parent is set to the number of the parent bone, or 0 if it is a root bone +//gettaginfo_name is set to the name of the bone whose index had been specified in gettaginfo +//gettaginfo_offset, gettaginfo_forward, gettaginfo_right, gettaginfo_up contain the transformation matrix of the bone relative to its parent. Note that the matrix may contain a scaling component. + +//DP_QC_GETTIME +//idea: tZork +//darkplaces implementation: tZork, divVerent +//constant definitions: +float GETTIME_FRAMESTART = 0; // time of start of frame +float GETTIME_REALTIME = 1; // current time (may be OS specific) +float GETTIME_HIRES = 2; // like REALTIME, but may reset between QC invocations and thus can be higher precision +float GETTIME_UPTIME = 3; // time since start of the engine +//builtin definitions: +float(float tmr) gettime = #519; +//description: +//some timers to query... + +//DP_QC_GETTIME_CDTRACK +//idea: divVerent +//darkplaces implementation: divVerent +//constant definitions: +float GETTIME_CDTRACK = 4; +//description: +//returns the playing time of the current cdtrack when passed to gettime() +//see DP_END_GETSOUNDTIME for similar functionality but for entity sound channels + +//DP_QC_LOG +//darkplaces implementation: divVerent +//builtin definitions: +float log(float f) = #532; +//description: +//logarithm + +//DP_QC_MINMAXBOUND +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +float(float a, float b) min = #94; +float(float a, float b, float c) min3 = #94; +float(float a, float b, float c, float d) min4 = #94; +float(float a, float b, float c, float d, float e) min5 = #94; +float(float a, float b, float c, float d, float e, float f) min6 = #94; +float(float a, float b, float c, float d, float e, float f, float g) min7 = #94; +float(float a, float b, float c, float d, float e, float f, float g, float h) min8 = #94; +float(float a, float b) max = #95; +float(float a, float b, float c) max3 = #95; +float(float a, float b, float c, float d) max4 = #95; +float(float a, float b, float c, float d, float e) max5 = #95; +float(float a, float b, float c, float d, float e, float f) max6 = #95; +float(float a, float b, float c, float d, float e, float f, float g) max7 = #95; +float(float a, float b, float c, float d, float e, float f, float g, float h) max8 = #95; +float(float minimum, float val, float maximum) bound = #96; +//description: +//min returns the lowest of all the supplied numbers. +//max returns the highest of all the supplied numbers. +//bound clamps the value to the range and returns it. + +//DP_QC_MULTIPLETEMPSTRINGS +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//this extension makes all builtins returning tempstrings (ftos for example) +//cycle through a pool of multiple tempstrings (at least 16), allowing +//multiple ftos results to be gathered before putting them to use, normal +//quake only had 1 tempstring, causing many headaches. +// +//Note that for longer term storage (or compatibility on engines having +//FRIK_FILE but not this extension) the FRIK_FILE extension's +//strzone/strunzone builtins provide similar functionality (slower though). +// +//NOTE: this extension is superseded by DP_QC_UNLIMITEDTEMPSTRINGS + +//DP_QC_NUM_FOR_EDICT +//idea: Blub\0 +//darkplaces implementation: Blub\0 +//Function to get the number of an entity - a clean way. +float(entity num) num_for_edict = #512; + +//DP_QC_RANDOMVEC +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +vector() randomvec = #91; +//description: +//returns a vector of length < 1, much quicker version of this QC: do {v_x = random()*2-1;v_y = random()*2-1;v_z = random()*2-1;} while(vlen(v) > 1) + +//DP_QC_SINCOSSQRTPOW +//idea: id Software, LordHavoc +//darkplaces implementation: id Software, LordHavoc +//builtin definitions: +float(float val) sin = #60; +float(float val) cos = #61; +float(float val) sqrt = #62; +float(float a, float b) pow = #97; +//description: +//useful math functions, sine of val, cosine of val, square root of val, and raise a to power b, respectively. + +//DP_QC_SPRINTF +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: +string(string format, ...) sprintf = #627; +//description: +//you know sprintf :P +//supported stuff: +// % +// optional: $ for the argument to format +// flags: #0- + +// optional: , *, or *$ for the field width +// optional: ., .*, or .*$ for the precision +// length modifiers: h for forcing a float, l for forcing an int/entity (by default, %d etc. cast a float to int) +// conversions: +// d takes a float if no length is specified or h is, and an int/entity if l is specified as length, and cast it to an int +// i takes an int/entity if no length is specified or i is, and a float if h is specified as length, and cast it to an int +// ouxXc take a float if no length is specified or h is, and an int/entity if l is specified as length, and cast it to an unsigned int +// eEfFgG take a float if no length is specified or h is, and an int/entity if l is specified as length, and cast it to a double +// s takes a string +// vV takes a vector, and processes the three components as if it were a gG for all three components, separated by space +// For conversions s and c, the flag # makes precision and width interpreted +// as byte count, by default it is interpreted as character count in UTF-8 +// enabled engines. No other conversions can create wide characters, and # +// has another meaning in these. + +//DP_QC_STRFTIME +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +string(float uselocaltime, string format, ...) strftime = #478; +//description: +//provides the ability to get the local (in your timezone) or world (Universal Coordinated Time) time as a string using the formatting of your choice: +//example: "%Y-%m-%d %H:%M:%S" (result looks like: 2007-02-08 01:03:15) +//note: "%F %T" gives the same result as "%Y-%m-%d %H:%M:%S" (ISO 8601 date format and 24-hour time) +//for more format codes please do a web search for strftime 3 and you should find the man(3) pages for this standard C function. +// +//practical uses: +//changing day/night cycle (shops closing, monsters going on the prowl) in an RPG, for this you probably want to use s = strftime(TRUE, "%H");hour = stof(s); +//printing current date/time for competitive multiplayer games, such as the beginning/end of each round in real world time. +//activating eastereggs in singleplayer games on certain dates. +// +//note: some codes such as %x and %X use your locale settings and thus may not make sense to international users, it is not advisable to use these as the server and clients may be in different countries. +//note: if you display local time to a player, it would be a good idea to note whether it is local time using a string like "%F %T (local)", and otherwise use "%F %T (UTC)". +//note: be aware that if the game is saved and reloaded a week later the date and time will be different, so if activating eastereggs in a singleplayer game or something you may want to only check when a level is loaded and then keep the same easteregg state throughout the level so that the easteregg does not deactivate when reloading from a savegame (also be aware that precaches should not depend on such date/time code because reloading a savegame often scrambles the precaches if so!). +//note: this function can return a NULL string (you can check for it with if (!s)) if the localtime/gmtime functions returned NULL in the engine code, such as if those functions don't work on this platform (consoles perhaps?), so be aware that this may return nothing. + +//DP_QC_STRINGCOLORFUNCTIONS +//idea: Dresk +//darkplaces implementation: Dresk +//builtin definitions: +float(string s) strlennocol = #476; // returns how many characters are in a string, minus color codes +string(string s) strdecolorize = #477; // returns a string minus the color codes of the string provided +//description: +//provides additional functionality to strings by supporting functions that isolate and identify strings with color codes + +//DP_QC_STRING_CASE_FUNCTIONS +//idea: Dresk +//darkplaces implementation: LordHavoc / Dresk +//builtin definitions: +string(string s) strtolower = #480; // returns the passed in string in pure lowercase form +string(string s) strtoupper = #481; // returns the passed in string in pure uppercase form +//description: +//provides simple string uppercase and lowercase functions + +//DP_QC_TOKENIZEBYSEPARATOR +//idea: Electro, SavageX, LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +float(string s, string separator1, ...) tokenizebyseparator = #479; +//description: +//this function returns tokens separated by any of the supplied separator strings, example: +//numnumbers = tokenizebyseparator("10.2.3.4", "."); +//returns 4 and the tokens are "10" "2" "3" "4" +//possibly useful for parsing IPv4 addresses (such as "1.2.3.4") and IPv6 addresses (such as "[1234:5678:9abc:def0:1234:5678:9abc:def0]:26000") + +//DP_QC_TOKENIZE_CONSOLE +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: +float(string s) tokenize_console = #514; +float(float i) argv_start_index = #515; +float(float i) argv_end_index = #516; +//description: +//this function returns tokens separated just like the console does +//also, functions are provided to get the index of the first and last character of each token in the original string +//Passing negative values to them, or to argv, will be treated as indexes from the LAST token (like lists work in Perl). So argv(-1) will return the LAST token. + +//DP_QC_TRACEBOX +//idea: id Software +//darkplaces implementation: id Software +//builtin definitions: +void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox = #90; +//description: +//similar to traceline but much more useful, traces a box of the size specified (technical note: in quake1 and halflife bsp maps the mins and maxs will be rounded up to one of the hull sizes, quake3 bsp does not have this problem, this is the case with normal moving entities as well). + +//DP_QC_TRACETOSS +//idea: id Software +//darkplaces implementation: id Software +//builtin definitions: +void(entity ent, entity ignore) tracetoss = #64; +//description: +//simulates movement of the entity as if it is MOVETYPE_TOSS and starting with it's current state (location, velocity, etc), returns relevant trace_ variables (trace_fraction is always 0, all other values are supported - trace_ent, trace_endpos, trace_plane_normal), does not actually alter the entity. + +//DP_QC_TRACE_MOVETYPE_HITMODEL +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//constant definitions: +float MOVE_HITMODEL = 4; +//description: +//allows traces to hit alias models (not sprites!) instead of entity boxes, use as the nomonsters parameter to trace functions, note that you can hit invisible model entities (alpha < 0 or EF_NODRAW or model "", it only checks modelindex) + +//DP_QC_TRACE_MOVETYPE_WORLDONLY +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//constant definitions: +float MOVE_WORLDONLY = 3; +//description: +//allows traces to hit only world (ignoring all entities, unlike MOVE_NOMONSTERS which hits all bmodels), use as the nomonsters parameter to trace functions + +//DP_QC_UNLIMITEDTEMPSTRINGS +//idea: divVerent +//darkplaces implementation: LordHavoc +//description: +//this extension alters Quake behavior such that instead of reusing a single +//tempstring (or multiple) there are an unlimited number of tempstrings, which +//are removed only when a QC function invoked by the engine returns, +//eliminating almost all imaginable concerns with string handling in QuakeC. +// +//in short: +//you can now use and abuse tempstrings as much as you like, you still have to +//use strzone (FRIK_FILE) for permanent storage however. +// +// +// +//implementation notes for other engine coders: +//these tempstrings are expected to be stored in a contiguous buffer in memory +//which may be fixed size or controlled by a cvar, or automatically grown on +//demand (in the case of DarkPlaces). +// +//this concept is similar to quake's Zone system, however these are all freed +//when the QC interpreter returns. +// +//this is basically a poor man's garbage collection system for strings. + +//DP_QC_VECTOANGLES_WITH_ROLL +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +vector(vector forward, vector up) vectoangles2 = #51; // same number as vectoangles +//description: +//variant of vectoangles that takes an up vector to calculate roll angle (also uses this to calculate yaw correctly if the forward is straight up or straight down) +//note: just like normal vectoangles you need to negate the pitch of the returned angles if you want to feed them to makevectors or assign to self.v_angle + +//DP_QC_VECTORVECTORS +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector dir) vectorvectors = #432; +//description: +//creates v_forward, v_right, and v_up vectors given a forward vector, similar to makevectors except it takes a forward direction vector instead of angles. + +//DP_QC_WHICHPACK +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: +string(string filename) whichpack = #503; +//description: +//returns the name of the pak/pk3/whatever containing the given file, in the same path space as FRIK_FILE functions use (that is, possibly with a path name prefix) + +//DP_QC_URI_ESCAPE +//idea: divVerent +//darkplaces implementation: divVerent +//URI::Escape's functionality +string(string in) uri_escape = #510; +string(string in) uri_unescape = #511; + +//DP_QC_URI_GET +//idea: divVerent +//darkplaces implementation: divVerent +//loads text from an URL into a string +//returns 1 on success of initiation, 0 if there are too many concurrent +//connections already or if the URL is invalid +//the following callback will receive the data and MUST exist! +// void(float id, float status, string data) URI_Get_Callback; +//status is either +// negative for an internal error, +// 0 for success, or +// the HTTP response code on server error (e.g. 404) +//if 1 is returned by uri_get, the callback will be called in the future +float(string url, float id) uri_get = #513; + +//DP_QC_URI_POST +//idea: divVerent +//darkplaces implementation: divVerent +//loads text from an URL into a string after POSTing via HTTP +//works like uri_get, but uri_post sends data with Content-Type: content_type to the server +//and uri_post sends the string buffer buf, joined using the delimiter delim +float(string url, float id, string content_type, string data) uri_post = #513; +float(string url, float id, string content_type, string delim, float buf) uri_postbuf = #513; + +//DP_SKELETONOBJECTS +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//this extension indicates that FTE_CSQC_SKELETONOBJECTS functionality is available in server QC (as well as CSQC). + +//DP_SV_SPAWNFUNC_PREFIX +//idea: divVerent +//darkplaces implementation: divVerent +//Make functions whose name start with spawnfunc_ take precedence as spawn function for loading entities. +//Useful if you have a field ammo_shells (required in any Quake mod) but want to support spawn functions called ammo_shells (like in Q3A). +//Optionally, you can declare a global "float require_spawnfunc_prefix;" which will require ANY spawn function to start with that prefix. + + +//DP_QUAKE2_MODEL +//idea: quake community +//darkplaces implementation: LordHavoc +//description: +//shows that the engine supports Quake2 .md2 files. + +//DP_QUAKE2_SPRITE +//idea: LordHavoc +//darkplaces implementation: Elric +//description: +//shows that the engine supports Quake2 .sp2 files. + +//DP_QUAKE3_MAP +//idea: quake community +//darkplaces implementation: LordHavoc +//description: +//shows that the engine supports Quake3 .bsp files. + +//DP_QUAKE3_MODEL +//idea: quake community +//darkplaces implementation: LordHavoc +//description: +//shows that the engine supports Quake3 .md3 files. + +//DP_REGISTERCVAR +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +float(string name, string value) registercvar = #93; +//description: +//adds a new console cvar to the server console (in singleplayer this is the player's console), the cvar exists until the mod is unloaded or the game quits. +//NOTE: DP_CON_SET is much better. + +//DP_SND_DIRECTIONLESSATTNNONE +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//make sounds with ATTN_NONE have no spatialization (enabling easy use as music sources). + +//DP_SND_FAKETRACKS +//idea: requested +//darkplaces implementation: Elric +//description: +//the engine plays sound/cdtracks/track001.wav instead of cd track 1 and so on if found, this allows games and mods to have music tracks without using ambientsound. +//Note: also plays .ogg with DP_SND_OGGVORBIS extension. + +//DP_SND_SOUND7_WIP1 +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: +void(entity e, float chan, string samp, float vol, float atten, float speed, float flags) sound7 = #8; +float SOUNDFLAG_RELIABLE = 1; +//description: +//plays a sound, with some more flags +//extensions to sound(): +//- channel may be in the range from -128 to 127; channels -128 to 0 are "auto", +// i.e. support multiple sounds at once, but cannot be stopped/restarted +//- a speed parameter has been reserved for later addition of pitch shifting. +// it MUST be set to 0 for now, meaning "no pitch change" +//- the flag SOUNDFLAG_RELIABLE can be specified, which makes the sound send +// to MSG_ALL (reliable) instead of MSG_BROADCAST (unreliable, default); +// similarily, SOUNDFLAG_RELIABLE_TO_ONE sends to MSG_ONE +//- channel 0 is controlled by snd_channel0volume; channel 1 and -1 by +// snd_channel1volume, etc. (so, a channel shares the cvar with its respective +// auto-channel); however, the mod MUST define snd_channel8volume and upwards +// in default.cfg if they are to be used, as the engine does not create them +// to not litter the cvar list +//- this extension applies to CSQC as well; CSQC_Event_Sound will get speed and +// flags as extra 7th and 8th argument +//- WIP2 ideas: SOUNDFLAG_RELIABLE_TO_ONE, SOUNDFLAG_NOPHS, SOUNDFLAG_FORCELOOP +//- NOTE: to check for this, ALSO OR a check with DP_SND_SOUND7 to also support +// the finished extension once done + +//DP_SND_OGGVORBIS +//idea: Transfusion +//darkplaces implementation: Elric +//description: +//the engine supports loading Ogg Vorbis sound files. Use either the .ogg filename directly, or a .wav of the same name (will try to load the .wav first and then .ogg). + +//DP_SND_STEREOWAV +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//the engine supports stereo WAV files. (useful with DP_SND_DIRECTIONLESSATTNNONE for music) + +//DP_SND_GETSOUNDTIME +//idea: VorteX +//darkplaces implementation: VorteX +//constant definitions: +float(entity e, float channel) getsoundtime = #533; // get currently sound playing position on entity channel, -1 if not playing or error +float(string sample) soundlength = #534; // returns length of sound sample in seconds, -1 on error (sound not precached, sound system not initialized etc.) +//description: provides opportunity to query length of sound samples and realtime tracking of sound playing on entities (similar to DP_GETTIME_CDTRACK) +//note: beware dedicated server not running sound engine at all, so in dedicated mode this builtins will not work in server progs +//note also: menu progs not supporting getsoundtime() (will give a warning) since it has no sound playing on entities +//examples of use: +// - QC-driven looped sounds +// - QC events when sound playing is finished +// - toggleable ambientsounds +// - subtitles + +//DP_VIDEO_DPV +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//console commands: +// playvideo - start playing video +// stopvideo - stops current video +//description: indicated that engine support playing videos in DPV format + +//DP_VIDEO_SUBTITLES +//idea: VorteX +//darkplaces implementation: VorteX +//cvars: +// cl_video_subtitles - toggles subtitles showing +// cl_video_subtitles_lines - how many lines to reserve for subtitles +// cl_video_subtitles_textsize - font size +//console commands: +// playvideo - start playing video +// stopvideo - stops current video +//description: indicates that engine support subtitles on videos +//subtitles stored in external text files, each video file has it's default subtitles file ( .dpsubs ) +//example: for video/act1.dpv default subtitles file will be video/act1.dpsubs +//also video could be played with custom subtitles file by utilizing second parm of playvideo command +//syntax of .dpsubs files: each line in .dpsubs file defines 1 subtitle, there are three tokens: +// "string" +// start: subtitle start time in seconds +// end: subtitle time-to-show in seconds, if 0 - subtitle will be showed until next subtitle is started, +// if below 0 - show until next subtitles minus this number of seconds +// text: subtitle text, color codes (Q3-style and ^xRGB) are allowed +//example of subtitle file: +// 3 0 "Vengeance! Vengeance for my eternity of suffering!" +// 9 0 "Whelp! As if you knew what eternity was!" +// 13 0 "Grovel before your true master." +// 17 0 "Never!" +// 18 7 "I'll hack you from crotch to gizzard and feed what's left of you to your brides..." + +//DP_SOLIDCORPSE +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//solid definitions: +float SOLID_CORPSE = 5; +//description: +//the entity will not collide with SOLID_CORPSE and SOLID_SLIDEBOX entities (and likewise they will not collide with it), this is useful if you want dead bodies that are shootable but do not obstruct movement by players and monsters, note that if you traceline with a SOLID_SLIDEBOX entity as the ignoreent, it will ignore SOLID_CORPSE entities, this is desirable for visibility and movement traces, but not for bullets, for the traceline to hit SOLID_CORPSE you must temporarily force the player (or whatever) to SOLID_BBOX and then restore to SOLID_SLIDEBOX after the traceline. + +//DP_SPRITE32 +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//the engine supports .spr32 sprites. + +//DP_SV_BOTCLIENT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//constants: +float CLIENTTYPE_DISCONNECTED = 0; +float CLIENTTYPE_REAL = 1; +float CLIENTTYPE_BOT = 2; +float CLIENTTYPE_NOTACLIENT = 3; +//builtin definitions: +entity() spawnclient = #454; // like spawn but for client slots (also calls relevant connect/spawn functions), returns world if no clients available +float(entity clent) clienttype = #455; // returns one of the CLIENTTYPE_* constants +//description: +//spawns a client with no network connection, to allow bots to use client slots with no hacks. +//How to use: +/* + // to spawn a bot + local entity oldself; + oldself = self; + self = spawnclient(); + if (!self) + { + bprint("Can not add bot, server full.\n"); + self = oldself; + return; + } + self.netname = "Yoyobot"; + self.clientcolors = 12 * 16 + 4; // yellow (12) shirt and red (4) pants + ClientConnect(); + PutClientInServer(); + self = oldself; + + // to remove all bots + local entity head; + head = find(world, classname, "player"); + while (head) + { + if (clienttype(head) == CLIENTTYPE_BOT) + dropclient(head); + head = find(head, classname, "player"); + } + + // to identify if a client is a bot (for example in PlayerPreThink) + if (clienttype(self) == CLIENTTYPE_BOT) + botthink(); +*/ +//see also DP_SV_CLIENTCOLORS DP_SV_CLIENTNAME DP_SV_DROPCLIENT +//How it works: +//creates a new client, calls SetNewParms and stores the parms, and returns the client. +//this intentionally does not call SV_SendServerinfo to allow the QuakeC a chance to set the netname and clientcolors fields before actually spawning the bot by calling ClientConnect and PutClientInServer manually +//on level change ClientConnect and PutClientInServer are called by the engine to spawn in the bot (this is why clienttype() exists to tell you on the next level what type of client this is). +//parms work the same on bot clients as they do on real clients, and do carry from level to level + +//DP_SV_BOUNCEFACTOR +//idea: divVerent +//darkplaces implementation: divVerent +//field definitions: +.float bouncefactor; // velocity multiplier after a bounce +.float bouncestop; // bounce stops if velocity to bounce plane is < bouncestop * gravity AFTER the bounce +//description: +//allows qc to customize MOVETYPE_BOUNCE a bit + +//DP_SV_CLIENTCAMERA +//idea: LordHavoc, others +//darkplaces implementation: Black +//field definitions: +.entity clientcamera; // override camera entity +//description: +//allows another entity to be the camera for a client, for example a remote camera, this is similar to sending svc_setview manually except that it also changes the network culling appropriately. + +//DP_SV_CLIENTCOLORS +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.float clientcolors; // colors of the client (format: pants + shirt * 16) +//description: +//allows qc to read and modify the client colors associated with a client entity (not particularly useful on other entities), and automatically sends out any appropriate network updates if changed + +//DP_SV_CLIENTNAME +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//description: +//allows qc to modify the client's .netname, and automatically sends out any appropriate network updates if changed + +//DP_SV_CUSTOMIZEENTITYFORCLIENT +//idea: LordHavoc +//darkplaces implementation: [515] and LordHavoc +//field definitions: +.float() customizeentityforclient; // self = this entity, other = client entity +//description: +//allows qc to modify an entity before it is sent to each client, the function returns TRUE if it should send, FALSE if it should not, and is fully capable of editing the entity's fields, this allows cloaked players to appear less transparent to their teammates, navigation markers to only show to their team, etc +//tips on writing customize functions: +//it is a good idea to return FALSE early in the function if possible to reduce cpu usage, because this function may be called many thousands of times per frame if there are many customized entities on a 64+ player server. +//you are free to change anything in self, but please do not change any other entities (the results may be very inconsistent). +//example ideas for use of this extension: +//making icons over teammates' heads which are only visible to teammates. for exasmple: float() playericon_customizeentityforclient = {return self.owner.team == other.team;}; +//making cloaked players more visible to their teammates than their enemies. for example: float() player_customizeentityforclient = {if (self.items & IT_CLOAKING) {if (self.team == other.team) self.alpha = 0.6;else self.alpha = 0.1;} return TRUE;}; +//making explosion models that face the viewer (does not work well with chase_active). for example: float() explosion_customizeentityforclient = {self.angles = vectoangles(other.origin + other.view_ofs - self.origin);self.angles_x = 0 - self.angles_x;}; +//implementation notes: +//entity customization is done before per-client culling (visibility for instance) because the entity may be doing setorigin to display itself in different locations on different clients, may be altering its .modelindex, .effects and other fields important to culling, so customized entities increase cpu usage (non-customized entities can use all the early culling they want however, as they are not changing on a per client basis). + +//DP_SV_DISCARDABLEDEMO +//idea: parasti +//darkplaces implementation: parasti +//field definitions: +.float discardabledemo; +//description: +//when this field is set to a non-zero value on a player entity, a possibly recorded server-side demo for the player is discarded +//Note that this extension only works if: +// auto demos are enabled (the cvar sv_autodemo_perclient is set) +// discarding demos is enabled (the cvar sv_autodemo_perclient_discardable is set) + +//DP_SV_DRAWONLYTOCLIENT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.entity drawonlytoclient; +//description: +//the entity is only visible to the specified client. + +//DP_SV_DROPCLIENT +//idea: FrikaC +//darkplaces implementation: LordHavoc +//builtin definitions: +void(entity clent) dropclient = #453; +//description: +//causes the server to immediately drop the client, more reliable than stuffcmd(clent, "disconnect\n"); which could be intentionally ignored by the client engine + +//DP_SV_EFFECT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org, string modelname, float startframe, float endframe, float framerate) effect = #404; +//SVC definitions: +//float svc_effect = #52; // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate +//float svc_effect2 = #53; // [vector] org [short] modelindex [byte] startframe [byte] framecount [byte] framerate +//description: +//clientside playback of simple custom sprite effects (explosion sprites, etc). + +//DP_SV_ENTITYCONTENTSTRANSITION +//idea: Dresk +//darkplaces implementation: Dresk +//field definitions: +.void(float nOriginalContents, float nNewContents) contentstransition; +//description: +//This field function, when provided, is triggered on an entity when the contents (ie. liquid / water / etc) is changed. +//The first parameter provides the entities original contents, prior to the transition. The second parameter provides the new contents. +//NOTE: If this field function is provided on an entity, the standard watersplash sound IS SUPPRESSED to allow for authors to create their own transition sounds. + +//DP_SV_MOVETYPESTEP_LANDEVENT +//idea: Dresk +//darkplaces implementation: Dresk +//field definitions: +.void(vector vImpactVelocity) movetypesteplandevent; +//description: +//This field function, when provided, is triggered on a MOVETYPE_STEP entity when it experiences "land event". +// The standard engine behavior for this event is to play the sv_sound_land CVar sound. +//The parameter provides the velocity of the entity at the time of the impact. The z value may therefore be used to calculate how "hard" the entity struck the surface. +//NOTE: If this field function is provided on a MOVETYPE_STEP entity, the standard sv_sound_land sound IS SUPPRESSED to allow for authors to create their own feedback. + +//DP_SV_POINTSOUND +//idea: Dresk +//darkplaces implementation: Dresk +//builtin definitions: +void(vector origin, string sample, float volume, float attenuation) pointsound = #483; +//description: +//Similar to the standard QC sound function, this function takes an origin instead of an entity and omits the channel parameter. +// This allows sounds to be played at arbitrary origins without spawning entities. + +//DP_SV_ONENTITYNOSPAWNFUNCTION +//idea: Dresk +//darkplaces implementation: Dresk +//engine-called QC prototypes: +//void() SV_OnEntityNoSpawnFunction; +//description: +// This function is called whenever an entity on the server has no spawn function, and therefore has no defined QC behavior. +// You may as such dictate the behavior as to what happens to the entity. +// To mimic the engine's default behavior, simply call remove(self). + +//DP_SV_ONENTITYPREPOSTSPAWNFUNCTION +//idea: divVerent +//darkplaces implementation: divVerent +//engine-called QC prototypes: +//void() SV_OnEntityPreSpawnFunction; +//void() SV_OnEntityPostSpawnFunction; +//description: +// These functions are called BEFORE or AFTER an entity is spawned the regular way. +// You may as such dictate the behavior as to what happens to the entity. +// SV_OnEntityPreSpawnFunction is called before even looking for the spawn function, so you can even change its classname in there. If it remove()s the entity, the spawn function will not be looked for. +// SV_OnEntityPostSpawnFunction is called ONLY after its spawn function or SV_OnEntityNoSpawnFunction was called, and skipped if the entity got removed by either. + +//DP_SV_MODELFLAGS_AS_EFFECTS +//idea: LordHavoc, Dresk +//darkplaces implementation: LordHavoc +//field definitions: +.float modelflags; +//constant definitions: +float EF_NOMODELFLAGS = 8388608; // ignore any effects in a model file and substitute your own +float MF_ROCKET = 1; // leave a trail +float MF_GRENADE = 2; // leave a trail +float MF_GIB = 4; // leave a trail +float MF_ROTATE = 8; // rotate (bonus items) +float MF_TRACER = 16; // green split trail +float MF_ZOMGIB = 32; // small blood trail +float MF_TRACER2 = 64; // orange split trail +float MF_TRACER3 = 128; // purple trail +//description: +//this extension allows model flags to be specified on entities so you can add a rocket trail and glow to any entity, etc. +//setting any of these will override the flags the model already has, to disable the model's flags without supplying any of your own you must use EF_NOMODELFLAGS. +// +//silly example modification #1 to W_FireRocket in weapons.qc: +//missile.effects = EF_NOMODELFLAGS; // rocket without a glow/fire trail +//silly example modification #2 to W_FireRocket in weapons.qc: +//missile.modelflags = MF_GIB; // leave a blood trail instead of glow/fire trail +// +//note: you can not combine multiple kinds of trail, only one of them will be active, you can combine MF_ROTATE and the other MF_ flags however, and using EF_NOMODELFLAGS along with these does no harm. +// +//note to engine coders: they are internally encoded in the protocol as extra EF_ flags (shift the values left 24 bits and send them in the protocol that way), so no protocol change was required (however 32bit effects is a protocol extension itself), within the engine they are referred to as EF_ for the 24bit shifted values. + +//DP_SV_NETADDRESS +//idea: Dresk +//darkplaces implementation: Dresk +//field definitions: +.string netaddress; +//description: +// provides the netaddress of the associated entity (ie. 127.0.0.1) and "null/botclient" if the netconnection of the entity is invalid + +//DP_SV_NODRAWTOCLIENT +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.entity nodrawtoclient; +//description: +//the entity is not visible to the specified client. + +//DP_SV_PING +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.float ping; +//description: +//continuously updated field indicating client's ping (based on average of last 16 packet time differences). + +//DP_SV_PING_PACKETLOSS +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.float ping_packetloss; +.float ping_movementloss; +//description: +//continuously updated field indicating client's packet loss, and movement loss (i.e. packet loss affecting player movement). + +//DP_SV_POINTPARTICLES +//idea: Spike +//darkplaces implementation: LordHavoc +//function definitions: +float(string effectname) particleeffectnum = #335; // same as in CSQC +void(entity ent, float effectnum, vector start, vector end) trailparticles = #336; // same as in CSQC +void(float effectnum, vector org, vector vel, float howmany) pointparticles = #337; // same as in CSQC +//SVC definitions: +//float svc_trailparticles = 60; // [short] entnum [short] effectnum [vector] start [vector] end +//float svc_pointparticles = 61; // [short] effectnum [vector] start [vector] velocity [short] count +//float svc_pointparticles1 = 62; // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 +//description: +//provides the ability to spawn non-standard particle effects, typically these are defined in a particle effect information file such as effectinfo.txt in darkplaces. +//this is a port of particle effect features from clientside QC (EXT_CSQC) to server QC, as these effects are potentially useful to all games even if they do not make use of EXT_CSQC. +//warning: server must have same order of effects in effectinfo.txt as client does or the numbers would not match up, except for standard quake effects which are always the same numbers. + +//DP_SV_PUNCHVECTOR +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.vector punchvector; +//description: +//offsets client view in worldspace, similar to view_ofs but all 3 components are used and are sent with at least 8 bits of fraction, this allows the view to be kicked around by damage or hard landings or whatever else, note that unlike punchangle this is not faded over time, it is up to the mod to fade it (see also DP_SV_PLAYERPHYSICS). + +//DP_SV_PLAYERPHYSICS +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.vector movement; +//cvar definitions: +//"sv_playerphysicsqc" (0/1, default 1, allows user to disable qc player physics) +//engine-called QC prototypes: +//void() SV_PlayerPhysics; +//description: +//.movement vector contains the movement input from the player, allowing QC to do as it wishs with the input, and SV_PlayerPhysics will completely replace the player physics if present (works for all MOVETYPE's), see darkplaces mod source for example of this function (in playermovement.qc, adds HalfLife ladders support, as well as acceleration/deceleration while airborn (rather than the quake sudden-stop while airborn), and simplifies the physics a bit) + +//DP_PHYSICS_ODE +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//globals: +//new movetypes: +const float MOVETYPE_PHYSICS = 32; // need to be set before any physics_* builtins applied +//new solid types: +const float SOLID_PHYSICS_BOX = 32; +const float SOLID_PHYSICS_SPHERE = 33; +const float SOLID_PHYSICS_CAPSULE = 34; +//SOLID_BSP; +//joint types: +const float JOINTTYPE_POINT = 1; +const float JOINTTYPE_HINGE = 2; +const float JOINTTYPE_SLIDER = 3; +const float JOINTTYPE_UNIVERSAL = 4; +const float JOINTTYPE_HINGE2 = 5; +const float JOINTTYPE_FIXED = -1; +// common joint properties: +// .entity aiment, enemy; // connected objects +// .vector movedir; +// for a spring: +// movedir_x = spring constant (force multiplier, must be > 0) +// movedir_y = spring dampening constant to prevent oscillation (must be > 0) +// movedir_z = spring stop position (+/-) +// for a motor: +// movedir_x = desired motor velocity +// movedir_y = -1 * max motor force to use +// movedir_z = stop position (+/-), set to 0 for no stop +// note that ODE does not support both in one anyway +//field definitions: +.float mass; // ODE mass, standart value is 1 +.float bouncefactor; +.float bouncestop; +.float jointtype; +//builtin definitions: +void(entity e, float physics_enabled) physics_enable = #540; // enable or disable physics on object +void(entity e, vector force, vector force_pos) physics_addforce = #541; // apply a force from certain origin, length of force vector is power of force +void(entity e, vector torque) physics_addtorque = #542; // add relative torque +//description: provides Open Dynamics Engine support, requires extenal dll to be present or engine compiled with statical link option +//be sure to checkextension for it to know if library is loaded and ready, also to enable physics set "physics_ode" cvar to 1 +//note: this extension is highly experimental and may be unstable +//note: use SOLID_BSP on entities to get a trimesh collision models on them + +//DP_SV_PRINT +//idea: id Software (QuakeWorld Server) +//darkplaces implementation: Black, LordHavoc +void(string s, ...) print = #339; // same number as in EXT_CSQC +//description: +//this is identical to dprint except that it always prints regardless of the developer cvar. + +//DP_SV_PRECACHEANYTIME +//idea: id Software (Quake2) +//darkplaces implementation: LordHavoc +//description: +//this extension allows precache_model and precache_sound (and any variants) to be used during the game (with automatic messages to clients to precache the new model/sound indices), also setmodel/sound/ambientsound can be called without precaching first (they will cause an automatic precache). + +//DP_SV_QCSTATUS +//idea: divVerent +//darkplaces implementation: divVerent +//1. A global variable +string worldstatus; +//Its content is set as "qcstatus" field in the rules. +//It may be at most 255 characters, and must not contain newlines or backslashes. +//2. A per-client field +.string clientstatus; +//It is sent instead of the "frags" in status responses. +//It should always be set in a way so that stof(player.clientstatus) is a meaningful score value. Other info may be appended. If used this way, the cvar sv_status_use_qcstatus may be set to 1, and then this value will replace the frags in "status". +//Currently, qstat does not support this and will not show player info if used and set to anything other than ftos(some integer). + +//DP_SV_ROTATINGBMODEL +//idea: id Software +//darkplaces implementation: LordHavoc +//description: +//this extension merely indicates that MOVETYPE_PUSH supports avelocity, allowing rotating brush models to be created, they rotate around their origin (needs rotation supporting qbsp/light utilities because id ones expected bmodel entity origins to be '0 0 0', recommend setting "origin" key in the entity fields in the map before compiling, there may be other methods depending on your qbsp, most are more complicated however). +//tip: level designers can create a func_wall with an origin, and avelocity (for example "avelocity" "0 90 0"), and "nextthink" "99999999" to make a rotating bmodel without any qc modifications, such entities will be solid in stock quake but will not rotate) + +//DP_SV_SETCOLOR +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(entity ent, float colors) setcolor = #401; +//engine called QC functions (optional): +//void(float color) SV_ChangeTeam; +//description: +//setcolor sets the color on a client and updates internal color information accordingly (equivalent to stuffing a "color" command but immediate) +//SV_ChangeTeam is called by the engine whenever a "color" command is recieved, it may decide to do anything it pleases with the color passed by the client, including rejecting it (by doing nothing), or calling setcolor to apply it, preventing team changes is one use for this. +//the color format is pants + shirt * 16 (0-255 potentially) + +//DP_SV_SLOWMO +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//cvars: +//"slowmo" (0+, default 1) +//description: +//sets the time scale of the server, mainly intended for use in singleplayer by the player, however potentially useful for mods, so here's an extension for it. +//range is 0 to infinite, recommended values to try are 0.1 (very slow, 10% speed), 1 (normal speed), 5 (500% speed). + +//DP_SV_WRITEPICTURE +//idea: divVerent +//darkplaces implementation: divVerent +//builtin definitions: +void(float to, string s, float sz) WritePicture = #501; +//description: +//writes a picture to the data stream so CSQC can read it using ReadPicture, which has the definition +// string(void) ReadPicture = #501; +//The picture data is sent as at most sz bytes, by compressing to low quality +//JPEG. The data being sent will be equivalent to: +// WriteString(to, s); +// WriteShort(to, imagesize); +// for(i = 0; i < imagesize; ++i) +// WriteByte(to, [the i-th byte of the compressed JPEG image]); + +//DP_SV_WRITEUNTERMINATEDSTRING +//idea: FrikaC +//darkplaces implementation: Sajt +//builtin definitions: +void(float to, string s) WriteUnterminatedString = #456; +//description: +//like WriteString, but does not write a terminating 0 after the string. This means you can include things like a player's netname in the middle of a string sent over the network. Just be sure to end it up with either a call to WriteString (which includes the trailing 0) or WriteByte(0) to terminate it yourself. +//A historical note: this extension was suggested by FrikaC years ago, more recently Shadowalker has been badmouthing LordHavoc and Spike for stealing 'his' extension writestring2 which does exactly the same thing but uses a different builtin number and name and extension string, this argument hinges on the idea that it was his idea in the first place, which is incorrect as FrikaC first suggested it and used a rough equivalent of it in his FrikBot mod years ago involving WriteByte calls on each character. + +//DP_TE_BLOOD +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org, vector velocity, float howmany) te_blood = #405; +//temp entity definitions: +float TE_BLOOD = 50; +//protocol: +//vector origin +//byte xvelocity (-128 to +127) +//byte yvelocity (-128 to +127) +//byte zvelocity (-128 to +127) +//byte count (0 to 255, how much blood) +//description: +//creates a blood effect. + +//DP_TE_BLOODSHOWER +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower = #406; +//temp entity definitions: +//float TE_BLOODSHOWER = 52; +//protocol: +//vector mins (minimum corner of the cube) +//vector maxs (maximum corner of the cube) +//coord explosionspeed (velocity of blood particles flying out of the center) +//short count (number of blood particles) +//description: +//creates an exploding shower of blood, for making gibbings more convincing. + +//DP_TE_CUSTOMFLASH +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org, float radius, float lifetime, vector color) te_customflash = #417; +//temp entity definitions: +//float TE_CUSTOMFLASH = 73; +//protocol: +//vector origin +//byte radius ((MSG_ReadByte() + 1) * 8, meaning 8-2048 unit radius) +//byte lifetime ((MSG_ReadByte() + 1) / 256.0, meaning approximately 0-1 second lifetime) +//byte red (0.0 to 1.0 converted to 0-255) +//byte green (0.0 to 1.0 converted to 0-255) +//byte blue (0.0 to 1.0 converted to 0-255) +//description: +//creates a customized light flash. + +//DP_TE_EXPLOSIONRGB +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org, vector color) te_explosionrgb = #407; +//temp entity definitions: +//float TE_EXPLOSIONRGB = 53; +//protocol: +//vector origin +//byte red (0.0 to 1.0 converted to 0 to 255) +//byte green (0.0 to 1.0 converted to 0 to 255) +//byte blue (0.0 to 1.0 converted to 0 to 255) +//description: +//creates a colored explosion effect. + +//DP_TE_FLAMEJET +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org, vector vel, float howmany) te_flamejet = #457; +//temp entity definitions: +//float TE_FLAMEJET = 74; +//protocol: +//vector origin +//vector velocity +//byte count (0 to 255, how many flame particles) +//description: +//creates a single puff of flame particles. (not very useful really) + +//DP_TE_PARTICLECUBE +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube = #408; +//temp entity definitions: +//float TE_PARTICLECUBE = 54; +//protocol: +//vector mins (minimum corner of the cube) +//vector maxs (maximum corner of the cube) +//vector velocity +//short count +//byte color (palette color) +//byte gravity (TRUE or FALSE, FIXME should this be a scaler instead?) +//coord randomvel (how much to jitter the velocity) +//description: +//creates a cloud of particles, useful for forcefields but quite customizable. + +//DP_TE_PARTICLERAIN +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain = #409; +//temp entity definitions: +//float TE_PARTICLERAIN = 55; +//protocol: +//vector mins (minimum corner of the cube) +//vector maxs (maximum corner of the cube) +//vector velocity (velocity of particles) +//short count (number of particles) +//byte color (8bit palette color) +//description: +//creates a shower of rain, the rain will appear either at the top (if falling down) or bottom (if falling up) of the cube. + +//DP_TE_PARTICLESNOW +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow = #410; +//temp entity definitions: +//float TE_PARTICLERAIN = 56; +//protocol: +//vector mins (minimum corner of the cube) +//vector maxs (maximum corner of the cube) +//vector velocity (velocity of particles) +//short count (number of particles) +//byte color (8bit palette color) +//description: +//creates a shower of snow, the snow will appear either at the top (if falling down) or bottom (if falling up) of the cube, low velocities are advisable for convincing snow. + +//DP_TE_PLASMABURN +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org) te_plasmaburn = #433; +//temp entity definitions: +//float TE_PLASMABURN = 75; +//protocol: +//vector origin +//description: +//creates a small light flash (radius 200, time 0.2) and marks the walls. + +//DP_TE_QUADEFFECTS1 +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org) te_gunshotquad = #412; +void(vector org) te_spikequad = #413; +void(vector org) te_superspikequad = #414; +void(vector org) te_explosionquad = #415; +//temp entity definitions: +//float TE_GUNSHOTQUAD = 57; // [vector] origin +//float TE_SPIKEQUAD = 58; // [vector] origin +//float TE_SUPERSPIKEQUAD = 59; // [vector] origin +//float TE_EXPLOSIONQUAD = 70; // [vector] origin +//protocol: +//vector origin +//description: +//all of these just take a location, and are equivalent in function (but not appearance :) to the original TE_GUNSHOT, etc. + +//DP_TE_SMALLFLASH +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org) te_smallflash = #416; +//temp entity definitions: +//float TE_SMALLFLASH = 72; +//protocol: +//vector origin +//description: +//creates a small light flash (radius 200, time 0.2). + +//DP_TE_SPARK +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org, vector vel, float howmany) te_spark = #411; +//temp entity definitions: +//float TE_SPARK = 51; +//protocol: +//vector origin +//byte xvelocity (-128 to 127) +//byte yvelocity (-128 to 127) +//byte zvelocity (-128 to 127) +//byte count (number of sparks) +//description: +//creates a shower of sparks and a smoke puff. + +//DP_TE_STANDARDEFFECTBUILTINS +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +void(vector org) te_gunshot = #418; +void(vector org) te_spike = #419; +void(vector org) te_superspike = #420; +void(vector org) te_explosion = #421; +void(vector org) te_tarexplosion = #422; +void(vector org) te_wizspike = #423; +void(vector org) te_knightspike = #424; +void(vector org) te_lavasplash = #425; +void(vector org) te_teleport = #426; +void(vector org, float color, float colorlength) te_explosion2 = #427; +void(entity own, vector start, vector end) te_lightning1 = #428; +void(entity own, vector start, vector end) te_lightning2 = #429; +void(entity own, vector start, vector end) te_lightning3 = #430; +void(entity own, vector start, vector end) te_beam = #431; +//description: +//to make life easier on mod coders. + +//DP_TRACE_HITCONTENTSMASK_SURFACEINFO +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//globals: +.float dphitcontentsmask; // if non-zero on the entity passed to traceline/tracebox/tracetoss this will override the normal collidable contents rules and instead hit these contents values (for example AI can use tracelines that hit DONOTENTER if it wants to, by simply changing this field on the entity passed to traceline), this affects normal movement as well as trace calls +float trace_dpstartcontents; // DPCONTENTS_ value at start position of trace +float trace_dphitcontents; // DPCONTENTS_ value of impacted surface (not contents at impact point, just contents of the surface that was hit) +float trace_dphitq3surfaceflags; // Q3SURFACEFLAG_ value of impacted surface +string trace_dphittexturename; // texture name of impacted surface +//constants: +float DPCONTENTS_SOLID = 1; // hit a bmodel, not a bounding box +float DPCONTENTS_WATER = 2; +float DPCONTENTS_SLIME = 4; +float DPCONTENTS_LAVA = 8; +float DPCONTENTS_SKY = 16; +float DPCONTENTS_BODY = 32; // hit a bounding box, not a bmodel +float DPCONTENTS_CORPSE = 64; // hit a SOLID_CORPSE entity +float DPCONTENTS_NODROP = 128; // an area where backpacks should not spawn +float DPCONTENTS_PLAYERCLIP = 256; // blocks player movement +float DPCONTENTS_MONSTERCLIP = 512; // blocks monster movement +float DPCONTENTS_DONOTENTER = 1024; // AI hint brush +float DPCONTENTS_LIQUIDSMASK = 14; // WATER | SLIME | LAVA +float DPCONTENTS_BOTCLIP = 2048; // AI hint brush +float DPCONTENTS_OPAQUE = 4096; // only fully opaque brushes get this (may be useful for line of sight checks) +float Q3SURFACEFLAG_NODAMAGE = 1; +float Q3SURFACEFLAG_SLICK = 2; // low friction surface +float Q3SURFACEFLAG_SKY = 4; // sky surface (also has NOIMPACT and NOMARKS set) +float Q3SURFACEFLAG_LADDER = 8; // climbable surface +float Q3SURFACEFLAG_NOIMPACT = 16; // projectiles should remove themselves on impact (this is set on sky) +float Q3SURFACEFLAG_NOMARKS = 32; // projectiles should not leave marks, such as decals (this is set on sky) +float Q3SURFACEFLAG_FLESH = 64; // projectiles should do a fleshy effect (blood?) on impact +float Q3SURFACEFLAG_NODRAW = 128; // compiler hint (not important to qc) +//float Q3SURFACEFLAG_HINT = 256; // compiler hint (not important to qc) +//float Q3SURFACEFLAG_SKIP = 512; // compiler hint (not important to qc) +//float Q3SURFACEFLAG_NOLIGHTMAP = 1024; // compiler hint (not important to qc) +//float Q3SURFACEFLAG_POINTLIGHT = 2048; // compiler hint (not important to qc) +float Q3SURFACEFLAG_METALSTEPS = 4096; // walking on this surface should make metal step sounds +float Q3SURFACEFLAG_NOSTEPS = 8192; // walking on this surface should not make footstep sounds +float Q3SURFACEFLAG_NONSOLID = 16384; // compiler hint (not important to qc) +//float Q3SURFACEFLAG_LIGHTFILTER = 32768; // compiler hint (not important to qc) +//float Q3SURFACEFLAG_ALPHASHADOW = 65536; // compiler hint (not important to qc) +//float Q3SURFACEFLAG_NODLIGHT = 131072; // compiler hint (not important to qc) +//float Q3SURFACEFLAG_DUST = 262144; // translucent 'light beam' effect (not important to qc) +//description: +//adds additional information after a traceline/tracebox/tracetoss call. +//also (very important) sets trace_* globals before calling .touch functions, +//this allows them to inspect the nature of the collision (for example +//determining if a projectile hit sky), clears trace_* variables for the other +//object in a touch event (that is to say, a projectile moving will see the +//trace results in its .touch function, but the player it hit will see very +//little information in the trace_ variables as it was not moving at the time) + +//DP_VIEWZOOM +//idea: LordHavoc +//darkplaces implementation: LordHavoc +//field definitions: +.float viewzoom; +//description: +//scales fov and sensitivity of player, valid range is 0 to 1 (intended for sniper rifle zooming, and such) + +//EXT_BITSHIFT +//idea: Spike +//darkplaces implementation: [515] +//builtin definitions: +float(float number, float quantity) bitshift = #218; +//description: +//multiplies number by a power of 2 corresponding to quantity (0 = *1, 1 = *2, 2 = *4, 3 = *8, -1 = /2, -2 = /4x, etc), and rounds down (due to integer math) like other bit operations do (& and | and the like). +//note: it is faster to use multiply if you are shifting by a constant, avoiding the quakec function call cost, however that does not do a floor for you. + +//FRIK_FILE +//idea: FrikaC +//darkplaces implementation: LordHavoc +//builtin definitions: +float(string s) stof = #81; // get numerical value from a string +float(string filename, float mode) fopen = #110; // opens a file inside quake/gamedir/data/ (mode is FILE_READ, FILE_APPEND, or FILE_WRITE), returns fhandle >= 0 if successful, or fhandle < 0 if unable to open file for any reason +void(float fhandle) fclose = #111; // closes a file +string(float fhandle) fgets = #112; // reads a line of text from the file and returns as a tempstring +void(float fhandle, string s, ...) fputs = #113; // writes a line of text to the end of the file +float(string s) strlen = #114; // returns how many characters are in a string +string(string s1, string s2, ...) strcat = #115; // concatenates two or more strings (for example "abc", "def" would return "abcdef") and returns as a tempstring +string(string s, float start, float length) substring = #116; // returns a section of a string as a tempstring - see FTE_STRINGS for enhanced version +vector(string s) stov = #117; // returns vector value from a string +string(string s, ...) strzone = #118; // makes a copy of a string into the string zone and returns it, this is often used to keep around a tempstring for longer periods of time (tempstrings are replaced often) +void(string s) strunzone = #119; // removes a copy of a string from the string zone (you can not use that string again or it may crash!!!) +//constants: +float FILE_READ = 0; +float FILE_APPEND = 1; +float FILE_WRITE = 2; +//cvars: +//pr_zone_min_strings : default 64 (64k), min 64 (64k), max 8192 (8mb) +//description: +//provides text file access functions and string manipulation functions, note that you may want to set pr_zone_min_strings in the worldspawn function if 64k is not enough string zone space. +// +//NOTE: strzone functionality is partially superseded by +//DP_QC_UNLIMITEDTEMPSTRINGS when longterm storage is not needed +//NOTE: substring is upgraded by FTE_STRINGS extension with negative start/length handling identical to php 5.2.0 + +//FTE_CSQC_SKELETONOBJECTS +//idea: Spike, LordHavoc +//darkplaces implementation: LordHavoc +//builtin definitions: +// all skeleton numbers are 1-based (0 being no skeleton) +// all bone numbers are 1-based (0 being invalid) +float(float modlindex) skel_create = #263; // create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex, as the skeleton uses the hierarchy from the model. +float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +float(float skel) skel_get_numbones = #265; // returns how many bones exist in the created skeleton, 0 if skeleton does not exist +string(float skel, float bonenum) skel_get_bonename = #266; // returns name of bone (as a tempstring), "" if invalid bonenum (< 1 for example) or skeleton does not exist +float(float skel, float bonenum) skel_get_boneparent = #267; // returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +float(float skel, string tagname) skel_find_bone = #268; // get number of bone with specified name, 0 on failure, bonenum (1-based) on success, same as using gettagindex but takes modelindex instead of entity +vector(float skel, float bonenum) skel_get_bonerel = #269; // get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +vector(float skel, float bonenum) skel_get_boneabs = #270; // get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +void(float skel, float bonenum, vector org) skel_set_bone = #271; // set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +void(float skel, float bonenum, vector org) skel_mul_bone = #272; // transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +void(float skel) skel_delete = #275; // deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +float(float modlindex, string framename) frameforname = #276; // finds number of a specified frame in the animation, returns -1 if no match found +float(float modlindex, float framenum) frameduration = #277; // returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +//fields: +.float skeletonindex; // active skeleton overriding standard animation on model +.float frame; // primary framegroup animation (strength = 1 - lerpfrac - lerpfrac3 - lerpfrac4) +.float frame2; // secondary framegroup animation (strength = lerpfrac) +.float frame3; // tertiary framegroup animation (strength = lerpfrac3) +.float frame4; // quaternary framegroup animation (strength = lerpfrac4) +.float lerpfrac; // strength of framegroup blend +.float lerpfrac3; // strength of framegroup blend +.float lerpfrac4; // strength of framegroup blend +.float frame1time; // start time of framegroup animation +.float frame2time; // start time of framegroup animation +.float frame3time; // start time of framegroup animation +.float frame4time; // start time of framegroup animation +//description: +//this extension provides a way to do complex skeletal animation on an entity. +// +//see also DP_SKELETONOBJECTS (this extension implemented on server as well as client) +// +//notes: +//each model contains its own skeleton, reusing a skeleton with incompatible models will yield garbage (or not render). +//each model contains its own animation data, you can use animations from other model files (for example saving out all character animations as separate model files). +//if an engine supports loading an animation-only file format such as .md5anim in FTEQW, it can be used to animate any model with a compatible skeleton. +//proper use of this extension may require understanding matrix transforms (v_forward, v_right, v_up, origin), and you must keep in mind that v_right is negative for this purpose. +// +//features include: +//multiple animations blended together. +//animating a model with animations from another model with a compatible skeleton. +//restricting animation blends to certain bones of a model - for example independent animation of legs, torso, head. +//custom bone controllers - for example making eyes track a target location. +// +// +// +//example code follows... +// +//this helper function lets you identify (by parentage) what group a bone +//belongs to - for example "torso", "leftarm", would return 1 ("torso") for +//all children of the bone named "torso", unless they are children of +//"leftarm" (which is a child of "torso") which would return 2 instead... +float(float skel, float bonenum, string g1, string g2, string g3, string g4, string g5, string g6) example_skel_findbonegroup = +{ + local string bonename; + while (bonenum >= 0) + { + bonename = skel_get_bonename(skel, bonenum); + if (bonename == g1) return 1; + if (bonename == g2) return 2; + if (bonename == g3) return 3; + if (bonename == g4) return 4; + if (bonename == g5) return 5; + if (bonename == g6) return 6; + bonenum = skel_get_boneparent(skel, bonenum); + } + return 0; +}; +// create a skeletonindex for our player using current modelindex +void() example_skel_player_setup = +{ + self.skeletonindex = skel_create(self.modelindex); +}; +// setup bones of skeleton based on an animation +// note: animmodelindex can be a different model than self.modelindex +void(float animmodelindex, float framegroup, float framegroupstarttime) example_skel_player_update_begin = +{ + // start with our standard animation + self.frame = framegroup; + self.frame2 = 0; + self.frame3 = 0; + self.frame4 = 0; + self.frame1time = framegroupstarttime; + self.frame2time = 0; + self.frame3time = 0; + self.frame4time = 0; + self.lerpfrac = 0; + self.lerpfrac3 = 0; + self.lerpfrac4 = 0; + skel_build(self.skeletonindex, self, animmodelindex, 0, 0, 100000); +}; +// apply a different framegroup animation to bones with a specified parent +void(float animmodelindex, float framegroup, float framegroupstarttime, float blendalpha, string groupbonename, string excludegroupname1, string excludegroupname2) example_skel_player_update_applyoverride = +{ + local float bonenum; + local float numbones; + self.frame = framegroup; + self.frame2 = 0; + self.frame3 = 0; + self.frame4 = 0; + self.frame1time = framegroupstarttime; + self.frame2time = 0; + self.frame3time = 0; + self.frame4time = 0; + self.lerpfrac = 0; + self.lerpfrac3 = 0; + self.lerpfrac4 = 0; + bonenum = 0; + numbones = skel_get_numbones(self.skeletonindex); + while (bonenum < numbones) + { + if (example_skel_findbonegroup(self.skeletonindex, bonenum, groupbonename, excludegroupname1, excludegroupname2, "", "", "") == 1) + skel_build(self.skeletonindex, self, animmodelindex, 1 - blendalpha, bonenum, bonenum + 1); + bonenum = bonenum + 1; + } +}; +// make eyes point at a target location, be sure v_forward, v_right, v_up are set correctly before calling +void(vector eyetarget, string bonename) example_skel_player_update_eyetarget = +{ + local float bonenum; + local vector ang; + local vector oldforward, oldright, oldup; + local vector relforward, relright, relup, relorg; + local vector boneforward, boneright, boneup, boneorg; + local vector parentforward, parentright, parentup, parentorg; + local vector u, v; + local vector modeleyetarget; + bonenum = skel_find_bone(self.skeletonindex, bonename) - 1; + if (bonenum < 0) + return; + oldforward = v_forward; + oldright = v_right; + oldup = v_up; + v = eyetarget - self.origin; + modeleyetarget_x = v * v_forward; + modeleyetarget_y = 0-v * v_right; + modeleyetarget_z = v * v_up; + // this is an eyeball, make it point at the target location + // first get all the data we can... + relorg = skel_get_bonerel(self.skeletonindex, bonenum); + relforward = v_forward; + relright = v_right; + relup = v_up; + boneorg = skel_get_boneabs(self.skeletonindex, bonenum); + boneforward = v_forward; + boneright = v_right; + boneup = v_up; + parentorg = skel_get_boneabs(self.skeletonindex, skel_get_boneparent(self.skeletonindex, bonenum)); + parentforward = v_forward; + parentright = v_right; + parentup = v_up; + // get the vector from the eyeball to the target + u = modeleyetarget - boneorg; + // now transform it inversely by the parent matrix to produce new rel vectors + v_x = u * parentforward; + v_y = u * parentright; + v_z = u * parentup; + ang = vectoangles2(v, relup); + ang_x = 0 - ang_x; + makevectors(ang); + // set the relative bone matrix + skel_set_bone(self.skeletonindex, bonenum, relorg); + // restore caller's v_ vectors + v_forward = oldforward; + v_right = oldright; + v_up = oldup; +}; +// delete skeleton when we're done with it +// note: skeleton remains valid until next frame when it is really deleted +void() example_skel_player_delete = +{ + skel_delete(self.skeletonindex); + self.skeletonindex = 0; +}; +// +// END OF EXAMPLES FOR FTE_CSQC_SKELETONOBJECTS +// + +//KRIMZON_SV_PARSECLIENTCOMMAND +//idea: KrimZon +//darkplaces implementation: KrimZon, LordHavoc +//engine-called QC prototypes: +//void(string s) SV_ParseClientCommand; +//builtin definitions: +void(entity e, string s) clientcommand = #440; +float(string s) tokenize = #441; +string(float n) argv = #442; +//description: +//provides QC the ability to completely control server interpretation of client commands ("say" and "color" for example, clientcommand is necessary for this and substring (FRIK_FILE) is useful) as well as adding new commands (tokenize, argv, and stof (FRIK_FILE) are useful for this)), whenever a clc_stringcmd is received the QC function is called, and it is up to the QC to decide what (if anything) to do with it + +//NEH_CMD_PLAY2 +//idea: Nehahra +//darkplaces implementation: LordHavoc +//description: +//shows that the engine supports the "play2" console command (plays a sound without spatialization). + +//NEH_RESTOREGAME +//idea: Nehahra +//darkplaces implementation: LordHavoc +//engine-called QC prototypes: +//void() RestoreGame; +//description: +//when a savegame is loaded, this function is called + +//NEXUIZ_PLAYERMODEL +//idea: Nexuiz +//darkplaces implementation: Black +//console commands: +//playermodel - FIXME: EXAMPLE NEEDED +//playerskin - FIXME: EXAMPLE NEEDED +//field definitions: +.string playermodel; // name of player model sent by client +.string playerskin; // name of player skin sent by client +//description: +//these client properties are used by Nexuiz. + +//NXQ_GFX_LETTERBOX +//idea: nxQuake +//darkplaces implementation: LordHavoc +//description: +//shows that the engine supports the "r_letterbox" console variable, set to values in the range 0-100 this restricts the view vertically (and turns off sbar and crosshair), value is a 0-100 percentage of how much to constrict the view, <=0 = normal view height, 25 = 75% of normal view height, 50 = 50%, 75 = 25%, >=100 = no view + +//PRYDON_CLIENTCURSOR +//idea: FrikaC +//darkplaces implementation: LordHavoc +//effects bit: +float EF_SELECTABLE = 16384; // allows cursor to highlight entity (brighten) +//field definitions: +.float cursor_active; // true if cl_prydoncursor mode is on +.vector cursor_screen; // screen position of cursor as -1 to +1 in _x and _y (_z unused) +.vector cursor_trace_start; // position of camera +.vector cursor_trace_endpos; // position of cursor in world (as traced from camera) +.entity cursor_trace_ent; // entity the cursor is pointing at (server forces this to world if the entity is currently free at time of receipt) +//cvar definitions: +//cl_prydoncursor (0/1+, default 0, 1 and above use cursors named gfx/prydoncursor%03i.lmp - or .tga and such if DP_GFX_EXTERNALTEXTURES is implemented) +//description: +//shows that the engine supports the cl_prydoncursor cvar, this puts a clientside mouse pointer on the screen and feeds input to the server for the QuakeC to use as it sees fit. +//the mouse pointer triggers button4 if cursor is at left edge of screen, button5 if at right edge of screen, button6 if at top edge of screen, button7 if at bottom edge of screen. +//the clientside trace skips transparent entities (except those marked EF_SELECTABLE). +//the selected entity highlights only if EF_SELECTABLE is set, a typical selection method would be doubling the brightness of the entity by some means (such as colormod[] *= 2). +//intended to be used by Prydon Gate. + +//TENEBRAE_GFX_DLIGHTS +//idea: Tenebrae +//darkplaces implementation: LordHavoc +//fields: +.float light_lev; // radius (does not affect brightness), typical value 350 +.vector color; // color (does not affect radius), typical value '1 1 1' (bright white), can be up to '255 255 255' (nuclear blast) +.float style; // light style (like normal light entities, flickering torches or switchable, etc) +.float pflags; // flags (see PFLAGS_ constants) +.vector angles; // orientation of the light +.float skin; // cubemap filter number, can be 1-255 (0 is assumed to be none, and tenebrae only allows 16-255), this selects a projective light filter, a value of 1 loads cubemaps/1posx.tga and cubemaps/1negx.tga and posy, negy, posz, and negz, similar to skybox but some sides need to be rotated or flipped +//constants: +float PFLAGS_NOSHADOW = 1; // light does not cast shadows +float PFLAGS_CORONA = 2; // light has a corona flare +float PFLAGS_FULLDYNAMIC = 128; // light enable (without this set no light is produced!) +//description: +//more powerful dynamic light settings +//warning: it is best not to use cubemaps on a light entity that has a model, as using a skin number that a model does not have will cause issues in glquake, and produce warnings in darkplaces (use developer 1 to see them) +//changes compared to tenebrae (because they're too 'leet' for standards): +//note: networking should send entities with PFLAGS_FULLDYNAMIC set even if they have no model (lights in general do not have a model, nor should they) +//EF_FULLDYNAMIC effects flag replaced by PFLAGS_FULLDYNAMIC flag (EF_FULLDYNAMIC conflicts with EF_NODRAW) + +//TW_SV_STEPCONTROL +//idea: Transfusion +//darkplaces implementation: LordHavoc +//cvars: +//sv_jumpstep (0/1, default 1) +//sv_stepheight (default 18) +//description: +//sv_jumpstep allows stepping up onto stairs while airborn, sv_stepheight controls how high a single step can be. + +//FTE_QC_CHECKPVS +//idea: Urre +//darkplaces implementation: divVerent +//builtin definitions: +float checkpvs(vector viewpos, entity viewee) = #240; +//description: +//returns true if viewee can be seen from viewpos according to PVS data + +//FTE_STRINGS +//idea: many +//darkplaces implementation: KrimZon +//builtin definitions: +float(string str, string sub, float startpos) strstrofs = #221; // returns the offset into a string of the matching text, or -1 if not found, case sensitive +float(string str, float ofs) str2chr = #222; // returns the character at the specified offset as an integer, or 0 if an invalid index, or byte value - 256 if the engine supports UTF8 and the byte is part of an extended character +string(float c, ...) chr2str = #223; // returns a string representing the character given, if the engine supports UTF8 this may be a multi-byte sequence (length may be more than 1) for characters over 127. +string(float ccase, float calpha, float cnum, string s, ...) strconv = #224; // reformat a string with special color characters in the font, DO NOT USE THIS ON UTF8 ENGINES (if you are lucky they will emit ^4 and such color codes instead), the parameter values are 0=same/1=lower/2=upper for ccase, 0=same/1=white/2=red/5=alternate/6=alternate-alternate for redalpha, 0=same/1=white/2=red/3=redspecial/4=whitespecial/5=alternate/6=alternate-alternate for rednum. +string(float chars, string s, ...) strpad = #225; // pad string with spaces to a specified length, < 0 = left padding, > 0 = right padding +string(string info, string key, string value, ...) infoadd = #226; // sets or adds a key/value pair to an infostring - note: forbidden characters are \ and " +string(string info, string key) infoget = #227; // gets a key/value pair in an infostring, returns value or null if not found +float(string s1, string s2, float len) strncmp = #228; // compare two strings up to the specified number of characters, if their length differs and is within the specified limit the result will be negative, otherwise it is the difference in value of their first non-matching character. +float(string s1, string s2) strcasecmp = #229; // compare two strings with case-insensitive matching, characters a-z are considered equivalent to the matching A-Z character, no other differences, and this does not consider special characters equal even if they look similar +float(string s1, string s2, float len) strncasecmp = #230; // same as strcasecmp but with a length limit, see strncmp +//string(string s, float start, float length) substring = #116; // see note below +//description: +//various string manipulation functions +//note: substring also exists in FRIK_FILE but this extension adds negative start and length as valid cases (see note above), substring is consistent with the php 5.2.0 substr function (not 5.2.3 behavior) +//substring returns a section of a string as a tempstring, if given negative +// start the start is measured back from the end of the string, if given a +// negative length the length is the offset back from the end of the string to +// stop at, rather than being relative to start, if start is negative and +// larger than length it is treated as 0. +// examples of substring: +// substring("blah", -3, 3) returns "lah" +// substring("blah", 3, 3) returns "h" +// substring("blah", -10, 3) returns "bla" +// substring("blah", -10, -3) returns "b" + +//DP_CON_BESTWEAPON +//idea: many +//darkplaces implementation: divVerent +//description: +//allows QC to register weapon properties for use by the bestweapon command, for mods that change required ammo count or type for the weapons +//it is done using console commands sent via stuffcmd: +// register_bestweapon quake +// register_bestweapon clear +// register_bestweapon +//for example, this is what Quake uses: +// register_bestweapon 1 1 4096 4096 6 0 // STAT_SHELLS is 6 +// register_bestweapon 2 2 1 1 6 1 +// register_bestweapon 3 3 2 2 6 1 +// register_bestweapon 4 4 4 4 7 1 // STAT_NAILS is 7 +// register_bestweapon 5 5 8 8 7 1 +// register_bestweapon 6 6 16 16 8 1 // STAT_ROCKETS is 8 +// register_bestweapon 7 7 32 32 8 1 +// register_bestweapon 8 8 64 64 9 1 // STAT_CELLS is 9 +//after each map client initialization, this is reset back to Quake settings. So you should send these data in ClientConnect. +//also, this extension introduces a new "cycleweapon" command to the user. + +//DP_QC_STRINGBUFFERS +//idea: ?? +//darkplaces implementation: LordHavoc +//functions to manage string buffer objects - that is, arbitrary length string arrays that are handled by the engine +float() buf_create = #460; +void(float bufhandle) buf_del = #461; +float(float bufhandle) buf_getsize = #462; +void(float bufhandle_from, float bufhandle_to) buf_copy = #463; +void(float bufhandle, float sortpower, float backward) buf_sort = #464; +string(float bufhandle, string glue) buf_implode = #465; +string(float bufhandle, float string_index) bufstr_get = #466; +void(float bufhandle, float string_index, string str) bufstr_set = #467; +float(float bufhandle, string str, float order) bufstr_add = #468; +void(float bufhandle, float string_index) bufstr_free = #469; + +//DP_QC_STRINGBUFFERS_CVARLIST +//idea: divVerent +//darkplaces implementation: divVerent +//functions to list cvars and store their names into a stringbuffer +//cvars that start with pattern but not with antipattern will be stored into the buffer +void(float bufhandle, string pattern, string antipattern) buf_cvarlist = #517; + +//DP_QC_STRREPLACE +//idea: Sajt +//darkplaces implementation: Sajt +//builtin definitions: +string(string search, string replace, string subject) strreplace = #484; +string(string search, string replace, string subject) strireplace = #485; +//description: +//strreplace replaces all occurrences of 'search' with 'replace' in the string 'subject', and returns the result as a tempstring. +//strireplace does the same but uses case-insensitive matching of the 'search' term +// +//DP_QC_CRC16 +//idea: divVerent +//darkplaces implementation: divVerent +//Some hash function to build hash tables with. This has to be be the CRC-16-CCITT that is also required for the QuakeWorld download protocol. +//When caseinsensitive is set, the CRC is calculated of the lower cased string. +float(float caseinsensitive, string s, ...) crc16 = #494; + +//DP_SV_SHUTDOWN +//idea: divVerent +//darkplaces implementation: divVerent +//A function that gets called just before progs exit. To save persistent data from. +//It is not called on a crash or error. +//void SV_Shutdown(); + +//EXT_CSQC +// #232 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) +void(float index, float type, ...) addstat = #232; + +//ZQ_PAUSE +//idea: ZQuake +//darkplaces implementation: GreEn`mArine +//builtin definitions: +void(float pause) setpause = #531; +//function definitions: +//void(float elapsedtime) SV_PausedTic; +//description: +//during pause the world is not updated (time does not advance), SV_PausedTic is the only function you can be sure will be called at regular intervals during the pause, elapsedtime is the current system time in seconds since the pause started (not affected by slowmo or anything else). +// +//calling setpause(0) will end a pause immediately. +// +//Note: it is worth considering that network-related functions may be called during the pause (including customizeentityforclient for example), and it is also important to consider the continued use of the KRIMZON_SV_PARSECLIENTCOMMAND extension while paused (chatting players, etc), players may also join/leave during the pause. In other words, the only things that are not called are think and other time-related functions. + + + + +// EXPERIMENTAL (not finalized) EXTENSIONS: + +//DP_CRYPTO +//idea: divVerent +//darkplaces implementation: divVerent +//field definitions: (SVQC) +.string crypto_keyfp; // fingerprint of CA key the player used to authenticate, or string_null if not verified +.string crypto_mykeyfp; // fingerprint of CA key the server used to authenticate to the player, or string_null if not verified +.string crypto_idfp; // fingerprint of ID used by the player entity, or string_null if not identified +.string crypto_encryptmethod; // the string "AES128" if encrypting, and string_null if plaintext +.string crypto_signmethod; // the string "HMAC-SHA256" if signing, and string_null if plaintext +// there is no field crypto_myidfp, as that info contains no additional information the QC may have a use for +//builtin definitions: (SVQC) +float(string url, float id, string content_type, string delim, float buf, float keyid) crypto_uri_postbuf = #513; +//description: +//use -1 as buffer handle to justs end delim as postdata diff --git a/misc/source/darkplaces-src/dpdefs/menudefs.qc b/misc/source/darkplaces-src/dpdefs/menudefs.qc new file mode 100644 index 00000000..b23e6baf --- /dev/null +++ b/misc/source/darkplaces-src/dpdefs/menudefs.qc @@ -0,0 +1,491 @@ +////////////////////////////////////////////////////////// +// sys globals + +entity self; + +///////////////////////////////////////////////////////// +void end_sys_globals; +///////////////////////////////////////////////////////// +// sys fields + +///////////////////////////////////////////////////////// +void end_sys_fields; +///////////////////////////////////////////////////////// +// sys functions + +void() m_init; +void(float keynr, float ascii) m_keydown; +void() m_draw; +void() m_toggle; +void() m_shutdown; + +///////////////////////////////////////////////////////// +// sys constants +/////////////////////////// +// key constants + +// +// these are the key numbers that should be passed to Key_Event +// +float K_TAB = 9; +float K_ENTER = 13; +float K_ESCAPE = 27; +float K_SPACE = 32; + +// normal keys should be passed as lowercased ascii + +float K_BACKSPACE = 127; +float K_UPARROW = 128; +float K_DOWNARROW = 129; +float K_LEFTARROW = 130; +float K_RIGHTARROW = 131; + +float K_ALT = 132; +float K_CTRL = 133; +float K_SHIFT = 134; +float K_F1 = 135; +float K_F2 = 136; +float K_F3 = 137; +float K_F4 = 138; +float K_F5 = 139; +float K_F6 = 140; +float K_F7 = 141; +float K_F8 = 142; +float K_F9 = 143; +float K_F10 = 144; +float K_F11 = 145; +float K_F12 = 146; +float K_INS = 147; +float K_DEL = 148; +float K_PGDN = 149; +float K_PGUP = 150; +float K_HOME = 151; +float K_END = 152; + +float K_PAUSE = 153; + +float K_NUMLOCK = 154; +float K_CAPSLOCK = 155; +float K_SCROLLLOCK = 156; + +float K_KP_0 = 157; +float K_KP_INS = K_KP_0; +float K_KP_1 = 158; +float K_KP_END = K_KP_1; +float K_KP_2 = 159; +float K_KP_DOWNARROW = K_KP_2; +float K_KP_3 = 160; +float K_KP_PGDN = K_KP_3; +float K_KP_4 = 161 +float K_KP_LEFTARROW = K_KP_4; +float K_KP_5 = 162; +float K_KP_6 = 163; +float K_KP_RIGHTARROW = K_KP_6; +float K_KP_7 = 164; +float K_KP_HOME = K_KP_7; +float K_KP_8 = 165; +float K_KP_UPARROW = K_KP_8; +float K_KP_9 = 166; +float K_KP_PGUP = K_KP_9; +float K_KP_PERIOD = 167; +float K_KP_DEL = K_KP_PERIOD; +float K_KP_DIVIDE = 168; +float K_KP_SLASH = K_KP_DIVIDE; +float K_KP_MULTIPLY = 169; +float K_KP_MINUS = 170; +float K_KP_PLUS = 171; +float K_KP_ENTER = 172; +float K_KP_EQUALS = 173; + +// mouse buttons generate virtual keys + +float K_MOUSE1 = 512; +float K_MOUSE2 = 513; +float K_MOUSE3 = 514; +float K_MOUSE4 = 515; +float K_MWHEELUP = K_MOUSE4; +float K_MOUSE5 = 516; +float K_MWHEELDOWN = K_MOUSE5; +float K_MOUSE6 = 517; +float K_MOUSE7 = 518; +float K_MOUSE8 = 519; +float K_MOUSE9 = 520; +float K_MOUSE10 = 521; +float K_MOUSE11 = 522; +float K_MOUSE12 = 523; +float K_MOUSE13 = 524; +float K_MOUSE14 = 525; +float K_MOUSE15 = 526; +float K_MOUSE16 = 527; + +// +// joystick buttons +// +float K_JOY1 = 768; +float K_JOY2 = 769; +float K_JOY3 = 770; +float K_JOY4 = 771; + +// +// +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// +float K_AUX1 = 772; +float K_AUX2 = 773; +float K_AUX3 = 774; +float K_AUX4 = 775; +float K_AUX5 = 776; +float K_AUX6 = 777; +float K_AUX7 = 778; +float K_AUX8 = 779; +float K_AUX9 = 780; +float K_AUX10 = 781; +float K_AUX11 = 782; +float K_AUX12 = 783; +float K_AUX13 = 784; +float K_AUX14 = 785; +float K_AUX15 = 786; +float K_AUX16 = 787; +float K_AUX17 = 788; +float K_AUX18 = 789; +float K_AUX19 = 790; +float K_AUX20 = 791; +float K_AUX21 = 792; +float K_AUX22 = 793; +float K_AUX23 = 794; +float K_AUX24 = 795; +float K_AUX25 = 796; +float K_AUX26 = 797; +float K_AUX27 = 798; +float K_AUX28 = 799; +float K_AUX29 = 800; +float K_AUX30 = 801; +float K_AUX31 = 802; +float K_AUX32 = 803; + +/////////////////////////// +// key dest constants + +float KEY_GAME = 0; +float KEY_MENU = 2; +float KEY_UNKNOWN = 3; + +/////////////////////////// +// file constants + +float FILE_READ = 0; +float FILE_APPEND = 1; +float FILE_WRITE = 2; + +/////////////////////////// +// logical constants (just for completeness) + +float TRUE = 1; +float FALSE = 0; + +/////////////////////////// +// boolean constants + +float true = 1; +float false = 0; + +/////////////////////////// +// msg constants + +float MSG_BROADCAST = 0; // unreliable to all +float MSG_ONE = 1; // reliable to one (msg_entity) +float MSG_ALL = 2; // reliable to all +float MSG_INIT = 3; // write to the init string + +///////////////////////////// +// mouse target constants + +float MT_MENU = 1; +float MT_CLIENT = 2; + +///////////////////////// +// client state constants + +float CS_DEDICATED = 0; +float CS_DISCONNECTED = 1; +float CS_CONNECTED = 2; + +/////////////////////////// +// blend flags + +float DRAWFLAG_NORMAL = 0; +float DRAWFLAG_ADDITIVE = 1; +float DRAWFLAG_MODULATE = 2; +float DRAWFLAG_2XMODULATE = 3; + +/////////////////////////// +// null entity (actually it is the same like the world entity) + +entity null_entity; + +/////////////////////////// +// error constants + +// file handling +float ERR_CANNOTOPEN = -1; // fopen +float ERR_NOTENOUGHFILEHANDLES = -2; // fopen +float ERR_INVALIDMODE = -3; // fopen +float ERR_BADFILENAME = -4; // fopen + +// drawing functions + +float ERR_NULLSTRING = -1; +float ERR_BADDRAWFLAG = -2; +float ERR_BADSCALE = -3; +float ERR_BADSIZE = ERR_BADSCALE; +float ERR_NOTCACHED = -4; + +/* not supported at the moment +/////////////////////////// +// os constants + +float OS_WINDOWS = 0; +float OS_LINUX = 1; +float OS_MAC = 2; +*/ + + + + + + + + + + +////////////////////////////////////////////////// +// common cmd +////////////////////////////////////////////////// +// AK FIXME: Create perhaps a special builtin file for the common cmds + +void checkextension(string ext) = #1; + +// error cmds +void error(string err,...) = #2; +void objerror(string err,...) = #3; + +// print + +void print(string text,...) = #4; +void bprint(string text,...) = #5; +void sprint(float clientnum, string text,...) = #6; +void centerprint(string text,...) = #7; + +// vector stuff + +vector normalize(vector v) = #8; +float vlen(vector v) = #9; +float vectoyaw(vector v) = #10; +vector vectoangles(vector v) = #11; + +float random(void) = #12; + +void cmd(string command) = #13; + +// cvar cmds + +float cvar(string name) = #14; +const string str_cvar(string name) = #71; +void cvar_set(string name, string value) = #15; + +void dprint(string text,...) = #16; + +// conversion functions + +string ftos(float f) = #17; +float fabs(float f) = #18; +string vtos(vector v) = #19; +string etos(entity e) = #20; + +float stof(string val,...) = #21; + +entity spawn(void) = #22; +void remove(entity e) = #23; + +entity findstring(entity start, .string field, string match) = #24; +entity findfloat(entity start, .float field, float match) = #25; +entity findentity(entity start, .entity field, entity match) = #25; + +entity findchainstring(.string field, string match) = #26; +entity findchainfloat(.float field, float match) = #27; +entity findchainentity(.entity field, entity match) = #27; + +string precache_file(string file) = #28; +string precache_sound(string sample) = #29; + +void crash(void) = #72; +void coredump(void) = #30; +void stackdump(void) = #73; +void traceon(void) = #31; +void traceoff(void) = #32; + +void eprint(entity e) = #33; +float rint(float f) = #34; +float floor(float f) = #35; +float ceil(float f) = #36; +entity nextent(entity e) = #37; +float sin(float f) = #38; +float cos(float f) = #39; +float sqrt(float f) = #40; +vector randomvec(void) = #41; + +float registercvar(string name, string value, float flags) = #42; // returns 1 if success + +float min(float f,...) = #43; +float max(float f,...) = #44; + +float bound(float min,float value, float max) = #45; +float pow(float a, float b) = #46; + +void copyentity(entity src, entity dst) = #47; + +float fopen(string filename, float mode) = #48; +void fclose(float fhandle) = #49; +string fgets(float fhandle) = #50; +void fputs(float fhandle, string s) = #51; + +float strlen(string s) = #52; +string strcat(string s1,string s2,...) = #53; +string substring(string s, float start, float length) = #54; + +vector stov(string s) = #55; + +string strzone(string s) = #56; +void strunzone(string s) = #57; + +float tokenize(string s) = #58 +string argv(float n) = #59; + +float isserver(void) = #60; +float clientcount(void) = #61; +float clientstate(void) = #62; +void clientcommand(float client, string s) = #63; +void changelevel(string map) = #64; +void localsound(string sample) = #65; +vector getmousepos(void) = #66; +float gettime(void) = #67; +void loadfromdata(string data) = #68; +void loadfromfile(string file) = #69; + +float mod(float val, float m) = #70; + +float search_begin(string pattern, float caseinsensitive, float quiet) = #74; +void search_end(float handle) = #75; +float search_getsize(float handle) = #76; +string search_getfilename(float handle, float num) = #77; + +string chr(float ascii) = #78; + +///////////////////////////////////////////////// +// Write* Functions +///////////////////////////////////////////////// +void WriteByte(float data, float dest, float desto) = #401; +void WriteChar(float data, float dest, float desto) = #402; +void WriteShort(float data, float dest, float desto) = #403; +void WriteLong(float data, float dest, float desto) = #404; +void WriteAngle(float data, float dest, float desto) = #405; +void WriteCoord(float data, float dest, float desto) = #406; +void WriteString(string data, float dest, float desto)= #407; +void WriteEntity(entity data, float dest, float desto) = #408; + +////////////////////////////////////////////////// +// Draw funtions +////////////////////////////////////////////////// + +float iscachedpic(string name) = #451; +string precache_pic(string name) = #452; +void freepic(string name) = #453; + +float drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) = #454; + +float drawstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) = #455; + +float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) = #467; + +vector drawcolorcodedstring2(vector position, string text, vector scale, vector rgb, float alpha, float flag) = #467; + +float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) = #456; + +float drawfill(vector position, vector size, vector rgb, float alpha, float flag) = #457; + +void drawsetcliparea(float x, float y, float width, float height) = #458; + +void drawresetcliparea(void) = #459; + +vector drawgetimagesize(string pic) = #460; + +//////////////////////////////////////////////// +// Menu functions +//////////////////////////////////////////////// + +void setkeydest(float dest) = #601; +float getkeydest(void) = #602; + +void setmousetarget(float trg) = #603; +float getmousetarget(void) = #604; + +float isfunction(string function_name) = #607; +void callfunction(...) = #605; +void writetofile(float fhandle, entity ent) = #606; +vector getresolution(float number) = #608; +string keynumtostring(float keynum) = #609; + +float gethostcachevalue(float type) = #611; +string gethostcachestring(float type, float hostnr) = #612; + +//DP_CSQC_BINDMAPS +//idea: daemon, motorsep +//darkplaces implementation: divVerent +//builtin definitions: +string(float key, float bindmap) getkeybind_bindmap = #342; +float(float key, string bind, float bindmap) setkeybind_bindmap = #630; +vector(void) getbindmaps = #631; +float(vector bm) setbindmaps = #632; +string(string command, float bindmap) findkeysforcommand = #610; +float(string key) stringtokeynum = #341; +// string(float keynum) keynumtostring = #340; +//description: key bind setting/getting including support for switchable +//bindmaps. + +//DP_CRYPTO +//idea: divVerent +//darkplaces implementation: divVerent +//field definitions: (MENUQC) +string(string serveraddress) crypto_getkeyfp = #633; // retrieves the cached host key's CA fingerprint of a server given by IP address +string(string serveraddress) crypto_getidfp = #634; // retrieves the cached host key fingerprint of a server given by IP address +string(string serveraddress) crypto_getencryptlevel = #635; // 0 if never encrypting, 1 supported, 2 requested, 3 required, appended by list of allowed methods in order of preference ("AES128"), preceded by a space each +string(float i) crypto_getmykeyfp = #636; // retrieves the CA key fingerprint of a given CA slot, or "" if slot is unused but more to come, or string_null if end of list +string(float i) crypto_getmyidfp = #637; // retrieves the ID fingerprint of a given CA slot, or "" if slot is unused but more to come, or string_null if end of list +float(string url, float id, string content_type, string delim, float buf, float keyid) crypto_uri_postbuf = #513; +//description: +//use -1 as buffer handle to justs end delim as postdata + +//DP_GECKO_SUPPORT +//idea: Res2k, BlackHC +//darkplaces implementation: Res2k, BlackHC +//constant definitions: +float GECKO_BUTTON_DOWN = 0; +float GECKO_BUTTON_UP = 1; +// either use down and up or just press but not all of them! +float GECKO_BUTTON_PRESS = 2; +// use this for mouse events if needed? +float GECKO_BUTTON_DOUBLECLICK = 3; +//builtin definitions: +float gecko_create( string name ) = #487; +void gecko_destroy( string name ) = #488; +void gecko_navigate( string name, string URI ) = #489; +float gecko_keyevent( string name, float key, float eventtype ) = #490; +void gecko_mousemove( string name, float x, float y ) = #491; +void gecko_resize( string name, float w, float h ) = #492; +vector gecko_get_texture_extent( string name ) = #493; +//engine-called QC prototypes: +//string(string name, string query) Qecko_Query; +//description: +//provides an interface to the offscreengecko library and allows for internet browsing in games + diff --git a/misc/source/darkplaces-src/dpdefs/progsdefs.qc b/misc/source/darkplaces-src/dpdefs/progsdefs.qc new file mode 100644 index 00000000..2904f7db --- /dev/null +++ b/misc/source/darkplaces-src/dpdefs/progsdefs.qc @@ -0,0 +1,1193 @@ +/* +============================================================================== + + SOURCE FOR GLOBALVARS_T C STRUCTURE + MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR + +============================================================================== +*/ + +// +// system globals +// +entity self; +entity other; +entity world; +float time; +float frametime; + +float force_retouch; // force all entities to touch triggers + // next frame. this is needed because + // non-moving things don't normally scan + // for triggers, and when a trigger is + // created (like a teleport trigger), it + // needs to catch everything. + // decremented each frame, so set to 2 + // to guarantee everything is touched +string mapname; + +float deathmatch; +float coop; +float teamplay; + +float serverflags; // propagated from level to level, used to + // keep track of completed episodes + +float total_secrets; +float total_monsters; + +float found_secrets; // number of secrets found +float killed_monsters; // number of monsters killed + + +// spawnparms are used to encode information about clients across server +// level changes +float parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16; + +// +// global variables set by built in functions +// +vector v_forward, v_up, v_right; // set by makevectors() + +// set by traceline / tracebox +float trace_allsolid; +float trace_startsolid; +float trace_fraction; +vector trace_endpos; +vector trace_plane_normal; +float trace_plane_dist; +entity trace_ent; +float trace_inopen; +float trace_inwater; + +entity msg_entity; // destination of single entity writes + +// +// required prog functions +// +void() main; // only for testing + +void() StartFrame; + +void() PlayerPreThink; +void() PlayerPostThink; + +void() ClientKill; +void() ClientConnect; +void() PutClientInServer; // call after setting the parm1... parms +void() ClientDisconnect; + +void() SetNewParms; // called when a client first connects to + // a server. sets parms so they can be + // saved off for restarts + +void() SetChangeParms; // call to set parms for self so they can + // be saved for a level transition + + +//================================================ +void end_sys_globals; // flag for structure dumping +//================================================ + +/* +============================================================================== + + SOURCE FOR ENTVARS_T C STRUCTURE + MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR + +============================================================================== +*/ + +// +// system fields (*** = do not set in prog code, maintained by C code) +// +.float modelindex; // *** model index in the precached list +.vector absmin, absmax; // *** origin + mins / maxs + +.float ltime; // local time for entity +.float movetype; +.float solid; + +.vector origin; // *** +.vector oldorigin; // *** +.vector velocity; +.vector angles; +.vector avelocity; + +.vector punchangle; // temp angle adjust from damage or recoil + +.string classname; // spawn function +.string model; +.float frame; +.float skin; +.float effects; + +.vector mins, maxs; // bounding box extents reletive to origin +.vector size; // maxs - mins + +.void() touch; +.void() use; +.void() think; +.void() blocked; // for doors or plats, called when can't push other + +.float nextthink; +.entity groundentity; + +// stats +.float health; +.float frags; +.float weapon; // one of the IT_SHOTGUN, etc flags +.string weaponmodel; +.float weaponframe; +.float currentammo; +.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; + +.float items; // bit flags + +.float takedamage; +.entity chain; +.float deadflag; + +.vector view_ofs; // add to origin to get eye point + + +.float button0; // fire +.float button1; // use +.float button2; // jump + +.float impulse; // weapon changes + +.float fixangle; +.vector v_angle; // view / targeting angle for players +.float idealpitch; // calculated pitch angle for lookup up slopes + + +.string netname; + +.entity enemy; + +.float flags; + +.float colormap; +.float team; + +.float max_health; // players maximum health is stored here + +.float teleport_time; // don't back up + +.float armortype; // save this fraction of incoming damage +.float armorvalue; + +.float waterlevel; // 0 = not in, 1 = feet, 2 = wast, 3 = eyes +.float watertype; // a contents value + +.float ideal_yaw; +.float yaw_speed; + +.entity aiment; + +.entity goalentity; // a movetarget or an enemy + +.float spawnflags; + +.string target; +.string targetname; + +// damage is accumulated through a frame. and sent as one single +// message, so the super shotgun doesn't generate huge messages +.float dmg_take; +.float dmg_save; +.entity dmg_inflictor; + +.entity owner; // who launched a missile +.vector movedir; // mostly for doors, but also used for waterjump + +.string message; // trigger messages + +.float sounds; // either a cd track number or sound number + +.string noise, noise1, noise2, noise3; // contains names of wavs to play + +//================================================ +void end_sys_fields; // flag for structure dumping +//================================================ + +/* +============================================================================== + + CONSTANT DEFINITIONS + +============================================================================== +*/ + + +// +// constants +// + +float FALSE = 0; +float TRUE = 1; + +// edict.flags +float FL_FLY = 1; +float FL_SWIM = 2; +float FL_CLIENT = 8; // set for all client edicts +float FL_INWATER = 16; // for enter / leave water splash +float FL_MONSTER = 32; +float FL_GODMODE = 64; // player cheat +float FL_NOTARGET = 128; // player cheat +float FL_ITEM = 256; // extra wide size for bonus items +float FL_ONGROUND = 512; // standing on something +float FL_PARTIALGROUND = 1024; // not all corners are valid +float FL_WATERJUMP = 2048; // player jumping out of water +float FL_JUMPRELEASED = 4096; // for jump debouncing + +// edict.movetype values +float MOVETYPE_NONE = 0; // never moves +//float MOVETYPE_ANGLENOCLIP = 1; +//float MOVETYPE_ANGLECLIP = 2; +float MOVETYPE_WALK = 3; // players only +float MOVETYPE_STEP = 4; // discrete, not real time unless fall +float MOVETYPE_FLY = 5; +float MOVETYPE_TOSS = 6; // gravity +float MOVETYPE_PUSH = 7; // no clip to world, push and crush +float MOVETYPE_NOCLIP = 8; +float MOVETYPE_FLYMISSILE = 9; // fly with extra size against monsters +float MOVETYPE_BOUNCE = 10; +float MOVETYPE_BOUNCEMISSILE = 11; // bounce with extra size + +// edict.solid values +float SOLID_NOT = 0; // no interaction with other objects +float SOLID_TRIGGER = 1; // touch on edge, but not blocking +float SOLID_BBOX = 2; // touch on edge, block +float SOLID_SLIDEBOX = 3; // touch on edge, but not an onground +float SOLID_BSP = 4; // bsp clip, touch on edge, block + +// range values +float RANGE_MELEE = 0; +float RANGE_NEAR = 1; +float RANGE_MID = 2; +float RANGE_FAR = 3; + +// deadflag values + +float DEAD_NO = 0; +float DEAD_DYING = 1; +float DEAD_DEAD = 2; +float DEAD_RESPAWNABLE = 3; +float DEAD_RESPAWNING = 4; // dead, waiting for buttons to be released + +// takedamage values + +float DAMAGE_NO = 0; +float DAMAGE_YES = 1; +float DAMAGE_AIM = 2; + +// items +float IT_AXE = 4096; +float IT_SHOTGUN = 1; +float IT_SUPER_SHOTGUN = 2; +float IT_NAILGUN = 4; +float IT_SUPER_NAILGUN = 8; +float IT_GRENADE_LAUNCHER = 16; +float IT_ROCKET_LAUNCHER = 32; +float IT_LIGHTNING = 64; +float IT_EXTRA_WEAPON = 128; + +float IT_SHELLS = 256; +float IT_NAILS = 512; +float IT_ROCKETS = 1024; +float IT_CELLS = 2048; + +float IT_ARMOR1 = 8192; +float IT_ARMOR2 = 16384; +float IT_ARMOR3 = 32768; +float IT_SUPERHEALTH = 65536; + +float IT_KEY1 = 131072; +float IT_KEY2 = 262144; + +float IT_INVISIBILITY = 524288; +float IT_INVULNERABILITY = 1048576; +float IT_SUIT = 2097152; +float IT_QUAD = 4194304; + +// point content values + +float CONTENT_EMPTY = -1; +float CONTENT_SOLID = -2; +float CONTENT_WATER = -3; +float CONTENT_SLIME = -4; +float CONTENT_LAVA = -5; +float CONTENT_SKY = -6; + +float STATE_TOP = 0; +float STATE_BOTTOM = 1; +float STATE_UP = 2; +float STATE_DOWN = 3; + +vector VEC_ORIGIN = '0 0 0'; +vector VEC_HULL_MIN = '-16 -16 -24'; +vector VEC_HULL_MAX = '16 16 32'; + +vector VEC_HULL2_MIN = '-32 -32 -24'; +vector VEC_HULL2_MAX = '32 32 64'; + +// protocol bytes +float SVC_TEMPENTITY = 23; +float SVC_KILLEDMONSTER = 27; +float SVC_FOUNDSECRET = 28; +float SVC_INTERMISSION = 30; +float SVC_FINALE = 31; +float SVC_CDTRACK = 32; +float SVC_SELLSCREEN = 33; + + +float TE_SPIKE = 0; +float TE_SUPERSPIKE = 1; +float TE_GUNSHOT = 2; +float TE_EXPLOSION = 3; +float TE_TAREXPLOSION = 4; +float TE_LIGHTNING1 = 5; +float TE_LIGHTNING2 = 6; +float TE_WIZSPIKE = 7; +float TE_KNIGHTSPIKE = 8; +float TE_LIGHTNING3 = 9; +float TE_LAVASPLASH = 10; +float TE_TELEPORT = 11; + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +float CHAN_AUTO = 0; +float CHAN_WEAPON = 1; +float CHAN_VOICE = 2; +float CHAN_ITEM = 3; +float CHAN_BODY = 4; + +float ATTN_NONE = 0; +float ATTN_NORM = 1; +float ATTN_IDLE = 2; +float ATTN_STATIC = 3; + +// update types + +float UPDATE_GENERAL = 0; +float UPDATE_STATIC = 1; +float UPDATE_BINARY = 2; +float UPDATE_TEMP = 3; + +// entity effects + +float EF_BRIGHTFIELD = 1; +float EF_MUZZLEFLASH = 2; +float EF_BRIGHTLIGHT = 4; +float EF_DIMLIGHT = 8; + + +// messages +float MSG_BROADCAST = 0; // unreliable to all +float MSG_ONE = 1; // reliable to one (msg_entity) +float MSG_ALL = 2; // reliable to all +float MSG_INIT = 3; // write to the init string + +//================================================ + +// +// globals +// +float movedist; +float gameover; // set when a rule exits + +string string_null; // null string, nothing should be held here +//float empty_float; + +entity newmis; // launch_spike sets this after spawning it + +entity activator; // the entity that activated a trigger or brush + +entity damage_attacker; // set by T_Damage +float framecount; + +float skill; + +//================================================ + +// +// world fields (FIXME: make globals) +// +//.string wad; +.string map; +.float worldtype; // 0=medieval 1=metal 2=base + +//================================================ + +.string killtarget; + +// +// quakeed fields +// +.float light_lev; // not used by game, but parsed by light util +.float style; + + +// +// monster ai +// +.void() th_stand; +.void() th_walk; +.void() th_run; +.float() th_missile; // LordHavoc: changed from void() to float(), returns true if attacking +.void() th_melee; +.void(entity attacker, float damage, float damgtype, string dethtype) th_pain; +.void() th_die; + +.entity oldenemy; // mad at this player before taking damage + +.float speed; + +.float lefty; + +.float search_time; +.float attack_state; + +float AS_STRAIGHT = 1; +float AS_SLIDING = 2; +float AS_MELEE = 3; +float AS_MISSILE = 4; + +// +// player only fields +// +//.float walkframe; + +.float attack_finished; +.float pain_finished; + +.float invincible_finished; +.float invisible_finished; +.float super_damage_finished; +.float radsuit_finished; +.float spawnshieldtime; + +.float invincible_time, invincible_sound; +.float invisible_time, invisible_sound; +.float super_time, super_sound; +.float rad_time; +.float fly_sound; + +//.float axhitme; + +.float show_hostile; // set to time+0.2 whenever a client fires a + // weapon or takes damage. Used to alert + // monsters that otherwise would let the player go +.float jump_flag; // player jump flag +.float swim_flag; // player swimming sound flag +.float air_finished; // when time > air_finished, start drowning +.float bubble_count; // keeps track of the number of bubbles +.string deathtype; // keeps track of how the player died + +// +// object stuff +// +.string mdl; +.vector mangle; // angle at start + +.vector oldorigin; // only used by secret door + +.float t_length, t_width; + + +// +// doors, etc +// +.vector dest, dest1, dest2; +.float wait; // time from firing to restarting +.float delay; // time from activation to firing +.entity trigger_field; // door's trigger entity +.string noise4; + +// +// monsters +// +.float pausetime; +.entity movetarget; + + +// +// doors +// +.float aflag; +.float dmg; // damage done by door when hit + +// +// misc +// +.float cnt; // misc flag + +// +// subs +// +.void() think1; +//.vector finaldest, finalangle; + +// +// triggers +// +.float count; // for counting triggers + + +// +// plats / doors / buttons +// +.float lip; +.float state; +.vector pos1, pos2; // top and bottom positions +.float height; + +// +// sounds +// +.float waitmin, waitmax; +//.float distance; +//.float volume; + + + + +//=========================================================================== + + +// +// builtin functions +// + +void(vector ang) makevectors = #1; // sets v_forward, etc globals +void(entity e, vector o) setorigin = #2; +void(entity e, string m) setmodel = #3; // set movetype and solid first +void(entity e, vector min, vector max) setsize = #4; +// #5 was removed +void() break = #6; +float() random = #7; // returns 0 - 1 +void(entity e, float chan, string samp, float vol, float atten) sound = #8; +vector(vector v) normalize = #9; +void(string e, ...) error = #10; +void(string e, ...) objerror = #11; +float(vector v) vlen = #12; +float(vector v) vectoyaw = #13; +entity() spawn = #14; +void(entity e) remove = #15; + +// sets trace_* globals +// nomonsters can be: +// An entity will also be ignored for testing if forent == test, +// forent->owner == test, or test->owner == forent +// a forent of world is ignored +void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16; + +entity() checkclient = #17; // returns a client to look for +entity(entity start, .string fld, string match) find = #18; +string(string s) precache_sound = #19; +string(string s) precache_model = #20; +void(entity client, string s, ...)stuffcmd = #21; +entity(vector org, float rad) findradius = #22; +void(string s, ...) bprint = #23; +void(entity client, string s, ...) sprint = #24; +void(string s, ...) dprint = #25; +string(float f) ftos = #26; +string(vector v) vtos = #27; +void() coredump = #28; // prints all edicts +void() traceon = #29; // turns statment trace on +void() traceoff = #30; +void(entity e) eprint = #31; // prints an entire edict +float(float yaw, float dist) walkmove = #32; // returns TRUE or FALSE +// #33 was removed +float() droptofloor= #34; // TRUE if landed on floor +void(float style, string value) lightstyle = #35; +float(float v) rint = #36; // round to nearest int +float(float v) floor = #37; // largest integer <= v +float(float v) ceil = #38; // smallest integer >= v +// #39 was removed +float(entity e) checkbottom = #40; // true if self is on ground +float(vector v) pointcontents = #41; // returns a CONTENT_* +// #42 was removed +float(float f) fabs = #43; +vector(entity e, float speed) aim = #44; // returns the shooting vector +float(string s) cvar = #45; // return cvar.value +void(string s, ...) localcmd = #46; // put string into local que +entity(entity e) nextent = #47; // for looping through all ents +void(vector o, vector d, float color, float count) particle = #48;// start a particle effect +void() ChangeYaw = #49; // turn towards self.ideal_yaw + // at self.yaw_speed +// #50 was removed +vector(vector v) vectoangles = #51; + +// +// direct client message generation +// +void(float to, float f) WriteByte = #52; +void(float to, float f) WriteChar = #53; +void(float to, float f) WriteShort = #54; +void(float to, float f) WriteLong = #55; +void(float to, float f) WriteCoord = #56; +void(float to, float f) WriteAngle = #57; +void(float to, string s, ...) WriteString = #58; +void(float to, entity s) WriteEntity = #59; + +// +// broadcast client message generation +// + +// void(float f) bWriteByte = #59; +// void(float f) bWriteChar = #60; +// void(float f) bWriteShort = #61; +// void(float f) bWriteLong = #62; +// void(float f) bWriteCoord = #63; +// void(float f) bWriteAngle = #64; +// void(string s) bWriteString = #65; +// void(entity e) bWriteEntity = #66; + +void(float step) movetogoal = #67; + +string(string s) precache_file = #68; // no effect except for -copy +void(entity e) makestatic = #69; +void(string s) changelevel = #70; + +//#71 was removed + +void(string var, string val) cvar_set = #72; // sets cvar.value + +void(entity client, string s, ...) centerprint = #73; // sprint, but in middle + +void(vector pos, string samp, float vol, float atten) ambientsound = #74; + +string(string s) precache_model2 = #75; // registered version only +string(string s) precache_sound2 = #76; // registered version only +string(string s) precache_file2 = #77; // registered version only + +void(entity e) setspawnparms = #78; // set parm1... to the + // values at level start + // for coop respawn + +//============================================================================ + +// +// subs.qc +// +void(vector tdest, float tspeed, void() func) SUB_CalcMove; +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt; +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove; +void() SUB_CalcMoveDone; +void() SUB_CalcAngleMoveDone; +void() SUB_Null; +void() SUB_UseTargets; +void() SUB_Remove; + +// +// combat.qc +// + + + +float(entity targ, entity inflictor) CanDamage; + + + + + +//void(vector org, entity en, vector dir, float holetype) newbullethole; + + +//void(float msg, vector a) WriteVec = +//{ +// WriteCoord(msg, a_x); +// WriteCoord(msg, a_y); +// WriteCoord(msg, a_z); +//}; + +float() crandom = +{ + return 2*(random() - 0.5); +}; + +vector(vector m1, vector m2) randompos = +{ + local vector v; + m2 = m2 - m1; + v_x = m2_x * random() + m1_x; + v_y = m2_y * random() + m1_y; + v_z = m2_z * random() + m1_z; + return v; +}; + +// LordHavoc: made this a builtin function +/* +vector() randomvec = +{ + local vector v; + do + { + v_x = random() - 0.5; // scaled up in the return() + v_y = random() - 0.5; + v_z = random() - 0.5; + } + while (vlen(v) > 0.25); + return(v * 2); +}; +*/ + +vector() randomvec2 = +{ + local vector v; + v_x = random() - 0.5; // scale doesn't matter because it's normalized + v_y = random() - 0.5; + v_z = random() - 0.5; + v = normalize(v); + return(v); +}; + +vector() randomdirvec = +{ + local vector v; + do + { + do + { + v_x = random() - 0.5; + v_y = random() - 0.5; + v_z = random() - 0.5; + } + while (vlen(v) > 0.25); + } + while (vlen(v) < 0.1); + return normalize(v); +}; + +/* +float(float a) sqrt = // now this IS silly (using vlen to do square root) +{ + local vector v; + v_x = a; + return (vlen(v)); +}; +*/ + +// returns an empty point near the location if it's in a solid +// (useful for explosions) +vector (vector org) findbetterlocation = +{ + local vector v; + local float c; + if (pointcontents(org) != CONTENT_SOLID) + return org; + c = 0; + while (c < 20) + { + c = c + 1; +// 4.1 to get it to choose +2 occasionally (rather than a 1 in 32768 chance) + v_x = org_x + random() * 4.1 - 2; + v_y = org_y + random() * 4.1 - 2; + v_z = org_z + random() * 4.1 - 2; + if (pointcontents(v) != CONTENT_SOLID) + return v; + } + return org; // failed to find an empty point +}; + +// adjusts the entity's origin to move it out of a solid +// (useful for projectiles with no size, so they explode in open air) +// note this is mostly useless on entities bigger than that +// (unless they happen to have their origin on the surface of a wall) +void (entity e) findbetterlocation2 = +{ + local vector v, org; + local float c; + if (pointcontents(e.origin) != CONTENT_SOLID) + return; + org = e.origin; + c = 0; + while (c < 20) + { + c = c + 1; +// 4.1 to get it to choose 4 occasionally (rather than a 1 in 32768 chance) + v_x = org_x + random() * 4.1 - 2; + v_y = org_y + random() * 4.1 - 2; + v_z = org_z + random() * 4.1 - 2; + if (pointcontents(v) != CONTENT_SOLID) + { + setorigin(e, v); + return; + } + } + return; // failed to find an empty point +}; + +// returns a point at least 12 units away from walls +// (useful for explosion animations, although the blast is performed where it really happened) +vector (vector org) findbetterlocation3 = +{ + local vector v2; + traceline(org, org - '12 0 0' , TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '12 0 0' , TRUE, world);if (trace_fraction >= 1) org = v2 + '12 0 0' ;} + traceline(org, org - '-12 0 0', TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '-12 0 0', TRUE, world);if (trace_fraction >= 1) org = v2 + '-12 0 0';} + traceline(org, org - '0 12 0' , TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '0 12 0' , TRUE, world);if (trace_fraction >= 1) org = v2 + '0 12 0' ;} + traceline(org, org - '0 -12 0', TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '0 -12 0', TRUE, world);if (trace_fraction >= 1) org = v2 + '0 -12 0';} + traceline(org, org - '0 0 12' , TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '0 0 12' , TRUE, world);if (trace_fraction >= 1) org = v2 + '0 0 12' ;} + traceline(org, org - '0 0 -12', TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '0 0 -12', TRUE, world);if (trace_fraction >= 1) org = v2 + '0 0 -12';} + return org; +}; + +vector (vector org) findbetterlocation4 = +{ + local vector v2; + traceline(org, org - '5 0 0' , TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '5 0 0' , TRUE, world);if (trace_fraction >= 1) org = v2 + '5 0 0' ;} + traceline(org, org - '-5 0 0', TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '-5 0 0', TRUE, world);if (trace_fraction >= 1) org = v2 + '-5 0 0';} + traceline(org, org - '0 5 0' , TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '0 5 0' , TRUE, world);if (trace_fraction >= 1) org = v2 + '0 5 0' ;} + traceline(org, org - '0 -5 0', TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '0 -5 0', TRUE, world);if (trace_fraction >= 1) org = v2 + '0 -5 0';} + traceline(org, org - '0 0 5' , TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '0 0 5' , TRUE, world);if (trace_fraction >= 1) org = v2 + '0 0 5' ;} + traceline(org, org - '0 0 -5', TRUE, world);if (trace_fraction < 1) {v2 = trace_endpos;traceline(v2, v2 + '0 0 -5', TRUE, world);if (trace_fraction >= 1) org = v2 + '0 0 -5';} + return org; +}; + +/* +void(vector v1, vector v2, vector m1, vector m2) lh_traceagainstbox = +{ + local vector b, dir, delta, dironx, dirony, dironz; + local float completedist, r; + trace_endpos = v2; + trace_fraction = 1; + if (vlen(v2 - v1) < 0.1) // too short, might do a divide by zero + return; + // throw out the complete misses + if (v1_x <= m1_x) if (v2_x <= m1_x) return; + if (v1_x >= m2_x) if (v2_x >= m2_x) return; + if (v1_y <= m1_y) if (v2_y <= m1_y) return; + if (v1_y >= m2_y) if (v2_y >= m2_y) return; + if (v1_z <= m1_z) if (v2_z <= m1_z) return; + if (v1_z >= m2_z) if (v2_z >= m2_z) return; + // starting inside the box? + if (v1_x >= m1_x) if (v1_x < m2_x) if (v1_y >= m1_y) if (v1_y < m2_y) if (v1_z >= m1_z) if (v1_z < m2_z) return; + delta = v2 - v1; + dir = normalize(delta); + completedist = vlen(v2 - v1); + if (delta_x < -0.001 || delta_x > 0.001) + { + dironx_x = 1;if (delta_x < 0) dironx_x = -1; + dironx_y = delta_y / delta_x; + dironx_z = delta_z / delta_x; + } + else + { + dironx_x = 1;if (delta_x < 0) dironx_x = -1; + dironx_y = 0; + dironx_z = 0; + } + if (delta_y < -0.001 || delta_y > 0.001) + { + dirony_x = delta_x / delta_y; + dirony_y = 1;if (delta_y < 0) dirony_y = -1; + dirony_z = delta_z / delta_y; + } + else + { + dirony_x = 0; + dirony_y = 1;if (delta_y < 0) dirony_y = -1; + dirony_z = 0; + } + if (delta_z < -0.001 || delta_z > 0.001) + { + dironz_x = delta_x / delta_z; + dironz_y = delta_y / delta_z; + dironz_z = 1;if (delta_z < 0) dironz_z = -1; + } + else + { + dironz_x = 0; + dironz_y = 0; + dironz_z = 1;if (delta_z < 0) dironz_z = -1; + } + b = v1; + r = floor(random() * 1000); + if (dir_x > 0) + { + if (b_x < m1_x) + { + b = b + dironx * (m1_x - b_x); + trace_fraction = vlen(b - v1) / completedist; + } + } + else + { + if (b_x > m2_x) + { + b = b - dironx * (m2_x - b_x); + trace_fraction = vlen(b - v1) / completedist; + } + } + trace_endpos = b; + if (trace_fraction > 1) trace_fraction = 1; + if (dir_y > 0) + { + if (b_y < m1_y) + { + b = b + dirony * (m1_y - b_y); + trace_fraction = vlen(b - v1) / completedist; + } + } + else + { + if (b_y > m2_y) + { + b = b - dirony * (m2_y - b_y); + trace_fraction = vlen(b - v1) / completedist; + } + } + trace_endpos = b; + if (trace_fraction > 1) trace_fraction = 1; + if (dir_z > 0) + { + if (b_z < m1_z) + { + b = b + dironz * (m1_z - b_z); + trace_fraction = vlen(b - v1) / completedist; + } + } + else + { + if (b_z > m2_z) + { + b = b - dironz * (m2_z - b_z); + trace_fraction = vlen(b - v1) / completedist; + } + } + trace_endpos = b; + if (trace_fraction > 1) trace_fraction = 1; +}; +*/ + +.float cantrigger; + +void(float n) bprintfloat = +{ + local string s; + s = ftos(n); + bprint(s); +}; + +void(vector n) bprintvector = +{ + local string s; + s = vtos(n); + bprint(s); +}; + +void(float n) dprintfloat = +{ + local string s; + s = ftos(n); + dprint(s); +}; + +void(vector n) dprintvector = +{ + local string s; + s = vtos(n); + dprint(s); +}; + +string deathstring1, deathstring2, deathstring3, deathstring4; + +float DTYPE_OTHER = 0; +float DTYPE_PLAYER = 1; +float DTYPE_WORLD = 2; +float DTYPE_TEAMKILL = 3; +float DTYPE_SUICIDE = 4; + +void(entity targ, entity attacker, string dmsg, float dtype) Obituary_Generic = +{ + deathstring1 = targ.netname; + deathstring2 = dmsg; + deathstring3 = ""; + deathstring4 = ""; +}; + +// called by various obit functions to give a generic message for cases they do not handle +void(entity targ, entity attacker, string dmsg, float dtype) Obituary_Fallback = +{ + if (dtype == DTYPE_OTHER) + { + deathstring1 = targ.netname; + deathstring2 = dmsg; + deathstring3 = ""; + deathstring4 = ""; + } + else if (dtype == DTYPE_SUICIDE) + { + deathstring1 = targ.netname; + deathstring2 = " became bored with life"; + deathstring3 = ""; + deathstring4 = ""; + } + else if (dtype == DTYPE_PLAYER) + { + deathstring1 = targ.netname; + deathstring2 = " was killed by "; + deathstring3 = attacker.netname; + deathstring4 = ""; + } + else if (dtype == DTYPE_TEAMKILL) + { + deathstring1 = targ.netname; + deathstring2 = " was mowed down by his teammate "; + deathstring3 = attacker.netname; + deathstring4 = ""; + } + else if (dtype == DTYPE_WORLD) + { + deathstring1 = targ.netname; + deathstring2 = " died of unknown causes"; + deathstring3 = ""; + deathstring4 = ""; + } +}; + +float GAME_QUAKE = 0; +float GAME_NEXUIZ = 1; +float game; + +float darkmode; + +float IT_WEAPON1 = 4096; +float IT_WEAPON2 = 1; +float IT_WEAPON3 = 2; +float IT_WEAPON4 = 4; +float IT_WEAPON5 = 8; +float IT_WEAPON6 = 16; +float IT_WEAPON7 = 32; +float IT_WEAPON8 = 64; +float IT_WEAPON9 = 128; +float IT_WEAPON10 = 8388608; + +// see modecheck.qc for deathmatch and teamplay settings + +// added for Dark Places +float CHAN_SPEECH = 5; +float CHAN_FOOT = 6; +float CHAN_WEAPON2 = 7; + +// .gravity field added in Quake 1.07 (Scourge of Armagon) +.float gravity; + +.float bodyhealth; // used by corpse code +.float iscorpse; // used by corpse code + +.vector dest, dest1, dest2, dest3, dest4;//, dest5; + + +.entity flame; // the flame burning this thing + +.float doobits; // set if this should do obit on death + +//.vector bodymins, bodymaxs, headmins, headmaxs; + +.vector rotate; +.string group; + +// counts of how many keys this player/bot has +.float keys_silver; +.float keys_gold; + +.float havocattack; +.float havocpickup; +.float(entity player, entity item) pickupevalfunc; // returns rating for item considering player's condition (don't pick up health if it's not needed, etc) +.float shoulddodge; +.float dangerrating; + +// called whenever damage is done, if not supplied there is no visible effect. +.void(vector org, float bodydamage, float armordamage, vector vel, float damgtype) bleedfunc; + +// several counters +.float count1, count2, count3, count4, count5, count6, cnt1, cnt2; + +.void(entity t, entity a, string m, float dtyp) obitfunc1; +.entity realowner; + +float maxclients; // set by worldspawn code +float numdecors; +float maxdecors; +.float createdtime; +.void() th_gib; + +//void(vector org, entity en, vector dir, float splattype, float importance) newbloodsplat; +void(vector org, float bodydamage, float armordamage, vector vel, float damgtype) genericbleedfunc; + + + + +// damage stuff + +void(entity targ, entity inflictor, entity attacker, float damage, float bdamage, string dt, float damgtype, vector damgpoint, vector force, void(entity t, entity a, string m, float dtyp) obitfunc) T_Damage; +void(entity inflictor, entity attacker, float damage, float force, float radius, entity ignore, string dethtype, float damgtype, void(entity t, entity a, string m, float dtyp) obitfunc) T_RadiusDamage; + + +// resistances to 4 kinds of damage: +.float resist_bullet, resist_explosive, resist_energy, resist_fire, resist_ice, resist_axe; +// every monster can have resistances +// note: -1 means immune, as 0.0 can't be used + +// kill message +.string deathtype; +// copied from .deathtype for the attacker +// a map entity can gain alot of meaning by +// adding a kill message in the deathtype key +// such as ' was chopped to bits' +// or ' fell into the abyss' +// if you set a deathtype for a monster +// in a map key it will override the normal +// kill message for that monster +// so you could have some hell knights that +// say ' was slain by one of the palace guards' +// and many other ways to add meaning to a map +.string deathmsg; + +.float regenthink; // next time player will regen some health +.float isdecor; +.float radiusdamage_amount; // damage counter +.vector radiusdamage_force; // direction and power of blast kick +.float radiusdamage_hit; // 1 if it was hit, reset to 0 afterward +.vector radiusdamage_lasthit; // location +.float radiusdamage_ownerdamagescale; // if set on inflictor this will scale damage to owner (overriding default of 0.5) +vector raddamage_lasthit; + +.float frozen; +.float thawtime; +.float thawedeffects; +.void() thawedtouch; +.float thawedmovetype; +.void() thawedthink; +.float thawedthinkdelay; + +.void() knockedloosefunc; // called when kinetically disturbed +.float forcescale; // used for damage force calculations (shambler has lower forcescale than player for instance) + +// how much bleeding 'matters' to this entity, a percentage between +// bodydamage (-1 = 0% as 0 doesn't work) and healthdamage (1.0 = 100%) +// for instance a player is 100% and a zombie is 0% (bleeding doesn't matter) +.float bleedratio; +.float(entity e, float healthdamage, float damage, float damgtype, string dethtype) damagemodifier; + +.void() th_gib; + +.float health; // if this goes below 1 the thing is dead +.float bodyhealth; // used for gibbing +.float healthregen; // regenerate this much health per second +.float healthlostthisframe; // reset by iscreature code each frame + +// adjustable monster damage settings +float monsterdamagescale; +float monsterresistancescale; +// adjustable player damage settings +float playerdamagescale; + +void(entity targ, entity attacker, string dmsg, float dtype, void(entity t, entity a, string m, float dtyp) obitfunc) ClientObituary; + +void() monster_death_use; diff --git a/misc/source/darkplaces-src/dpdefs/source_compare.pl b/misc/source/darkplaces-src/dpdefs/source_compare.pl new file mode 100644 index 00000000..01dbfd49 --- /dev/null +++ b/misc/source/darkplaces-src/dpdefs/source_compare.pl @@ -0,0 +1,269 @@ +use strict; +use warnings; + +my %vm = ( + menu => {}, + csprogs => {}, + progs => {} +); + +my $skip = 0; + +my $parsing_builtins = undef; +my $parsing_builtin = 0; + +my $parsing_fields = undef; +my $parsing_globals = undef; +my $parsing_vm = undef; + +for(<../*.h>, <../*.c>) +{ + open my $fh, "<", $_ + or die "<$_: $!"; + while(<$fh>) + { + chomp; + if(/^#if 0$/) + { + $skip = 1; + } + elsif(/^#else$/) + { + $skip = 0; + } + elsif(/^#endif$/) + { + $skip = 0; + } + elsif($skip) + { + } + elsif(/^prvm_builtin_t vm_m_/) + { + $parsing_builtins = "menu"; + $parsing_builtin = 0; + } + elsif(/^prvm_builtin_t vm_cl_/) + { + $parsing_builtins = "csprogs"; + $parsing_builtin = 0; + } + elsif(/^prvm_builtin_t vm_sv_/) + { + $parsing_builtins = "progs"; + $parsing_builtin = 0; + } + elsif(/^\}/) + { + $parsing_builtins = undef; + $parsing_globals = undef; + $parsing_fields = undef; + $parsing_vm = undef; + } + elsif(/^typedef struct entvars_s$/) + { + $parsing_fields = "fields"; + $parsing_vm = "progs"; + } + elsif(/^typedef struct cl_entvars_s$/) + { + $parsing_fields = "fields"; + $parsing_vm = "csprogs"; + } + elsif(/^typedef struct prvm_prog_fieldoffsets_s$/) + { + $parsing_fields = "fields"; + } + elsif(/^typedef struct globalvars_s$/) + { + $parsing_globals = "globals"; + $parsing_vm = "progs"; + } + elsif(/^typedef struct cl_globalvars_s$/) + { + $parsing_globals = "globals"; + $parsing_vm = "csprogs"; + } + elsif(/^typedef struct m_globalvars_s$/) + { + $parsing_globals = "globals"; + $parsing_vm = "menu"; + } + elsif(/^typedef struct prvm_prog_globaloffsets_s$/) + { + $parsing_globals = "globals"; + } + elsif($parsing_builtins) + { + s/\/\*.*?\*\// /g; + if(/^\s*\/\//) + { + } + elsif(/^NULL\b/) + { + $parsing_builtin += 1; + } + elsif(/^(\w+)\s*,?\s*\/\/\s+#(\d+)\s*(.*)/) + { + my $func = $1; + my $builtin = int $2; + my $descr = $3; + my $extension = "DP_UNKNOWN"; + + if($descr =~ s/\s+\(([0-9A-Z_]*)\)//) + { + $extension = $1; + } + # 'void(vector ang) makevectors' + + if($descr eq "") + { + } + elsif($descr eq "draw functions...") + { + } + elsif($descr =~ /^\/\//) + { + } + elsif($descr =~ /\) (\w+)/) + { + $func = $1; + } + elsif($descr =~ /(\w+)\s*\(/) + { + $func = $1; + } + elsif($descr =~ /^\w+$/) + { + $func = $descr; + } + else + { + warn "No function name found in $descr"; + } + + warn "builtin sequence error: #$builtin (expected: $parsing_builtin)" + if $builtin != $parsing_builtin; + $parsing_builtin = $builtin + 1; + $vm{$parsing_builtins}{builtins}[$builtin] = [0, $func, $extension]; + } + else + { + warn "Fails to parse: $_"; + } + } + elsif($parsing_fields || $parsing_globals) + { + my $f = $parsing_fields || $parsing_globals; + if(/^\s*\/\//) + { + } + elsif(/^\s+(?:int|float|string_t|vec3_t|func_t)\s+(\w+);\s*(?:\/\/(.*))?/) + { + my $name = $1; + my $descr = $2 || ""; + my $extension = "DP_UNKNOWN"; + $extension = $1 + if $descr =~ /\b([0-9A-Z_]+)\b/; + my $found = undef; + $vm{menu}{$f}{$name} = ($found = [0, $extension]) + if $descr =~ /common|menu/; + $vm{progs}{$f}{$name} = ($found = [0, $extension]) + if $descr =~ /common|ssqc/; + $vm{csprogs}{$f}{$name} = ($found = [0, $extension]) + if $descr =~ /common|csqc/; + $vm{$parsing_vm}{$f}{$name} = ($found = [0, $extension]) + if not defined $found and defined $parsing_vm; + warn "$descr does not yield info about target VM" + if not defined $found; + } + } + elsif(/getglobal\w*\(\w+, "(\w+)"\)/) + { + # hack for weird DP source + $vm{csprogs}{globals}{$1} = [0, "DP_CSQC_SPAWNPARTICLE"]; + } + } + close $fh; +} + +# now read in dpdefs +for(( + ["csprogsdefs.qc", "csprogs"], + ["dpextensions.qc", "progs"], + ["menudefs.qc", "menu"], + ["progsdefs.qc", "progs"] +)) +{ + my ($file, $v) = @$_; + open my $fh, "<", "$file" + or die "<$file: $!"; + while(<$fh>) + { + s/\/\/.*//; + if(/^(?:float|entity|string|vector)\s+((?:\w+\s*,\s*)*\w+)\s*;/) + { + for(split /\s*,\s*/, $1) + { + print "// $v: Global $_ declared but not defined\n" + if not $vm{$v}{globals}{$_}; + $vm{$v}{globals}{$_}[0] = 1; # documented! + } + } + elsif(/^\.(?:float|entity|string|vector|void)(?:.*\))?\s+((?:\w+\s*,\s*)*\w+)\s*;/) + { + for(split /\s*,\s*/, $1) + { + print "// $v: Field $_ declared but not defined\n" + if not $vm{$v}{fields}{$_}; + $vm{$v}{fields}{$_}[0] = 1; # documented! + } + } + elsif(/#(\d+)/) + { + print "// $v: Builtin #$1 declared but not defined\n" + if not $vm{$v}{builtins}[$1]; + $vm{$v}{builtins}[$1][0] = 1; # documented! + } + else + { + } + } + close $fh; +} + +# some dumb output +for my $v(sort keys %vm) +{ + print "/******************************************\n"; + print " * $v\n"; + print " ******************************************/\n"; + my $b = $vm{$v}{builtins}; + for(0..@$b) + { + next if not defined $b->[$_]; + my ($documented, $func, $extension) = @{$b->[$_]}; + print "float $func(...) = #$_; // $extension\n" + unless $documented; + } + my $g = $vm{$v}{globals}; + for(sort keys %$g) + { + my ($documented, $extension) = @{$g->{$_}}; + print "float $_; // $extension\n" + unless $documented; + } + my $f = $vm{$v}{fields}; + for(sort keys %$f) + { + my ($documented, $extension) = @{$f->{$_}}; + print ".float $_; // $extension\n" + unless $documented; + } + +} + +__END__ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +print Dumper \%vm; diff --git a/misc/source/darkplaces-src/dpdefs/source_compare.txt b/misc/source/darkplaces-src/dpdefs/source_compare.txt new file mode 100644 index 00000000..4fb6d6b1 --- /dev/null +++ b/misc/source/darkplaces-src/dpdefs/source_compare.txt @@ -0,0 +1,433 @@ +// progs: Builtin #487 declared but not defined +// progs: Builtin #488 declared but not defined +// progs: Builtin #489 declared but not defined +// progs: Builtin #490 declared but not defined +// progs: Builtin #491 declared but not defined +// progs: Builtin #492 declared but not defined +// progs: Builtin #493 declared but not defined +// progs: Builtin #608 declared but not defined +// progs: Field frame2 declared but not defined +// progs: Field frame3 declared but not defined +// progs: Field frame4 declared but not defined +// progs: Field lerpfrac declared but not defined +// progs: Field lerpfrac3 declared but not defined +// progs: Field lerpfrac4 declared but not defined +// progs: Field frame1time declared but not defined +// progs: Field frame2time declared but not defined +// progs: Field frame3time declared but not defined +// progs: Field frame4time declared but not defined +// menu: Global null_entity declared but not defined +// progs: Global movedist declared but not defined +// progs: Global gameover declared but not defined +// progs: Global string_null declared but not defined +// progs: Global newmis declared but not defined +// progs: Global activator declared but not defined +// progs: Global damage_attacker declared but not defined +// progs: Global framecount declared but not defined +// progs: Global skill declared but not defined +// progs: Field map declared but not defined +// progs: Field worldtype declared but not defined +// progs: Field killtarget declared but not defined +// progs: Field th_stand declared but not defined +// progs: Field th_walk declared but not defined +// progs: Field th_run declared but not defined +// progs: Field th_missile declared but not defined +// progs: Field th_melee declared but not defined +// progs: Field th_pain declared but not defined +// progs: Field th_die declared but not defined +// progs: Field oldenemy declared but not defined +// progs: Field speed declared but not defined +// progs: Field lefty declared but not defined +// progs: Field search_time declared but not defined +// progs: Field attack_state declared but not defined +// progs: Field attack_finished declared but not defined +// progs: Field pain_finished declared but not defined +// progs: Field invincible_finished declared but not defined +// progs: Field invisible_finished declared but not defined +// progs: Field super_damage_finished declared but not defined +// progs: Field radsuit_finished declared but not defined +// progs: Field spawnshieldtime declared but not defined +// progs: Field invincible_time declared but not defined +// progs: Field invincible_sound declared but not defined +// progs: Field invisible_time declared but not defined +// progs: Field invisible_sound declared but not defined +// progs: Field super_time declared but not defined +// progs: Field super_sound declared but not defined +// progs: Field rad_time declared but not defined +// progs: Field fly_sound declared but not defined +// progs: Field show_hostile declared but not defined +// progs: Field jump_flag declared but not defined +// progs: Field swim_flag declared but not defined +// progs: Field air_finished declared but not defined +// progs: Field bubble_count declared but not defined +// progs: Field deathtype declared but not defined +// progs: Field mdl declared but not defined +// progs: Field mangle declared but not defined +// progs: Field t_length declared but not defined +// progs: Field t_width declared but not defined +// progs: Field dest declared but not defined +// progs: Field dest1 declared but not defined +// progs: Field dest2 declared but not defined +// progs: Field wait declared but not defined +// progs: Field delay declared but not defined +// progs: Field trigger_field declared but not defined +// progs: Field noise4 declared but not defined +// progs: Field pausetime declared but not defined +// progs: Field movetarget declared but not defined +// progs: Field aflag declared but not defined +// progs: Field dmg declared but not defined +// progs: Field cnt declared but not defined +// progs: Field think1 declared but not defined +// progs: Field count declared but not defined +// progs: Field lip declared but not defined +// progs: Field state declared but not defined +// progs: Field pos1 declared but not defined +// progs: Field pos2 declared but not defined +// progs: Field height declared but not defined +// progs: Field waitmin declared but not defined +// progs: Field waitmax declared but not defined +// progs: Field cantrigger declared but not defined +// progs: Global deathstring1 declared but not defined +// progs: Global deathstring2 declared but not defined +// progs: Global deathstring3 declared but not defined +// progs: Global deathstring4 declared but not defined +// progs: Global game declared but not defined +// progs: Global darkmode declared but not defined +// progs: Field bodyhealth declared but not defined +// progs: Field iscorpse declared but not defined +// progs: Field dest3 declared but not defined +// progs: Field dest4 declared but not defined +// progs: Field flame declared but not defined +// progs: Field doobits declared but not defined +// progs: Field rotate declared but not defined +// progs: Field group declared but not defined +// progs: Field keys_silver declared but not defined +// progs: Field keys_gold declared but not defined +// progs: Field havocattack declared but not defined +// progs: Field havocpickup declared but not defined +// progs: Field pickupevalfunc declared but not defined +// progs: Field shoulddodge declared but not defined +// progs: Field dangerrating declared but not defined +// progs: Field bleedfunc declared but not defined +// progs: Field count1 declared but not defined +// progs: Field count2 declared but not defined +// progs: Field count3 declared but not defined +// progs: Field count4 declared but not defined +// progs: Field count5 declared but not defined +// progs: Field count6 declared but not defined +// progs: Field cnt1 declared but not defined +// progs: Field cnt2 declared but not defined +// progs: Field obitfunc1 declared but not defined +// progs: Field realowner declared but not defined +// progs: Global maxclients declared but not defined +// progs: Global numdecors declared but not defined +// progs: Global maxdecors declared but not defined +// progs: Field createdtime declared but not defined +// progs: Field th_gib declared but not defined +// progs: Field resist_bullet declared but not defined +// progs: Field resist_explosive declared but not defined +// progs: Field resist_energy declared but not defined +// progs: Field resist_fire declared but not defined +// progs: Field resist_ice declared but not defined +// progs: Field resist_axe declared but not defined +// progs: Field deathmsg declared but not defined +// progs: Field regenthink declared but not defined +// progs: Field isdecor declared but not defined +// progs: Field radiusdamage_amount declared but not defined +// progs: Field radiusdamage_force declared but not defined +// progs: Field radiusdamage_hit declared but not defined +// progs: Field radiusdamage_lasthit declared but not defined +// progs: Field radiusdamage_ownerdamagescale declared but not defined +// progs: Global raddamage_lasthit declared but not defined +// progs: Field frozen declared but not defined +// progs: Field thawtime declared but not defined +// progs: Field thawedeffects declared but not defined +// progs: Field thawedtouch declared but not defined +// progs: Field thawedmovetype declared but not defined +// progs: Field thawedthink declared but not defined +// progs: Field thawedthinkdelay declared but not defined +// progs: Field knockedloosefunc declared but not defined +// progs: Field forcescale declared but not defined +// progs: Field bleedratio declared but not defined +// progs: Field damagemodifier declared but not defined +// progs: Field healthregen declared but not defined +// progs: Field healthlostthisframe declared but not defined +// progs: Global monsterdamagescale declared but not defined +// progs: Global monsterresistancescale declared but not defined +// progs: Global playerdamagescale declared but not defined +/****************************************** + * csprogs + ******************************************/ +float VM_CL_checkpvs(...) = #240; // DP_UNKNOWN +float skel_create(...) = #263; // FTE_CSQC_SKELETONOBJECTS +float skel_build(...) = #264; // FTE_CSQC_SKELETONOBJECTS +float skel_get_numbones(...) = #265; // FTE_CSQC_SKELETONOBJECTS +float skel_get_bonename(...) = #266; // FTE_CSQC_SKELETONOBJECTS +float skel_get_boneparent(...) = #267; // FTE_CSQC_SKELETONOBJECTS +float skel_find_bone(...) = #268; // FTE_CSQC_SKELETONOBJECTS +float skel_get_bonerel(...) = #269; // FTE_CSQC_SKELETONOBJECTS +float skel_get_boneabs(...) = #270; // FTE_CSQC_SKELETONOBJECTS +float skel_set_bone(...) = #271; // FTE_CSQC_SKELETONOBJECTS +float skel_mul_bone(...) = #272; // FTE_CSQC_SKELETONOBJECTS +float skel_mul_bones(...) = #273; // FTE_CSQC_SKELETONOBJECTS +float skel_copybones(...) = #274; // FTE_CSQC_SKELETONOBJECTS +float skel_delete(...) = #275; // FTE_CSQC_SKELETONOBJECTS +float frameforname(...) = #276; // FTE_CSQC_SKELETONOBJECTS +float frameduration(...) = #277; // FTE_CSQC_SKELETONOBJECTS +float VM_drawsubpic(...) = #328; // DP_UNKNOWN +float VM_drawrotpic(...) = #329; // DP_UNKNOWN +float VM_CL_videoplaying(...) = #355; // DP_UNKNOWN +float crc16(...) = #494; // DP_QC_CRC16 +float cvar_type(...) = #495; // DP_QC_CVAR_TYPE +float numentityfields(...) = #496; // QP_QC_ENTITYDATA +float entityfieldname(...) = #497; // DP_QC_ENTITYDATA +float entityfieldtype(...) = #498; // DP_QC_ENTITYDATA +float getentityfieldstring(...) = #499; // DP_QC_ENTITYDATA +float putentityfieldstring(...) = #500; // DP_QC_ENTITYDATA +float ReadPicture(...) = #501; // DP_UNKNOWN +float boxparticles(...) = #502; // DP_CSQC_BOXPARTICLES +float whichpack(...) = #503; // DP_UNKNOWN +float uri_escape(...) = #510; // DP_UNKNOWN +float uri_unescape(...) = #511; // DP_UNKNOWN +float num_for_edict(...) = #512; // DP_QC_NUM_FOR_EDICT +float tokenize_console(...) = #514; // DP_QC_TOKENIZE_CONSOLE +float argv_start_index(...) = #515; // DP_QC_TOKENIZE_CONSOLE +float argv_end_index(...) = #516; // DP_QC_TOKENIZE_CONSOLE +float buf_cvarlist(...) = #517; // DP_QC_STRINGBUFFERS_CVARLIST +float cvar_description(...) = #518; // DP_QC_CVAR_DESCRIPTION +float gettime(...) = #519; // DP_QC_GETTIME +float keynumtostring(...) = #520; // DP_UNKNOWN +float findkeysforcommand(...) = #521; // DP_UNKNOWN +float VM_loadfromdata(...) = #529; // DP_UNKNOWN +float VM_loadfromfile(...) = #530; // DP_UNKNOWN +float VM_log(...) = #532; // DP_UNKNOWN +float getsoundtime(...) = #533; // DP_SND_GETSOUNDTIME +float soundlength(...) = #534; // DP_SND_GETSOUNDTIME +float physics_enable(...) = #540; // DP_PHYSICS_ODE +float physics_addforce(...) = #541; // DP_PHYSICS_ODE +float physics_addtorque(...) = #542; // DP_PHYSICS_ODE +float VM_callfunction(...) = #605; // DP_UNKNOWN +float VM_writetofile(...) = #606; // DP_UNKNOWN +float VM_isfunction(...) = #607; // DP_UNKNOWN +float VM_parseentitydata(...) = #613; // DP_UNKNOWN +float getextresponse(...) = #624; // DP_UNKNOWN +float sprintf(...) = #627; // DP_UNKNOWN +float getsurfacenumpoints(...) = #628; // DP_QC_GETSURFACETRIANGLE +float getsurfacepoint(...) = #629; // DP_QC_GETSURFACETRIANGLE +float VM_CL_RotateMoves(...) = #638; // DP_UNKNOWN +float CSQC_ConsoleCommand; // DP_UNKNOWN +float CSQC_Init; // DP_UNKNOWN +float CSQC_InputEvent; // DP_UNKNOWN +float CSQC_Shutdown; // DP_UNKNOWN +float CSQC_UpdateView; // DP_UNKNOWN +float coop; // DP_UNKNOWN +float deathmatch; // DP_UNKNOWN +float dmg_origin; // DP_UNKNOWN +float dmg_save; // DP_UNKNOWN +float dmg_take; // DP_UNKNOWN +float drawfontscale; // DP_UNKNOWN +float gettaginfo_forward; // DP_UNKNOWN +float gettaginfo_name; // DP_UNKNOWN +float gettaginfo_offset; // DP_UNKNOWN +float gettaginfo_parent; // DP_UNKNOWN +float gettaginfo_right; // DP_UNKNOWN +float gettaginfo_up; // DP_UNKNOWN +float particles_alphamax; // DP_UNKNOWN +float particles_alphamin; // DP_UNKNOWN +float particles_colormax; // DP_UNKNOWN +float particles_colormin; // DP_UNKNOWN +float sb_showscores; // DP_UNKNOWN +float serverdeltatime; // DP_UNKNOWN +float serverprevtime; // DP_UNKNOWN +float servertime; // DP_UNKNOWN +float trace_dphitcontents; // DP_UNKNOWN +float trace_dphitq3surfaceflags; // DP_UNKNOWN +float trace_dphittexturename; // DP_UNKNOWN +float trace_dpstartcontents; // DP_UNKNOWN +float trace_networkentity; // DP_UNKNOWN +.float aiment; // DP_UNKNOWN +.float alpha; // DP_UNKNOWN +.float camera_transform; // DP_UNKNOWN +.float colormod; // DP_UNKNOWN +.float dimension_hit; // DP_UNKNOWN +.float dimension_solid; // DP_UNKNOWN +.float dphitcontentsmask; // DP_UNKNOWN +.float fatness; // DP_UNKNOWN +.float forceshader; // DP_UNKNOWN +.float frame1time; // DP_UNKNOWN +.float frame2; // DP_UNKNOWN +.float frame2time; // DP_UNKNOWN +.float frame3; // DP_UNKNOWN +.float frame3time; // DP_UNKNOWN +.float frame4; // DP_UNKNOWN +.float frame4time; // DP_UNKNOWN +.float glowmod; // DP_UNKNOWN +.float groundentity; // DP_UNKNOWN +.float hull; // DP_UNKNOWN +.float ideal_yaw; // DP_UNKNOWN +.float idealpitch; // DP_UNKNOWN +.float jointtype; // DP_UNKNOWN +.float lerpfrac; // DP_UNKNOWN +.float lerpfrac3; // DP_UNKNOWN +.float lerpfrac4; // DP_UNKNOWN +.float mass; // DP_UNKNOWN +.float message; // DP_UNKNOWN +.float movedir; // DP_UNKNOWN +.float pitch_speed; // DP_UNKNOWN +.float renderflags; // DP_UNKNOWN +.float scale; // DP_UNKNOWN +.float shadertime; // DP_UNKNOWN +.float skeletonindex; // FTE_CSQC_SKELETONOBJECTS +.float tag_entity; // DP_UNKNOWN +.float tag_index; // DP_UNKNOWN +.float userwavefunc_param0; // DP_UNKNOWN +.float userwavefunc_param1; // DP_UNKNOWN +.float userwavefunc_param2; // DP_UNKNOWN +.float userwavefunc_param3; // DP_UNKNOWN +.float yaw_speed; // DP_UNKNOWN +/****************************************** + * menu + ******************************************/ +float VM_itof(...) = #79; // DP_UNKNOWN +float VM_ftoe(...) = #80; // DP_UNKNOWN +float isString(...) = #81; // DP_UNKNOWN +float VM_altstr_count(...) = #82; // DP_UNKNOWN +float VM_altstr_prepare(...) = #83; // DP_UNKNOWN +float VM_altstr_get(...) = #84; // DP_UNKNOWN +float VM_altstr_set(...) = #85; // DP_UNKNOWN +float VM_altstr_ins(...) = #86; // DP_UNKNOWN +float VM_findflags(...) = #87; // DP_UNKNOWN +float VM_findchainflags(...) = #88; // DP_UNKNOWN +float VM_cvar_defstring(...) = #89; // DP_UNKNOWN +float strstrofs(...) = #221; // FTE_STRINGS +float str2chr(...) = #222; // FTE_STRINGS +float chr2str(...) = #223; // FTE_STRINGS +float strconv(...) = #224; // FTE_STRINGS +float strpad(...) = #225; // FTE_STRINGS +float infoadd(...) = #226; // FTE_STRINGS +float infoget(...) = #227; // FTE_STRINGS +float strncmp(...) = #228; // FTE_STRINGS +float strcasecmp(...) = #229; // FTE_STRINGS +float strncasecmp(...) = #230; // FTE_STRINGS +float keynumtostring(...) = #340; // DP_UNKNOWN +float VM_CL_isdemo(...) = #349; // DP_UNKNOWN +float wasfreed(...) = #353; // DP_UNKNOWN +float VM_CL_videoplaying(...) = #355; // DP_UNKNOWN +float loadfont(...) = #356; // DP_GFX_FONTS +float loadfont(...) = #357; // DP_GFX_FONTS +float buf_create(...) = #440; // DP_QC_STRINGBUFFERS +float buf_del(...) = #441; // DP_QC_STRINGBUFFERS +float buf_getsize(...) = #442; // DP_QC_STRINGBUFFERS +float buf_copy(...) = #443; // DP_QC_STRINGBUFFERS +float buf_sort(...) = #444; // DP_QC_STRINGBUFFERS +float buf_implode(...) = #445; // DP_QC_STRINGBUFFERS +float bufstr_get(...) = #446; // DP_QC_STRINGBUFFERS +float bufstr_set(...) = #447; // DP_QC_STRINGBUFFERS +float bufstr_add(...) = #448; // DP_QC_STRINGBUFFERS +float bufstr_free(...) = #449; // DP_QC_STRINGBUFFERS +float VM_cin_open(...) = #461; // DP_UNKNOWN +float VM_cin_close(...) = #462; // DP_UNKNOWN +float VM_cin_setstate(...) = #463; // DP_UNKNOWN +float VM_cin_getstate(...) = #464; // DP_UNKNOWN +float VM_cin_restart(...) = #465; // DP_UNKNOWN +float VM_drawline(...) = #466; // DP_UNKNOWN +float VM_stringwidth(...) = #468; // DP_UNKNOWN +float VM_drawsubpic(...) = #469; // DP_UNKNOWN +float VM_drawrotpic(...) = #470; // DP_UNKNOWN +float VM_asin(...) = #471; // DP_QC_ASINACOSATANATAN2TAN +float VM_acos(...) = #472; // DP_QC_ASINACOSATANATAN2TAN +float VM_atan(...) = #473; // DP_QC_ASINACOSATANATAN2TAN +float VM_atan2(...) = #474; // DP_QC_ASINACOSATANATAN2TAN +float VM_tan(...) = #475; // DP_QC_ASINACOSATANATAN2TAN +float float(...) = #476; // DP_QC_STRINGCOLORFUNCTIONS +float string(...) = #477; // DP_QC_STRINGCOLORFUNCTIONS +float string(...) = #478; // DP_QC_STRFTIME +float tokenizebyseparator(...) = #479; // DP_QC_TOKENIZEBYSEPARATOR +float VM_strtolower(...) = #480; // DP_UNKNOWN +float VM_strtoupper(...) = #481; // DP_UNKNOWN +float strreplace(...) = #484; // DP_QC_STRREPLACE +float strireplace(...) = #485; // DP_QC_STRREPLACE +float gecko_create(...) = #487; // DP_UNKNOWN +float gecko_destroy(...) = #488; // DP_UNKNOWN +float gecko_navigate(...) = #489; // DP_UNKNOWN +float gecko_keyevent(...) = #490; // DP_UNKNOWN +float gecko_mousemove(...) = #491; // DP_UNKNOWN +float gecko_resize(...) = #492; // DP_UNKNOWN +float gecko_get_texture_extent(...) = #493; // DP_UNKNOWN +float crc16(...) = #494; // DP_QC_CRC16 +float cvar_type(...) = #495; // DP_QC_CVAR_TYPE +float whichpack(...) = #503; // DP_UNKNOWN +float uri_escape(...) = #510; // DP_UNKNOWN +float uri_unescape(...) = #511; // DP_UNKNOWN +float num_for_edict(...) = #512; // DP_QC_NUM_FOR_EDICT +float tokenize_console(...) = #514; // DP_QC_TOKENIZE_CONSOLE +float argv_start_index(...) = #515; // DP_QC_TOKENIZE_CONSOLE +float argv_end_index(...) = #516; // DP_QC_TOKENIZE_CONSOLE +float buf_cvarlist(...) = #517; // DP_QC_STRINGBUFFERS_CVARLIST +float cvar_description(...) = #518; // DP_QC_CVAR_DESCRIPTION +float VM_log(...) = #532; // DP_UNKNOWN +float getsoundtime(...) = #533; // DP_SND_GETSOUNDTIME +float soundlength(...) = #534; // DP_SND_GETSOUNDTIME +float parseentitydata(...) = #613; // DP_UNKNOWN +float stringtokeynum(...) = #614; // DP_UNKNOWN +float resethostcachemasks(...) = #615; // DP_UNKNOWN +float sethostcachemaskstring(...) = #616; // DP_UNKNOWN +float sethostcachemasknumber(...) = #617; // DP_UNKNOWN +float resorthostcache(...) = #618; // DP_UNKNOWN +float sethostcachesort(...) = #619; // DP_UNKNOWN +float refreshhostcache(...) = #620; // DP_UNKNOWN +float gethostcachenumber(...) = #621; // DP_UNKNOWN +float gethostcacheindexforkey(...) = #622; // DP_UNKNOWN +float addwantedhostcachekey(...) = #623; // DP_UNKNOWN +float getextresponse(...) = #624; // DP_UNKNOWN +float netaddress_resolve(...) = #625; // DP_UNKNOWN +float getgamedirinfo(...) = #626; // DP_UNKNOWN +float sprintf(...) = #627; // DP_UNKNOWN +float drawfont; // DP_UNKNOWN +float drawfontscale; // DP_UNKNOWN +.float angles; // DP_UNKNOWN +.float chain; // DP_UNKNOWN +.float classname; // DP_UNKNOWN +.float frame; // OP_STATE +.float nextthink; // OP_STATE +.float think; // OP_STATE +/****************************************** + * progs + ******************************************/ +float setmodelindex(...) = #333; // EXT_CSQC +float modelnameforindex(...) = #334; // EXT_CSQC +float isserver(...) = #350; // EXT_CSQC +float serverkey(...) = #354; // EXT_CSQC +float VM_parseentitydata(...) = #613; // DP_UNKNOWN +float ClientConnect; // DP_UNKNOWN +float ClientDisconnect; // DP_UNKNOWN +float ClientKill; // DP_UNKNOWN +float PlayerPostThink; // DP_UNKNOWN +float PlayerPreThink; // DP_UNKNOWN +float PutClientInServer; // DP_UNKNOWN +float SV_InitCmd; // DP_UNKNOWN +float SetChangeParms; // DP_UNKNOWN +float SetNewParms; // DP_UNKNOWN +float StartFrame; // DP_UNKNOWN +float main; // DP_UNKNOWN +float require_spawnfunc_prefix; // DP_UNKNOWN +.float SendEntity; // DP_UNKNOWN +.float SendFlags; // DP_UNKNOWN +.float Version; // DP_UNKNOWN +.float ammo_cells1; // DP_UNKNOWN +.float ammo_lava_nails; // DP_UNKNOWN +.float ammo_multi_rockets; // DP_UNKNOWN +.float ammo_nails1; // DP_UNKNOWN +.float ammo_plasma; // DP_UNKNOWN +.float ammo_rockets1; // DP_UNKNOWN +.float ammo_shells1; // DP_UNKNOWN +.float dimension_hit; // DP_UNKNOWN +.float dimension_solid; // DP_UNKNOWN +.float fatness; // DP_UNKNOWN +.float fullbright; // DP_UNKNOWN +.float hull; // DP_UNKNOWN +.float items2; // DP_UNKNOWN +.float pmodel; // DP_UNKNOWN +.float renderamt; // DP_UNKNOWN +.float rendermode; // DP_UNKNOWN +.float sendcomplexanimation; // DP_UNKNOWN diff --git a/misc/source/darkplaces-src/dpsoftrast.c b/misc/source/darkplaces-src/dpsoftrast.c new file mode 100644 index 00000000..179a4d0c --- /dev/null +++ b/misc/source/darkplaces-src/dpsoftrast.c @@ -0,0 +1,5682 @@ +#include +#include +#define _USE_MATH_DEFINES +#include +#include "quakedef.h" +#include "thread.h" +#include "dpsoftrast.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4324) +#endif + +#ifndef __cplusplus +typedef qboolean bool; +#endif + +#define ALIGN_SIZE 16 +#define ATOMIC_SIZE 32 + +#ifdef SSE_POSSIBLE + #if defined(__APPLE__) + #include + #define ALIGN(var) var __attribute__((__aligned__(16))) + #define ATOMIC(var) var __attribute__((__aligned__(32))) + #define MEMORY_BARRIER (_mm_sfence()) + #define ATOMIC_COUNTER volatile int32_t + #define ATOMIC_INCREMENT(counter) (OSAtomicIncrement32Barrier(&(counter))) + #define ATOMIC_DECREMENT(counter) (OSAtomicDecrement32Barrier(&(counter))) + #define ATOMIC_ADD(counter, val) ((void)OSAtomicAdd32Barrier((val), &(counter))) + #elif defined(__GNUC__) && defined(WIN32) + #define ALIGN(var) var __attribute__((__aligned__(16))) + #define ATOMIC(var) var __attribute__((__aligned__(32))) + #define MEMORY_BARRIER (_mm_sfence()) + //(__sync_synchronize()) + #define ATOMIC_COUNTER volatile LONG + // this LONG * cast serves to fix an issue with broken mingw + // packages on Ubuntu; these only declare the function to take + // a LONG *, causing a compile error here. This seems to be + // error- and warn-free on platforms that DO declare + // InterlockedIncrement correctly, like mingw on Windows. + #define ATOMIC_INCREMENT(counter) (InterlockedIncrement((LONG *) &(counter))) + #define ATOMIC_DECREMENT(counter) (InterlockedDecrement((LONG *) &(counter))) + #define ATOMIC_ADD(counter, val) ((void)InterlockedExchangeAdd((LONG *) &(counter), (val))) + #elif defined(__GNUC__) + #define ALIGN(var) var __attribute__((__aligned__(16))) + #define ATOMIC(var) var __attribute__((__aligned__(32))) + #define MEMORY_BARRIER (_mm_sfence()) + //(__sync_synchronize()) + #define ATOMIC_COUNTER volatile int + #define ATOMIC_INCREMENT(counter) (__sync_add_and_fetch(&(counter), 1)) + #define ATOMIC_DECREMENT(counter) (__sync_add_and_fetch(&(counter), -1)) + #define ATOMIC_ADD(counter, val) ((void)__sync_fetch_and_add(&(counter), (val))) + #elif defined(_MSC_VER) + #define ALIGN(var) __declspec(align(16)) var + #define ATOMIC(var) __declspec(align(32)) var + #define MEMORY_BARRIER (_mm_sfence()) + //(MemoryBarrier()) + #define ATOMIC_COUNTER volatile LONG + #define ATOMIC_INCREMENT(counter) (InterlockedIncrement(&(counter))) + #define ATOMIC_DECREMENT(counter) (InterlockedDecrement(&(counter))) + #define ATOMIC_ADD(counter, val) ((void)InterlockedExchangeAdd(&(counter), (val))) + #endif +#endif + +#ifndef ALIGN +#define ALIGN(var) var +#endif +#ifndef ATOMIC +#define ATOMIC(var) var +#endif +#ifndef MEMORY_BARRIER +#define MEMORY_BARRIER ((void)0) +#endif +#ifndef ATOMIC_COUNTER +#define ATOMIC_COUNTER int +#endif +#ifndef ATOMIC_INCREMENT +#define ATOMIC_INCREMENT(counter) (++(counter)) +#endif +#ifndef ATOMIC_DECREMENT +#define ATOMIC_DECREMENT(counter) (--(counter)) +#endif +#ifndef ATOMIC_ADD +#define ATOMIC_ADD(counter, val) ((void)((counter) += (val))) +#endif + +#ifdef SSE_POSSIBLE +#include + +#if defined(__GNUC__) && (__GNUC < 4 || __GNUC_MINOR__ < 6) && !defined(__clang__) + #define _mm_cvtss_f32(val) (__builtin_ia32_vec_ext_v4sf ((__v4sf)(val), 0)) +#endif + +#define MM_MALLOC(size) _mm_malloc(size, ATOMIC_SIZE) + +static void *MM_CALLOC(size_t nmemb, size_t size) +{ + void *ptr = _mm_malloc(nmemb*size, ATOMIC_SIZE); + if (ptr != NULL) memset(ptr, 0, nmemb*size); + return ptr; +} + +#define MM_FREE _mm_free +#else +#define MM_MALLOC(size) malloc(size) +#define MM_CALLOC(nmemb, size) calloc(nmemb, size) +#define MM_FREE free +#endif + +typedef enum DPSOFTRAST_ARRAY_e +{ + DPSOFTRAST_ARRAY_POSITION, + DPSOFTRAST_ARRAY_COLOR, + DPSOFTRAST_ARRAY_TEXCOORD0, + DPSOFTRAST_ARRAY_TEXCOORD1, + DPSOFTRAST_ARRAY_TEXCOORD2, + DPSOFTRAST_ARRAY_TEXCOORD3, + DPSOFTRAST_ARRAY_TEXCOORD4, + DPSOFTRAST_ARRAY_TEXCOORD5, + DPSOFTRAST_ARRAY_TEXCOORD6, + DPSOFTRAST_ARRAY_TEXCOORD7, + DPSOFTRAST_ARRAY_TOTAL +} +DPSOFTRAST_ARRAY; + +typedef struct DPSOFTRAST_Texture_s +{ + int flags; + int width; + int height; + int depth; + int sides; + DPSOFTRAST_TEXTURE_FILTER filter; + int mipmaps; + int size; + ATOMIC_COUNTER binds; + unsigned char *bytes; + int mipmap[DPSOFTRAST_MAXMIPMAPS][5]; +} +DPSOFTRAST_Texture; + +#define COMMAND_SIZE ALIGN_SIZE +#define COMMAND_ALIGN(var) ALIGN(var) + +typedef COMMAND_ALIGN(struct DPSOFTRAST_Command_s +{ + unsigned char opcode; + unsigned short commandsize; +} +DPSOFTRAST_Command); + +enum { DPSOFTRAST_OPCODE_Reset = 0 }; + +#define DEFCOMMAND(opcodeval, name, fields) \ + enum { DPSOFTRAST_OPCODE_##name = opcodeval }; \ + typedef COMMAND_ALIGN(struct DPSOFTRAST_Command_##name##_s \ + { \ + unsigned char opcode; \ + unsigned short commandsize; \ + fields \ + } DPSOFTRAST_Command_##name ); + +#define DPSOFTRAST_DRAW_MAXCOMMANDPOOL 2097152 +#define DPSOFTRAST_DRAW_MAXCOMMANDSIZE 16384 + +typedef ATOMIC(struct DPSOFTRAST_State_Command_Pool_s +{ + int freecommand; + int usedcommands; + ATOMIC(unsigned char commands[DPSOFTRAST_DRAW_MAXCOMMANDPOOL]); +} +DPSOFTRAST_State_Command_Pool); + +typedef ATOMIC(struct DPSOFTRAST_State_Triangle_s +{ + unsigned char mip[DPSOFTRAST_MAXTEXTUREUNITS]; // texcoord to screen space density values (for picking mipmap of textures) + float w[3]; + ALIGN(float attribs[DPSOFTRAST_ARRAY_TOTAL][3][4]); +} +DPSOFTRAST_State_Triangle); + +#define DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex) { \ + slope = _mm_load_ps((triangle)->attribs[arrayindex][0]); \ + data = _mm_add_ps(_mm_load_ps((triangle)->attribs[arrayindex][2]), \ + _mm_add_ps(_mm_mul_ps(_mm_set1_ps((span)->x), slope), \ + _mm_mul_ps(_mm_set1_ps((span)->y), _mm_load_ps((triangle)->attribs[arrayindex][1])))); \ +} +#define DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex) { \ + slope[0] = (triangle)->attribs[arrayindex][0][0]; \ + slope[1] = (triangle)->attribs[arrayindex][0][1]; \ + slope[2] = (triangle)->attribs[arrayindex][0][2]; \ + slope[3] = (triangle)->attribs[arrayindex][0][3]; \ + data[0] = (triangle)->attribs[arrayindex][2][0] + (span->x)*slope[0] + (span->y)*(triangle)->attribs[arrayindex][1][0]; \ + data[1] = (triangle)->attribs[arrayindex][2][1] + (span->x)*slope[1] + (span->y)*(triangle)->attribs[arrayindex][1][1]; \ + data[2] = (triangle)->attribs[arrayindex][2][2] + (span->x)*slope[2] + (span->y)*(triangle)->attribs[arrayindex][1][2]; \ + data[3] = (triangle)->attribs[arrayindex][2][3] + (span->x)*slope[3] + (span->y)*(triangle)->attribs[arrayindex][1][3]; \ +} + +#define DPSOFTRAST_DRAW_MAXSUBSPAN 16 + +typedef ALIGN(struct DPSOFTRAST_State_Span_s +{ + int triangle; // triangle this span was generated by + int x; // framebuffer x coord + int y; // framebuffer y coord + int startx; // usable range (according to pixelmask) + int endx; // usable range (according to pixelmask) + unsigned char *pixelmask; // true for pixels that passed depth test, false for others + int depthbase; // depthbuffer value at x (add depthslope*startx to get first pixel's depthbuffer value) + int depthslope; // depthbuffer value pixel delta +} +DPSOFTRAST_State_Span); + +#define DPSOFTRAST_DRAW_MAXSPANS 1024 +#define DPSOFTRAST_DRAW_MAXTRIANGLES 128 +#define DPSOFTRAST_DRAW_MAXSPANLENGTH 256 + +#define DPSOFTRAST_VALIDATE_FB 1 +#define DPSOFTRAST_VALIDATE_DEPTHFUNC 2 +#define DPSOFTRAST_VALIDATE_BLENDFUNC 4 +#define DPSOFTRAST_VALIDATE_DRAW (DPSOFTRAST_VALIDATE_FB | DPSOFTRAST_VALIDATE_DEPTHFUNC | DPSOFTRAST_VALIDATE_BLENDFUNC) + +typedef enum DPSOFTRAST_BLENDMODE_e +{ + DPSOFTRAST_BLENDMODE_OPAQUE, + DPSOFTRAST_BLENDMODE_ALPHA, + DPSOFTRAST_BLENDMODE_ADDALPHA, + DPSOFTRAST_BLENDMODE_ADD, + DPSOFTRAST_BLENDMODE_INVMOD, + DPSOFTRAST_BLENDMODE_MUL, + DPSOFTRAST_BLENDMODE_MUL2, + DPSOFTRAST_BLENDMODE_SUBALPHA, + DPSOFTRAST_BLENDMODE_PSEUDOALPHA, + DPSOFTRAST_BLENDMODE_INVADD, + DPSOFTRAST_BLENDMODE_TOTAL +} +DPSOFTRAST_BLENDMODE; + +typedef ATOMIC(struct DPSOFTRAST_State_Thread_s +{ + void *thread; + int index; + + int cullface; + int colormask[4]; + int blendfunc[2]; + int blendsubtract; + int depthmask; + int depthtest; + int depthfunc; + int scissortest; + int viewport[4]; + int scissor[4]; + float depthrange[2]; + float polygonoffset[2]; + float clipplane[4]; + ALIGN(float fb_clipplane[4]); + + int shader_mode; + int shader_permutation; + int shader_exactspecularmath; + + DPSOFTRAST_Texture *texbound[DPSOFTRAST_MAXTEXTUREUNITS]; + + ALIGN(float uniform4f[DPSOFTRAST_UNIFORM_TOTAL*4]); + int uniform1i[DPSOFTRAST_UNIFORM_TOTAL]; + + // DPSOFTRAST_VALIDATE_ flags + int validate; + + // derived values (DPSOFTRAST_VALIDATE_FB) + int fb_colormask; + int fb_scissor[4]; + ALIGN(float fb_viewportcenter[4]); + ALIGN(float fb_viewportscale[4]); + + // derived values (DPSOFTRAST_VALIDATE_DEPTHFUNC) + int fb_depthfunc; + + // derived values (DPSOFTRAST_VALIDATE_BLENDFUNC) + int fb_blendmode; + + // band boundaries + int miny1; + int maxy1; + int miny2; + int maxy2; + + ATOMIC(volatile int commandoffset); + + volatile bool waiting; + volatile bool starving; + void *waitcond; + void *drawcond; + void *drawmutex; + + int numspans; + int numtriangles; + DPSOFTRAST_State_Span spans[DPSOFTRAST_DRAW_MAXSPANS]; + DPSOFTRAST_State_Triangle triangles[DPSOFTRAST_DRAW_MAXTRIANGLES]; + unsigned char pixelmaskarray[DPSOFTRAST_DRAW_MAXSPANLENGTH+4]; // LordHavoc: padded to allow some termination bytes +} +DPSOFTRAST_State_Thread); + +typedef ATOMIC(struct DPSOFTRAST_State_s +{ + int fb_width; + int fb_height; + unsigned int *fb_depthpixels; + unsigned int *fb_colorpixels[4]; + + int viewport[4]; + ALIGN(float fb_viewportcenter[4]); + ALIGN(float fb_viewportscale[4]); + + float color[4]; + ALIGN(float uniform4f[DPSOFTRAST_UNIFORM_TOTAL*4]); + int uniform1i[DPSOFTRAST_UNIFORM_TOTAL]; + + const float *pointer_vertex3f; + const float *pointer_color4f; + const unsigned char *pointer_color4ub; + const float *pointer_texcoordf[DPSOFTRAST_MAXTEXCOORDARRAYS]; + int stride_vertex; + int stride_color; + int stride_texcoord[DPSOFTRAST_MAXTEXCOORDARRAYS]; + int components_texcoord[DPSOFTRAST_MAXTEXCOORDARRAYS]; + DPSOFTRAST_Texture *texbound[DPSOFTRAST_MAXTEXTUREUNITS]; + + int firstvertex; + int numvertices; + float *post_array4f[DPSOFTRAST_ARRAY_TOTAL]; + float *screencoord4f; + int drawstarty; + int drawendy; + int drawclipped; + + int shader_mode; + int shader_permutation; + int shader_exactspecularmath; + + int texture_max; + int texture_end; + int texture_firstfree; + DPSOFTRAST_Texture *texture; + + int bigendian; + + // error reporting + const char *errorstring; + + bool usethreads; + int interlace; + int numthreads; + DPSOFTRAST_State_Thread *threads; + + ATOMIC(volatile int drawcommand); + + DPSOFTRAST_State_Command_Pool commandpool; +} +DPSOFTRAST_State); + +DPSOFTRAST_State dpsoftrast; + +#define DPSOFTRAST_DEPTHSCALE (1024.0f*1048576.0f) +#define DPSOFTRAST_DEPTHOFFSET (128.0f) +#define DPSOFTRAST_BGRA8_FROM_RGBA32F(r,g,b,a) (((int)(r * 255.0f + 0.5f) << 16) | ((int)(g * 255.0f + 0.5f) << 8) | (int)(b * 255.0f + 0.5f) | ((int)(a * 255.0f + 0.5f) << 24)) +#define DPSOFTRAST_DEPTH32_FROM_DEPTH32F(d) ((int)(DPSOFTRAST_DEPTHSCALE * (1-d))) + +static void DPSOFTRAST_Draw_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_State_Span *span); +static void DPSOFTRAST_Draw_DepthWrite(const DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Span *span); + +static void DPSOFTRAST_RecalcViewport(const int *viewport, float *fb_viewportcenter, float *fb_viewportscale) +{ + fb_viewportcenter[1] = viewport[0] + 0.5f * viewport[2] - 0.5f; + fb_viewportcenter[2] = dpsoftrast.fb_height - viewport[1] - 0.5f * viewport[3] - 0.5f; + fb_viewportcenter[3] = 0.5f; + fb_viewportcenter[0] = 0.0f; + fb_viewportscale[1] = 0.5f * viewport[2]; + fb_viewportscale[2] = -0.5f * viewport[3]; + fb_viewportscale[3] = 0.5f; + fb_viewportscale[0] = 1.0f; +} + +static void DPSOFTRAST_RecalcThread(DPSOFTRAST_State_Thread *thread) +{ + if (dpsoftrast.interlace) + { + thread->miny1 = (thread->index*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); + thread->maxy1 = ((thread->index+1)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); + thread->miny2 = ((dpsoftrast.numthreads+thread->index)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); + thread->maxy2 = ((dpsoftrast.numthreads+thread->index+1)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); + } + else + { + thread->miny1 = thread->miny2 = (thread->index*dpsoftrast.fb_height)/dpsoftrast.numthreads; + thread->maxy1 = thread->maxy2 = ((thread->index+1)*dpsoftrast.fb_height)/dpsoftrast.numthreads; + } +} + +static void DPSOFTRAST_RecalcClipPlane(DPSOFTRAST_State_Thread *thread) +{ + thread->fb_clipplane[0] = thread->clipplane[0] / thread->fb_viewportscale[1]; + thread->fb_clipplane[1] = thread->clipplane[1] / thread->fb_viewportscale[2]; + thread->fb_clipplane[2] = thread->clipplane[2] / thread->fb_viewportscale[3]; + thread->fb_clipplane[3] = thread->clipplane[3] / thread->fb_viewportscale[0]; + thread->fb_clipplane[3] -= thread->fb_viewportcenter[1]*thread->fb_clipplane[0] + thread->fb_viewportcenter[2]*thread->fb_clipplane[1] + thread->fb_viewportcenter[3]*thread->fb_clipplane[2] + thread->fb_viewportcenter[0]*thread->fb_clipplane[3]; +} + +static void DPSOFTRAST_RecalcFB(DPSOFTRAST_State_Thread *thread) +{ + // calculate framebuffer scissor, viewport, viewport clipped by scissor, + // and viewport projection values + int x1, x2; + int y1, y2; + x1 = thread->scissor[0]; + x2 = thread->scissor[0] + thread->scissor[2]; + y1 = dpsoftrast.fb_height - thread->scissor[1] - thread->scissor[3]; + y2 = dpsoftrast.fb_height - thread->scissor[1]; + if (!thread->scissortest) {x1 = 0;y1 = 0;x2 = dpsoftrast.fb_width;y2 = dpsoftrast.fb_height;} + if (x1 < 0) x1 = 0; + if (x2 > dpsoftrast.fb_width) x2 = dpsoftrast.fb_width; + if (y1 < 0) y1 = 0; + if (y2 > dpsoftrast.fb_height) y2 = dpsoftrast.fb_height; + thread->fb_scissor[0] = x1; + thread->fb_scissor[1] = y1; + thread->fb_scissor[2] = x2 - x1; + thread->fb_scissor[3] = y2 - y1; + + DPSOFTRAST_RecalcViewport(thread->viewport, thread->fb_viewportcenter, thread->fb_viewportscale); + DPSOFTRAST_RecalcClipPlane(thread); + DPSOFTRAST_RecalcThread(thread); +} + +static void DPSOFTRAST_RecalcDepthFunc(DPSOFTRAST_State_Thread *thread) +{ + thread->fb_depthfunc = thread->depthtest ? thread->depthfunc : GL_ALWAYS; +} + +static void DPSOFTRAST_RecalcBlendFunc(DPSOFTRAST_State_Thread *thread) +{ + if (thread->blendsubtract) + { + switch ((thread->blendfunc[0]<<16)|thread->blendfunc[1]) + { + #define BLENDFUNC(sfactor, dfactor, blendmode) \ + case (sfactor<<16)|dfactor: thread->fb_blendmode = blendmode; break; + BLENDFUNC(GL_SRC_ALPHA, GL_ONE, DPSOFTRAST_BLENDMODE_SUBALPHA) + default: thread->fb_blendmode = DPSOFTRAST_BLENDMODE_OPAQUE; break; + } + } + else + { + switch ((thread->blendfunc[0]<<16)|thread->blendfunc[1]) + { + BLENDFUNC(GL_ONE, GL_ZERO, DPSOFTRAST_BLENDMODE_OPAQUE) + BLENDFUNC(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, DPSOFTRAST_BLENDMODE_ALPHA) + BLENDFUNC(GL_SRC_ALPHA, GL_ONE, DPSOFTRAST_BLENDMODE_ADDALPHA) + BLENDFUNC(GL_ONE, GL_ONE, DPSOFTRAST_BLENDMODE_ADD) + BLENDFUNC(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, DPSOFTRAST_BLENDMODE_INVMOD) + BLENDFUNC(GL_ZERO, GL_SRC_COLOR, DPSOFTRAST_BLENDMODE_MUL) + BLENDFUNC(GL_DST_COLOR, GL_ZERO, DPSOFTRAST_BLENDMODE_MUL) + BLENDFUNC(GL_DST_COLOR, GL_SRC_COLOR, DPSOFTRAST_BLENDMODE_MUL2) + BLENDFUNC(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, DPSOFTRAST_BLENDMODE_PSEUDOALPHA) + BLENDFUNC(GL_ONE_MINUS_DST_COLOR, GL_ONE, DPSOFTRAST_BLENDMODE_INVADD) + default: thread->fb_blendmode = DPSOFTRAST_BLENDMODE_OPAQUE; break; + } + } +} + +#define DPSOFTRAST_ValidateQuick(thread, f) ((thread->validate & (f)) ? (DPSOFTRAST_Validate(thread, f), 0) : 0) + +static void DPSOFTRAST_Validate(DPSOFTRAST_State_Thread *thread, int mask) +{ + mask &= thread->validate; + if (!mask) + return; + if (mask & DPSOFTRAST_VALIDATE_FB) + { + thread->validate &= ~DPSOFTRAST_VALIDATE_FB; + DPSOFTRAST_RecalcFB(thread); + } + if (mask & DPSOFTRAST_VALIDATE_DEPTHFUNC) + { + thread->validate &= ~DPSOFTRAST_VALIDATE_DEPTHFUNC; + DPSOFTRAST_RecalcDepthFunc(thread); + } + if (mask & DPSOFTRAST_VALIDATE_BLENDFUNC) + { + thread->validate &= ~DPSOFTRAST_VALIDATE_BLENDFUNC; + DPSOFTRAST_RecalcBlendFunc(thread); + } +} + +DPSOFTRAST_Texture *DPSOFTRAST_Texture_GetByIndex(int index) +{ + if (index >= 1 && index < dpsoftrast.texture_end && dpsoftrast.texture[index].bytes) + return &dpsoftrast.texture[index]; + return NULL; +} + +static void DPSOFTRAST_Texture_Grow(void) +{ + DPSOFTRAST_Texture *oldtexture = dpsoftrast.texture; + DPSOFTRAST_State_Thread *thread; + int i; + int j; + DPSOFTRAST_Flush(); + // expand texture array as needed + if (dpsoftrast.texture_max < 1024) + dpsoftrast.texture_max = 1024; + else + dpsoftrast.texture_max *= 2; + dpsoftrast.texture = (DPSOFTRAST_Texture *)realloc(dpsoftrast.texture, dpsoftrast.texture_max * sizeof(DPSOFTRAST_Texture)); + for (i = 0; i < DPSOFTRAST_MAXTEXTUREUNITS; i++) + if (dpsoftrast.texbound[i]) + dpsoftrast.texbound[i] = dpsoftrast.texture + (dpsoftrast.texbound[i] - oldtexture); + for (j = 0; j < dpsoftrast.numthreads; j++) + { + thread = &dpsoftrast.threads[j]; + for (i = 0; i < DPSOFTRAST_MAXTEXTUREUNITS; i++) + if (thread->texbound[i]) + thread->texbound[i] = dpsoftrast.texture + (thread->texbound[i] - oldtexture); + } +} + +int DPSOFTRAST_Texture_New(int flags, int width, int height, int depth) +{ + int w; + int h; + int d; + int size; + int s; + int texnum; + int mipmaps; + int sides = (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) ? 6 : 1; + int texformat = flags & DPSOFTRAST_TEXTURE_FORMAT_COMPAREMASK; + DPSOFTRAST_Texture *texture; + if (width*height*depth < 1) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: width, height or depth is less than 1"; + return 0; + } + if (width > DPSOFTRAST_TEXTURE_MAXSIZE || height > DPSOFTRAST_TEXTURE_MAXSIZE || depth > DPSOFTRAST_TEXTURE_MAXSIZE) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: texture size is too large"; + return 0; + } + switch(texformat) + { + case DPSOFTRAST_TEXTURE_FORMAT_BGRA8: + case DPSOFTRAST_TEXTURE_FORMAT_RGBA8: + case DPSOFTRAST_TEXTURE_FORMAT_ALPHA8: + break; + case DPSOFTRAST_TEXTURE_FORMAT_DEPTH: + if (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH only permitted on 2D textures"; + return 0; + } + if (depth != 1) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH only permitted on 2D textures"; + return 0; + } + if ((flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP) && (texformat == DPSOFTRAST_TEXTURE_FORMAT_DEPTH)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH does not permit mipmaps"; + return 0; + } + break; + } + if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_CUBEMAP can not be used on 3D textures"; + return 0; + } + if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on 3D textures"; + return 0; + } + if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on 3D textures"; + return 0; + } + if ((flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on cubemap textures"; + return 0; + } + if ((width & (width-1)) || (height & (height-1)) || (depth & (depth-1))) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: dimensions are not power of two"; + return 0; + } + // find first empty slot in texture array + for (texnum = dpsoftrast.texture_firstfree;texnum < dpsoftrast.texture_end;texnum++) + if (!dpsoftrast.texture[texnum].bytes) + break; + dpsoftrast.texture_firstfree = texnum + 1; + if (dpsoftrast.texture_max <= texnum) + DPSOFTRAST_Texture_Grow(); + if (dpsoftrast.texture_end <= texnum) + dpsoftrast.texture_end = texnum + 1; + texture = &dpsoftrast.texture[texnum]; + memset(texture, 0, sizeof(*texture)); + texture->flags = flags; + texture->width = width; + texture->height = height; + texture->depth = depth; + texture->sides = sides; + texture->binds = 0; + w = width; + h = height; + d = depth; + size = 0; + mipmaps = 0; + w = width; + h = height; + d = depth; + for (;;) + { + s = w * h * d * sides * 4; + texture->mipmap[mipmaps][0] = size; + texture->mipmap[mipmaps][1] = s; + texture->mipmap[mipmaps][2] = w; + texture->mipmap[mipmaps][3] = h; + texture->mipmap[mipmaps][4] = d; + size += s; + mipmaps++; + if (w * h * d == 1 || !(flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) + break; + if (w > 1) w >>= 1; + if (h > 1) h >>= 1; + if (d > 1) d >>= 1; + } + texture->mipmaps = mipmaps; + texture->size = size; + + // allocate the pixels now + texture->bytes = (unsigned char *)MM_CALLOC(1, size); + + return texnum; +} +void DPSOFTRAST_Texture_Free(int index) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (texture->binds) + DPSOFTRAST_Flush(); + if (texture->bytes) + MM_FREE(texture->bytes); + texture->bytes = NULL; + memset(texture, 0, sizeof(*texture)); + // adjust the free range and used range + if (dpsoftrast.texture_firstfree > index) + dpsoftrast.texture_firstfree = index; + while (dpsoftrast.texture_end > 0 && dpsoftrast.texture[dpsoftrast.texture_end-1].bytes == NULL) + dpsoftrast.texture_end--; +} +void DPSOFTRAST_Texture_CalculateMipmaps(int index) +{ + int i, x, y, z, w, layer0, layer1, row0, row1; + unsigned char *o, *i0, *i1, *i2, *i3; + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (texture->mipmaps <= 1) + return; + for (i = 1;i < texture->mipmaps;i++) + { + for (z = 0;z < texture->mipmap[i][4];z++) + { + layer0 = z*2; + layer1 = z*2+1; + if (layer1 >= texture->mipmap[i-1][4]) + layer1 = texture->mipmap[i-1][4]-1; + for (y = 0;y < texture->mipmap[i][3];y++) + { + row0 = y*2; + row1 = y*2+1; + if (row1 >= texture->mipmap[i-1][3]) + row1 = texture->mipmap[i-1][3]-1; + o = texture->bytes + texture->mipmap[i ][0] + 4*((texture->mipmap[i ][3] * z + y ) * texture->mipmap[i ][2]); + i0 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer0 + row0) * texture->mipmap[i-1][2]); + i1 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer0 + row1) * texture->mipmap[i-1][2]); + i2 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer1 + row0) * texture->mipmap[i-1][2]); + i3 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer1 + row1) * texture->mipmap[i-1][2]); + w = texture->mipmap[i][2]; + if (layer1 > layer0) + { + if (texture->mipmap[i-1][2] > 1) + { + // average 3D texture + for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8, i2 += 8, i3 += 8) + { + o[0] = (i0[0] + i0[4] + i1[0] + i1[4] + i2[0] + i2[4] + i3[0] + i3[4] + 4) >> 3; + o[1] = (i0[1] + i0[5] + i1[1] + i1[5] + i2[1] + i2[5] + i3[1] + i3[5] + 4) >> 3; + o[2] = (i0[2] + i0[6] + i1[2] + i1[6] + i2[2] + i2[6] + i3[2] + i3[6] + 4) >> 3; + o[3] = (i0[3] + i0[7] + i1[3] + i1[7] + i2[3] + i2[7] + i3[3] + i3[7] + 4) >> 3; + } + } + else + { + // average 3D mipmap with parent width == 1 + for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8) + { + o[0] = (i0[0] + i1[0] + i2[0] + i3[0] + 2) >> 2; + o[1] = (i0[1] + i1[1] + i2[1] + i3[1] + 2) >> 2; + o[2] = (i0[2] + i1[2] + i2[2] + i3[2] + 2) >> 2; + o[3] = (i0[3] + i1[3] + i2[3] + i3[3] + 2) >> 2; + } + } + } + else + { + if (texture->mipmap[i-1][2] > 1) + { + // average 2D texture (common case) + for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8) + { + o[0] = (i0[0] + i0[4] + i1[0] + i1[4] + 2) >> 2; + o[1] = (i0[1] + i0[5] + i1[1] + i1[5] + 2) >> 2; + o[2] = (i0[2] + i0[6] + i1[2] + i1[6] + 2) >> 2; + o[3] = (i0[3] + i0[7] + i1[3] + i1[7] + 2) >> 2; + } + } + else + { + // 2D texture with parent width == 1 + o[0] = (i0[0] + i1[0] + 1) >> 1; + o[1] = (i0[1] + i1[1] + 1) >> 1; + o[2] = (i0[2] + i1[2] + 1) >> 1; + o[3] = (i0[3] + i1[3] + 1) >> 1; + } + } + } + } + } +} +void DPSOFTRAST_Texture_UpdatePartial(int index, int mip, const unsigned char *pixels, int blockx, int blocky, int blockwidth, int blockheight) +{ + DPSOFTRAST_Texture *texture; + unsigned char *dst; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (texture->binds) + DPSOFTRAST_Flush(); + if (pixels) + { + dst = texture->bytes + (blocky * texture->mipmap[0][2] + blockx) * 4; + while (blockheight > 0) + { + memcpy(dst, pixels, blockwidth * 4); + pixels += blockwidth * 4; + dst += texture->mipmap[0][2] * 4; + blockheight--; + } + } + DPSOFTRAST_Texture_CalculateMipmaps(index); +} +void DPSOFTRAST_Texture_UpdateFull(int index, const unsigned char *pixels) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (texture->binds) + DPSOFTRAST_Flush(); + if (pixels) + memcpy(texture->bytes, pixels, texture->mipmap[0][1]); + DPSOFTRAST_Texture_CalculateMipmaps(index); +} +int DPSOFTRAST_Texture_GetWidth(int index, int mip) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; + return texture->mipmap[mip][2]; +} +int DPSOFTRAST_Texture_GetHeight(int index, int mip) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; + return texture->mipmap[mip][3]; +} +int DPSOFTRAST_Texture_GetDepth(int index, int mip) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; + return texture->mipmap[mip][4]; +} +unsigned char *DPSOFTRAST_Texture_GetPixelPointer(int index, int mip) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; + if (texture->binds) + DPSOFTRAST_Flush(); + return texture->bytes + texture->mipmap[mip][0]; +} +void DPSOFTRAST_Texture_Filter(int index, DPSOFTRAST_TEXTURE_FILTER filter) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (!(texture->flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP) && filter > DPSOFTRAST_TEXTURE_FILTER_LINEAR) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_Filter: requested filter mode requires mipmaps"; + return; + } + if (texture->binds) + DPSOFTRAST_Flush(); + texture->filter = filter; +} + +static void DPSOFTRAST_Draw_FlushThreads(void); + +static void DPSOFTRAST_Draw_SyncCommands(void) +{ + if(dpsoftrast.usethreads) MEMORY_BARRIER; + dpsoftrast.drawcommand = dpsoftrast.commandpool.freecommand; +} + +static void DPSOFTRAST_Draw_FreeCommandPool(int space) +{ + DPSOFTRAST_State_Thread *thread; + int i; + int freecommand = dpsoftrast.commandpool.freecommand; + int usedcommands = dpsoftrast.commandpool.usedcommands; + if (usedcommands <= DPSOFTRAST_DRAW_MAXCOMMANDPOOL-space) + return; + DPSOFTRAST_Draw_SyncCommands(); + for(;;) + { + int waitindex = -1; + int commandoffset; + usedcommands = 0; + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + commandoffset = freecommand - thread->commandoffset; + if (commandoffset < 0) + commandoffset += DPSOFTRAST_DRAW_MAXCOMMANDPOOL; + if (commandoffset > usedcommands) + { + waitindex = i; + usedcommands = commandoffset; + } + } + if (usedcommands <= DPSOFTRAST_DRAW_MAXCOMMANDPOOL-space || waitindex < 0) + break; + thread = &dpsoftrast.threads[waitindex]; + Thread_LockMutex(thread->drawmutex); + if (thread->commandoffset != dpsoftrast.drawcommand) + { + thread->waiting = true; + if (thread->starving) Thread_CondSignal(thread->drawcond); + Thread_CondWait(thread->waitcond, thread->drawmutex); + thread->waiting = false; + } + Thread_UnlockMutex(thread->drawmutex); + } + dpsoftrast.commandpool.usedcommands = usedcommands; +} + +#define DPSOFTRAST_ALIGNCOMMAND(size) \ + ((size) + ((COMMAND_SIZE - ((size)&(COMMAND_SIZE-1))) & (COMMAND_SIZE-1))) +#define DPSOFTRAST_ALLOCATECOMMAND(name) \ + ((DPSOFTRAST_Command_##name *) DPSOFTRAST_AllocateCommand( DPSOFTRAST_OPCODE_##name , DPSOFTRAST_ALIGNCOMMAND(sizeof( DPSOFTRAST_Command_##name )))) + +static void *DPSOFTRAST_AllocateCommand(int opcode, int size) +{ + DPSOFTRAST_Command *command; + int freecommand = dpsoftrast.commandpool.freecommand; + int usedcommands = dpsoftrast.commandpool.usedcommands; + int extra = sizeof(DPSOFTRAST_Command); + if (DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand < size) + extra += DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand; + if (usedcommands > DPSOFTRAST_DRAW_MAXCOMMANDPOOL - (size + extra)) + { + if (dpsoftrast.usethreads) + DPSOFTRAST_Draw_FreeCommandPool(size + extra); + else + DPSOFTRAST_Draw_FlushThreads(); + freecommand = dpsoftrast.commandpool.freecommand; + usedcommands = dpsoftrast.commandpool.usedcommands; + } + if (DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand < size) + { + command = (DPSOFTRAST_Command *) &dpsoftrast.commandpool.commands[freecommand]; + command->opcode = DPSOFTRAST_OPCODE_Reset; + usedcommands += DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand; + freecommand = 0; + } + command = (DPSOFTRAST_Command *) &dpsoftrast.commandpool.commands[freecommand]; + command->opcode = opcode; + command->commandsize = size; + freecommand += size; + if (freecommand >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) + freecommand = 0; + dpsoftrast.commandpool.freecommand = freecommand; + dpsoftrast.commandpool.usedcommands = usedcommands + size; + return command; +} + +static void DPSOFTRAST_UndoCommand(int size) +{ + int freecommand = dpsoftrast.commandpool.freecommand; + int usedcommands = dpsoftrast.commandpool.usedcommands; + freecommand -= size; + if (freecommand < 0) + freecommand += DPSOFTRAST_DRAW_MAXCOMMANDPOOL; + usedcommands -= size; + dpsoftrast.commandpool.freecommand = freecommand; + dpsoftrast.commandpool.usedcommands = usedcommands; +} + +DEFCOMMAND(1, Viewport, int x; int y; int width; int height;) +static void DPSOFTRAST_Interpret_Viewport(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_Viewport *command) +{ + thread->viewport[0] = command->x; + thread->viewport[1] = command->y; + thread->viewport[2] = command->width; + thread->viewport[3] = command->height; + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_Viewport(int x, int y, int width, int height) +{ + DPSOFTRAST_Command_Viewport *command = DPSOFTRAST_ALLOCATECOMMAND(Viewport); + command->x = x; + command->y = y; + command->width = width; + command->height = height; + + dpsoftrast.viewport[0] = x; + dpsoftrast.viewport[1] = y; + dpsoftrast.viewport[2] = width; + dpsoftrast.viewport[3] = height; + DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); +} + +DEFCOMMAND(2, ClearColor, float r; float g; float b; float a;) +static void DPSOFTRAST_Interpret_ClearColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_ClearColor *command) +{ + int i, x1, y1, x2, y2, w, h, x, y; + int miny1, maxy1, miny2, maxy2; + int bandy; + unsigned int *p; + unsigned int c; + DPSOFTRAST_Validate(thread, DPSOFTRAST_VALIDATE_FB); + miny1 = thread->miny1; + maxy1 = thread->maxy1; + miny2 = thread->miny2; + maxy2 = thread->maxy2; + x1 = thread->fb_scissor[0]; + y1 = thread->fb_scissor[1]; + x2 = thread->fb_scissor[0] + thread->fb_scissor[2]; + y2 = thread->fb_scissor[1] + thread->fb_scissor[3]; + if (y1 < miny1) y1 = miny1; + if (y2 > maxy2) y2 = maxy2; + w = x2 - x1; + h = y2 - y1; + if (w < 1 || h < 1) + return; + // FIXME: honor fb_colormask? + c = DPSOFTRAST_BGRA8_FROM_RGBA32F(command->r,command->g,command->b,command->a); + for (i = 0;i < 4;i++) + { + if (!dpsoftrast.fb_colorpixels[i]) + continue; + for (y = y1, bandy = min(y2, maxy1); y < y2; bandy = min(y2, maxy2), y = max(y, miny2)) + for (;y < bandy;y++) + { + p = dpsoftrast.fb_colorpixels[i] + y * dpsoftrast.fb_width; + for (x = x1;x < x2;x++) + p[x] = c; + } + } +} +void DPSOFTRAST_ClearColor(float r, float g, float b, float a) +{ + DPSOFTRAST_Command_ClearColor *command = DPSOFTRAST_ALLOCATECOMMAND(ClearColor); + command->r = r; + command->g = g; + command->b = b; + command->a = a; +} + +DEFCOMMAND(3, ClearDepth, float depth;) +static void DPSOFTRAST_Interpret_ClearDepth(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ClearDepth *command) +{ + int x1, y1, x2, y2, w, h, x, y; + int miny1, maxy1, miny2, maxy2; + int bandy; + unsigned int *p; + unsigned int c; + DPSOFTRAST_Validate(thread, DPSOFTRAST_VALIDATE_FB); + miny1 = thread->miny1; + maxy1 = thread->maxy1; + miny2 = thread->miny2; + maxy2 = thread->maxy2; + x1 = thread->fb_scissor[0]; + y1 = thread->fb_scissor[1]; + x2 = thread->fb_scissor[0] + thread->fb_scissor[2]; + y2 = thread->fb_scissor[1] + thread->fb_scissor[3]; + if (y1 < miny1) y1 = miny1; + if (y2 > maxy2) y2 = maxy2; + w = x2 - x1; + h = y2 - y1; + if (w < 1 || h < 1) + return; + c = DPSOFTRAST_DEPTH32_FROM_DEPTH32F(command->depth); + for (y = y1, bandy = min(y2, maxy1); y < y2; bandy = min(y2, maxy2), y = max(y, miny2)) + for (;y < bandy;y++) + { + p = dpsoftrast.fb_depthpixels + y * dpsoftrast.fb_width; + for (x = x1;x < x2;x++) + p[x] = c; + } +} +void DPSOFTRAST_ClearDepth(float d) +{ + DPSOFTRAST_Command_ClearDepth *command = DPSOFTRAST_ALLOCATECOMMAND(ClearDepth); + command->depth = d; +} + +DEFCOMMAND(4, ColorMask, int r; int g; int b; int a;) +static void DPSOFTRAST_Interpret_ColorMask(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ColorMask *command) +{ + thread->colormask[0] = command->r != 0; + thread->colormask[1] = command->g != 0; + thread->colormask[2] = command->b != 0; + thread->colormask[3] = command->a != 0; + thread->fb_colormask = ((-thread->colormask[0]) & 0x00FF0000) | ((-thread->colormask[1]) & 0x0000FF00) | ((-thread->colormask[2]) & 0x000000FF) | ((-thread->colormask[3]) & 0xFF000000); +} +void DPSOFTRAST_ColorMask(int r, int g, int b, int a) +{ + DPSOFTRAST_Command_ColorMask *command = DPSOFTRAST_ALLOCATECOMMAND(ColorMask); + command->r = r; + command->g = g; + command->b = b; + command->a = a; +} + +DEFCOMMAND(5, DepthTest, int enable;) +static void DPSOFTRAST_Interpret_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthTest *command) +{ + thread->depthtest = command->enable; + thread->validate |= DPSOFTRAST_VALIDATE_DEPTHFUNC; +} +void DPSOFTRAST_DepthTest(int enable) +{ + DPSOFTRAST_Command_DepthTest *command = DPSOFTRAST_ALLOCATECOMMAND(DepthTest); + command->enable = enable; +} + +DEFCOMMAND(6, ScissorTest, int enable;) +static void DPSOFTRAST_Interpret_ScissorTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ScissorTest *command) +{ + thread->scissortest = command->enable; + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_ScissorTest(int enable) +{ + DPSOFTRAST_Command_ScissorTest *command = DPSOFTRAST_ALLOCATECOMMAND(ScissorTest); + command->enable = enable; +} + +DEFCOMMAND(7, Scissor, float x; float y; float width; float height;) +static void DPSOFTRAST_Interpret_Scissor(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Scissor *command) +{ + thread->scissor[0] = command->x; + thread->scissor[1] = command->y; + thread->scissor[2] = command->width; + thread->scissor[3] = command->height; + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_Scissor(float x, float y, float width, float height) +{ + DPSOFTRAST_Command_Scissor *command = DPSOFTRAST_ALLOCATECOMMAND(Scissor); + command->x = x; + command->y = y; + command->width = width; + command->height = height; +} + +DEFCOMMAND(8, BlendFunc, int sfactor; int dfactor;) +static void DPSOFTRAST_Interpret_BlendFunc(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_BlendFunc *command) +{ + thread->blendfunc[0] = command->sfactor; + thread->blendfunc[1] = command->dfactor; + thread->validate |= DPSOFTRAST_VALIDATE_BLENDFUNC; +} +void DPSOFTRAST_BlendFunc(int sfactor, int dfactor) +{ + DPSOFTRAST_Command_BlendFunc *command = DPSOFTRAST_ALLOCATECOMMAND(BlendFunc); + command->sfactor = sfactor; + command->dfactor = dfactor; +} + +DEFCOMMAND(9, BlendSubtract, int enable;) +static void DPSOFTRAST_Interpret_BlendSubtract(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_BlendSubtract *command) +{ + thread->blendsubtract = command->enable; + thread->validate |= DPSOFTRAST_VALIDATE_BLENDFUNC; +} +void DPSOFTRAST_BlendSubtract(int enable) +{ + DPSOFTRAST_Command_BlendSubtract *command = DPSOFTRAST_ALLOCATECOMMAND(BlendSubtract); + command->enable = enable; +} + +DEFCOMMAND(10, DepthMask, int enable;) +static void DPSOFTRAST_Interpret_DepthMask(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthMask *command) +{ + thread->depthmask = command->enable; +} +void DPSOFTRAST_DepthMask(int enable) +{ + DPSOFTRAST_Command_DepthMask *command = DPSOFTRAST_ALLOCATECOMMAND(DepthMask); + command->enable = enable; +} + +DEFCOMMAND(11, DepthFunc, int func;) +static void DPSOFTRAST_Interpret_DepthFunc(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthFunc *command) +{ + thread->depthfunc = command->func; +} +void DPSOFTRAST_DepthFunc(int func) +{ + DPSOFTRAST_Command_DepthFunc *command = DPSOFTRAST_ALLOCATECOMMAND(DepthFunc); + command->func = func; +} + +DEFCOMMAND(12, DepthRange, float nearval; float farval;) +static void DPSOFTRAST_Interpret_DepthRange(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthRange *command) +{ + thread->depthrange[0] = command->nearval; + thread->depthrange[1] = command->farval; +} +void DPSOFTRAST_DepthRange(float nearval, float farval) +{ + DPSOFTRAST_Command_DepthRange *command = DPSOFTRAST_ALLOCATECOMMAND(DepthRange); + command->nearval = nearval; + command->farval = farval; +} + +DEFCOMMAND(13, PolygonOffset, float alongnormal; float intoview;) +static void DPSOFTRAST_Interpret_PolygonOffset(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_PolygonOffset *command) +{ + thread->polygonoffset[0] = command->alongnormal; + thread->polygonoffset[1] = command->intoview; +} +void DPSOFTRAST_PolygonOffset(float alongnormal, float intoview) +{ + DPSOFTRAST_Command_PolygonOffset *command = DPSOFTRAST_ALLOCATECOMMAND(PolygonOffset); + command->alongnormal = alongnormal; + command->intoview = intoview; +} + +DEFCOMMAND(14, CullFace, int mode;) +static void DPSOFTRAST_Interpret_CullFace(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_CullFace *command) +{ + thread->cullface = command->mode; +} +void DPSOFTRAST_CullFace(int mode) +{ + DPSOFTRAST_Command_CullFace *command = DPSOFTRAST_ALLOCATECOMMAND(CullFace); + command->mode = mode; +} + +void DPSOFTRAST_Color4f(float r, float g, float b, float a) +{ + dpsoftrast.color[0] = r; + dpsoftrast.color[1] = g; + dpsoftrast.color[2] = b; + dpsoftrast.color[3] = a; +} + +void DPSOFTRAST_GetPixelsBGRA(int blockx, int blocky, int blockwidth, int blockheight, unsigned char *outpixels) +{ + int outstride = blockwidth * 4; + int instride = dpsoftrast.fb_width * 4; + int bx1 = blockx; + int by1 = blocky; + int bx2 = blockx + blockwidth; + int by2 = blocky + blockheight; + int bw; + int x; + int y; + unsigned char *inpixels; + unsigned char *b; + unsigned char *o; + DPSOFTRAST_Flush(); + if (bx1 < 0) bx1 = 0; + if (by1 < 0) by1 = 0; + if (bx2 > dpsoftrast.fb_width) bx2 = dpsoftrast.fb_width; + if (by2 > dpsoftrast.fb_height) by2 = dpsoftrast.fb_height; + bw = bx2 - bx1; + inpixels = (unsigned char *)dpsoftrast.fb_colorpixels[0]; + if (dpsoftrast.bigendian) + { + for (y = by1;y < by2;y++) + { + b = (unsigned char *)inpixels + (dpsoftrast.fb_height - 1 - y) * instride + 4 * bx1; + o = (unsigned char *)outpixels + (y - by1) * outstride; + for (x = bx1;x < bx2;x++) + { + o[0] = b[3]; + o[1] = b[2]; + o[2] = b[1]; + o[3] = b[0]; + o += 4; + b += 4; + } + } + } + else + { + for (y = by1;y < by2;y++) + { + b = (unsigned char *)inpixels + (dpsoftrast.fb_height - 1 - y) * instride + 4 * bx1; + o = (unsigned char *)outpixels + (y - by1) * outstride; + memcpy(o, b, bw*4); + } + } + +} +void DPSOFTRAST_CopyRectangleToTexture(int index, int mip, int tx, int ty, int sx, int sy, int width, int height) +{ + int tx1 = tx; + int ty1 = ty; + int tx2 = tx + width; + int ty2 = ty + height; + int sx1 = sx; + int sy1 = sy; + int sx2 = sx + width; + int sy2 = sy + height; + int swidth; + int sheight; + int twidth; + int theight; + int sw; + int sh; + int tw; + int th; + int y; + unsigned int *spixels; + unsigned int *tpixels; + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (mip < 0 || mip >= texture->mipmaps) return; + DPSOFTRAST_Flush(); + spixels = dpsoftrast.fb_colorpixels[0]; + swidth = dpsoftrast.fb_width; + sheight = dpsoftrast.fb_height; + tpixels = (unsigned int *)(texture->bytes + texture->mipmap[mip][0]); + twidth = texture->mipmap[mip][2]; + theight = texture->mipmap[mip][3]; + if (tx1 < 0) tx1 = 0; + if (ty1 < 0) ty1 = 0; + if (tx2 > twidth) tx2 = twidth; + if (ty2 > theight) ty2 = theight; + if (sx1 < 0) sx1 = 0; + if (sy1 < 0) sy1 = 0; + if (sx2 > swidth) sx2 = swidth; + if (sy2 > sheight) sy2 = sheight; + tw = tx2 - tx1; + th = ty2 - ty1; + sw = sx2 - sx1; + sh = sy2 - sy1; + if (tw > sw) tw = sw; + if (th > sh) th = sh; + if (tw < 1 || th < 1) + return; + sy1 = sheight - 1 - sy1; + for (y = 0;y < th;y++) + memcpy(tpixels + ((ty1 + y) * twidth + tx1), spixels + ((sy1 - y) * swidth + sx1), tw*4); + if (texture->mipmaps > 1) + DPSOFTRAST_Texture_CalculateMipmaps(index); +} + +DEFCOMMAND(17, SetTexture, int unitnum; DPSOFTRAST_Texture *texture;) +static void DPSOFTRAST_Interpret_SetTexture(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_SetTexture *command) +{ + if (thread->texbound[command->unitnum]) + ATOMIC_DECREMENT(thread->texbound[command->unitnum]->binds); + thread->texbound[command->unitnum] = command->texture; +} +void DPSOFTRAST_SetTexture(int unitnum, int index) +{ + DPSOFTRAST_Command_SetTexture *command; + DPSOFTRAST_Texture *texture; + if (unitnum < 0 || unitnum >= DPSOFTRAST_MAXTEXTUREUNITS) + { + dpsoftrast.errorstring = "DPSOFTRAST_SetTexture: invalid unit number"; + return; + } + texture = DPSOFTRAST_Texture_GetByIndex(index); + if (index && !texture) + { + dpsoftrast.errorstring = "DPSOFTRAST_SetTexture: invalid texture handle"; + return; + } + + command = DPSOFTRAST_ALLOCATECOMMAND(SetTexture); + command->unitnum = unitnum; + command->texture = texture; + + dpsoftrast.texbound[unitnum] = texture; + ATOMIC_ADD(texture->binds, dpsoftrast.numthreads); +} + +void DPSOFTRAST_SetVertexPointer(const float *vertex3f, size_t stride) +{ + dpsoftrast.pointer_vertex3f = vertex3f; + dpsoftrast.stride_vertex = stride; +} +void DPSOFTRAST_SetColorPointer(const float *color4f, size_t stride) +{ + dpsoftrast.pointer_color4f = color4f; + dpsoftrast.pointer_color4ub = NULL; + dpsoftrast.stride_color = stride; +} +void DPSOFTRAST_SetColorPointer4ub(const unsigned char *color4ub, size_t stride) +{ + dpsoftrast.pointer_color4f = NULL; + dpsoftrast.pointer_color4ub = color4ub; + dpsoftrast.stride_color = stride; +} +void DPSOFTRAST_SetTexCoordPointer(int unitnum, int numcomponents, size_t stride, const float *texcoordf) +{ + dpsoftrast.pointer_texcoordf[unitnum] = texcoordf; + dpsoftrast.components_texcoord[unitnum] = numcomponents; + dpsoftrast.stride_texcoord[unitnum] = stride; +} + +DEFCOMMAND(18, SetShader, int mode; int permutation; int exactspecularmath;) +static void DPSOFTRAST_Interpret_SetShader(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_SetShader *command) +{ + thread->shader_mode = command->mode; + thread->shader_permutation = command->permutation; + thread->shader_exactspecularmath = command->exactspecularmath; +} +void DPSOFTRAST_SetShader(int mode, int permutation, int exactspecularmath) +{ + DPSOFTRAST_Command_SetShader *command = DPSOFTRAST_ALLOCATECOMMAND(SetShader); + command->mode = mode; + command->permutation = permutation; + command->exactspecularmath = exactspecularmath; + + dpsoftrast.shader_mode = mode; + dpsoftrast.shader_permutation = permutation; + dpsoftrast.shader_exactspecularmath = exactspecularmath; +} + +DEFCOMMAND(19, Uniform4f, DPSOFTRAST_UNIFORM index; float val[4];) +static void DPSOFTRAST_Interpret_Uniform4f(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Uniform4f *command) +{ + memcpy(&thread->uniform4f[command->index*4], command->val, sizeof(command->val)); +} +void DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM index, float v0, float v1, float v2, float v3) +{ + DPSOFTRAST_Command_Uniform4f *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform4f); + command->index = index; + command->val[0] = v0; + command->val[1] = v1; + command->val[2] = v2; + command->val[3] = v3; + + dpsoftrast.uniform4f[index*4+0] = v0; + dpsoftrast.uniform4f[index*4+1] = v1; + dpsoftrast.uniform4f[index*4+2] = v2; + dpsoftrast.uniform4f[index*4+3] = v3; +} +void DPSOFTRAST_Uniform4fv(DPSOFTRAST_UNIFORM index, const float *v) +{ + DPSOFTRAST_Command_Uniform4f *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform4f); + command->index = index; + memcpy(command->val, v, sizeof(command->val)); + + memcpy(&dpsoftrast.uniform4f[index*4], v, sizeof(float[4])); +} + +DEFCOMMAND(20, UniformMatrix4f, DPSOFTRAST_UNIFORM index; ALIGN(float val[16]);) +static void DPSOFTRAST_Interpret_UniformMatrix4f(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_UniformMatrix4f *command) +{ + memcpy(&thread->uniform4f[command->index*4], command->val, sizeof(command->val)); +} +void DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM uniform, int arraysize, int transpose, const float *v) +{ +#ifdef SSE_POSSIBLE + int i, index; + for (i = 0, index = (int)uniform;i < arraysize;i++, index += 4, v += 16) + { + __m128 m0, m1, m2, m3; + DPSOFTRAST_Command_UniformMatrix4f *command = DPSOFTRAST_ALLOCATECOMMAND(UniformMatrix4f); + command->index = (DPSOFTRAST_UNIFORM)index; + if (((size_t)v)&(ALIGN_SIZE-1)) + { + m0 = _mm_loadu_ps(v); + m1 = _mm_loadu_ps(v+4); + m2 = _mm_loadu_ps(v+8); + m3 = _mm_loadu_ps(v+12); + } + else + { + m0 = _mm_load_ps(v); + m1 = _mm_load_ps(v+4); + m2 = _mm_load_ps(v+8); + m3 = _mm_load_ps(v+12); + } + if (transpose) + { + __m128 t0, t1, t2, t3; + t0 = _mm_unpacklo_ps(m0, m1); + t1 = _mm_unpacklo_ps(m2, m3); + t2 = _mm_unpackhi_ps(m0, m1); + t3 = _mm_unpackhi_ps(m2, m3); + m0 = _mm_movelh_ps(t0, t1); + m1 = _mm_movehl_ps(t1, t0); + m2 = _mm_movelh_ps(t2, t3); + m3 = _mm_movehl_ps(t3, t2); + } + _mm_store_ps(command->val, m0); + _mm_store_ps(command->val+4, m1); + _mm_store_ps(command->val+8, m2); + _mm_store_ps(command->val+12, m3); + _mm_store_ps(&dpsoftrast.uniform4f[index*4+0], m0); + _mm_store_ps(&dpsoftrast.uniform4f[index*4+4], m1); + _mm_store_ps(&dpsoftrast.uniform4f[index*4+8], m2); + _mm_store_ps(&dpsoftrast.uniform4f[index*4+12], m3); + } +#endif +} + +DEFCOMMAND(21, Uniform1i, DPSOFTRAST_UNIFORM index; int val;) +static void DPSOFTRAST_Interpret_Uniform1i(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Uniform1i *command) +{ + thread->uniform1i[command->index] = command->val; +} +void DPSOFTRAST_Uniform1i(DPSOFTRAST_UNIFORM index, int i0) +{ + DPSOFTRAST_Command_Uniform1i *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform1i); + command->index = index; + command->val = i0; + + dpsoftrast.uniform1i[command->index] = i0; +} + +DEFCOMMAND(24, ClipPlane, float clipplane[4];) +static void DPSOFTRAST_Interpret_ClipPlane(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ClipPlane *command) +{ + memcpy(thread->clipplane, command->clipplane, 4*sizeof(float)); + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_ClipPlane(float x, float y, float z, float w) +{ + DPSOFTRAST_Command_ClipPlane *command = DPSOFTRAST_ALLOCATECOMMAND(ClipPlane); + command->clipplane[0] = x; + command->clipplane[1] = y; + command->clipplane[2] = z; + command->clipplane[3] = w; +} + +#ifdef SSE_POSSIBLE +static void DPSOFTRAST_Load4fTo4f(float *dst, const unsigned char *src, int size, int stride) +{ + float *end = dst + size*4; + if ((((size_t)src)|stride)&(ALIGN_SIZE - 1)) // check for alignment + { + while (dst < end) + { + _mm_store_ps(dst, _mm_loadu_ps((const float *)src)); + dst += 4; + src += stride; + } + } + else + { + while (dst < end) + { + _mm_store_ps(dst, _mm_load_ps((const float *)src)); + dst += 4; + src += stride; + } + } +} + +static void DPSOFTRAST_Load3fTo4f(float *dst, const unsigned char *src, int size, int stride) +{ + float *end = dst + size*4; + if (stride == sizeof(float[3])) + { + float *end4 = dst + (size&~3)*4; + if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment + { + while (dst < end4) + { + __m128 v1 = _mm_loadu_ps((const float *)src), v2 = _mm_loadu_ps((const float *)src + 4), v3 = _mm_loadu_ps((const float *)src + 8), dv; + dv = _mm_shuffle_ps(v1, v1, _MM_SHUFFLE(2, 1, 0, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_shuffle_ps(v1, v2, _MM_SHUFFLE(1, 0, 3, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 4, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_shuffle_ps(v2, v3, _MM_SHUFFLE(0, 0, 3, 2)); + dv = _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(2, 1, 0, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 8, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_move_ss(v3, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 12, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dst += 16; + src += 4*sizeof(float[3]); + } + } + else + { + while (dst < end4) + { + __m128 v1 = _mm_load_ps((const float *)src), v2 = _mm_load_ps((const float *)src + 4), v3 = _mm_load_ps((const float *)src + 8), dv; + dv = _mm_shuffle_ps(v1, v1, _MM_SHUFFLE(2, 1, 0, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_shuffle_ps(v1, v2, _MM_SHUFFLE(1, 0, 3, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 4, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_shuffle_ps(v2, v3, _MM_SHUFFLE(0, 0, 3, 2)); + dv = _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(2, 1, 0, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 8, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_move_ss(v3, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 12, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dst += 16; + src += 4*sizeof(float[3]); + } + } + } + if ((((size_t)src)|stride)&(ALIGN_SIZE - 1)) + { + while (dst < end) + { + __m128 v = _mm_loadu_ps((const float *)src); + v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3)); + v = _mm_move_ss(v, _mm_set_ss(1.0f)); + v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ps(dst, v); + dst += 4; + src += stride; + } + } + else + { + while (dst < end) + { + __m128 v = _mm_load_ps((const float *)src); + v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3)); + v = _mm_move_ss(v, _mm_set_ss(1.0f)); + v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ps(dst, v); + dst += 4; + src += stride; + } + } +} + +static void DPSOFTRAST_Load2fTo4f(float *dst, const unsigned char *src, int size, int stride) +{ + float *end = dst + size*4; + __m128 v2 = _mm_setr_ps(0.0f, 0.0f, 0.0f, 1.0f); + if (stride == sizeof(float[2])) + { + float *end2 = dst + (size&~1)*4; + if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment + { + while (dst < end2) + { + __m128 v = _mm_loadu_ps((const float *)src); + _mm_store_ps(dst, _mm_shuffle_ps(v, v2, _MM_SHUFFLE(3, 2, 1, 0))); + _mm_store_ps(dst + 4, _mm_movehl_ps(v2, v)); + dst += 8; + src += 2*sizeof(float[2]); + } + } + else + { + while (dst < end2) + { + __m128 v = _mm_load_ps((const float *)src); + _mm_store_ps(dst, _mm_shuffle_ps(v, v2, _MM_SHUFFLE(3, 2, 1, 0))); + _mm_store_ps(dst + 4, _mm_movehl_ps(v2, v)); + dst += 8; + src += 2*sizeof(float[2]); + } + } + } + while (dst < end) + { + _mm_store_ps(dst, _mm_loadl_pi(v2, (__m64 *)src)); + dst += 4; + src += stride; + } +} + +static void DPSOFTRAST_Load4bTo4f(float *dst, const unsigned char *src, int size, int stride) +{ + float *end = dst + size*4; + __m128 scale = _mm_set1_ps(1.0f/255.0f); + if (stride == sizeof(unsigned char[4])) + { + float *end4 = dst + (size&~3)*4; + if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment + { + while (dst < end4) + { + __m128i v = _mm_loadu_si128((const __m128i *)src), v1 = _mm_unpacklo_epi8(v, _mm_setzero_si128()), v2 = _mm_unpackhi_epi8(v, _mm_setzero_si128()); + _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v1, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 4, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v1, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 8, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v2, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 12, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v2, _mm_setzero_si128())), scale)); + dst += 16; + src += 4*sizeof(unsigned char[4]); + } + } + else + { + while (dst < end4) + { + __m128i v = _mm_load_si128((const __m128i *)src), v1 = _mm_unpacklo_epi8(v, _mm_setzero_si128()), v2 = _mm_unpackhi_epi8(v, _mm_setzero_si128()); + _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v1, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 4, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v1, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 8, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v2, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 12, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v2, _mm_setzero_si128())), scale)); + dst += 16; + src += 4*sizeof(unsigned char[4]); + } + } + } + while (dst < end) + { + __m128i v = _mm_cvtsi32_si128(*(const int *)src); + _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(_mm_unpacklo_epi8(v, _mm_setzero_si128()), _mm_setzero_si128())), scale)); + dst += 4; + src += stride; + } +} + +static void DPSOFTRAST_Fill4f(float *dst, const float *src, int size) +{ + float *end = dst + 4*size; + __m128 v = _mm_loadu_ps(src); + while (dst < end) + { + _mm_store_ps(dst, v); + dst += 4; + } +} +#endif + +void DPSOFTRAST_Vertex_Transform(float *out4f, const float *in4f, int numitems, const float *inmatrix16f) +{ +#ifdef SSE_POSSIBLE + static const float identitymatrix[4][4] = {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}; + __m128 m0, m1, m2, m3; + float *end; + if (!memcmp(identitymatrix, inmatrix16f, sizeof(float[16]))) + { + // fast case for identity matrix + if (out4f != in4f) memcpy(out4f, in4f, numitems * sizeof(float[4])); + return; + } + end = out4f + numitems*4; + m0 = _mm_loadu_ps(inmatrix16f); + m1 = _mm_loadu_ps(inmatrix16f + 4); + m2 = _mm_loadu_ps(inmatrix16f + 8); + m3 = _mm_loadu_ps(inmatrix16f + 12); + if (((size_t)in4f)&(ALIGN_SIZE-1)) // check alignment + { + while (out4f < end) + { + __m128 v = _mm_loadu_ps(in4f); + _mm_store_ps(out4f, + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)), m0), + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 1, 1, 1)), m1), + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 2, 2, 2)), m2), + _mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 3, 3, 3)), m3))))); + out4f += 4; + in4f += 4; + } + } + else + { + while (out4f < end) + { + __m128 v = _mm_load_ps(in4f); + _mm_store_ps(out4f, + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)), m0), + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 1, 1, 1)), m1), + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 2, 2, 2)), m2), + _mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 3, 3, 3)), m3))))); + out4f += 4; + in4f += 4; + } + } +#endif +} + +void DPSOFTRAST_Vertex_Copy(float *out4f, const float *in4f, int numitems) +{ + memcpy(out4f, in4f, numitems * sizeof(float[4])); +} + +#ifdef SSE_POSSIBLE +#define DPSOFTRAST_PROJECTVERTEX(out, in, viewportcenter, viewportscale) \ +{ \ + __m128 p = (in), w = _mm_shuffle_ps(p, p, _MM_SHUFFLE(3, 3, 3, 3)); \ + p = _mm_move_ss(_mm_shuffle_ps(p, p, _MM_SHUFFLE(2, 1, 0, 3)), _mm_set_ss(1.0f)); \ + p = _mm_add_ps(viewportcenter, _mm_div_ps(_mm_mul_ps(viewportscale, p), w)); \ + out = _mm_shuffle_ps(p, p, _MM_SHUFFLE(0, 3, 2, 1)); \ +} + +#define DPSOFTRAST_PROJECTY(out, in, viewportcenter, viewportscale) \ +{ \ + __m128 p = (in), w = _mm_shuffle_ps(p, p, _MM_SHUFFLE(3, 3, 3, 3)); \ + p = _mm_move_ss(_mm_shuffle_ps(p, p, _MM_SHUFFLE(2, 1, 0, 3)), _mm_set_ss(1.0f)); \ + p = _mm_add_ps(viewportcenter, _mm_div_ps(_mm_mul_ps(viewportscale, p), w)); \ + out = _mm_shuffle_ps(p, p, _MM_SHUFFLE(0, 3, 2, 1)); \ +} + +#define DPSOFTRAST_TRANSFORMVERTEX(out, in, m0, m1, m2, m3) \ +{ \ + __m128 p = (in); \ + out = _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(0, 0, 0, 0)), m0), \ + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(1, 1, 1, 1)), m1), \ + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(2, 2, 2, 2)), m2), \ + _mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(3, 3, 3, 3)), m3)))); \ +} + +static int DPSOFTRAST_Vertex_BoundY(int *starty, int *endy, const float *minposf, const float *maxposf, const float *inmatrix16f) +{ + int clipmask = 0xFF; + __m128 viewportcenter = _mm_load_ps(dpsoftrast.fb_viewportcenter), viewportscale = _mm_load_ps(dpsoftrast.fb_viewportscale); + __m128 bb[8], clipdist[8], minproj = _mm_set_ss(2.0f), maxproj = _mm_set_ss(-2.0f); + __m128 m0 = _mm_loadu_ps(inmatrix16f), m1 = _mm_loadu_ps(inmatrix16f + 4), m2 = _mm_loadu_ps(inmatrix16f + 8), m3 = _mm_loadu_ps(inmatrix16f + 12); + __m128 minpos = _mm_load_ps(minposf), maxpos = _mm_load_ps(maxposf); + m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(3, 2, 0, 1)); + m1 = _mm_shuffle_ps(m1, m1, _MM_SHUFFLE(3, 2, 0, 1)); + m2 = _mm_shuffle_ps(m2, m2, _MM_SHUFFLE(3, 2, 0, 1)); + m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(3, 2, 0, 1)); + #define BBFRONT(k, pos) \ + { \ + DPSOFTRAST_TRANSFORMVERTEX(bb[k], pos, m0, m1, m2, m3); \ + clipdist[k] = _mm_add_ss(_mm_shuffle_ps(bb[k], bb[k], _MM_SHUFFLE(2, 2, 2, 2)), _mm_shuffle_ps(bb[k], bb[k], _MM_SHUFFLE(3, 3, 3, 3))); \ + if (_mm_ucomige_ss(clipdist[k], _mm_setzero_ps())) \ + { \ + __m128 proj; \ + clipmask &= ~(1<= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; + DPSOFTRAST_Vertex_Transform(data, data, dpsoftrast.numvertices, inmatrix16f); + return data; +} + +#if 0 +static float *DPSOFTRAST_Array_Project(int outarray, int inarray) +{ +#ifdef SSE_POSSIBLE + float *data = inarray >= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; + dpsoftrast.drawclipped = DPSOFTRAST_Vertex_Project(data, dpsoftrast.screencoord4f, &dpsoftrast.drawstarty, &dpsoftrast.drawendy, data, dpsoftrast.numvertices); + return data; +#else + return NULL; +#endif +} +#endif + +static float *DPSOFTRAST_Array_TransformProject(int outarray, int inarray, const float *inmatrix16f) +{ +#ifdef SSE_POSSIBLE + float *data = inarray >= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; + dpsoftrast.drawclipped = DPSOFTRAST_Vertex_TransformProject(data, dpsoftrast.screencoord4f, &dpsoftrast.drawstarty, &dpsoftrast.drawendy, data, dpsoftrast.numvertices, inmatrix16f); + return data; +#else + return NULL; +#endif +} + +void DPSOFTRAST_Draw_Span_Begin(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *zf) +{ + int x; + int startx = span->startx; + int endx = span->endx; + float wslope = triangle->w[0]; + float w = triangle->w[2] + span->x*wslope + span->y*triangle->w[1]; + float endz = 1.0f / (w + wslope * startx); + if (triangle->w[0] == 0) + { + // LordHavoc: fast flat polygons (HUD/menu) + for (x = startx;x < endx;x++) + zf[x] = endz; + return; + } + for (x = startx;x < endx;) + { + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + float z = endz, dz; + if (nextsub >= endx) nextsub = endsub = endx-1; + endz = 1.0f / (w + wslope * nextsub); + dz = x < nextsub ? (endz - z) / (nextsub - x) : 0.0f; + for (; x <= endsub; x++, z += dz) + zf[x] = z; + } +} + +void DPSOFTRAST_Draw_Span_FinishBGRA8(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, const unsigned char* RESTRICT in4ub) +{ +#ifdef SSE_POSSIBLE + int x; + int startx = span->startx; + int endx = span->endx; + int maskx; + int subx; + const unsigned int * RESTRICT ini = (const unsigned int *)in4ub; + unsigned char * RESTRICT pixelmask = span->pixelmask; + unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0]; + unsigned int * RESTRICT pixeli = (unsigned int *)dpsoftrast.fb_colorpixels[0]; + if (!pixel) + return; + pixel += (span->y * dpsoftrast.fb_width + span->x) * 4; + pixeli += span->y * dpsoftrast.fb_width + span->x; + // handle alphatest now (this affects depth writes too) + if (thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) + for (x = startx;x < endx;x++) + if (in4ub[x*4+3] < 128) + pixelmask[x] = false; + // LordHavoc: clear pixelmask for some pixels in alphablend cases, this + // helps sprites, text and hud artwork + switch(thread->fb_blendmode) + { + case DPSOFTRAST_BLENDMODE_ALPHA: + case DPSOFTRAST_BLENDMODE_ADDALPHA: + case DPSOFTRAST_BLENDMODE_SUBALPHA: + maskx = startx; + for (x = startx;x < endx;x++) + { + if (in4ub[x*4+3] >= 1) + { + startx = x; + for (;;) + { + while (++x < endx && in4ub[x*4+3] >= 1) ; + maskx = x; + if (x >= endx) break; + ++x; + while (++x < endx && in4ub[x*4+3] < 1) pixelmask[x] = false; + if (x >= endx) break; + } + break; + } + } + endx = maskx; + break; + case DPSOFTRAST_BLENDMODE_OPAQUE: + case DPSOFTRAST_BLENDMODE_ADD: + case DPSOFTRAST_BLENDMODE_INVMOD: + case DPSOFTRAST_BLENDMODE_MUL: + case DPSOFTRAST_BLENDMODE_MUL2: + case DPSOFTRAST_BLENDMODE_PSEUDOALPHA: + case DPSOFTRAST_BLENDMODE_INVADD: + break; + } + // put some special values at the end of the mask to ensure the loops end + pixelmask[endx] = 1; + pixelmask[endx+1] = 0; + // LordHavoc: use a double loop to identify subspans, this helps the + // optimized copy/blend loops to perform at their best, most triangles + // have only one run of pixels, and do the search using wide reads... + x = startx; + while (x < endx) + { + // if this pixel is masked off, it's probably not alone... + if (!pixelmask[x]) + { + x++; +#if 1 + if (x + 8 < endx) + { + // the 4-item search must be aligned or else it stalls badly + if ((x & 3) && !pixelmask[x]) + { + if(pixelmask[x]) goto endmasked; + x++; + if (x & 3) + { + if(pixelmask[x]) goto endmasked; + x++; + if (x & 3) + { + if(pixelmask[x]) goto endmasked; + x++; + } + } + } + while (*(unsigned int *)&pixelmask[x] == 0x00000000) + x += 4; + } +#endif + for (;!pixelmask[x];x++) + ; + // rather than continue the loop, just check the end variable + if (x >= endx) + break; + } + endmasked: + // find length of subspan + subx = x + 1; +#if 1 + if (subx + 8 < endx) + { + if (subx & 3) + { + if(!pixelmask[subx]) goto endunmasked; + subx++; + if (subx & 3) + { + if(!pixelmask[subx]) goto endunmasked; + subx++; + if (subx & 3) + { + if(!pixelmask[subx]) goto endunmasked; + subx++; + } + } + } + while (*(unsigned int *)&pixelmask[subx] == 0x01010101) + subx += 4; + } +#endif + for (;pixelmask[subx];subx++) + ; + // the checks can overshoot, so make sure to clip it... + if (subx > endx) + subx = endx; + endunmasked: + // now that we know the subspan length... process! + switch(thread->fb_blendmode) + { + case DPSOFTRAST_BLENDMODE_OPAQUE: +#if 0 + if (subx - x >= 16) + { + memcpy(pixeli + x, ini + x, (subx - x) * sizeof(pixeli[x])); + x = subx; + } + else +#elif 1 + while (x + 16 <= subx) + { + _mm_storeu_si128((__m128i *)&pixeli[x], _mm_loadu_si128((const __m128i *)&ini[x])); + _mm_storeu_si128((__m128i *)&pixeli[x+4], _mm_loadu_si128((const __m128i *)&ini[x+4])); + _mm_storeu_si128((__m128i *)&pixeli[x+8], _mm_loadu_si128((const __m128i *)&ini[x+8])); + _mm_storeu_si128((__m128i *)&pixeli[x+12], _mm_loadu_si128((const __m128i *)&ini[x+12])); + x += 16; + } +#endif + { + while (x + 4 <= subx) + { + _mm_storeu_si128((__m128i *)&pixeli[x], _mm_loadu_si128((const __m128i *)&ini[x])); + x += 4; + } + if (x + 2 <= subx) + { + pixeli[x] = ini[x]; + pixeli[x+1] = ini[x+1]; + x += 2; + } + if (x < subx) + { + pixeli[x] = ini[x]; + x++; + } + } + break; + case DPSOFTRAST_BLENDMODE_ALPHA: + #define FINISHBLEND(blend2, blend1) \ + for (;x + 1 < subx;x += 2) \ + { \ + __m128i src, dst; \ + src = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ini[x]), _mm_setzero_si128()); \ + dst = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&pixeli[x]), _mm_setzero_si128()); \ + blend2; \ + _mm_storel_epi64((__m128i *)&pixeli[x], _mm_packus_epi16(dst, dst)); \ + } \ + if (x < subx) \ + { \ + __m128i src, dst; \ + src = _mm_unpacklo_epi8(_mm_cvtsi32_si128(ini[x]), _mm_setzero_si128()); \ + dst = _mm_unpacklo_epi8(_mm_cvtsi32_si128(pixeli[x]), _mm_setzero_si128()); \ + blend1; \ + pixeli[x] = _mm_cvtsi128_si32(_mm_packus_epi16(dst, dst)); \ + x++; \ + } + FINISHBLEND({ + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(src, dst), 4), _mm_slli_epi16(blend, 4))); + }, { + __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(src, dst), 4), _mm_slli_epi16(blend, 4))); + }); + break; + case DPSOFTRAST_BLENDMODE_ADDALPHA: + FINISHBLEND({ + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); + }, { + __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); + }); + break; + case DPSOFTRAST_BLENDMODE_ADD: + FINISHBLEND({ dst = _mm_add_epi16(src, dst); }, { dst = _mm_add_epi16(src, dst); }); + break; + case DPSOFTRAST_BLENDMODE_INVMOD: + FINISHBLEND({ + dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, src), 8)); + }, { + dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, src), 8)); + }); + break; + case DPSOFTRAST_BLENDMODE_MUL: + FINISHBLEND({ dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 8); }, { dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 8); }); + break; + case DPSOFTRAST_BLENDMODE_MUL2: + FINISHBLEND({ dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 7); }, { dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 7); }); + break; + case DPSOFTRAST_BLENDMODE_SUBALPHA: + FINISHBLEND({ + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); + }, { + __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); + }); + break; + case DPSOFTRAST_BLENDMODE_PSEUDOALPHA: + FINISHBLEND({ + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(src, _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, blend), 8))); + }, { + __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(src, _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, blend), 8))); + }); + break; + case DPSOFTRAST_BLENDMODE_INVADD: + FINISHBLEND({ + dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(_mm_set1_epi16(255), dst), 4), _mm_slli_epi16(src, 4))); + }, { + dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(_mm_set1_epi16(255), dst), 4), _mm_slli_epi16(src, 4))); + }); + break; + } + } +#endif +} + +static void DPSOFTRAST_Texture2DBGRA8(DPSOFTRAST_Texture *texture, int mip, float x, float y, unsigned char c[4]) + // warning: this is SLOW, only use if the optimized per-span functions won't do +{ + const unsigned char * RESTRICT pixelbase; + const unsigned char * RESTRICT pixel[4]; + int width = texture->mipmap[mip][2], height = texture->mipmap[mip][3]; + int wrapmask[2] = { width-1, height-1 }; + pixelbase = (unsigned char *)texture->bytes + texture->mipmap[mip][0]; + if(texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR) + { + unsigned int tc[2] = { x * (width<<12) - 2048, y * (height<<12) - 2048}; + unsigned int frac[2] = { tc[0]&0xFFF, tc[1]&0xFFF }; + unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; + unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; + int tci[2] = { tc[0]>>12, tc[1]>>12 }; + int tci1[2] = { tci[0] + 1, tci[1] + 1 }; + if (texture->flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + tci[0] = tci[0] >= 0 ? (tci[0] <= wrapmask[0] ? tci[0] : wrapmask[0]) : 0; + tci[1] = tci[1] >= 0 ? (tci[1] <= wrapmask[1] ? tci[1] : wrapmask[1]) : 0; + tci1[0] = tci1[0] >= 0 ? (tci1[0] <= wrapmask[0] ? tci1[0] : wrapmask[0]) : 0; + tci1[1] = tci1[1] >= 0 ? (tci1[1] <= wrapmask[1] ? tci1[1] : wrapmask[1]) : 0; + } + else + { + tci[0] &= wrapmask[0]; + tci[1] &= wrapmask[1]; + tci1[0] &= wrapmask[0]; + tci1[1] &= wrapmask[1]; + } + pixel[0] = pixelbase + 4 * (tci[1]*width+tci[0]); + pixel[1] = pixelbase + 4 * (tci[1]*width+tci1[0]); + pixel[2] = pixelbase + 4 * (tci1[1]*width+tci[0]); + pixel[3] = pixelbase + 4 * (tci1[1]*width+tci1[0]); + c[0] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3])>>24; + c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3])>>24; + c[2] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3])>>24; + c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3])>>24; + } + else + { + int tci[2] = { x * width, y * height }; + if (texture->flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + tci[0] = tci[0] >= 0 ? (tci[0] <= wrapmask[0] ? tci[0] : wrapmask[0]) : 0; + tci[1] = tci[1] >= 0 ? (tci[1] <= wrapmask[1] ? tci[1] : wrapmask[1]) : 0; + } + else + { + tci[0] &= wrapmask[0]; + tci[1] &= wrapmask[1]; + } + pixel[0] = pixelbase + 4 * (tci[1]*width+tci[0]); + c[0] = pixel[0][0]; + c[1] = pixel[0][1]; + c[2] = pixel[0][2]; + c[3] = pixel[0][3]; + } +} + +void DPSOFTRAST_Draw_Span_Texture2DVarying(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float * RESTRICT out4f, int texunitindex, int arrayindex, const float * RESTRICT zf) +{ + int x; + int startx = span->startx; + int endx = span->endx; + int flags; + float c[4]; + float data[4]; + float slope[4]; + float tc[2], endtc[2]; + float tcscale[2]; + unsigned int tci[2]; + unsigned int tci1[2]; + unsigned int tcimin[2]; + unsigned int tcimax[2]; + int tciwrapmask[2]; + int tciwidth; + int filter; + int mip; + const unsigned char * RESTRICT pixelbase; + const unsigned char * RESTRICT pixel[4]; + DPSOFTRAST_Texture *texture = thread->texbound[texunitindex]; + // if no texture is bound, just fill it with white + if (!texture) + { + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = 1.0f; + out4f[x*4+1] = 1.0f; + out4f[x*4+2] = 1.0f; + out4f[x*4+3] = 1.0f; + } + return; + } + mip = triangle->mip[texunitindex]; + pixelbase = (unsigned char *)texture->bytes + texture->mipmap[mip][0]; + // if this mipmap of the texture is 1 pixel, just fill it with that color + if (texture->mipmap[mip][1] == 4) + { + c[0] = texture->bytes[2] * (1.0f/255.0f); + c[1] = texture->bytes[1] * (1.0f/255.0f); + c[2] = texture->bytes[0] * (1.0f/255.0f); + c[3] = texture->bytes[3] * (1.0f/255.0f); + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + return; + } + filter = texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR; + DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); + flags = texture->flags; + tcscale[0] = texture->mipmap[mip][2]; + tcscale[1] = texture->mipmap[mip][3]; + tciwidth = texture->mipmap[mip][2]; + tcimin[0] = 0; + tcimin[1] = 0; + tcimax[0] = texture->mipmap[mip][2]-1; + tcimax[1] = texture->mipmap[mip][3]-1; + tciwrapmask[0] = texture->mipmap[mip][2]-1; + tciwrapmask[1] = texture->mipmap[mip][3]-1; + endtc[0] = (data[0] + slope[0]*startx) * zf[startx] * tcscale[0]; + endtc[1] = (data[1] + slope[1]*startx) * zf[startx] * tcscale[1]; + if (filter) + { + endtc[0] -= 0.5f; + endtc[1] -= 0.5f; + } + for (x = startx;x < endx;) + { + unsigned int subtc[2]; + unsigned int substep[2]; + float subscale = 4096.0f/DPSOFTRAST_DRAW_MAXSUBSPAN; + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + if (nextsub >= endx) + { + nextsub = endsub = endx-1; + if (x < nextsub) subscale = 4096.0f / (nextsub - x); + } + tc[0] = endtc[0]; + tc[1] = endtc[1]; + endtc[0] = (data[0] + slope[0]*nextsub) * zf[nextsub] * tcscale[0]; + endtc[1] = (data[1] + slope[1]*nextsub) * zf[nextsub] * tcscale[1]; + if (filter) + { + endtc[0] -= 0.5f; + endtc[1] -= 0.5f; + } + substep[0] = (endtc[0] - tc[0]) * subscale; + substep[1] = (endtc[1] - tc[1]) * subscale; + subtc[0] = tc[0] * (1<<12); + subtc[1] = tc[1] * (1<<12); + if (filter) + { + if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) + { + unsigned int frac[2] = { subtc[0]&0xFFF, subtc[1]&0xFFF }; + unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; + unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; + tci[0] = subtc[0]>>12; + tci[1] = subtc[1]>>12; + tci1[0] = tci[0] + 1; + tci1[1] = tci[1] + 1; + tci[0] = tci[0] >= tcimin[0] ? (tci[0] <= tcimax[0] ? tci[0] : tcimax[0]) : tcimin[0]; + tci[1] = tci[1] >= tcimin[1] ? (tci[1] <= tcimax[1] ? tci[1] : tcimax[1]) : tcimin[1]; + tci1[0] = tci1[0] >= tcimin[0] ? (tci1[0] <= tcimax[0] ? tci1[0] : tcimax[0]) : tcimin[0]; + tci1[1] = tci1[1] >= tcimin[1] ? (tci1[1] <= tcimax[1] ? tci1[1] : tcimax[1]) : tcimin[1]; + pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); + pixel[1] = pixelbase + 4 * (tci[1]*tciwidth+tci1[0]); + pixel[2] = pixelbase + 4 * (tci1[1]*tciwidth+tci[0]); + pixel[3] = pixelbase + 4 * (tci1[1]*tciwidth+tci1[0]); + c[0] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3]) * (1.0f / 0xFF000000); + c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3]) * (1.0f / 0xFF000000); + c[2] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3]) * (1.0f / 0xFF000000); + c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3]) * (1.0f / 0xFF000000); + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + } + else + { + for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) + { + unsigned int frac[2] = { subtc[0]&0xFFF, subtc[1]&0xFFF }; + unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; + unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; + tci[0] = subtc[0]>>12; + tci[1] = subtc[1]>>12; + tci1[0] = tci[0] + 1; + tci1[1] = tci[1] + 1; + tci[0] &= tciwrapmask[0]; + tci[1] &= tciwrapmask[1]; + tci1[0] &= tciwrapmask[0]; + tci1[1] &= tciwrapmask[1]; + pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); + pixel[1] = pixelbase + 4 * (tci[1]*tciwidth+tci1[0]); + pixel[2] = pixelbase + 4 * (tci1[1]*tciwidth+tci[0]); + pixel[3] = pixelbase + 4 * (tci1[1]*tciwidth+tci1[0]); + c[0] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3]) * (1.0f / 0xFF000000); + c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3]) * (1.0f / 0xFF000000); + c[2] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3]) * (1.0f / 0xFF000000); + c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3]) * (1.0f / 0xFF000000); + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + } + } + else if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) + { + tci[0] = subtc[0]>>12; + tci[1] = subtc[1]>>12; + tci[0] = tci[0] >= tcimin[0] ? (tci[0] <= tcimax[0] ? tci[0] : tcimax[0]) : tcimin[0]; + tci[1] = tci[1] >= tcimin[1] ? (tci[1] <= tcimax[1] ? tci[1] : tcimax[1]) : tcimin[1]; + pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); + c[0] = pixel[0][2] * (1.0f / 255.0f); + c[1] = pixel[0][1] * (1.0f / 255.0f); + c[2] = pixel[0][0] * (1.0f / 255.0f); + c[3] = pixel[0][3] * (1.0f / 255.0f); + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + } + else + { + for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) + { + tci[0] = subtc[0]>>12; + tci[1] = subtc[1]>>12; + tci[0] &= tciwrapmask[0]; + tci[1] &= tciwrapmask[1]; + pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); + c[0] = pixel[0][2] * (1.0f / 255.0f); + c[1] = pixel[0][1] * (1.0f / 255.0f); + c[2] = pixel[0][0] * (1.0f / 255.0f); + c[3] = pixel[0][3] * (1.0f / 255.0f); + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + } + } +} + +void DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char * RESTRICT out4ub, int texunitindex, int arrayindex, const float * RESTRICT zf) +{ +#ifdef SSE_POSSIBLE + int x; + int startx = span->startx; + int endx = span->endx; + int flags; + __m128 data, slope, tcscale; + __m128i tcsize, tcmask, tcoffset, tcmax; + __m128 tc, endtc; + __m128i subtc, substep, endsubtc; + int filter; + int mip; + int affine; // LordHavoc: optimized affine texturing case + unsigned int * RESTRICT outi = (unsigned int *)out4ub; + const unsigned char * RESTRICT pixelbase; + DPSOFTRAST_Texture *texture = thread->texbound[texunitindex]; + // if no texture is bound, just fill it with white + if (!texture) + { + memset(out4ub + startx*4, 255, (span->endx - span->startx)*4); + return; + } + mip = triangle->mip[texunitindex]; + pixelbase = (const unsigned char *)texture->bytes + texture->mipmap[mip][0]; + // if this mipmap of the texture is 1 pixel, just fill it with that color + if (texture->mipmap[mip][1] == 4) + { + unsigned int k = *((const unsigned int *)pixelbase); + for (x = startx;x < endx;x++) + outi[x] = k; + return; + } + affine = zf[startx] == zf[endx-1]; + filter = texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR; + DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); + flags = texture->flags; + tcsize = _mm_shuffle_epi32(_mm_loadu_si128((const __m128i *)&texture->mipmap[mip][0]), _MM_SHUFFLE(3, 2, 3, 2)); + tcmask = _mm_sub_epi32(tcsize, _mm_set1_epi32(1)); + tcscale = _mm_cvtepi32_ps(tcsize); + data = _mm_mul_ps(_mm_movelh_ps(data, data), tcscale); + slope = _mm_mul_ps(_mm_movelh_ps(slope, slope), tcscale); + endtc = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); + if (filter) + endtc = _mm_sub_ps(endtc, _mm_set1_ps(0.5f)); + endsubtc = _mm_cvtps_epi32(_mm_mul_ps(endtc, _mm_set1_ps(65536.0f))); + tcoffset = _mm_add_epi32(_mm_slli_epi32(_mm_shuffle_epi32(tcsize, _MM_SHUFFLE(0, 0, 0, 0)), 18), _mm_set1_epi32(4)); + tcmax = _mm_packs_epi32(tcmask, tcmask); + for (x = startx;x < endx;) + { + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + __m128 subscale = _mm_set1_ps(65536.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); + if (nextsub >= endx || affine) + { + nextsub = endsub = endx-1; + if (x < nextsub) subscale = _mm_set1_ps(65536.0f / (nextsub - x)); + } + tc = endtc; + subtc = endsubtc; + endtc = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); + if (filter) + endtc = _mm_sub_ps(endtc, _mm_set1_ps(0.5f)); + substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endtc, tc), subscale)); + endsubtc = _mm_cvtps_epi32(_mm_mul_ps(endtc, _mm_set1_ps(65536.0f))); + subtc = _mm_unpacklo_epi64(subtc, _mm_add_epi32(subtc, substep)); + substep = _mm_slli_epi32(substep, 1); + if (filter) + { + __m128i tcrange = _mm_srai_epi32(_mm_unpacklo_epi64(subtc, _mm_add_epi32(endsubtc, substep)), 16); + if (_mm_movemask_epi8(_mm_andnot_si128(_mm_cmplt_epi32(tcrange, _mm_setzero_si128()), _mm_cmplt_epi32(tcrange, tcmask))) == 0xFFFF) + { + int stride = _mm_cvtsi128_si32(tcoffset)>>16; + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + const unsigned char * RESTRICT ptr1, * RESTRICT ptr2; + __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)), pix1, pix2, pix3, pix4, fracm; + tci = _mm_madd_epi16(tci, tcoffset); + ptr1 = pixelbase + _mm_cvtsi128_si32(tci); + ptr2 = pixelbase + _mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2))); + pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr1), _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr1 + stride)), _mm_setzero_si128()); + pix3 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr2), _mm_setzero_si128()); + pix4 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr2 + stride)), _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix3 = _mm_add_epi16(pix3, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), + _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); + pix2 = _mm_unpacklo_epi64(pix1, pix3); + pix4 = _mm_unpackhi_epi64(pix1, pix3); + pix2 = _mm_add_epi16(pix2, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), + _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); + _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); + } + if (x <= endsub) + { + const unsigned char * RESTRICT ptr1; + __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), pix1, pix2, fracm; + tci = _mm_madd_epi16(tci, tcoffset); + ptr1 = pixelbase + _mm_cvtsi128_si32(tci); + pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr1), _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr1 + stride)), _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); + outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + x++; + } + } + else if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, pix3, pix4, fracm; + tci = _mm_min_epi16(_mm_max_epi16(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), _mm_setzero_si128()), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + tci = _mm_shuffle_epi32(_mm_shufflehi_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 2, 3, 2)); + tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix3 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix4 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix3 = _mm_add_epi16(pix3, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), + _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); + pix2 = _mm_unpacklo_epi64(pix1, pix3); + pix4 = _mm_unpackhi_epi64(pix1, pix3); + pix2 = _mm_add_epi16(pix2, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), + _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); + _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); + } + if (x <= endsub) + { + __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, fracm; + tci = _mm_min_epi16(_mm_max_epi16(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), _mm_setzero_si128()), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); + outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + x++; + } + } + else + { + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, pix3, pix4, fracm; + tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + tci = _mm_shuffle_epi32(_mm_shufflehi_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 2, 3, 2)); + tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix3 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix4 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix3 = _mm_add_epi16(pix3, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), + _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); + pix2 = _mm_unpacklo_epi64(pix1, pix3); + pix4 = _mm_unpackhi_epi64(pix1, pix3); + pix2 = _mm_add_epi16(pix2, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), + _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); + _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); + } + if (x <= endsub) + { + __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, fracm; + tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); + outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + x++; + } + } + } + else + { + if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)); + tci = _mm_min_epi16(_mm_max_epi16(tci, _mm_setzero_si128()), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; + outi[x+1] = *(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]; + } + if (x <= endsub) + { + __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)); + tci =_mm_min_epi16(_mm_max_epi16(tci, _mm_setzero_si128()), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; + x++; + } + } + else + { + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)); + tci = _mm_and_si128(tci, tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; + outi[x+1] = *(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]; + } + if (x <= endsub) + { + __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)); + tci = _mm_and_si128(tci, tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; + x++; + } + } + } + } +#endif +} + +void DPSOFTRAST_Draw_Span_TextureCubeVaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char * RESTRICT out4ub, int texunitindex, int arrayindex, const float * RESTRICT zf) +{ + // TODO: IMPLEMENT + memset(out4ub + span->startx*4, 255, (span->startx - span->endx)*4); +} + +float DPSOFTRAST_SampleShadowmap(const float *vector) +{ + // TODO: IMPLEMENT + return 1.0f; +} + +void DPSOFTRAST_Draw_Span_MultiplyVarying(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *in4f, int arrayindex, const float *zf) +{ + int x; + int startx = span->startx; + int endx = span->endx; + float c[4]; + float data[4]; + float slope[4]; + float z; + DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); + for (x = startx;x < endx;x++) + { + z = zf[x]; + c[0] = (data[0] + slope[0]*x) * z; + c[1] = (data[1] + slope[1]*x) * z; + c[2] = (data[2] + slope[2]*x) * z; + c[3] = (data[3] + slope[3]*x) * z; + out4f[x*4+0] = in4f[x*4+0] * c[0]; + out4f[x*4+1] = in4f[x*4+1] * c[1]; + out4f[x*4+2] = in4f[x*4+2] * c[2]; + out4f[x*4+3] = in4f[x*4+3] * c[3]; + } +} + +void DPSOFTRAST_Draw_Span_Varying(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, int arrayindex, const float *zf) +{ + int x; + int startx = span->startx; + int endx = span->endx; + float c[4]; + float data[4]; + float slope[4]; + float z; + DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); + for (x = startx;x < endx;x++) + { + z = zf[x]; + c[0] = (data[0] + slope[0]*x) * z; + c[1] = (data[1] + slope[1]*x) * z; + c[2] = (data[2] + slope[2]*x) * z; + c[3] = (data[3] + slope[3]*x) * z; + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } +} + +void DPSOFTRAST_Draw_Span_AddBloom(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f, const float *subcolor) +{ + int x, startx = span->startx, endx = span->endx; + float c[4], localcolor[4]; + localcolor[0] = subcolor[0]; + localcolor[1] = subcolor[1]; + localcolor[2] = subcolor[2]; + localcolor[3] = subcolor[3]; + for (x = startx;x < endx;x++) + { + c[0] = inb4f[x*4+0] - localcolor[0];if (c[0] < 0.0f) c[0] = 0.0f; + c[1] = inb4f[x*4+1] - localcolor[1];if (c[1] < 0.0f) c[1] = 0.0f; + c[2] = inb4f[x*4+2] - localcolor[2];if (c[2] < 0.0f) c[2] = 0.0f; + c[3] = inb4f[x*4+3] - localcolor[3];if (c[3] < 0.0f) c[3] = 0.0f; + out4f[x*4+0] = ina4f[x*4+0] + c[0]; + out4f[x*4+1] = ina4f[x*4+1] + c[1]; + out4f[x*4+2] = ina4f[x*4+2] + c[2]; + out4f[x*4+3] = ina4f[x*4+3] + c[3]; + } +} + +void DPSOFTRAST_Draw_Span_MultiplyBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) +{ + int x, startx = span->startx, endx = span->endx; + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = ina4f[x*4+0] * inb4f[x*4+0]; + out4f[x*4+1] = ina4f[x*4+1] * inb4f[x*4+1]; + out4f[x*4+2] = ina4f[x*4+2] * inb4f[x*4+2]; + out4f[x*4+3] = ina4f[x*4+3] * inb4f[x*4+3]; + } +} + +void DPSOFTRAST_Draw_Span_AddBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) +{ + int x, startx = span->startx, endx = span->endx; + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = ina4f[x*4+0] + inb4f[x*4+0]; + out4f[x*4+1] = ina4f[x*4+1] + inb4f[x*4+1]; + out4f[x*4+2] = ina4f[x*4+2] + inb4f[x*4+2]; + out4f[x*4+3] = ina4f[x*4+3] + inb4f[x*4+3]; + } +} + +void DPSOFTRAST_Draw_Span_MixBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) +{ + int x, startx = span->startx, endx = span->endx; + float a, b; + for (x = startx;x < endx;x++) + { + a = 1.0f - inb4f[x*4+3]; + b = inb4f[x*4+3]; + out4f[x*4+0] = ina4f[x*4+0] * a + inb4f[x*4+0] * b; + out4f[x*4+1] = ina4f[x*4+1] * a + inb4f[x*4+1] * b; + out4f[x*4+2] = ina4f[x*4+2] * a + inb4f[x*4+2] * b; + out4f[x*4+3] = ina4f[x*4+3] * a + inb4f[x*4+3] * b; + } +} + +void DPSOFTRAST_Draw_Span_MixUniformColor(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *in4f, const float *color) +{ + int x, startx = span->startx, endx = span->endx; + float localcolor[4], ilerp, lerp; + localcolor[0] = color[0]; + localcolor[1] = color[1]; + localcolor[2] = color[2]; + localcolor[3] = color[3]; + ilerp = 1.0f - localcolor[3]; + lerp = localcolor[3]; + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = in4f[x*4+0] * ilerp + localcolor[0] * lerp; + out4f[x*4+1] = in4f[x*4+1] * ilerp + localcolor[1] * lerp; + out4f[x*4+2] = in4f[x*4+2] * ilerp + localcolor[2] * lerp; + out4f[x*4+3] = in4f[x*4+3] * ilerp + localcolor[3] * lerp; + } +} + + + +void DPSOFTRAST_Draw_Span_MultiplyVaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *in4ub, int arrayindex, const float *zf) +{ +#ifdef SSE_POSSIBLE + int x; + int startx = span->startx; + int endx = span->endx; + __m128 data, slope; + __m128 mod, endmod; + __m128i submod, substep, endsubmod; + DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); + data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); + slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); + endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); + endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(256.0f))); + for (x = startx; x < endx;) + { + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + __m128 subscale = _mm_set1_ps(256.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); + if (nextsub >= endx) + { + nextsub = endsub = endx-1; + if (x < nextsub) subscale = _mm_set1_ps(256.0f / (nextsub - x)); + } + mod = endmod; + submod = endsubmod; + endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); + substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endmod, mod), subscale)); + endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(256.0f))); + submod = _mm_packs_epi32(submod, _mm_add_epi32(submod, substep)); + substep = _mm_packs_epi32(substep, substep); + for (; x + 1 <= endsub; x += 2, submod = _mm_add_epi16(submod, substep)) + { + __m128i pix = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&in4ub[x*4])); + pix = _mm_mulhi_epu16(pix, submod); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); + } + if (x <= endsub) + { + __m128i pix = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&in4ub[x*4])); + pix = _mm_mulhi_epu16(pix, submod); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + x++; + } + } +#endif +} + +void DPSOFTRAST_Draw_Span_VaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, int arrayindex, const float *zf) +{ +#ifdef SSE_POSSIBLE + int x; + int startx = span->startx; + int endx = span->endx; + __m128 data, slope; + __m128 mod, endmod; + __m128i submod, substep, endsubmod; + DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); + data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); + slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); + endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); + endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(4095.0f))); + for (x = startx; x < endx;) + { + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + __m128 subscale = _mm_set1_ps(4095.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); + if (nextsub >= endx) + { + nextsub = endsub = endx-1; + if (x < nextsub) subscale = _mm_set1_ps(4095.0f / (nextsub - x)); + } + mod = endmod; + submod = endsubmod; + endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); + substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endmod, mod), subscale)); + endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(4095.0f))); + submod = _mm_packs_epi32(submod, _mm_add_epi32(submod, substep)); + substep = _mm_packs_epi32(substep, substep); + for (; x + 1 <= endsub; x += 2, submod = _mm_add_epi16(submod, substep)) + { + __m128i pix = _mm_srai_epi16(submod, 4); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); + } + if (x <= endsub) + { + __m128i pix = _mm_srai_epi16(submod, 4); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + x++; + } + } +#endif +} + +void DPSOFTRAST_Draw_Span_AddBloomBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub, const float *subcolor) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + __m128i localcolor = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(subcolor), _mm_set1_ps(255.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + localcolor = _mm_packs_epi32(localcolor, localcolor); + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); + pix1 = _mm_add_epi16(pix1, _mm_subs_epu16(pix2, localcolor)); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); + pix1 = _mm_add_epi16(pix1, _mm_subs_epu16(pix2, localcolor)); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +void DPSOFTRAST_Draw_Span_MultiplyBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&inb4ub[x*4])); + pix1 = _mm_mulhi_epu16(pix1, pix2); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&inb4ub[x*4])); + pix1 = _mm_mulhi_epu16(pix1, pix2); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +void DPSOFTRAST_Draw_Span_AddBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); + pix1 = _mm_add_epi16(pix1, pix2); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); + pix1 = _mm_add_epi16(pix1, pix2); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +void DPSOFTRAST_Draw_Span_TintedAddBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub, const float *inbtintbgra) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + __m128i tint = _mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(inbtintbgra), _mm_set1_ps(256.0f))); + tint = _mm_packs_epi32(tint, tint); + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&inb4ub[x*4])); + pix1 = _mm_add_epi16(pix1, _mm_mulhi_epu16(tint, pix2)); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&inb4ub[x*4])); + pix1 = _mm_add_epi16(pix1, _mm_mulhi_epu16(tint, pix2)); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +void DPSOFTRAST_Draw_Span_MixBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 4), _mm_slli_epi16(blend, 4))); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); + __m128i blend = _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 3, 3, 3)); + pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 4), _mm_slli_epi16(blend, 4))); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +void DPSOFTRAST_Draw_Span_MixUniformColorBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *in4ub, const float *color) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + __m128i localcolor = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(color), _mm_set1_ps(255.0f))), _MM_SHUFFLE(3, 0, 1, 2)), blend; + localcolor = _mm_packs_epi32(localcolor, localcolor); + blend = _mm_slli_epi16(_mm_shufflehi_epi16(_mm_shufflelo_epi16(localcolor, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)), 4); + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&in4ub[x*4]), _mm_setzero_si128()); + pix = _mm_add_epi16(pix, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(localcolor, pix), 4), blend)); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); + } + if (x < endx) + { + __m128i pix = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&in4ub[x*4]), _mm_setzero_si128()); + pix = _mm_add_epi16(pix, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(localcolor, pix), 4), blend)); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } +#endif +} + + + +void DPSOFTRAST_VertexShader_Generic(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_COLOR); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0); + if (dpsoftrast.shader_permutation & SHADERPERMUTATION_SPECULAR) + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); +} + +void DPSOFTRAST_PixelShader_Generic(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_FIRST, 2, buffer_z); + DPSOFTRAST_Draw_Span_MultiplyVaryingBGRA8(triangle, span, buffer_FragColorbgra8, buffer_texture_colorbgra8, 1, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_SECOND, 2, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + // multiply + DPSOFTRAST_Draw_Span_MultiplyBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); + } + else if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + // add + DPSOFTRAST_Draw_Span_AddBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); + } + else if (thread->shader_permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) + { + // alphablend + DPSOFTRAST_Draw_Span_MixBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); + } + } + } + else + DPSOFTRAST_Draw_Span_VaryingBGRA8(triangle, span, buffer_FragColorbgra8, 1, buffer_z); + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +void DPSOFTRAST_VertexShader_PostProcess(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD4); +} + +void DPSOFTRAST_PixelShader_PostProcess(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // TODO: optimize!! at the very least there is no reason to use texture sampling on the frame texture + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_FragColorbgra8, GL20TU_FIRST, 2, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_BLOOM) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_SECOND, 3, buffer_z); + DPSOFTRAST_Draw_Span_AddBloomBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_colorbgra8, thread->uniform4f + DPSOFTRAST_UNIFORM_BloomColorSubtract * 4); + } + DPSOFTRAST_Draw_Span_MixUniformColorBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, thread->uniform4f + DPSOFTRAST_UNIFORM_ViewTintColor * 4); + if (thread->shader_permutation & SHADERPERMUTATION_SATURATION) + { + // TODO: implement saturation + } + if (thread->shader_permutation & SHADERPERMUTATION_GAMMARAMPS) + { + // TODO: implement gammaramps + } + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +void DPSOFTRAST_VertexShader_Depth_Or_Shadow(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +void DPSOFTRAST_PixelShader_Depth_Or_Shadow(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // this is never called (because colormask is off when this shader is used) + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +void DPSOFTRAST_VertexShader_FlatColor(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); +} + +void DPSOFTRAST_PixelShader_FlatColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ +#ifdef SSE_POSSIBLE + unsigned char * RESTRICT pixelmask = span->pixelmask; + unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; + int x, startx = span->startx, endx = span->endx; + __m128i Color_Ambientm; + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, 2, buffer_z); + if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) + pixel = buffer_FragColorbgra8; + Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); + Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); + for (x = startx;x < endx;x++) + { + __m128i color, pix; + if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) + { + __m128i pix2; + color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); + pix = _mm_mulhi_epu16(Color_Ambientm, _mm_unpacklo_epi8(_mm_setzero_si128(), color)); + pix2 = _mm_mulhi_epu16(Color_Ambientm, _mm_unpackhi_epi8(_mm_setzero_si128(), color)); + _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); + x += 3; + continue; + } + if (!pixelmask[x]) + continue; + color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); + pix = _mm_mulhi_epu16(Color_Ambientm, color); + *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } + if (pixel == buffer_FragColorbgra8) + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +#endif +} + + + +void DPSOFTRAST_VertexShader_VertexColor(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_COLOR); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); +} + +void DPSOFTRAST_PixelShader_VertexColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ +#ifdef SSE_POSSIBLE + unsigned char * RESTRICT pixelmask = span->pixelmask; + unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; + int x, startx = span->startx, endx = span->endx; + __m128i Color_Ambientm, Color_Diffusem; + __m128 data, slope; + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + int arrayindex = DPSOFTRAST_ARRAY_COLOR; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, 2, buffer_z); + if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) + pixel = buffer_FragColorbgra8; + Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); + Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); + Color_Diffusem = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4]), _mm_set1_ps(4096.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Diffusem = _mm_and_si128(Color_Diffusem, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Diffusem = _mm_packs_epi32(Color_Diffusem, Color_Diffusem); + DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); + data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); + slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); + data = _mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))); + data = _mm_mul_ps(data, _mm_set1_ps(4096.0f)); + slope = _mm_mul_ps(slope, _mm_set1_ps(4096.0f)); + for (x = startx;x < endx;x++, data = _mm_add_ps(data, slope)) + { + __m128i color, mod, pix; + if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) + { + __m128i pix2, mod2; + __m128 z = _mm_loadu_ps(&buffer_z[x]); + color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); + mod = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(0, 0, 0, 0)))); + data = _mm_add_ps(data, slope); + mod = _mm_packs_epi32(mod, _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(1, 1, 1, 1))))); + data = _mm_add_ps(data, slope); + mod2 = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(2, 2, 2, 2)))); + data = _mm_add_ps(data, slope); + mod2 = _mm_packs_epi32(mod2, _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(3, 3, 3, 3))))); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, mod), Color_Ambientm), + _mm_unpacklo_epi8(_mm_setzero_si128(), color)); + pix2 = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, mod2), Color_Ambientm), + _mm_unpackhi_epi8(_mm_setzero_si128(), color)); + _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); + x += 3; + continue; + } + if (!pixelmask[x]) + continue; + color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); + mod = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_load1_ps(&buffer_z[x]))); + mod = _mm_packs_epi32(mod, mod); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(mod, Color_Diffusem), Color_Ambientm), color); + *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } + if (pixel == buffer_FragColorbgra8) + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +#endif +} + + + +void DPSOFTRAST_VertexShader_Lightmap(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); +} + +void DPSOFTRAST_PixelShader_Lightmap(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ +#ifdef SSE_POSSIBLE + unsigned char * RESTRICT pixelmask = span->pixelmask; + unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; + int x, startx = span->startx, endx = span->endx; + __m128i Color_Ambientm, Color_Diffusem, Color_Glowm, Color_AmbientGlowm; + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_glowbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) + pixel = buffer_FragColorbgra8; + Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); + Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); + Color_Diffusem = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Diffusem = _mm_and_si128(Color_Diffusem, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Diffusem = _mm_packs_epi32(Color_Diffusem, Color_Diffusem); + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glowbgra8, GL20TU_GLOW, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + Color_Glowm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Glowm = _mm_and_si128(Color_Glowm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Glowm = _mm_packs_epi32(Color_Glowm, Color_Glowm); + Color_AmbientGlowm = _mm_unpacklo_epi64(Color_Ambientm, Color_Glowm); + for (x = startx;x < endx;x++) + { + __m128i color, lightmap, glow, pix; + if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) + { + __m128i pix2; + color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); + lightmap = _mm_loadu_si128((const __m128i *)&buffer_texture_lightmapbgra8[x*4]); + glow = _mm_loadu_si128((const __m128i *)&buffer_texture_glowbgra8[x*4]); + pix = _mm_add_epi16(_mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpacklo_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), + _mm_unpacklo_epi8(_mm_setzero_si128(), color)), + _mm_mulhi_epu16(Color_Glowm, _mm_unpacklo_epi8(_mm_setzero_si128(), glow))); + pix2 = _mm_add_epi16(_mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpackhi_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), + _mm_unpackhi_epi8(_mm_setzero_si128(), color)), + _mm_mulhi_epu16(Color_Glowm, _mm_unpackhi_epi8(_mm_setzero_si128(), glow))); + _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); + x += 3; + continue; + } + if (!pixelmask[x]) + continue; + color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); + lightmap = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_lightmapbgra8[x*4])); + glow = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_glowbgra8[x*4])); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, lightmap), Color_AmbientGlowm), _mm_unpacklo_epi64(color, glow)); + pix = _mm_add_epi16(pix, _mm_shuffle_epi32(pix, _MM_SHUFFLE(3, 2, 3, 2))); + *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } + } + else + { + for (x = startx;x < endx;x++) + { + __m128i color, lightmap, pix; + if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) + { + __m128i pix2; + color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); + lightmap = _mm_loadu_si128((const __m128i *)&buffer_texture_lightmapbgra8[x*4]); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpacklo_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), + _mm_unpacklo_epi8(_mm_setzero_si128(), color)); + pix2 = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpackhi_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), + _mm_unpackhi_epi8(_mm_setzero_si128(), color)); + _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); + x += 3; + continue; + } + if (!pixelmask[x]) + continue; + color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); + lightmap = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_lightmapbgra8[x*4])); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(lightmap, Color_Diffusem), Color_Ambientm), color); + *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } + } + if (pixel == buffer_FragColorbgra8) + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +#endif +} + + +void DPSOFTRAST_VertexShader_LightDirection(void); +void DPSOFTRAST_PixelShader_LightDirection(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span); + +void DPSOFTRAST_VertexShader_FakeLight(void) +{ + DPSOFTRAST_VertexShader_LightDirection(); +} + +void DPSOFTRAST_PixelShader_FakeLight(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); +} + + + +void DPSOFTRAST_VertexShader_LightDirectionMap_ModelSpace(void) +{ + DPSOFTRAST_VertexShader_LightDirection(); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); +} + +void DPSOFTRAST_PixelShader_LightDirectionMap_ModelSpace(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); +} + + + +void DPSOFTRAST_VertexShader_LightDirectionMap_TangentSpace(void) +{ + DPSOFTRAST_VertexShader_LightDirection(); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); +} + +void DPSOFTRAST_PixelShader_LightDirectionMap_TangentSpace(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); +} + + + +void DPSOFTRAST_VertexShader_LightDirection(void) +{ + int i; + int numvertices = dpsoftrast.numvertices; + float LightDir[4]; + float LightVector[4]; + float EyePosition[4]; + float EyeVectorModelSpace[4]; + float EyeVector[4]; + float position[4]; + float svector[4]; + float tvector[4]; + float normal[4]; + LightDir[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+0]; + LightDir[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+1]; + LightDir[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+2]; + LightDir[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+3]; + EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; + EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; + EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; + EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); + for (i = 0;i < numvertices;i++) + { + position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; + position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; + position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; + svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; + svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; + svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; + tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; + tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; + tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; + normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; + normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; + normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; + LightVector[0] = svector[0] * LightDir[0] + svector[1] * LightDir[1] + svector[2] * LightDir[2]; + LightVector[1] = tvector[0] * LightDir[0] + tvector[1] * LightDir[1] + tvector[2] * LightDir[2]; + LightVector[2] = normal[0] * LightDir[0] + normal[1] * LightDir[1] + normal[2] * LightDir[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+0] = LightVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+1] = LightVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+2] = LightVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+3] = 0.0f; + EyeVectorModelSpace[0] = EyePosition[0] - position[0]; + EyeVectorModelSpace[1] = EyePosition[1] - position[1]; + EyeVectorModelSpace[2] = EyePosition[2] - position[2]; + EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; + EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; + EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+0] = EyeVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+1] = EyeVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+2] = EyeVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+3] = 0.0f; + } + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +#define DPSOFTRAST_Min(a,b) ((a) < (b) ? (a) : (b)) +#define DPSOFTRAST_Max(a,b) ((a) > (b) ? (a) : (b)) +#define DPSOFTRAST_Vector3Dot(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) +#define DPSOFTRAST_Vector3LengthSquared(v) (DPSOFTRAST_Vector3Dot((v),(v))) +#define DPSOFTRAST_Vector3Length(v) (sqrt(DPSOFTRAST_Vector3LengthSquared(v))) +#define DPSOFTRAST_Vector3Normalize(v)\ +do\ +{\ + float len = sqrt(DPSOFTRAST_Vector3Dot(v,v));\ + if (len)\ + {\ + len = 1.0f / len;\ + v[0] *= len;\ + v[1] *= len;\ + v[2] *= len;\ + }\ +}\ +while(0) + +void DPSOFTRAST_PixelShader_LightDirection(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_glossbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_glowbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_pantsbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_shirtbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_deluxemapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + int x, startx = span->startx, endx = span->endx; + float Color_Ambient[4], Color_Diffuse[4], Color_Specular[4], Color_Glow[4], Color_Pants[4], Color_Shirt[4], LightColor[4]; + float LightVectordata[4]; + float LightVectorslope[4]; + float EyeVectordata[4]; + float EyeVectorslope[4]; + float VectorSdata[4]; + float VectorSslope[4]; + float VectorTdata[4]; + float VectorTslope[4]; + float VectorRdata[4]; + float VectorRslope[4]; + float z; + float diffusetex[4]; + float glosstex[4]; + float surfacenormal[4]; + float lightnormal[4]; + float lightnormal_modelspace[4]; + float eyenormal[4]; + float specularnormal[4]; + float diffuse; + float specular; + float SpecularPower; + int d[4]; + Color_Glow[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+0]; + Color_Glow[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+1]; + Color_Glow[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+2]; + Color_Glow[3] = 0.0f; + Color_Ambient[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+0]; + Color_Ambient[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+1]; + Color_Ambient[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+2]; + Color_Ambient[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]; + Color_Pants[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+0]; + Color_Pants[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+1]; + Color_Pants[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+2]; + Color_Pants[3] = 0.0f; + Color_Shirt[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+0]; + Color_Shirt[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+1]; + Color_Shirt[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+2]; + Color_Shirt[3] = 0.0f; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_pantsbgra8, GL20TU_PANTS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_shirtbgra8, GL20TU_SHIRT, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + } + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glowbgra8, GL20TU_GLOW, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + } + if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) + { + Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; + Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; + Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; + Color_Diffuse[3] = 0.0f; + LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; + LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; + LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; + LightColor[3] = 0.0f; + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + Color_Specular[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+0]; + Color_Specular[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+1]; + Color_Specular[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+2]; + Color_Specular[3] = 0.0f; + SpecularPower = thread->uniform4f[DPSOFTRAST_UNIFORM_SpecularPower*4+0] * (1.0f / 255.0f); + DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glossbgra8, GL20TU_GLOSS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + + if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorSdata, VectorSslope, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorTdata, VectorTslope, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorRdata, VectorRslope, DPSOFTRAST_ARRAY_TEXCOORD3); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + } + else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + } + else if(thread->shader_mode == SHADERMODE_FAKELIGHT) + { + // nothing of this needed + } + else + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD5); + } + + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; + diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; + diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; + diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; + } + glosstex[0] = buffer_texture_glossbgra8[x*4+0]; + glosstex[1] = buffer_texture_glossbgra8[x*4+1]; + glosstex[2] = buffer_texture_glossbgra8[x*4+2]; + glosstex[3] = buffer_texture_glossbgra8[x*4+3]; + surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(surfacenormal); + + if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) + { + // myhalf3 lightnormal_modelspace = myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + myhalf3(-1.0, -1.0, -1.0);\n"; + lightnormal_modelspace[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + lightnormal_modelspace[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + lightnormal_modelspace[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + + // lightnormal.x = dot(lightnormal_modelspace, myhalf3(VectorS));\n" + lightnormal[0] = lightnormal_modelspace[0] * (VectorSdata[0] + VectorSslope[0] * x) + + lightnormal_modelspace[1] * (VectorSdata[1] + VectorSslope[1] * x) + + lightnormal_modelspace[2] * (VectorSdata[2] + VectorSslope[2] * x); + + // lightnormal.y = dot(lightnormal_modelspace, myhalf3(VectorT));\n" + lightnormal[1] = lightnormal_modelspace[0] * (VectorTdata[0] + VectorTslope[0] * x) + + lightnormal_modelspace[1] * (VectorTdata[1] + VectorTslope[1] * x) + + lightnormal_modelspace[2] * (VectorTdata[2] + VectorTslope[2] * x); + + // lightnormal.z = dot(lightnormal_modelspace, myhalf3(VectorR));\n" + lightnormal[2] = lightnormal_modelspace[0] * (VectorRdata[0] + VectorRslope[0] * x) + + lightnormal_modelspace[1] * (VectorRdata[1] + VectorRslope[1] * x) + + lightnormal_modelspace[2] * (VectorRdata[2] + VectorRslope[2] * x); + + // lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n" + DPSOFTRAST_Vector3Normalize(lightnormal); + + // myhalf3 lightcolor = myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n"; + { + float f = 1.0f / (256.0f * max(0.25f, lightnormal[2])); + LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; + LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; + LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; + } + } + else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) + { + lightnormal[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + lightnormal[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + lightnormal[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + { + float f = 1.0f / 256.0f; + LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; + LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; + LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; + } + } + else if(thread->shader_mode == SHADERMODE_FAKELIGHT) + { + lightnormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + lightnormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + lightnormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + + LightColor[0] = 1.0; + LightColor[1] = 1.0; + LightColor[2] = 1.0; + } + else + { + lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; + lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; + lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + } + + diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; + + if(thread->shader_exactspecularmath) + { + // reflect lightnormal at surfacenormal, take the negative of that + // i.e. we want (2*dot(N, i) * N - I) for N=surfacenormal, I=lightnormal + float f; + f = DPSOFTRAST_Vector3Dot(lightnormal, surfacenormal); + specularnormal[0] = 2*f*surfacenormal[0] - lightnormal[0]; + specularnormal[1] = 2*f*surfacenormal[1] - lightnormal[1]; + specularnormal[2] = 2*f*surfacenormal[2] - lightnormal[2]; + + // dot of this and normalize(EyeVectorFogDepth.xyz) + eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(eyenormal); + + specular = DPSOFTRAST_Vector3Dot(eyenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; + } + else + { + eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(eyenormal); + + specularnormal[0] = lightnormal[0] + eyenormal[0]; + specularnormal[1] = lightnormal[1] + eyenormal[1]; + specularnormal[2] = lightnormal[2] + eyenormal[2]; + DPSOFTRAST_Vector3Normalize(specularnormal); + + specular = DPSOFTRAST_Vector3Dot(surfacenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; + } + + specular = pow(specular, SpecularPower * glosstex[3]); + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * Color_Ambient[0] + (diffusetex[0] * Color_Diffuse[0] * diffuse + glosstex[0] * Color_Specular[0] * specular) * LightColor[0]);if (d[0] > 255) d[0] = 255; + d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * Color_Ambient[1] + (diffusetex[1] * Color_Diffuse[1] * diffuse + glosstex[1] * Color_Specular[1] * specular) * LightColor[1]);if (d[1] > 255) d[1] = 255; + d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * Color_Ambient[2] + (diffusetex[2] * Color_Diffuse[2] * diffuse + glosstex[2] * Color_Specular[2] * specular) * LightColor[2]);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)( diffusetex[0] * Color_Ambient[0] + (diffusetex[0] * Color_Diffuse[0] * diffuse + glosstex[0] * Color_Specular[0] * specular) * LightColor[0]);if (d[0] > 255) d[0] = 255; + d[1] = (int)( diffusetex[1] * Color_Ambient[1] + (diffusetex[1] * Color_Diffuse[1] * diffuse + glosstex[1] * Color_Specular[1] * specular) * LightColor[1]);if (d[1] > 255) d[1] = 255; + d[2] = (int)( diffusetex[2] * Color_Ambient[2] + (diffusetex[2] * Color_Diffuse[2] * diffuse + glosstex[2] * Color_Specular[2] * specular) * LightColor[2]);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; + } + + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + else if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) + { + Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; + Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; + Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; + Color_Diffuse[3] = 0.0f; + LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; + LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; + LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; + LightColor[3] = 0.0f; + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + + if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorSdata, VectorSslope, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorTdata, VectorTslope, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorRdata, VectorRslope, DPSOFTRAST_ARRAY_TEXCOORD3); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + } + else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + } + else if(thread->shader_mode == SHADERMODE_FAKELIGHT) + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); + } + else + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD5); + } + + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(surfacenormal); + + if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) + { + // myhalf3 lightnormal_modelspace = myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + myhalf3(-1.0, -1.0, -1.0);\n"; + lightnormal_modelspace[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + lightnormal_modelspace[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + lightnormal_modelspace[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + + // lightnormal.x = dot(lightnormal_modelspace, myhalf3(VectorS));\n" + lightnormal[0] = lightnormal_modelspace[0] * (VectorSdata[0] + VectorSslope[0] * x) + + lightnormal_modelspace[1] * (VectorSdata[1] + VectorSslope[1] * x) + + lightnormal_modelspace[2] * (VectorSdata[2] + VectorSslope[2] * x); + + // lightnormal.y = dot(lightnormal_modelspace, myhalf3(VectorT));\n" + lightnormal[1] = lightnormal_modelspace[0] * (VectorTdata[0] + VectorTslope[0] * x) + + lightnormal_modelspace[1] * (VectorTdata[1] + VectorTslope[1] * x) + + lightnormal_modelspace[2] * (VectorTdata[2] + VectorTslope[2] * x); + + // lightnormal.z = dot(lightnormal_modelspace, myhalf3(VectorR));\n" + lightnormal[2] = lightnormal_modelspace[0] * (VectorRdata[0] + VectorRslope[0] * x) + + lightnormal_modelspace[1] * (VectorRdata[1] + VectorRslope[1] * x) + + lightnormal_modelspace[2] * (VectorRdata[2] + VectorRslope[2] * x); + + // lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n" + DPSOFTRAST_Vector3Normalize(lightnormal); + + // myhalf3 lightcolor = myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n"; + { + float f = 1.0f / (256.0f * max(0.25f, lightnormal[2])); + LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; + LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; + LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; + } + } + else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) + { + lightnormal[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + lightnormal[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + lightnormal[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + { + float f = 1.0f / 256.0f; + LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; + LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; + LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; + } + } + else if(thread->shader_mode == SHADERMODE_FAKELIGHT) + { + lightnormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + lightnormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + lightnormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + + LightColor[0] = 1.0; + LightColor[1] = 1.0; + LightColor[2] = 1.0; + } + else + { + lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; + lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; + lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + } + + diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse * LightColor[0]));if (d[0] > 255) d[0] = 255; + d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse * LightColor[1]));if (d[1] > 255) d[1] = 255; + d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse * LightColor[2]));if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * (Color_Ambient[3] ));if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)( + diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse * LightColor[0]));if (d[0] > 255) d[0] = 255; + d[1] = (int)( + diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse * LightColor[1]));if (d[1] > 255) d[1] = 255; + d[2] = (int)( + diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse * LightColor[2]));if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * (Color_Ambient[3] ));if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + else + { + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * Color_Ambient[0]);if (d[0] > 255) d[0] = 255; + d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * Color_Ambient[1]);if (d[1] > 255) d[1] = 255; + d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * Color_Ambient[2]);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)( diffusetex[0] * Color_Ambient[0]);if (d[0] > 255) d[0] = 255; + d[1] = (int)( diffusetex[1] * Color_Ambient[1]);if (d[1] > 255) d[1] = 255; + d[2] = (int)( diffusetex[2] * Color_Ambient[2]);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +void DPSOFTRAST_VertexShader_LightSource(void) +{ + int i; + int numvertices = dpsoftrast.numvertices; + float LightPosition[4]; + float LightVector[4]; + float LightVectorModelSpace[4]; + float EyePosition[4]; + float EyeVectorModelSpace[4]; + float EyeVector[4]; + float position[4]; + float svector[4]; + float tvector[4]; + float normal[4]; + LightPosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+0]; + LightPosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+1]; + LightPosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+2]; + LightPosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+3]; + EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; + EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; + EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; + EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); + for (i = 0;i < numvertices;i++) + { + position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; + position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; + position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; + svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; + svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; + svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; + tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; + tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; + tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; + normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; + normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; + normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; + LightVectorModelSpace[0] = LightPosition[0] - position[0]; + LightVectorModelSpace[1] = LightPosition[1] - position[1]; + LightVectorModelSpace[2] = LightPosition[2] - position[2]; + LightVector[0] = svector[0] * LightVectorModelSpace[0] + svector[1] * LightVectorModelSpace[1] + svector[2] * LightVectorModelSpace[2]; + LightVector[1] = tvector[0] * LightVectorModelSpace[0] + tvector[1] * LightVectorModelSpace[1] + tvector[2] * LightVectorModelSpace[2]; + LightVector[2] = normal[0] * LightVectorModelSpace[0] + normal[1] * LightVectorModelSpace[1] + normal[2] * LightVectorModelSpace[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0] = LightVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1] = LightVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2] = LightVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+3] = 0.0f; + EyeVectorModelSpace[0] = EyePosition[0] - position[0]; + EyeVectorModelSpace[1] = EyePosition[1] - position[1]; + EyeVectorModelSpace[2] = EyePosition[2] - position[2]; + EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; + EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; + EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0] = EyeVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1] = EyeVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2] = EyeVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+3] = 0.0f; + } + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelToLightM1); +} + +void DPSOFTRAST_PixelShader_LightSource(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ +#ifdef SSE_POSSIBLE + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_glossbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_cubebgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_pantsbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_shirtbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + int x, startx = span->startx, endx = span->endx; + float Color_Ambient[4], Color_Diffuse[4], Color_Specular[4], Color_Glow[4], Color_Pants[4], Color_Shirt[4], LightColor[4]; + float CubeVectordata[4]; + float CubeVectorslope[4]; + float LightVectordata[4]; + float LightVectorslope[4]; + float EyeVectordata[4]; + float EyeVectorslope[4]; + float z; + float diffusetex[4]; + float glosstex[4]; + float surfacenormal[4]; + float lightnormal[4]; + float eyenormal[4]; + float specularnormal[4]; + float diffuse; + float specular; + float SpecularPower; + float CubeVector[4]; + float attenuation; + int d[4]; + Color_Glow[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+0]; + Color_Glow[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+1]; + Color_Glow[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+2]; + Color_Glow[3] = 0.0f; + Color_Ambient[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+0]; + Color_Ambient[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+1]; + Color_Ambient[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+2]; + Color_Ambient[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]; + Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; + Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; + Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; + Color_Diffuse[3] = 0.0f; + Color_Specular[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+0]; + Color_Specular[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+1]; + Color_Specular[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+2]; + Color_Specular[3] = 0.0f; + Color_Pants[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+0]; + Color_Pants[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+1]; + Color_Pants[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+2]; + Color_Pants[3] = 0.0f; + Color_Shirt[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+0]; + Color_Shirt[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+1]; + Color_Shirt[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+2]; + Color_Shirt[3] = 0.0f; + LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; + LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; + LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; + LightColor[3] = 0.0f; + SpecularPower = thread->uniform4f[DPSOFTRAST_UNIFORM_SpecularPower*4+0] * (1.0f / 255.0f); + DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_CALCATTRIB4F(triangle, span, CubeVectordata, CubeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD3); + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + startx*4, 0, (endx-startx)*4); // clear first, because we skip writing black pixels, and there are a LOT of them... + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_pantsbgra8, GL20TU_PANTS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_shirtbgra8, GL20TU_SHIRT, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + } + if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) + DPSOFTRAST_Draw_Span_TextureCubeVaryingBGRA8(triangle, span, buffer_texture_cubebgra8, GL20TU_CUBE, DPSOFTRAST_ARRAY_TEXCOORD3, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glossbgra8, GL20TU_GLOSS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; + CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; + CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; + attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); + if (attenuation < 0.01f) + continue; + if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) + { + attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); + if (attenuation < 0.01f) + continue; + } + + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; + diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; + diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; + diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; + } + glosstex[0] = buffer_texture_glossbgra8[x*4+0]; + glosstex[1] = buffer_texture_glossbgra8[x*4+1]; + glosstex[2] = buffer_texture_glossbgra8[x*4+2]; + glosstex[3] = buffer_texture_glossbgra8[x*4+3]; + surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(surfacenormal); + + lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; + lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; + lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + + diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; + + if(thread->shader_exactspecularmath) + { + // reflect lightnormal at surfacenormal, take the negative of that + // i.e. we want (2*dot(N, i) * N - I) for N=surfacenormal, I=lightnormal + float f; + f = DPSOFTRAST_Vector3Dot(lightnormal, surfacenormal); + specularnormal[0] = 2*f*surfacenormal[0] - lightnormal[0]; + specularnormal[1] = 2*f*surfacenormal[1] - lightnormal[1]; + specularnormal[2] = 2*f*surfacenormal[2] - lightnormal[2]; + + // dot of this and normalize(EyeVectorFogDepth.xyz) + eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(eyenormal); + + specular = DPSOFTRAST_Vector3Dot(eyenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; + } + else + { + eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(eyenormal); + + specularnormal[0] = lightnormal[0] + eyenormal[0]; + specularnormal[1] = lightnormal[1] + eyenormal[1]; + specularnormal[2] = lightnormal[2] + eyenormal[2]; + DPSOFTRAST_Vector3Normalize(specularnormal); + + specular = DPSOFTRAST_Vector3Dot(surfacenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; + } + specular = pow(specular, SpecularPower * glosstex[3]); + + if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) + { + // scale down the attenuation to account for the cubefilter multiplying everything by 255 + attenuation *= (1.0f / 255.0f); + d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse) + glosstex[0] * Color_Specular[0] * specular) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse) + glosstex[1] * Color_Specular[1] * specular) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse) + glosstex[2] * Color_Specular[2] * specular) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse) + glosstex[0] * Color_Specular[0] * specular) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse) + glosstex[1] * Color_Specular[1] * specular) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse) + glosstex[2] * Color_Specular[2] * specular) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + else if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; + CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; + CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; + attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); + if (attenuation < 0.01f) + continue; + if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) + { + attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); + if (attenuation < 0.01f) + continue; + } + + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; + diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; + diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; + diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; + } + surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(surfacenormal); + + lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; + lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; + lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + + diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; + if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) + { + // scale down the attenuation to account for the cubefilter multiplying everything by 255 + attenuation *= (1.0f / 255.0f); + d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse)) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse)) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse)) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse)) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse)) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse)) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + else + { + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; + CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; + CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; + attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); + if (attenuation < 0.01f) + continue; + if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) + { + attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); + if (attenuation < 0.01f) + continue; + } + + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; + diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; + diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; + diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; + } + if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) + { + // scale down the attenuation to account for the cubefilter multiplying everything by 255 + attenuation *= (1.0f / 255.0f); + d[0] = (int)((diffusetex[0] * (Color_Ambient[0])) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1])) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2])) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)((diffusetex[0] * (Color_Ambient[0])) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1])) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2])) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +#endif +} + + + +void DPSOFTRAST_VertexShader_Refraction(void) +{ + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +void DPSOFTRAST_PixelShader_Refraction(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + float z; + int x, startx = span->startx, endx = span->endx; + + // texture reads + unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + + // varyings + float ModelViewProjectionPositiondata[4]; + float ModelViewProjectionPositionslope[4]; + + // uniforms + float ScreenScaleRefractReflect[2]; + float ScreenCenterRefractReflect[2]; + float DistortScaleRefractReflect[2]; + float RefractColor[4]; + + DPSOFTRAST_Texture *texture = thread->texbound[GL20TU_REFRACTION]; + if(!texture) return; + + // read textures + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + + // read varyings + DPSOFTRAST_CALCATTRIB4F(triangle, span, ModelViewProjectionPositiondata, ModelViewProjectionPositionslope, DPSOFTRAST_ARRAY_TEXCOORD4); + + // read uniforms + ScreenScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+0]; + ScreenScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+1]; + ScreenCenterRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+0]; + ScreenCenterRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+1]; + DistortScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+0]; + DistortScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+1]; + RefractColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+2]; + RefractColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+1]; + RefractColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+0]; + RefractColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+3]; + + // do stuff + for (x = startx;x < endx;x++) + { + float SafeScreenTexCoord[2]; + float ScreenTexCoord[2]; + float v[3]; + float iw; + unsigned char c[4]; + + z = buffer_z[x]; + + // " vec2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n" + iw = 1.0f / (ModelViewProjectionPositiondata[3] + ModelViewProjectionPositionslope[3]*x); // / z + + // " vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n" + SafeScreenTexCoord[0] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[0] + ScreenCenterRefractReflect[0]; // * z (disappears) + SafeScreenTexCoord[1] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[1] + ScreenCenterRefractReflect[1]; // * z (disappears) + + // " vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(myhalf3(dp_texture2D(Texture_Normal, TexCoord)) - myhalf3(0.5))).xy * DistortScaleRefractReflect.zw;\n" + v[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + v[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + v[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(v); + ScreenTexCoord[0] = SafeScreenTexCoord[0] + v[0] * DistortScaleRefractReflect[0]; + ScreenTexCoord[1] = SafeScreenTexCoord[1] + v[1] * DistortScaleRefractReflect[1]; + + // " dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * RefractColor;\n" + DPSOFTRAST_Texture2DBGRA8(texture, 0, ScreenTexCoord[0], ScreenTexCoord[1], c); + + buffer_FragColorbgra8[x*4+0] = c[0] * RefractColor[0]; + buffer_FragColorbgra8[x*4+1] = c[1] * RefractColor[1]; + buffer_FragColorbgra8[x*4+2] = c[2] * RefractColor[2]; + buffer_FragColorbgra8[x*4+3] = min(RefractColor[3] * 256, 255); + } + + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +void DPSOFTRAST_VertexShader_Water(void) +{ + int i; + int numvertices = dpsoftrast.numvertices; + float EyePosition[4]; + float EyeVectorModelSpace[4]; + float EyeVector[4]; + float position[4]; + float svector[4]; + float tvector[4]; + float normal[4]; + EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; + EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; + EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; + EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); + for (i = 0;i < numvertices;i++) + { + position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; + position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; + position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; + svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; + svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; + svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; + tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; + tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; + tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; + normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; + normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; + normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; + EyeVectorModelSpace[0] = EyePosition[0] - position[0]; + EyeVectorModelSpace[1] = EyePosition[1] - position[1]; + EyeVectorModelSpace[2] = EyePosition[2] - position[2]; + EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; + EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; + EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+0] = EyeVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+1] = EyeVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+2] = EyeVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+3] = 0.0f; + } + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); +} + + +void DPSOFTRAST_PixelShader_Water(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + float z; + int x, startx = span->startx, endx = span->endx; + + // texture reads + unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + + // varyings + float ModelViewProjectionPositiondata[4]; + float ModelViewProjectionPositionslope[4]; + float EyeVectordata[4]; + float EyeVectorslope[4]; + + // uniforms + float ScreenScaleRefractReflect[4]; + float ScreenCenterRefractReflect[4]; + float DistortScaleRefractReflect[4]; + float RefractColor[4]; + float ReflectColor[4]; + float ReflectFactor; + float ReflectOffset; + + DPSOFTRAST_Texture *texture_refraction = thread->texbound[GL20TU_REFRACTION]; + DPSOFTRAST_Texture *texture_reflection = thread->texbound[GL20TU_REFLECTION]; + if(!texture_refraction || !texture_reflection) return; + + // read textures + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + + // read varyings + DPSOFTRAST_CALCATTRIB4F(triangle, span, ModelViewProjectionPositiondata, ModelViewProjectionPositionslope, DPSOFTRAST_ARRAY_TEXCOORD4); + DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); + + // read uniforms + ScreenScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+0]; + ScreenScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+1]; + ScreenScaleRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+2]; + ScreenScaleRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+3]; + ScreenCenterRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+0]; + ScreenCenterRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+1]; + ScreenCenterRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+2]; + ScreenCenterRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+3]; + DistortScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+0]; + DistortScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+1]; + DistortScaleRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+2]; + DistortScaleRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+3]; + RefractColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+2]; + RefractColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+1]; + RefractColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+0]; + RefractColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+3]; + ReflectColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+2]; + ReflectColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+1]; + ReflectColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+0]; + ReflectColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+3]; + ReflectFactor = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectFactor*4+0]; + ReflectOffset = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectOffset*4+0]; + + // do stuff + for (x = startx;x < endx;x++) + { + float SafeScreenTexCoord[4]; + float ScreenTexCoord[4]; + float v[3]; + float iw; + unsigned char c1[4]; + unsigned char c2[4]; + float Fresnel; + + z = buffer_z[x]; + + // " vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n" + iw = 1.0f / (ModelViewProjectionPositiondata[3] + ModelViewProjectionPositionslope[3]*x); // / z + + // " vec4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" + SafeScreenTexCoord[0] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[0] + ScreenCenterRefractReflect[0]; // * z (disappears) + SafeScreenTexCoord[1] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[1] + ScreenCenterRefractReflect[1]; // * z (disappears) + SafeScreenTexCoord[2] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[2] + ScreenCenterRefractReflect[2]; // * z (disappears) + SafeScreenTexCoord[3] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[3] + ScreenCenterRefractReflect[3]; // * z (disappears) + + // " vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5))).xyxy * DistortScaleRefractReflect;\n" + v[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + v[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + v[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(v); + ScreenTexCoord[0] = SafeScreenTexCoord[0] + v[0] * DistortScaleRefractReflect[0]; + ScreenTexCoord[1] = SafeScreenTexCoord[1] + v[1] * DistortScaleRefractReflect[1]; + ScreenTexCoord[2] = SafeScreenTexCoord[2] + v[0] * DistortScaleRefractReflect[2]; + ScreenTexCoord[3] = SafeScreenTexCoord[3] + v[1] * DistortScaleRefractReflect[3]; + + // " float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * ReflectFactor + ReflectOffset;\n" + v[0] = (EyeVectordata[0] + EyeVectorslope[0] * x); // * z (disappears) + v[1] = (EyeVectordata[1] + EyeVectorslope[1] * x); // * z (disappears) + v[2] = (EyeVectordata[2] + EyeVectorslope[2] * x); // * z (disappears) + DPSOFTRAST_Vector3Normalize(v); + Fresnel = 1.0f - v[2]; + Fresnel = min(1.0f, Fresnel); + Fresnel = Fresnel * Fresnel * ReflectFactor + ReflectOffset; + + // " dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * RefractColor;\n" + // " dp_FragColor = mix(vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * RefractColor, vec4(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n" + DPSOFTRAST_Texture2DBGRA8(texture_refraction, 0, ScreenTexCoord[0], ScreenTexCoord[1], c1); + DPSOFTRAST_Texture2DBGRA8(texture_reflection, 0, ScreenTexCoord[2], ScreenTexCoord[3], c2); + + buffer_FragColorbgra8[x*4+0] = (c1[0] * RefractColor[0]) * (1.0f - Fresnel) + (c2[0] * ReflectColor[0]) * Fresnel; + buffer_FragColorbgra8[x*4+1] = (c1[1] * RefractColor[1]) * (1.0f - Fresnel) + (c2[1] * ReflectColor[1]) * Fresnel; + buffer_FragColorbgra8[x*4+2] = (c1[2] * RefractColor[2]) * (1.0f - Fresnel) + (c2[2] * ReflectColor[2]) * Fresnel; + buffer_FragColorbgra8[x*4+3] = min(( RefractColor[3] * (1.0f - Fresnel) + ReflectColor[3] * Fresnel) * 256, 255); + } + + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +void DPSOFTRAST_VertexShader_ShowDepth(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +void DPSOFTRAST_PixelShader_ShowDepth(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // TODO: IMPLEMENT + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +void DPSOFTRAST_VertexShader_DeferredGeometry(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +void DPSOFTRAST_PixelShader_DeferredGeometry(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // TODO: IMPLEMENT + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +void DPSOFTRAST_VertexShader_DeferredLightSource(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +void DPSOFTRAST_PixelShader_DeferredLightSource(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // TODO: IMPLEMENT + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +typedef struct DPSOFTRAST_ShaderModeInfo_s +{ + int lodarrayindex; + void (*Vertex)(void); + void (*Span)(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span); + unsigned char arrays[DPSOFTRAST_ARRAY_TOTAL]; + unsigned char texunits[DPSOFTRAST_MAXTEXTUREUNITS]; +} +DPSOFTRAST_ShaderModeInfo; + +static const DPSOFTRAST_ShaderModeInfo DPSOFTRAST_ShaderModeTable[SHADERMODE_COUNT] = +{ + {2, DPSOFTRAST_VertexShader_Generic, DPSOFTRAST_PixelShader_Generic, {DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, ~0}, {GL20TU_FIRST, GL20TU_SECOND, ~0}}, + {2, DPSOFTRAST_VertexShader_PostProcess, DPSOFTRAST_PixelShader_PostProcess, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, ~0}, {GL20TU_FIRST, GL20TU_SECOND, ~0}}, + {2, DPSOFTRAST_VertexShader_Depth_Or_Shadow, DPSOFTRAST_PixelShader_Depth_Or_Shadow, {~0}, {~0}}, + {2, DPSOFTRAST_VertexShader_FlatColor, DPSOFTRAST_PixelShader_FlatColor, {DPSOFTRAST_ARRAY_TEXCOORD0, ~0}, {GL20TU_COLOR, ~0}}, + {2, DPSOFTRAST_VertexShader_VertexColor, DPSOFTRAST_PixelShader_VertexColor, {DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, ~0}, {GL20TU_COLOR, ~0}}, + {2, DPSOFTRAST_VertexShader_Lightmap, DPSOFTRAST_PixelShader_Lightmap, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD4, ~0}, {GL20TU_COLOR, GL20TU_LIGHTMAP, GL20TU_GLOW, ~0}}, + {2, DPSOFTRAST_VertexShader_FakeLight, DPSOFTRAST_PixelShader_FakeLight, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, ~0}}, + {2, DPSOFTRAST_VertexShader_LightDirectionMap_ModelSpace, DPSOFTRAST_PixelShader_LightDirectionMap_ModelSpace, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_LIGHTMAP, GL20TU_DELUXEMAP, ~0}}, + {2, DPSOFTRAST_VertexShader_LightDirectionMap_TangentSpace, DPSOFTRAST_PixelShader_LightDirectionMap_TangentSpace, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_LIGHTMAP, GL20TU_DELUXEMAP, ~0}}, + {2, DPSOFTRAST_VertexShader_LightDirection, DPSOFTRAST_PixelShader_LightDirection, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, ~0}}, + {2, DPSOFTRAST_VertexShader_LightSource, DPSOFTRAST_PixelShader_LightSource, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_CUBE, ~0}}, + {2, DPSOFTRAST_VertexShader_Refraction, DPSOFTRAST_PixelShader_Refraction, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD4, ~0}, {GL20TU_NORMAL, GL20TU_REFRACTION, ~0}}, + {2, DPSOFTRAST_VertexShader_Water, DPSOFTRAST_PixelShader_Water, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_NORMAL, GL20TU_REFLECTION, GL20TU_REFRACTION, ~0}}, + {2, DPSOFTRAST_VertexShader_ShowDepth, DPSOFTRAST_PixelShader_ShowDepth, {~0}}, + {2, DPSOFTRAST_VertexShader_DeferredGeometry, DPSOFTRAST_PixelShader_DeferredGeometry, {~0}}, + {2, DPSOFTRAST_VertexShader_DeferredLightSource, DPSOFTRAST_PixelShader_DeferredLightSource, {~0}}, +}; + +static void DPSOFTRAST_Draw_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_State_Span *span) +{ + int x; + int startx; + int endx; + unsigned int *depthpixel; + int depth; + int depthslope; + unsigned int d; + unsigned char *pixelmask; + DPSOFTRAST_State_Triangle *triangle; + triangle = &thread->triangles[span->triangle]; + depthpixel = dpsoftrast.fb_depthpixels + span->y * dpsoftrast.fb_width + span->x; + startx = span->startx; + endx = span->endx; + depth = span->depthbase; + depthslope = span->depthslope; + pixelmask = thread->pixelmaskarray; + if (thread->depthtest && dpsoftrast.fb_depthpixels) + { + switch(thread->fb_depthfunc) + { + default: + case GL_ALWAYS: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = true; break; + case GL_LESS: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] < d; break; + case GL_LEQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] <= d; break; + case GL_EQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] == d; break; + case GL_GEQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] >= d; break; + case GL_GREATER: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] > d; break; + case GL_NEVER: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = false; break; + } + while (startx < endx && !pixelmask[startx]) + startx++; + while (endx > startx && !pixelmask[endx-1]) + endx--; + } + else + { + // no depth testing means we're just dealing with color... + memset(pixelmask + startx, 1, endx - startx); + } + span->pixelmask = pixelmask; + span->startx = startx; + span->endx = endx; +} + +static void DPSOFTRAST_Draw_DepthWrite(const DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Span *span) +{ + int x, d, depth, depthslope, startx, endx; + const unsigned char *pixelmask; + unsigned int *depthpixel; + if (thread->depthmask && thread->depthtest && dpsoftrast.fb_depthpixels) + { + depth = span->depthbase; + depthslope = span->depthslope; + pixelmask = span->pixelmask; + startx = span->startx; + endx = span->endx; + depthpixel = dpsoftrast.fb_depthpixels + span->y * dpsoftrast.fb_width + span->x; + for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) + if (pixelmask[x]) + depthpixel[x] = d; + } +} + +void DPSOFTRAST_Draw_ProcessSpans(DPSOFTRAST_State_Thread *thread) +{ + int i; + DPSOFTRAST_State_Triangle *triangle; + DPSOFTRAST_State_Span *span; + for (i = 0; i < thread->numspans; i++) + { + span = &thread->spans[i]; + triangle = &thread->triangles[span->triangle]; + DPSOFTRAST_Draw_DepthTest(thread, span); + if (span->startx >= span->endx) + continue; + // run pixel shader if appropriate + // do this before running depthmask code, to allow the pixelshader + // to clear pixelmask values for alpha testing + if (dpsoftrast.fb_colorpixels[0] && thread->fb_colormask) + DPSOFTRAST_ShaderModeTable[thread->shader_mode].Span(thread, triangle, span); + DPSOFTRAST_Draw_DepthWrite(thread, span); + } + thread->numspans = 0; +} + +DEFCOMMAND(22, Draw, int datasize; int starty; int endy; ATOMIC_COUNTER refcount; int clipped; int firstvertex; int numvertices; int numtriangles; float *arrays; int *element3i; unsigned short *element3s;); + +static void DPSOFTRAST_Interpret_Draw(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Draw *command) +{ +#ifdef SSE_POSSIBLE + int cullface = thread->cullface; + int minx, maxx, miny, maxy; + int miny1, maxy1, miny2, maxy2; + __m128i fbmin, fbmax; + __m128 viewportcenter, viewportscale; + int firstvertex = command->firstvertex; + int numvertices = command->numvertices; + int numtriangles = command->numtriangles; + const int *element3i = command->element3i; + const unsigned short *element3s = command->element3s; + int clipped = command->clipped; + int i; + int j; + int k; + int y; + int e[3]; + __m128i screeny; + int starty, endy, bandy; + int numpoints; + int clipcase; + float clipdist[4]; + float clip0origin, clip0slope; + int clip0dir; + __m128 triangleedge1, triangleedge2, trianglenormal; + __m128 clipfrac[3]; + __m128 screen[4]; + DPSOFTRAST_State_Triangle *triangle; + DPSOFTRAST_Texture *texture; + DPSOFTRAST_ValidateQuick(thread, DPSOFTRAST_VALIDATE_DRAW); + miny = thread->fb_scissor[1]; + maxy = thread->fb_scissor[1] + thread->fb_scissor[3]; + miny1 = bound(miny, thread->miny1, maxy); + maxy1 = bound(miny, thread->maxy1, maxy); + miny2 = bound(miny, thread->miny2, maxy); + maxy2 = bound(miny, thread->maxy2, maxy); + if ((command->starty >= maxy1 || command->endy <= miny1) && (command->starty >= maxy2 || command->endy <= miny2)) + { + if (!ATOMIC_DECREMENT(command->refcount)) + { + if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) + MM_FREE(command->arrays); + } + return; + } + minx = thread->fb_scissor[0]; + maxx = thread->fb_scissor[0] + thread->fb_scissor[2]; + fbmin = _mm_setr_epi16(minx, miny1, minx, miny1, minx, miny1, minx, miny1); + fbmax = _mm_sub_epi16(_mm_setr_epi16(maxx, maxy2, maxx, maxy2, maxx, maxy2, maxx, maxy2), _mm_set1_epi16(1)); + viewportcenter = _mm_load_ps(thread->fb_viewportcenter); + viewportscale = _mm_load_ps(thread->fb_viewportscale); + screen[3] = _mm_setzero_ps(); + clipfrac[0] = clipfrac[1] = clipfrac[2] = _mm_setzero_ps(); + for (i = 0;i < numtriangles;i++) + { + const float *screencoord4f = command->arrays; + const float *arrays = screencoord4f + numvertices*4; + + // generate the 3 edges of this triangle + // generate spans for the triangle - switch based on left split or right split classification of triangle + if (element3s) + { + e[0] = element3s[i*3+0] - firstvertex; + e[1] = element3s[i*3+1] - firstvertex; + e[2] = element3s[i*3+2] - firstvertex; + } + else if (element3i) + { + e[0] = element3i[i*3+0] - firstvertex; + e[1] = element3i[i*3+1] - firstvertex; + e[2] = element3i[i*3+2] - firstvertex; + } + else + { + e[0] = i*3+0; + e[1] = i*3+1; + e[2] = i*3+2; + } + +#define SKIPBACKFACE \ + triangleedge1 = _mm_sub_ps(screen[0], screen[1]); \ + triangleedge2 = _mm_sub_ps(screen[2], screen[1]); \ + /* store normal in 2, 0, 1 order instead of 0, 1, 2 as it requires fewer shuffles and leaves z component accessible as scalar */ \ + trianglenormal = _mm_sub_ss(_mm_mul_ss(triangleedge1, _mm_shuffle_ps(triangleedge2, triangleedge2, _MM_SHUFFLE(3, 0, 2, 1))), \ + _mm_mul_ss(_mm_shuffle_ps(triangleedge1, triangleedge1, _MM_SHUFFLE(3, 0, 2, 1)), triangleedge2)); \ + switch(cullface) \ + { \ + case GL_BACK: \ + if (_mm_ucomilt_ss(trianglenormal, _mm_setzero_ps())) \ + continue; \ + break; \ + case GL_FRONT: \ + if (_mm_ucomigt_ss(trianglenormal, _mm_setzero_ps())) \ + continue; \ + break; \ + } + +#define CLIPPEDVERTEXLERP(k,p1, p2) \ + clipfrac[p1] = _mm_set1_ps(clipdist[p1] / (clipdist[p1] - clipdist[p2])); \ + { \ + __m128 v1 = _mm_load_ps(&arrays[e[p1]*4]), v2 = _mm_load_ps(&arrays[e[p2]*4]); \ + DPSOFTRAST_PROJECTVERTEX(screen[k], _mm_add_ps(v1, _mm_mul_ps(_mm_sub_ps(v2, v1), clipfrac[p1])), viewportcenter, viewportscale); \ + } +#define CLIPPEDVERTEXCOPY(k,p1) \ + screen[k] = _mm_load_ps(&screencoord4f[e[p1]*4]); + +#define GENATTRIBCOPY(attrib, p1) \ + attrib = _mm_load_ps(&arrays[e[p1]*4]); +#define GENATTRIBLERP(attrib, p1, p2) \ + { \ + __m128 v1 = _mm_load_ps(&arrays[e[p1]*4]), v2 = _mm_load_ps(&arrays[e[p2]*4]); \ + attrib = _mm_add_ps(v1, _mm_mul_ps(_mm_sub_ps(v2, v1), clipfrac[p1])); \ + } +#define GENATTRIBS(attrib0, attrib1, attrib2) \ + switch(clipcase) \ + { \ + default: \ + case 0: GENATTRIBCOPY(attrib0, 0); GENATTRIBCOPY(attrib1, 1); GENATTRIBCOPY(attrib2, 2); break; \ + case 1: GENATTRIBCOPY(attrib0, 0); GENATTRIBCOPY(attrib1, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ + case 2: GENATTRIBCOPY(attrib0, 0); GENATTRIBLERP(attrib1, 0, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ + case 3: GENATTRIBCOPY(attrib0, 0); GENATTRIBLERP(attrib1, 0, 1); GENATTRIBLERP(attrib2, 2, 0); break; \ + case 4: GENATTRIBLERP(attrib0, 0, 1); GENATTRIBCOPY(attrib1, 1); GENATTRIBCOPY(attrib2, 2); break; \ + case 5: GENATTRIBLERP(attrib0, 0, 1); GENATTRIBCOPY(attrib1, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ + case 6: GENATTRIBLERP(attrib0, 1, 2); GENATTRIBCOPY(attrib1, 2); GENATTRIBLERP(attrib2, 2, 0); break; \ + } + + if (! clipped) + goto notclipped; + + // calculate distance from nearplane + clipdist[0] = arrays[e[0]*4+2] + arrays[e[0]*4+3]; + clipdist[1] = arrays[e[1]*4+2] + arrays[e[1]*4+3]; + clipdist[2] = arrays[e[2]*4+2] + arrays[e[2]*4+3]; + if (clipdist[0] >= 0.0f) + { + if (clipdist[1] >= 0.0f) + { + if (clipdist[2] >= 0.0f) + { + notclipped: + // triangle is entirely in front of nearplane + CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXCOPY(2,2); + SKIPBACKFACE; + numpoints = 3; + clipcase = 0; + } + else + { + CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXLERP(2,1,2); CLIPPEDVERTEXLERP(3,2,0); + SKIPBACKFACE; + numpoints = 4; + clipcase = 1; + } + } + else + { + if (clipdist[2] >= 0.0f) + { + CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXLERP(1,0,1); CLIPPEDVERTEXLERP(2,1,2); CLIPPEDVERTEXCOPY(3,2); + SKIPBACKFACE; + numpoints = 4; + clipcase = 2; + } + else + { + CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXLERP(1,0,1); CLIPPEDVERTEXLERP(2,2,0); + SKIPBACKFACE; + numpoints = 3; + clipcase = 3; + } + } + } + else if (clipdist[1] >= 0.0f) + { + if (clipdist[2] >= 0.0f) + { + CLIPPEDVERTEXLERP(0,0,1); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXCOPY(2,2); CLIPPEDVERTEXLERP(3,2,0); + SKIPBACKFACE; + numpoints = 4; + clipcase = 4; + } + else + { + CLIPPEDVERTEXLERP(0,0,1); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXLERP(2,1,2); + SKIPBACKFACE; + numpoints = 3; + clipcase = 5; + } + } + else if (clipdist[2] >= 0.0f) + { + CLIPPEDVERTEXLERP(0,1,2); CLIPPEDVERTEXCOPY(1,2); CLIPPEDVERTEXLERP(2,2,0); + SKIPBACKFACE; + numpoints = 3; + clipcase = 6; + } + else continue; // triangle is entirely behind nearplane + + { + // calculate integer y coords for triangle points + __m128i screeni = _mm_packs_epi32(_mm_cvttps_epi32(_mm_movelh_ps(screen[0], screen[1])), _mm_cvttps_epi32(_mm_movelh_ps(screen[2], numpoints > 3 ? screen[3] : screen[2]))), + screenir = _mm_shuffle_epi32(screeni, _MM_SHUFFLE(1, 0, 3, 2)), + screenmin = _mm_min_epi16(screeni, screenir), + screenmax = _mm_max_epi16(screeni, screenir); + screenmin = _mm_min_epi16(screenmin, _mm_shufflelo_epi16(screenmin, _MM_SHUFFLE(1, 0, 3, 2))); + screenmax = _mm_max_epi16(screenmax, _mm_shufflelo_epi16(screenmax, _MM_SHUFFLE(1, 0, 3, 2))); + screenmin = _mm_max_epi16(screenmin, fbmin); + screenmax = _mm_min_epi16(screenmax, fbmax); + // skip offscreen triangles + if (_mm_cvtsi128_si32(_mm_cmplt_epi16(screenmax, screenmin))) + continue; + starty = _mm_extract_epi16(screenmin, 1); + endy = _mm_extract_epi16(screenmax, 1)+1; + if (starty >= maxy1 && endy <= miny2) + continue; + screeny = _mm_srai_epi32(screeni, 16); + } + + triangle = &thread->triangles[thread->numtriangles]; + + // calculate attribute plans for triangle data... + // okay, this triangle is going to produce spans, we'd better project + // the interpolants now (this is what gives perspective texturing), + // this consists of simply multiplying all arrays by the W coord + // (which is basically 1/Z), which will be undone per-pixel + // (multiplying by Z again) to get the perspective-correct array + // values + { + __m128 attribuvslope, attribuxslope, attribuyslope, attribvxslope, attribvyslope, attriborigin, attribedge1, attribedge2, attribxslope, attribyslope, w0, w1, w2, x1, y1; + __m128 mipedgescale, mipdensity; + attribuvslope = _mm_div_ps(_mm_movelh_ps(triangleedge1, triangleedge2), _mm_shuffle_ps(trianglenormal, trianglenormal, _MM_SHUFFLE(0, 0, 0, 0))); + attribuxslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(3, 3, 3, 3)); + attribuyslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(2, 2, 2, 2)); + attribvxslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(1, 1, 1, 1)); + attribvyslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(0, 0, 0, 0)); + w0 = _mm_shuffle_ps(screen[0], screen[0], _MM_SHUFFLE(3, 3, 3, 3)); + w1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(3, 3, 3, 3)); + w2 = _mm_shuffle_ps(screen[2], screen[2], _MM_SHUFFLE(3, 3, 3, 3)); + attribedge1 = _mm_sub_ss(w0, w1); + attribedge2 = _mm_sub_ss(w2, w1); + attribxslope = _mm_sub_ss(_mm_mul_ss(attribuxslope, attribedge1), _mm_mul_ss(attribvxslope, attribedge2)); + attribyslope = _mm_sub_ss(_mm_mul_ss(attribvyslope, attribedge2), _mm_mul_ss(attribuyslope, attribedge1)); + x1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(0, 0, 0, 0)); + y1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(1, 1, 1, 1)); + attriborigin = _mm_sub_ss(w1, _mm_add_ss(_mm_mul_ss(attribxslope, x1), _mm_mul_ss(attribyslope, y1))); + _mm_store_ss(&triangle->w[0], attribxslope); + _mm_store_ss(&triangle->w[1], attribyslope); + _mm_store_ss(&triangle->w[2], attriborigin); + + clip0origin = 0; + clip0slope = 0; + clip0dir = 0; + if(thread->fb_clipplane[0] || thread->fb_clipplane[1] || thread->fb_clipplane[2]) + { + float cliporigin, clipxslope, clipyslope; + attriborigin = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(2, 2, 2, 2)); + attribedge1 = _mm_sub_ss(_mm_shuffle_ps(screen[0], screen[0], _MM_SHUFFLE(2, 2, 2, 2)), attriborigin); + attribedge2 = _mm_sub_ss(_mm_shuffle_ps(screen[2], screen[2], _MM_SHUFFLE(2, 2, 2, 2)), attriborigin); + attribxslope = _mm_sub_ss(_mm_mul_ss(attribuxslope, attribedge1), _mm_mul_ss(attribvxslope, attribedge2)); + attribyslope = _mm_sub_ss(_mm_mul_ss(attribvyslope, attribedge2), _mm_mul_ss(attribuyslope, attribedge1)); + attriborigin = _mm_sub_ss(attriborigin, _mm_add_ss(_mm_mul_ss(attribxslope, x1), _mm_mul_ss(attribyslope, y1))); + cliporigin = _mm_cvtss_f32(attriborigin)*thread->fb_clipplane[2] + thread->fb_clipplane[3]; + clipxslope = thread->fb_clipplane[0] + _mm_cvtss_f32(attribxslope)*thread->fb_clipplane[2]; + clipyslope = thread->fb_clipplane[1] + _mm_cvtss_f32(attribyslope)*thread->fb_clipplane[2]; + if(clipxslope != 0) + { + clip0origin = -cliporigin/clipxslope; + clip0slope = -clipyslope/clipxslope; + clip0dir = clipxslope > 0 ? 1 : -1; + } + else if(clipyslope > 0) + { + clip0origin = dpsoftrast.fb_width*floor(cliporigin/clipyslope); + clip0slope = dpsoftrast.fb_width; + clip0dir = -1; + } + else if(clipyslope < 0) + { + clip0origin = dpsoftrast.fb_width*ceil(cliporigin/clipyslope); + clip0slope = -dpsoftrast.fb_width; + clip0dir = -1; + } + else if(clip0origin < 0) continue; + } + + mipedgescale = _mm_setzero_ps(); + for (j = 0;j < DPSOFTRAST_ARRAY_TOTAL; j++) + { + __m128 attrib0, attrib1, attrib2; + k = DPSOFTRAST_ShaderModeTable[thread->shader_mode].arrays[j]; + if (k >= DPSOFTRAST_ARRAY_TOTAL) + break; + arrays += numvertices*4; + GENATTRIBS(attrib0, attrib1, attrib2); + attriborigin = _mm_mul_ps(attrib1, w1); + attribedge1 = _mm_sub_ps(_mm_mul_ps(attrib0, w0), attriborigin); + attribedge2 = _mm_sub_ps(_mm_mul_ps(attrib2, w2), attriborigin); + attribxslope = _mm_sub_ps(_mm_mul_ps(attribuxslope, attribedge1), _mm_mul_ps(attribvxslope, attribedge2)); + attribyslope = _mm_sub_ps(_mm_mul_ps(attribvyslope, attribedge2), _mm_mul_ps(attribuyslope, attribedge1)); + attriborigin = _mm_sub_ps(attriborigin, _mm_add_ps(_mm_mul_ps(attribxslope, x1), _mm_mul_ps(attribyslope, y1))); + _mm_storeu_ps(triangle->attribs[k][0], attribxslope); + _mm_storeu_ps(triangle->attribs[k][1], attribyslope); + _mm_storeu_ps(triangle->attribs[k][2], attriborigin); + if (k == DPSOFTRAST_ShaderModeTable[thread->shader_mode].lodarrayindex) + { + mipedgescale = _mm_movelh_ps(triangleedge1, triangleedge2); + mipedgescale = _mm_mul_ps(mipedgescale, mipedgescale); + mipedgescale = _mm_rsqrt_ps(_mm_add_ps(mipedgescale, _mm_shuffle_ps(mipedgescale, mipedgescale, _MM_SHUFFLE(2, 3, 0, 1)))); + mipedgescale = _mm_mul_ps(_mm_sub_ps(_mm_movelh_ps(attrib0, attrib2), _mm_movelh_ps(attrib1, attrib1)), mipedgescale); + } + } + + memset(triangle->mip, 0, sizeof(triangle->mip)); + for (j = 0;j < DPSOFTRAST_MAXTEXTUREUNITS;j++) + { + int texunit = DPSOFTRAST_ShaderModeTable[thread->shader_mode].texunits[j]; + if (texunit >= DPSOFTRAST_MAXTEXTUREUNITS) + break; + texture = thread->texbound[texunit]; + if (texture && texture->filter > DPSOFTRAST_TEXTURE_FILTER_LINEAR) + { + mipdensity = _mm_mul_ps(mipedgescale, _mm_cvtepi32_ps(_mm_shuffle_epi32(_mm_loadl_epi64((const __m128i *)&texture->mipmap[0][2]), _MM_SHUFFLE(1, 0, 1, 0)))); + mipdensity = _mm_mul_ps(mipdensity, mipdensity); + mipdensity = _mm_add_ps(mipdensity, _mm_shuffle_ps(mipdensity, mipdensity, _MM_SHUFFLE(2, 3, 0, 1))); + mipdensity = _mm_min_ss(mipdensity, _mm_shuffle_ps(mipdensity, mipdensity, _MM_SHUFFLE(2, 2, 2, 2))); + // this will be multiplied in the texturing routine by the texture resolution + y = _mm_cvtss_si32(mipdensity); + if (y > 0) + { + y = (int)(log((float)y)*0.5f/M_LN2); + if (y > texture->mipmaps - 1) + y = texture->mipmaps - 1; + triangle->mip[texunit] = y; + } + } + } + } + + for (y = starty, bandy = min(endy, maxy1); y < endy; bandy = min(endy, maxy2), y = max(y, miny2)) + for (; y < bandy;) + { + __m128 xcoords, xslope; + __m128i ycc = _mm_cmpgt_epi32(_mm_set1_epi32(y), screeny); + int yccmask = _mm_movemask_epi8(ycc); + int edge0p, edge0n, edge1p, edge1n; + int nexty; + float w, wslope; + float clip0; + if (numpoints == 4) + { + switch(yccmask) + { + default: + case 0xFFFF: /*0000*/ y = endy; continue; + case 0xFFF0: /*1000*/ edge0p = 3;edge0n = 0;edge1p = 1;edge1n = 0;break; + case 0xFF0F: /*0100*/ edge0p = 0;edge0n = 1;edge1p = 2;edge1n = 1;break; + case 0xFF00: /*1100*/ edge0p = 3;edge0n = 0;edge1p = 2;edge1n = 1;break; + case 0xF0FF: /*0010*/ edge0p = 1;edge0n = 2;edge1p = 3;edge1n = 2;break; + case 0xF0F0: /*1010*/ edge0p = 1;edge0n = 2;edge1p = 3;edge1n = 2;break; // concave - nonsense + case 0xF00F: /*0110*/ edge0p = 0;edge0n = 1;edge1p = 3;edge1n = 2;break; + case 0xF000: /*1110*/ edge0p = 3;edge0n = 0;edge1p = 3;edge1n = 2;break; + case 0x0FFF: /*0001*/ edge0p = 2;edge0n = 3;edge1p = 0;edge1n = 3;break; + case 0x0FF0: /*1001*/ edge0p = 2;edge0n = 3;edge1p = 1;edge1n = 0;break; + case 0x0F0F: /*0101*/ edge0p = 2;edge0n = 3;edge1p = 2;edge1n = 1;break; // concave - nonsense + case 0x0F00: /*1101*/ edge0p = 2;edge0n = 3;edge1p = 2;edge1n = 1;break; + case 0x00FF: /*0011*/ edge0p = 1;edge0n = 2;edge1p = 0;edge1n = 3;break; + case 0x00F0: /*1011*/ edge0p = 1;edge0n = 2;edge1p = 1;edge1n = 0;break; + case 0x000F: /*0111*/ edge0p = 0;edge0n = 1;edge1p = 0;edge1n = 3;break; + case 0x0000: /*1111*/ y++; continue; + } + } + else + { + switch(yccmask) + { + default: + case 0xFFFF: /*000*/ y = endy; continue; + case 0xFFF0: /*100*/ edge0p = 2;edge0n = 0;edge1p = 1;edge1n = 0;break; + case 0xFF0F: /*010*/ edge0p = 0;edge0n = 1;edge1p = 2;edge1n = 1;break; + case 0xFF00: /*110*/ edge0p = 2;edge0n = 0;edge1p = 2;edge1n = 1;break; + case 0x00FF: /*001*/ edge0p = 1;edge0n = 2;edge1p = 0;edge1n = 2;break; + case 0x00F0: /*101*/ edge0p = 1;edge0n = 2;edge1p = 1;edge1n = 0;break; + case 0x000F: /*011*/ edge0p = 0;edge0n = 1;edge1p = 0;edge1n = 2;break; + case 0x0000: /*111*/ y++; continue; + } + } + ycc = _mm_max_epi16(_mm_srli_epi16(ycc, 1), screeny); + ycc = _mm_min_epi16(ycc, _mm_shuffle_epi32(ycc, _MM_SHUFFLE(1, 0, 3, 2))); + ycc = _mm_min_epi16(ycc, _mm_shuffle_epi32(ycc, _MM_SHUFFLE(2, 3, 0, 1))); + nexty = _mm_extract_epi16(ycc, 0); + if (nexty >= bandy) nexty = bandy-1; + xslope = _mm_sub_ps(_mm_movelh_ps(screen[edge0n], screen[edge1n]), _mm_movelh_ps(screen[edge0p], screen[edge1p])); + xslope = _mm_div_ps(xslope, _mm_shuffle_ps(xslope, xslope, _MM_SHUFFLE(3, 3, 1, 1))); + xcoords = _mm_add_ps(_mm_movelh_ps(screen[edge0p], screen[edge1p]), + _mm_mul_ps(xslope, _mm_sub_ps(_mm_set1_ps(y), _mm_shuffle_ps(screen[edge0p], screen[edge1p], _MM_SHUFFLE(1, 1, 1, 1))))); + xcoords = _mm_add_ps(xcoords, _mm_set1_ps(0.5f)); + if (_mm_ucomigt_ss(xcoords, _mm_shuffle_ps(xcoords, xcoords, _MM_SHUFFLE(1, 0, 3, 2)))) + { + xcoords = _mm_shuffle_ps(xcoords, xcoords, _MM_SHUFFLE(1, 0, 3, 2)); + xslope = _mm_shuffle_ps(xslope, xslope, _MM_SHUFFLE(1, 0, 3, 2)); + } + clip0 = clip0origin + (y+0.5f)*clip0slope + 0.5f; + for(; y <= nexty; y++, xcoords = _mm_add_ps(xcoords, xslope), clip0 += clip0slope) + { + int startx, endx, offset; + startx = _mm_cvtss_si32(xcoords); + endx = _mm_cvtss_si32(_mm_movehl_ps(xcoords, xcoords)); + if (startx < minx) startx = minx; + if (endx > maxx) endx = maxx; + if (startx >= endx) continue; + + if (clip0dir) + { + if (clip0dir > 0) + { + if (startx < clip0) + { + if(endx <= clip0) continue; + startx = (int)clip0; + } + } + else if (endx > clip0) + { + if(startx >= clip0) continue; + endx = (int)clip0; + } + } + + for (offset = startx; offset < endx;offset += DPSOFTRAST_DRAW_MAXSPANLENGTH) + { + DPSOFTRAST_State_Span *span = &thread->spans[thread->numspans]; + span->triangle = thread->numtriangles; + span->x = offset; + span->y = y; + span->startx = 0; + span->endx = min(endx - offset, DPSOFTRAST_DRAW_MAXSPANLENGTH); + if (span->startx >= span->endx) + continue; + wslope = triangle->w[0]; + w = triangle->w[2] + span->x*wslope + span->y*triangle->w[1]; + span->depthslope = (int)(wslope*DPSOFTRAST_DEPTHSCALE); + span->depthbase = (int)(w*DPSOFTRAST_DEPTHSCALE - DPSOFTRAST_DEPTHOFFSET*(thread->polygonoffset[1] + fabs(wslope)*thread->polygonoffset[0])); + if (++thread->numspans >= DPSOFTRAST_DRAW_MAXSPANS) + DPSOFTRAST_Draw_ProcessSpans(thread); + } + } + } + + if (++thread->numtriangles >= DPSOFTRAST_DRAW_MAXTRIANGLES) + { + DPSOFTRAST_Draw_ProcessSpans(thread); + thread->numtriangles = 0; + } + } + + if (!ATOMIC_DECREMENT(command->refcount)) + { + if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) + MM_FREE(command->arrays); + } + + if (thread->numspans > 0 || thread->numtriangles > 0) + { + DPSOFTRAST_Draw_ProcessSpans(thread); + thread->numtriangles = 0; + } +#endif +} + +static DPSOFTRAST_Command_Draw *DPSOFTRAST_Draw_AllocateDrawCommand(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s) +{ + int i; + int j; + int commandsize = DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw)); + int datasize = 2*numvertices*sizeof(float[4]); + DPSOFTRAST_Command_Draw *command; + unsigned char *data; + for (i = 0; i < DPSOFTRAST_ARRAY_TOTAL; i++) + { + j = DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].arrays[i]; + if (j >= DPSOFTRAST_ARRAY_TOTAL) + break; + datasize += numvertices*sizeof(float[4]); + } + if (element3s) + datasize += numtriangles*sizeof(unsigned short[3]); + else if (element3i) + datasize += numtriangles*sizeof(int[3]); + datasize = DPSOFTRAST_ALIGNCOMMAND(datasize); + if (commandsize + datasize > DPSOFTRAST_DRAW_MAXCOMMANDSIZE) + { + command = (DPSOFTRAST_Command_Draw *) DPSOFTRAST_AllocateCommand(DPSOFTRAST_OPCODE_Draw, commandsize); + data = (unsigned char *)MM_CALLOC(datasize, 1); + } + else + { + command = (DPSOFTRAST_Command_Draw *) DPSOFTRAST_AllocateCommand(DPSOFTRAST_OPCODE_Draw, commandsize + datasize); + data = (unsigned char *)command + commandsize; + } + command->firstvertex = firstvertex; + command->numvertices = numvertices; + command->numtriangles = numtriangles; + command->arrays = (float *)data; + memset(dpsoftrast.post_array4f, 0, sizeof(dpsoftrast.post_array4f)); + dpsoftrast.firstvertex = firstvertex; + dpsoftrast.numvertices = numvertices; + dpsoftrast.screencoord4f = (float *)data; + data += numvertices*sizeof(float[4]); + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION] = (float *)data; + data += numvertices*sizeof(float[4]); + for (i = 0; i < DPSOFTRAST_ARRAY_TOTAL; i++) + { + j = DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].arrays[i]; + if (j >= DPSOFTRAST_ARRAY_TOTAL) + break; + dpsoftrast.post_array4f[j] = (float *)data; + data += numvertices*sizeof(float[4]); + } + command->element3i = NULL; + command->element3s = NULL; + if (element3s) + { + command->element3s = (unsigned short *)data; + memcpy(command->element3s, element3s, numtriangles*sizeof(unsigned short[3])); + } + else if (element3i) + { + command->element3i = (int *)data; + memcpy(command->element3i, element3i, numtriangles*sizeof(int[3])); + } + return command; +} + +void DPSOFTRAST_DrawTriangles(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s) +{ + DPSOFTRAST_Command_Draw *command = DPSOFTRAST_Draw_AllocateDrawCommand(firstvertex, numvertices, numtriangles, element3i, element3s); + DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].Vertex(); + command->starty = bound(0, dpsoftrast.drawstarty, dpsoftrast.fb_height); + command->endy = bound(0, dpsoftrast.drawendy, dpsoftrast.fb_height); + if (command->starty >= command->endy) + { + if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) + MM_FREE(command->arrays); + DPSOFTRAST_UndoCommand(command->commandsize); + return; + } + command->clipped = dpsoftrast.drawclipped; + command->refcount = dpsoftrast.numthreads; + + if (dpsoftrast.usethreads) + { + int i; + DPSOFTRAST_Draw_SyncCommands(); + for (i = 0; i < dpsoftrast.numthreads; i++) + { + DPSOFTRAST_State_Thread *thread = &dpsoftrast.threads[i]; + if (((command->starty < thread->maxy1 && command->endy > thread->miny1) || (command->starty < thread->maxy2 && command->endy > thread->miny2)) && thread->starving) + Thread_CondSignal(thread->drawcond); + } + } + else + { + DPSOFTRAST_Draw_FlushThreads(); + } +} + +DEFCOMMAND(23, SetRenderTargets, int width; int height;); +static void DPSOFTRAST_Interpret_SetRenderTargets(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_SetRenderTargets *command) +{ + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_SetRenderTargets(int width, int height, unsigned int *depthpixels, unsigned int *colorpixels0, unsigned int *colorpixels1, unsigned int *colorpixels2, unsigned int *colorpixels3) +{ + DPSOFTRAST_Command_SetRenderTargets *command; + if (width != dpsoftrast.fb_width || height != dpsoftrast.fb_height || depthpixels != dpsoftrast.fb_depthpixels || + colorpixels0 != dpsoftrast.fb_colorpixels[0] || colorpixels1 != dpsoftrast.fb_colorpixels[1] || + colorpixels2 != dpsoftrast.fb_colorpixels[2] || colorpixels3 != dpsoftrast.fb_colorpixels[3]) + DPSOFTRAST_Flush(); + dpsoftrast.fb_width = width; + dpsoftrast.fb_height = height; + dpsoftrast.fb_depthpixels = depthpixels; + dpsoftrast.fb_colorpixels[0] = colorpixels0; + dpsoftrast.fb_colorpixels[1] = colorpixels1; + dpsoftrast.fb_colorpixels[2] = colorpixels2; + dpsoftrast.fb_colorpixels[3] = colorpixels3; + DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); + command = DPSOFTRAST_ALLOCATECOMMAND(SetRenderTargets); + command->width = width; + command->height = height; +} + +static void DPSOFTRAST_Draw_InterpretCommands(DPSOFTRAST_State_Thread *thread, int endoffset) +{ + int commandoffset = thread->commandoffset; + while (commandoffset != endoffset) + { + DPSOFTRAST_Command *command = (DPSOFTRAST_Command *)&dpsoftrast.commandpool.commands[commandoffset]; + switch (command->opcode) + { +#define INTERPCOMMAND(name) \ + case DPSOFTRAST_OPCODE_##name : \ + DPSOFTRAST_Interpret_##name (thread, (DPSOFTRAST_Command_##name *)command); \ + commandoffset += DPSOFTRAST_ALIGNCOMMAND(sizeof( DPSOFTRAST_Command_##name )); \ + if (commandoffset >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) \ + commandoffset = 0; \ + break; + INTERPCOMMAND(Viewport) + INTERPCOMMAND(ClearColor) + INTERPCOMMAND(ClearDepth) + INTERPCOMMAND(ColorMask) + INTERPCOMMAND(DepthTest) + INTERPCOMMAND(ScissorTest) + INTERPCOMMAND(Scissor) + INTERPCOMMAND(BlendFunc) + INTERPCOMMAND(BlendSubtract) + INTERPCOMMAND(DepthMask) + INTERPCOMMAND(DepthFunc) + INTERPCOMMAND(DepthRange) + INTERPCOMMAND(PolygonOffset) + INTERPCOMMAND(CullFace) + INTERPCOMMAND(SetTexture) + INTERPCOMMAND(SetShader) + INTERPCOMMAND(Uniform4f) + INTERPCOMMAND(UniformMatrix4f) + INTERPCOMMAND(Uniform1i) + INTERPCOMMAND(SetRenderTargets) + INTERPCOMMAND(ClipPlane) + + case DPSOFTRAST_OPCODE_Draw: + DPSOFTRAST_Interpret_Draw(thread, (DPSOFTRAST_Command_Draw *)command); + commandoffset += command->commandsize; + if (commandoffset >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) + commandoffset = 0; + thread->commandoffset = commandoffset; + break; + + case DPSOFTRAST_OPCODE_Reset: + commandoffset = 0; + break; + } + } + thread->commandoffset = commandoffset; +} + +static int DPSOFTRAST_Draw_Thread(void *data) +{ + DPSOFTRAST_State_Thread *thread = (DPSOFTRAST_State_Thread *)data; + while(thread->index >= 0) + { + if (thread->commandoffset != dpsoftrast.drawcommand) + { + DPSOFTRAST_Draw_InterpretCommands(thread, dpsoftrast.drawcommand); + } + else + { + Thread_LockMutex(thread->drawmutex); + if (thread->commandoffset == dpsoftrast.drawcommand && thread->index >= 0) + { + if (thread->waiting) Thread_CondSignal(thread->waitcond); + thread->starving = true; + Thread_CondWait(thread->drawcond, thread->drawmutex); + thread->starving = false; + } + Thread_UnlockMutex(thread->drawmutex); + } + } + return 0; +} + +static void DPSOFTRAST_Draw_FlushThreads(void) +{ + DPSOFTRAST_State_Thread *thread; + int i; + DPSOFTRAST_Draw_SyncCommands(); + if (dpsoftrast.usethreads) + { + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + if (thread->commandoffset != dpsoftrast.drawcommand) + { + Thread_LockMutex(thread->drawmutex); + if (thread->commandoffset != dpsoftrast.drawcommand && thread->starving) + Thread_CondSignal(thread->drawcond); + Thread_UnlockMutex(thread->drawmutex); + } + } + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + if (thread->commandoffset != dpsoftrast.drawcommand) + { + Thread_LockMutex(thread->drawmutex); + if (thread->commandoffset != dpsoftrast.drawcommand) + { + thread->waiting = true; + Thread_CondWait(thread->waitcond, thread->drawmutex); + thread->waiting = false; + } + Thread_UnlockMutex(thread->drawmutex); + } + } + } + else + { + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + if (thread->commandoffset != dpsoftrast.drawcommand) + DPSOFTRAST_Draw_InterpretCommands(thread, dpsoftrast.drawcommand); + } + } + dpsoftrast.commandpool.usedcommands = 0; +} + +void DPSOFTRAST_Flush(void) +{ + DPSOFTRAST_Draw_FlushThreads(); +} + +void DPSOFTRAST_Finish(void) +{ + DPSOFTRAST_Flush(); +} + +int DPSOFTRAST_Init(int width, int height, int numthreads, int interlace, unsigned int *colorpixels, unsigned int *depthpixels) +{ + int i; + union + { + int i; + unsigned char b[4]; + } + u; + u.i = 1; + memset(&dpsoftrast, 0, sizeof(dpsoftrast)); + dpsoftrast.bigendian = u.b[3]; + dpsoftrast.fb_width = width; + dpsoftrast.fb_height = height; + dpsoftrast.fb_depthpixels = depthpixels; + dpsoftrast.fb_colorpixels[0] = colorpixels; + dpsoftrast.fb_colorpixels[1] = NULL; + dpsoftrast.fb_colorpixels[1] = NULL; + dpsoftrast.fb_colorpixels[1] = NULL; + dpsoftrast.viewport[0] = 0; + dpsoftrast.viewport[1] = 0; + dpsoftrast.viewport[2] = dpsoftrast.fb_width; + dpsoftrast.viewport[3] = dpsoftrast.fb_height; + DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); + dpsoftrast.texture_firstfree = 1; + dpsoftrast.texture_end = 1; + dpsoftrast.texture_max = 0; + dpsoftrast.color[0] = 1; + dpsoftrast.color[1] = 1; + dpsoftrast.color[2] = 1; + dpsoftrast.color[3] = 1; + dpsoftrast.usethreads = numthreads > 0 && Thread_HasThreads(); + dpsoftrast.interlace = dpsoftrast.usethreads ? bound(0, interlace, 1) : 0; + dpsoftrast.numthreads = dpsoftrast.usethreads ? bound(1, numthreads, 64) : 1; + dpsoftrast.threads = (DPSOFTRAST_State_Thread *)MM_CALLOC(dpsoftrast.numthreads, sizeof(DPSOFTRAST_State_Thread)); + for (i = 0; i < dpsoftrast.numthreads; i++) + { + DPSOFTRAST_State_Thread *thread = &dpsoftrast.threads[i]; + thread->index = i; + thread->cullface = GL_BACK; + thread->colormask[0] = 1; + thread->colormask[1] = 1; + thread->colormask[2] = 1; + thread->colormask[3] = 1; + thread->blendfunc[0] = GL_ONE; + thread->blendfunc[1] = GL_ZERO; + thread->depthmask = true; + thread->depthtest = true; + thread->depthfunc = GL_LEQUAL; + thread->scissortest = false; + thread->viewport[0] = 0; + thread->viewport[1] = 0; + thread->viewport[2] = dpsoftrast.fb_width; + thread->viewport[3] = dpsoftrast.fb_height; + thread->scissor[0] = 0; + thread->scissor[1] = 0; + thread->scissor[2] = dpsoftrast.fb_width; + thread->scissor[3] = dpsoftrast.fb_height; + thread->depthrange[0] = 0; + thread->depthrange[1] = 1; + thread->polygonoffset[0] = 0; + thread->polygonoffset[1] = 0; + thread->clipplane[0] = 0; + thread->clipplane[1] = 0; + thread->clipplane[2] = 0; + thread->clipplane[3] = 1; + + thread->numspans = 0; + thread->numtriangles = 0; + thread->commandoffset = 0; + thread->waiting = false; + thread->starving = false; + + thread->validate = -1; + DPSOFTRAST_Validate(thread, -1); + + if (dpsoftrast.usethreads) + { + thread->waitcond = Thread_CreateCond(); + thread->drawcond = Thread_CreateCond(); + thread->drawmutex = Thread_CreateMutex(); + thread->thread = Thread_CreateThread(DPSOFTRAST_Draw_Thread, thread); + } + } + return 0; +} + +void DPSOFTRAST_Shutdown(void) +{ + int i; + if (dpsoftrast.usethreads && dpsoftrast.numthreads > 0) + { + DPSOFTRAST_State_Thread *thread; + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + Thread_LockMutex(thread->drawmutex); + thread->index = -1; + Thread_CondSignal(thread->drawcond); + Thread_UnlockMutex(thread->drawmutex); + Thread_WaitThread(thread->thread, 0); + Thread_DestroyCond(thread->waitcond); + Thread_DestroyCond(thread->drawcond); + Thread_DestroyMutex(thread->drawmutex); + } + } + for (i = 0;i < dpsoftrast.texture_end;i++) + if (dpsoftrast.texture[i].bytes) + MM_FREE(dpsoftrast.texture[i].bytes); + if (dpsoftrast.texture) + free(dpsoftrast.texture); + if (dpsoftrast.threads) + MM_FREE(dpsoftrast.threads); + memset(&dpsoftrast, 0, sizeof(dpsoftrast)); +} + diff --git a/misc/source/darkplaces-src/dpsoftrast.h b/misc/source/darkplaces-src/dpsoftrast.h new file mode 100644 index 00000000..3f267a2e --- /dev/null +++ b/misc/source/darkplaces-src/dpsoftrast.h @@ -0,0 +1,326 @@ + +#ifndef DPSOFTRAST_H +#define DPSOFTRAST_H + +#include + +#define DPSOFTRAST_MAXMIPMAPS 16 +#define DPSOFTRAST_TEXTURE_MAXSIZE (1<<(DPSOFTRAST_MAXMIPMAPS - 1)) +#define DPSOFTRAST_MAXTEXTUREUNITS 16 +#define DPSOFTRAST_MAXTEXCOORDARRAYS 8 + +// type of pixels in texture (some of these are converted to BGRA8 on update) +#define DPSOFTRAST_TEXTURE_FORMAT_BGRA8 0 +#define DPSOFTRAST_TEXTURE_FORMAT_DEPTH 1 +#define DPSOFTRAST_TEXTURE_FORMAT_RGBA8 2 +#define DPSOFTRAST_TEXTURE_FORMAT_ALPHA8 3 +#define DPSOFTRAST_TEXTURE_FORMAT_RGBA16F 4 +#define DPSOFTRAST_TEXTURE_FORMAT_RGBA32F 5 +#define DPSOFTRAST_TEXTURE_FORMAT_COMPAREMASK 0x0F + +// modifier flags for texture (can not be changed after creation) +#define DPSOFTRAST_TEXTURE_FLAG_MIPMAP 0x10 +#define DPSOFTRAST_TEXTURE_FLAG_CUBEMAP 0x20 +#define DPSOFTRAST_TEXTURE_FLAG_USEALPHA 0x40 +#define DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE 0x80 + +typedef enum DPSOFTRAST_TEXTURE_FILTER_e +{ + DPSOFTRAST_TEXTURE_FILTER_NEAREST = 0, + DPSOFTRAST_TEXTURE_FILTER_LINEAR = 1, + DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE = 2, + DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE = 3, +} +DPSOFTRAST_TEXTURE_FILTER; + +int DPSOFTRAST_Init(int width, int height, int numthreads, int interlace, unsigned int *colorpixels, unsigned int *depthpixels); +void DPSOFTRAST_Shutdown(void); +void DPSOFTRAST_Flush(void); +void DPSOFTRAST_Finish(void); + +int DPSOFTRAST_Texture_New(int flags, int width, int height, int depth); +void DPSOFTRAST_Texture_Free(int index); +void DPSOFTRAST_Texture_UpdatePartial(int index, int mip, const unsigned char *pixels, int blockx, int blocky, int blockwidth, int blockheight); +void DPSOFTRAST_Texture_UpdateFull(int index, const unsigned char *pixels); +int DPSOFTRAST_Texture_GetWidth(int index, int mip); +int DPSOFTRAST_Texture_GetHeight(int index, int mip); +int DPSOFTRAST_Texture_GetDepth(int index, int mip); +unsigned char *DPSOFTRAST_Texture_GetPixelPointer(int index, int mip); +void DPSOFTRAST_Texture_Filter(int index, DPSOFTRAST_TEXTURE_FILTER filter); + +void DPSOFTRAST_SetRenderTargets(int width, int height, unsigned int *depthpixels, unsigned int *colorpixels0, unsigned int *colorpixels1, unsigned int *colorpixels2, unsigned int *colorpixels3); +void DPSOFTRAST_Viewport(int x, int y, int width, int height); +void DPSOFTRAST_ClearColor(float r, float g, float b, float a); +void DPSOFTRAST_ClearDepth(float d); +void DPSOFTRAST_ColorMask(int r, int g, int b, int a); +void DPSOFTRAST_DepthTest(int enable); +void DPSOFTRAST_ScissorTest(int enable); +void DPSOFTRAST_Scissor(float x, float y, float width, float height); +void DPSOFTRAST_ClipPlane(float x, float y, float z, float w); + +void DPSOFTRAST_BlendFunc(int smodulate, int dmodulate); +void DPSOFTRAST_BlendSubtract(int enable); +void DPSOFTRAST_DepthMask(int enable); +void DPSOFTRAST_DepthFunc(int comparemode); +void DPSOFTRAST_DepthRange(float range0, float range1); +void DPSOFTRAST_PolygonOffset(float alongnormal, float intoview); +void DPSOFTRAST_CullFace(int mode); +void DPSOFTRAST_Color4f(float r, float g, float b, float a); +void DPSOFTRAST_GetPixelsBGRA(int blockx, int blocky, int blockwidth, int blockheight, unsigned char *outpixels); +void DPSOFTRAST_CopyRectangleToTexture(int index, int mip, int tx, int ty, int sx, int sy, int width, int height); +void DPSOFTRAST_SetTexture(int unitnum, int index); + +void DPSOFTRAST_SetVertexPointer(const float *vertex3f, size_t stride); +void DPSOFTRAST_SetColorPointer(const float *color4f, size_t stride); +void DPSOFTRAST_SetColorPointer4ub(const unsigned char *color4ub, size_t stride); +void DPSOFTRAST_SetTexCoordPointer(int unitnum, int numcomponents, size_t stride, const float *texcoordf); + +typedef enum gl20_texunit_e +{ + // postprocess shaders, and generic shaders: + GL20TU_FIRST = 0, + GL20TU_SECOND = 1, + GL20TU_GAMMARAMPS = 2, + // standard material properties + GL20TU_NORMAL = 0, + GL20TU_COLOR = 1, + GL20TU_GLOSS = 2, + GL20TU_GLOW = 3, + // material properties for a second material + GL20TU_SECONDARY_NORMAL = 4, + GL20TU_SECONDARY_COLOR = 5, + GL20TU_SECONDARY_GLOSS = 6, + GL20TU_SECONDARY_GLOW = 7, + // material properties for a colormapped material + // conflicts with secondary material + GL20TU_PANTS = 4, + GL20TU_SHIRT = 7, + // fog fade in the distance + GL20TU_FOGMASK = 8, + // compiled ambient lightmap and deluxemap + GL20TU_LIGHTMAP = 9, + GL20TU_DELUXEMAP = 10, + // refraction, used by water shaders + GL20TU_REFRACTION = 3, + // reflection, used by water shaders, also with normal material rendering + // conflicts with secondary material + GL20TU_REFLECTION = 7, + // rtlight attenuation (distance fade) and cubemap filter (projection texturing) + // conflicts with lightmap/deluxemap + GL20TU_ATTENUATION = 9, + GL20TU_CUBE = 10, + GL20TU_SHADOWMAP2D = 15, + GL20TU_CUBEPROJECTION = 12, + // rtlight prepass data (screenspace depth and normalmap) + GL20TU_SCREENDEPTH = 13, + GL20TU_SCREENNORMALMAP = 14, + // lightmap prepass data (screenspace diffuse and specular from lights) + GL20TU_SCREENDIFFUSE = 11, + GL20TU_SCREENSPECULAR = 12, + // fake reflections + GL20TU_REFLECTMASK = 5, + GL20TU_REFLECTCUBE = 6, + GL20TU_FOGHEIGHTTEXTURE = 14 +} +gl20_texunit; + +typedef enum glsl_attrib_e +{ + GLSLATTRIB_POSITION = 0, + GLSLATTRIB_COLOR = 1, + GLSLATTRIB_TEXCOORD0 = 2, + GLSLATTRIB_TEXCOORD1 = 3, + GLSLATTRIB_TEXCOORD2 = 4, + GLSLATTRIB_TEXCOORD3 = 5, + GLSLATTRIB_TEXCOORD4 = 6, + GLSLATTRIB_TEXCOORD5 = 7, + GLSLATTRIB_TEXCOORD6 = 8, + GLSLATTRIB_TEXCOORD7 = 9, +} +glsl_attrib; + +// this enum selects which of the glslshadermodeinfo entries should be used +typedef enum shadermode_e +{ + SHADERMODE_GENERIC, ///< (particles/HUD/etc) vertex color, optionally multiplied by one texture + SHADERMODE_POSTPROCESS, ///< postprocessing shader (r_glsl_postprocess) + SHADERMODE_DEPTH_OR_SHADOW, ///< (depthfirst/shadows) vertex shader only + SHADERMODE_FLATCOLOR, ///< (lightmap) modulate texture by uniform color (q1bsp, q3bsp) + SHADERMODE_VERTEXCOLOR, ///< (lightmap) modulate texture by vertex colors (q3bsp) + SHADERMODE_LIGHTMAP, ///< (lightmap) modulate texture by lightmap texture (q1bsp, q3bsp) + SHADERMODE_FAKELIGHT, ///< (fakelight) modulate texture by "fake" lighting (no lightmaps, no nothing) + SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE, ///< (lightmap) use directional pixel shading from texture containing modelspace light directions (q3bsp deluxemap) + SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE, ///< (lightmap) use directional pixel shading from texture containing tangentspace light directions (q1bsp deluxemap) + SHADERMODE_LIGHTDIRECTION, ///< (lightmap) use directional pixel shading from fixed light direction (q3bsp) + SHADERMODE_LIGHTSOURCE, ///< (lightsource) use directional pixel shading from light source (rtlight) + SHADERMODE_REFRACTION, ///< refract background (the material is rendered normally after this pass) + SHADERMODE_WATER, ///< refract background and reflection (the material is rendered normally after this pass) + SHADERMODE_SHOWDEPTH, ///< (debugging) renders depth as color + SHADERMODE_DEFERREDGEOMETRY, ///< (deferred) render material properties to screenspace geometry buffers + SHADERMODE_DEFERREDLIGHTSOURCE, ///< (deferred) use directional pixel shading from light source (rtlight) on screenspace geometry buffers + SHADERMODE_COUNT +} +shadermode_t; + +typedef enum shaderpermutation_e +{ + SHADERPERMUTATION_DIFFUSE = 1<<0, ///< (lightsource) whether to use directional shading + SHADERPERMUTATION_VERTEXTEXTUREBLEND = 1<<1, ///< indicates this is a two-layer material blend based on vertex alpha (q3bsp) + SHADERPERMUTATION_VIEWTINT = 1<<2, ///< view tint (postprocessing only), use vertex colors (generic only) + SHADERPERMUTATION_COLORMAPPING = 1<<3, ///< indicates this is a colormapped skin + SHADERPERMUTATION_SATURATION = 1<<4, ///< saturation (postprocessing only) + SHADERPERMUTATION_FOGINSIDE = 1<<5, ///< tint the color by fog color or black if using additive blend mode + SHADERPERMUTATION_FOGOUTSIDE = 1<<6, ///< tint the color by fog color or black if using additive blend mode + SHADERPERMUTATION_FOGHEIGHTTEXTURE = 1<<7, ///< fog color and density determined by texture mapped on vertical axis + SHADERPERMUTATION_FOGALPHAHACK = 1<<8, ///< fog color and density determined by texture mapped on vertical axis + SHADERPERMUTATION_GAMMARAMPS = 1<<9, ///< gamma (postprocessing only) + SHADERPERMUTATION_CUBEFILTER = 1<<10, ///< (lightsource) use cubemap light filter + SHADERPERMUTATION_GLOW = 1<<11, ///< (lightmap) blend in an additive glow texture + SHADERPERMUTATION_BLOOM = 1<<12, ///< bloom (postprocessing only) + SHADERPERMUTATION_SPECULAR = 1<<13, ///< (lightsource or deluxemapping) render specular effects + SHADERPERMUTATION_POSTPROCESSING = 1<<14, ///< user defined postprocessing (postprocessing only) + SHADERPERMUTATION_REFLECTION = 1<<15, ///< normalmap-perturbed reflection of the scene infront of the surface, preformed as an overlay on the surface + SHADERPERMUTATION_OFFSETMAPPING = 1<<16, ///< adjust texcoords to roughly simulate a displacement mapped surface + SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING = 1<<17, ///< adjust texcoords to accurately simulate a displacement mapped surface (requires OFFSETMAPPING to also be set!) + SHADERPERMUTATION_SHADOWMAP2D = 1<<18, ///< (lightsource) use shadowmap texture as light filter + SHADERPERMUTATION_SHADOWMAPPCF = 1<<19, ///< (lightsource) use percentage closer filtering on shadowmap test results + SHADERPERMUTATION_SHADOWMAPPCF2 = 1<<20, ///< (lightsource) use higher quality percentage closer filtering on shadowmap test results + SHADERPERMUTATION_SHADOWSAMPLER = 1<<21, ///< (lightsource) use hardware shadowmap test + SHADERPERMUTATION_SHADOWMAPVSDCT = 1<<22, ///< (lightsource) use virtual shadow depth cube texture for shadowmap indexing + SHADERPERMUTATION_SHADOWMAPORTHO = 1<<23, ///< (lightsource) use orthographic shadowmap projection + SHADERPERMUTATION_DEFERREDLIGHTMAP = 1<<24, ///< (lightmap) read Texture_ScreenDiffuse/Specular textures and add them on top of lightmapping + SHADERPERMUTATION_ALPHAKILL = 1<<25, ///< (deferredgeometry) discard pixel if diffuse texture alpha below 0.5 + SHADERPERMUTATION_REFLECTCUBE = 1<<26, ///< fake reflections using global cubemap (not HDRI light probe) + SHADERPERMUTATION_NORMALMAPSCROLLBLEND = 1<<27, ///< (water) counter-direction normalmaps scrolling + SHADERPERMUTATION_BOUNCEGRID = 1<<28, ///< (lightmap) use Texture_BounceGrid as an additional source of ambient light + SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL = 1<<29, ///< (lightmap) use 16-component pixels in bouncegrid texture for directional lighting rather than standard 4-component + SHADERPERMUTATION_TRIPPY = 1<<30, ///< use trippy vertex shader effect + SHADERPERMUTATION_LIMIT = 1<<31, ///< size of permutations array + SHADERPERMUTATION_COUNT = 31 ///< size of shaderpermutationinfo array +} +shaderpermutation_t; + +typedef enum DPSOFTRAST_UNIFORM_e +{ + DPSOFTRAST_UNIFORM_Texture_First, + DPSOFTRAST_UNIFORM_Texture_Second, + DPSOFTRAST_UNIFORM_Texture_GammaRamps, + DPSOFTRAST_UNIFORM_Texture_Normal, + DPSOFTRAST_UNIFORM_Texture_Color, + DPSOFTRAST_UNIFORM_Texture_Gloss, + DPSOFTRAST_UNIFORM_Texture_Glow, + DPSOFTRAST_UNIFORM_Texture_SecondaryNormal, + DPSOFTRAST_UNIFORM_Texture_SecondaryColor, + DPSOFTRAST_UNIFORM_Texture_SecondaryGloss, + DPSOFTRAST_UNIFORM_Texture_SecondaryGlow, + DPSOFTRAST_UNIFORM_Texture_Pants, + DPSOFTRAST_UNIFORM_Texture_Shirt, + DPSOFTRAST_UNIFORM_Texture_FogHeightTexture, + DPSOFTRAST_UNIFORM_Texture_FogMask, + DPSOFTRAST_UNIFORM_Texture_Lightmap, + DPSOFTRAST_UNIFORM_Texture_Deluxemap, + DPSOFTRAST_UNIFORM_Texture_Attenuation, + DPSOFTRAST_UNIFORM_Texture_Cube, + DPSOFTRAST_UNIFORM_Texture_Refraction, + DPSOFTRAST_UNIFORM_Texture_Reflection, + DPSOFTRAST_UNIFORM_Texture_ShadowMap2D, + DPSOFTRAST_UNIFORM_Texture_CubeProjection, + DPSOFTRAST_UNIFORM_Texture_ScreenDepth, + DPSOFTRAST_UNIFORM_Texture_ScreenNormalMap, + DPSOFTRAST_UNIFORM_Texture_ScreenDiffuse, + DPSOFTRAST_UNIFORM_Texture_ScreenSpecular, + DPSOFTRAST_UNIFORM_Texture_ReflectMask, + DPSOFTRAST_UNIFORM_Texture_ReflectCube, + DPSOFTRAST_UNIFORM_Alpha, + DPSOFTRAST_UNIFORM_BloomBlur_Parameters, + DPSOFTRAST_UNIFORM_ClientTime, + DPSOFTRAST_UNIFORM_Color_Ambient, + DPSOFTRAST_UNIFORM_Color_Diffuse, + DPSOFTRAST_UNIFORM_Color_Specular, + DPSOFTRAST_UNIFORM_Color_Glow, + DPSOFTRAST_UNIFORM_Color_Pants, + DPSOFTRAST_UNIFORM_Color_Shirt, + DPSOFTRAST_UNIFORM_DeferredColor_Ambient, + DPSOFTRAST_UNIFORM_DeferredColor_Diffuse, + DPSOFTRAST_UNIFORM_DeferredColor_Specular, + DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, + DPSOFTRAST_UNIFORM_DeferredMod_Specular, + DPSOFTRAST_UNIFORM_DistortScaleRefractReflect, + DPSOFTRAST_UNIFORM_EyePosition, + DPSOFTRAST_UNIFORM_FogColor, + DPSOFTRAST_UNIFORM_FogHeightFade, + DPSOFTRAST_UNIFORM_FogPlane, + DPSOFTRAST_UNIFORM_FogPlaneViewDist, + DPSOFTRAST_UNIFORM_FogRangeRecip, + DPSOFTRAST_UNIFORM_LightColor, + DPSOFTRAST_UNIFORM_LightDir, + DPSOFTRAST_UNIFORM_LightPosition, + DPSOFTRAST_UNIFORM_OffsetMapping_ScaleSteps, + DPSOFTRAST_UNIFORM_PixelSize, + DPSOFTRAST_UNIFORM_ReflectColor, + DPSOFTRAST_UNIFORM_ReflectFactor, + DPSOFTRAST_UNIFORM_ReflectOffset, + DPSOFTRAST_UNIFORM_RefractColor, + DPSOFTRAST_UNIFORM_Saturation, + DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect, + DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect, + DPSOFTRAST_UNIFORM_ScreenToDepth, + DPSOFTRAST_UNIFORM_ShadowMap_Parameters, + DPSOFTRAST_UNIFORM_ShadowMap_TextureScale, + DPSOFTRAST_UNIFORM_SpecularPower, + DPSOFTRAST_UNIFORM_UserVec1, + DPSOFTRAST_UNIFORM_UserVec2, + DPSOFTRAST_UNIFORM_UserVec3, + DPSOFTRAST_UNIFORM_UserVec4, + DPSOFTRAST_UNIFORM_ViewTintColor, + DPSOFTRAST_UNIFORM_ViewToLightM1, + DPSOFTRAST_UNIFORM_ViewToLightM2, + DPSOFTRAST_UNIFORM_ViewToLightM3, + DPSOFTRAST_UNIFORM_ViewToLightM4, + DPSOFTRAST_UNIFORM_ModelToLightM1, + DPSOFTRAST_UNIFORM_ModelToLightM2, + DPSOFTRAST_UNIFORM_ModelToLightM3, + DPSOFTRAST_UNIFORM_ModelToLightM4, + DPSOFTRAST_UNIFORM_TexMatrixM1, + DPSOFTRAST_UNIFORM_TexMatrixM2, + DPSOFTRAST_UNIFORM_TexMatrixM3, + DPSOFTRAST_UNIFORM_TexMatrixM4, + DPSOFTRAST_UNIFORM_BackgroundTexMatrixM1, + DPSOFTRAST_UNIFORM_BackgroundTexMatrixM2, + DPSOFTRAST_UNIFORM_BackgroundTexMatrixM3, + DPSOFTRAST_UNIFORM_BackgroundTexMatrixM4, + DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, + DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM2, + DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM3, + DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM4, + DPSOFTRAST_UNIFORM_ModelViewMatrixM1, + DPSOFTRAST_UNIFORM_ModelViewMatrixM2, + DPSOFTRAST_UNIFORM_ModelViewMatrixM3, + DPSOFTRAST_UNIFORM_ModelViewMatrixM4, + DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, + DPSOFTRAST_UNIFORM_ModelToReflectCubeM1, + DPSOFTRAST_UNIFORM_ModelToReflectCubeM2, + DPSOFTRAST_UNIFORM_ModelToReflectCubeM3, + DPSOFTRAST_UNIFORM_ModelToReflectCubeM4, + DPSOFTRAST_UNIFORM_ShadowMapMatrixM1, + DPSOFTRAST_UNIFORM_ShadowMapMatrixM2, + DPSOFTRAST_UNIFORM_ShadowMapMatrixM3, + DPSOFTRAST_UNIFORM_ShadowMapMatrixM4, + DPSOFTRAST_UNIFORM_BloomColorSubtract, + DPSOFTRAST_UNIFORM_NormalmapScrollBlend, + DPSOFTRAST_UNIFORM_TOTAL +} +DPSOFTRAST_UNIFORM; + +void DPSOFTRAST_SetShader(int mode, int permutation, int exactspecularmath); +#define DPSOFTRAST_Uniform1f(index, v0) DPSOFTRAST_Uniform4f(index, v0, 0, 0, 0) +#define DPSOFTRAST_Uniform2f(index, v0, v1) DPSOFTRAST_Uniform4f(index, v0, v1, 0, 0) +#define DPSOFTRAST_Uniform3f(index, v0, v1, v2) DPSOFTRAST_Uniform4f(index, v0, v1, v2, 0) +void DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM index, float v0, float v1, float v2, float v3); +void DPSOFTRAST_Uniform4fv(DPSOFTRAST_UNIFORM index, const float *v); +void DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM index, int arraysize, int transpose, const float *v); +void DPSOFTRAST_Uniform1i(DPSOFTRAST_UNIFORM index, int i0); + +void DPSOFTRAST_DrawTriangles(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s); + +#endif // DPSOFTRAST_H diff --git a/misc/source/darkplaces-src/dpvsimpledecode.c b/misc/source/darkplaces-src/dpvsimpledecode.c new file mode 100644 index 00000000..2cf5e82f --- /dev/null +++ b/misc/source/darkplaces-src/dpvsimpledecode.c @@ -0,0 +1,646 @@ +#include "quakedef.h" +#include "dpvsimpledecode.h" + +#define HZREADERROR_OK 0 +#define HZREADERROR_EOF 1 +#define HZREADERROR_MALLOCFAILED 2 + +//#define HZREADBLOCKSIZE 16000 +#define HZREADBLOCKSIZE 1048576 + +typedef struct hz_bitstream_read_s +{ + qfile_t *file; + int endoffile; +} +hz_bitstream_read_t; + +typedef struct hz_bitstream_readblock_s +{ + struct hz_bitstream_readblock_s *next; + unsigned int size; + unsigned char data[HZREADBLOCKSIZE]; +} +hz_bitstream_readblock_t; + +typedef struct hz_bitstream_readblocks_s +{ + hz_bitstream_readblock_t *blocks; + hz_bitstream_readblock_t *current; + unsigned int position; + unsigned int store; + int count; +} +hz_bitstream_readblocks_t; + +hz_bitstream_read_t *hz_bitstream_read_open(char *filename) +{ + qfile_t *file; + hz_bitstream_read_t *stream; + if ((file = FS_OpenVirtualFile(filename, false))) + { + stream = (hz_bitstream_read_t *)Z_Malloc(sizeof(hz_bitstream_read_t)); + memset(stream, 0, sizeof(*stream)); + stream->file = file; + return stream; + } + else + return NULL; +} + +void hz_bitstream_read_close(hz_bitstream_read_t *stream) +{ + if (stream) + { + FS_Close(stream->file); + Z_Free(stream); + } +} + +hz_bitstream_readblocks_t *hz_bitstream_read_blocks_new(void) +{ + hz_bitstream_readblocks_t *blocks; + blocks = (hz_bitstream_readblocks_t *)Z_Malloc(sizeof(hz_bitstream_readblocks_t)); + if (blocks == NULL) + return NULL; + memset(blocks, 0, sizeof(hz_bitstream_readblocks_t)); + return blocks; +} + +void hz_bitstream_read_blocks_free(hz_bitstream_readblocks_t *blocks) +{ + hz_bitstream_readblock_t *b, *n; + if (blocks == NULL) + return; + for (b = blocks->blocks;b;b = n) + { + n = b->next; + Z_Free(b); + } + Z_Free(blocks); +} + +void hz_bitstream_read_flushbits(hz_bitstream_readblocks_t *blocks) +{ + blocks->store = 0; + blocks->count = 0; +} + +int hz_bitstream_read_blocks_read(hz_bitstream_readblocks_t *blocks, hz_bitstream_read_t *stream, unsigned int size) +{ + int s; + hz_bitstream_readblock_t *b, *p; + s = size; + p = NULL; + b = blocks->blocks; + while (s > 0) + { + if (b == NULL) + { + b = (hz_bitstream_readblock_t *)Z_Malloc(sizeof(hz_bitstream_readblock_t)); + if (b == NULL) + return HZREADERROR_MALLOCFAILED; + b->next = NULL; + b->size = 0; + if (p != NULL) + p->next = b; + else + blocks->blocks = b; + } + if (s > HZREADBLOCKSIZE) + b->size = HZREADBLOCKSIZE; + else + b->size = s; + s -= b->size; + if (FS_Read(stream->file, b->data, b->size) != (fs_offset_t)b->size) + { + stream->endoffile = 1; + break; + } + p = b; + b = b->next; + } + while (b) + { + b->size = 0; + b = b->next; + } + blocks->current = blocks->blocks; + blocks->position = 0; + hz_bitstream_read_flushbits(blocks); + if (stream->endoffile) + return HZREADERROR_EOF; + return HZREADERROR_OK; +} + +unsigned int hz_bitstream_read_blocks_getbyte(hz_bitstream_readblocks_t *blocks) +{ + while (blocks->current != NULL && blocks->position >= blocks->current->size) + { + blocks->position = 0; + blocks->current = blocks->current->next; + } + if (blocks->current == NULL) + return 0; + return blocks->current->data[blocks->position++]; +} + +int hz_bitstream_read_bit(hz_bitstream_readblocks_t *blocks) +{ + if (!blocks->count) + { + blocks->count += 8; + blocks->store <<= 8; + blocks->store |= hz_bitstream_read_blocks_getbyte(blocks) & 0xFF; + } + blocks->count--; + return (blocks->store >> blocks->count) & 1; +} + +unsigned int hz_bitstream_read_bits(hz_bitstream_readblocks_t *blocks, int size) +{ + unsigned int num = 0; + // we can only handle about 24 bits at a time safely + // (there might be up to 7 bits more than we need in the bit store) + if (size > 24) + { + size -= 8; + num |= hz_bitstream_read_bits(blocks, 8) << size; + } + while (blocks->count < size) + { + blocks->count += 8; + blocks->store <<= 8; + blocks->store |= hz_bitstream_read_blocks_getbyte(blocks) & 0xFF; + } + blocks->count -= size; + num |= (blocks->store >> blocks->count) & ((1 << size) - 1); + return num; +} + +unsigned int hz_bitstream_read_byte(hz_bitstream_readblocks_t *blocks) +{ + return hz_bitstream_read_blocks_getbyte(blocks); +} + +unsigned int hz_bitstream_read_short(hz_bitstream_readblocks_t *blocks) +{ + return (hz_bitstream_read_byte(blocks) << 8) + | (hz_bitstream_read_byte(blocks)); +} + +unsigned int hz_bitstream_read_int(hz_bitstream_readblocks_t *blocks) +{ + return (hz_bitstream_read_byte(blocks) << 24) + | (hz_bitstream_read_byte(blocks) << 16) + | (hz_bitstream_read_byte(blocks) << 8) + | (hz_bitstream_read_byte(blocks)); +} + +void hz_bitstream_read_bytes(hz_bitstream_readblocks_t *blocks, void *outdata, unsigned int size) +{ + unsigned char *out; + out = (unsigned char *)outdata; + while (size--) + *out++ = hz_bitstream_read_byte(blocks); +} + +#define BLOCKSIZE 8 + +typedef struct dpvsimpledecodestream_s +{ + hz_bitstream_read_t *bitstream; + hz_bitstream_readblocks_t *framedatablocks; + + int error; + + double info_framerate; + unsigned int info_frames; + + unsigned int info_imagewidth; + unsigned int info_imageheight; + unsigned int info_imagebpp; + unsigned int info_imageRloss; + unsigned int info_imageRmask; + unsigned int info_imageRshift; + unsigned int info_imageGloss; + unsigned int info_imageGmask; + unsigned int info_imageGshift; + unsigned int info_imageBloss; + unsigned int info_imageBmask; + unsigned int info_imageBshift; + unsigned int info_imagesize; + + // current video frame (needed because of delta compression) + int videoframenum; + // current video frame data (needed because of delta compression) + unsigned int *videopixels; + + // channel the sound file is being played on + int sndchan; +} +dpvsimpledecodestream_t; + +static int dpvsimpledecode_setpixelformat(dpvsimpledecodestream_t *s, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel) +{ + int Rshift, Rbits, Gshift, Gbits, Bshift, Bbits; + if (!Rmask) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDRMASK; + return s->error; + } + if (!Gmask) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDGMASK; + return s->error; + } + if (!Bmask) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDBMASK; + return s->error; + } + if (Rmask & Gmask || Rmask & Bmask || Gmask & Bmask) + { + s->error = DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP; + return s->error; + } + switch (bytesperpixel) + { + case 2: + if ((Rmask | Gmask | Bmask) > 65536) + { + s->error = DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP; + return s->error; + } + break; + case 4: + break; + default: + s->error = DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP; + return s->error; + } + for (Rshift = 0;!(Rmask & 1);Rshift++, Rmask >>= 1); + for (Gshift = 0;!(Gmask & 1);Gshift++, Gmask >>= 1); + for (Bshift = 0;!(Bmask & 1);Bshift++, Bmask >>= 1); + if (((Rmask + 1) & Rmask) != 0) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDRMASK; + return s->error; + } + if (((Gmask + 1) & Gmask) != 0) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDGMASK; + return s->error; + } + if (((Bmask + 1) & Bmask) != 0) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDBMASK; + return s->error; + } + for (Rbits = 0;Rmask & 1;Rbits++, Rmask >>= 1); + for (Gbits = 0;Gmask & 1;Gbits++, Gmask >>= 1); + for (Bbits = 0;Bmask & 1;Bbits++, Bmask >>= 1); + if (Rbits > 8) + { + Rshift += (Rbits - 8); + Rbits = 8; + } + if (Gbits > 8) + { + Gshift += (Gbits - 8); + Gbits = 8; + } + if (Bbits > 8) + { + Bshift += (Bbits - 8); + Bbits = 8; + } + s->info_imagebpp = bytesperpixel; + s->info_imageRloss = 16 + (8 - Rbits); + s->info_imageGloss = 8 + (8 - Gbits); + s->info_imageBloss = 0 + (8 - Bbits); + s->info_imageRmask = (1 << Rbits) - 1; + s->info_imageGmask = (1 << Gbits) - 1; + s->info_imageBmask = (1 << Bbits) - 1; + s->info_imageRshift = Rshift; + s->info_imageGshift = Gshift; + s->info_imageBshift = Bshift; + s->info_imagesize = s->info_imagewidth * s->info_imageheight * s->info_imagebpp; + return s->error; +} + +// opening and closing streams + +// opens a stream +void *dpvsimpledecode_open(clvideo_t *video, char *filename, const char **errorstring) +{ + dpvsimpledecodestream_t *s; + char t[8], *wavename; + if (errorstring != NULL) + *errorstring = NULL; + s = (dpvsimpledecodestream_t *)Z_Malloc(sizeof(dpvsimpledecodestream_t)); + if (s != NULL) + { + s->bitstream = hz_bitstream_read_open(filename); + if (s->bitstream != NULL) + { + // check file identification + s->framedatablocks = hz_bitstream_read_blocks_new(); + if (s->framedatablocks != NULL) + { + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 8); + hz_bitstream_read_bytes(s->framedatablocks, t, 8); + if (!memcmp(t, "DPVideo", 8)) + { + // check version number + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 2); + if (hz_bitstream_read_short(s->framedatablocks) == 1) + { + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 12); + s->info_imagewidth = hz_bitstream_read_short(s->framedatablocks); + s->info_imageheight = hz_bitstream_read_short(s->framedatablocks); + s->info_framerate = (double) hz_bitstream_read_int(s->framedatablocks) * (1.0 / 65536.0); + + if (s->info_framerate > 0.0) + { + s->videopixels = (unsigned int *)Z_Malloc(s->info_imagewidth * s->info_imageheight * sizeof(*s->videopixels)); + if (s->videopixels != NULL) + { + size_t namelen; + + namelen = strlen(filename) + 10; + wavename = (char *)Z_Malloc(namelen); + if (wavename) + { + sfx_t* sfx; + + FS_StripExtension(filename, wavename, namelen); + strlcat(wavename, ".wav", namelen); + sfx = S_PrecacheSound (wavename, false, false); + if (sfx != NULL) + s->sndchan = S_StartSound (-1, 0, sfx, vec3_origin, 1.0f, 0); + else + s->sndchan = -1; + Z_Free(wavename); + } + // all is well... + // set the module functions + s->videoframenum = -10000; + video->close = dpvsimpledecode_close; + video->getwidth = dpvsimpledecode_getwidth; + video->getheight = dpvsimpledecode_getheight; + video->getframerate = dpvsimpledecode_getframerate; + video->decodeframe = dpvsimpledecode_video; + + return s; + } + else if (errorstring != NULL) + *errorstring = "unable to allocate video image buffer"; + } + else if (errorstring != NULL) + *errorstring = "error in video info chunk"; + } + else if (errorstring != NULL) + *errorstring = "read error"; + } + else if (errorstring != NULL) + *errorstring = "not a dpvideo file"; + hz_bitstream_read_blocks_free(s->framedatablocks); + } + else if (errorstring != NULL) + *errorstring = "unable to allocate memory for reading buffer"; + hz_bitstream_read_close(s->bitstream); + } + else if (errorstring != NULL) + *errorstring = "unable to open file"; + Z_Free(s); + } + else if (errorstring != NULL) + *errorstring = "unable to allocate memory for stream info structure"; + return NULL; +} + +// closes a stream +void dpvsimpledecode_close(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + if (s == NULL) + return; + if (s->videopixels) + Z_Free(s->videopixels); + if (s->sndchan != -1) + S_StopChannel (s->sndchan, true, true); + if (s->framedatablocks) + hz_bitstream_read_blocks_free(s->framedatablocks); + if (s->bitstream) + hz_bitstream_read_close(s->bitstream); + Z_Free(s); +} + +// utilitarian functions + +// returns the current error number for the stream, and resets the error +// number to DPVSIMPLEDECODEERROR_NONE +// if the supplied string pointer variable is not NULL, it will be set to the +// error message +int dpvsimpledecode_error(void *stream, const char **errorstring) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + int e; + e = s->error; + s->error = 0; + if (errorstring) + { + switch (e) + { + case DPVSIMPLEDECODEERROR_NONE: + *errorstring = "no error"; + break; + case DPVSIMPLEDECODEERROR_EOF: + *errorstring = "end of file reached (this is not an error)"; + break; + case DPVSIMPLEDECODEERROR_READERROR: + *errorstring = "read error (corrupt or incomplete file)"; + break; + case DPVSIMPLEDECODEERROR_SOUNDBUFFERTOOSMALL: + *errorstring = "sound buffer is too small for decoding frame (please allocate it as large as dpvsimpledecode_getneededsoundbufferlength suggests)"; + break; + case DPVSIMPLEDECODEERROR_INVALIDRMASK: + *errorstring = "invalid red bits mask"; + break; + case DPVSIMPLEDECODEERROR_INVALIDGMASK: + *errorstring = "invalid green bits mask"; + break; + case DPVSIMPLEDECODEERROR_INVALIDBMASK: + *errorstring = "invalid blue bits mask"; + break; + case DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP: + *errorstring = "color bit masks overlap"; + break; + case DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP: + *errorstring = "color masks too big for specified bytes per pixel"; + break; + case DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP: + *errorstring = "unsupported bytes per pixel (must be 2 for 16bit, or 4 for 32bit)"; + break; + default: + *errorstring = "unknown error"; + break; + } + } + return e; +} + +// returns the width of the image data +unsigned int dpvsimpledecode_getwidth(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + return s->info_imagewidth; +} + +// returns the height of the image data +unsigned int dpvsimpledecode_getheight(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + return s->info_imageheight; +} + +// returns the framerate of the stream +double dpvsimpledecode_getframerate(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + return s->info_framerate; +} + +static int dpvsimpledecode_convertpixels(dpvsimpledecodestream_t *s, void *imagedata, int imagebytesperrow) +{ + unsigned int a, x, y, width, height; + unsigned int Rloss, Rmask, Rshift, Gloss, Gmask, Gshift, Bloss, Bmask, Bshift; + unsigned int *in; + + width = s->info_imagewidth; + height = s->info_imageheight; + + Rloss = s->info_imageRloss; + Rmask = s->info_imageRmask; + Rshift = s->info_imageRshift; + Gloss = s->info_imageGloss; + Gmask = s->info_imageGmask; + Gshift = s->info_imageGshift; + Bloss = s->info_imageBloss; + Bmask = s->info_imageBmask; + Bshift = s->info_imageBshift; + + in = s->videopixels; + if (s->info_imagebpp == 4) + { + unsigned int *outrow; + for (y = 0;y < height;y++) + { + outrow = (unsigned int *)((unsigned char *)imagedata + y * imagebytesperrow); + for (x = 0;x < width;x++) + { + a = *in++; + outrow[x] = (((a >> Rloss) & Rmask) << Rshift) | (((a >> Gloss) & Gmask) << Gshift) | (((a >> Bloss) & Bmask) << Bshift); + } + } + } + else + { + unsigned short *outrow; + for (y = 0;y < height;y++) + { + outrow = (unsigned short *)((unsigned char *)imagedata + y * imagebytesperrow); + if (Rloss == 19 && Gloss == 10 && Bloss == 3 && Rshift == 11 && Gshift == 5 && Bshift == 0) + { + // optimized + for (x = 0;x < width;x++) + { + a = *in++; + outrow[x] = ((a >> 8) & 0xF800) | ((a >> 5) & 0x07E0) | ((a >> 3) & 0x001F); + } + } + else + { + for (x = 0;x < width;x++) + { + a = *in++; + outrow[x] = (((a >> Rloss) & Rmask) << Rshift) | (((a >> Gloss) & Gmask) << Gshift) | (((a >> Bloss) & Bmask) << Bshift); + } + } + } + } + return s->error; +} + +static int dpvsimpledecode_decompressimage(dpvsimpledecodestream_t *s) +{ + int i, a, b, colors, g, x1, y1, bw, bh, width, height, palettebits; + unsigned int palette[256], *outrow, *out; + g = BLOCKSIZE; + width = s->info_imagewidth; + height = s->info_imageheight; + for (y1 = 0;y1 < height;y1 += g) + { + outrow = s->videopixels + y1 * width; + bh = g; + if (y1 + bh > height) + bh = height - y1; + for (x1 = 0;x1 < width;x1 += g) + { + out = outrow + x1; + bw = g; + if (x1 + bw > width) + bw = width - x1; + if (hz_bitstream_read_bit(s->framedatablocks)) + { + // updated block + palettebits = hz_bitstream_read_bits(s->framedatablocks, 3); + colors = 1 << palettebits; + for (i = 0;i < colors;i++) + palette[i] = hz_bitstream_read_bits(s->framedatablocks, 24); + if (palettebits) + { + for (b = 0;b < bh;b++, out += width) + for (a = 0;a < bw;a++) + out[a] = palette[hz_bitstream_read_bits(s->framedatablocks, palettebits)]; + } + else + { + for (b = 0;b < bh;b++, out += width) + for (a = 0;a < bw;a++) + out[a] = palette[0]; + } + } + } + } + return s->error; +} + +// decodes a video frame to the supplied output pixels +int dpvsimpledecode_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + unsigned int framedatasize; + char t[4]; + s->error = DPVSIMPLEDECODEERROR_NONE; + if (dpvsimpledecode_setpixelformat(s, Rmask, Gmask, Bmask, bytesperpixel)) + return s->error; + + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 8); + hz_bitstream_read_bytes(s->framedatablocks, t, 4); + if (memcmp(t, "VID0", 4)) + { + if (t[0] == 0) + return (s->error = DPVSIMPLEDECODEERROR_EOF); + else + return (s->error = DPVSIMPLEDECODEERROR_READERROR); + } + framedatasize = hz_bitstream_read_int(s->framedatablocks); + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, framedatasize); + if (dpvsimpledecode_decompressimage(s)) + return s->error; + + dpvsimpledecode_convertpixels(s, imagedata, imagebytesperrow); + return s->error; +} diff --git a/misc/source/darkplaces-src/dpvsimpledecode.h b/misc/source/darkplaces-src/dpvsimpledecode.h new file mode 100644 index 00000000..621b001c --- /dev/null +++ b/misc/source/darkplaces-src/dpvsimpledecode.h @@ -0,0 +1,46 @@ + +#ifndef DPVSIMPLEDECODE_H +#define DPVSIMPLEDECODE_H + +#include "cl_video.h" + +#define DPVSIMPLEDECODEERROR_NONE 0 +#define DPVSIMPLEDECODEERROR_EOF 1 +#define DPVSIMPLEDECODEERROR_READERROR 2 +#define DPVSIMPLEDECODEERROR_SOUNDBUFFERTOOSMALL 3 +#define DPVSIMPLEDECODEERROR_INVALIDRMASK 4 +#define DPVSIMPLEDECODEERROR_INVALIDGMASK 5 +#define DPVSIMPLEDECODEERROR_INVALIDBMASK 6 +#define DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP 7 +#define DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP 8 +#define DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP 9 + +// opening and closing streams + +// opens a stream +void *dpvsimpledecode_open(clvideo_t *video, char *filename, const char **errorstring); + +// closes a stream +void dpvsimpledecode_close(void *stream); + +// utilitarian functions + +// returns the current error number for the stream, and resets the error +// number to DPVDECODEERROR_NONE +// if the supplied string pointer variable is not NULL, it will be set to the +// error message +int dpvsimpledecode_error(void *stream, const char **errorstring); + +// returns the width of the image data +unsigned int dpvsimpledecode_getwidth(void *stream); + +// returns the height of the image data +unsigned int dpvsimpledecode_getheight(void *stream); + +// returns the framerate of the stream +double dpvsimpledecode_getframerate(void *stream); + +// decodes a video frame to the supplied output pixels +int dpvsimpledecode_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); + +#endif diff --git a/misc/source/darkplaces-src/draw.h b/misc/source/darkplaces-src/draw.h new file mode 100644 index 00000000..2a5681a9 --- /dev/null +++ b/misc/source/darkplaces-src/draw.h @@ -0,0 +1,201 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// draw.h -- these are the only functions outside the refresh allowed +// to touch the vid buffer + +#ifndef DRAW_H +#define DRAW_H + +// FIXME: move this stuff to cl_screen +typedef struct cachepic_s +{ + // size of pic + int width, height; + // this flag indicates that it should be loaded and unloaded on demand + int autoload; + // texture flags to upload with + int texflags; + // texture may be freed after a while + int lastusedframe; + // renderer texture to use + rtexture_t *tex; + // used for hash lookups + struct cachepic_s *chain; + // flags - CACHEPICFLAG_NEWPIC for example + unsigned int flags; + // has alpha? + qboolean hasalpha; + // name of pic + char name[MAX_QPATH]; +} +cachepic_t; + +typedef enum cachepicflags_e +{ + CACHEPICFLAG_NOTPERSISTENT = 1, + CACHEPICFLAG_QUIET = 2, + CACHEPICFLAG_NOCOMPRESSION = 4, + CACHEPICFLAG_NOCLAMP = 8, + CACHEPICFLAG_NEWPIC = 16 // disables matching texflags check, because a pic created with Draw_NewPic should not be subject to that +} +cachepicflags_t; + +void Draw_Init (void); +void Draw_Frame (void); +cachepic_t *Draw_CachePic_Flags (const char *path, unsigned int cachepicflags); +cachepic_t *Draw_CachePic (const char *path); // standard function with no options, used throughout engine +// create or update a pic's image +cachepic_t *Draw_NewPic(const char *picname, int width, int height, int alpha, unsigned char *pixels); +// free the texture memory used by a pic +void Draw_FreePic(const char *picname); + +// a triangle mesh.. +// each vertex is 3 floats +// each texcoord is 2 floats +// each color is 4 floats +typedef struct drawqueuemesh_s +{ + rtexture_t *texture; + int num_triangles; + int num_vertices; + int *data_element3i; + unsigned short *data_element3s; + float *data_vertex3f; + float *data_texcoord2f; + float *data_color4f; +} +drawqueuemesh_t; + +enum drawqueue_drawflag_e { +DRAWFLAG_NORMAL, +DRAWFLAG_ADDITIVE, +DRAWFLAG_MODULATE, +DRAWFLAG_2XMODULATE, +DRAWFLAG_SCREEN, +DRAWFLAG_NUMFLAGS, +DRAWFLAG_MASK = 0xFF, // ONLY R_BeginPolygon() +DRAWFLAG_MIPMAP = 0x100 // ONLY R_BeginPolygon() +}; + +typedef struct ft2_settings_s +{ + float scale, voffset; + // cvar parameters (only read on loadfont command) + int antialias, hinting; + float outline, blur, shadowx, shadowy, shadowz; +} ft2_settings_t; + +#define MAX_FONT_SIZES 16 +#define MAX_FONT_FALLBACKS 3 +typedef struct dp_font_s +{ + rtexture_t *tex; + float width_of[256]; // width_of[0] == max width of any char; 1.0f is base width (1/16 of texture width); therefore, all widths have to be <= 1 (does not include scale) + float maxwidth; // precalculated max width of the font (includes scale) + char texpath[MAX_QPATH]; + char title[MAX_QPATH]; + + int req_face; // requested face index, usually 0 + float req_sizes[MAX_FONT_SIZES]; // sizes to render the font with, 0 still defaults to 16 (backward compatibility when loadfont doesn't get a size parameter) and -1 = disabled + char fallbacks[MAX_FONT_FALLBACKS][MAX_QPATH]; + int fallback_faces[MAX_FONT_FALLBACKS]; + struct ft2_font_s *ft2; + + ft2_settings_t settings; +} +dp_font_t; + +typedef struct dp_fonts_s +{ + dp_font_t *f; + int maxsize; +} +dp_fonts_t; +extern dp_fonts_t dp_fonts; + +#define MAX_FONTS 16 // fonts at the start +#define FONTS_EXPAND 8 // fonts grow when no free slots +#define FONT_DEFAULT (&dp_fonts.f[0]) // should be fixed width +#define FONT_CONSOLE (&dp_fonts.f[1]) // REALLY should be fixed width (ls!) +#define FONT_SBAR (&dp_fonts.f[2]) // must be fixed width +#define FONT_NOTIFY (&dp_fonts.f[3]) // free +#define FONT_CHAT (&dp_fonts.f[4]) // free +#define FONT_CENTERPRINT (&dp_fonts.f[5]) // free +#define FONT_INFOBAR (&dp_fonts.f[6]) // free +#define FONT_MENU (&dp_fonts.f[7]) // should be fixed width +#define FONT_USER(i) (&dp_fonts.f[8+i]) // userdefined fonts +#define MAX_USERFONTS (dp_fonts.maxsize - 8) + +// shared color tag printing constants +#define STRING_COLOR_TAG '^' +#define STRING_COLOR_DEFAULT 7 +#define STRING_COLOR_DEFAULT_STR "^7" +#define STRING_COLOR_RGB_TAG_CHAR 'x' +#define STRING_COLOR_RGB_TAG "^x" + +// all of these functions will set r_defdef.draw2dstage if not in 2D rendering mode (and of course prepare for 2D rendering in that case) + +// draw an image (or a filled rectangle if pic == NULL) +void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags); +// draw a rotated image +void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags); +// draw a filled rectangle (slightly faster than DrawQ_Pic with pic = NULL) +void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags); +// draw a text string, +// with optional color tag support, +// returns final unclipped x coordinate +// if outcolor is provided the initial color is read from it, and it is updated at the end with the new value at the end of the text (not at the end of the clipped part) +// the color is tinted by the provided base color +// if r_textshadow is not zero, an additional instance of the text is drawn first at an offset with an inverted shade of gray (black text produces a white shadow, brightly colored text produces a black shadow) +extern float DrawQ_Color[4]; +float DrawQ_String(float x, float y, const char *text, size_t maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt); +float DrawQ_String_Scale(float x, float y, const char *text, size_t maxlen, float sizex, float sizey, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt); +float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt); +float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth); +float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); +float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); +// draw a very fancy pic (per corner texcoord/color control), the order is tl, tr, bl, br +void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags); +// draw a triangle mesh +void DrawQ_Mesh(drawqueuemesh_t *mesh, int flags, qboolean hasalpha); +// set the clipping area +void DrawQ_SetClipArea(float x, float y, float width, float height); +// reset the clipping area +void DrawQ_ResetClipArea(void); +// draw a line +void DrawQ_Line(float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags); +// draw a lot of lines +void DrawQ_Lines (float width, int numlines, const float *vertex3f, const float *color4f, int flags); +// draw a line loop +void DrawQ_LineLoop(drawqueuemesh_t *mesh, int flags); +// resets r_refdef.draw2dstage +void DrawQ_Finish(void); +void DrawQ_ProcessDrawFlag(int flags, qboolean alpha); // sets GL_DepthMask and GL_BlendFunc +void DrawQ_RecalcView(void); // use this when changing r_refdef.view.* from e.g. csqc + +rtexture_t *Draw_GetPicTexture(cachepic_t *pic); + +void R_DrawGamma(void); + +extern rtexturepool_t *drawtexturepool; // used by ft2.c + +#endif + diff --git a/misc/source/darkplaces-src/filematch.c b/misc/source/darkplaces-src/filematch.c new file mode 100644 index 00000000..99711676 --- /dev/null +++ b/misc/source/darkplaces-src/filematch.c @@ -0,0 +1,204 @@ + +#ifdef WIN32 +#include +#else +#include +#endif + +#include "quakedef.h" + +// LordHavoc: some portable directory listing code I wrote for lmp2pcx, now used in darkplaces to load id1/*.pak and such... + +int matchpattern(const char *in, const char *pattern, int caseinsensitive) +{ + return matchpattern_with_separator(in, pattern, caseinsensitive, "/\\:", false); +} + +// wildcard_least_one: if true * matches 1 or more characters +// if false * matches 0 or more characters +int matchpattern_with_separator(const char *in, const char *pattern, int caseinsensitive, const char *separators, qboolean wildcard_least_one) +{ + int c1, c2; + while (*pattern) + { + switch (*pattern) + { + case 0: + return 1; // end of pattern + case '?': // match any single character + if (*in == 0 || strchr(separators, *in)) + return 0; // no match + in++; + pattern++; + break; + case '*': // match anything until following string + if(wildcard_least_one) + { + if (*in == 0 || strchr(separators, *in)) + return 0; // no match + in++; + } + pattern++; + while (*in) + { + if (strchr(separators, *in)) + break; + // see if pattern matches at this offset + if (matchpattern_with_separator(in, pattern, caseinsensitive, separators, wildcard_least_one)) + return 1; + // nope, advance to next offset + in++; + } + break; + default: + if (*in != *pattern) + { + if (!caseinsensitive) + return 0; // no match + c1 = *in; + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + c2 = *pattern; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + if (c1 != c2) + return 0; // no match + } + in++; + pattern++; + break; + } + } + if (*in) + return 0; // reached end of pattern but not end of input + return 1; // success +} + +// a little strings system +void stringlistinit(stringlist_t *list) +{ + memset(list, 0, sizeof(*list)); +} + +void stringlistfreecontents(stringlist_t *list) +{ + int i; + for (i = 0;i < list->numstrings;i++) + { + if (list->strings[i]) + Z_Free(list->strings[i]); + list->strings[i] = NULL; + } + list->numstrings = 0; + list->maxstrings = 0; + if (list->strings) + Z_Free(list->strings); + list->strings = NULL; +} + +void stringlistappend(stringlist_t *list, const char *text) +{ + size_t textlen; + char **oldstrings; + + if (list->numstrings >= list->maxstrings) + { + oldstrings = list->strings; + list->maxstrings += 4096; + list->strings = (char **) Z_Malloc(list->maxstrings * sizeof(*list->strings)); + if (list->numstrings) + memcpy(list->strings, oldstrings, list->numstrings * sizeof(*list->strings)); + if (oldstrings) + Z_Free(oldstrings); + } + textlen = strlen(text) + 1; + list->strings[list->numstrings] = (char *) Z_Malloc(textlen); + memcpy(list->strings[list->numstrings], text, textlen); + list->numstrings++; +} + +static int stringlistsort_cmp(const void *a, const void *b) +{ + return strcasecmp(*(const char **)a, *(const char **)b); +} + +void stringlistsort(stringlist_t *list, qboolean uniq) +{ + int i, j; + if(list->numstrings < 1) + return; + qsort(&list->strings[0], list->numstrings, sizeof(list->strings[0]), stringlistsort_cmp); + if(uniq) + { + // i: the item to read + // j: the item last written + for (i = 1, j = 0; i < list->numstrings; ++i) + { + char *save; + if(!strcasecmp(list->strings[i], list->strings[j])) + continue; + ++j; + save = list->strings[j]; + list->strings[j] = list->strings[i]; + list->strings[i] = save; + } + for(i = j+1; i < list->numstrings; ++i) + { + if (list->strings[i]) + Z_Free(list->strings[i]); + } + list->numstrings = j+1; + } +} + +// operating system specific code +static void adddirentry(stringlist_t *list, const char *path, const char *name) +{ + if (strcmp(name, ".") && strcmp(name, "..")) + { + char temp[MAX_OSPATH]; + dpsnprintf( temp, sizeof( temp ), "%s%s", path, name ); + stringlistappend(list, temp); + } +} +#ifdef WIN32 +void listdirectory(stringlist_t *list, const char *basepath, const char *path) +{ + int i; + char pattern[4096], *c; + WIN32_FIND_DATA n_file; + HANDLE hFile; + strlcpy (pattern, basepath, sizeof(pattern)); + strlcat (pattern, path, sizeof (pattern)); + strlcat (pattern, "*", sizeof (pattern)); + // ask for the directory listing handle + hFile = FindFirstFile(pattern, &n_file); + if(hFile == INVALID_HANDLE_VALUE) + return; + do { + adddirentry(list, path, n_file.cFileName); + } while (FindNextFile(hFile, &n_file) != 0); + FindClose(hFile); + + // convert names to lowercase because windows does not care, but pattern matching code often does + for (i = 0;i < list->numstrings;i++) + for (c = list->strings[i];*c;c++) + if (*c >= 'A' && *c <= 'Z') + *c += 'a' - 'A'; +} +#else +void listdirectory(stringlist_t *list, const char *basepath, const char *path) +{ + char fullpath[MAX_OSPATH]; + DIR *dir; + struct dirent *ent; + dpsnprintf(fullpath, sizeof(fullpath), "%s%s", basepath, *path ? path : "./"); + dir = opendir(fullpath); + if (!dir) + return; + while ((ent = readdir(dir))) + adddirentry(list, path, ent->d_name); + closedir(dir); +} +#endif + diff --git a/misc/source/darkplaces-src/fogeval.pl b/misc/source/darkplaces-src/fogeval.pl new file mode 100644 index 00000000..fe4d0523 --- /dev/null +++ b/misc/source/darkplaces-src/fogeval.pl @@ -0,0 +1,147 @@ +use strict; +use warnings; + +# generates the blendfunc flags function in gl_rmain.c + +my %blendfuncs = +( + GL_ONE => sub { (1, 1); }, + GL_ZERO => sub { (0, 0); }, + GL_SRC_COLOR => sub { ($_[0], $_[1]); }, + GL_ONE_MINUS_SRC_COLOR => sub { (1-$_[0], 1-$_[1]); }, + GL_SRC_ALPHA => sub { ($_[1], $_[1]); }, + GL_ONE_MINUS_SRC_ALPHA => sub { (1-$_[1], 1-$_[1]); }, + GL_DST_COLOR => sub { ($_[2], $_[3]); }, + GL_ONE_MINUS_DST_COLOR => sub { (1-$_[2], 1-$_[3]); }, + GL_DST_ALPHA => sub { ($_[3], $_[3]); }, + GL_ONE_MINUS_DST_ALPHA => sub { (1-$_[3], 1-$_[3]); }, +); + +sub evalblend($$$$$$) +{ + my ($fs, $fd, $s, $sa, $d, $da) = @_; + my @fs = $fs->($s, $sa, $d, $da); + my @fd = $fd->($s, $sa, $d, $da); + return ( + $fs[0] * $s + $fd[0] * $d, + $fs[1] * $sa + $fd[1] * $da + ); +} + +sub isinvariant($$$$) +{ + my ($fs, $fd, $s, $sa) = @_; + my ($d, $da) = (rand, rand); + my ($out, $outa) = evalblend $fs, $fd, $s, $sa, $d, $da; + return abs($out - $d) < 0.0001 && abs($outa - $da) < 0.0001; +} + +sub isfogfriendly($$$$$) +{ + my ($fs, $fd, $s, $sa, $foghack) = @_; + my ($d, $da) = (rand, rand); + my $fogamount = rand; + my $fogcolor = rand; + + # compare: + # 1. blend(fog(s), sa, fog(d), da) + # 2. fog(blend(s, sa, d, da)) + + my ($out1, $out1a) = evalblend $fs, $fd, $s + ((defined $foghack ? $foghack eq 'ALPHA' ? $fogcolor*$sa : $foghack : $fogcolor) - $s) * $fogamount, $sa, $d + ($fogcolor - $d) * $fogamount, $da; + my ($out2, $out2a) = evalblend $fs, $fd, $s, $sa, $d, $da; + $out2 = $out2 + ($fogcolor - $out2) * $fogamount; + + return abs($out1 - $out2) < 0.0001 && abs($out1a - $out2a) < 0.0001; +} + +use Carp; +sub decide(&) +{ + my ($sub) = @_; + my $good = 0; + my $bad = 0; + for(;;) + { + for(1..200) + { + my $r = $sub->(); + ++$good if $r; + ++$bad if not $r; + } + #print STDERR "decide: $good vs $bad\n"; + return 1 if $good > $bad + 150; + return 0 if $bad > $good + 150; + warn "No clear decision, continuing to test ($good : $bad)"; + } +} + +#die isfogfriendly $blendfuncs{GL_ONE}, $blendfuncs{GL_ONE}, 1, 0, 0; +# out1 = 0 + fog($d) +# out2 = fog(1 + $d) + +sub willitblend($$) +{ + my ($fs, $fd) = @_; + for my $s(0, 0.25, 0.5, 0.75, 1) + { + for my $sa(0, 0.25, 0.5, 0.75, 1) + { + if(decide { isinvariant($fs, $fd, $s, $sa); }) + { + if(!decide { isinvariant($fs, $fd, 0, $sa); }) + { + return 0; # no colormod possible + } + } + } + } + return 1; +} + +sub willitfog($$) +{ + my ($fs, $fd) = @_; + + FOGHACK: + for my $foghack(undef, 0, 'ALPHA') + { + for my $s(0, 0.25, 0.5, 0.75, 1) + { + for my $sa(0, 0.25, 0.5, 0.75, 1) + { + if(!decide { isfogfriendly($fs, $fd, $s, $sa, $foghack); }) + { + next FOGHACK; + } + } + } + return (1, $foghack); + } + return (0, undef); +} + +print "\tr |= BLENDFUNC_ALLOWS_COLORMOD;\n"; +for my $s(sort keys %blendfuncs) +{ + for my $d(sort keys %blendfuncs) + { + #print STDERR "$s $d\n"; + if(!willitblend $blendfuncs{$s}, $blendfuncs{$d}) + { + print "\tif(src == $s && dst == $d) r &= ~BLENDFUNC_ALLOWS_COLORMOD;\n"; + } + my ($result, $h) = willitfog $blendfuncs{$s}, $blendfuncs{$d}; + if($result) + { + if(defined $h) + { + print "\tif(src == $s && dst == $d) r |= BLENDFUNC_ALLOWS_FOG_HACK$h;\n"; + } + else + { + print "\tif(src == $s && dst == $d) r |= BLENDFUNC_ALLOWS_FOG;\n"; + } + } + } +} + diff --git a/misc/source/darkplaces-src/fractalnoise.c b/misc/source/darkplaces-src/fractalnoise.c new file mode 100644 index 00000000..5d68d196 --- /dev/null +++ b/misc/source/darkplaces-src/fractalnoise.c @@ -0,0 +1,226 @@ + +#include "quakedef.h" + +void fractalnoise(unsigned char *noise, int size, int startgrid) +{ + int x, y, g, g2, amplitude, min, max, size1 = size - 1, sizepower, gridpower; + int *noisebuf; +#define n(x,y) noisebuf[((y)&size1)*size+((x)&size1)] + + for (sizepower = 0;(1 << sizepower) < size;sizepower++); + if (size != (1 << sizepower)) + { + Con_Printf("fractalnoise: size must be power of 2\n"); + return; + } + + for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++); + if (startgrid != (1 << gridpower)) + { + Con_Printf("fractalnoise: grid must be power of 2\n"); + return; + } + + startgrid = bound(0, startgrid, size); + + amplitude = 0xFFFF; // this gets halved before use + noisebuf = (int *)Mem_Alloc(tempmempool, size*size*sizeof(int)); + memset(noisebuf, 0, size*size*sizeof(int)); + + for (g2 = startgrid;g2;g2 >>= 1) + { + // brownian motion (at every smaller level there is random behavior) + amplitude >>= 1; + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + n(x,y) += (rand()&litude); + + g = g2 >> 1; + if (g) + { + // subdivide, diamond-square algorithm (really this has little to do with squares) + // diamond + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + n(x+g,y+g) = (n(x,y) + n(x+g2,y) + n(x,y+g2) + n(x+g2,y+g2)) >> 2; + // square + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + { + n(x+g,y) = (n(x,y) + n(x+g2,y) + n(x+g,y-g) + n(x+g,y+g)) >> 2; + n(x,y+g) = (n(x,y) + n(x,y+g2) + n(x-g,y+g) + n(x+g,y+g)) >> 2; + } + } + } + // find range of noise values + min = max = 0; + for (y = 0;y < size;y++) + for (x = 0;x < size;x++) + { + if (n(x,y) < min) min = n(x,y); + if (n(x,y) > max) max = n(x,y); + } + max -= min; + max++; + // normalize noise and copy to output + for (y = 0;y < size;y++) + for (x = 0;x < size;x++) + *noise++ = (unsigned char) (((n(x,y) - min) * 256) / max); + Mem_Free(noisebuf); +#undef n +} + +// unnormalized, used for explosions mainly, does not allocate/free memory (hence the name quick) +void fractalnoisequick(unsigned char *noise, int size, int startgrid) +{ + int x, y, g, g2, amplitude, size1 = size - 1, sizepower, gridpower; +#define n(x,y) noise[((y)&size1)*size+((x)&size1)] + + for (sizepower = 0;(1 << sizepower) < size;sizepower++); + if (size != (1 << sizepower)) + { + Con_Printf("fractalnoise: size must be power of 2\n"); + return; + } + + for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++); + if (startgrid != (1 << gridpower)) + { + Con_Printf("fractalnoise: grid must be power of 2\n"); + return; + } + + startgrid = bound(0, startgrid, size); + + amplitude = 255; // this gets halved before use + memset(noise, 0, size*size); + + for (g2 = startgrid;g2;g2 >>= 1) + { + // brownian motion (at every smaller level there is random behavior) + amplitude >>= 1; + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + n(x,y) += (rand()&litude); + + g = g2 >> 1; + if (g) + { + // subdivide, diamond-square algorithm (really this has little to do with squares) + // diamond + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + n(x+g,y+g) = (unsigned char) (((int) n(x,y) + (int) n(x+g2,y) + (int) n(x,y+g2) + (int) n(x+g2,y+g2)) >> 2); + // square + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + { + n(x+g,y) = (unsigned char) (((int) n(x,y) + (int) n(x+g2,y) + (int) n(x+g,y-g) + (int) n(x+g,y+g)) >> 2); + n(x,y+g) = (unsigned char) (((int) n(x,y) + (int) n(x,y+g2) + (int) n(x-g,y+g) + (int) n(x+g,y+g)) >> 2); + } + } + } +#undef n +} + +#define NOISE_SIZE 256 +#define NOISE_MASK 255 +float noise4f(float x, float y, float z, float w) +{ + int i; + int index[4][2]; + float frac[4][2]; + float v[4]; + static float noisetable[NOISE_SIZE]; + static int r[NOISE_SIZE]; + // LordHavoc: this is inspired by code I saw in Quake3, however I think my + // version is much cleaner and substantially faster as well + // + // the following changes were made: + // 1. for the permutation indexing (r[] array in this code) I substituted + // the ^ operator (which never overflows) for the original addition and + // masking code, this should not have any effect on quality. + // 2. removed the outermost randomization array lookup. + // (it really wasn't necessary, it's fine if X indexes the array + // directly without permutation indexing) + // 3. reimplemented the blending using frac[] arrays rather than a macro. + // (the original macro read one parameter twice - not good) + // 4. cleaned up the code by using 4 nested loops to make it read nicer + // (but then I unrolled it completely for speed, it still looks nicer). + if (!noisetable[0]) + { + // noisetable is a random-ish series of float values in +/- 1 range + for (i = 0;i < NOISE_SIZE;i++) + noisetable[i] = (rand() / (double)RAND_MAX) * 2 - 1; + // r is a remapping table to make each dimension of the index have different indexing behavior + for (i = 0;i < NOISE_SIZE;i++) + r[i] = (int)(rand() * (double)NOISE_SIZE / ((double)RAND_MAX + 1)) & NOISE_MASK; + // that & is only needed if RAND_MAX is > the range of double, which isn't the case on most platforms + } + frac[0][1] = x - floor(x);index[0][0] = ((int)floor(x)) & NOISE_MASK; + frac[1][1] = y - floor(y);index[1][0] = ((int)floor(y)) & NOISE_MASK; + frac[2][1] = z - floor(z);index[2][0] = ((int)floor(z)) & NOISE_MASK; + frac[3][1] = w - floor(w);index[3][0] = ((int)floor(w)) & NOISE_MASK; + for (i = 0;i < 4;i++) + frac[i][0] = 1 - frac[i][1]; + for (i = 0;i < 4;i++) + index[i][1] = (index[i][0] < NOISE_SIZE - 1) ? (index[i][0] + 1) : 0; +#if 1 + // short version + v[0] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]); + v[1] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]); + v[2] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]); + v[3] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]); + return frac[3][0] * (frac[2][0] * v[0] + frac[2][1] * v[1]) + frac[3][1] * (frac[2][0] * v[2] + frac[2][1] * v[3]); +#elif 1 + // longer version + v[ 0] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]]; + v[ 1] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]; + v[ 2] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]]; + v[ 3] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]; + v[ 4] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]]; + v[ 5] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]; + v[ 6] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]]; + v[ 7] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]; + v[ 8] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]]; + v[ 9] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]; + v[10] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]]; + v[11] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]; + v[12] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]]; + v[13] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]; + v[14] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]]; + v[15] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]; + v[16] = frac[0][0] * v[ 0] + frac[0][1] * v[ 1]; + v[17] = frac[0][0] * v[ 2] + frac[0][1] * v[ 3]; + v[18] = frac[0][0] * v[ 4] + frac[0][1] * v[ 5]; + v[19] = frac[0][0] * v[ 6] + frac[0][1] * v[ 7]; + v[20] = frac[0][0] * v[ 8] + frac[0][1] * v[ 9]; + v[21] = frac[0][0] * v[10] + frac[0][1] * v[11]; + v[22] = frac[0][0] * v[12] + frac[0][1] * v[13]; + v[23] = frac[0][0] * v[14] + frac[0][1] * v[15]; + v[24] = frac[1][0] * v[16] + frac[1][1] * v[17]; + v[25] = frac[1][0] * v[18] + frac[1][1] * v[19]; + v[26] = frac[1][0] * v[20] + frac[1][1] * v[21]; + v[27] = frac[1][0] * v[22] + frac[1][1] * v[23]; + v[28] = frac[2][0] * v[24] + frac[2][1] * v[25]; + v[29] = frac[2][0] * v[26] + frac[2][1] * v[27]; + return frac[3][0] * v[28] + frac[3][1] * v[29]; +#else + // the algorithm... + for (l = 0;l < 2;l++) + { + for (k = 0;k < 2;k++) + { + for (j = 0;j < 2;j++) + { + for (i = 0;i < 2;i++) + v[l][k][j][i] = noisetable[r[r[r[index[l][3]] ^ index[k][2]] ^ index[j][1]] ^ index[i][0]]; + v[l][k][j][2] = frac[0][0] * v[l][k][j][0] + frac[0][1] * v[l][k][j][1]; + } + v[l][k][2][2] = frac[1][0] * v[l][k][0][2] + frac[1][1] * v[l][k][1][2]; + } + v[l][2][2][2] = frac[2][0] * v[l][0][2][2] + frac[2][1] * v[l][1][2][2]; + } + v[2][2][2][2] = frac[3][0] * v[0][2][2][2] + frac[3][1] * v[1][2][2][2]; +#endif +} diff --git a/misc/source/darkplaces-src/fs.c b/misc/source/darkplaces-src/fs.c new file mode 100644 index 00000000..d810ff54 --- /dev/null +++ b/misc/source/darkplaces-src/fs.c @@ -0,0 +1,3944 @@ +/* + DarkPlaces file system + + Copyright (C) 2003-2006 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + +#ifdef __APPLE__ +// include SDL for IPHONEOS code +# include +# if TARGET_OS_IPHONE +# include +# endif +#endif + +#include +#include + +#ifdef WIN32 +# include +# include +# include +#else +# include +# include +# include +#endif + +#include "quakedef.h" + +#include "fs.h" +#include "wad.h" + +// Win32 requires us to add O_BINARY, but the other OSes don't have it +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +// In case the system doesn't support the O_NONBLOCK flag +#ifndef O_NONBLOCK +# define O_NONBLOCK 0 +#endif + +// largefile support for Win32 +#ifdef WIN32 +#undef lseek +# define lseek _lseeki64 +#endif + +#if _MSC_VER >= 1400 +// suppress deprecated warnings +# include +# include +# define read _read +# define write _write +# define close _close +# define unlink _unlink +# define dup _dup +#endif + +/** \page fs File System + +All of Quake's data access is through a hierchal file system, but the contents +of the file system can be transparently merged from several sources. + +The "base directory" is the path to the directory holding the quake.exe and +all game directories. The sys_* files pass this to host_init in +quakeparms_t->basedir. This can be overridden with the "-basedir" command +line parm to allow code debugging in a different directory. The base +directory is only used during filesystem initialization. + +The "game directory" is the first tree on the search path and directory that +all generated files (savegames, screenshots, demos, config files) will be +saved to. This can be overridden with the "-game" command line parameter. +The game directory can never be changed while quake is executing. This is a +precaution against having a malicious server instruct clients to write files +over areas they shouldn't. + +*/ + + +/* +============================================================================= + +CONSTANTS + +============================================================================= +*/ + +// Magic numbers of a ZIP file (big-endian format) +#define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4" +#define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2" +#define ZIP_END_HEADER 0x504B0506 // "PK\5\6" + +// Other constants for ZIP files +#define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF) +#define ZIP_END_CDIR_SIZE 22 +#define ZIP_CDIR_CHUNK_BASE_SIZE 46 +#define ZIP_LOCAL_CHUNK_BASE_SIZE 30 + +#ifdef LINK_TO_ZLIB +#include + +#define qz_inflate inflate +#define qz_inflateEnd inflateEnd +#define qz_inflateInit2_ inflateInit2_ +#define qz_inflateReset inflateReset +#define qz_deflateInit2_ deflateInit2_ +#define qz_deflateEnd deflateEnd +#define qz_deflate deflate +#define Z_MEMLEVEL_DEFAULT 8 +#else + +// Zlib constants (from zlib.h) +#define Z_SYNC_FLUSH 2 +#define MAX_WBITS 15 +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define ZLIB_VERSION "1.2.3" + +#define Z_BINARY 0 +#define Z_DEFLATED 8 +#define Z_MEMLEVEL_DEFAULT 8 + +#define Z_NULL 0 +#define Z_DEFAULT_COMPRESSION (-1) +#define Z_NO_FLUSH 0 +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 + +// Uncomment the following line if the zlib DLL you have still uses +// the 1.1.x series calling convention on Win32 (WINAPI) +//#define ZLIB_USES_WINAPI + + +/* +============================================================================= + +TYPES + +============================================================================= +*/ + +/*! Zlib stream (from zlib.h) + * \warning: some pointers we don't use directly have + * been cast to "void*" for a matter of simplicity + */ +typedef struct +{ + unsigned char *next_in; ///< next input byte + unsigned int avail_in; ///< number of bytes available at next_in + unsigned long total_in; ///< total nb of input bytes read so far + + unsigned char *next_out; ///< next output byte should be put there + unsigned int avail_out; ///< remaining free space at next_out + unsigned long total_out; ///< total nb of bytes output so far + + char *msg; ///< last error message, NULL if no error + void *state; ///< not visible by applications + + void *zalloc; ///< used to allocate the internal state + void *zfree; ///< used to free the internal state + void *opaque; ///< private data object passed to zalloc and zfree + + int data_type; ///< best guess about the data type: ascii or binary + unsigned long adler; ///< adler32 value of the uncompressed data + unsigned long reserved; ///< reserved for future use +} z_stream; +#endif + + +/// inside a package (PAK or PK3) +#define QFILE_FLAG_PACKED (1 << 0) +/// file is compressed using the deflate algorithm (PK3 only) +#define QFILE_FLAG_DEFLATED (1 << 1) +/// file is actually already loaded data +#define QFILE_FLAG_DATA (1 << 2) +/// real file will be removed on close +#define QFILE_FLAG_REMOVE (1 << 3) + +#define FILE_BUFF_SIZE 2048 +typedef struct +{ + z_stream zstream; + size_t comp_length; ///< length of the compressed file + size_t in_ind, in_len; ///< input buffer current index and length + size_t in_position; ///< position in the compressed file + unsigned char input [FILE_BUFF_SIZE]; +} ztoolkit_t; + +struct qfile_s +{ + int flags; + int handle; ///< file descriptor + fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode) + fs_offset_t position; ///< current position in the file + fs_offset_t offset; ///< offset into the package (0 if external file) + int ungetc; ///< single stored character from ungetc, cleared to EOF when read + + // Contents buffer + fs_offset_t buff_ind, buff_len; ///< buffer current index and length + unsigned char buff [FILE_BUFF_SIZE]; + + ztoolkit_t* ztk; ///< For zipped files. + + const unsigned char *data; ///< For data files. + + const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise +}; + + +// ------ PK3 files on disk ------ // + +// You can get the complete ZIP format description from PKWARE website + +typedef struct pk3_endOfCentralDir_s +{ + unsigned int signature; + unsigned short disknum; + unsigned short cdir_disknum; ///< number of the disk with the start of the central directory + unsigned short localentries; ///< number of entries in the central directory on this disk + unsigned short nbentries; ///< total number of entries in the central directory on this disk + unsigned int cdir_size; ///< size of the central directory + unsigned int cdir_offset; ///< with respect to the starting disk number + unsigned short comment_size; + fs_offset_t prepended_garbage; +} pk3_endOfCentralDir_t; + + +// ------ PAK files on disk ------ // +typedef struct dpackfile_s +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct dpackheader_s +{ + char id[4]; + int dirofs; + int dirlen; +} dpackheader_t; + + +/*! \name Packages in memory + * @{ + */ +/// the offset in packfile_t is the true contents offset +#define PACKFILE_FLAG_TRUEOFFS (1 << 0) +/// file compressed using the deflate algorithm +#define PACKFILE_FLAG_DEFLATED (1 << 1) +/// file is a symbolic link +#define PACKFILE_FLAG_SYMLINK (1 << 2) + +typedef struct packfile_s +{ + char name [MAX_QPATH]; + int flags; + fs_offset_t offset; + fs_offset_t packsize; ///< size in the package + fs_offset_t realsize; ///< real file size (uncompressed) +} packfile_t; + +typedef struct pack_s +{ + char filename [MAX_OSPATH]; + char shortname [MAX_QPATH]; + int handle; + int ignorecase; ///< PK3 ignores case + int numfiles; + qboolean vpack; + packfile_t *files; +} pack_t; +//@} + +/// Search paths for files (including packages) +typedef struct searchpath_s +{ + // only one of filename / pack will be used + char filename[MAX_OSPATH]; + pack_t *pack; + struct searchpath_s *next; +} searchpath_t; + + +/* +============================================================================= + +FUNCTION PROTOTYPES + +============================================================================= +*/ + +void FS_Dir_f(void); +void FS_Ls_f(void); +void FS_Which_f(void); + +static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet); +static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack, + fs_offset_t offset, fs_offset_t packsize, + fs_offset_t realsize, int flags); + + +/* +============================================================================= + +VARIABLES + +============================================================================= +*/ + +mempool_t *fs_mempool; + +searchpath_t *fs_searchpaths = NULL; +const char *const fs_checkgamedir_missing = "missing"; + +#define MAX_FILES_IN_PACK 65536 + +char fs_userdir[MAX_OSPATH]; +char fs_gamedir[MAX_OSPATH]; +char fs_basedir[MAX_OSPATH]; +static pack_t *fs_selfpack = NULL; + +// list of active game directories (empty if not running a mod) +int fs_numgamedirs = 0; +char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH]; + +// list of all gamedirs with modinfo.txt +gamedir_t *fs_all_gamedirs = NULL; +int fs_all_gamedirs_count = 0; + +cvar_t scr_screenshot_name = {CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"}; +cvar_t fs_empty_files_in_pack_mark_deletions = {0, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"}; +cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"}; + + +/* +============================================================================= + +PRIVATE FUNCTIONS - PK3 HANDLING + +============================================================================= +*/ + +#ifndef LINK_TO_ZLIB +// Functions exported from zlib +#if defined(WIN32) && defined(ZLIB_USES_WINAPI) +# define ZEXPORT WINAPI +#else +# define ZEXPORT +#endif + +static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush); +static int (ZEXPORT *qz_inflateEnd) (z_stream* strm); +static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size); +static int (ZEXPORT *qz_inflateReset) (z_stream* strm); +static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size); +static int (ZEXPORT *qz_deflateEnd) (z_stream* strm); +static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush); +#endif + +#define qz_inflateInit2(strm, windowBits) \ + qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream)) + +#ifndef LINK_TO_ZLIB +// qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) + +static dllfunction_t zlibfuncs[] = +{ + {"inflate", (void **) &qz_inflate}, + {"inflateEnd", (void **) &qz_inflateEnd}, + {"inflateInit2_", (void **) &qz_inflateInit2_}, + {"inflateReset", (void **) &qz_inflateReset}, + {"deflateInit2_", (void **) &qz_deflateInit2_}, + {"deflateEnd", (void **) &qz_deflateEnd}, + {"deflate", (void **) &qz_deflate}, + {NULL, NULL} +}; + +/// Handle for Zlib DLL +static dllhandle_t zlib_dll = NULL; +#endif + +#ifdef WIN32 +static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath); +static dllfunction_t shfolderfuncs[] = +{ + {"SHGetFolderPathA", (void **) &qSHGetFolderPath}, + {NULL, NULL} +}; +static const char* shfolderdllnames [] = +{ + "shfolder.dll", // IE 4, or Win NT and higher + NULL +}; +static dllhandle_t shfolder_dll = NULL; + +const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}}; +#define qREFKNOWNFOLDERID const GUID * +#define qKF_FLAG_CREATE 0x8000 +#define qKF_FLAG_NO_ALIAS 0x1000 +static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); +static dllfunction_t shell32funcs[] = +{ + {"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath}, + {NULL, NULL} +}; +static const char* shell32dllnames [] = +{ + "shell32.dll", // Vista and higher + NULL +}; +static dllhandle_t shell32_dll = NULL; + +static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit); +static void (WINAPI *qCoUninitialize)(void); +static void (WINAPI *qCoTaskMemFree)(LPVOID pv); +static dllfunction_t ole32funcs[] = +{ + {"CoInitializeEx", (void **) &qCoInitializeEx}, + {"CoUninitialize", (void **) &qCoUninitialize}, + {"CoTaskMemFree", (void **) &qCoTaskMemFree}, + {NULL, NULL} +}; +static const char* ole32dllnames [] = +{ + "ole32.dll", // 2000 and higher + NULL +}; +static dllhandle_t ole32_dll = NULL; +#endif + +/* +==================== +PK3_CloseLibrary + +Unload the Zlib DLL +==================== +*/ +void PK3_CloseLibrary (void) +{ +#ifndef LINK_TO_ZLIB + Sys_UnloadLibrary (&zlib_dll); +#endif +} + + +/* +==================== +PK3_OpenLibrary + +Try to load the Zlib DLL +==================== +*/ +qboolean PK3_OpenLibrary (void) +{ +#ifdef LINK_TO_ZLIB + return true; +#else + const char* dllnames [] = + { +#if defined(WIN32) +# ifdef ZLIB_USES_WINAPI + "zlibwapi.dll", + "zlib.dll", +# else + "zlib1.dll", +# endif +#elif defined(MACOSX) + "libz.dylib", +#else + "libz.so.1", + "libz.so", +#endif + NULL + }; + + // Already loaded? + if (zlib_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs); +#endif +} + +/* +==================== +FS_HasZlib + +See if zlib is available +==================== +*/ +qboolean FS_HasZlib(void) +{ +#ifdef LINK_TO_ZLIB + return true; +#else + PK3_OpenLibrary(); // to be safe + return (zlib_dll != 0); +#endif +} + +/* +==================== +PK3_GetEndOfCentralDir + +Extract the end of the central directory from a PK3 package +==================== +*/ +qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd) +{ + fs_offset_t filesize, maxsize; + unsigned char *buffer, *ptr; + int ind; + + // Get the package size + filesize = lseek (packhandle, 0, SEEK_END); + if (filesize < ZIP_END_CDIR_SIZE) + return false; + + // Load the end of the file in memory + if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE) + maxsize = filesize; + else + maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE; + buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize); + lseek (packhandle, filesize - maxsize, SEEK_SET); + if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize) + { + Mem_Free (buffer); + return false; + } + + // Look for the end of central dir signature around the end of the file + maxsize -= ZIP_END_CDIR_SIZE; + ptr = &buffer[maxsize]; + ind = 0; + while (BuffBigLong (ptr) != ZIP_END_HEADER) + { + if (ind == maxsize) + { + Mem_Free (buffer); + return false; + } + + ind++; + ptr--; + } + + memcpy (eocd, ptr, ZIP_END_CDIR_SIZE); + eocd->signature = LittleLong (eocd->signature); + eocd->disknum = LittleShort (eocd->disknum); + eocd->cdir_disknum = LittleShort (eocd->cdir_disknum); + eocd->localentries = LittleShort (eocd->localentries); + eocd->nbentries = LittleShort (eocd->nbentries); + eocd->cdir_size = LittleLong (eocd->cdir_size); + eocd->cdir_offset = LittleLong (eocd->cdir_offset); + eocd->comment_size = LittleShort (eocd->comment_size); + eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files + eocd->cdir_offset += eocd->prepended_garbage; + + Mem_Free (buffer); + + return true; +} + + +/* +==================== +PK3_BuildFileList + +Extract the file list from a PK3 file +==================== +*/ +int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd) +{ + unsigned char *central_dir, *ptr; + unsigned int ind; + fs_offset_t remaining; + + // Load the central directory in memory + central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size); + lseek (pack->handle, eocd->cdir_offset, SEEK_SET); + if(read (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size) + { + Mem_Free (central_dir); + return -1; + } + + // Extract the files properties + // The parsing is done "by hand" because some fields have variable sizes and + // the constant part isn't 4-bytes aligned, which makes the use of structs difficult + remaining = eocd->cdir_size; + pack->numfiles = 0; + ptr = central_dir; + for (ind = 0; ind < eocd->nbentries; ind++) + { + fs_offset_t namesize, count; + + // Checking the remaining size + if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE) + { + Mem_Free (central_dir); + return -1; + } + remaining -= ZIP_CDIR_CHUNK_BASE_SIZE; + + // Check header + if (BuffBigLong (ptr) != ZIP_CDIR_HEADER) + { + Mem_Free (central_dir); + return -1; + } + + namesize = BuffLittleShort (&ptr[28]); // filename length + + // Check encryption, compression, and attributes + // 1st uint8 : general purpose bit flag + // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?)) + // + // LordHavoc: bit 3 would be a problem if we were scanning the archive + // but is not a problem in the central directory where the values are + // always real. + // + // bit 3 seems to always be set by the standard Mac OSX zip maker + // + // 2nd uint8 : external file attributes + // Check bits 3 (file is a directory) and 5 (file is a volume (?)) + if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0) + { + // Still enough bytes for the name? + if (remaining < namesize || namesize >= (int)sizeof (*pack->files)) + { + Mem_Free (central_dir); + return -1; + } + + // WinZip doesn't use the "directory" attribute, so we need to check the name directly + if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/') + { + char filename [sizeof (pack->files[0].name)]; + fs_offset_t offset, packsize, realsize; + int flags; + + // Extract the name (strip it if necessary) + namesize = min(namesize, (int)sizeof (filename) - 1); + memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize); + filename[namesize] = '\0'; + + if (BuffLittleShort (&ptr[10])) + flags = PACKFILE_FLAG_DEFLATED; + else + flags = 0; + offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage); + packsize = (unsigned int)BuffLittleLong (&ptr[20]); + realsize = (unsigned int)BuffLittleLong (&ptr[24]); + + switch(ptr[5]) // C_VERSION_MADE_BY_1 + { + case 3: // UNIX_ + case 2: // VMS_ + case 16: // BEOS_ + if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000) + // can't use S_ISLNK here, as this has to compile on non-UNIX too + flags |= PACKFILE_FLAG_SYMLINK; + break; + } + + FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags); + } + } + + // Skip the name, additionnal field, and comment + // 1er uint16 : extra field length + // 2eme uint16 : file comment length + count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]); + ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count; + remaining -= count; + } + + // If the package is empty, central_dir is NULL here + if (central_dir != NULL) + Mem_Free (central_dir); + return pack->numfiles; +} + + +/* +==================== +FS_LoadPackPK3 + +Create a package entry associated with a PK3 file +==================== +*/ +pack_t *FS_LoadPackPK3FromFD (const char *packfile, int packhandle, qboolean silent) +{ + pk3_endOfCentralDir_t eocd; + pack_t *pack; + int real_nb_files; + + if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd)) + { + if(!silent) + Con_Printf ("%s is not a PK3 file\n", packfile); + close(packhandle); + return NULL; + } + + // Multi-volume ZIP archives are NOT allowed + if (eocd.disknum != 0 || eocd.cdir_disknum != 0) + { + Con_Printf ("%s is a multi-volume ZIP archive\n", packfile); + close(packhandle); + return NULL; + } + + // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535 + // since eocd.nbentries is an unsigned 16 bits integer +#if MAX_FILES_IN_PACK < 65535 + if (eocd.nbentries > MAX_FILES_IN_PACK) + { + Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries); + close(packhandle); + return NULL; + } +#endif + + // Create a package structure in memory + pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); + pack->ignorecase = true; // PK3 ignores case + strlcpy (pack->filename, packfile, sizeof (pack->filename)); + pack->handle = packhandle; + pack->numfiles = eocd.nbentries; + pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t)); + + real_nb_files = PK3_BuildFileList (pack, &eocd); + if (real_nb_files < 0) + { + Con_Printf ("%s is not a valid PK3 file\n", packfile); + close(pack->handle); + Mem_Free(pack); + return NULL; + } + + Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files); + return pack; +} +pack_t *FS_LoadPackPK3 (const char *packfile) +{ + int packhandle; +#if _MSC_VER >= 1400 + _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE); +#else + packhandle = open (packfile, O_RDONLY | O_BINARY); +#endif + if (packhandle < 0) + return NULL; + return FS_LoadPackPK3FromFD(packfile, packhandle, false); +} + + +/* +==================== +PK3_GetTrueFileOffset + +Find where the true file data offset is +==================== +*/ +qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack) +{ + unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE]; + fs_offset_t count; + + // Already found? + if (pfile->flags & PACKFILE_FLAG_TRUEOFFS) + return true; + + // Load the local file description + lseek (pack->handle, pfile->offset, SEEK_SET); + count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE); + if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER) + { + Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename); + return false; + } + + // Skip name and extra field + pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE; + + pfile->flags |= PACKFILE_FLAG_TRUEOFFS; + return true; +} + + +/* +============================================================================= + +OTHER PRIVATE FUNCTIONS + +============================================================================= +*/ + + +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack, + fs_offset_t offset, fs_offset_t packsize, + fs_offset_t realsize, int flags) +{ + int (*strcmp_funct) (const char* str1, const char* str2); + int left, right, middle; + packfile_t *pfile; + + strcmp_funct = pack->ignorecase ? strcasecmp : strcmp; + + // Look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + while (left <= right) + { + int diff; + + middle = (left + right) / 2; + diff = strcmp_funct (pack->files[middle].name, name); + + // If we found the file, there's a problem + if (!diff) + Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name); + + // If we're too far in the list + if (diff > 0) + right = middle - 1; + else + left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile)); + pack->numfiles++; + + strlcpy (pfile->name, name, sizeof (pfile->name)); + pfile->offset = offset; + pfile->packsize = packsize; + pfile->realsize = realsize; + pfile->flags = flags; + + return pfile; +} + + +/* +============ +FS_CreatePath + +Only used for FS_OpenRealFile. +============ +*/ +void FS_CreatePath (char *path) +{ + char *ofs, save; + + for (ofs = path+1 ; *ofs ; ofs++) + { + if (*ofs == '/' || *ofs == '\\') + { + // create the directory + save = *ofs; + *ofs = 0; + FS_mkdir (path); + *ofs = save; + } + } +} + + +/* +============ +FS_Path_f + +============ +*/ +void FS_Path_f (void) +{ + searchpath_t *s; + + Con_Print("Current search path:\n"); + for (s=fs_searchpaths ; s ; s=s->next) + { + if (s->pack) + { + if(s->pack->vpack) + Con_Printf("%sdir (virtual pack)\n", s->pack->filename); + else + Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles); + } + else + Con_Printf("%s\n", s->filename); + } +} + + +/* +================= +FS_LoadPackPAK +================= +*/ +/*! Takes an explicit (not game tree related) path to a pak file. + *Loads the header and directory, adding the files at the beginning + *of the list so they override previous pack files. + */ +pack_t *FS_LoadPackPAK (const char *packfile) +{ + dpackheader_t header; + int i, numpackfiles; + int packhandle; + pack_t *pack; + dpackfile_t *info; + +#if _MSC_VER >= 1400 + _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE); +#else + packhandle = open (packfile, O_RDONLY | O_BINARY); +#endif + if (packhandle < 0) + return NULL; + if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header)) + { + Con_Printf ("%s is not a packfile\n", packfile); + close(packhandle); + return NULL; + } + if (memcmp(header.id, "PACK", 4)) + { + Con_Printf ("%s is not a packfile\n", packfile); + close(packhandle); + return NULL; + } + header.dirofs = LittleLong (header.dirofs); + header.dirlen = LittleLong (header.dirlen); + + if (header.dirlen % sizeof(dpackfile_t)) + { + Con_Printf ("%s has an invalid directory size\n", packfile); + close(packhandle); + return NULL; + } + + numpackfiles = header.dirlen / sizeof(dpackfile_t); + + if (numpackfiles > MAX_FILES_IN_PACK) + { + Con_Printf ("%s has %i files\n", packfile, numpackfiles); + close(packhandle); + return NULL; + } + + info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles); + lseek (packhandle, header.dirofs, SEEK_SET); + if(header.dirlen != read (packhandle, (void *)info, header.dirlen)) + { + Con_Printf("%s is an incomplete PAK, not loading\n", packfile); + Mem_Free(info); + close(packhandle); + return NULL; + } + + pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); + pack->ignorecase = false; // PAK is case sensitive + strlcpy (pack->filename, packfile, sizeof (pack->filename)); + pack->handle = packhandle; + pack->numfiles = 0; + pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t)); + + // parse the directory + for (i = 0;i < numpackfiles;i++) + { + fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos); + fs_offset_t size = (unsigned int)LittleLong (info[i].filelen); + + FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS); + } + + Mem_Free(info); + + Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles); + return pack; +} + +/* +==================== +FS_LoadPackVirtual + +Create a package entry associated with a directory file +==================== +*/ +pack_t *FS_LoadPackVirtual (const char *dirname) +{ + pack_t *pack; + pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); + pack->vpack = true; + pack->ignorecase = false; + strlcpy (pack->filename, dirname, sizeof(pack->filename)); + pack->handle = -1; + pack->numfiles = -1; + pack->files = NULL; + Con_DPrintf("Added packfile %s (virtual pack)\n", dirname); + return pack; +} + +/* +================ +FS_AddPack_Fullpath +================ +*/ +/*! Adds the given pack to the search path. + * The pack type is autodetected by the file extension. + * + * Returns true if the file was successfully added to the + * search path or if it was already included. + * + * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of + * plain directories. + * + */ +static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = FS_FileExtension(pakfile); + size_t l; + + for(search = fs_searchpaths; search; search = search->next) + { + if(search->pack && !strcasecmp(search->pack->filename, pakfile)) + { + if(already_loaded) + *already_loaded = true; + return true; // already loaded + } + } + + if(already_loaded) + *already_loaded = false; + + if(!strcasecmp(ext, "pk3dir")) + pak = FS_LoadPackVirtual (pakfile); + else if(!strcasecmp(ext, "pak")) + pak = FS_LoadPackPAK (pakfile); + else if(!strcasecmp(ext, "pk3")) + pak = FS_LoadPackPK3 (pakfile); + else + Con_Printf("\"%s\" does not have a pack extension\n", pakfile); + + if(pak) + { + strlcpy(pak->shortname, shortname, sizeof(pak->shortname)); + + //Con_DPrintf(" Registered pack with short name %s\n", shortname); + if(keep_plain_dirs) + { + // find the first item whose next one is a pack or NULL + searchpath_t *insertion_point = 0; + if(fs_searchpaths && !fs_searchpaths->pack) + { + insertion_point = fs_searchpaths; + for(;;) + { + if(!insertion_point->next) + break; + if(insertion_point->next->pack) + break; + insertion_point = insertion_point->next; + } + } + // If insertion_point is NULL, this means that either there is no + // item in the list yet, or that the very first item is a pack. In + // that case, we want to insert at the beginning... + if(!insertion_point) + { + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + search->next = fs_searchpaths; + fs_searchpaths = search; + } + else + // otherwise we want to append directly after insertion_point. + { + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + search->next = insertion_point->next; + insertion_point->next = search; + } + } + else + { + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + search->next = fs_searchpaths; + fs_searchpaths = search; + } + search->pack = pak; + if(pak->vpack) + { + dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile); + // if shortname ends with "pk3dir", strip that suffix to make it just "pk3" + // same goes for the name inside the pack structure + l = strlen(pak->shortname); + if(l >= 7) + if(!strcasecmp(pak->shortname + l - 7, ".pk3dir")) + pak->shortname[l - 3] = 0; + l = strlen(pak->filename); + if(l >= 7) + if(!strcasecmp(pak->filename + l - 7, ".pk3dir")) + pak->filename[l - 3] = 0; + } + return true; + } + else + { + Con_Printf("unable to load pak \"%s\"\n", pakfile); + return false; + } +} + + +/* +================ +FS_AddPack +================ +*/ +/*! Adds the given pack to the search path and searches for it in the game path. + * The pack type is autodetected by the file extension. + * + * Returns true if the file was successfully added to the + * search path or if it was already included. + * + * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of + * plain directories. + */ +qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs) +{ + char fullpath[MAX_OSPATH]; + int index; + searchpath_t *search; + + if(already_loaded) + *already_loaded = false; + + // then find the real name... + search = FS_FindFile(pakfile, &index, true); + if(!search || search->pack) + { + Con_Printf("could not find pak \"%s\"\n", pakfile); + return false; + } + + dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile); + + return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs); +} + + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads and adds pak1.pak pak2.pak ... +================ +*/ +void FS_AddGameDirectory (const char *dir) +{ + int i; + stringlist_t list; + searchpath_t *search; + + strlcpy (fs_gamedir, dir, sizeof (fs_gamedir)); + + stringlistinit(&list); + listdirectory(&list, "", dir); + stringlistsort(&list, false); + + // add any PAK package in the directory + for (i = 0;i < list.numstrings;i++) + { + if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak")) + { + FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false); + } + } + + // add any PK3 package in the directory + for (i = 0;i < list.numstrings;i++) + { + if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir")) + { + FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false); + } + } + + stringlistfreecontents(&list); + + // Add the directory to the search path + // (unpacked files have the priority over packed files) + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + strlcpy (search->filename, dir, sizeof (search->filename)); + search->next = fs_searchpaths; + fs_searchpaths = search; +} + + +/* +================ +FS_AddGameHierarchy +================ +*/ +void FS_AddGameHierarchy (const char *dir) +{ + // Add the common game directory + FS_AddGameDirectory (va("%s%s/", fs_basedir, dir)); + + if (*fs_userdir) + FS_AddGameDirectory(va("%s%s/", fs_userdir, dir)); +} + + +/* +============ +FS_FileExtension +============ +*/ +const char *FS_FileExtension (const char *in) +{ + const char *separator, *backslash, *colon, *dot; + + separator = strrchr(in, '/'); + backslash = strrchr(in, '\\'); + if (!separator || separator < backslash) + separator = backslash; + colon = strrchr(in, ':'); + if (!separator || separator < colon) + separator = colon; + + dot = strrchr(in, '.'); + if (dot == NULL || (separator && (dot < separator))) + return ""; + + return dot + 1; +} + + +/* +============ +FS_FileWithoutPath +============ +*/ +const char *FS_FileWithoutPath (const char *in) +{ + const char *separator, *backslash, *colon; + + separator = strrchr(in, '/'); + backslash = strrchr(in, '\\'); + if (!separator || separator < backslash) + separator = backslash; + colon = strrchr(in, ':'); + if (!separator || separator < colon) + separator = colon; + return separator ? separator + 1 : in; +} + + +/* +================ +FS_ClearSearchPath +================ +*/ +void FS_ClearSearchPath (void) +{ + // unload all packs and directory information, close all pack files + // (if a qfile is still reading a pack it won't be harmed because it used + // dup() to get its own handle already) + while (fs_searchpaths) + { + searchpath_t *search = fs_searchpaths; + fs_searchpaths = search->next; + if (search->pack && search->pack != fs_selfpack) + { + if(!search->pack->vpack) + { + // close the file + close(search->pack->handle); + // free any memory associated with it + if (search->pack->files) + Mem_Free(search->pack->files); + } + Mem_Free(search->pack); + } + Mem_Free(search); + } +} + +static void FS_AddSelfPack(void) +{ + if(fs_selfpack) + { + searchpath_t *search; + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + search->next = fs_searchpaths; + search->pack = fs_selfpack; + fs_searchpaths = search; + } +} + + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan (void) +{ + int i; + qboolean fs_modified = false; + qboolean reset = false; + char gamedirbuf[MAX_INPUTLINE]; + + if (fs_searchpaths) + reset = true; + FS_ClearSearchPath(); + + // automatically activate gamemode for the gamedirs specified + if (reset) + COM_ChangeGameTypeForGameDirs(); + + // add the game-specific paths + // gamedirname1 (typically id1) + FS_AddGameHierarchy (gamedirname1); + // update the com_modname (used for server info) + if (gamedirname2 && gamedirname2[0]) + strlcpy(com_modname, gamedirname2, sizeof(com_modname)); + else + strlcpy(com_modname, gamedirname1, sizeof(com_modname)); + + // add the game-specific path, if any + // (only used for mission packs and the like, which should set fs_modified) + if (gamedirname2 && gamedirname2[0]) + { + fs_modified = true; + FS_AddGameHierarchy (gamedirname2); + } + + // -game + // Adds basedir/gamedir as an override game + // LordHavoc: now supports multiple -game directories + // set the com_modname (reported in server info) + *gamedirbuf = 0; + for (i = 0;i < fs_numgamedirs;i++) + { + fs_modified = true; + FS_AddGameHierarchy (fs_gamedirs[i]); + // update the com_modname (used server info) + strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname)); + if(i) + strlcat(gamedirbuf, va(" %s", fs_gamedirs[i]), sizeof(gamedirbuf)); + else + strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf)); + } + Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it + + // add back the selfpack as new first item + FS_AddSelfPack(); + + // set the default screenshot name to either the mod name or the + // gamemode screenshot name + if (strcmp(com_modname, gamedirname1)) + Cvar_SetQuick (&scr_screenshot_name, com_modname); + else + Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname); + + if((i = COM_CheckParm("-modname")) && i < com_argc - 1) + strlcpy(com_modname, com_argv[i+1], sizeof(com_modname)); + + // If "-condebug" is in the command line, remove the previous log file + if (COM_CheckParm ("-condebug") != 0) + unlink (va("%s/qconsole.log", fs_gamedir)); + + // look for the pop.lmp file and set registered to true if it is found + if (FS_FileExists("gfx/pop.lmp")) + Cvar_Set ("registered", "1"); + switch(gamemode) + { + case GAME_NORMAL: + case GAME_HIPNOTIC: + case GAME_ROGUE: + if (!registered.integer) + { + if (fs_modified) + Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n"); + else + Con_Print("Playing shareware version.\n"); + } + else + Con_Print("Playing registered version.\n"); + break; + case GAME_STEELSTORM: + if (registered.integer) + Con_Print("Playing registered version.\n"); + else + Con_Print("Playing shareware version.\n"); + break; + default: + break; + } + + // unload all wads so that future queries will return the new data + W_UnloadAll(); +} + +void FS_Rescan_f(void) +{ + FS_Rescan(); +} + +/* +================ +FS_ChangeGameDirs +================ +*/ +extern void Host_SaveConfig (void); +extern void Host_LoadConfig_f (void); +extern qboolean vid_opened; +extern void VID_Stop(void); +qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing) +{ + int i; + const char *p; + + if (fs_numgamedirs == numgamedirs) + { + for (i = 0;i < numgamedirs;i++) + if (strcasecmp(fs_gamedirs[i], gamedirs[i])) + break; + if (i == numgamedirs) + return true; // already using this set of gamedirs, do nothing + } + + if (numgamedirs > MAX_GAMEDIRS) + { + if (complain) + Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS); + return false; // too many gamedirs + } + + for (i = 0;i < numgamedirs;i++) + { + // if string is nasty, reject it + p = FS_CheckGameDir(gamedirs[i]); + if(!p) + { + if (complain) + Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]); + return false; // nasty gamedirs + } + if(p == fs_checkgamedir_missing && failmissing) + { + if (complain) + Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]); + return false; // missing gamedirs + } + } + + Host_SaveConfig(); + + fs_numgamedirs = numgamedirs; + for (i = 0;i < fs_numgamedirs;i++) + strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i])); + + // reinitialize filesystem to detect the new paks + FS_Rescan(); + + if (cls.demoplayback) + { + CL_Disconnect_f(); + cls.demonum = 0; + } + + // unload all sounds so they will be reloaded from the new files as needed + S_UnloadAllSounds_f(); + + // close down the video subsystem, it will start up again when the config finishes... + VID_Stop(); + vid_opened = false; + + // restart the video subsystem after the config is executed + Cbuf_InsertText("\nloadconfig\nvid_restart\n\n"); + + return true; +} + +/* +================ +FS_GameDir_f +================ +*/ +void FS_GameDir_f (void) +{ + int i; + int numgamedirs; + char gamedirs[MAX_GAMEDIRS][MAX_QPATH]; + + if (Cmd_Argc() < 2) + { + Con_Printf("gamedirs active:"); + for (i = 0;i < fs_numgamedirs;i++) + Con_Printf(" %s", fs_gamedirs[i]); + Con_Printf("\n"); + return; + } + + numgamedirs = Cmd_Argc() - 1; + if (numgamedirs > MAX_GAMEDIRS) + { + Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS); + return; + } + + for (i = 0;i < numgamedirs;i++) + strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i])); + + if ((cls.state == ca_connected && !cls.demoplayback) || sv.active) + { + // actually, changing during game would work fine, but would be stupid + Con_Printf("Can not change gamedir while client is connected or server is running!\n"); + return; + } + + // halt demo playback to close the file + CL_Disconnect(); + + FS_ChangeGameDirs(numgamedirs, gamedirs, true, true); +} + +static const char *FS_SysCheckGameDir(const char *gamedir) +{ + static char buf[8192]; + qboolean success; + qfile_t *f; + stringlist_t list; + fs_offset_t n; + + stringlistinit(&list); + listdirectory(&list, gamedir, ""); + success = list.numstrings > 0; + stringlistfreecontents(&list); + + if(success) + { + f = FS_SysOpen(va("%smodinfo.txt", gamedir), "r", false); + if(f) + { + n = FS_Read (f, buf, sizeof(buf) - 1); + if(n >= 0) + buf[n] = 0; + else + *buf = 0; + FS_Close(f); + } + else + *buf = 0; + return buf; + } + + return NULL; +} + +/* +================ +FS_CheckGameDir +================ +*/ +const char *FS_CheckGameDir(const char *gamedir) +{ + const char *ret; + + if (FS_CheckNastyPath(gamedir, true)) + return NULL; + + ret = FS_SysCheckGameDir(va("%s%s/", fs_userdir, gamedir)); + if(ret) + { + if(!*ret) + { + // get description from basedir + ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir)); + if(ret) + return ret; + return ""; + } + return ret; + } + + ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir)); + if(ret) + return ret; + + return fs_checkgamedir_missing; +} + +static void FS_ListGameDirs(void) +{ + stringlist_t list, list2; + int i, j; + const char *info; + + fs_all_gamedirs_count = 0; + if(fs_all_gamedirs) + Mem_Free(fs_all_gamedirs); + + stringlistinit(&list); + listdirectory(&list, va("%s/", fs_basedir), ""); + listdirectory(&list, va("%s/", fs_userdir), ""); + stringlistsort(&list, false); + + stringlistinit(&list2); + for(i = 0; i < list.numstrings; ++i) + { + if(i) + if(!strcmp(list.strings[i-1], list.strings[i])) + continue; + info = FS_CheckGameDir(list.strings[i]); + if(!info) + continue; + if(info == fs_checkgamedir_missing) + continue; + if(!*info) + continue; + stringlistappend(&list2, list.strings[i]); + } + stringlistfreecontents(&list); + + fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs)); + for(i = 0; i < list2.numstrings; ++i) + { + info = FS_CheckGameDir(list2.strings[i]); + // all this cannot happen any more, but better be safe than sorry + if(!info) + continue; + if(info == fs_checkgamedir_missing) + continue; + if(!*info) + continue; + strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[j].name)); + strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[j].description)); + ++fs_all_gamedirs_count; + } +} + +/* +#ifdef WIN32 +#pragma comment(lib, "shell32.lib") +#include +#endif +*/ + +/* +================ +FS_Init_SelfPack +================ +*/ +void FS_Init_SelfPack (void) +{ + PK3_OpenLibrary (); + fs_mempool = Mem_AllocPool("file management", 0, NULL); + if(com_selffd >= 0) + { + fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true); + if(fs_selfpack) + { + char *buf, *q; + const char *p; + FS_AddSelfPack(); + buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL); + if(buf) + { + const char **new_argv; + int i = 0; + int args_left = 256; + new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2)); + if(com_argc == 0) + { + new_argv[0] = "dummy"; + com_argc = 1; + } + else + { + memcpy((char *)(&new_argv[0]), &com_argv[0], sizeof(*com_argv) * com_argc); + } + p = buf; + while(COM_ParseToken_Console(&p)) + { + if(i >= args_left) + break; + q = (char *)Mem_Alloc(fs_mempool, strlen(com_token) + 1); + strlcpy(q, com_token, strlen(com_token) + 1); + new_argv[com_argc + i] = q; + ++i; + } + new_argv[i+com_argc] = NULL; + com_argv = new_argv; + com_argc = com_argc + i; + } + Mem_Free(buf); + } + } +} + +int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize) +{ +#if defined(__IPHONEOS__) + if (userdirmode == USERDIRMODE_HOME) + { + // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode + // fs_userdir stores configurations to the Documents folder of the app + strlcpy(userdir, maxlength, "../Documents/"); + return 1; + } + return -1; + +#elif defined(WIN32) + char *homedir; +#if _MSC_VER >= 1400 + size_t homedirlen; +#endif + TCHAR mydocsdir[MAX_PATH + 1]; + wchar_t *savedgamesdirw; + char savedgamesdir[MAX_OSPATH]; + int fd; + + userdir[0] = 0; + switch(userdirmode) + { + default: + return -1; + case USERDIRMODE_NOHOME: + strlcpy(userdir, fs_basedir, userdirsize); + break; + case USERDIRMODE_MYGAMES: + if (!shfolder_dll) + Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs); + mydocsdir[0] = 0; + if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK) + { + dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname); + break; + } +#if _MSC_VER >= 1400 + _dupenv_s(&homedir, &homedirlen, "USERPROFILE"); + if(homedir) + { + dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); + free(homedir); + break; + } +#else + homedir = getenv("USERPROFILE"); + if(homedir) + { + dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); + break; + } +#endif + return -1; + case USERDIRMODE_SAVEDGAMES: + if (!shell32_dll) + Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs); + if (!ole32_dll) + Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs); + if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize) + { + savedgamesdir[0] = 0; + qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED); +/* +#ifdef __cplusplus + if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) +#else + if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) +#endif +*/ + if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) + { + memset(savedgamesdir, 0, sizeof(savedgamesdir)); +#if _MSC_VER >= 1400 + wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1); +#else + wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1); +#endif + qCoTaskMemFree(savedgamesdirw); + } + qCoUninitialize(); + if (savedgamesdir[0]) + { + dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname); + break; + } + } + return -1; + } +#else + int fd; + char *homedir; + userdir[0] = 0; + switch(userdirmode) + { + default: + return -1; + case USERDIRMODE_NOHOME: + strlcpy(userdir, fs_basedir, userdirsize); + break; + case USERDIRMODE_HOME: + homedir = getenv("HOME"); + if(homedir) + { + dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); + break; + } + return -1; + case USERDIRMODE_SAVEDGAMES: + homedir = getenv("HOME"); + if(homedir) + { +#ifdef MACOSX + dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname); +#else + // the XDG say some files would need to go in: + // XDG_CONFIG_HOME (or ~/.config/%s/) + // XDG_DATA_HOME (or ~/.local/share/%s/) + // XDG_CACHE_HOME (or ~/.cache/%s/) + // and also search the following global locations if defined: + // XDG_CONFIG_DIRS (normally /etc/xdg/%s/) + // XDG_DATA_DIRS (normally /usr/share/%s/) + // this would be too complicated... + return -1; +#endif + break; + } + return -1; + } +#endif + + +#ifdef WIN32 + // historical behavior... + if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1")) + return 0; // don't bother checking if the basedir folder is writable, it's annoying... unless it is Quake on Windows where NOHOME is the default preferred and we have to check for an error case +#endif + // see if we can write to this path (note: won't create path) +#if _MSC_VER >= 1400 + _sopen_s(&fd, va("%s%s/config.cfg", userdir, gamedirname1), O_WRONLY | O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); // note: no O_TRUNC here! +#else + fd = open (va("%s%s/config.cfg", userdir, gamedirname1), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here! +#endif + if(fd >= 0) + { + close(fd); + return 1; // good choice - the path exists and is writable + } + else + return 0; // probably good - failed to write but maybe we need to create path +} + +/* +================ +FS_Init +================ +*/ +void FS_Init (void) +{ + const char *p; + int i; + + *fs_basedir = 0; + *fs_userdir = 0; + *fs_gamedir = 0; + + // -basedir + // Overrides the system supplied base directory (under GAMENAME) +// COMMANDLINEOPTION: Filesystem: -basedir chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1) + i = COM_CheckParm ("-basedir"); + if (i && i < com_argc-1) + { + strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir)); + i = (int)strlen (fs_basedir); + if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/')) + fs_basedir[i-1] = 0; + } + else + { +// If the base directory is explicitly defined by the compilation process +#ifdef DP_FS_BASEDIR + strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir)); +#elif defined(MACOSX) + // FIXME: is there a better way to find the directory outside the .app, without using Objective-C? + if (strstr(com_argv[0], ".app/")) + { + char *split; + strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir)); + split = strstr(fs_basedir, ".app/"); + if (split) + { + struct stat statresult; + // truncate to just after the .app/ + split[5] = 0; + // see if gamedir exists in Resources + if (stat(va("%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0) + { + // found gamedir inside Resources, use it + strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir)); + } + else + { + // no gamedir found in Resources, gamedir is probably + // outside the .app, remove .app part of path + while (split > fs_basedir && *split != '/') + split--; + *split = 0; + } + } + } +#endif + } + + // make sure the appending of a path separator won't create an unterminated string + memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2); + // add a path separator to the end of the basedir if it lacks one + if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\') + strlcat(fs_basedir, "/", sizeof(fs_basedir)); + + // Add the personal game directory + if((i = COM_CheckParm("-userdir")) && i < com_argc - 1) + dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]); + else if (COM_CheckParm("-nohome")) + *fs_userdir = 0; // user wants roaming installation, no userdir + else + { + int dirmode; + int highestuserdirmode = USERDIRMODE_COUNT - 1; + int preferreduserdirmode = USERDIRMODE_COUNT - 1; + int userdirstatus[USERDIRMODE_COUNT]; +#ifdef WIN32 + // historical behavior... + if (!strcmp(gamedirname1, "id1")) + preferreduserdirmode = USERDIRMODE_NOHOME; +#endif + // check what limitations the user wants to impose + if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME; + if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES; + if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES; + // gather the status of the possible userdirs + for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++) + { + userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir)); + if (userdirstatus[dirmode] == 1) + Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir); + else if (userdirstatus[dirmode] == 0) + Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir); + else + Con_DPrintf("userdir %i (not applicable)\n", dirmode); + } + // some games may prefer writing to basedir, but if write fails we + // have to search for a real userdir... + if (preferreduserdirmode == 0 && userdirstatus[0] < 1) + preferreduserdirmode = highestuserdirmode; + // check for an existing userdir and continue using it if possible... + for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--) + if (userdirstatus[dirmode] == 1) + break; + // if no existing userdir found, make a new one... + if (dirmode == 0 && preferreduserdirmode > 0) + for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--) + if (userdirstatus[dirmode] >= 0) + break; + // and finally, we picked one... + FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir)); + Con_DPrintf("userdir %i is the winner\n", dirmode); + } + + // if userdir equal to basedir, clear it to avoid confusion later + if (!strcmp(fs_basedir, fs_userdir)) + fs_userdir[0] = 0; + + FS_ListGameDirs(); + + p = FS_CheckGameDir(gamedirname1); + if(!p || p == fs_checkgamedir_missing) + Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1); + + if(gamedirname2) + { + p = FS_CheckGameDir(gamedirname2); + if(!p || p == fs_checkgamedir_missing) + Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2); + } + + // -game + // Adds basedir/gamedir as an override game + // LordHavoc: now supports multiple -game directories + for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++) + { + if (!com_argv[i]) + continue; + if (!strcmp (com_argv[i], "-game") && i < com_argc-1) + { + i++; + p = FS_CheckGameDir(com_argv[i]); + if(!p) + Sys_Error("Nasty -game name rejected: %s", com_argv[i]); + if(p == fs_checkgamedir_missing) + Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]); + // add the gamedir to the list of active gamedirs + strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs])); + fs_numgamedirs++; + } + } + + // generate the searchpath + FS_Rescan(); +} + +void FS_Init_Commands(void) +{ + Cvar_RegisterVariable (&scr_screenshot_name); + Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions); + Cvar_RegisterVariable (&cvar_fs_gamedir); + + Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)"); + Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes"); + Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)"); + Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line"); + Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line"); + Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from"); +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown (void) +{ + // close all pack files and such + // (hopefully there aren't any other open files, but they'll be cleaned up + // by the OS anyway) + FS_ClearSearchPath(); + Mem_FreePool (&fs_mempool); + +#ifdef WIN32 + Sys_UnloadLibrary (&shfolder_dll); + Sys_UnloadLibrary (&shell32_dll); + Sys_UnloadLibrary (&ole32_dll); +#endif +} + +int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking) +{ + int handle; + int mod, opt; + unsigned int ind; + + // Parse the mode string + switch (mode[0]) + { + case 'r': + mod = O_RDONLY; + opt = 0; + break; + case 'w': + mod = O_WRONLY; + opt = O_CREAT | O_TRUNC; + break; + case 'a': + mod = O_WRONLY; + opt = O_CREAT | O_APPEND; + break; + default: + Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode); + return -1; + } + for (ind = 1; mode[ind] != '\0'; ind++) + { + switch (mode[ind]) + { + case '+': + mod = O_RDWR; + break; + case 'b': + opt |= O_BINARY; + break; + default: + Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n", + filepath, mode, mode[ind]); + } + } + + if (nonblocking) + opt |= O_NONBLOCK; + +#if _MSC_VER >= 1400 + _sopen_s(&handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE); +#else + handle = open (filepath, mod | opt, 0666); +#endif + return handle; +} + +/* +==================== +FS_SysOpen + +Internal function used to create a qfile_t and open the relevant non-packed file on disk +==================== +*/ +qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking) +{ + qfile_t* file; + + file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); + file->ungetc = EOF; + file->handle = FS_SysOpenFD(filepath, mode, nonblocking); + if (file->handle < 0) + { + Mem_Free (file); + return NULL; + } + + file->filename = Mem_strdup(fs_mempool, filepath); + + file->real_length = lseek (file->handle, 0, SEEK_END); + + // For files opened in append mode, we start at the end of the file + if (mode[0] == 'a') + file->position = file->real_length; + else + lseek (file->handle, 0, SEEK_SET); + + return file; +} + + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind) +{ + packfile_t *pfile; + int dup_handle; + qfile_t* file; + + pfile = &pack->files[pack_ind]; + + // If we don't have the true offset, get it now + if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS)) + if (!PK3_GetTrueFileOffset (pfile, pack)) + return NULL; + +#ifndef LINK_TO_ZLIB + // No Zlib DLL = no compressed files + if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED)) + { + Con_Printf("WARNING: can't open the compressed file %s\n" + "You need the Zlib DLL to use compressed files\n", + pfile->name); + return NULL; + } +#endif + + // LordHavoc: lseek affects all duplicates of a handle so we do it before + // the dup() call to avoid having to close the dup_handle on error here + if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1) + { + Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n", + pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset)); + return NULL; + } + + dup_handle = dup (pack->handle); + if (dup_handle < 0) + { + Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename); + return NULL; + } + + file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); + memset (file, 0, sizeof (*file)); + file->handle = dup_handle; + file->flags = QFILE_FLAG_PACKED; + file->real_length = pfile->realsize; + file->offset = pfile->offset; + file->position = 0; + file->ungetc = EOF; + + if (pfile->flags & PACKFILE_FLAG_DEFLATED) + { + ztoolkit_t *ztk; + + file->flags |= QFILE_FLAG_DEFLATED; + + // We need some more variables + ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk)); + + ztk->comp_length = pfile->packsize; + + // Initialize zlib stream + ztk->zstream.next_in = ztk->input; + ztk->zstream.avail_in = 0; + + /* From Zlib's "unzip.c": + * + * windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK) + { + Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name); + close(dup_handle); + Mem_Free(file); + return NULL; + } + + ztk->zstream.next_out = file->buff; + ztk->zstream.avail_out = sizeof (file->buff); + + file->ztk = ztk; + } + + return file; +} + +/* +==================== +FS_CheckNastyPath + +Return true if the path should be rejected due to one of the following: +1: path elements that are non-portable +2: path elements that would allow access to files outside the game directory, + or are just not a good idea for a mod to be using. +==================== +*/ +int FS_CheckNastyPath (const char *path, qboolean isgamedir) +{ + // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless + if (!path[0]) + return 2; + + // Windows: don't allow \ in filenames (windows-only), period. + // (on Windows \ is a directory separator, but / is also supported) + if (strstr(path, "\\")) + return 1; // non-portable + + // Mac: don't allow Mac-only filenames - : is a directory separator + // instead of /, but we rely on / working already, so there's no reason to + // support a Mac-only path + // Amiga and Windows: : tries to go to root of drive + if (strstr(path, ":")) + return 1; // non-portable attempt to go to root of drive + + // Amiga: // is parent directory + if (strstr(path, "//")) + return 1; // non-portable attempt to go to parent directory + + // all: don't allow going to parent directory (../ or /../) + if (strstr(path, "..")) + return 2; // attempt to go outside the game directory + + // Windows and UNIXes: don't allow absolute paths + if (path[0] == '/') + return 2; // attempt to go outside the game directory + + // all: don't allow . characters before the last slash (it should only be used in filenames, not path elements), this catches all imaginable cases of ./, ../, .../, etc + if (strchr(path, '.')) + { + if (isgamedir) + { + // gamedir is entirely path elements, so simply forbid . entirely + return 2; + } + if (strchr(path, '.') < strrchr(path, '/')) + return 2; // possible attempt to go outside the game directory + } + + // all: forbid trailing slash on gamedir + if (isgamedir && path[strlen(path)-1] == '/') + return 2; + + // all: forbid leading dot on any filename for any reason + if (strstr(path, "/.")) + return 2; // attempt to go outside the game directory + + // after all these checks we're pretty sure it's a / separated filename + // and won't do much if any harm + return false; +} + + +/* +==================== +FS_FindFile + +Look for a file in the packages and in the filesystem + +Return the searchpath where the file was found (or NULL) +and the file index in the package if relevant +==================== +*/ +static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet) +{ + searchpath_t *search; + pack_t *pak; + + // search through the path, one element at a time + for (search = fs_searchpaths;search;search = search->next) + { + // is the element a pak file? + if (search->pack && !search->pack->vpack) + { + int (*strcmp_funct) (const char* str1, const char* str2); + int left, right, middle; + + pak = search->pack; + strcmp_funct = pak->ignorecase ? strcasecmp : strcmp; + + // Look for the file (binary search) + left = 0; + right = pak->numfiles - 1; + while (left <= right) + { + int diff; + + middle = (left + right) / 2; + diff = strcmp_funct (pak->files[middle].name, name); + + // Found it + if (!diff) + { + if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0) + { + // yes, but the first one is empty so we treat it as not being there + if (!quiet && developer_extra.integer) + Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name); + + if (index != NULL) + *index = -1; + return NULL; + } + + if (!quiet && developer_extra.integer) + Con_DPrintf("FS_FindFile: %s in %s\n", + pak->files[middle].name, pak->filename); + + if (index != NULL) + *index = middle; + return search; + } + + // If we're too far in the list + if (diff > 0) + right = middle - 1; + else + left = middle + 1; + } + } + else + { + char netpath[MAX_OSPATH]; + dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name); + if (FS_SysFileExists (netpath)) + { + if (!quiet && developer_extra.integer) + Con_DPrintf("FS_FindFile: %s\n", netpath); + + if (index != NULL) + *index = -1; + return search; + } + } + } + + if (!quiet && developer_extra.integer) + Con_DPrintf("FS_FindFile: can't find %s\n", name); + + if (index != NULL) + *index = -1; + return NULL; +} + + +/* +=========== +FS_OpenReadFile + +Look for a file in the search paths and open it in read-only mode +=========== +*/ +qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels) +{ + searchpath_t *search; + int pack_ind; + + search = FS_FindFile (filename, &pack_ind, quiet); + + // Not found? + if (search == NULL) + return NULL; + + // Found in the filesystem? + if (pack_ind < 0) + { + // this works with vpacks, so we are fine + char path [MAX_OSPATH]; + dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename); + return FS_SysOpen (path, "rb", nonblocking); + } + + // So, we found it in a package... + + // Is it a PK3 symlink? + // TODO also handle directory symlinks by parsing the whole structure... + // but heck, file symlinks are good enough for now + if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK) + { + if(symlinkLevels <= 0) + { + Con_Printf("symlink: %s: too many levels of symbolic links\n", filename); + return NULL; + } + else + { + char linkbuf[MAX_QPATH]; + fs_offset_t count; + qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind); + const char *mergeslash; + char *mergestart; + + if(!linkfile) + return NULL; + count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1); + FS_Close(linkfile); + if(count < 0) + return NULL; + linkbuf[count] = 0; + + // Now combine the paths... + mergeslash = strrchr(filename, '/'); + mergestart = linkbuf; + if(!mergeslash) + mergeslash = filename; + while(!strncmp(mergestart, "../", 3)) + { + mergestart += 3; + while(mergeslash > filename) + { + --mergeslash; + if(*mergeslash == '/') + break; + } + } + // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended + if(mergeslash == filename) + { + // Either mergeslash == filename, then we just replace the name (done below) + } + else + { + // Or, we append the name after mergeslash; + // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first + int spaceNeeded = mergeslash - filename + 1; + int spaceRemoved = mergestart - linkbuf; + if(count - spaceRemoved + spaceNeeded >= MAX_QPATH) + { + Con_DPrintf("symlink: too long path rejected\n"); + return NULL; + } + memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved); + memcpy(linkbuf, filename, spaceNeeded); + linkbuf[count - spaceRemoved + spaceNeeded] = 0; + mergestart = linkbuf; + } + if (!quiet && developer_loading.integer) + Con_DPrintf("symlink: %s -> %s\n", filename, mergestart); + if(FS_CheckNastyPath (mergestart, false)) + { + Con_DPrintf("symlink: nasty path %s rejected\n", mergestart); + return NULL; + } + return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1); + } + } + + return FS_OpenPackedFile (search->pack, pack_ind); +} + + +/* +============================================================================= + +MAIN PUBLIC FUNCTIONS + +============================================================================= +*/ + +/* +==================== +FS_OpenRealFile + +Open a file in the userpath. The syntax is the same as fopen +Used for savegame scanning in menu, and all file writing. +==================== +*/ +qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet) +{ + char real_path [MAX_OSPATH]; + + if (FS_CheckNastyPath(filepath, false)) + { + Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false"); + return NULL; + } + + dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack + + // If the file is opened in "write", "append", or "read/write" mode, + // create directories up to the file. + if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+')) + FS_CreatePath (real_path); + return FS_SysOpen (real_path, mode, false); +} + + +/* +==================== +FS_OpenVirtualFile + +Open a file. The syntax is the same as fopen +==================== +*/ +qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet) +{ + if (FS_CheckNastyPath(filepath, false)) + { + Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false"); + return NULL; + } + + return FS_OpenReadFile (filepath, quiet, false, 16); +} + + +/* +==================== +FS_FileFromData + +Open a file. The syntax is the same as fopen +==================== +*/ +qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet) +{ + qfile_t* file; + file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); + memset (file, 0, sizeof (*file)); + file->flags = QFILE_FLAG_DATA; + file->ungetc = EOF; + file->real_length = size; + file->data = data; + return file; +} + +/* +==================== +FS_Close + +Close a file +==================== +*/ +int FS_Close (qfile_t* file) +{ + if(file->flags & QFILE_FLAG_DATA) + { + Mem_Free(file); + return 0; + } + + if (close (file->handle)) + return EOF; + + if (file->filename) + { + if (file->flags & QFILE_FLAG_REMOVE) + remove(file->filename); + + Mem_Free((void *) file->filename); + } + + if (file->ztk) + { + qz_inflateEnd (&file->ztk->zstream); + Mem_Free (file->ztk); + } + + Mem_Free (file); + return 0; +} + +void FS_RemoveOnClose(qfile_t* file) +{ + file->flags |= QFILE_FLAG_REMOVE; +} + +/* +==================== +FS_Write + +Write "datasize" bytes into a file +==================== +*/ +fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize) +{ + fs_offset_t result; + + // If necessary, seek to the exact file position we're supposed to be + if (file->buff_ind != file->buff_len) + lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR); + + // Purge cached data + FS_Purge (file); + + // Write the buffer and update the position + result = write (file->handle, data, (fs_offset_t)datasize); + file->position = lseek (file->handle, 0, SEEK_CUR); + if (file->real_length < file->position) + file->real_length = file->position; + + if (result < 0) + return 0; + + return result; +} + + +/* +==================== +FS_Read + +Read up to "buffersize" bytes from a file +==================== +*/ +fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize) +{ + fs_offset_t count, done; + + if (buffersize == 0) + return 0; + + // Get rid of the ungetc character + if (file->ungetc != EOF) + { + ((char*)buffer)[0] = file->ungetc; + buffersize--; + file->ungetc = EOF; + done = 1; + } + else + done = 0; + + if(file->flags & QFILE_FLAG_DATA) + { + size_t left = file->real_length - file->position; + if(buffersize > left) + buffersize = left; + memcpy(buffer, file->data + file->position, buffersize); + file->position += buffersize; + return buffersize; + } + + // First, we copy as many bytes as we can from "buff" + if (file->buff_ind < file->buff_len) + { + count = file->buff_len - file->buff_ind; + count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize; + done += count; + memcpy (buffer, &file->buff[file->buff_ind], count); + file->buff_ind += count; + + buffersize -= count; + if (buffersize == 0) + return done; + } + + // NOTE: at this point, the read buffer is always empty + + // If the file isn't compressed + if (! (file->flags & QFILE_FLAG_DEFLATED)) + { + fs_offset_t nb; + + // We must take care to not read after the end of the file + count = file->real_length - file->position; + + // If we have a lot of data to get, put them directly into "buffer" + if (buffersize > sizeof (file->buff) / 2) + { + if (count > (fs_offset_t)buffersize) + count = (fs_offset_t)buffersize; + lseek (file->handle, file->offset + file->position, SEEK_SET); + nb = read (file->handle, &((unsigned char*)buffer)[done], count); + if (nb > 0) + { + done += nb; + file->position += nb; + + // Purge cached data + FS_Purge (file); + } + } + else + { + if (count > (fs_offset_t)sizeof (file->buff)) + count = (fs_offset_t)sizeof (file->buff); + lseek (file->handle, file->offset + file->position, SEEK_SET); + nb = read (file->handle, file->buff, count); + if (nb > 0) + { + file->buff_len = nb; + file->position += nb; + + // Copy the requested data in "buffer" (as much as we can) + count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize; + memcpy (&((unsigned char*)buffer)[done], file->buff, count); + file->buff_ind = count; + done += count; + } + } + + return done; + } + + // If the file is compressed, it's more complicated... + // We cycle through a few operations until we have read enough data + while (buffersize > 0) + { + ztoolkit_t *ztk = file->ztk; + int error; + + // NOTE: at this point, the read buffer is always empty + + // If "input" is also empty, we need to refill it + if (ztk->in_ind == ztk->in_len) + { + // If we are at the end of the file + if (file->position == file->real_length) + return done; + + count = (fs_offset_t)(ztk->comp_length - ztk->in_position); + if (count > (fs_offset_t)sizeof (ztk->input)) + count = (fs_offset_t)sizeof (ztk->input); + lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET); + if (read (file->handle, ztk->input, count) != count) + { + Con_Printf ("FS_Read: unexpected end of file\n"); + break; + } + + ztk->in_ind = 0; + ztk->in_len = count; + ztk->in_position += count; + } + + ztk->zstream.next_in = &ztk->input[ztk->in_ind]; + ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind); + + // Now that we are sure we have compressed data available, we need to determine + // if it's better to inflate it in "file->buff" or directly in "buffer" + + // Inflate the data in "file->buff" + if (buffersize < sizeof (file->buff) / 2) + { + ztk->zstream.next_out = file->buff; + ztk->zstream.avail_out = sizeof (file->buff); + error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + { + Con_Printf ("FS_Read: Can't inflate file\n"); + break; + } + ztk->in_ind = ztk->in_len - ztk->zstream.avail_in; + + file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out; + file->position += file->buff_len; + + // Copy the requested data in "buffer" (as much as we can) + count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize; + memcpy (&((unsigned char*)buffer)[done], file->buff, count); + file->buff_ind = count; + } + + // Else, we inflate directly in "buffer" + else + { + ztk->zstream.next_out = &((unsigned char*)buffer)[done]; + ztk->zstream.avail_out = (unsigned int)buffersize; + error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + { + Con_Printf ("FS_Read: Can't inflate file\n"); + break; + } + ztk->in_ind = ztk->in_len - ztk->zstream.avail_in; + + // How much data did it inflate? + count = (fs_offset_t)(buffersize - ztk->zstream.avail_out); + file->position += count; + + // Purge cached data + FS_Purge (file); + } + + done += count; + buffersize -= count; + } + + return done; +} + + +/* +==================== +FS_Print + +Print a string into a file +==================== +*/ +int FS_Print (qfile_t* file, const char *msg) +{ + return (int)FS_Write (file, msg, strlen (msg)); +} + +/* +==================== +FS_Printf + +Print a string into a file +==================== +*/ +int FS_Printf(qfile_t* file, const char* format, ...) +{ + int result; + va_list args; + + va_start (args, format); + result = FS_VPrintf (file, format, args); + va_end (args); + + return result; +} + + +/* +==================== +FS_VPrintf + +Print a string into a file +==================== +*/ +int FS_VPrintf (qfile_t* file, const char* format, va_list ap) +{ + int len; + fs_offset_t buff_size = MAX_INPUTLINE; + char *tempbuff; + + for (;;) + { + tempbuff = (char *)Mem_Alloc (tempmempool, buff_size); + len = dpvsnprintf (tempbuff, buff_size, format, ap); + if (len >= 0 && len < buff_size) + break; + Mem_Free (tempbuff); + buff_size *= 2; + } + + len = write (file->handle, tempbuff, len); + Mem_Free (tempbuff); + + return len; +} + + +/* +==================== +FS_Getc + +Get the next character of a file +==================== +*/ +int FS_Getc (qfile_t* file) +{ + unsigned char c; + + if (FS_Read (file, &c, 1) != 1) + return EOF; + + return c; +} + + +/* +==================== +FS_UnGetc + +Put a character back into the read buffer (only supports one character!) +==================== +*/ +int FS_UnGetc (qfile_t* file, unsigned char c) +{ + // If there's already a character waiting to be read + if (file->ungetc != EOF) + return EOF; + + file->ungetc = c; + return c; +} + + +/* +==================== +FS_Seek + +Move the position index in a file +==================== +*/ +int FS_Seek (qfile_t* file, fs_offset_t offset, int whence) +{ + ztoolkit_t *ztk; + unsigned char* buffer; + fs_offset_t buffersize; + + // Compute the file offset + switch (whence) + { + case SEEK_CUR: + offset += file->position - file->buff_len + file->buff_ind; + break; + + case SEEK_SET: + break; + + case SEEK_END: + offset += file->real_length; + break; + + default: + return -1; + } + if (offset < 0 || offset > file->real_length) + return -1; + + if(file->flags & QFILE_FLAG_DATA) + { + file->position = offset; + return 0; + } + + // If we have the data in our read buffer, we don't need to actually seek + if (file->position - file->buff_len <= offset && offset <= file->position) + { + file->buff_ind = offset + file->buff_len - file->position; + return 0; + } + + // Purge cached data + FS_Purge (file); + + // Unpacked or uncompressed files can seek directly + if (! (file->flags & QFILE_FLAG_DEFLATED)) + { + if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1) + return -1; + file->position = offset; + return 0; + } + + // Seeking in compressed files is more a hack than anything else, + // but we need to support it, so here we go. + ztk = file->ztk; + + // If we have to go back in the file, we need to restart from the beginning + if (offset <= file->position) + { + ztk->in_ind = 0; + ztk->in_len = 0; + ztk->in_position = 0; + file->position = 0; + lseek (file->handle, file->offset, SEEK_SET); + + // Reset the Zlib stream + ztk->zstream.next_in = ztk->input; + ztk->zstream.avail_in = 0; + qz_inflateReset (&ztk->zstream); + } + + // We need a big buffer to force inflating into it directly + buffersize = 2 * sizeof (file->buff); + buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize); + + // Skip all data until we reach the requested offset + while (offset > file->position) + { + fs_offset_t diff = offset - file->position; + fs_offset_t count, len; + + count = (diff > buffersize) ? buffersize : diff; + len = FS_Read (file, buffer, count); + if (len != count) + { + Mem_Free (buffer); + return -1; + } + } + + Mem_Free (buffer); + return 0; +} + + +/* +==================== +FS_Tell + +Give the current position in a file +==================== +*/ +fs_offset_t FS_Tell (qfile_t* file) +{ + return file->position - file->buff_len + file->buff_ind; +} + + +/* +==================== +FS_FileSize + +Give the total size of a file +==================== +*/ +fs_offset_t FS_FileSize (qfile_t* file) +{ + return file->real_length; +} + + +/* +==================== +FS_Purge + +Erases any buffered input or output data +==================== +*/ +void FS_Purge (qfile_t* file) +{ + file->buff_len = 0; + file->buff_ind = 0; + file->ungetc = EOF; +} + + +/* +============ +FS_LoadFile + +Filename are relative to the quake directory. +Always appends a 0 byte. +============ +*/ +unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer) +{ + qfile_t *file; + unsigned char *buf = NULL; + fs_offset_t filesize = 0; + + file = FS_OpenVirtualFile(path, quiet); + if (file) + { + filesize = file->real_length; + if(filesize < 0) + { + Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false"); + FS_Close(file); + return NULL; + } + + buf = (unsigned char *)Mem_Alloc (pool, filesize + 1); + buf[filesize] = '\0'; + FS_Read (file, buf, filesize); + FS_Close (file); + if (developer_loadfile.integer) + Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize); + } + + if (filesizepointer) + *filesizepointer = filesize; + return buf; +} + + +/* +============ +FS_WriteFile + +The filename will be prefixed by the current game directory +============ +*/ +qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count) +{ + qfile_t *file; + size_t i; + fs_offset_t lentotal; + + file = FS_OpenRealFile(filename, "wb", false); + if (!file) + { + Con_Printf("FS_WriteFile: failed on %s\n", filename); + return false; + } + + lentotal = 0; + for(i = 0; i < count; ++i) + lentotal += len[i]; + Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal); + for(i = 0; i < count; ++i) + FS_Write (file, data[i], len[i]); + FS_Close (file); + return true; +} + +qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len) +{ + return FS_WriteFileInBlocks(filename, &data, &len, 1); +} + + +/* +============================================================================= + +OTHERS PUBLIC FUNCTIONS + +============================================================================= +*/ + +/* +============ +FS_StripExtension +============ +*/ +void FS_StripExtension (const char *in, char *out, size_t size_out) +{ + char *last = NULL; + char currentchar; + + if (size_out == 0) + return; + + while ((currentchar = *in) && size_out > 1) + { + if (currentchar == '.') + last = out; + else if (currentchar == '/' || currentchar == '\\' || currentchar == ':') + last = NULL; + *out++ = currentchar; + in++; + size_out--; + } + if (last) + *last = 0; + else + *out = 0; +} + + +/* +================== +FS_DefaultExtension +================== +*/ +void FS_DefaultExtension (char *path, const char *extension, size_t size_path) +{ + const char *src; + + // if path doesn't have a .EXT, append extension + // (extension should include the .) + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strlcat (path, extension, size_path); +} + + +/* +================== +FS_FileType + +Look for a file in the packages and in the filesystem +================== +*/ +int FS_FileType (const char *filename) +{ + searchpath_t *search; + char fullpath[MAX_OSPATH]; + + search = FS_FindFile (filename, NULL, true); + if(!search) + return FS_FILETYPE_NONE; + + if(search->pack && !search->pack->vpack) + return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later + + dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename); + return FS_SysFileType(fullpath); +} + + +/* +================== +FS_FileExists + +Look for a file in the packages and in the filesystem +================== +*/ +qboolean FS_FileExists (const char *filename) +{ + return (FS_FindFile (filename, NULL, true) != NULL); +} + + +/* +================== +FS_SysFileExists + +Look for a file in the filesystem only +================== +*/ +int FS_SysFileType (const char *path) +{ +#if WIN32 +// Sajt - some older sdks are missing this define +# ifndef INVALID_FILE_ATTRIBUTES +# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +# endif + + DWORD result = GetFileAttributes(path); + + if(result == INVALID_FILE_ATTRIBUTES) + return FS_FILETYPE_NONE; + + if(result & FILE_ATTRIBUTE_DIRECTORY) + return FS_FILETYPE_DIRECTORY; + + return FS_FILETYPE_FILE; +#else + struct stat buf; + + if (stat (path,&buf) == -1) + return FS_FILETYPE_NONE; + +#ifndef S_ISDIR +#define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR) +#endif + if(S_ISDIR(buf.st_mode)) + return FS_FILETYPE_DIRECTORY; + + return FS_FILETYPE_FILE; +#endif +} + +qboolean FS_SysFileExists (const char *path) +{ + return FS_SysFileType (path) != FS_FILETYPE_NONE; +} + +void FS_mkdir (const char *path) +{ +#if WIN32 + _mkdir (path); +#else + mkdir (path, 0777); +#endif +} + +/* +=========== +FS_Search + +Allocate and fill a search structure with information on matching filenames. +=========== +*/ +fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet) +{ + fssearch_t *search; + searchpath_t *searchpath; + pack_t *pak; + int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex; + stringlist_t resultlist; + stringlist_t dirlist; + const char *slash, *backslash, *colon, *separator; + char *basepath; + char temp[MAX_OSPATH]; + + for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++) + ; + + if (i > 0) + { + Con_Printf("Don't use punctuation at the beginning of a search pattern!\n"); + return NULL; + } + + stringlistinit(&resultlist); + stringlistinit(&dirlist); + search = NULL; + slash = strrchr(pattern, '/'); + backslash = strrchr(pattern, '\\'); + colon = strrchr(pattern, ':'); + separator = max(slash, backslash); + separator = max(separator, colon); + basepathlength = separator ? (separator + 1 - pattern) : 0; + basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1); + if (basepathlength) + memcpy(basepath, pattern, basepathlength); + basepath[basepathlength] = 0; + + // search through the path, one element at a time + for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next) + { + // is the element a pak file? + if (searchpath->pack && !searchpath->pack->vpack) + { + // look through all the pak file elements + pak = searchpath->pack; + for (i = 0;i < pak->numfiles;i++) + { + strlcpy(temp, pak->files[i].name, sizeof(temp)); + while (temp[0]) + { + if (matchpattern(temp, (char *)pattern, true)) + { + for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) + if (!strcmp(resultlist.strings[resultlistindex], temp)) + break; + if (resultlistindex == resultlist.numstrings) + { + stringlistappend(&resultlist, temp); + if (!quiet && developer_loading.integer) + Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp); + } + } + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = strrchr(temp, '/'); + backslash = strrchr(temp, '\\'); + colon = strrchr(temp, ':'); + separator = temp; + if (separator < slash) + separator = slash; + if (separator < backslash) + separator = backslash; + if (separator < colon) + separator = colon; + *((char *)separator) = 0; + } + } + } + else + { + stringlist_t matchedSet, foundSet; + const char *start = pattern; + + stringlistinit(&matchedSet); + stringlistinit(&foundSet); + // add a first entry to the set + stringlistappend(&matchedSet, ""); + // iterate through pattern's path + while (*start) + { + const char *asterisk, *wildcard, *nextseparator, *prevseparator; + char subpath[MAX_OSPATH]; + char subpattern[MAX_OSPATH]; + + // find the next wildcard + wildcard = strchr(start, '?'); + asterisk = strchr(start, '*'); + if (asterisk && (!wildcard || asterisk < wildcard)) + { + wildcard = asterisk; + } + + if (wildcard) + { + nextseparator = strchr( wildcard, '/' ); + } + else + { + nextseparator = NULL; + } + + if( !nextseparator ) { + nextseparator = start + strlen( start ); + } + + // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string) + // copy everything up except nextseperator + strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1))); + // find the last '/' before the wildcard + prevseparator = strrchr( subpattern, '/' ); + if (!prevseparator) + prevseparator = subpattern; + else + prevseparator++; + // copy everything from start to the previous including the '/' (before the wildcard) + // everything up to start is already included in the path of matchedSet's entries + strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1))); + + // for each entry in matchedSet try to open the subdirectories specified in subpath + for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) { + strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) ); + strlcat( temp, subpath, sizeof(temp) ); + listdirectory( &foundSet, searchpath->filename, temp ); + } + if( dirlistindex == 0 ) { + break; + } + // reset the current result set + stringlistfreecontents( &matchedSet ); + // match against the pattern + for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) { + const char *direntry = foundSet.strings[ dirlistindex ]; + if (matchpattern(direntry, subpattern, true)) { + stringlistappend( &matchedSet, direntry ); + } + } + stringlistfreecontents( &foundSet ); + + start = nextseparator; + } + + for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++) + { + const char *temp = matchedSet.strings[dirlistindex]; + if (matchpattern(temp, (char *)pattern, true)) + { + for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) + if (!strcmp(resultlist.strings[resultlistindex], temp)) + break; + if (resultlistindex == resultlist.numstrings) + { + stringlistappend(&resultlist, temp); + if (!quiet && developer_loading.integer) + Con_Printf("SearchDirFile: %s\n", temp); + } + } + } + stringlistfreecontents( &matchedSet ); + } + } + + if (resultlist.numstrings) + { + stringlistsort(&resultlist, true); + numfiles = resultlist.numstrings; + numchars = 0; + for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) + numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1; + search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *)); + search->filenames = (char **)((char *)search + sizeof(fssearch_t)); + search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *)); + search->numfilenames = (int)numfiles; + numfiles = 0; + numchars = 0; + for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) + { + size_t textlen; + search->filenames[numfiles] = search->filenamesbuffer + numchars; + textlen = strlen(resultlist.strings[resultlistindex]) + 1; + memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen); + numfiles++; + numchars += (int)textlen; + } + } + stringlistfreecontents(&resultlist); + + Mem_Free(basepath); + return search; +} + +void FS_FreeSearch(fssearch_t *search) +{ + Z_Free(search); +} + +extern int con_linewidth; +int FS_ListDirectory(const char *pattern, int oneperline) +{ + int numfiles; + int numcolumns; + int numlines; + int columnwidth; + int linebufpos; + int i, j, k, l; + const char *name; + char linebuf[MAX_INPUTLINE]; + fssearch_t *search; + search = FS_Search(pattern, true, true); + if (!search) + return 0; + numfiles = search->numfilenames; + if (!oneperline) + { + // FIXME: the names could be added to one column list and then + // gradually shifted into the next column if they fit, and then the + // next to make a compact variable width listing but it's a lot more + // complicated... + // find width for columns + columnwidth = 0; + for (i = 0;i < numfiles;i++) + { + l = (int)strlen(search->filenames[i]); + if (columnwidth < l) + columnwidth = l; + } + // count the spacing character + columnwidth++; + // calculate number of columns + numcolumns = con_linewidth / columnwidth; + // don't bother with the column printing if it's only one column + if (numcolumns >= 2) + { + numlines = (numfiles + numcolumns - 1) / numcolumns; + for (i = 0;i < numlines;i++) + { + linebufpos = 0; + for (k = 0;k < numcolumns;k++) + { + l = i * numcolumns + k; + if (l < numfiles) + { + name = search->filenames[l]; + for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++) + linebuf[linebufpos++] = name[j]; + // space out name unless it's the last on the line + if (k + 1 < numcolumns && l + 1 < numfiles) + for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++) + linebuf[linebufpos++] = ' '; + } + } + linebuf[linebufpos] = 0; + Con_Printf("%s\n", linebuf); + } + } + else + oneperline = true; + } + if (oneperline) + for (i = 0;i < numfiles;i++) + Con_Printf("%s\n", search->filenames[i]); + FS_FreeSearch(search); + return (int)numfiles; +} + +static void FS_ListDirectoryCmd (const char* cmdname, int oneperline) +{ + const char *pattern; + if (Cmd_Argc() >= 3) + { + Con_Printf("usage:\n%s [path/pattern]\n", cmdname); + return; + } + if (Cmd_Argc() == 2) + pattern = Cmd_Argv(1); + else + pattern = "*"; + if (!FS_ListDirectory(pattern, oneperline)) + Con_Print("No files found.\n"); +} + +void FS_Dir_f(void) +{ + FS_ListDirectoryCmd("dir", true); +} + +void FS_Ls_f(void) +{ + FS_ListDirectoryCmd("ls", false); +} + +void FS_Which_f(void) +{ + const char *filename; + int index; + searchpath_t *sp; + if (Cmd_Argc() != 2) + { + Con_Printf("usage:\n%s \n", Cmd_Argv(0)); + return; + } + filename = Cmd_Argv(1); + sp = FS_FindFile(filename, &index, true); + if (!sp) { + Con_Printf("%s isn't anywhere\n", filename); + return; + } + if (sp->pack) + { + if(sp->pack->vpack) + Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname); + else + Con_Printf("%s is in package %s\n", filename, sp->pack->shortname); + } + else + Con_Printf("%s is file %s%s\n", filename, sp->filename, filename); +} + + +const char *FS_WhichPack(const char *filename) +{ + int index; + searchpath_t *sp = FS_FindFile(filename, &index, true); + if(sp && sp->pack) + return sp->pack->shortname; + else + return 0; +} + +/* +==================== +FS_IsRegisteredQuakePack + +Look for a proof of purchase file file in the requested package + +If it is found, this file should NOT be downloaded. +==================== +*/ +qboolean FS_IsRegisteredQuakePack(const char *name) +{ + searchpath_t *search; + pack_t *pak; + + // search through the path, one element at a time + for (search = fs_searchpaths;search;search = search->next) + { + if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name)) + // TODO do we want to support vpacks in here too? + { + int (*strcmp_funct) (const char* str1, const char* str2); + int left, right, middle; + + pak = search->pack; + strcmp_funct = pak->ignorecase ? strcasecmp : strcmp; + + // Look for the file (binary search) + left = 0; + right = pak->numfiles - 1; + while (left <= right) + { + int diff; + + middle = (left + right) / 2; + diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp"); + + // Found it + if (!diff) + return true; + + // If we're too far in the list + if (diff > 0) + right = middle - 1; + else + left = middle + 1; + } + + // we found the requested pack but it is not registered quake + return false; + } + } + + return false; +} + +int FS_CRCFile(const char *filename, size_t *filesizepointer) +{ + int crc = -1; + unsigned char *filedata; + fs_offset_t filesize; + if (filesizepointer) + *filesizepointer = 0; + if (!filename || !*filename) + return crc; + filedata = FS_LoadFile(filename, tempmempool, true, &filesize); + if (filedata) + { + if (filesizepointer) + *filesizepointer = filesize; + crc = CRC_Block(filedata, filesize); + Mem_Free(filedata); + } + return crc; +} + +unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool) +{ + z_stream strm; + unsigned char *out = NULL; + unsigned char *tmp; + + *deflated_size = 0; +#ifndef LINK_TO_ZLIB + if(!zlib_dll) + return NULL; +#endif + + memset(&strm, 0, sizeof(strm)); + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + if(level < 0) + level = Z_DEFAULT_COMPRESSION; + + if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK) + { + Con_Printf("FS_Deflate: deflate init error!\n"); + return NULL; + } + + strm.next_in = (unsigned char*)data; + strm.avail_in = size; + + tmp = (unsigned char *) Mem_Alloc(tempmempool, size); + if(!tmp) + { + Con_Printf("FS_Deflate: not enough memory in tempmempool!\n"); + qz_deflateEnd(&strm); + return NULL; + } + + strm.next_out = tmp; + strm.avail_out = size; + + if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END) + { + Con_Printf("FS_Deflate: deflate failed!\n"); + qz_deflateEnd(&strm); + Mem_Free(tmp); + return NULL; + } + + if(qz_deflateEnd(&strm) != Z_OK) + { + Con_Printf("FS_Deflate: deflateEnd failed\n"); + Mem_Free(tmp); + return NULL; + } + + if(strm.total_out >= size) + { + Con_Printf("FS_Deflate: deflate is useless on this data!\n"); + Mem_Free(tmp); + return NULL; + } + + out = (unsigned char *) Mem_Alloc(mempool, strm.total_out); + if(!out) + { + Con_Printf("FS_Deflate: not enough memory in target mempool!\n"); + Mem_Free(tmp); + return NULL; + } + + if(deflated_size) + *deflated_size = (size_t)strm.total_out; + + memcpy(out, tmp, strm.total_out); + Mem_Free(tmp); + + return out; +} + +static void AssertBufsize(sizebuf_t *buf, int length) +{ + if(buf->cursize + length > buf->maxsize) + { + int oldsize = buf->maxsize; + unsigned char *olddata; + olddata = buf->data; + buf->maxsize += length; + buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize); + if(olddata) + { + memcpy(buf->data, olddata, oldsize); + Mem_Free(olddata); + } + } +} + +unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool) +{ + int ret; + z_stream strm; + unsigned char *out = NULL; + unsigned char tmp[2048]; + unsigned int have; + sizebuf_t outbuf; + + *inflated_size = 0; +#ifndef LINK_TO_ZLIB + if(!zlib_dll) + return NULL; +#endif + + memset(&outbuf, 0, sizeof(outbuf)); + outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp)); + outbuf.maxsize = sizeof(tmp); + + memset(&strm, 0, sizeof(strm)); + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK) + { + Con_Printf("FS_Inflate: inflate init error!\n"); + Mem_Free(outbuf.data); + return NULL; + } + + strm.next_in = (unsigned char*)data; + strm.avail_in = size; + + do + { + strm.next_out = tmp; + strm.avail_out = sizeof(tmp); + ret = qz_inflate(&strm, Z_NO_FLUSH); + // it either returns Z_OK on progress, Z_STREAM_END on end + // or an error code + switch(ret) + { + case Z_STREAM_END: + case Z_OK: + break; + + case Z_STREAM_ERROR: + Con_Print("FS_Inflate: stream error!\n"); + break; + case Z_DATA_ERROR: + Con_Print("FS_Inflate: data error!\n"); + break; + case Z_MEM_ERROR: + Con_Print("FS_Inflate: mem error!\n"); + break; + case Z_BUF_ERROR: + Con_Print("FS_Inflate: buf error!\n"); + break; + default: + Con_Print("FS_Inflate: unknown error!\n"); + break; + + } + if(ret != Z_OK && ret != Z_STREAM_END) + { + Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in); + Mem_Free(outbuf.data); + qz_inflateEnd(&strm); + return NULL; + } + have = sizeof(tmp) - strm.avail_out; + AssertBufsize(&outbuf, max(have, sizeof(tmp))); + SZ_Write(&outbuf, tmp, have); + } while(ret != Z_STREAM_END); + + qz_inflateEnd(&strm); + + out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize); + if(!out) + { + Con_Printf("FS_Inflate: not enough memory in target mempool!\n"); + Mem_Free(outbuf.data); + return NULL; + } + + memcpy(out, outbuf.data, outbuf.cursize); + Mem_Free(outbuf.data); + + if(inflated_size) + *inflated_size = (size_t)outbuf.cursize; + + return out; +} diff --git a/misc/source/darkplaces-src/fs.h b/misc/source/darkplaces-src/fs.h new file mode 100644 index 00000000..7ffba78e --- /dev/null +++ b/misc/source/darkplaces-src/fs.h @@ -0,0 +1,139 @@ +/* + DarkPlaces file system + + Copyright (C) 2003-2005 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + +#ifndef FS_H +#define FS_H + + +// ------ Types ------ // + +typedef struct qfile_s qfile_t; + +#ifdef WIN32 +//typedef long fs_offset_t; // 32bit +typedef __int64 fs_offset_t; ///< 64bit (lots of warnings, and read/write still don't take 64bit on win64) +#else +typedef long long fs_offset_t; +#endif + + + +// ------ Variables ------ // + +extern char fs_gamedir [MAX_OSPATH]; +extern char fs_basedir [MAX_OSPATH]; +extern char fs_userdir [MAX_OSPATH]; + +// list of active game directories (empty if not running a mod) +#define MAX_GAMEDIRS 16 +extern int fs_numgamedirs; +extern char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH]; + + +// ------ Main functions ------ // + +// IMPORTANT: the file path is automatically prefixed by the current game directory for +// each file created by FS_WriteFile, or opened in "write" or "append" mode by FS_OpenRealFile + +qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs); // already_loaded may be NULL if caller does not care +const char *FS_WhichPack(const char *filename); +void FS_CreatePath (char *path); +int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking); // uses absolute path +qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking); // uses absolute path +qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet); +qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet); +qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet); +int FS_Close (qfile_t* file); +void FS_RemoveOnClose(qfile_t* file); +fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize); +fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize); +int FS_Print(qfile_t* file, const char *msg); +int FS_Printf(qfile_t* file, const char* format, ...) DP_FUNC_PRINTF(2); +int FS_VPrintf(qfile_t* file, const char* format, va_list ap); +int FS_Getc (qfile_t* file); +int FS_UnGetc (qfile_t* file, unsigned char c); +int FS_Seek (qfile_t* file, fs_offset_t offset, int whence); +fs_offset_t FS_Tell (qfile_t* file); +fs_offset_t FS_FileSize (qfile_t* file); +void FS_Purge (qfile_t* file); +const char *FS_FileWithoutPath (const char *in); +const char *FS_FileExtension (const char *in); +int FS_CheckNastyPath (const char *path, qboolean isgamedir); + +extern const char *const fs_checkgamedir_missing; // "(missing)" +const char *FS_CheckGameDir(const char *gamedir); // returns NULL if nasty, fs_checkgamedir_missing (exact pointer) if missing + +typedef struct +{ + char name[MAX_OSPATH]; + char description[8192]; +} +gamedir_t; +extern gamedir_t *fs_all_gamedirs; // terminated by entry with empty name +extern int fs_all_gamedirs_count; + +qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing); +qboolean FS_IsRegisteredQuakePack(const char *name); +int FS_CRCFile(const char *filename, size_t *filesizepointer); +void FS_Rescan(void); + +typedef struct fssearch_s +{ + int numfilenames; + char **filenames; + // array of filenames + char *filenamesbuffer; +} +fssearch_t; + +fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet); +void FS_FreeSearch(fssearch_t *search); + +unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer); +qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count); +qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len); + + +// ------ Other functions ------ // + +void FS_StripExtension (const char *in, char *out, size_t size_out); +void FS_DefaultExtension (char *path, const char *extension, size_t size_path); + +#define FS_FILETYPE_NONE 0 +#define FS_FILETYPE_FILE 1 +#define FS_FILETYPE_DIRECTORY 2 +int FS_FileType (const char *filename); // the file can be into a package +int FS_SysFileType (const char *filename); // only look for files outside of packages + +qboolean FS_FileExists (const char *filename); // the file can be into a package +qboolean FS_SysFileExists (const char *filename); // only look for files outside of packages + +void FS_mkdir (const char *path); + +unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool); +unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool); + +qboolean FS_HasZlib(void); + +#endif diff --git a/misc/source/darkplaces-src/ft2.c b/misc/source/darkplaces-src/ft2.c new file mode 100644 index 00000000..484a3159 --- /dev/null +++ b/misc/source/darkplaces-src/ft2.c @@ -0,0 +1,1589 @@ +/* FreeType 2 and UTF-8 encoding support for + * DarkPlaces + */ +#include "quakedef.h" + +#include "ft2.h" +#include "ft2_defs.h" +#include "ft2_fontdefs.h" +#include "image.h" + +static int img_fontmap[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // shift+digit line + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // digits + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // caps + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // caps + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // small + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // small + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // specials + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // faces + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* +================================================================================ +CVars introduced with the freetype extension +================================================================================ +*/ + +cvar_t r_font_disable_freetype = {CVAR_SAVE, "r_font_disable_freetype", "1", "disable freetype support for fonts entirely"}; +cvar_t r_font_use_alpha_textures = {CVAR_SAVE, "r_font_use_alpha_textures", "0", "use alpha-textures for font rendering, this should safe memory"}; +cvar_t r_font_size_snapping = {CVAR_SAVE, "r_font_size_snapping", "1", "stick to good looking font sizes whenever possible - bad when the mod doesn't support it!"}; +cvar_t r_font_kerning = {CVAR_SAVE, "r_font_kerning", "1", "Use kerning if available"}; +cvar_t r_font_diskcache = {CVAR_SAVE, "r_font_diskcache", "0", "save font textures to disk for future loading rather than generating them every time"}; +cvar_t r_font_compress = {CVAR_SAVE, "r_font_compress", "0", "use texture compression on font textures to save video memory"}; +cvar_t r_font_nonpoweroftwo = {CVAR_SAVE, "r_font_nonpoweroftwo", "1", "use nonpoweroftwo textures for font (saves memory, potentially slower)"}; +cvar_t developer_font = {CVAR_SAVE, "developer_font", "0", "prints debug messages about fonts"}; + +/* +================================================================================ +Function definitions. Taken from the freetype2 headers. +================================================================================ +*/ + + +FT_EXPORT( FT_Error ) +(*qFT_Init_FreeType)( FT_Library *alibrary ); +FT_EXPORT( FT_Error ) +(*qFT_Done_FreeType)( FT_Library library ); +/* +FT_EXPORT( FT_Error ) +(*qFT_New_Face)( FT_Library library, + const char* filepathname, + FT_Long face_index, + FT_Face *aface ); +*/ +FT_EXPORT( FT_Error ) +(*qFT_New_Memory_Face)( FT_Library library, + const FT_Byte* file_base, + FT_Long file_size, + FT_Long face_index, + FT_Face *aface ); +FT_EXPORT( FT_Error ) +(*qFT_Done_Face)( FT_Face face ); +FT_EXPORT( FT_Error ) +(*qFT_Select_Size)( FT_Face face, + FT_Int strike_index ); +FT_EXPORT( FT_Error ) +(*qFT_Request_Size)( FT_Face face, + FT_Size_Request req ); +FT_EXPORT( FT_Error ) +(*qFT_Set_Char_Size)( FT_Face face, + FT_F26Dot6 char_width, + FT_F26Dot6 char_height, + FT_UInt horz_resolution, + FT_UInt vert_resolution ); +FT_EXPORT( FT_Error ) +(*qFT_Set_Pixel_Sizes)( FT_Face face, + FT_UInt pixel_width, + FT_UInt pixel_height ); +FT_EXPORT( FT_Error ) +(*qFT_Load_Glyph)( FT_Face face, + FT_UInt glyph_index, + FT_Int32 load_flags ); +FT_EXPORT( FT_Error ) +(*qFT_Load_Char)( FT_Face face, + FT_ULong char_code, + FT_Int32 load_flags ); +FT_EXPORT( FT_UInt ) +(*qFT_Get_Char_Index)( FT_Face face, + FT_ULong charcode ); +FT_EXPORT( FT_Error ) +(*qFT_Render_Glyph)( FT_GlyphSlot slot, + FT_Render_Mode render_mode ); +FT_EXPORT( FT_Error ) +(*qFT_Get_Kerning)( FT_Face face, + FT_UInt left_glyph, + FT_UInt right_glyph, + FT_UInt kern_mode, + FT_Vector *akerning ); +FT_EXPORT( FT_Error ) +(*qFT_Attach_Stream)( FT_Face face, + FT_Open_Args* parameters ); +/* +================================================================================ +Support for dynamically loading the FreeType2 library +================================================================================ +*/ + +static dllfunction_t ft2funcs[] = +{ + {"FT_Init_FreeType", (void **) &qFT_Init_FreeType}, + {"FT_Done_FreeType", (void **) &qFT_Done_FreeType}, + //{"FT_New_Face", (void **) &qFT_New_Face}, + {"FT_New_Memory_Face", (void **) &qFT_New_Memory_Face}, + {"FT_Done_Face", (void **) &qFT_Done_Face}, + {"FT_Select_Size", (void **) &qFT_Select_Size}, + {"FT_Request_Size", (void **) &qFT_Request_Size}, + {"FT_Set_Char_Size", (void **) &qFT_Set_Char_Size}, + {"FT_Set_Pixel_Sizes", (void **) &qFT_Set_Pixel_Sizes}, + {"FT_Load_Glyph", (void **) &qFT_Load_Glyph}, + {"FT_Load_Char", (void **) &qFT_Load_Char}, + {"FT_Get_Char_Index", (void **) &qFT_Get_Char_Index}, + {"FT_Render_Glyph", (void **) &qFT_Render_Glyph}, + {"FT_Get_Kerning", (void **) &qFT_Get_Kerning}, + {"FT_Attach_Stream", (void **) &qFT_Attach_Stream}, + {NULL, NULL} +}; + +/// Handle for FreeType2 DLL +static dllhandle_t ft2_dll = NULL; + +/// Memory pool for fonts +static mempool_t *font_mempool= NULL; + +/// FreeType library handle +static FT_Library font_ft2lib = NULL; + +#define POSTPROCESS_MAXRADIUS 8 +typedef struct +{ + unsigned char *buf, *buf2; + int bufsize, bufwidth, bufheight, bufpitch; + float blur, outline, shadowx, shadowy, shadowz; + int padding_t, padding_b, padding_l, padding_r, blurpadding_lt, blurpadding_rb, outlinepadding_t, outlinepadding_b, outlinepadding_l, outlinepadding_r; + unsigned char circlematrix[2*POSTPROCESS_MAXRADIUS+1][2*POSTPROCESS_MAXRADIUS+1]; + unsigned char gausstable[2*POSTPROCESS_MAXRADIUS+1]; +} +font_postprocess_t; +static font_postprocess_t pp; + +typedef struct fontfilecache_s +{ + unsigned char *buf; + fs_offset_t len; + int refcount; + char path[MAX_QPATH]; +} +fontfilecache_t; +#define MAX_FONTFILES 8 +static fontfilecache_t fontfiles[MAX_FONTFILES]; +static const unsigned char *fontfilecache_LoadFile(const char *path, qboolean quiet, fs_offset_t *filesizepointer) +{ + int i; + unsigned char *buf; + + for(i = 0; i < MAX_FONTFILES; ++i) + { + if(fontfiles[i].refcount > 0) + if(!strcmp(path, fontfiles[i].path)) + { + *filesizepointer = fontfiles[i].len; + ++fontfiles[i].refcount; + return fontfiles[i].buf; + } + } + + buf = FS_LoadFile(path, font_mempool, quiet, filesizepointer); + if(buf) + { + for(i = 0; i < MAX_FONTFILES; ++i) + if(fontfiles[i].refcount <= 0) + { + strlcpy(fontfiles[i].path, path, sizeof(fontfiles[i].path)); + fontfiles[i].len = *filesizepointer; + fontfiles[i].buf = buf; + fontfiles[i].refcount = 1; + return buf; + } + } + + return buf; +} +static void fontfilecache_Free(const unsigned char *buf) +{ + int i; + for(i = 0; i < MAX_FONTFILES; ++i) + { + if(fontfiles[i].refcount > 0) + if(fontfiles[i].buf == buf) + { + if(--fontfiles[i].refcount <= 0) + { + Mem_Free(fontfiles[i].buf); + fontfiles[i].buf = NULL; + } + return; + } + } + // if we get here, it used regular allocation + Mem_Free((void *) buf); +} +static void fontfilecache_FreeAll(void) +{ + int i; + for(i = 0; i < MAX_FONTFILES; ++i) + { + if(fontfiles[i].refcount > 0) + Mem_Free(fontfiles[i].buf); + fontfiles[i].buf = NULL; + fontfiles[i].refcount = 0; + } +} + +/* +==================== +Font_CloseLibrary + +Unload the FreeType2 DLL +==================== +*/ +void Font_CloseLibrary (void) +{ + fontfilecache_FreeAll(); + if (font_mempool) + Mem_FreePool(&font_mempool); + if (font_ft2lib && qFT_Done_FreeType) + { + qFT_Done_FreeType(font_ft2lib); + font_ft2lib = NULL; + } + Sys_UnloadLibrary (&ft2_dll); + pp.buf = NULL; +} + +/* +==================== +Font_OpenLibrary + +Try to load the FreeType2 DLL +==================== +*/ +qboolean Font_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libfreetype-6.dll", + "freetype6.dll", +#elif defined(MACOSX) + "libfreetype.6.dylib", + "libfreetype.dylib", +#else + "libfreetype.so.6", + "libfreetype.so", +#endif + NULL + }; + + if (r_font_disable_freetype.integer) + return false; + + // Already loaded? + if (ft2_dll) + return true; + + // Load the DLL + if (!Sys_LoadLibrary (dllnames, &ft2_dll, ft2funcs)) + return false; + return true; +} + +/* +==================== +Font_Init + +Initialize the freetype2 font subsystem +==================== +*/ + +void font_start(void) +{ + if (!Font_OpenLibrary()) + return; + + if (qFT_Init_FreeType(&font_ft2lib)) + { + Con_Print("ERROR: Failed to initialize the FreeType2 library!\n"); + Font_CloseLibrary(); + return; + } + + font_mempool = Mem_AllocPool("FONT", 0, NULL); + if (!font_mempool) + { + Con_Print("ERROR: Failed to allocate FONT memory pool!\n"); + Font_CloseLibrary(); + return; + } +} + +void font_shutdown(void) +{ + int i; + for (i = 0; i < dp_fonts.maxsize; ++i) + { + if (dp_fonts.f[i].ft2) + { + Font_UnloadFont(dp_fonts.f[i].ft2); + dp_fonts.f[i].ft2 = NULL; + } + } + Font_CloseLibrary(); +} + +void font_newmap(void) +{ +} + +void Font_Init(void) +{ + Cvar_RegisterVariable(&r_font_nonpoweroftwo); + Cvar_RegisterVariable(&r_font_disable_freetype); + Cvar_RegisterVariable(&r_font_use_alpha_textures); + Cvar_RegisterVariable(&r_font_size_snapping); + Cvar_RegisterVariable(&r_font_kerning); + Cvar_RegisterVariable(&r_font_diskcache); + Cvar_RegisterVariable(&r_font_compress); + Cvar_RegisterVariable(&developer_font); + + // let's open it at startup already + Font_OpenLibrary(); +} + +/* +================================================================================ +Implementation of a more or less lazy font loading and rendering code. +================================================================================ +*/ + +#include "ft2_fontdefs.h" + +ft2_font_t *Font_Alloc(void) +{ + if (!ft2_dll) + return NULL; + return (ft2_font_t *)Mem_Alloc(font_mempool, sizeof(ft2_font_t)); +} + +qboolean Font_Attach(ft2_font_t *font, ft2_attachment_t *attachment) +{ + ft2_attachment_t *na; + + font->attachmentcount++; + na = (ft2_attachment_t*)Mem_Alloc(font_mempool, sizeof(font->attachments[0]) * font->attachmentcount); + if (na == NULL) + return false; + if (font->attachments && font->attachmentcount > 1) + { + memcpy(na, font->attachments, sizeof(font->attachments[0]) * (font->attachmentcount - 1)); + Mem_Free(font->attachments); + } + memcpy(na + sizeof(font->attachments[0]) * (font->attachmentcount - 1), attachment, sizeof(*attachment)); + font->attachments = na; + return true; +} + +float Font_VirtualToRealSize(float sz) +{ + int vh; + //int vw; + int si; + float sn; + if(sz < 0) + return sz; + //vw = ((vid.width > 0) ? vid.width : vid_width.value); + vh = ((vid.height > 0) ? vid.height : vid_height.value); + // now try to scale to our actual size: + sn = sz * vh / vid_conheight.value; + si = (int)sn; + if ( sn - (float)si >= 0.5 ) + ++si; + return si; +} + +float Font_SnapTo(float val, float snapwidth) +{ + return floor(val / snapwidth + 0.5f) * snapwidth; +} + +static qboolean Font_LoadFile(const char *name, int _face, ft2_settings_t *settings, ft2_font_t *font); +static qboolean Font_LoadSize(ft2_font_t *font, float size, qboolean check_only); +qboolean Font_LoadFont(const char *name, dp_font_t *dpfnt) +{ + int s, count, i; + ft2_font_t *ft2, *fbfont, *fb; + + ft2 = Font_Alloc(); + if (!ft2) + { + dpfnt->ft2 = NULL; + return false; + } + + // check if a fallback font has been specified, if it has been, and the + // font fails to load, use the image font as main font + for (i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + if (dpfnt->fallbacks[i][0]) + break; + } + + if (!Font_LoadFile(name, dpfnt->req_face, &dpfnt->settings, ft2)) + { + if (i >= MAX_FONT_FALLBACKS) + { + dpfnt->ft2 = NULL; + Mem_Free(ft2); + return false; + } + strlcpy(ft2->name, name, sizeof(ft2->name)); + ft2->image_font = true; + ft2->has_kerning = false; + } + else + { + ft2->image_font = false; + } + + // attempt to load fallback fonts: + fbfont = ft2; + for (i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + if (!dpfnt->fallbacks[i][0]) + break; + if (! (fb = Font_Alloc()) ) + { + Con_Printf("Failed to allocate font for fallback %i of font %s\n", i, name); + break; + } + + if (!Font_LoadFile(dpfnt->fallbacks[i], dpfnt->fallback_faces[i], &dpfnt->settings, fb)) + { + if(!FS_FileExists(va("%s.tga", dpfnt->fallbacks[i]))) + if(!FS_FileExists(va("%s.png", dpfnt->fallbacks[i]))) + if(!FS_FileExists(va("%s.jpg", dpfnt->fallbacks[i]))) + if(!FS_FileExists(va("%s.pcx", dpfnt->fallbacks[i]))) + Con_Printf("Failed to load font %s for fallback %i of font %s\n", dpfnt->fallbacks[i], i, name); + Mem_Free(fb); + continue; + } + count = 0; + for (s = 0; s < MAX_FONT_SIZES && dpfnt->req_sizes[s] >= 0; ++s) + { + if (Font_LoadSize(fb, Font_VirtualToRealSize(dpfnt->req_sizes[s]), true)) + ++count; + } + if (!count) + { + Con_Printf("Failed to allocate font for fallback %i of font %s\n", i, name); + Font_UnloadFont(fb); + Mem_Free(fb); + break; + } + // at least one size of the fallback font loaded successfully + // link it: + fbfont->next = fb; + fbfont = fb; + } + + if (fbfont == ft2 && ft2->image_font) + { + // no fallbacks were loaded successfully: + dpfnt->ft2 = NULL; + Mem_Free(ft2); + return false; + } + + count = 0; + for (s = 0; s < MAX_FONT_SIZES && dpfnt->req_sizes[s] >= 0; ++s) + { + if (Font_LoadSize(ft2, Font_VirtualToRealSize(dpfnt->req_sizes[s]), false)) + ++count; + } + if (!count) + { + // loading failed for every requested size + Font_UnloadFont(ft2); + Mem_Free(ft2); + dpfnt->ft2 = NULL; + return false; + } + + //Con_Printf("%i sizes loaded\n", count); + dpfnt->ft2 = ft2; + return true; +} + +static qboolean Font_LoadFile(const char *name, int _face, ft2_settings_t *settings, ft2_font_t *font) +{ + size_t namelen; + char filename[MAX_QPATH]; + int status; + size_t i; + const unsigned char *data; + fs_offset_t datasize; + + memset(font, 0, sizeof(*font)); + + if (!Font_OpenLibrary()) + { + if (!r_font_disable_freetype.integer) + { + Con_Printf("WARNING: can't open load font %s\n" + "You need the FreeType2 DLL to load font files\n", + name); + } + return false; + } + + font->settings = settings; + + namelen = strlen(name); + + // try load direct file + memcpy(filename, name, namelen+1); + data = fontfilecache_LoadFile(filename, false, &datasize); + // try load .ttf + if (!data) + { + memcpy(filename + namelen, ".ttf", 5); + data = fontfilecache_LoadFile(filename, false, &datasize); + } + // try load .otf + if (!data) + { + memcpy(filename + namelen, ".otf", 5); + data = fontfilecache_LoadFile(filename, false, &datasize); + } + // try load .pfb/afm + if (!data) + { + ft2_attachment_t afm; + + memcpy(filename + namelen, ".pfb", 5); + data = fontfilecache_LoadFile(filename, false, &datasize); + + if (data) + { + memcpy(filename + namelen, ".afm", 5); + afm.data = fontfilecache_LoadFile(filename, false, &afm.size); + + if (afm.data) + Font_Attach(font, &afm); + } + } + if (!data) + { + // FS_LoadFile being not-quiet should print an error :) + return false; + } + Con_DPrintf("Loading font %s face %i...\n", filename, _face); + + status = qFT_New_Memory_Face(font_ft2lib, (FT_Bytes)data, datasize, _face, (FT_Face*)&font->face); + if (status && _face != 0) + { + Con_Printf("Failed to load face %i of %s. Falling back to face 0\n", _face, name); + _face = 0; + status = qFT_New_Memory_Face(font_ft2lib, (FT_Bytes)data, datasize, 0, (FT_Face*)&font->face); + } + font->data = data; + if (status) + { + Con_Printf("ERROR: can't create face for %s\n" + "Error %i\n", // TODO: error strings + name, status); + Font_UnloadFont(font); + return false; + } + + // add the attachments + for (i = 0; i < font->attachmentcount; ++i) + { + FT_Open_Args args; + memset(&args, 0, sizeof(args)); + args.flags = FT_OPEN_MEMORY; + args.memory_base = (const FT_Byte*)font->attachments[i].data; + args.memory_size = font->attachments[i].size; + if (qFT_Attach_Stream((FT_Face)font->face, &args)) + Con_Printf("Failed to add attachment %u to %s\n", (unsigned)i, font->name); + } + + memcpy(font->name, name, namelen+1); + font->image_font = false; + font->has_kerning = !!(((FT_Face)(font->face))->face_flags & FT_FACE_FLAG_KERNING); + return true; +} + +void Font_Postprocess_Update(ft2_font_t *fnt, int bpp, int w, int h) +{ + int needed, x, y; + float gausstable[2*POSTPROCESS_MAXRADIUS+1]; + qboolean need_gauss = (!pp.buf || pp.blur != fnt->settings->blur || pp.shadowz != fnt->settings->shadowz); + qboolean need_circle = (!pp.buf || pp.outline != fnt->settings->outline || pp.shadowx != fnt->settings->shadowx || pp.shadowy != fnt->settings->shadowy); + pp.blur = fnt->settings->blur; + pp.outline = fnt->settings->outline; + pp.shadowx = fnt->settings->shadowx; + pp.shadowy = fnt->settings->shadowy; + pp.shadowz = fnt->settings->shadowz; + pp.outlinepadding_l = bound(0, ceil(pp.outline - pp.shadowx), POSTPROCESS_MAXRADIUS); + pp.outlinepadding_r = bound(0, ceil(pp.outline + pp.shadowx), POSTPROCESS_MAXRADIUS); + pp.outlinepadding_t = bound(0, ceil(pp.outline - pp.shadowy), POSTPROCESS_MAXRADIUS); + pp.outlinepadding_b = bound(0, ceil(pp.outline + pp.shadowy), POSTPROCESS_MAXRADIUS); + pp.blurpadding_lt = bound(0, ceil(pp.blur - pp.shadowz), POSTPROCESS_MAXRADIUS); + pp.blurpadding_rb = bound(0, ceil(pp.blur + pp.shadowz), POSTPROCESS_MAXRADIUS); + pp.padding_l = pp.blurpadding_lt + pp.outlinepadding_l; + pp.padding_r = pp.blurpadding_rb + pp.outlinepadding_r; + pp.padding_t = pp.blurpadding_lt + pp.outlinepadding_t; + pp.padding_b = pp.blurpadding_rb + pp.outlinepadding_b; + if(need_gauss) + { + float sum = 0; + for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) + gausstable[POSTPROCESS_MAXRADIUS+x] = (pp.blur > 0 ? exp(-(pow(x + pp.shadowz, 2))/(pp.blur*pp.blur * 2)) : (floor(x + pp.shadowz + 0.5) == 0)); + for(x = -pp.blurpadding_rb; x <= pp.blurpadding_lt; ++x) + sum += gausstable[POSTPROCESS_MAXRADIUS+x]; + for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) + pp.gausstable[POSTPROCESS_MAXRADIUS+x] = floor(gausstable[POSTPROCESS_MAXRADIUS+x] / sum * 255 + 0.5); + } + if(need_circle) + { + for(y = -POSTPROCESS_MAXRADIUS; y <= POSTPROCESS_MAXRADIUS; ++y) + for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) + { + float d = pp.outline + 1 - sqrt(pow(x + pp.shadowx, 2) + pow(y + pp.shadowy, 2)); + pp.circlematrix[POSTPROCESS_MAXRADIUS+y][POSTPROCESS_MAXRADIUS+x] = (d >= 1) ? 255 : (d <= 0) ? 0 : floor(d * 255 + 0.5); + } + } + pp.bufwidth = w + pp.padding_l + pp.padding_r; + pp.bufheight = h + pp.padding_t + pp.padding_b; + pp.bufpitch = pp.bufwidth; + needed = pp.bufwidth * pp.bufheight; + if(!pp.buf || pp.bufsize < needed * 2) + { + if(pp.buf) + Mem_Free(pp.buf); + pp.bufsize = needed * 4; + pp.buf = (unsigned char *)Mem_Alloc(font_mempool, pp.bufsize); + pp.buf2 = pp.buf + needed; + } +} + +void Font_Postprocess(ft2_font_t *fnt, unsigned char *imagedata, int pitch, int bpp, int w, int h, int *pad_l, int *pad_r, int *pad_t, int *pad_b) +{ + int x, y; + + // calculate gauss table + Font_Postprocess_Update(fnt, bpp, w, h); + + if(imagedata) + { + // enlarge buffer + // perform operation, not exceeding the passed padding values, + // but possibly reducing them + *pad_l = min(*pad_l, pp.padding_l); + *pad_r = min(*pad_r, pp.padding_r); + *pad_t = min(*pad_t, pp.padding_t); + *pad_b = min(*pad_b, pp.padding_b); + + // outline the font (RGBA only) + if(bpp == 4 && (pp.outline > 0 || pp.blur > 0 || pp.shadowx != 0 || pp.shadowy != 0 || pp.shadowz != 0)) // we can only do this in BGRA + { + // this is like mplayer subtitle rendering + // bbuffer, bitmap buffer: this is our font + // abuffer, alpha buffer: this is pp.buf + // tmp: this is pp.buf2 + + // create outline buffer + memset(pp.buf, 0, pp.bufwidth * pp.bufheight); + for(y = -*pad_t; y < h + *pad_b; ++y) + for(x = -*pad_l; x < w + *pad_r; ++x) + { + int x1 = max(-x, -pp.outlinepadding_r); + int y1 = max(-y, -pp.outlinepadding_b); + int x2 = min(pp.outlinepadding_l, w-1-x); + int y2 = min(pp.outlinepadding_t, h-1-y); + int mx, my; + int cur = 0; + int highest = 0; + for(my = y1; my <= y2; ++my) + for(mx = x1; mx <= x2; ++mx) + { + cur = pp.circlematrix[POSTPROCESS_MAXRADIUS+my][POSTPROCESS_MAXRADIUS+mx] * (int)imagedata[(x+mx) * bpp + pitch * (y+my) + (bpp - 1)]; + if(cur > highest) + highest = cur; + } + pp.buf[((x + pp.padding_l) + pp.bufpitch * (y + pp.padding_t))] = (highest + 128) / 255; + } + + // blur the outline buffer + if(pp.blur > 0 || pp.shadowz != 0) + { + // horizontal blur + for(y = 0; y < pp.bufheight; ++y) + for(x = 0; x < pp.bufwidth; ++x) + { + int x1 = max(-x, -pp.blurpadding_rb); + int x2 = min(pp.blurpadding_lt, pp.bufwidth-1-x); + int mx; + int blurred = 0; + for(mx = x1; mx <= x2; ++mx) + blurred += pp.gausstable[POSTPROCESS_MAXRADIUS+mx] * (int)pp.buf[(x+mx) + pp.bufpitch * y]; + pp.buf2[x + pp.bufpitch * y] = bound(0, blurred, 65025) / 255; + } + + // vertical blur + for(y = 0; y < pp.bufheight; ++y) + for(x = 0; x < pp.bufwidth; ++x) + { + int y1 = max(-y, -pp.blurpadding_rb); + int y2 = min(pp.blurpadding_lt, pp.bufheight-1-y); + int my; + int blurred = 0; + for(my = y1; my <= y2; ++my) + blurred += pp.gausstable[POSTPROCESS_MAXRADIUS+my] * (int)pp.buf2[x + pp.bufpitch * (y+my)]; + pp.buf[x + pp.bufpitch * y] = bound(0, blurred, 65025) / 255; + } + } + + // paste the outline below the font + for(y = -*pad_t; y < h + *pad_b; ++y) + for(x = -*pad_l; x < w + *pad_r; ++x) + { + unsigned char outlinealpha = pp.buf[(x + pp.padding_l) + pp.bufpitch * (y + pp.padding_t)]; + if(outlinealpha > 0) + { + unsigned char oldalpha = imagedata[x * bpp + pitch * y + (bpp - 1)]; + // a' = 1 - (1 - a1) (1 - a2) + unsigned char newalpha = 255 - ((255 - (int)outlinealpha) * (255 - (int)oldalpha)) / 255; // this is >= oldalpha + // c' = (a2 c2 - a1 a2 c1 + a1 c1) / a' = (a2 c2 + a1 (1 - a2) c1) / a' + unsigned char oldfactor = (255 * (int)oldalpha) / newalpha; + //unsigned char outlinefactor = ((255 - oldalpha) * (int)outlinealpha) / newalpha; + int i; + for(i = 0; i < bpp-1; ++i) + { + unsigned char c = imagedata[x * bpp + pitch * y + i]; + c = (c * (int)oldfactor) / 255 /* + outlinecolor[i] * (int)outlinefactor */; + imagedata[x * bpp + pitch * y + i] = c; + } + imagedata[x * bpp + pitch * y + (bpp - 1)] = newalpha; + } + //imagedata[x * bpp + pitch * y + (bpp - 1)] |= 0x80; + } + } + } + else if(pitch) + { + // perform operation, not exceeding the passed padding values, + // but possibly reducing them + *pad_l = min(*pad_l, pp.padding_l); + *pad_r = min(*pad_r, pp.padding_r); + *pad_t = min(*pad_t, pp.padding_t); + *pad_b = min(*pad_b, pp.padding_b); + } + else + { + // just calculate parameters + *pad_l = pp.padding_l; + *pad_r = pp.padding_r; + *pad_t = pp.padding_t; + *pad_b = pp.padding_b; + } +} + +static float Font_SearchSize(ft2_font_t *font, FT_Face fontface, float size); +static qboolean Font_LoadMap(ft2_font_t *font, ft2_font_map_t *mapstart, Uchar _ch, ft2_font_map_t **outmap); +static qboolean Font_LoadSize(ft2_font_t *font, float size, qboolean check_only) +{ + int map_index; + ft2_font_map_t *fmap, temp; + int gpad_l, gpad_r, gpad_t, gpad_b; + + if (!(size > 0.001f && size < 1000.0f)) + size = 0; + + if (!size) + size = 16; + if (size < 2) // bogus sizes are not allowed - and they screw up our allocations + return false; + + for (map_index = 0; map_index < MAX_FONT_SIZES; ++map_index) + { + if (!font->font_maps[map_index]) + break; + // if a similar size has already been loaded, ignore this one + //abs(font->font_maps[map_index]->size - size) < 4 + if (font->font_maps[map_index]->size == size) + return true; + } + + if (map_index >= MAX_FONT_SIZES) + return false; + + if (check_only) { + FT_Face fontface; + if (font->image_font) + fontface = (FT_Face)font->next->face; + else + fontface = (FT_Face)font->face; + return (Font_SearchSize(font, fontface, size) > 0); + } + + Font_Postprocess(font, NULL, 0, 4, size*2, size*2, &gpad_l, &gpad_r, &gpad_t, &gpad_b); + + memset(&temp, 0, sizeof(temp)); + temp.size = size; + temp.glyphSize = size*2 + max(gpad_l + gpad_r, gpad_t + gpad_b); + if (!(r_font_nonpoweroftwo.integer && vid.support.arb_texture_non_power_of_two)) + temp.glyphSize = CeilPowerOf2(temp.glyphSize); + temp.sfx = (1.0/64.0)/(double)size; + temp.sfy = (1.0/64.0)/(double)size; + temp.intSize = -1; // negative value: LoadMap must search now :) + if (!Font_LoadMap(font, &temp, 0, &fmap)) + { + Con_Printf("ERROR: can't load the first character map for %s\n" + "This is fatal\n", + font->name); + Font_UnloadFont(font); + return false; + } + font->font_maps[map_index] = temp.next; + + fmap->sfx = temp.sfx; + fmap->sfy = temp.sfy; + + // load the default kerning vector: + if (font->has_kerning) + { + Uchar l, r; + FT_Vector kernvec; + for (l = 0; l < 256; ++l) + { + for (r = 0; r < 256; ++r) + { + FT_ULong ul, ur; + ul = qFT_Get_Char_Index((FT_Face)font->face, l); + ur = qFT_Get_Char_Index((FT_Face)font->face, r); + if (qFT_Get_Kerning((FT_Face)font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) + { + fmap->kerning.kerning[l][r][0] = 0; + fmap->kerning.kerning[l][r][1] = 0; + } + else + { + fmap->kerning.kerning[l][r][0] = Font_SnapTo((kernvec.x / 64.0) / fmap->size, 1 / fmap->size); + fmap->kerning.kerning[l][r][1] = Font_SnapTo((kernvec.y / 64.0) / fmap->size, 1 / fmap->size); + } + } + } + } + return true; +} + +int Font_IndexForSize(ft2_font_t *font, float _fsize, float *outw, float *outh) +{ + int match = -1; + float value = 1000000; + float nval; + int matchsize = -10000; + int m; + float fsize_x, fsize_y; + ft2_font_map_t **maps = font->font_maps; + + fsize_x = fsize_y = _fsize * vid.height / vid_conheight.value; + if(outw && *outw) + fsize_x = *outw * vid.width / vid_conwidth.value; + if(outh && *outh) + fsize_y = *outh * vid.height / vid_conheight.value; + + if (fsize_x < 0) + { + if(fsize_y < 0) + fsize_x = fsize_y = 16; + else + fsize_x = fsize_y; + } + else + { + if(fsize_y < 0) + fsize_y = fsize_x; + } + + for (m = 0; m < MAX_FONT_SIZES; ++m) + { + if (!maps[m]) + continue; + // "round up" to the bigger size if two equally-valued matches exist + nval = 0.5 * (fabs(maps[m]->size - fsize_x) + fabs(maps[m]->size - fsize_y)); + if (match == -1 || nval < value || (nval == value && matchsize < maps[m]->size)) + { + value = nval; + match = m; + matchsize = maps[m]->size; + if (value == 0) // there is no better match + break; + } + } + if (value <= r_font_size_snapping.value) + { + // do NOT keep the aspect for perfect rendering + if (outh) *outh = maps[match]->size * vid_conheight.value / vid.height; + if (outw) *outw = maps[match]->size * vid_conwidth.value / vid.width; + } + return match; +} + +ft2_font_map_t *Font_MapForIndex(ft2_font_t *font, int index) +{ + if (index < 0 || index >= MAX_FONT_SIZES) + return NULL; + return font->font_maps[index]; +} + +static qboolean Font_SetSize(ft2_font_t *font, float w, float h) +{ + if (font->currenth == h && + ((!w && (!font->currentw || font->currentw == font->currenth)) || // check if w==h when w is not set + font->currentw == w)) // same size has been requested + { + return true; + } + // sorry, but freetype doesn't seem to care about other sizes + w = (int)w; + h = (int)h; + if (font->image_font) + { + if (qFT_Set_Char_Size((FT_Face)font->next->face, (FT_F26Dot6)(w*64), (FT_F26Dot6)(h*64), 72, 72)) + return false; + } + else + { + if (qFT_Set_Char_Size((FT_Face)font->face, (FT_F26Dot6)(w*64), (FT_F26Dot6)(h*64), 72, 72)) + return false; + } + font->currentw = w; + font->currenth = h; + return true; +} + +qboolean Font_GetKerningForMap(ft2_font_t *font, int map_index, float w, float h, Uchar left, Uchar right, float *outx, float *outy) +{ + ft2_font_map_t *fmap; + if (!font->has_kerning || !r_font_kerning.integer) + return false; + if (map_index < 0 || map_index >= MAX_FONT_SIZES) + return false; + fmap = font->font_maps[map_index]; + if (!fmap) + return false; + if (left < 256 && right < 256) + { + //Con_Printf("%g : %f, %f, %f :: %f\n", (w / (float)fmap->size), w, fmap->size, fmap->intSize, Font_VirtualToRealSize(w)); + // quick-kerning, be aware of the size: scale it + if (outx) *outx = fmap->kerning.kerning[left][right][0];// * (w / (float)fmap->size); + if (outy) *outy = fmap->kerning.kerning[left][right][1];// * (h / (float)fmap->size); + return true; + } + else + { + FT_Vector kernvec; + FT_ULong ul, ur; + + //if (qFT_Set_Pixel_Sizes((FT_Face)font->face, 0, fmap->size)) +#if 0 + if (!Font_SetSize(font, w, h)) + { + // this deserves an error message + Con_Printf("Failed to get kerning for %s\n", font->name); + return false; + } + ul = qFT_Get_Char_Index(font->face, left); + ur = qFT_Get_Char_Index(font->face, right); + if (qFT_Get_Kerning(font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) + { + if (outx) *outx = Font_SnapTo(kernvec.x * fmap->sfx, 1 / fmap->size); + if (outy) *outy = Font_SnapTo(kernvec.y * fmap->sfy, 1 / fmap->size); + return true; + } +#endif + if (!Font_SetSize(font, fmap->intSize, fmap->intSize)) + { + // this deserves an error message + Con_Printf("Failed to get kerning for %s\n", font->name); + return false; + } + ul = qFT_Get_Char_Index((FT_Face)font->face, left); + ur = qFT_Get_Char_Index((FT_Face)font->face, right); + if (qFT_Get_Kerning((FT_Face)font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) + { + if (outx) *outx = Font_SnapTo(kernvec.x * fmap->sfx, 1 / fmap->size);// * (w / (float)fmap->size); + if (outy) *outy = Font_SnapTo(kernvec.y * fmap->sfy, 1 / fmap->size);// * (h / (float)fmap->size); + return true; + } + return false; + } +} + +qboolean Font_GetKerningForSize(ft2_font_t *font, float w, float h, Uchar left, Uchar right, float *outx, float *outy) +{ + return Font_GetKerningForMap(font, Font_IndexForSize(font, h, NULL, NULL), w, h, left, right, outx, outy); +} + +static void UnloadMapRec(ft2_font_map_t *map) +{ + if (map->pic) + { + //Draw_FreePic(map->pic); // FIXME: refcounting needed... + map->pic = NULL; + } + if (map->next) + UnloadMapRec(map->next); + Mem_Free(map); +} + +void Font_UnloadFont(ft2_font_t *font) +{ + int i; + + // unload fallbacks + if(font->next) + Font_UnloadFont(font->next); + + if (font->attachments && font->attachmentcount) + { + for (i = 0; i < (int)font->attachmentcount; ++i) { + if (font->attachments[i].data) + fontfilecache_Free(font->attachments[i].data); + } + Mem_Free(font->attachments); + font->attachmentcount = 0; + font->attachments = NULL; + } + for (i = 0; i < MAX_FONT_SIZES; ++i) + { + if (font->font_maps[i]) + { + UnloadMapRec(font->font_maps[i]); + font->font_maps[i] = NULL; + } + } + if (ft2_dll) + { + if (font->face) + { + qFT_Done_Face((FT_Face)font->face); + font->face = NULL; + } + } + if (font->data) { + fontfilecache_Free(font->data); + font->data = NULL; + } +} + +static float Font_SearchSize(ft2_font_t *font, FT_Face fontface, float size) +{ + float intSize = size; + while (1) + { + if (!Font_SetSize(font, intSize, intSize)) + { + Con_Printf("ERROR: can't set size for font %s: %f ((%f))\n", font->name, size, intSize); + return -1; + } + if ((fontface->size->metrics.height>>6) <= size) + return intSize; + if (intSize < 2) + { + Con_Printf("ERROR: no appropriate size found for font %s: %f\n", font->name, size); + return -1; + } + --intSize; + } +} + +static qboolean Font_LoadMap(ft2_font_t *font, ft2_font_map_t *mapstart, Uchar _ch, ft2_font_map_t **outmap) +{ + char map_identifier[MAX_QPATH]; + unsigned long mapidx = _ch / FONT_CHARS_PER_MAP; + unsigned char *data = NULL; + FT_ULong ch, mapch; + int status; + int tp; + FT_Int32 load_flags; + int gpad_l, gpad_r, gpad_t, gpad_b; + + int pitch; + int gR, gC; // glyph position: row and column + + ft2_font_map_t *map, *next; + ft2_font_t *usefont; + + FT_Face fontface; + + int bytesPerPixel = 4; // change the conversion loop too if you change this! + + if (outmap) + *outmap = NULL; + + if (r_font_use_alpha_textures.integer) + bytesPerPixel = 1; + + if (font->image_font) + fontface = (FT_Face)font->next->face; + else + fontface = (FT_Face)font->face; + + switch(font->settings->antialias) + { + case 0: + switch(font->settings->hinting) + { + case 0: + load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT | FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; + break; + case 1: + case 2: + load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; + break; + default: + case 3: + load_flags = FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; + break; + } + break; + default: + case 1: + switch(font->settings->hinting) + { + case 0: + load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT | FT_LOAD_TARGET_NORMAL; + break; + case 1: + load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT; + break; + case 2: + load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL; + break; + default: + case 3: + load_flags = FT_LOAD_TARGET_NORMAL; + break; + } + break; + } + + //status = qFT_Set_Pixel_Sizes((FT_Face)font->face, /*size*/0, mapstart->size); + //if (status) + if (font->image_font && mapstart->intSize < 0) + mapstart->intSize = mapstart->size; + if (mapstart->intSize < 0) + { + /* + mapstart->intSize = mapstart->size; + while (1) + { + if (!Font_SetSize(font, mapstart->intSize, mapstart->intSize)) + { + Con_Printf("ERROR: can't set size for font %s: %f ((%f))\n", font->name, mapstart->size, mapstart->intSize); + return false; + } + if ((fontface->size->metrics.height>>6) <= mapstart->size) + break; + if (mapstart->intSize < 2) + { + Con_Printf("ERROR: no appropriate size found for font %s: %f\n", font->name, mapstart->size); + return false; + } + --mapstart->intSize; + } + */ + if ((mapstart->intSize = Font_SearchSize(font, fontface, mapstart->size)) <= 0) + return false; + Con_DPrintf("Using size: %f for requested size %f\n", mapstart->intSize, mapstart->size); + } + + if (!font->image_font && !Font_SetSize(font, mapstart->intSize, mapstart->intSize)) + { + Con_Printf("ERROR: can't set sizes for font %s: %f\n", font->name, mapstart->size); + return false; + } + + map = (ft2_font_map_t *)Mem_Alloc(font_mempool, sizeof(ft2_font_map_t)); + if (!map) + { + Con_Printf("ERROR: Out of memory when loading fontmap for %s\n", font->name); + return false; + } + + // create a totally unique name for this map, then we will use it to make a unique cachepic_t to avoid redundant textures + dpsnprintf(map_identifier, sizeof(map_identifier), + "%s_cache_%g_%d_%g_%g_%g_%g_%g_%u", + font->name, + (double) mapstart->intSize, + (int) load_flags, + (double) font->settings->blur, + (double) font->settings->outline, + (double) font->settings->shadowx, + (double) font->settings->shadowy, + (double) font->settings->shadowz, + (unsigned) mapidx); + + // create a cachepic_t from the data now, or reuse an existing one + map->pic = Draw_CachePic_Flags(map_identifier, CACHEPICFLAG_QUIET); + if (developer_font.integer) + { + if (map->pic->tex == r_texture_notexture) + Con_Printf("Generating font map %s (size: %.1f MB)\n", map_identifier, mapstart->glyphSize * (256 * 4 / 1048576.0) * mapstart->glyphSize); + else + Con_Printf("Using cached font map %s (size: %.1f MB)\n", map_identifier, mapstart->glyphSize * (256 * 4 / 1048576.0) * mapstart->glyphSize); + } + + Font_Postprocess(font, NULL, 0, bytesPerPixel, mapstart->size*2, mapstart->size*2, &gpad_l, &gpad_r, &gpad_t, &gpad_b); + + // copy over the information + map->size = mapstart->size; + map->intSize = mapstart->intSize; + map->glyphSize = mapstart->glyphSize; + map->sfx = mapstart->sfx; + map->sfy = mapstart->sfy; + + pitch = map->glyphSize * FONT_CHARS_PER_LINE * bytesPerPixel; + if (map->pic->tex == r_texture_notexture) + { + data = (unsigned char *)Mem_Alloc(font_mempool, (FONT_CHAR_LINES * map->glyphSize) * pitch); + if (!data) + { + Con_Printf("ERROR: Failed to allocate memory for font %s size %g\n", font->name, map->size); + Mem_Free(map); + return false; + } + // initialize as white texture with zero alpha + tp = 0; + while (tp < (FONT_CHAR_LINES * map->glyphSize) * pitch) + { + if (bytesPerPixel == 4) + { + data[tp++] = 0xFF; + data[tp++] = 0xFF; + data[tp++] = 0xFF; + } + data[tp++] = 0x00; + } + } + + memset(map->width_of, 0, sizeof(map->width_of)); + + // insert the map + map->start = mapidx * FONT_CHARS_PER_MAP; + next = mapstart; + while(next->next && next->next->start < map->start) + next = next->next; + map->next = next->next; + next->next = map; + + gR = 0; + gC = -1; + for (ch = map->start; + ch < (FT_ULong)map->start + FONT_CHARS_PER_MAP; + ++ch) + { + FT_ULong glyphIndex; + int w, h, x, y; + FT_GlyphSlot glyph; + FT_Bitmap *bmp; + unsigned char *imagedata = NULL, *dst, *src; + glyph_slot_t *mapglyph; + FT_Face face; + int pad_l, pad_r, pad_t, pad_b; + + mapch = ch - map->start; + + if (developer_font.integer) + Con_DPrint("glyphinfo: ------------- GLYPH INFO -----------------\n"); + + ++gC; + if (gC >= FONT_CHARS_PER_LINE) + { + gC -= FONT_CHARS_PER_LINE; + ++gR; + } + + if (data) + { + imagedata = data + gR * pitch * map->glyphSize + gC * map->glyphSize * bytesPerPixel; + imagedata += gpad_t * pitch + gpad_l * bytesPerPixel; + } + //status = qFT_Load_Char(face, ch, FT_LOAD_RENDER); + // we need the glyphIndex + face = (FT_Face)font->face; + usefont = NULL; + if (font->image_font && mapch == ch && img_fontmap[mapch]) + { + map->glyphs[mapch].image = true; + continue; + } + glyphIndex = qFT_Get_Char_Index(face, ch); + if (glyphIndex == 0) + { + // by convention, 0 is the "missing-glyph"-glyph + // try to load from a fallback font + for(usefont = font->next; usefont != NULL; usefont = usefont->next) + { + if (!Font_SetSize(usefont, mapstart->intSize, mapstart->intSize)) + continue; + // try that glyph + face = (FT_Face)usefont->face; + glyphIndex = qFT_Get_Char_Index(face, ch); + if (glyphIndex == 0) + continue; + status = qFT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER | load_flags); + if (status) + continue; + break; + } + if (!usefont) + { + //Con_Printf("failed to load fallback glyph for char %lx from font %s\n", (unsigned long)ch, font->name); + // now we let it use the "missing-glyph"-glyph + face = (FT_Face)font->face; + glyphIndex = 0; + } + } + + if (!usefont) + { + usefont = font; + face = (FT_Face)font->face; + status = qFT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER | load_flags); + if (status) + { + //Con_Printf("failed to load glyph %lu for %s\n", glyphIndex, font->name); + Con_DPrintf("failed to load glyph for char %lx from font %s\n", (unsigned long)ch, font->name); + continue; + } + } + + glyph = face->glyph; + bmp = &glyph->bitmap; + + w = bmp->width; + h = bmp->rows; + + if (w > (map->glyphSize - gpad_l - gpad_r) || h > (map->glyphSize - gpad_t - gpad_b)) { + Con_Printf("WARNING: Glyph %lu is too big in font %s, size %g: %i x %i\n", ch, font->name, map->size, w, h); + if (w > map->glyphSize) + w = map->glyphSize - gpad_l - gpad_r; + if (h > map->glyphSize) + h = map->glyphSize; + } + + if (imagedata) + { + switch (bmp->pixel_mode) + { + case FT_PIXEL_MODE_MONO: + if (developer_font.integer) + Con_DPrint("glyphinfo: Pixel Mode: MONO\n"); + break; + case FT_PIXEL_MODE_GRAY2: + if (developer_font.integer) + Con_DPrint("glyphinfo: Pixel Mode: GRAY2\n"); + break; + case FT_PIXEL_MODE_GRAY4: + if (developer_font.integer) + Con_DPrint("glyphinfo: Pixel Mode: GRAY4\n"); + break; + case FT_PIXEL_MODE_GRAY: + if (developer_font.integer) + Con_DPrint("glyphinfo: Pixel Mode: GRAY\n"); + break; + default: + if (developer_font.integer) + Con_DPrintf("glyphinfo: Pixel Mode: Unknown: %i\n", bmp->pixel_mode); + Mem_Free(data); + Con_Printf("ERROR: Unrecognized pixel mode for font %s size %f: %i\n", font->name, mapstart->size, bmp->pixel_mode); + return false; + } + for (y = 0; y < h; ++y) + { + dst = imagedata + y * pitch; + src = bmp->buffer + y * bmp->pitch; + + switch (bmp->pixel_mode) + { + case FT_PIXEL_MODE_MONO: + dst += bytesPerPixel - 1; // shift to alpha byte + for (x = 0; x < bmp->width; x += 8) + { + unsigned char ch = *src++; + *dst = 255 * !!((ch & 0x80) >> 7); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x40) >> 6); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x20) >> 5); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x10) >> 4); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x08) >> 3); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x04) >> 2); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x02) >> 1); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x01) >> 0); dst += bytesPerPixel; + } + break; + case FT_PIXEL_MODE_GRAY2: + dst += bytesPerPixel - 1; // shift to alpha byte + for (x = 0; x < bmp->width; x += 4) + { + unsigned char ch = *src++; + *dst = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2; dst += bytesPerPixel; + *dst = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2; dst += bytesPerPixel; + *dst = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2; dst += bytesPerPixel; + *dst = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2; dst += bytesPerPixel; + } + break; + case FT_PIXEL_MODE_GRAY4: + dst += bytesPerPixel - 1; // shift to alpha byte + for (x = 0; x < bmp->width; x += 2) + { + unsigned char ch = *src++; + *dst = ( ((ch & 0xF0) >> 4) * 0x11); dst += bytesPerPixel; + *dst = ( ((ch & 0x0F) ) * 0x11); dst += bytesPerPixel; + } + break; + case FT_PIXEL_MODE_GRAY: + // in this case pitch should equal width + for (tp = 0; tp < bmp->pitch; ++tp) + dst[(bytesPerPixel - 1) + tp*bytesPerPixel] = src[tp]; // copy the grey value into the alpha bytes + + //memcpy((void*)dst, (void*)src, bmp->pitch); + //dst += bmp->pitch; + break; + default: + break; + } + } + + pad_l = gpad_l; + pad_r = gpad_r; + pad_t = gpad_t; + pad_b = gpad_b; + Font_Postprocess(font, imagedata, pitch, bytesPerPixel, w, h, &pad_l, &pad_r, &pad_t, &pad_b); + } + else + { + pad_l = gpad_l; + pad_r = gpad_r; + pad_t = gpad_t; + pad_b = gpad_b; + Font_Postprocess(font, NULL, pitch, bytesPerPixel, w, h, &pad_l, &pad_r, &pad_t, &pad_b); + } + + + // now fill map->glyphs[ch - map->start] + mapglyph = &map->glyphs[mapch]; + + { + // old way + // double advance = (double)glyph->metrics.horiAdvance * map->sfx; + + double bearingX = (glyph->metrics.horiBearingX / 64.0) / map->size; + //double bearingY = (glyph->metrics.horiBearingY >> 6) / map->size; + double advance = (glyph->advance.x / 64.0) / map->size; + //double mWidth = (glyph->metrics.width >> 6) / map->size; + //double mHeight = (glyph->metrics.height >> 6) / map->size; + + mapglyph->txmin = ( (double)(gC * map->glyphSize) + (double)(gpad_l - pad_l) ) / ( (double)(map->glyphSize * FONT_CHARS_PER_LINE) ); + mapglyph->txmax = mapglyph->txmin + (double)(bmp->width + pad_l + pad_r) / ( (double)(map->glyphSize * FONT_CHARS_PER_LINE) ); + mapglyph->tymin = ( (double)(gR * map->glyphSize) + (double)(gpad_r - pad_r) ) / ( (double)(map->glyphSize * FONT_CHAR_LINES) ); + mapglyph->tymax = mapglyph->tymin + (double)(bmp->rows + pad_t + pad_b) / ( (double)(map->glyphSize * FONT_CHAR_LINES) ); + //mapglyph->vxmin = bearingX; + //mapglyph->vxmax = bearingX + mWidth; + mapglyph->vxmin = (glyph->bitmap_left - pad_l) / map->size; + mapglyph->vxmax = mapglyph->vxmin + (bmp->width + pad_l + pad_r) / map->size; // don't ask + //mapglyph->vymin = -bearingY; + //mapglyph->vymax = mHeight - bearingY; + mapglyph->vymin = (-glyph->bitmap_top - pad_t) / map->size; + mapglyph->vymax = mapglyph->vymin + (bmp->rows + pad_t + pad_b) / map->size; + //Con_Printf("dpi = %f %f (%f %d) %d %d\n", bmp->width / (mapglyph->vxmax - mapglyph->vxmin), bmp->rows / (mapglyph->vymax - mapglyph->vymin), map->size, map->glyphSize, (int)fontface->size->metrics.x_ppem, (int)fontface->size->metrics.y_ppem); + //mapglyph->advance_x = advance * usefont->size; + //mapglyph->advance_x = advance; + mapglyph->advance_x = Font_SnapTo(advance, 1 / map->size); + mapglyph->advance_y = 0; + + if (developer_font.integer) + { + Con_DPrintf("glyphinfo: Glyph: %lu at (%i, %i)\n", (unsigned long)ch, gC, gR); + Con_DPrintf("glyphinfo: %f, %f, %lu\n", bearingX, map->sfx, (unsigned long)glyph->metrics.horiBearingX); + if (ch >= 32 && ch <= 128) + Con_DPrintf("glyphinfo: Character: %c\n", (int)ch); + Con_DPrintf("glyphinfo: Vertex info:\n"); + Con_DPrintf("glyphinfo: X: ( %f -- %f )\n", mapglyph->vxmin, mapglyph->vxmax); + Con_DPrintf("glyphinfo: Y: ( %f -- %f )\n", mapglyph->vymin, mapglyph->vymax); + Con_DPrintf("glyphinfo: Texture info:\n"); + Con_DPrintf("glyphinfo: S: ( %f -- %f )\n", mapglyph->txmin, mapglyph->txmax); + Con_DPrintf("glyphinfo: T: ( %f -- %f )\n", mapglyph->tymin, mapglyph->tymax); + Con_DPrintf("glyphinfo: Advance: %f, %f\n", mapglyph->advance_x, mapglyph->advance_y); + } + } + map->glyphs[mapch].image = false; + } + + if (map->pic->tex == r_texture_notexture) + { + int w = map->glyphSize * FONT_CHARS_PER_LINE; + int h = map->glyphSize * FONT_CHAR_LINES; + rtexture_t *tex; + // abuse the Draw_CachePic system to keep track of this texture + tex = R_LoadTexture2D(drawtexturepool, map_identifier, w, h, data, r_font_use_alpha_textures.integer ? TEXTYPE_ALPHA : TEXTYPE_RGBA, TEXF_ALPHA | (r_font_compress.integer > 0 ? TEXF_COMPRESS : 0), -1, NULL); + // if tex is NULL for any reason, the pic->tex will remain set to r_texture_notexture + if (tex) + map->pic->tex = tex; + + if (r_font_diskcache.integer >= 1) + { + // swap to BGRA for tga writing... + int s = w * h; + int x; + int b; + for (x = 0;x < s;x++) + { + b = data[x*4+0]; + data[x*4+0] = data[x*4+2]; + data[x*4+2] = b; + } + Image_WriteTGABGRA(va("%s.tga", map_identifier), w, h, data); + if (r_font_compress.integer && qglGetCompressedTexImageARB && tex) + R_SaveTextureDDSFile(tex, va("dds/%s.dds", map_identifier), r_texture_dds_save.integer < 2, true); + } + } + + if(data) + Mem_Free(data); + + if (map->pic->tex == r_texture_notexture) + { + // if the first try isn't successful, keep it with a broken texture + // otherwise we retry to load it every single frame where ft2 rendering is used + // this would be bad... + // only `data' must be freed + Con_Printf("ERROR: Failed to generate texture for font %s size %f map %lu\n", + font->name, mapstart->size, mapidx); + return false; + } + if (outmap) + *outmap = map; + return true; +} + +qboolean Font_LoadMapForIndex(ft2_font_t *font, int map_index, Uchar _ch, ft2_font_map_t **outmap) +{ + if (map_index < 0 || map_index >= MAX_FONT_SIZES) + return false; + // the first map must have been loaded already + if (!font->font_maps[map_index]) + return false; + return Font_LoadMap(font, font->font_maps[map_index], _ch, outmap); +} + +ft2_font_map_t *FontMap_FindForChar(ft2_font_map_t *start, Uchar ch) +{ + while (start && start->start + FONT_CHARS_PER_MAP <= ch) + start = start->next; + if (start && start->start > ch) + return NULL; + return start; +} diff --git a/misc/source/darkplaces-src/ft2.h b/misc/source/darkplaces-src/ft2.h new file mode 100644 index 00000000..e8110a72 --- /dev/null +++ b/misc/source/darkplaces-src/ft2.h @@ -0,0 +1,81 @@ +/* Header for FreeType 2 and UTF-8 encoding support for + * DarkPlaces + */ + +#ifndef DP_FREETYPE2_H__ +#define DP_FREETYPE2_H__ + +//#include + +#include "utf8lib.h" + +/* + * From http://www.unicode.org/Public/UNIDATA/Blocks.txt + * + * E000..F8FF; Private Use Area + * F0000..FFFFF; Supplementary Private Use Area-A + * + * We use: + * Range E000 - E0FF + * Contains the non-FreeType2 version of characters. + */ + +typedef struct ft2_font_map_s ft2_font_map_t; +typedef struct ft2_attachment_s ft2_attachment_t; +#define ft2_oldstyle_map ((ft2_font_map_t*)-1) + +typedef float ft2_kernvec[2]; +typedef struct ft2_kerning_s +{ + ft2_kernvec kerning[256][256]; /* kerning[left char][right char] */ +} ft2_kerning_t; + +typedef struct ft2_font_s +{ + char name[64]; + qboolean has_kerning; + // last requested size loaded using Font_SetSize + float currentw; + float currenth; + float ascend; + float descend; + qboolean image_font; // only fallbacks are freetype fonts + + // TODO: clean this up and do not expose everything. + + const unsigned char *data; // FT2 needs it to stay + //fs_offset_t datasize; + void *face; + + // an unordered array of ordered linked lists of glyph maps for a specific size + ft2_font_map_t *font_maps[MAX_FONT_SIZES]; + int num_sizes; + + // attachments + size_t attachmentcount; + ft2_attachment_t *attachments; + + ft2_settings_t *settings; + + // fallback mechanism + struct ft2_font_s *next; +} ft2_font_t; + +void Font_CloseLibrary(void); +void Font_Init(void); +qboolean Font_OpenLibrary(void); +ft2_font_t* Font_Alloc(void); +void Font_UnloadFont(ft2_font_t *font); +// IndexForSize suggests to change the width and height if a font size is in a reasonable range +// for example, you render at a size of 12.4, and a font of size 12 has been loaded +// in such a case, *outw and *outh are set to 12, which is often a good alternative size +int Font_IndexForSize(ft2_font_t *font, float size, float *outw, float *outh); +ft2_font_map_t *Font_MapForIndex(ft2_font_t *font, int index); +qboolean Font_LoadFont(const char *name, dp_font_t *dpfnt); +qboolean Font_GetKerningForSize(ft2_font_t *font, float w, float h, Uchar left, Uchar right, float *outx, float *outy); +qboolean Font_GetKerningForMap(ft2_font_t *font, int map_index, float w, float h, Uchar left, Uchar right, float *outx, float *outy); +float Font_VirtualToRealSize(float sz); +float Font_SnapTo(float val, float snapwidth); +// since this is used on a font_map_t, let's name it FontMap_* +ft2_font_map_t *FontMap_FindForChar(ft2_font_map_t *start, Uchar ch); +#endif // DP_FREETYPE2_H__ diff --git a/misc/source/darkplaces-src/ft2_defs.h b/misc/source/darkplaces-src/ft2_defs.h new file mode 100644 index 00000000..c8d38c6e --- /dev/null +++ b/misc/source/darkplaces-src/ft2_defs.h @@ -0,0 +1,500 @@ +/* FreeType 2 definitions from the freetype header mostly. + */ + +#ifndef FT2_DEFS_H_H__ +#define FT2_DEFS_H_H__ + +#ifdef _MSC_VER +typedef __int32 FT_Int32; +typedef unsigned __int32 FT_UInt32; +#else +# include +typedef int32_t FT_Int32; +typedef uint32_t FT_UInt32; +#endif + +typedef int FT_Error; + +typedef signed char FT_Char; +typedef unsigned char FT_Byte; +typedef const FT_Byte *FT_Bytes; +typedef char FT_String; +typedef signed short FT_Short; +typedef unsigned short FT_UShort; +typedef signed int FT_Int; +typedef unsigned int FT_UInt; +typedef signed long FT_Long; +typedef signed long FT_Fixed; +typedef unsigned long FT_ULong; +typedef void *FT_Pointer; +typedef size_t FT_Offset; +typedef signed long FT_F26Dot6; + +typedef void *FT_Stream; +typedef void *FT_Module; +typedef void *FT_Library; +typedef struct FT_FaceRec_ *FT_Face; +typedef struct FT_CharMapRec_* FT_CharMap; +typedef struct FT_SizeRec_* FT_Size; +typedef struct FT_Size_InternalRec_* FT_Size_Internal; +typedef struct FT_GlyphSlotRec_* FT_GlyphSlot; +typedef struct FT_SubGlyphRec_* FT_SubGlyph; +typedef struct FT_Slot_InternalRec_* FT_Slot_Internal; + +// Taken from the freetype headers: +typedef signed long FT_Pos; +typedef struct FT_Vector_ +{ + FT_Pos x; + FT_Pos y; +} FT_Vector; + +typedef struct FT_BBox_ +{ + FT_Pos xMin, yMin; + FT_Pos xMax, yMax; +} FT_BBox; + +typedef enum FT_Pixel_Mode_ +{ + FT_PIXEL_MODE_NONE = 0, + FT_PIXEL_MODE_MONO, + FT_PIXEL_MODE_GRAY, + FT_PIXEL_MODE_GRAY2, + FT_PIXEL_MODE_GRAY4, + FT_PIXEL_MODE_LCD, + FT_PIXEL_MODE_LCD_V, + FT_PIXEL_MODE_MAX /* do not remove */ +} FT_Pixel_Mode; +typedef enum FT_Render_Mode_ +{ + FT_RENDER_MODE_NORMAL = 0, + FT_RENDER_MODE_LIGHT, + FT_RENDER_MODE_MONO, + FT_RENDER_MODE_LCD, + FT_RENDER_MODE_LCD_V, + + FT_RENDER_MODE_MAX +} FT_Render_Mode; + +#define ft_pixel_mode_none FT_PIXEL_MODE_NONE +#define ft_pixel_mode_mono FT_PIXEL_MODE_MONO +#define ft_pixel_mode_grays FT_PIXEL_MODE_GRAY +#define ft_pixel_mode_pal2 FT_PIXEL_MODE_GRAY2 +#define ft_pixel_mode_pal4 FT_PIXEL_MODE_GRAY4 + +typedef struct FT_Bitmap_ +{ + int rows; + int width; + int pitch; + unsigned char* buffer; + short num_grays; + char pixel_mode; + char palette_mode; + void* palette; +} FT_Bitmap; + +typedef struct FT_Outline_ +{ + short n_contours; /* number of contours in glyph */ + short n_points; /* number of points in the glyph */ + + FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + short* contours; /* the contour end points */ + + int flags; /* outline masks */ +} FT_Outline; + +#define FT_OUTLINE_NONE 0x0 +#define FT_OUTLINE_OWNER 0x1 +#define FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define FT_OUTLINE_REVERSE_FILL 0x4 +#define FT_OUTLINE_IGNORE_DROPOUTS 0x8 +#define FT_OUTLINE_SMART_DROPOUTS 0x10 +#define FT_OUTLINE_INCLUDE_STUBS 0x20 + +#define FT_OUTLINE_HIGH_PRECISION 0x100 +#define FT_OUTLINE_SINGLE_PASS 0x200 + +#define ft_outline_none FT_OUTLINE_NONE +#define ft_outline_owner FT_OUTLINE_OWNER +#define ft_outline_even_odd_fill FT_OUTLINE_EVEN_ODD_FILL +#define ft_outline_reverse_fill FT_OUTLINE_REVERSE_FILL +#define ft_outline_ignore_dropouts FT_OUTLINE_IGNORE_DROPOUTS +#define ft_outline_high_precision FT_OUTLINE_HIGH_PRECISION +#define ft_outline_single_pass FT_OUTLINE_SINGLE_PASS + +#define FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define FT_CURVE_TAG_ON 1 +#define FT_CURVE_TAG_CONIC 0 +#define FT_CURVE_TAG_CUBIC 2 + +#define FT_CURVE_TAG_TOUCH_X 8 /* reserved for the TrueType hinter */ +#define FT_CURVE_TAG_TOUCH_Y 16 /* reserved for the TrueType hinter */ + +#define FT_CURVE_TAG_TOUCH_BOTH ( FT_CURVE_TAG_TOUCH_X | \ + FT_CURVE_TAG_TOUCH_Y ) + +#define FT_Curve_Tag_On FT_CURVE_TAG_ON +#define FT_Curve_Tag_Conic FT_CURVE_TAG_CONIC +#define FT_Curve_Tag_Cubic FT_CURVE_TAG_CUBIC +#define FT_Curve_Tag_Touch_X FT_CURVE_TAG_TOUCH_X +#define FT_Curve_Tag_Touch_Y FT_CURVE_TAG_TOUCH_Y + +typedef int +(*FT_Outline_MoveToFunc)( const FT_Vector* to, + void* user ); +#define FT_Outline_MoveTo_Func FT_Outline_MoveToFunc + +typedef int +(*FT_Outline_LineToFunc)( const FT_Vector* to, + void* user ); +#define FT_Outline_LineTo_Func FT_Outline_LineToFunc + +typedef int +(*FT_Outline_ConicToFunc)( const FT_Vector* control, + const FT_Vector* to, + void* user ); +#define FT_Outline_ConicTo_Func FT_Outline_ConicToFunc + +typedef int +(*FT_Outline_CubicToFunc)( const FT_Vector* control1, + const FT_Vector* control2, + const FT_Vector* to, + void* user ); +#define FT_Outline_CubicTo_Func FT_Outline_CubicToFunc + +typedef struct FT_Outline_Funcs_ +{ + FT_Outline_MoveToFunc move_to; + FT_Outline_LineToFunc line_to; + FT_Outline_ConicToFunc conic_to; + FT_Outline_CubicToFunc cubic_to; + + int shift; + FT_Pos delta; +} FT_Outline_Funcs; + +#ifndef FT_IMAGE_TAG +#define FT_IMAGE_TAG( value, _x1, _x2, _x3, _x4 ) \ + value = ( ( (unsigned long)_x1 << 24 ) | \ + ( (unsigned long)_x2 << 16 ) | \ + ( (unsigned long)_x3 << 8 ) | \ + (unsigned long)_x4 ) +#endif /* FT_IMAGE_TAG */ + +typedef enum FT_Glyph_Format_ +{ + FT_IMAGE_TAG( FT_GLYPH_FORMAT_NONE, 0, 0, 0, 0 ), + + FT_IMAGE_TAG( FT_GLYPH_FORMAT_COMPOSITE, 'c', 'o', 'm', 'p' ), + FT_IMAGE_TAG( FT_GLYPH_FORMAT_BITMAP, 'b', 'i', 't', 's' ), + FT_IMAGE_TAG( FT_GLYPH_FORMAT_OUTLINE, 'o', 'u', 't', 'l' ), + FT_IMAGE_TAG( FT_GLYPH_FORMAT_PLOTTER, 'p', 'l', 'o', 't' ) +} FT_Glyph_Format; +#define ft_glyph_format_none FT_GLYPH_FORMAT_NONE +#define ft_glyph_format_composite FT_GLYPH_FORMAT_COMPOSITE +#define ft_glyph_format_bitmap FT_GLYPH_FORMAT_BITMAP +#define ft_glyph_format_outline FT_GLYPH_FORMAT_OUTLINE +#define ft_glyph_format_plotter FT_GLYPH_FORMAT_PLOTTER + +typedef struct FT_Glyph_Metrics_ +{ + FT_Pos width; + FT_Pos height; + + FT_Pos horiBearingX; + FT_Pos horiBearingY; + FT_Pos horiAdvance; + + FT_Pos vertBearingX; + FT_Pos vertBearingY; + FT_Pos vertAdvance; +} FT_Glyph_Metrics; + +#define FT_EXPORT( x ) x + +#define FT_OPEN_MEMORY 0x1 +#define FT_OPEN_STREAM 0x2 +#define FT_OPEN_PATHNAME 0x4 +#define FT_OPEN_DRIVER 0x8 +#define FT_OPEN_PARAMS 0x10 + +typedef struct FT_Parameter_ +{ + FT_ULong tag; + FT_Pointer data; +} FT_Parameter; + +typedef struct FT_Open_Args_ +{ + FT_UInt flags; + const FT_Byte* memory_base; + FT_Long memory_size; + FT_String* pathname; + FT_Stream stream; + FT_Module driver; + FT_Int num_params; + FT_Parameter* params; +} FT_Open_Args; +typedef enum FT_Size_Request_Type_ +{ + FT_SIZE_REQUEST_TYPE_NOMINAL, + FT_SIZE_REQUEST_TYPE_REAL_DIM, + FT_SIZE_REQUEST_TYPE_BBOX, + FT_SIZE_REQUEST_TYPE_CELL, + FT_SIZE_REQUEST_TYPE_SCALES, + + FT_SIZE_REQUEST_TYPE_MAX + +} FT_Size_Request_Type; +typedef struct FT_Size_RequestRec_ +{ + FT_Size_Request_Type type; + FT_Long width; + FT_Long height; + FT_UInt horiResolution; + FT_UInt vertResolution; +} FT_Size_RequestRec; +typedef struct FT_Size_RequestRec_ *FT_Size_Request; + +#define FT_LOAD_DEFAULT 0x0 +#define FT_LOAD_NO_SCALE 0x1 +#define FT_LOAD_NO_HINTING 0x2 +#define FT_LOAD_RENDER 0x4 +#define FT_LOAD_NO_BITMAP 0x8 +#define FT_LOAD_VERTICAL_LAYOUT 0x10 +#define FT_LOAD_FORCE_AUTOHINT 0x20 +#define FT_LOAD_CROP_BITMAP 0x40 +#define FT_LOAD_PEDANTIC 0x80 +#define FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH 0x200 +#define FT_LOAD_NO_RECURSE 0x400 +#define FT_LOAD_IGNORE_TRANSFORM 0x800 +#define FT_LOAD_MONOCHROME 0x1000 +#define FT_LOAD_LINEAR_DESIGN 0x2000 +#define FT_LOAD_NO_AUTOHINT 0x8000U + +#define FT_LOAD_TARGET_( x ) ( (FT_Int32)( (x) & 15 ) << 16 ) + +#define FT_LOAD_TARGET_NORMAL FT_LOAD_TARGET_( FT_RENDER_MODE_NORMAL ) +#define FT_LOAD_TARGET_LIGHT FT_LOAD_TARGET_( FT_RENDER_MODE_LIGHT ) +#define FT_LOAD_TARGET_MONO FT_LOAD_TARGET_( FT_RENDER_MODE_MONO ) +#define FT_LOAD_TARGET_LCD FT_LOAD_TARGET_( FT_RENDER_MODE_LCD ) +#define FT_LOAD_TARGET_LCD_V FT_LOAD_TARGET_( FT_RENDER_MODE_LCD_V ) + +#define FT_ENC_TAG( value, a, b, c, d ) \ + value = ( ( (FT_UInt32)(a) << 24 ) | \ + ( (FT_UInt32)(b) << 16 ) | \ + ( (FT_UInt32)(c) << 8 ) | \ + (FT_UInt32)(d) ) + +typedef enum FT_Encoding_ +{ + FT_ENC_TAG( FT_ENCODING_NONE, 0, 0, 0, 0 ), + + FT_ENC_TAG( FT_ENCODING_MS_SYMBOL, 's', 'y', 'm', 'b' ), + FT_ENC_TAG( FT_ENCODING_UNICODE, 'u', 'n', 'i', 'c' ), + + FT_ENC_TAG( FT_ENCODING_SJIS, 's', 'j', 'i', 's' ), + FT_ENC_TAG( FT_ENCODING_GB2312, 'g', 'b', ' ', ' ' ), + FT_ENC_TAG( FT_ENCODING_BIG5, 'b', 'i', 'g', '5' ), + FT_ENC_TAG( FT_ENCODING_WANSUNG, 'w', 'a', 'n', 's' ), + FT_ENC_TAG( FT_ENCODING_JOHAB, 'j', 'o', 'h', 'a' ), + + /* for backwards compatibility */ + FT_ENCODING_MS_SJIS = FT_ENCODING_SJIS, + FT_ENCODING_MS_GB2312 = FT_ENCODING_GB2312, + FT_ENCODING_MS_BIG5 = FT_ENCODING_BIG5, + FT_ENCODING_MS_WANSUNG = FT_ENCODING_WANSUNG, + FT_ENCODING_MS_JOHAB = FT_ENCODING_JOHAB, + + FT_ENC_TAG( FT_ENCODING_ADOBE_STANDARD, 'A', 'D', 'O', 'B' ), + FT_ENC_TAG( FT_ENCODING_ADOBE_EXPERT, 'A', 'D', 'B', 'E' ), + FT_ENC_TAG( FT_ENCODING_ADOBE_CUSTOM, 'A', 'D', 'B', 'C' ), + FT_ENC_TAG( FT_ENCODING_ADOBE_LATIN_1, 'l', 'a', 't', '1' ), + + FT_ENC_TAG( FT_ENCODING_OLD_LATIN_2, 'l', 'a', 't', '2' ), + + FT_ENC_TAG( FT_ENCODING_APPLE_ROMAN, 'a', 'r', 'm', 'n' ) +} FT_Encoding; + +#define ft_encoding_none FT_ENCODING_NONE +#define ft_encoding_unicode FT_ENCODING_UNICODE +#define ft_encoding_symbol FT_ENCODING_MS_SYMBOL +#define ft_encoding_latin_1 FT_ENCODING_ADOBE_LATIN_1 +#define ft_encoding_latin_2 FT_ENCODING_OLD_LATIN_2 +#define ft_encoding_sjis FT_ENCODING_SJIS +#define ft_encoding_gb2312 FT_ENCODING_GB2312 +#define ft_encoding_big5 FT_ENCODING_BIG5 +#define ft_encoding_wansung FT_ENCODING_WANSUNG +#define ft_encoding_johab FT_ENCODING_JOHAB + +#define ft_encoding_adobe_standard FT_ENCODING_ADOBE_STANDARD +#define ft_encoding_adobe_expert FT_ENCODING_ADOBE_EXPERT +#define ft_encoding_adobe_custom FT_ENCODING_ADOBE_CUSTOM +#define ft_encoding_apple_roman FT_ENCODING_APPLE_ROMAN + +typedef struct FT_Bitmap_Size_ +{ + FT_Short height; + FT_Short width; + + FT_Pos size; + + FT_Pos x_ppem; + FT_Pos y_ppem; +} FT_Bitmap_Size; + +typedef struct FT_CharMapRec_ +{ + FT_Face face; + FT_Encoding encoding; + FT_UShort platform_id; + FT_UShort encoding_id; +} FT_CharMapRec; + +typedef void (*FT_Generic_Finalizer)(void* object); +typedef struct FT_Generic_ +{ + void* data; + FT_Generic_Finalizer finalizer; +} FT_Generic; + +typedef struct FT_Size_Metrics_ +{ + FT_UShort x_ppem; /* horizontal pixels per EM */ + FT_UShort y_ppem; /* vertical pixels per EM */ + + FT_Fixed x_scale; /* scaling values used to convert font */ + FT_Fixed y_scale; /* units to 26.6 fractional pixels */ + + FT_Pos ascender; /* ascender in 26.6 frac. pixels */ + FT_Pos descender; /* descender in 26.6 frac. pixels */ + FT_Pos height; /* text height in 26.6 frac. pixels */ + FT_Pos max_advance; /* max horizontal advance, in 26.6 pixels */ +} FT_Size_Metrics; + +typedef struct FT_SizeRec_ +{ + FT_Face face; /* parent face object */ + FT_Generic generic; /* generic pointer for client uses */ + FT_Size_Metrics metrics; /* size metrics */ + FT_Size_Internal internal; +} FT_SizeRec; + +typedef struct FT_FaceRec_ +{ + FT_Long num_faces; + FT_Long face_index; + + FT_Long face_flags; + FT_Long style_flags; + + FT_Long num_glyphs; + + FT_String* family_name; + FT_String* style_name; + + FT_Int num_fixed_sizes; + FT_Bitmap_Size* available_sizes; + + FT_Int num_charmaps; + FT_CharMap* charmaps; + + FT_Generic generic; + + /*# The following member variables (down to `underline_thickness') */ + /*# are only relevant to scalable outlines; cf. @FT_Bitmap_Size */ + /*# for bitmap fonts. */ + FT_BBox bbox; + + FT_UShort units_per_EM; + FT_Short ascender; + FT_Short descender; + FT_Short height; + + FT_Short max_advance_width; + FT_Short max_advance_height; + + FT_Short underline_position; + FT_Short underline_thickness; + + FT_GlyphSlot glyph; + FT_Size size; + FT_CharMap charmap; + + /* ft2 private + FT_Driver driver; + FT_Memory memory; + FT_Stream stream; + + FT_ListRec sizes_list; + + FT_Generic autohint; + void* extensions; + + FT_Face_Internal internal; + */ +} FT_FaceRec; + +typedef struct FT_GlyphSlotRec_ +{ + FT_Library library; + FT_Face face; + FT_GlyphSlot next; + FT_UInt reserved; /* retained for binary compatibility */ + FT_Generic generic; + + FT_Glyph_Metrics metrics; + FT_Fixed linearHoriAdvance; + FT_Fixed linearVertAdvance; + FT_Vector advance; + + FT_Glyph_Format format; + + FT_Bitmap bitmap; + FT_Int bitmap_left; + FT_Int bitmap_top; + + FT_Outline outline; + + FT_UInt num_subglyphs; + FT_SubGlyph subglyphs; + + void* control_data; + long control_len; + + FT_Pos lsb_delta; + FT_Pos rsb_delta; + + void* other; + + FT_Slot_Internal internal; +} FT_GlyphSlotRec; + +#define FT_FACE_FLAG_SCALABLE ( 1L << 0 ) +#define FT_FACE_FLAG_FIXED_SIZES ( 1L << 1 ) +#define FT_FACE_FLAG_FIXED_WIDTH ( 1L << 2 ) +#define FT_FACE_FLAG_SFNT ( 1L << 3 ) +#define FT_FACE_FLAG_HORIZONTAL ( 1L << 4 ) +#define FT_FACE_FLAG_VERTICAL ( 1L << 5 ) +#define FT_FACE_FLAG_KERNING ( 1L << 6 ) +#define FT_FACE_FLAG_FAST_GLYPHS ( 1L << 7 ) +#define FT_FACE_FLAG_MULTIPLE_MASTERS ( 1L << 8 ) +#define FT_FACE_FLAG_GLYPH_NAMES ( 1L << 9 ) +#define FT_FACE_FLAG_EXTERNAL_STREAM ( 1L << 10 ) +#define FT_FACE_FLAG_HINTER ( 1L << 11 ) +#define FT_FACE_FLAG_CID_KEYED ( 1L << 12 ) +#define FT_FACE_FLAG_TRICKY ( 1L << 13 ) + +typedef enum FT_Kerning_Mode_ +{ + FT_KERNING_DEFAULT = 0, + FT_KERNING_UNFITTED, + FT_KERNING_UNSCALED +} FT_Kerning_Mode; + +#endif // FT2_DEFS_H_H__ diff --git a/misc/source/darkplaces-src/ft2_fontdefs.h b/misc/source/darkplaces-src/ft2_fontdefs.h new file mode 100644 index 00000000..3f08187d --- /dev/null +++ b/misc/source/darkplaces-src/ft2_fontdefs.h @@ -0,0 +1,64 @@ +#ifndef FT2_PRIVATE_H__ +#define FT2_PRIVATE_H__ + +// anything should work, but I recommend multiples of 8 +// since the texture size should be a power of 2 +#define FONT_CHARS_PER_LINE 16 +#define FONT_CHAR_LINES 16 +#define FONT_CHARS_PER_MAP (FONT_CHARS_PER_LINE * FONT_CHAR_LINES) + +typedef struct glyph_slot_s +{ + qboolean image; + // we keep the quad coords here only currently + // if you need other info, make Font_LoadMapForIndex fill it into this slot + float txmin; // texture coordinate in [0,1] + float txmax; + float tymin; + float tymax; + float vxmin; + float vxmax; + float vymin; + float vymax; + float advance_x; + float advance_y; +} glyph_slot_t; + +struct ft2_font_map_s +{ + Uchar start; + struct ft2_font_map_s *next; + float size; + // the actual size used in the freetype code + // by convention, the requested size is the height of the font's bounding box. + float intSize; + int glyphSize; + + cachepic_t *pic; + qboolean static_tex; + glyph_slot_t glyphs[FONT_CHARS_PER_MAP]; + + // contains the kerning information for the first 256 characters + // for the other characters, we will lookup the kerning information + ft2_kerning_t kerning; + // safes us the trouble of calculating these over and over again + double sfx, sfy; + + // the width_of for the image-font, pixel-snapped for this size + float width_of[256]; +}; + +struct ft2_attachment_s +{ + const unsigned char *data; + fs_offset_t size; +}; + +//qboolean Font_LoadMapForIndex(ft2_font_t *font, Uchar _ch, ft2_font_map_t **outmap); +qboolean Font_LoadMapForIndex(ft2_font_t *font, int map_index, Uchar _ch, ft2_font_map_t **outmap); + +void font_start(void); +void font_shutdown(void); +void font_newmap(void); + +#endif // FT2_PRIVATE_H__ diff --git a/misc/source/darkplaces-src/gl_backend.c b/misc/source/darkplaces-src/gl_backend.c new file mode 100644 index 00000000..74938dc5 --- /dev/null +++ b/misc/source/darkplaces-src/gl_backend.c @@ -0,0 +1,4536 @@ + +#include "quakedef.h" +#include "cl_collision.h" +#include "dpsoftrast.h" +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +extern D3DCAPS9 vid_d3d9caps; +#endif + +#define MAX_RENDERTARGETS 4 + +cvar_t gl_mesh_drawrangeelements = {0, "gl_mesh_drawrangeelements", "1", "use glDrawRangeElements function if available instead of glDrawElements (for performance comparisons or bug testing)"}; +cvar_t gl_mesh_testmanualfeeding = {0, "gl_mesh_testmanualfeeding", "0", "use glBegin(GL_TRIANGLES);glTexCoord2f();glVertex3f();glEnd(); primitives instead of glDrawElements (useful to test for driver bugs with glDrawElements)"}; +cvar_t gl_mesh_prefer_short_elements = {CVAR_SAVE, "gl_mesh_prefer_short_elements", "1", "use GL_UNSIGNED_SHORT element arrays instead of GL_UNSIGNED_INT"}; +cvar_t gl_paranoid = {0, "gl_paranoid", "0", "enables OpenGL error checking and other tests"}; +cvar_t gl_printcheckerror = {0, "gl_printcheckerror", "0", "prints all OpenGL error checks, useful to identify location of driver crashes"}; + +cvar_t r_render = {0, "r_render", "1", "enables rendering 3D views (you want this on!)"}; +cvar_t r_renderview = {0, "r_renderview", "1", "enables rendering 3D views (you want this on!)"}; +cvar_t r_waterwarp = {CVAR_SAVE, "r_waterwarp", "1", "warp view while underwater"}; +cvar_t gl_polyblend = {CVAR_SAVE, "gl_polyblend", "1", "tints view while underwater, hurt, etc"}; +cvar_t gl_dither = {CVAR_SAVE, "gl_dither", "1", "enables OpenGL dithering (16bit looks bad with this off)"}; +cvar_t gl_vbo = {CVAR_SAVE, "gl_vbo", "3", "make use of GL_ARB_vertex_buffer_object extension to store static geometry in video memory for faster rendering, 0 disables VBO allocation or use, 1 enables VBOs for vertex and triangle data, 2 only for vertex data, 3 for vertex data and triangle data of simple meshes (ones with only one surface)"}; +cvar_t gl_vbo_dynamicvertex = {CVAR_SAVE, "gl_vbo_dynamicvertex", "0", "make use of GL_ARB_vertex_buffer_object extension when rendering dynamic (animated/procedural) geometry such as text and particles"}; +cvar_t gl_vbo_dynamicindex = {CVAR_SAVE, "gl_vbo_dynamicindex", "0", "make use of GL_ARB_vertex_buffer_object extension when rendering dynamic (animated/procedural) geometry such as text and particles"}; +cvar_t gl_fbo = {CVAR_SAVE, "gl_fbo", "1", "make use of GL_ARB_framebuffer_object extension to enable shadowmaps and other features using pixel formats different from the framebuffer"}; + +cvar_t v_flipped = {0, "v_flipped", "0", "mirror the screen (poor man's left handed mode)"}; +qboolean v_flipped_state = false; + +r_viewport_t gl_viewport; +matrix4x4_t gl_modelmatrix; +matrix4x4_t gl_viewmatrix; +matrix4x4_t gl_modelviewmatrix; +matrix4x4_t gl_projectionmatrix; +matrix4x4_t gl_modelviewprojectionmatrix; +float gl_modelview16f[16]; +float gl_modelviewprojection16f[16]; +qboolean gl_modelmatrixchanged; + +int gl_maxdrawrangeelementsvertices; +int gl_maxdrawrangeelementsindices; + +#ifdef DEBUGGL +int errornumber = 0; + +void GL_PrintError(int errornumber, const char *filename, int linenumber) +{ + switch(errornumber) + { +#ifdef GL_INVALID_ENUM + case GL_INVALID_ENUM: + Con_Printf("GL_INVALID_ENUM at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_INVALID_VALUE + case GL_INVALID_VALUE: + Con_Printf("GL_INVALID_VALUE at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_INVALID_OPERATION + case GL_INVALID_OPERATION: + Con_Printf("GL_INVALID_OPERATION at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_STACK_OVERFLOW + case GL_STACK_OVERFLOW: + Con_Printf("GL_STACK_OVERFLOW at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_STACK_UNDERFLOW + case GL_STACK_UNDERFLOW: + Con_Printf("GL_STACK_UNDERFLOW at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_OUT_OF_MEMORY + case GL_OUT_OF_MEMORY: + Con_Printf("GL_OUT_OF_MEMORY at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_TABLE_TOO_LARGE + case GL_TABLE_TOO_LARGE: + Con_Printf("GL_TABLE_TOO_LARGE at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_INVALID_FRAMEBUFFER_OPERATION_EXT + case GL_INVALID_FRAMEBUFFER_OPERATION_EXT: + Con_Printf("GL_INVALID_FRAMEBUFFER_OPERATION at %s:%i\n", filename, linenumber); + break; +#endif + default: + Con_Printf("GL UNKNOWN (%i) at %s:%i\n", errornumber, filename, linenumber); + break; + } +} +#endif + +#define BACKENDACTIVECHECK if (!gl_state.active) Sys_Error("GL backend function called when backend is not active"); + +void SCR_ScreenShot_f (void); + +typedef struct gltextureunit_s +{ + int pointer_texcoord_components; + int pointer_texcoord_gltype; + size_t pointer_texcoord_stride; + const void *pointer_texcoord_pointer; + const r_meshbuffer_t *pointer_texcoord_vertexbuffer; + size_t pointer_texcoord_offset; + + rtexture_t *texture; + int t2d, t3d, tcubemap; + int arrayenabled; + int rgbscale, alphascale; + int combine; + int combinergb, combinealpha; + // texmatrixenabled exists only to avoid unnecessary texmatrix compares + int texmatrixenabled; + matrix4x4_t matrix; +} +gltextureunit_t; + +typedef struct gl_state_s +{ + int cullface; + int cullfaceenable; + int blendfunc1; + int blendfunc2; + qboolean blend; + GLboolean depthmask; + int colormask; // stored as bottom 4 bits: r g b a (3 2 1 0 order) + int depthtest; + int depthfunc; + float depthrange[2]; + float polygonoffset[2]; + int alphatest; + int alphafunc; + float alphafuncvalue; + qboolean alphatocoverage; + int scissortest; + unsigned int unit; + unsigned int clientunit; + gltextureunit_t units[MAX_TEXTUREUNITS]; + float color4f[4]; + int lockrange_first; + int lockrange_count; + int vertexbufferobject; + int elementbufferobject; + int framebufferobject; + int defaultframebufferobject; // deal with platforms that use a non-zero default fbo + qboolean pointer_color_enabled; + + int pointer_vertex_components; + int pointer_vertex_gltype; + size_t pointer_vertex_stride; + const void *pointer_vertex_pointer; + const r_meshbuffer_t *pointer_vertex_vertexbuffer; + size_t pointer_vertex_offset; + + int pointer_color_components; + int pointer_color_gltype; + size_t pointer_color_stride; + const void *pointer_color_pointer; + const r_meshbuffer_t *pointer_color_vertexbuffer; + size_t pointer_color_offset; + + void *preparevertices_tempdata; + size_t preparevertices_tempdatamaxsize; + r_meshbuffer_t *preparevertices_dynamicvertexbuffer; + r_vertexgeneric_t *preparevertices_vertexgeneric; + r_vertexmesh_t *preparevertices_vertexmesh; + int preparevertices_numvertices; + + r_meshbuffer_t *draw_dynamicindexbuffer; + + qboolean usevbo_staticvertex; + qboolean usevbo_staticindex; + qboolean usevbo_dynamicvertex; + qboolean usevbo_dynamicindex; + + memexpandablearray_t meshbufferarray; + + qboolean active; + +#ifdef SUPPORTD3D +// rtexture_t *d3drt_depthtexture; +// rtexture_t *d3drt_colortextures[MAX_RENDERTARGETS]; + IDirect3DSurface9 *d3drt_depthsurface; + IDirect3DSurface9 *d3drt_colorsurfaces[MAX_RENDERTARGETS]; + IDirect3DSurface9 *d3drt_backbufferdepthsurface; + IDirect3DSurface9 *d3drt_backbuffercolorsurface; + void *d3dvertexbuffer; + void *d3dvertexdata; + size_t d3dvertexsize; +#endif +} +gl_state_t; + +static gl_state_t gl_state; + + +/* +note: here's strip order for a terrain row: +0--1--2--3--4 +|\ |\ |\ |\ | +| \| \| \| \| +A--B--C--D--E +clockwise + +A0B, 01B, B1C, 12C, C2D, 23D, D3E, 34E + +*elements++ = i + row; +*elements++ = i; +*elements++ = i + row + 1; +*elements++ = i; +*elements++ = i + 1; +*elements++ = i + row + 1; + + +for (y = 0;y < rows - 1;y++) +{ + for (x = 0;x < columns - 1;x++) + { + i = y * rows + x; + *elements++ = i + columns; + *elements++ = i; + *elements++ = i + columns + 1; + *elements++ = i; + *elements++ = i + 1; + *elements++ = i + columns + 1; + } +} + +alternative: +0--1--2--3--4 +| /| /|\ | /| +|/ |/ | \|/ | +A--B--C--D--E +counterclockwise + +for (y = 0;y < rows - 1;y++) +{ + for (x = 0;x < columns - 1;x++) + { + i = y * rows + x; + *elements++ = i; + *elements++ = i + columns; + *elements++ = i + columns + 1; + *elements++ = i + columns; + *elements++ = i + columns + 1; + *elements++ = i + 1; + } +} +*/ + +int polygonelement3i[(POLYGONELEMENTS_MAXPOINTS-2)*3]; +unsigned short polygonelement3s[(POLYGONELEMENTS_MAXPOINTS-2)*3]; +int quadelement3i[QUADELEMENTS_MAXQUADS*6]; +unsigned short quadelement3s[QUADELEMENTS_MAXQUADS*6]; + +void GL_VBOStats_f(void) +{ + GL_Mesh_ListVBOs(true); +} + +static void GL_Backend_ResetState(void); + +static void R_Mesh_InitVertexDeclarations(void); +static void R_Mesh_DestroyVertexDeclarations(void); + +static void R_Mesh_SetUseVBO(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + gl_state.usevbo_staticvertex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; + gl_state.usevbo_staticindex = (vid.support.arb_vertex_buffer_object && (gl_vbo.integer == 1 || gl_vbo.integer == 3)) || vid.forcevbo; + gl_state.usevbo_dynamicvertex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer) || vid.forcevbo; + gl_state.usevbo_dynamicindex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicindex.integer) || vid.forcevbo; + break; + case RENDERPATH_D3D9: + gl_state.usevbo_staticvertex = gl_state.usevbo_staticindex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; + gl_state.usevbo_dynamicvertex = gl_state.usevbo_dynamicindex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer && gl_vbo_dynamicindex.integer) || vid.forcevbo; + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + gl_state.usevbo_staticvertex = false; + gl_state.usevbo_staticindex = false; + gl_state.usevbo_dynamicvertex = false; + gl_state.usevbo_dynamicindex = false; + break; + case RENDERPATH_GLES2: + gl_state.usevbo_staticvertex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; + gl_state.usevbo_staticindex = false; + gl_state.usevbo_dynamicvertex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer) || vid.forcevbo; + gl_state.usevbo_dynamicindex = false; + break; + } +} + +static void gl_backend_start(void) +{ + memset(&gl_state, 0, sizeof(gl_state)); + + R_Mesh_InitVertexDeclarations(); + + R_Mesh_SetUseVBO(); + Mem_ExpandableArray_NewArray(&gl_state.meshbufferarray, r_main_mempool, sizeof(r_meshbuffer_t), 128); + + Con_DPrintf("OpenGL backend started.\n"); + + CHECKGLERROR + + GL_Backend_ResetState(); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // fetch current fbo here (default fbo is not 0 on some GLES devices) + if (vid.support.ext_framebuffer_object) + qglGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &gl_state.defaultframebufferobject); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_GetDepthStencilSurface(vid_d3d9dev, &gl_state.d3drt_backbufferdepthsurface); + IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, &gl_state.d3drt_backbuffercolorsurface); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } +} + +static void gl_backend_shutdown(void) +{ + Con_DPrint("OpenGL Backend shutting down\n"); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DSurface9_Release(gl_state.d3drt_backbufferdepthsurface); + IDirect3DSurface9_Release(gl_state.d3drt_backbuffercolorsurface); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + + if (gl_state.preparevertices_tempdata) + Mem_Free(gl_state.preparevertices_tempdata); + if (gl_state.preparevertices_dynamicvertexbuffer) + R_Mesh_DestroyMeshBuffer(gl_state.preparevertices_dynamicvertexbuffer); + + Mem_ExpandableArray_FreeArray(&gl_state.meshbufferarray); + + R_Mesh_DestroyVertexDeclarations(); + + memset(&gl_state, 0, sizeof(gl_state)); +} + +static void gl_backend_newmap(void) +{ +} + +static void gl_backend_devicelost(void) +{ + int i, endindex; + r_meshbuffer_t *buffer; +#ifdef SUPPORTD3D + gl_state.d3dvertexbuffer = NULL; +#endif + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DSurface9_Release(gl_state.d3drt_backbufferdepthsurface); + IDirect3DSurface9_Release(gl_state.d3drt_backbuffercolorsurface); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + endindex = Mem_ExpandableArray_IndexRange(&gl_state.meshbufferarray); + for (i = 0;i < endindex;i++) + { + buffer = (r_meshbuffer_t *) Mem_ExpandableArray_RecordAtIndex(&gl_state.meshbufferarray, i); + if (!buffer || !buffer->isdynamic) + continue; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (buffer->devicebuffer) + { + if (buffer->isindexbuffer) + IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9*)buffer->devicebuffer); + else + IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9*)buffer->devicebuffer); + buffer->devicebuffer = NULL; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + } +} + +static void gl_backend_devicerestored(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_GetDepthStencilSurface(vid_d3d9dev, &gl_state.d3drt_backbufferdepthsurface); + IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, &gl_state.d3drt_backbuffercolorsurface); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } +} + +void gl_backend_init(void) +{ + int i; + + for (i = 0;i < POLYGONELEMENTS_MAXPOINTS - 2;i++) + { + polygonelement3s[i * 3 + 0] = 0; + polygonelement3s[i * 3 + 1] = i + 1; + polygonelement3s[i * 3 + 2] = i + 2; + } + // elements for rendering a series of quads as triangles + for (i = 0;i < QUADELEMENTS_MAXQUADS;i++) + { + quadelement3s[i * 6 + 0] = i * 4; + quadelement3s[i * 6 + 1] = i * 4 + 1; + quadelement3s[i * 6 + 2] = i * 4 + 2; + quadelement3s[i * 6 + 3] = i * 4; + quadelement3s[i * 6 + 4] = i * 4 + 2; + quadelement3s[i * 6 + 5] = i * 4 + 3; + } + + for (i = 0;i < (POLYGONELEMENTS_MAXPOINTS - 2)*3;i++) + polygonelement3i[i] = polygonelement3s[i]; + for (i = 0;i < QUADELEMENTS_MAXQUADS*6;i++) + quadelement3i[i] = quadelement3s[i]; + + Cvar_RegisterVariable(&r_render); + Cvar_RegisterVariable(&r_renderview); + Cvar_RegisterVariable(&r_waterwarp); + Cvar_RegisterVariable(&gl_polyblend); + Cvar_RegisterVariable(&v_flipped); + Cvar_RegisterVariable(&gl_dither); + Cvar_RegisterVariable(&gl_vbo); + Cvar_RegisterVariable(&gl_vbo_dynamicvertex); + Cvar_RegisterVariable(&gl_vbo_dynamicindex); + Cvar_RegisterVariable(&gl_paranoid); + Cvar_RegisterVariable(&gl_printcheckerror); + + Cvar_RegisterVariable(&gl_mesh_drawrangeelements); + Cvar_RegisterVariable(&gl_mesh_testmanualfeeding); + Cvar_RegisterVariable(&gl_mesh_prefer_short_elements); + + Cmd_AddCommand("gl_vbostats", GL_VBOStats_f, "prints a list of all buffer objects (vertex data and triangle elements) and total video memory used by them"); + + R_RegisterModule("GL_Backend", gl_backend_start, gl_backend_shutdown, gl_backend_newmap, gl_backend_devicelost, gl_backend_devicerestored); +} + +void GL_SetMirrorState(qboolean state); + +void R_Viewport_TransformToScreen(const r_viewport_t *v, const vec4_t in, vec4_t out) +{ + vec4_t temp; + float iw; + Matrix4x4_Transform4 (&v->viewmatrix, in, temp); + Matrix4x4_Transform4 (&v->projectmatrix, temp, out); + iw = 1.0f / out[3]; + out[0] = v->x + (out[0] * iw + 1.0f) * v->width * 0.5f; + + // for an odd reason, inverting this is wrong for R_Shadow_ScissorForBBox (we then get badly scissored lights) + //out[1] = v->y + v->height - (out[1] * iw + 1.0f) * v->height * 0.5f; + out[1] = v->y + (out[1] * iw + 1.0f) * v->height * 0.5f; + + out[2] = v->z + (out[2] * iw + 1.0f) * v->depth * 0.5f; +} + +void GL_Finish(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglFinish(); + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Finish(); + break; + } +} + +static int bboxedges[12][2] = +{ + // top + {0, 1}, // +X + {0, 2}, // +Y + {1, 3}, // Y, +X + {2, 3}, // X, +Y + // bottom + {4, 5}, // +X + {4, 6}, // +Y + {5, 7}, // Y, +X + {6, 7}, // X, +Y + // verticals + {0, 4}, // +Z + {1, 5}, // X, +Z + {2, 6}, // Y, +Z + {3, 7}, // XY, +Z +}; + +qboolean R_ScissorForBBox(const float *mins, const float *maxs, int *scissor) +{ + int i, ix1, iy1, ix2, iy2; + float x1, y1, x2, y2; + vec4_t v, v2; + float vertex[20][3]; + int j, k; + vec4_t plane4f; + int numvertices; + float corner[8][4]; + float dist[8]; + int sign[8]; + float f; + + scissor[0] = r_refdef.view.viewport.x; + scissor[1] = r_refdef.view.viewport.y; + scissor[2] = r_refdef.view.viewport.width; + scissor[3] = r_refdef.view.viewport.height; + + // if view is inside the box, just say yes it's visible + if (BoxesOverlap(r_refdef.view.origin, r_refdef.view.origin, mins, maxs)) + return false; + + x1 = y1 = x2 = y2 = 0; + + // transform all corners that are infront of the nearclip plane + VectorNegate(r_refdef.view.frustum[4].normal, plane4f); + plane4f[3] = r_refdef.view.frustum[4].dist; + numvertices = 0; + for (i = 0;i < 8;i++) + { + Vector4Set(corner[i], (i & 1) ? maxs[0] : mins[0], (i & 2) ? maxs[1] : mins[1], (i & 4) ? maxs[2] : mins[2], 1); + dist[i] = DotProduct4(corner[i], plane4f); + sign[i] = dist[i] > 0; + if (!sign[i]) + { + VectorCopy(corner[i], vertex[numvertices]); + numvertices++; + } + } + // if some points are behind the nearclip, add clipped edge points to make + // sure that the scissor boundary is complete + if (numvertices > 0 && numvertices < 8) + { + // add clipped edge points + for (i = 0;i < 12;i++) + { + j = bboxedges[i][0]; + k = bboxedges[i][1]; + if (sign[j] != sign[k]) + { + f = dist[j] / (dist[j] - dist[k]); + VectorLerp(corner[j], f, corner[k], vertex[numvertices]); + numvertices++; + } + } + } + + // if we have no points to check, it is behind the view plane + if (!numvertices) + return true; + + // if we have some points to transform, check what screen area is covered + x1 = y1 = x2 = y2 = 0; + v[3] = 1.0f; + //Con_Printf("%i vertices to transform...\n", numvertices); + for (i = 0;i < numvertices;i++) + { + VectorCopy(vertex[i], v); + R_Viewport_TransformToScreen(&r_refdef.view.viewport, v, v2); + //Con_Printf("%.3f %.3f %.3f %.3f transformed to %.3f %.3f %.3f %.3f\n", v[0], v[1], v[2], v[3], v2[0], v2[1], v2[2], v2[3]); + if (i) + { + if (x1 > v2[0]) x1 = v2[0]; + if (x2 < v2[0]) x2 = v2[0]; + if (y1 > v2[1]) y1 = v2[1]; + if (y2 < v2[1]) y2 = v2[1]; + } + else + { + x1 = x2 = v2[0]; + y1 = y2 = v2[1]; + } + } + + // now convert the scissor rectangle to integer screen coordinates + ix1 = (int)(x1 - 1.0f); + //iy1 = vid.height - (int)(y2 - 1.0f); + //iy1 = r_refdef.view.viewport.width + 2 * r_refdef.view.viewport.x - (int)(y2 - 1.0f); + iy1 = (int)(y1 - 1.0f); + ix2 = (int)(x2 + 1.0f); + //iy2 = vid.height - (int)(y1 + 1.0f); + //iy2 = r_refdef.view.viewport.height + 2 * r_refdef.view.viewport.y - (int)(y1 + 1.0f); + iy2 = (int)(y2 + 1.0f); + //Con_Printf("%f %f %f %f\n", x1, y1, x2, y2); + + // clamp it to the screen + if (ix1 < r_refdef.view.viewport.x) ix1 = r_refdef.view.viewport.x; + if (iy1 < r_refdef.view.viewport.y) iy1 = r_refdef.view.viewport.y; + if (ix2 > r_refdef.view.viewport.x + r_refdef.view.viewport.width) ix2 = r_refdef.view.viewport.x + r_refdef.view.viewport.width; + if (iy2 > r_refdef.view.viewport.y + r_refdef.view.viewport.height) iy2 = r_refdef.view.viewport.y + r_refdef.view.viewport.height; + + // if it is inside out, it's not visible + if (ix2 <= ix1 || iy2 <= iy1) + return true; + + // the light area is visible, set up the scissor rectangle + scissor[0] = ix1; + scissor[1] = iy1; + scissor[2] = ix2 - ix1; + scissor[3] = iy2 - iy1; + + // D3D Y coordinate is top to bottom, OpenGL is bottom to top, fix the D3D one + switch(vid.renderpath) + { + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + scissor[1] = vid.height - scissor[1] - scissor[3]; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + } + + return false; +} + + +static void R_Viewport_ApplyNearClipPlaneFloatGL(const r_viewport_t *v, float *m, float normalx, float normaly, float normalz, float dist) +{ + float q[4]; + float d; + float clipPlane[4], v3[3], v4[3]; + float normal[3]; + + // This is inspired by Oblique Depth Projection from http://www.terathon.com/code/oblique.php + + VectorSet(normal, normalx, normaly, normalz); + Matrix4x4_Transform3x3(&v->viewmatrix, normal, clipPlane); + VectorScale(normal, -dist, v3); + Matrix4x4_Transform(&v->viewmatrix, v3, v4); + // FIXME: LordHavoc: I think this can be done more efficiently somehow but I can't remember the technique + clipPlane[3] = -DotProduct(v4, clipPlane); + +#if 0 +{ + // testing code for comparing results + float clipPlane2[4]; + VectorCopy4(clipPlane, clipPlane2); + R_EntityMatrix(&identitymatrix); + VectorSet(q, normal[0], normal[1], normal[2], -dist); + qglClipPlane(GL_CLIP_PLANE0, q); + qglGetClipPlane(GL_CLIP_PLANE0, q); + VectorCopy4(q, clipPlane); +} +#endif + + // Calculate the clip-space corner point opposite the clipping plane + // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and + // transform it into camera space by multiplying it + // by the inverse of the projection matrix + q[0] = ((clipPlane[0] < 0.0f ? -1.0f : clipPlane[0] > 0.0f ? 1.0f : 0.0f) + m[8]) / m[0]; + q[1] = ((clipPlane[1] < 0.0f ? -1.0f : clipPlane[1] > 0.0f ? 1.0f : 0.0f) + m[9]) / m[5]; + q[2] = -1.0f; + q[3] = (1.0f + m[10]) / m[14]; + + // Calculate the scaled plane vector + d = 2.0f / DotProduct4(clipPlane, q); + + // Replace the third row of the projection matrix + m[2] = clipPlane[0] * d; + m[6] = clipPlane[1] * d; + m[10] = clipPlane[2] * d + 1.0f; + m[14] = clipPlane[3] * d; +} + +void R_Viewport_InitOrtho(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float x1, float y1, float x2, float y2, float nearclip, float farclip, const float *nearplane) +{ + float left = x1, right = x2, bottom = y2, top = y1, zNear = nearclip, zFar = farclip; + float m[16]; + memset(v, 0, sizeof(*v)); + v->type = R_VIEWPORTTYPE_ORTHO; + v->cameramatrix = *cameramatrix; + v->x = x; + v->y = y; + v->z = 0; + v->width = width; + v->height = height; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[0] = 2/(right - left); + m[5] = 2/(top - bottom); + m[10] = -2/(zFar - zNear); + m[12] = - (right + left)/(right - left); + m[13] = - (top + bottom)/(top - bottom); + m[14] = - (zFar + zNear)/(zFar - zNear); + m[15] = 1; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + m[10] = -1/(zFar - zNear); + m[14] = -zNear/(zFar-zNear); + break; + } + v->screentodepth[0] = -farclip / (farclip - nearclip); + v->screentodepth[1] = farclip * nearclip / (farclip - nearclip); + + Matrix4x4_Invert_Full(&v->viewmatrix, &v->cameramatrix); + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); + +#if 0 + { + vec4_t test1; + vec4_t test2; + Vector4Set(test1, (x1+x2)*0.5f, (y1+y2)*0.5f, 0.0f, 1.0f); + R_Viewport_TransformToScreen(v, test1, test2); + Con_Printf("%f %f %f -> %f %f %f\n", test1[0], test1[1], test1[2], test2[0], test2[1], test2[2]); + } +#endif +} + +void R_Viewport_InitPerspective(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float nearclip, float farclip, const float *nearplane) +{ + matrix4x4_t tempmatrix, basematrix; + float m[16]; + memset(v, 0, sizeof(*v)); + + v->type = R_VIEWPORTTYPE_PERSPECTIVE; + v->cameramatrix = *cameramatrix; + v->x = x; + v->y = y; + v->z = 0; + v->width = width; + v->height = height; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[0] = 1.0 / frustumx; + m[5] = 1.0 / frustumy; + m[10] = -(farclip + nearclip) / (farclip - nearclip); + m[11] = -1; + m[14] = -2 * nearclip * farclip / (farclip - nearclip); + v->screentodepth[0] = -farclip / (farclip - nearclip); + v->screentodepth[1] = farclip * nearclip / (farclip - nearclip); + + Matrix4x4_Invert_Full(&tempmatrix, &v->cameramatrix); + Matrix4x4_CreateRotate(&basematrix, -90, 1, 0, 0); + Matrix4x4_ConcatRotate(&basematrix, 90, 0, 0, 1); + Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + if(v_flipped.integer) + { + m[0] = -m[0]; + m[4] = -m[4]; + m[8] = -m[8]; + m[12] = -m[12]; + } + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); +} + +void R_Viewport_InitPerspectiveInfinite(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float nearclip, const float *nearplane) +{ + matrix4x4_t tempmatrix, basematrix; + const float nudge = 1.0 - 1.0 / (1<<23); + float m[16]; + memset(v, 0, sizeof(*v)); + + v->type = R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP; + v->cameramatrix = *cameramatrix; + v->x = x; + v->y = y; + v->z = 0; + v->width = width; + v->height = height; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[ 0] = 1.0 / frustumx; + m[ 5] = 1.0 / frustumy; + m[10] = -nudge; + m[11] = -1; + m[14] = -2 * nearclip * nudge; + v->screentodepth[0] = (m[10] + 1) * 0.5 - 1; + v->screentodepth[1] = m[14] * -0.5; + + Matrix4x4_Invert_Full(&tempmatrix, &v->cameramatrix); + Matrix4x4_CreateRotate(&basematrix, -90, 1, 0, 0); + Matrix4x4_ConcatRotate(&basematrix, 90, 0, 0, 1); + Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + if(v_flipped.integer) + { + m[0] = -m[0]; + m[4] = -m[4]; + m[8] = -m[8]; + m[12] = -m[12]; + } + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); +} + +float cubeviewmatrix[6][16] = +{ + // standard cubemap projections + { // +X + 0, 0,-1, 0, + 0,-1, 0, 0, + -1, 0, 0, 0, + 0, 0, 0, 1, + }, + { // -X + 0, 0, 1, 0, + 0,-1, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 1, + }, + { // +Y + 1, 0, 0, 0, + 0, 0,-1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1, + }, + { // -Y + 1, 0, 0, 0, + 0, 0, 1, 0, + 0,-1, 0, 0, + 0, 0, 0, 1, + }, + { // +Z + 1, 0, 0, 0, + 0,-1, 0, 0, + 0, 0,-1, 0, + 0, 0, 0, 1, + }, + { // -Z + -1, 0, 0, 0, + 0,-1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }, +}; +float rectviewmatrix[6][16] = +{ + // sign-preserving cubemap projections + { // +X + 0, 0,-1, 0, + 0, 1, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 1, + }, + { // -X + 0, 0, 1, 0, + 0, 1, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 1, + }, + { // +Y + 1, 0, 0, 0, + 0, 0,-1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1, + }, + { // -Y + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1, + }, + { // +Z + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0,-1, 0, + 0, 0, 0, 1, + }, + { // -Z + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }, +}; + +void R_Viewport_InitCubeSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, float nearclip, float farclip, const float *nearplane) +{ + matrix4x4_t tempmatrix, basematrix; + float m[16]; + memset(v, 0, sizeof(*v)); + v->type = R_VIEWPORTTYPE_PERSPECTIVECUBESIDE; + v->cameramatrix = *cameramatrix; + v->width = size; + v->height = size; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[0] = m[5] = 1.0f; + m[10] = -(farclip + nearclip) / (farclip - nearclip); + m[11] = -1; + m[14] = -2 * nearclip * farclip / (farclip - nearclip); + + Matrix4x4_FromArrayFloatGL(&basematrix, cubeviewmatrix[side]); + Matrix4x4_Invert_Simple(&tempmatrix, &v->cameramatrix); + Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); +} + +void R_Viewport_InitRectSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, int border, float nearclip, float farclip, const float *nearplane) +{ + matrix4x4_t tempmatrix, basematrix; + float m[16]; + memset(v, 0, sizeof(*v)); + v->type = R_VIEWPORTTYPE_PERSPECTIVECUBESIDE; + v->cameramatrix = *cameramatrix; + v->x = (side & 1) * size; + v->y = (side >> 1) * size; + v->width = size; + v->height = size; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[0] = m[5] = 1.0f * ((float)size - border) / size; + m[10] = -(farclip + nearclip) / (farclip - nearclip); + m[11] = -1; + m[14] = -2 * nearclip * farclip / (farclip - nearclip); + + Matrix4x4_FromArrayFloatGL(&basematrix, rectviewmatrix[side]); + Matrix4x4_Invert_Simple(&tempmatrix, &v->cameramatrix); + Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GL13: + case RENDERPATH_GL11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + m[5] *= -1; + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); +} + +void R_SetViewport(const r_viewport_t *v) +{ + float m[16]; + gl_viewport = *v; + + // FIXME: v_flipped_state is evil, this probably breaks somewhere + GL_SetMirrorState(v_flipped.integer && (v->type == R_VIEWPORTTYPE_PERSPECTIVE || v->type == R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP)); + + // copy over the matrices to our state + gl_viewmatrix = v->viewmatrix; + gl_projectionmatrix = v->projectmatrix; + + switch(vid.renderpath) + { + case RENDERPATH_GL13: + case RENDERPATH_GL11: + case RENDERPATH_GLES1: + CHECKGLERROR + qglViewport(v->x, v->y, v->width, v->height);CHECKGLERROR + // Load the projection matrix into OpenGL + qglMatrixMode(GL_PROJECTION);CHECKGLERROR + Matrix4x4_ToArrayFloatGL(&gl_projectionmatrix, m); + qglLoadMatrixf(m);CHECKGLERROR + qglMatrixMode(GL_MODELVIEW);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DVIEWPORT9 d3dviewport; + d3dviewport.X = gl_viewport.x; + d3dviewport.Y = gl_viewport.y; + d3dviewport.Width = gl_viewport.width; + d3dviewport.Height = gl_viewport.height; + d3dviewport.MinZ = gl_state.depthrange[0]; + d3dviewport.MaxZ = gl_state.depthrange[1]; + IDirect3DDevice9_SetViewport(vid_d3d9dev, &d3dviewport); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Viewport(v->x, v->y, v->width, v->height); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + CHECKGLERROR + qglViewport(v->x, v->y, v->width, v->height);CHECKGLERROR + break; + } + + // force an update of the derived matrices + gl_modelmatrixchanged = true; + R_EntityMatrix(&gl_modelmatrix); +} + +void R_GetViewport(r_viewport_t *v) +{ + *v = gl_viewport; +} + +static void GL_BindVBO(int bufferobject) +{ + if (gl_state.vertexbufferobject != bufferobject) + { + gl_state.vertexbufferobject = bufferobject; + CHECKGLERROR + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, bufferobject);CHECKGLERROR + } +} + +static void GL_BindEBO(int bufferobject) +{ + if (gl_state.elementbufferobject != bufferobject) + { + gl_state.elementbufferobject = bufferobject; + CHECKGLERROR + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, bufferobject);CHECKGLERROR + } +} + +int R_Mesh_CreateFramebufferObject(rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4) +{ + int temp; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (!vid.support.ext_framebuffer_object) + return 0; + qglGenFramebuffersEXT(1, (GLuint*)&temp);CHECKGLERROR + R_Mesh_SetRenderTargets(temp, NULL, NULL, NULL, NULL, NULL); + if (depthtexture) qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, depthtexture->gltexturetypeenum, R_GetTexture(depthtexture), 0);CHECKGLERROR + if (colortexture) qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, colortexture->gltexturetypeenum, R_GetTexture(colortexture), 0);CHECKGLERROR + if (colortexture2) qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, colortexture2->gltexturetypeenum, R_GetTexture(colortexture2), 0);CHECKGLERROR + if (colortexture3) qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT2_EXT, colortexture3->gltexturetypeenum, R_GetTexture(colortexture3), 0);CHECKGLERROR + if (colortexture4) qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT3_EXT, colortexture4->gltexturetypeenum, R_GetTexture(colortexture4), 0);CHECKGLERROR + return temp; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + return 1; + case RENDERPATH_SOFT: + return 1; + } + return 0; +} + +void R_Mesh_DestroyFramebufferObject(int fbo) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (fbo) + qglDeleteFramebuffersEXT(1, (GLuint*)&fbo); + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } +} + +#ifdef SUPPORTD3D +void R_Mesh_SetRenderTargetsD3D9(IDirect3DSurface9 *depthsurface, IDirect3DSurface9 *colorsurface0, IDirect3DSurface9 *colorsurface1, IDirect3DSurface9 *colorsurface2, IDirect3DSurface9 *colorsurface3) +{ +// LordHavoc: for some weird reason the redundant SetDepthStencilSurface calls are necessary (otherwise the lights fail depth test, as if they were using the shadowmap depth surface and render target still) + if (gl_state.d3drt_depthsurface == depthsurface && gl_state.d3drt_colorsurfaces[0] == colorsurface0 && gl_state.d3drt_colorsurfaces[1] == colorsurface1 && gl_state.d3drt_colorsurfaces[2] == colorsurface2 && gl_state.d3drt_colorsurfaces[3] == colorsurface3) + return; + + gl_state.framebufferobject = depthsurface != gl_state.d3drt_backbufferdepthsurface || colorsurface0 != gl_state.d3drt_backbuffercolorsurface; + if (gl_state.d3drt_depthsurface != depthsurface) + { + gl_state.d3drt_depthsurface = depthsurface; + IDirect3DDevice9_SetDepthStencilSurface(vid_d3d9dev, gl_state.d3drt_depthsurface); + } + if (gl_state.d3drt_colorsurfaces[0] != colorsurface0) + { + gl_state.d3drt_colorsurfaces[0] = colorsurface0; + IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 0, gl_state.d3drt_colorsurfaces[0]); + } + if (gl_state.d3drt_colorsurfaces[1] != colorsurface1) + { + gl_state.d3drt_colorsurfaces[1] = colorsurface1; + IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 1, gl_state.d3drt_colorsurfaces[1]); + } + if (gl_state.d3drt_colorsurfaces[2] != colorsurface2) + { + gl_state.d3drt_colorsurfaces[2] = colorsurface2; + IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 2, gl_state.d3drt_colorsurfaces[2]); + } + if (gl_state.d3drt_colorsurfaces[3] != colorsurface3) + { + gl_state.d3drt_colorsurfaces[3] = colorsurface3; + IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 3, gl_state.d3drt_colorsurfaces[3]); + } +} +#endif + +void R_Mesh_ResetRenderTargets(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (gl_state.framebufferobject) + { + gl_state.framebufferobject = 0; + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, gl_state.defaultframebufferobject); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + R_Mesh_SetRenderTargetsD3D9(gl_state.d3drt_backbufferdepthsurface, gl_state.d3drt_backbuffercolorsurface, NULL, NULL, NULL); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); + break; + } +} + +void R_Mesh_SetRenderTargets(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4) +{ + unsigned int i; + unsigned int j; + rtexture_t *textures[5]; + Vector4Set(textures, colortexture, colortexture2, colortexture3, colortexture4); + textures[4] = depthtexture; + // unbind any matching textures immediately, otherwise D3D will complain about a bound texture being used as a render target + for (j = 0;j < 5;j++) + if (textures[j]) + for (i = 0;i < vid.teximageunits;i++) + if (gl_state.units[i].texture == textures[j]) + R_Mesh_TexBind(i, NULL); + // set up framebuffer object or render targets for the active rendering API + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (gl_state.framebufferobject != fbo) + { + gl_state.framebufferobject = fbo; + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, gl_state.framebufferobject ? gl_state.framebufferobject : gl_state.defaultframebufferobject); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + // set up the new render targets, a NULL depthtexture intentionally binds nothing + // TODO: optimize: keep surface pointer around in rtexture_t until texture is freed or lost + if (fbo) + { + IDirect3DSurface9 *colorsurfaces[4]; + for (i = 0;i < 4;i++) + { + colorsurfaces[i] = NULL; + if (textures[i]) + IDirect3DTexture9_GetSurfaceLevel((IDirect3DTexture9 *)textures[i]->d3dtexture, 0, &colorsurfaces[i]); + } + // set the render targets for real + R_Mesh_SetRenderTargetsD3D9(depthtexture ? (IDirect3DSurface9 *)depthtexture->d3dtexture : NULL, colorsurfaces[0], colorsurfaces[1], colorsurfaces[2], colorsurfaces[3]); + // release the texture surface levels (they won't be lost while bound...) + for (i = 0;i < 4;i++) + if (textures[i]) + IDirect3DSurface9_Release(colorsurfaces[i]); + } + else + R_Mesh_SetRenderTargetsD3D9(gl_state.d3drt_backbufferdepthsurface, gl_state.d3drt_backbuffercolorsurface, NULL, NULL, NULL); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (fbo) + { + int width, height; + unsigned int *pointers[5]; + memset(pointers, 0, sizeof(pointers)); + for (i = 0;i < 5;i++) + pointers[i] = textures[i] ? (unsigned int *)DPSOFTRAST_Texture_GetPixelPointer(textures[i]->texnum, 0) : NULL; + width = DPSOFTRAST_Texture_GetWidth(textures[0] ? textures[0]->texnum : textures[4]->texnum, 0); + height = DPSOFTRAST_Texture_GetHeight(textures[0] ? textures[0]->texnum : textures[4]->texnum, 0); + DPSOFTRAST_SetRenderTargets(width, height, pointers[4], pointers[0], pointers[1], pointers[2], pointers[3]); + } + else + DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); + break; + } +} + +#ifdef SUPPORTD3D +static int d3dcmpforglfunc(int f) +{ + switch(f) + { + case GL_NEVER: return D3DCMP_NEVER; + case GL_LESS: return D3DCMP_LESS; + case GL_EQUAL: return D3DCMP_EQUAL; + case GL_LEQUAL: return D3DCMP_LESSEQUAL; + case GL_GREATER: return D3DCMP_GREATER; + case GL_NOTEQUAL: return D3DCMP_NOTEQUAL; + case GL_GEQUAL: return D3DCMP_GREATEREQUAL; + case GL_ALWAYS: return D3DCMP_ALWAYS; + default: Con_DPrintf("Unknown GL_DepthFunc\n");return D3DCMP_ALWAYS; + } +} + +static int d3dstencilopforglfunc(int f) +{ + switch(f) + { + case GL_KEEP: return D3DSTENCILOP_KEEP; + case GL_INCR: return D3DSTENCILOP_INCR; // note: GL_INCR is clamped, D3DSTENCILOP_INCR wraps + case GL_DECR: return D3DSTENCILOP_DECR; // note: GL_DECR is clamped, D3DSTENCILOP_DECR wraps + default: Con_DPrintf("Unknown GL_StencilFunc\n");return D3DSTENCILOP_KEEP; + } +} +#endif + +extern cvar_t r_transparent_alphatocoverage; + +static void GL_Backend_ResetState(void) +{ + unsigned int i; + gl_state.active = true; + gl_state.depthtest = true; + gl_state.alphatest = false; + gl_state.alphafunc = GL_GEQUAL; + gl_state.alphafuncvalue = 0.5f; + gl_state.alphatocoverage = false; + gl_state.blendfunc1 = GL_ONE; + gl_state.blendfunc2 = GL_ZERO; + gl_state.blend = false; + gl_state.depthmask = GL_TRUE; + gl_state.colormask = 15; + gl_state.color4f[0] = gl_state.color4f[1] = gl_state.color4f[2] = gl_state.color4f[3] = 1; + gl_state.lockrange_first = 0; + gl_state.lockrange_count = 0; + gl_state.cullface = GL_FRONT; + gl_state.cullfaceenable = false; + gl_state.polygonoffset[0] = 0; + gl_state.polygonoffset[1] = 0; + gl_state.framebufferobject = 0; + gl_state.depthfunc = GL_LEQUAL; + + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_COLORWRITEENABLE, gl_state.colormask); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_NONE); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZFUNC, d3dcmpforglfunc(gl_state.depthfunc)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZENABLE, gl_state.depthtest); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZWRITEENABLE, gl_state.depthmask); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SLOPESCALEDEPTHBIAS, gl_state.polygonoffset[0]); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DEPTHBIAS, gl_state.polygonoffset[1] * (1.0f / 16777216.0f)); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + CHECKGLERROR + + qglColorMask(1, 1, 1, 1);CHECKGLERROR + qglAlphaFunc(gl_state.alphafunc, gl_state.alphafuncvalue);CHECKGLERROR + qglDisable(GL_ALPHA_TEST);CHECKGLERROR + qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR + qglDisable(GL_BLEND);CHECKGLERROR + qglCullFace(gl_state.cullface);CHECKGLERROR + qglDisable(GL_CULL_FACE);CHECKGLERROR + qglDepthFunc(GL_LEQUAL);CHECKGLERROR + qglEnable(GL_DEPTH_TEST);CHECKGLERROR + qglDepthMask(gl_state.depthmask);CHECKGLERROR + qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + + if (vid.support.arb_vertex_buffer_object) + { + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + } + + if (vid.support.ext_framebuffer_object) + { + //qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + } + + qglVertexPointer(3, GL_FLOAT, sizeof(float[3]), NULL);CHECKGLERROR + qglEnableClientState(GL_VERTEX_ARRAY);CHECKGLERROR + + qglColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL);CHECKGLERROR + qglDisableClientState(GL_COLOR_ARRAY);CHECKGLERROR + qglColor4f(1, 1, 1, 1);CHECKGLERROR + + if (vid.support.ext_framebuffer_object) + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, gl_state.framebufferobject); + + gl_state.unit = MAX_TEXTUREUNITS; + gl_state.clientunit = MAX_TEXTUREUNITS; + for (i = 0;i < vid.texunits;i++) + { + GL_ActiveTexture(i); + GL_ClientActiveTexture(i); + qglDisable(GL_TEXTURE_2D);CHECKGLERROR + qglBindTexture(GL_TEXTURE_2D, 0);CHECKGLERROR + if (vid.support.ext_texture_3d) + { + qglDisable(GL_TEXTURE_3D);CHECKGLERROR + qglBindTexture(GL_TEXTURE_3D, 0);CHECKGLERROR + } + if (vid.support.arb_texture_cube_map) + { + qglDisable(GL_TEXTURE_CUBE_MAP_ARB);CHECKGLERROR + qglBindTexture(GL_TEXTURE_CUBE_MAP_ARB, 0);CHECKGLERROR + } + GL_BindVBO(0); + qglTexCoordPointer(2, GL_FLOAT, sizeof(float[2]), NULL);CHECKGLERROR + qglDisableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR + qglMatrixMode(GL_TEXTURE);CHECKGLERROR + qglLoadIdentity();CHECKGLERROR + qglMatrixMode(GL_MODELVIEW);CHECKGLERROR + qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);CHECKGLERROR + } + CHECKGLERROR + break; + case RENDERPATH_SOFT: + DPSOFTRAST_ColorMask(1,1,1,1); + DPSOFTRAST_BlendFunc(gl_state.blendfunc1, gl_state.blendfunc2); + DPSOFTRAST_CullFace(gl_state.cullface); + DPSOFTRAST_DepthFunc(gl_state.depthfunc); + DPSOFTRAST_DepthMask(gl_state.depthmask); + DPSOFTRAST_PolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); + DPSOFTRAST_Viewport(0, 0, vid.width, vid.height); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + CHECKGLERROR + qglColorMask(1, 1, 1, 1);CHECKGLERROR + qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR + qglDisable(GL_BLEND);CHECKGLERROR + qglCullFace(gl_state.cullface);CHECKGLERROR + qglDisable(GL_CULL_FACE);CHECKGLERROR + qglDepthFunc(GL_LEQUAL);CHECKGLERROR + qglEnable(GL_DEPTH_TEST);CHECKGLERROR + qglDepthMask(gl_state.depthmask);CHECKGLERROR + qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + if (vid.support.arb_vertex_buffer_object) + { + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + } + if (vid.support.ext_framebuffer_object) + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, gl_state.defaultframebufferobject); + qglEnableVertexAttribArray(GLSLATTRIB_POSITION); + qglVertexAttribPointer(GLSLATTRIB_POSITION, 3, GL_FLOAT, false, sizeof(float[3]), NULL);CHECKGLERROR + qglDisableVertexAttribArray(GLSLATTRIB_COLOR); + qglVertexAttribPointer(GLSLATTRIB_COLOR, 4, GL_FLOAT, false, sizeof(float[4]), NULL);CHECKGLERROR + qglVertexAttrib4f(GLSLATTRIB_COLOR, 1, 1, 1, 1); + gl_state.unit = MAX_TEXTUREUNITS; + gl_state.clientunit = MAX_TEXTUREUNITS; + for (i = 0;i < vid.teximageunits;i++) + { + GL_ActiveTexture(i); + qglBindTexture(GL_TEXTURE_2D, 0);CHECKGLERROR + if (vid.support.ext_texture_3d) + { + qglBindTexture(GL_TEXTURE_3D, 0);CHECKGLERROR + } + if (vid.support.arb_texture_cube_map) + { + qglBindTexture(GL_TEXTURE_CUBE_MAP_ARB, 0);CHECKGLERROR + } + } + for (i = 0;i < vid.texarrayunits;i++) + { + GL_BindVBO(0); + qglVertexAttribPointer(i+GLSLATTRIB_TEXCOORD0, 2, GL_FLOAT, false, sizeof(float[2]), NULL);CHECKGLERROR + qglDisableVertexAttribArray(i+GLSLATTRIB_TEXCOORD0);CHECKGLERROR + } + CHECKGLERROR + break; + } +} + +void GL_ActiveTexture(unsigned int num) +{ + if (gl_state.unit != num) + { + gl_state.unit = num; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (qglActiveTexture) + { + CHECKGLERROR + qglActiveTexture(GL_TEXTURE0_ARB + gl_state.unit); + CHECKGLERROR + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } + } +} + +void GL_ClientActiveTexture(unsigned int num) +{ + if (gl_state.clientunit != num) + { + gl_state.clientunit = num; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (qglActiveTexture) + { + CHECKGLERROR + qglClientActiveTexture(GL_TEXTURE0_ARB + gl_state.clientunit); + CHECKGLERROR + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + break; + } + } +} + +void GL_BlendFunc(int blendfunc1, int blendfunc2) +{ + if (gl_state.blendfunc1 != blendfunc1 || gl_state.blendfunc2 != blendfunc2) + { + qboolean blendenable; + gl_state.blendfunc1 = blendfunc1; + gl_state.blendfunc2 = blendfunc2; + blendenable = (gl_state.blendfunc1 != GL_ONE || gl_state.blendfunc2 != GL_ZERO); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR + if (gl_state.blend != blendenable) + { + gl_state.blend = blendenable; + if (!gl_state.blend) + { + qglDisable(GL_BLEND);CHECKGLERROR + } + else + { + qglEnable(GL_BLEND);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + int i; + int glblendfunc[2]; + D3DBLEND d3dblendfunc[2]; + glblendfunc[0] = gl_state.blendfunc1; + glblendfunc[1] = gl_state.blendfunc2; + for (i = 0;i < 2;i++) + { + switch(glblendfunc[i]) + { + case GL_ZERO: d3dblendfunc[i] = D3DBLEND_ZERO;break; + case GL_ONE: d3dblendfunc[i] = D3DBLEND_ONE;break; + case GL_SRC_COLOR: d3dblendfunc[i] = D3DBLEND_SRCCOLOR;break; + case GL_ONE_MINUS_SRC_COLOR: d3dblendfunc[i] = D3DBLEND_INVSRCCOLOR;break; + case GL_SRC_ALPHA: d3dblendfunc[i] = D3DBLEND_SRCALPHA;break; + case GL_ONE_MINUS_SRC_ALPHA: d3dblendfunc[i] = D3DBLEND_INVSRCALPHA;break; + case GL_DST_ALPHA: d3dblendfunc[i] = D3DBLEND_DESTALPHA;break; + case GL_ONE_MINUS_DST_ALPHA: d3dblendfunc[i] = D3DBLEND_INVDESTALPHA;break; + case GL_DST_COLOR: d3dblendfunc[i] = D3DBLEND_DESTCOLOR;break; + case GL_ONE_MINUS_DST_COLOR: d3dblendfunc[i] = D3DBLEND_INVDESTCOLOR;break; + } + } + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SRCBLEND, d3dblendfunc[0]); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DESTBLEND, d3dblendfunc[1]); + if (gl_state.blend != blendenable) + { + gl_state.blend = blendenable; + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ALPHABLENDENABLE, gl_state.blend); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_BlendFunc(gl_state.blendfunc1, gl_state.blendfunc2); + break; + } + } +} + +void GL_DepthMask(int state) +{ + if (gl_state.depthmask != state) + { + gl_state.depthmask = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglDepthMask(gl_state.depthmask);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZWRITEENABLE, gl_state.depthmask); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DepthMask(gl_state.depthmask); + break; + } + } +} + +void GL_DepthTest(int state) +{ + if (gl_state.depthtest != state) + { + gl_state.depthtest = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (gl_state.depthtest) + { + qglEnable(GL_DEPTH_TEST);CHECKGLERROR + } + else + { + qglDisable(GL_DEPTH_TEST);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZENABLE, gl_state.depthtest); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DepthTest(gl_state.depthtest); + break; + } + } +} + +void GL_DepthFunc(int state) +{ + if (gl_state.depthfunc != state) + { + gl_state.depthfunc = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglDepthFunc(gl_state.depthfunc);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZFUNC, d3dcmpforglfunc(gl_state.depthfunc)); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DepthFunc(gl_state.depthfunc); + break; + } + } +} + +void GL_DepthRange(float nearfrac, float farfrac) +{ + if (gl_state.depthrange[0] != nearfrac || gl_state.depthrange[1] != farfrac) + { + gl_state.depthrange[0] = nearfrac; + gl_state.depthrange[1] = farfrac; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglDepthRange(gl_state.depthrange[0], gl_state.depthrange[1]); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DVIEWPORT9 d3dviewport; + d3dviewport.X = gl_viewport.x; + d3dviewport.Y = gl_viewport.y; + d3dviewport.Width = gl_viewport.width; + d3dviewport.Height = gl_viewport.height; + d3dviewport.MinZ = gl_state.depthrange[0]; + d3dviewport.MaxZ = gl_state.depthrange[1]; + IDirect3DDevice9_SetViewport(vid_d3d9dev, &d3dviewport); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DepthRange(gl_state.depthrange[0], gl_state.depthrange[1]); + break; + } + } +} + +void R_SetStencilSeparate(qboolean enable, int writemask, int frontfail, int frontzfail, int frontzpass, int backfail, int backzfail, int backzpass, int frontcompare, int backcompare, int comparereference, int comparemask) +{ + switch (vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (enable) + { + qglEnable(GL_STENCIL_TEST);CHECKGLERROR + } + else + { + qglDisable(GL_STENCIL_TEST);CHECKGLERROR + } + if (vid.support.ati_separate_stencil) + { + qglStencilMask(writemask);CHECKGLERROR + qglStencilOpSeparate(GL_FRONT, frontfail, frontzfail, frontzpass);CHECKGLERROR + qglStencilOpSeparate(GL_BACK, backfail, backzfail, backzpass);CHECKGLERROR + qglStencilFuncSeparate(frontcompare, backcompare, comparereference, comparereference);CHECKGLERROR + } + else if (vid.support.ext_stencil_two_side) + { + qglEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);CHECKGLERROR + qglActiveStencilFaceEXT(GL_FRONT);CHECKGLERROR + qglStencilMask(writemask);CHECKGLERROR + qglStencilOp(frontfail, frontzfail, frontzpass);CHECKGLERROR + qglStencilFunc(frontcompare, comparereference, comparemask);CHECKGLERROR + qglActiveStencilFaceEXT(GL_BACK);CHECKGLERROR + qglStencilMask(writemask);CHECKGLERROR + qglStencilOp(backfail, backzfail, backzpass);CHECKGLERROR + qglStencilFunc(backcompare, comparereference, comparemask);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_TWOSIDEDSTENCILMODE, true); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILENABLE, enable); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILWRITEMASK, writemask); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFAIL, d3dstencilopforglfunc(frontfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILZFAIL, d3dstencilopforglfunc(frontzfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILPASS, d3dstencilopforglfunc(frontzpass)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFUNC, d3dcmpforglfunc(frontcompare)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILFAIL, d3dstencilopforglfunc(backfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILZFAIL, d3dstencilopforglfunc(backzfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILPASS, d3dstencilopforglfunc(backzpass)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILFUNC, d3dcmpforglfunc(backcompare)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILREF, comparereference); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILMASK, comparemask); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } +} + +void R_SetStencil(qboolean enable, int writemask, int fail, int zfail, int zpass, int compare, int comparereference, int comparemask) +{ + switch (vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (enable) + { + qglEnable(GL_STENCIL_TEST);CHECKGLERROR + } + else + { + qglDisable(GL_STENCIL_TEST);CHECKGLERROR + } + if (vid.support.ext_stencil_two_side) + { + qglDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);CHECKGLERROR + } + qglStencilMask(writemask);CHECKGLERROR + qglStencilOp(fail, zfail, zpass);CHECKGLERROR + qglStencilFunc(compare, comparereference, comparemask);CHECKGLERROR + CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (vid.support.ati_separate_stencil) + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_TWOSIDEDSTENCILMODE, true); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILENABLE, enable); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILWRITEMASK, writemask); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFAIL, d3dstencilopforglfunc(fail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILZFAIL, d3dstencilopforglfunc(zfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILPASS, d3dstencilopforglfunc(zpass)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFUNC, d3dcmpforglfunc(compare)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILREF, comparereference); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILMASK, comparemask); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } +} + +void GL_PolygonOffset(float planeoffset, float depthoffset) +{ + if (gl_state.polygonoffset[0] != planeoffset || gl_state.polygonoffset[1] != depthoffset) + { + gl_state.polygonoffset[0] = planeoffset; + gl_state.polygonoffset[1] = depthoffset; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SLOPESCALEDEPTHBIAS, gl_state.polygonoffset[0]); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DEPTHBIAS, gl_state.polygonoffset[1] * (1.0f / 16777216.0f)); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_PolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + break; + } + } +} + +void GL_SetMirrorState(qboolean state) +{ + if (v_flipped_state != state) + { + v_flipped_state = state; + if (gl_state.cullface == GL_BACK) + gl_state.cullface = GL_FRONT; + else if (gl_state.cullface == GL_FRONT) + gl_state.cullface = GL_BACK; + else + return; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglCullFace(gl_state.cullface);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, gl_state.cullface == GL_FRONT ? D3DCULL_CCW : D3DCULL_CW); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_CullFace(gl_state.cullface); + break; + } + } +} + +void GL_CullFace(int state) +{ + if(v_flipped_state) + { + if(state == GL_FRONT) + state = GL_BACK; + else if(state == GL_BACK) + state = GL_FRONT; + } + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + + if (state != GL_NONE) + { + if (!gl_state.cullfaceenable) + { + gl_state.cullfaceenable = true; + qglEnable(GL_CULL_FACE);CHECKGLERROR + } + if (gl_state.cullface != state) + { + gl_state.cullface = state; + qglCullFace(gl_state.cullface);CHECKGLERROR + } + } + else + { + if (gl_state.cullfaceenable) + { + gl_state.cullfaceenable = false; + qglDisable(GL_CULL_FACE);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (gl_state.cullface != state) + { + gl_state.cullface = state; + switch(gl_state.cullface) + { + case GL_NONE: + gl_state.cullfaceenable = false; + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_NONE); + break; + case GL_FRONT: + gl_state.cullfaceenable = true; + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_CCW); + break; + case GL_BACK: + gl_state.cullfaceenable = true; + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_CW); + break; + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (gl_state.cullface != state) + { + gl_state.cullface = state; + gl_state.cullfaceenable = state != GL_NONE ? true : false; + DPSOFTRAST_CullFace(gl_state.cullface); + } + break; + } +} + +void GL_AlphaTest(int state) +{ + if (gl_state.alphatest != state) + { + gl_state.alphatest = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + // only fixed function uses alpha test, other paths use pixel kill capability in shaders + CHECKGLERROR + if (gl_state.alphatest) + { + qglEnable(GL_ALPHA_TEST);CHECKGLERROR + } + else + { + qglDisable(GL_ALPHA_TEST);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + break; + } + } +} + +void GL_AlphaToCoverage(qboolean state) +{ + if (gl_state.alphatocoverage != state) + { + gl_state.alphatocoverage = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + case RENDERPATH_GL20: + // alpha to coverage turns the alpha value of the pixel into 0%, 25%, 50%, 75% or 100% by masking the multisample fragments accordingly + CHECKGLERROR + if (gl_state.alphatocoverage) + { + qglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);CHECKGLERROR +// qglEnable(GL_MULTISAMPLE_ARB);CHECKGLERROR + } + else + { + qglDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);CHECKGLERROR +// qglDisable(GL_MULTISAMPLE_ARB);CHECKGLERROR + } + break; + } + } +} + +void GL_ColorMask(int r, int g, int b, int a) +{ + // NOTE: this matches D3DCOLORWRITEENABLE_RED, GREEN, BLUE, ALPHA + int state = (r ? 1 : 0) | (g ? 2 : 0) | (b ? 4 : 0) | (a ? 8 : 0); + if (gl_state.colormask != state) + { + gl_state.colormask = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglColorMask((GLboolean)r, (GLboolean)g, (GLboolean)b, (GLboolean)a);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_COLORWRITEENABLE, state); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_ColorMask(r, g, b, a); + break; + } + } +} + +void GL_Color(float cr, float cg, float cb, float ca) +{ + if (gl_state.pointer_color_enabled || gl_state.color4f[0] != cr || gl_state.color4f[1] != cg || gl_state.color4f[2] != cb || gl_state.color4f[3] != ca) + { + gl_state.color4f[0] = cr; + gl_state.color4f[1] = cg; + gl_state.color4f[2] = cb; + gl_state.color4f[3] = ca; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + CHECKGLERROR + qglColor4f(gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]); + CHECKGLERROR + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + // no equivalent in D3D + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Color4f(cr, cg, cb, ca); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + qglVertexAttrib4f(GLSLATTRIB_COLOR, cr, cg, cb, ca); + break; + } + } +} + +void GL_Scissor (int x, int y, int width, int height) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglScissor(x, y,width,height); + CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + RECT d3drect; + d3drect.left = x; + d3drect.top = y; + d3drect.right = x + width; + d3drect.bottom = y + height; + IDirect3DDevice9_SetScissorRect(vid_d3d9dev, &d3drect); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Scissor(x, y, width, height); + break; + } +} + +void GL_ScissorTest(int state) +{ + if (gl_state.scissortest != state) + { + gl_state.scissortest = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if(gl_state.scissortest) + qglEnable(GL_SCISSOR_TEST); + else + qglDisable(GL_SCISSOR_TEST); + CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SCISSORTESTENABLE, gl_state.scissortest); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_ScissorTest(gl_state.scissortest); + break; + } + } +} + +void GL_Clear(int mask, const float *colorvalue, float depthvalue, int stencilvalue) +{ + static const float blackcolor[4] = {0, 0, 0, 0}; + // prevent warnings when trying to clear a buffer that does not exist + if (!colorvalue) + colorvalue = blackcolor; + if (!vid.stencil) + { + mask &= ~GL_STENCIL_BUFFER_BIT; + stencilvalue = 0; + } + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (mask & GL_COLOR_BUFFER_BIT) + { + qglClearColor(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]);CHECKGLERROR + } + if (mask & GL_DEPTH_BUFFER_BIT) + { + qglClearDepth(depthvalue);CHECKGLERROR + } + if (mask & GL_STENCIL_BUFFER_BIT) + { + qglClearStencil(stencilvalue);CHECKGLERROR + } + qglClear(mask);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_Clear(vid_d3d9dev, 0, NULL, ((mask & GL_COLOR_BUFFER_BIT) ? D3DCLEAR_TARGET : 0) | ((mask & GL_STENCIL_BUFFER_BIT) ? D3DCLEAR_STENCIL : 0) | ((mask & GL_DEPTH_BUFFER_BIT) ? D3DCLEAR_ZBUFFER : 0), D3DCOLOR_COLORVALUE(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]), depthvalue, stencilvalue); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (mask & GL_COLOR_BUFFER_BIT) + DPSOFTRAST_ClearColor(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]); + if (mask & GL_DEPTH_BUFFER_BIT) + DPSOFTRAST_ClearDepth(depthvalue); + break; + } +} + +void GL_ReadPixelsBGRA(int x, int y, int width, int height, unsigned char *outpixels) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglReadPixels(x, y, width, height, GL_BGRA, GL_UNSIGNED_BYTE, outpixels);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + // LordHavoc: we can't directly download the backbuffer because it may be + // multisampled, and it may not be lockable, so we blit it to a lockable + // surface of the same dimensions (but without multisample) to resolve the + // multisample buffer to a normal image, and then lock that... + IDirect3DSurface9 *stretchsurface = NULL; + if (!FAILED(IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, TRUE, &stretchsurface, NULL))) + { + D3DLOCKED_RECT lockedrect; + if (!FAILED(IDirect3DDevice9_StretchRect(vid_d3d9dev, gl_state.d3drt_backbuffercolorsurface, NULL, stretchsurface, NULL, D3DTEXF_POINT))) + { + if (!FAILED(IDirect3DSurface9_LockRect(stretchsurface, &lockedrect, NULL, D3DLOCK_READONLY))) + { + int line; + unsigned char *row = (unsigned char *)lockedrect.pBits + x * 4 + lockedrect.Pitch * (vid.height - 1 - y); + for (line = 0;line < height;line++, row -= lockedrect.Pitch) + memcpy(outpixels + line * width * 4, row, width * 4); + IDirect3DSurface9_UnlockRect(stretchsurface); + } + } + IDirect3DSurface9_Release(stretchsurface); + } + // code scraps + //IDirect3DSurface9 *syssurface = NULL; + //if (!FAILED(IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, FALSE, &stretchsurface, NULL))) + //if (!FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &syssurface, NULL))) + //IDirect3DDevice9_GetRenderTargetData(vid_d3d9dev, gl_state.d3drt_backbuffercolorsurface, syssurface); + //if (!FAILED(IDirect3DDevice9_GetFrontBufferData(vid_d3d9dev, 0, syssurface))) + //if (!FAILED(IDirect3DSurface9_LockRect(syssurface, &lockedrect, NULL, D3DLOCK_READONLY))) + //IDirect3DSurface9_UnlockRect(syssurface); + //IDirect3DSurface9_Release(syssurface); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_GetPixelsBGRA(x, y, width, height, outpixels); + break; + } +} + +// called at beginning of frame +void R_Mesh_Start(void) +{ + BACKENDACTIVECHECK + R_Mesh_ResetRenderTargets(); + R_Mesh_SetUseVBO(); + if (gl_printcheckerror.integer && !gl_paranoid.integer) + { + Con_Printf("WARNING: gl_printcheckerror is on but gl_paranoid is off, turning it on...\n"); + Cvar_SetValueQuick(&gl_paranoid, 1); + } +} + +qboolean GL_Backend_CompileShader(int programobject, GLenum shadertypeenum, const char *shadertype, int numstrings, const char **strings) +{ + int shaderobject; + int shadercompiled; + char compilelog[MAX_INPUTLINE]; + shaderobject = qglCreateShader(shadertypeenum);CHECKGLERROR + if (!shaderobject) + return false; + qglShaderSource(shaderobject, numstrings, strings, NULL);CHECKGLERROR + qglCompileShader(shaderobject);CHECKGLERROR + qglGetShaderiv(shaderobject, GL_COMPILE_STATUS, &shadercompiled);CHECKGLERROR + qglGetShaderInfoLog(shaderobject, sizeof(compilelog), NULL, compilelog);CHECKGLERROR + if (compilelog[0] && (strstr(compilelog, "error") || strstr(compilelog, "ERROR") || strstr(compilelog, "Error") || strstr(compilelog, "WARNING") || strstr(compilelog, "warning") || strstr(compilelog, "Warning"))) + { + int i, j, pretextlines = 0; + for (i = 0;i < numstrings - 1;i++) + for (j = 0;strings[i][j];j++) + if (strings[i][j] == '\n') + pretextlines++; + Con_Printf("%s shader compile log:\n%s\n(line offset for any above warnings/errors: %i)\n", shadertype, compilelog, pretextlines); + } + if (!shadercompiled) + { + qglDeleteShader(shaderobject);CHECKGLERROR + return false; + } + qglAttachShader(programobject, shaderobject);CHECKGLERROR + qglDeleteShader(shaderobject);CHECKGLERROR + return true; +} + +unsigned int GL_Backend_CompileProgram(int vertexstrings_count, const char **vertexstrings_list, int geometrystrings_count, const char **geometrystrings_list, int fragmentstrings_count, const char **fragmentstrings_list) +{ + GLint programlinked; + GLuint programobject = 0; + char linklog[MAX_INPUTLINE]; + CHECKGLERROR + + programobject = qglCreateProgram();CHECKGLERROR + if (!programobject) + return 0; + + qglBindAttribLocation(programobject, GLSLATTRIB_POSITION , "Attrib_Position" ); + qglBindAttribLocation(programobject, GLSLATTRIB_COLOR , "Attrib_Color" ); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD0, "Attrib_TexCoord0"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD1, "Attrib_TexCoord1"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD2, "Attrib_TexCoord2"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD3, "Attrib_TexCoord3"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD4, "Attrib_TexCoord4"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD5, "Attrib_TexCoord5"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD6, "Attrib_TexCoord6"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD7, "Attrib_TexCoord7"); + if(vid.support.gl20shaders130) + qglBindFragDataLocation(programobject, 0, "dp_FragColor"); + + if (vertexstrings_count && !GL_Backend_CompileShader(programobject, GL_VERTEX_SHADER, "vertex", vertexstrings_count, vertexstrings_list)) + goto cleanup; + +#ifdef GL_GEOMETRY_SHADER + if (geometrystrings_count && !GL_Backend_CompileShader(programobject, GL_GEOMETRY_SHADER, "geometry", geometrystrings_count, geometrystrings_list)) + goto cleanup; +#endif + + if (fragmentstrings_count && !GL_Backend_CompileShader(programobject, GL_FRAGMENT_SHADER, "fragment", fragmentstrings_count, fragmentstrings_list)) + goto cleanup; + + qglLinkProgram(programobject);CHECKGLERROR + qglGetProgramiv(programobject, GL_LINK_STATUS, &programlinked);CHECKGLERROR + qglGetProgramInfoLog(programobject, sizeof(linklog), NULL, linklog);CHECKGLERROR + if (linklog[0]) + { + if (strstr(linklog, "error") || strstr(linklog, "ERROR") || strstr(linklog, "Error") || strstr(linklog, "WARNING") || strstr(linklog, "warning") || strstr(linklog, "Warning")) + Con_DPrintf("program link log:\n%s\n", linklog); + // software vertex shader is ok but software fragment shader is WAY + // too slow, fail program if so. + // NOTE: this string might be ATI specific, but that's ok because the + // ATI R300 chip (Radeon 9500-9800/X300) is the most likely to use a + // software fragment shader due to low instruction and dependent + // texture limits. + if (strstr(linklog, "fragment shader will run in software")) + programlinked = false; + } + if (!programlinked) + goto cleanup; + return programobject; +cleanup: + qglDeleteProgram(programobject);CHECKGLERROR + return 0; +} + +void GL_Backend_FreeProgram(unsigned int prog) +{ + CHECKGLERROR + qglDeleteProgram(prog); + CHECKGLERROR +} + +void GL_Backend_RenumberElements(int *out, int count, const int *in, int offset) +{ + int i; + if (offset) + { + for (i = 0;i < count;i++) + *out++ = *in++ + offset; + } + else + memcpy(out, in, sizeof(*out) * count); +} + +// renders triangles using vertices from the active arrays +int paranoidblah = 0; +void R_Mesh_Draw(int firstvertex, int numvertices, int firsttriangle, int numtriangles, const int *element3i, const r_meshbuffer_t *element3i_indexbuffer, size_t element3i_bufferoffset, const unsigned short *element3s, const r_meshbuffer_t *element3s_indexbuffer, size_t element3s_bufferoffset) +{ + unsigned int numelements = numtriangles * 3; + int bufferobject3i; + size_t bufferoffset3i; + int bufferobject3s; + size_t bufferoffset3s; + if (numvertices < 3 || numtriangles < 1) + { + if (numvertices < 0 || numtriangles < 0 || developer_extra.integer) + Con_DPrintf("R_Mesh_Draw(%d, %d, %d, %d, %8p, %8p, %8x, %8p, %8p, %8x);\n", firstvertex, numvertices, firsttriangle, numtriangles, (void *)element3i, (void *)element3i_indexbuffer, (int)element3i_bufferoffset, (void *)element3s, (void *)element3s_indexbuffer, (int)element3s_bufferoffset); + return; + } + if (!gl_mesh_prefer_short_elements.integer) + { + if (element3i) + element3s = NULL; + if (element3i_indexbuffer) + element3i_indexbuffer = NULL; + } + // adjust the pointers for firsttriangle + if (element3i) + element3i += firsttriangle * 3; + if (element3i_indexbuffer) + element3i_bufferoffset += firsttriangle * 3 * sizeof(*element3i); + if (element3s) + element3s += firsttriangle * 3; + if (element3s_indexbuffer) + element3s_bufferoffset += firsttriangle * 3 * sizeof(*element3s); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // check if the user specified to ignore static index buffers + if (!gl_state.usevbo_staticindex || (gl_vbo.integer == 3 && !vid.forcevbo && (element3i_bufferoffset || element3s_bufferoffset))) + { + element3i_indexbuffer = NULL; + element3s_indexbuffer = NULL; + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } + // upload a dynamic index buffer if needed + if (element3s) + { + if (!element3s_indexbuffer && gl_state.usevbo_dynamicindex) + { + if (gl_state.draw_dynamicindexbuffer) + R_Mesh_UpdateMeshBuffer(gl_state.draw_dynamicindexbuffer, (void *)element3s, numelements * sizeof(*element3s)); + else + gl_state.draw_dynamicindexbuffer = R_Mesh_CreateMeshBuffer((void *)element3s, numelements * sizeof(*element3s), "temporary", true, true, true); + element3s_indexbuffer = gl_state.draw_dynamicindexbuffer; + element3s_bufferoffset = 0; + } + } + else if (element3i) + { + if (!element3i_indexbuffer && gl_state.usevbo_dynamicindex) + { + if (gl_state.draw_dynamicindexbuffer) + R_Mesh_UpdateMeshBuffer(gl_state.draw_dynamicindexbuffer, (void *)element3i, numelements * sizeof(*element3i)); + else + gl_state.draw_dynamicindexbuffer = R_Mesh_CreateMeshBuffer((void *)element3i, numelements * sizeof(*element3i), "temporary", true, true, false); + element3i_indexbuffer = gl_state.draw_dynamicindexbuffer; + element3i_bufferoffset = 0; + } + } + bufferobject3i = element3i_indexbuffer ? element3i_indexbuffer->bufferobject : 0; + bufferoffset3i = element3i_bufferoffset; + bufferobject3s = element3s_indexbuffer ? element3s_indexbuffer->bufferobject : 0; + bufferoffset3s = element3s_bufferoffset; + r_refdef.stats.draws++; + r_refdef.stats.draws_vertices += numvertices; + r_refdef.stats.draws_elements += numelements; + if (gl_paranoid.integer) + { + unsigned int i; + // LordHavoc: disabled this - it needs to be updated to handle components and gltype and stride in each array +#if 0 + unsigned int j, size; + const int *p; + // note: there's no validation done here on buffer objects because it + // is somewhat difficult to get at the data, and gl_paranoid can be + // used without buffer objects if the need arises + // (the data could be gotten using glMapBuffer but it would be very + // slow due to uncachable video memory reads) + if (!qglIsEnabled(GL_VERTEX_ARRAY)) + Con_Print("R_Mesh_Draw: vertex array not enabled\n"); + CHECKGLERROR + if (gl_state.pointer_vertex_pointer) + for (j = 0, size = numvertices * 3, p = (int *)((float *)gl_state.pointer_vertex + firstvertex * 3);j < size;j++, p++) + paranoidblah += *p; + if (gl_state.pointer_color_enabled) + { + if (!qglIsEnabled(GL_COLOR_ARRAY)) + Con_Print("R_Mesh_Draw: color array set but not enabled\n"); + CHECKGLERROR + if (gl_state.pointer_color && gl_state.pointer_color_enabled) + for (j = 0, size = numvertices * 4, p = (int *)((float *)gl_state.pointer_color + firstvertex * 4);j < size;j++, p++) + paranoidblah += *p; + } + for (i = 0;i < vid.texarrayunits;i++) + { + if (gl_state.units[i].arrayenabled) + { + GL_ClientActiveTexture(i); + if (!qglIsEnabled(GL_TEXTURE_COORD_ARRAY)) + Con_Print("R_Mesh_Draw: texcoord array set but not enabled\n"); + CHECKGLERROR + if (gl_state.units[i].pointer_texcoord && gl_state.units[i].arrayenabled) + for (j = 0, size = numvertices * gl_state.units[i].arraycomponents, p = (int *)((float *)gl_state.units[i].pointer_texcoord + firstvertex * gl_state.units[i].arraycomponents);j < size;j++, p++) + paranoidblah += *p; + } + } +#endif + if (element3i) + { + for (i = 0;i < (unsigned int) numtriangles * 3;i++) + { + if (element3i[i] < firstvertex || element3i[i] >= firstvertex + numvertices) + { + Con_Printf("R_Mesh_Draw: invalid vertex index %i (outside range %i - %i) in element3i array\n", element3i[i], firstvertex, firstvertex + numvertices); + return; + } + } + } + if (element3s) + { + for (i = 0;i < (unsigned int) numtriangles * 3;i++) + { + if (element3s[i] < firstvertex || element3s[i] >= firstvertex + numvertices) + { + Con_Printf("R_Mesh_Draw: invalid vertex index %i (outside range %i - %i) in element3s array\n", element3s[i], firstvertex, firstvertex + numvertices); + return; + } + } + } + } + if (r_render.integer || r_refdef.draw2dstage) + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + CHECKGLERROR + if (gl_mesh_testmanualfeeding.integer) + { + unsigned int i, j, element; + const GLfloat *p; + qglBegin(GL_TRIANGLES); + if(vid.renderpath == RENDERPATH_GL20) + { + for (i = 0;i < (unsigned int) numtriangles * 3;i++) + { + if (element3i) + element = element3i[i]; + else if (element3s) + element = element3s[i]; + else + element = firstvertex + i; + for (j = 0;j < vid.texarrayunits;j++) + { + if (gl_state.units[j].pointer_texcoord_pointer && gl_state.units[j].arrayenabled) + { + if (gl_state.units[j].pointer_texcoord_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (gl_state.units[j].pointer_texcoord_components == 4) + qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1], p[2], p[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1], p[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1]); + else + qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, p[0]); + } + else if (gl_state.units[j].pointer_texcoord_gltype == GL_SHORT) + { + const GLshort *s = (const GLshort *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (gl_state.units[j].pointer_texcoord_components == 4) + qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1], s[2], s[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1], s[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, s[0]); + } + else if (gl_state.units[j].pointer_texcoord_gltype == GL_BYTE) + { + const GLbyte *sb = (const GLbyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (gl_state.units[j].pointer_texcoord_components == 4) + qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1], sb[2], sb[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1], sb[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, sb[0]); + } + } + } + if (gl_state.pointer_color_pointer && gl_state.pointer_color_enabled && gl_state.pointer_color_components == 4) + { + if (gl_state.pointer_color_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); + qglVertexAttrib4f(GLSLATTRIB_COLOR, p[0], p[1], p[2], p[3]); + } + else if (gl_state.pointer_color_gltype == GL_UNSIGNED_BYTE) + { + const GLubyte *ub = (const GLubyte *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); + qglVertexAttrib4Nub(GLSLATTRIB_COLOR, ub[0], ub[1], ub[2], ub[3]); + } + } + if (gl_state.pointer_vertex_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.pointer_vertex_pointer + element * gl_state.pointer_vertex_stride); + if (gl_state.pointer_vertex_components == 4) + qglVertexAttrib4f(GLSLATTRIB_POSITION, p[0], p[1], p[2], p[3]); + else if (gl_state.pointer_vertex_components == 3) + qglVertexAttrib3f(GLSLATTRIB_POSITION, p[0], p[1], p[2]); + else + qglVertexAttrib2f(GLSLATTRIB_POSITION, p[0], p[1]); + } + } + } + else + { + for (i = 0;i < (unsigned int) numtriangles * 3;i++) + { + if (element3i) + element = element3i[i]; + else if (element3s) + element = element3s[i]; + else + element = firstvertex + i; + for (j = 0;j < vid.texarrayunits;j++) + { + if (gl_state.units[j].pointer_texcoord_pointer && gl_state.units[j].arrayenabled) + { + if (gl_state.units[j].pointer_texcoord_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (vid.texarrayunits > 1) + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglMultiTexCoord4f(GL_TEXTURE0_ARB + j, p[0], p[1], p[2], p[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglMultiTexCoord3f(GL_TEXTURE0_ARB + j, p[0], p[1], p[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglMultiTexCoord2f(GL_TEXTURE0_ARB + j, p[0], p[1]); + else + qglMultiTexCoord1f(GL_TEXTURE0_ARB + j, p[0]); + } + else + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglTexCoord4f(p[0], p[1], p[2], p[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglTexCoord3f(p[0], p[1], p[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglTexCoord2f(p[0], p[1]); + else + qglTexCoord1f(p[0]); + } + } + else if (gl_state.units[j].pointer_texcoord_gltype == GL_SHORT) + { + const GLshort *s = (const GLshort *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (vid.texarrayunits > 1) + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglMultiTexCoord4f(GL_TEXTURE0_ARB + j, s[0], s[1], s[2], s[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglMultiTexCoord3f(GL_TEXTURE0_ARB + j, s[0], s[1], s[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglMultiTexCoord2f(GL_TEXTURE0_ARB + j, s[0], s[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglMultiTexCoord1f(GL_TEXTURE0_ARB + j, s[0]); + } + else + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglTexCoord4f(s[0], s[1], s[2], s[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglTexCoord3f(s[0], s[1], s[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglTexCoord2f(s[0], s[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglTexCoord1f(s[0]); + } + } + else if (gl_state.units[j].pointer_texcoord_gltype == GL_BYTE) + { + const GLbyte *sb = (const GLbyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (vid.texarrayunits > 1) + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglMultiTexCoord4f(GL_TEXTURE0_ARB + j, sb[0], sb[1], sb[2], sb[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglMultiTexCoord3f(GL_TEXTURE0_ARB + j, sb[0], sb[1], sb[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglMultiTexCoord2f(GL_TEXTURE0_ARB + j, sb[0], sb[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglMultiTexCoord1f(GL_TEXTURE0_ARB + j, sb[0]); + } + else + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglTexCoord4f(sb[0], sb[1], sb[2], sb[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglTexCoord3f(sb[0], sb[1], sb[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglTexCoord2f(sb[0], sb[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglTexCoord1f(sb[0]); + } + } + } + } + if (gl_state.pointer_color_pointer && gl_state.pointer_color_enabled && gl_state.pointer_color_components == 4) + { + if (gl_state.pointer_color_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); + qglColor4f(p[0], p[1], p[2], p[3]); + } + else if (gl_state.pointer_color_gltype == GL_UNSIGNED_BYTE) + { + const GLubyte *ub = (const GLubyte *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); + qglColor4ub(ub[0], ub[1], ub[2], ub[3]); + } + } + if (gl_state.pointer_vertex_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.pointer_vertex_pointer + element * gl_state.pointer_vertex_stride); + if (gl_state.pointer_vertex_components == 4) + qglVertex4f(p[0], p[1], p[2], p[3]); + else if (gl_state.pointer_vertex_components == 3) + qglVertex3f(p[0], p[1], p[2]); + else + qglVertex2f(p[0], p[1]); + } + } + } + qglEnd(); + CHECKGLERROR + } + else if (bufferobject3s) + { + GL_BindEBO(bufferobject3s); + if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) + { + qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_SHORT, (void *)bufferoffset3s); + CHECKGLERROR + } + else + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, (void *)(firsttriangle * sizeof(unsigned short[3]))); + CHECKGLERROR + } + } + else if (bufferobject3i) + { + GL_BindEBO(bufferobject3i); + if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) + { + qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_INT, (void *)bufferoffset3i); + CHECKGLERROR + } + else + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, (void *)(firsttriangle * sizeof(unsigned int[3]))); + CHECKGLERROR + } + } + else if (element3s) + { + GL_BindEBO(0); + if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) + { + qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_SHORT, element3s); + CHECKGLERROR + } + else + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, element3s); + CHECKGLERROR + } + } + else if (element3i) + { + GL_BindEBO(0); + if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) + { + qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_INT, element3i); + CHECKGLERROR + } + else + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, element3i); + CHECKGLERROR + } + } + else + { + qglDrawArrays(GL_TRIANGLES, firstvertex, numvertices); + CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (gl_state.d3dvertexbuffer && ((element3s && element3s_indexbuffer) || (element3i && element3i_indexbuffer))) + { + if (element3s_indexbuffer) + { + IDirect3DDevice9_SetIndices(vid_d3d9dev, (IDirect3DIndexBuffer9 *)element3s_indexbuffer->devicebuffer); + IDirect3DDevice9_DrawIndexedPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, 0, firstvertex, numvertices, element3s_bufferoffset>>1, numtriangles); + } + else if (element3i_indexbuffer) + { + IDirect3DDevice9_SetIndices(vid_d3d9dev, (IDirect3DIndexBuffer9 *)element3i_indexbuffer->devicebuffer); + IDirect3DDevice9_DrawIndexedPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, 0, firstvertex, numvertices, element3i_bufferoffset>>2, numtriangles); + } + else + IDirect3DDevice9_DrawPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices); + } + else + { + if (element3s) + IDirect3DDevice9_DrawIndexedPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices, numtriangles, element3s, D3DFMT_INDEX16, gl_state.d3dvertexdata, gl_state.d3dvertexsize); + else if (element3i) + IDirect3DDevice9_DrawIndexedPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices, numtriangles, element3i, D3DFMT_INDEX32, gl_state.d3dvertexdata, gl_state.d3dvertexsize); + else + IDirect3DDevice9_DrawPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, numvertices, (void *)gl_state.d3dvertexdata, gl_state.d3dvertexsize); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DrawTriangles(firstvertex, numvertices, numtriangles, element3i, element3s); + break; + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // GLES does not have glDrawRangeElements, and generally + // underperforms with index buffers, so this code path is + // relatively straightforward... +#if 0 + if (gl_paranoid.integer) + { + int r, prog, enabled, i; + GLsizei attriblength; + GLint attribsize; + GLenum attribtype; + GLchar attribname[1024]; + r = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);CHECKGLERROR + if (r != GL_FRAMEBUFFER_COMPLETE_EXT) + Con_DPrintf("fbo %i not complete (default %i)\n", gl_state.framebufferobject, gl_state.defaultframebufferobject); +#ifndef GL_CURRENT_PROGRAM +#define GL_CURRENT_PROGRAM 0x8B8D +#endif + qglGetIntegerv(GL_CURRENT_PROGRAM, &r);CHECKGLERROR + if (r < 0 || r > 10000) + Con_DPrintf("GL_CURRENT_PROGRAM = %i\n", r); + prog = r; + for (i = 0;i < 8;i++) + { + qglGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &r);CHECKGLERROR + if (!r) + continue; + qglGetActiveAttrib(prog, i, sizeof(attribname), &attriblength, &attribsize, &attribtype, attribname);CHECKGLERROR + Con_DPrintf("prog %i position %i length %i size %04X type %i name \"%s\"\n", prog, i, (int)attriblength, (int)attribsize, (int)attribtype, (char *)attribname); + } + } +#endif + if (element3s) + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, element3s); + CHECKGLERROR + } + else if (element3i) + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, element3i); + CHECKGLERROR + } + else + { + qglDrawArrays(GL_TRIANGLES, firstvertex, numvertices); + CHECKGLERROR + } + break; + } + } +} + +// restores backend state, used when done with 3D rendering +void R_Mesh_Finish(void) +{ + R_Mesh_ResetRenderTargets(); +} + +r_meshbuffer_t *R_Mesh_CreateMeshBuffer(const void *data, size_t size, const char *name, qboolean isindexbuffer, qboolean isdynamic, qboolean isindex16) +{ + r_meshbuffer_t *buffer; + if (!(isdynamic ? (isindexbuffer ? gl_state.usevbo_dynamicindex : gl_state.usevbo_dynamicvertex) : (isindexbuffer ? gl_state.usevbo_staticindex : gl_state.usevbo_staticvertex))) + return NULL; + buffer = (r_meshbuffer_t *)Mem_ExpandableArray_AllocRecord(&gl_state.meshbufferarray); + memset(buffer, 0, sizeof(*buffer)); + buffer->bufferobject = 0; + buffer->devicebuffer = NULL; + buffer->size = 0; + buffer->isindexbuffer = isindexbuffer; + buffer->isdynamic = isdynamic; + buffer->isindex16 = isindex16; + strlcpy(buffer->name, name, sizeof(buffer->name)); + R_Mesh_UpdateMeshBuffer(buffer, data, size); + return buffer; +} + +void R_Mesh_UpdateMeshBuffer(r_meshbuffer_t *buffer, const void *data, size_t size) +{ + if (!buffer) + return; + if (buffer->isindexbuffer) + { + r_refdef.stats.indexbufferuploadcount++; + r_refdef.stats.indexbufferuploadsize += size; + } + else + { + r_refdef.stats.vertexbufferuploadcount++; + r_refdef.stats.vertexbufferuploadsize += size; + } + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (!buffer->bufferobject) + qglGenBuffersARB(1, (GLuint *)&buffer->bufferobject); + if (buffer->isindexbuffer) + GL_BindEBO(buffer->bufferobject); + else + GL_BindVBO(buffer->bufferobject); + qglBufferDataARB(buffer->isindexbuffer ? GL_ELEMENT_ARRAY_BUFFER_ARB : GL_ARRAY_BUFFER_ARB, size, data, buffer->isdynamic ? GL_STREAM_DRAW_ARB : GL_STATIC_DRAW_ARB); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + int result; + void *datapointer = NULL; + if (buffer->isindexbuffer) + { + IDirect3DIndexBuffer9 *d3d9indexbuffer = (IDirect3DIndexBuffer9 *)buffer->devicebuffer; + if (size > buffer->size || !buffer->devicebuffer) + { + if (buffer->devicebuffer) + IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9*)buffer->devicebuffer); + buffer->devicebuffer = NULL; + if (FAILED(result = IDirect3DDevice9_CreateIndexBuffer(vid_d3d9dev, size, buffer->isdynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : 0, buffer->isindex16 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, buffer->isdynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, &d3d9indexbuffer, NULL))) + Sys_Error("IDirect3DDevice9_CreateIndexBuffer(%p, %d, %x, %x, %x, %p, NULL) returned %x\n", vid_d3d9dev, (int)size, buffer->isdynamic ? (int)D3DUSAGE_DYNAMIC : 0, buffer->isindex16 ? (int)D3DFMT_INDEX16 : (int)D3DFMT_INDEX32, buffer->isdynamic ? (int)D3DPOOL_DEFAULT : (int)D3DPOOL_MANAGED, &d3d9indexbuffer, (int)result); + buffer->devicebuffer = (void *)d3d9indexbuffer; + buffer->size = size; + } + if (!FAILED(IDirect3DIndexBuffer9_Lock(d3d9indexbuffer, 0, 0, &datapointer, buffer->isdynamic ? D3DLOCK_DISCARD : 0))) + { + if (data) + memcpy(datapointer, data, size); + else + memset(datapointer, 0, size); + IDirect3DIndexBuffer9_Unlock(d3d9indexbuffer); + } + } + else + { + IDirect3DVertexBuffer9 *d3d9vertexbuffer = (IDirect3DVertexBuffer9 *)buffer->devicebuffer; + if (size > buffer->size || !buffer->devicebuffer) + { + if (buffer->devicebuffer) + IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9*)buffer->devicebuffer); + buffer->devicebuffer = NULL; + if (FAILED(result = IDirect3DDevice9_CreateVertexBuffer(vid_d3d9dev, size, buffer->isdynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : 0, 0, buffer->isdynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, &d3d9vertexbuffer, NULL))) + Sys_Error("IDirect3DDevice9_CreateVertexBuffer(%p, %d, %x, %x, %x, %p, NULL) returned %x\n", vid_d3d9dev, (int)size, buffer->isdynamic ? (int)D3DUSAGE_DYNAMIC : 0, 0, buffer->isdynamic ? (int)D3DPOOL_DEFAULT : (int)D3DPOOL_MANAGED, &d3d9vertexbuffer, (int)result); + buffer->devicebuffer = (void *)d3d9vertexbuffer; + buffer->size = size; + } + if (!FAILED(IDirect3DVertexBuffer9_Lock(d3d9vertexbuffer, 0, 0, &datapointer, buffer->isdynamic ? D3DLOCK_DISCARD : 0))) + { + if (data) + memcpy(datapointer, data, size); + else + memset(datapointer, 0, size); + IDirect3DVertexBuffer9_Unlock(d3d9vertexbuffer); + } + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_DestroyMeshBuffer(r_meshbuffer_t *buffer) +{ + if (!buffer) + return; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglDeleteBuffersARB(1, (GLuint *)&buffer->bufferobject); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (gl_state.d3dvertexbuffer == (void *)buffer) + gl_state.d3dvertexbuffer = NULL; + if (buffer->devicebuffer) + { + if (buffer->isindexbuffer) + IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9 *)buffer->devicebuffer); + else + IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9 *)buffer->devicebuffer); + buffer->devicebuffer = NULL; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + Mem_ExpandableArray_FreeRecord(&gl_state.meshbufferarray, (void *)buffer); +} + +void GL_Mesh_ListVBOs(qboolean printeach) +{ + int i, endindex; + size_t ebocount = 0, ebomemory = 0; + size_t vbocount = 0, vbomemory = 0; + r_meshbuffer_t *buffer; + endindex = Mem_ExpandableArray_IndexRange(&gl_state.meshbufferarray); + for (i = 0;i < endindex;i++) + { + buffer = (r_meshbuffer_t *) Mem_ExpandableArray_RecordAtIndex(&gl_state.meshbufferarray, i); + if (!buffer) + continue; + if (buffer->isindexbuffer) {ebocount++;ebomemory += buffer->size;if (printeach) Con_Printf("indexbuffer #%i %s = %i bytes%s\n", i, buffer->name, (int)buffer->size, buffer->isdynamic ? " (dynamic)" : " (static)");} + else {vbocount++;vbomemory += buffer->size;if (printeach) Con_Printf("vertexbuffer #%i %s = %i bytes%s\n", i, buffer->name, (int)buffer->size, buffer->isdynamic ? " (dynamic)" : " (static)");} + } + Con_Printf("vertex buffers: %i indexbuffers totalling %i bytes (%.3f MB), %i vertexbuffers totalling %i bytes (%.3f MB), combined %i bytes (%.3fMB)\n", (int)ebocount, (int)ebomemory, ebomemory / 1048576.0, (int)vbocount, (int)vbomemory, vbomemory / 1048576.0, (int)(ebomemory + vbomemory), (ebomemory + vbomemory) / 1048576.0); +} + + + +void R_Mesh_VertexPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (gl_state.pointer_vertex_components != components || gl_state.pointer_vertex_gltype != gltype || gl_state.pointer_vertex_stride != stride || gl_state.pointer_vertex_pointer != pointer || gl_state.pointer_vertex_vertexbuffer != vertexbuffer || gl_state.pointer_vertex_offset != bufferoffset) + { + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + gl_state.pointer_vertex_components = components; + gl_state.pointer_vertex_gltype = gltype; + gl_state.pointer_vertex_stride = stride; + gl_state.pointer_vertex_pointer = pointer; + gl_state.pointer_vertex_vertexbuffer = vertexbuffer; + gl_state.pointer_vertex_offset = bufferoffset; + CHECKGLERROR + GL_BindVBO(bufferobject); + qglVertexPointer(components, gltype, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (gl_state.pointer_vertex_components != components || gl_state.pointer_vertex_gltype != gltype || gl_state.pointer_vertex_stride != stride || gl_state.pointer_vertex_pointer != pointer || gl_state.pointer_vertex_vertexbuffer != vertexbuffer || gl_state.pointer_vertex_offset != bufferoffset) + { + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + gl_state.pointer_vertex_components = components; + gl_state.pointer_vertex_gltype = gltype; + gl_state.pointer_vertex_stride = stride; + gl_state.pointer_vertex_pointer = pointer; + gl_state.pointer_vertex_vertexbuffer = vertexbuffer; + gl_state.pointer_vertex_offset = bufferoffset; + CHECKGLERROR + GL_BindVBO(bufferobject); + qglVertexAttribPointer(GLSLATTRIB_POSITION, components, gltype, false, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_ColorPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) +{ + // note: vertexbuffer may be non-NULL even if pointer is NULL, so check + // the pointer only. + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + CHECKGLERROR + if (pointer) + { + // caller wants color array enabled + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + if (!gl_state.pointer_color_enabled) + { + gl_state.pointer_color_enabled = true; + CHECKGLERROR + qglEnableClientState(GL_COLOR_ARRAY);CHECKGLERROR + } + if (gl_state.pointer_color_components != components || gl_state.pointer_color_gltype != gltype || gl_state.pointer_color_stride != stride || gl_state.pointer_color_pointer != pointer || gl_state.pointer_color_vertexbuffer != vertexbuffer || gl_state.pointer_color_offset != bufferoffset) + { + gl_state.pointer_color_components = components; + gl_state.pointer_color_gltype = gltype; + gl_state.pointer_color_stride = stride; + gl_state.pointer_color_pointer = pointer; + gl_state.pointer_color_vertexbuffer = vertexbuffer; + gl_state.pointer_color_offset = bufferoffset; + CHECKGLERROR + GL_BindVBO(bufferobject); + qglColorPointer(components, gltype, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + } + else + { + // caller wants color array disabled + if (gl_state.pointer_color_enabled) + { + gl_state.pointer_color_enabled = false; + CHECKGLERROR + qglDisableClientState(GL_COLOR_ARRAY);CHECKGLERROR + // when color array is on the glColor gets trashed, set it again + qglColor4f(gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]);CHECKGLERROR + } + } + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + CHECKGLERROR + if (pointer) + { + // caller wants color array enabled + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + if (!gl_state.pointer_color_enabled) + { + gl_state.pointer_color_enabled = true; + CHECKGLERROR + qglEnableVertexAttribArray(GLSLATTRIB_COLOR);CHECKGLERROR + } + if (gl_state.pointer_color_components != components || gl_state.pointer_color_gltype != gltype || gl_state.pointer_color_stride != stride || gl_state.pointer_color_pointer != pointer || gl_state.pointer_color_vertexbuffer != vertexbuffer || gl_state.pointer_color_offset != bufferoffset) + { + gl_state.pointer_color_components = components; + gl_state.pointer_color_gltype = gltype; + gl_state.pointer_color_stride = stride; + gl_state.pointer_color_pointer = pointer; + gl_state.pointer_color_vertexbuffer = vertexbuffer; + gl_state.pointer_color_offset = bufferoffset; + CHECKGLERROR + GL_BindVBO(bufferobject); + qglVertexAttribPointer(GLSLATTRIB_COLOR, components, gltype, false, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + } + else + { + // caller wants color array disabled + if (gl_state.pointer_color_enabled) + { + gl_state.pointer_color_enabled = false; + CHECKGLERROR + qglDisableVertexAttribArray(GLSLATTRIB_COLOR);CHECKGLERROR + // when color array is on the glColor gets trashed, set it again + qglVertexAttrib4f(GLSLATTRIB_COLOR, gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_TexCoordPointer(unsigned int unitnum, int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + // update array settings + // note: there is no need to check bufferobject here because all cases + // that involve a valid bufferobject also supply a texcoord array + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + CHECKGLERROR + if (pointer) + { + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + // texture array unit is enabled, enable the array + if (!unit->arrayenabled) + { + unit->arrayenabled = true; + GL_ClientActiveTexture(unitnum); + qglEnableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR + } + // texcoord array + if (unit->pointer_texcoord_components != components || unit->pointer_texcoord_gltype != gltype || unit->pointer_texcoord_stride != stride || unit->pointer_texcoord_pointer != pointer || unit->pointer_texcoord_vertexbuffer != vertexbuffer || unit->pointer_texcoord_offset != bufferoffset) + { + unit->pointer_texcoord_components = components; + unit->pointer_texcoord_gltype = gltype; + unit->pointer_texcoord_stride = stride; + unit->pointer_texcoord_pointer = pointer; + unit->pointer_texcoord_vertexbuffer = vertexbuffer; + unit->pointer_texcoord_offset = bufferoffset; + GL_ClientActiveTexture(unitnum); + GL_BindVBO(bufferobject); + qglTexCoordPointer(components, gltype, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + } + else + { + // texture array unit is disabled, disable the array + if (unit->arrayenabled) + { + unit->arrayenabled = false; + GL_ClientActiveTexture(unitnum); + qglDisableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR + } + } + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + CHECKGLERROR + if (pointer) + { + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + // texture array unit is enabled, enable the array + if (!unit->arrayenabled) + { + unit->arrayenabled = true; + qglEnableVertexAttribArray(unitnum+GLSLATTRIB_TEXCOORD0);CHECKGLERROR + } + // texcoord array + if (unit->pointer_texcoord_components != components || unit->pointer_texcoord_gltype != gltype || unit->pointer_texcoord_stride != stride || unit->pointer_texcoord_pointer != pointer || unit->pointer_texcoord_vertexbuffer != vertexbuffer || unit->pointer_texcoord_offset != bufferoffset) + { + unit->pointer_texcoord_components = components; + unit->pointer_texcoord_gltype = gltype; + unit->pointer_texcoord_stride = stride; + unit->pointer_texcoord_pointer = pointer; + unit->pointer_texcoord_vertexbuffer = vertexbuffer; + unit->pointer_texcoord_offset = bufferoffset; + GL_BindVBO(bufferobject); + qglVertexAttribPointer(unitnum+GLSLATTRIB_TEXCOORD0, components, gltype, false, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + } + else + { + // texture array unit is disabled, disable the array + if (unit->arrayenabled) + { + unit->arrayenabled = false; + qglDisableVertexAttribArray(unitnum+GLSLATTRIB_TEXCOORD0);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } +} + +int R_Mesh_TexBound(unsigned int unitnum, int id) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + if (unitnum >= vid.teximageunits) + return 0; + if (id == GL_TEXTURE_2D) + return unit->t2d; + if (id == GL_TEXTURE_3D) + return unit->t3d; + if (id == GL_TEXTURE_CUBE_MAP_ARB) + return unit->tcubemap; + return 0; +} + +void R_Mesh_CopyToTexture(rtexture_t *tex, int tx, int ty, int sx, int sy, int width, int height) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + R_Mesh_TexBind(0, tex); + GL_ActiveTexture(0);CHECKGLERROR + qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, tx, ty, sx, sy, width, height);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + IDirect3DSurface9 *currentsurface = NULL; + IDirect3DSurface9 *texturesurface = NULL; + RECT sourcerect; + RECT destrect; + sourcerect.left = sx; + sourcerect.top = sy; + sourcerect.right = sx + width; + sourcerect.bottom = sy + height; + destrect.left = tx; + destrect.top = ty; + destrect.right = tx + width; + destrect.bottom = ty + height; + if (!FAILED(IDirect3DTexture9_GetSurfaceLevel(((IDirect3DTexture9 *)tex->d3dtexture), 0, &texturesurface))) + { + if (!FAILED(IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, ¤tsurface))) + { + IDirect3DDevice9_StretchRect(vid_d3d9dev, currentsurface, &sourcerect, texturesurface, &destrect, D3DTEXF_NONE); + IDirect3DSurface9_Release(currentsurface); + } + IDirect3DSurface9_Release(texturesurface); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_CopyRectangleToTexture(tex->texnum, 0, tx, ty, sx, sy, width, height); + break; + } +} + +#ifdef SUPPORTD3D +int d3drswrap[16] = {D3DRS_WRAP0, D3DRS_WRAP1, D3DRS_WRAP2, D3DRS_WRAP3, D3DRS_WRAP4, D3DRS_WRAP5, D3DRS_WRAP6, D3DRS_WRAP7, D3DRS_WRAP8, D3DRS_WRAP9, D3DRS_WRAP10, D3DRS_WRAP11, D3DRS_WRAP12, D3DRS_WRAP13, D3DRS_WRAP14, D3DRS_WRAP15}; +#endif + +void R_Mesh_ClearBindingsForTexture(int texnum) +{ + gltextureunit_t *unit; + unsigned int unitnum; + // this doesn't really unbind the texture, but it does prevent a mistaken "do nothing" behavior on the next time this same texnum is bound on the same unit as the same type (this mainly affects r_shadow_bouncegrid because 3D textures are so rarely used) + for (unitnum = 0;unitnum < vid.teximageunits;unitnum++) + { + unit = gl_state.units + unitnum; + if (unit->t2d == texnum) + unit->t2d = -1; + if (unit->t3d == texnum) + unit->t3d = -1; + if (unit->tcubemap == texnum) + unit->tcubemap = -1; + } +} + +void R_Mesh_TexBind(unsigned int unitnum, rtexture_t *tex) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + int tex2d, tex3d, texcubemap, texnum; + if (unitnum >= vid.teximageunits) + return; +// if (unit->texture == tex) +// return; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (!tex) + { + tex = r_texture_white; + // not initialized enough yet... + if (!tex) + return; + } + unit->texture = tex; + texnum = R_GetTexture(tex); + switch(tex->gltexturetypeenum) + { + case GL_TEXTURE_2D: if (unit->t2d != texnum) {GL_ActiveTexture(unitnum);unit->t2d = texnum;qglBindTexture(GL_TEXTURE_2D, unit->t2d);CHECKGLERROR}break; + case GL_TEXTURE_3D: if (unit->t3d != texnum) {GL_ActiveTexture(unitnum);unit->t3d = texnum;qglBindTexture(GL_TEXTURE_3D, unit->t3d);CHECKGLERROR}break; + case GL_TEXTURE_CUBE_MAP_ARB: if (unit->tcubemap != texnum) {GL_ActiveTexture(unitnum);unit->tcubemap = texnum;qglBindTexture(GL_TEXTURE_CUBE_MAP_ARB, unit->tcubemap);CHECKGLERROR}break; + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + unit->texture = tex; + tex2d = 0; + tex3d = 0; + texcubemap = 0; + if (tex) + { + texnum = R_GetTexture(tex); + switch(tex->gltexturetypeenum) + { + case GL_TEXTURE_2D: + tex2d = texnum; + break; + case GL_TEXTURE_3D: + tex3d = texnum; + break; + case GL_TEXTURE_CUBE_MAP_ARB: + texcubemap = texnum; + break; + } + } + // update 2d texture binding + if (unit->t2d != tex2d) + { + GL_ActiveTexture(unitnum); + if (tex2d) + { + if (unit->t2d == 0) + { + qglEnable(GL_TEXTURE_2D);CHECKGLERROR + } + } + else + { + if (unit->t2d) + { + qglDisable(GL_TEXTURE_2D);CHECKGLERROR + } + } + unit->t2d = tex2d; + qglBindTexture(GL_TEXTURE_2D, unit->t2d);CHECKGLERROR + } + // update 3d texture binding + if (unit->t3d != tex3d) + { + GL_ActiveTexture(unitnum); + if (tex3d) + { + if (unit->t3d == 0) + { + qglEnable(GL_TEXTURE_3D);CHECKGLERROR + } + } + else + { + if (unit->t3d) + { + qglDisable(GL_TEXTURE_3D);CHECKGLERROR + } + } + unit->t3d = tex3d; + qglBindTexture(GL_TEXTURE_3D, unit->t3d);CHECKGLERROR + } + // update cubemap texture binding + if (unit->tcubemap != texcubemap) + { + GL_ActiveTexture(unitnum); + if (texcubemap) + { + if (unit->tcubemap == 0) + { + qglEnable(GL_TEXTURE_CUBE_MAP_ARB);CHECKGLERROR + } + } + else + { + if (unit->tcubemap) + { + qglDisable(GL_TEXTURE_CUBE_MAP_ARB);CHECKGLERROR + } + } + unit->tcubemap = texcubemap; + qglBindTexture(GL_TEXTURE_CUBE_MAP_ARB, unit->tcubemap);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + extern cvar_t gl_texture_anisotropy; + if (!tex) + { + tex = r_texture_white; + // not initialized enough yet... + if (!tex) + return; + } + // upload texture if needed + R_GetTexture(tex); + if (unit->texture == tex) + return; + unit->texture = tex; + IDirect3DDevice9_SetTexture(vid_d3d9dev, unitnum, (IDirect3DBaseTexture9*)tex->d3dtexture); + //IDirect3DDevice9_SetRenderState(vid_d3d9dev, d3drswrap[unitnum], (tex->flags & TEXF_CLAMP) ? (D3DWRAPCOORD_0 | D3DWRAPCOORD_1 | D3DWRAPCOORD_2) : 0); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSU, tex->d3daddressu); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSV, tex->d3daddressv); + if (tex->d3daddressw) + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSW, tex->d3daddressw); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAGFILTER, tex->d3dmagfilter); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MINFILTER, tex->d3dminfilter); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MIPFILTER, tex->d3dmipfilter); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MIPMAPLODBIAS, tex->d3dmipmaplodbias); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAXMIPLEVEL, tex->d3dmaxmiplevelfilter); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAXANISOTROPY, gl_texture_anisotropy.integer); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (!tex) + { + tex = r_texture_white; + // not initialized enough yet... + if (!tex) + return; + } + texnum = R_GetTexture(tex); + if (unit->texture == tex) + return; + unit->texture = tex; + DPSOFTRAST_SetTexture(unitnum, texnum); + break; + } +} + +void R_Mesh_TexMatrix(unsigned int unitnum, const matrix4x4_t *matrix) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (matrix && matrix->m[3][3]) + { + // texmatrix specified, check if it is different + if (!unit->texmatrixenabled || memcmp(&unit->matrix, matrix, sizeof(matrix4x4_t))) + { + float glmatrix[16]; + unit->texmatrixenabled = true; + unit->matrix = *matrix; + CHECKGLERROR + Matrix4x4_ToArrayFloatGL(&unit->matrix, glmatrix); + GL_ActiveTexture(unitnum); + qglMatrixMode(GL_TEXTURE);CHECKGLERROR + qglLoadMatrixf(glmatrix);CHECKGLERROR + qglMatrixMode(GL_MODELVIEW);CHECKGLERROR + } + } + else + { + // no texmatrix specified, revert to identity + if (unit->texmatrixenabled) + { + unit->texmatrixenabled = false; + unit->matrix = identitymatrix; + CHECKGLERROR + GL_ActiveTexture(unitnum); + qglMatrixMode(GL_TEXTURE);CHECKGLERROR + qglLoadIdentity();CHECKGLERROR + qglMatrixMode(GL_MODELVIEW);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_TexCombine(unsigned int unitnum, int combinergb, int combinealpha, int rgbscale, int alphascale) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + CHECKGLERROR + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + // do nothing + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + // GL_ARB_texture_env_combine + if (!combinergb) + combinergb = GL_MODULATE; + if (!combinealpha) + combinealpha = GL_MODULATE; + if (!rgbscale) + rgbscale = 1; + if (!alphascale) + alphascale = 1; + if (combinergb != combinealpha || rgbscale != 1 || alphascale != 1) + { + if (combinergb == GL_DECAL) + combinergb = GL_INTERPOLATE_ARB; + if (unit->combine != GL_COMBINE_ARB) + { + unit->combine = GL_COMBINE_ARB; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);CHECKGLERROR + qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_TEXTURE);CHECKGLERROR // for GL_INTERPOLATE_ARB mode + } + if (unit->combinergb != combinergb) + { + unit->combinergb = combinergb; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, unit->combinergb);CHECKGLERROR + } + if (unit->combinealpha != combinealpha) + { + unit->combinealpha = combinealpha; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, unit->combinealpha);CHECKGLERROR + } + if (unit->rgbscale != rgbscale) + { + unit->rgbscale = rgbscale; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, unit->rgbscale);CHECKGLERROR + } + if (unit->alphascale != alphascale) + { + unit->alphascale = alphascale; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, unit->alphascale);CHECKGLERROR + } + } + else + { + if (unit->combine != combinergb) + { + unit->combine = combinergb; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, unit->combine);CHECKGLERROR + } + } + break; + case RENDERPATH_GL11: + // normal GL texenv + if (!combinergb) + combinergb = GL_MODULATE; + if (unit->combine != combinergb) + { + unit->combine = combinergb; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, unit->combine);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_ResetTextureState(void) +{ + unsigned int unitnum; + + BACKENDACTIVECHECK + + for (unitnum = 0;unitnum < vid.teximageunits;unitnum++) + R_Mesh_TexBind(unitnum, NULL); + for (unitnum = 0;unitnum < vid.texarrayunits;unitnum++) + R_Mesh_TexCoordPointer(unitnum, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + for (unitnum = 0;unitnum < vid.texunits;unitnum++) + { + R_Mesh_TexCombine(unitnum, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexMatrix(unitnum, NULL); + } + break; + } +} + + + +#ifdef SUPPORTD3D +//#define r_vertex3f_d3d9fvf (D3DFVF_XYZ) +//#define r_vertexgeneric_d3d9fvf (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1) +//#define r_vertexmesh_d3d9fvf (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX5 | D3DFVF_TEXCOORDSIZE1(3) | D3DFVF_TEXCOORDSIZE2(3) | D3DFVF_TEXCOORDSIZE3(3)) + +D3DVERTEXELEMENT9 r_vertex3f_d3d9elements[] = +{ + {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, + D3DDECL_END() +}; + +D3DVERTEXELEMENT9 r_vertexgeneric_d3d9elements[] = +{ + {0, (int)((size_t)&((r_vertexgeneric_t *)0)->vertex3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, + {0, (int)((size_t)&((r_vertexgeneric_t *)0)->color4f ), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, + {0, (int)((size_t)&((r_vertexgeneric_t *)0)->texcoord2f), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, + D3DDECL_END() +}; + +D3DVERTEXELEMENT9 r_vertexmesh_d3d9elements[] = +{ + {0, (int)((size_t)&((r_vertexmesh_t *)0)->vertex3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->color4f ), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->texcoordtexture2f ), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->svector3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->tvector3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->normal3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->texcoordlightmap2f), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4}, + D3DDECL_END() +}; + +IDirect3DVertexDeclaration9 *r_vertex3f_d3d9decl; +IDirect3DVertexDeclaration9 *r_vertexgeneric_d3d9decl; +IDirect3DVertexDeclaration9 *r_vertexmesh_d3d9decl; +#endif + +static void R_Mesh_InitVertexDeclarations(void) +{ +#ifdef SUPPORTD3D + r_vertex3f_d3d9decl = NULL; + r_vertexgeneric_d3d9decl = NULL; + r_vertexmesh_d3d9decl = NULL; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GL13: + case RENDERPATH_GL11: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertex3f_d3d9elements, &r_vertex3f_d3d9decl); + IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertexgeneric_d3d9elements, &r_vertexgeneric_d3d9decl); + IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertexmesh_d3d9elements, &r_vertexmesh_d3d9decl); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } +#endif +} + +static void R_Mesh_DestroyVertexDeclarations(void) +{ +#ifdef SUPPORTD3D + if (r_vertex3f_d3d9decl) + IDirect3DVertexDeclaration9_Release(r_vertex3f_d3d9decl); + r_vertex3f_d3d9decl = NULL; + if (r_vertexgeneric_d3d9decl) + IDirect3DVertexDeclaration9_Release(r_vertexgeneric_d3d9decl); + r_vertexgeneric_d3d9decl = NULL; + if (r_vertexmesh_d3d9decl) + IDirect3DVertexDeclaration9_Release(r_vertexmesh_d3d9decl); + r_vertexmesh_d3d9decl = NULL; +#endif +} + +void R_Mesh_PrepareVertices_Vertex3f(int numvertices, const float *vertex3f, const r_meshbuffer_t *vertexbuffer) +{ + // upload temporary vertexbuffer for this rendering + if (!gl_state.usevbo_staticvertex) + vertexbuffer = NULL; + if (!vertexbuffer && gl_state.usevbo_dynamicvertex) + { + if (gl_state.preparevertices_dynamicvertexbuffer) + R_Mesh_UpdateMeshBuffer(gl_state.preparevertices_dynamicvertexbuffer, vertex3f, numvertices * sizeof(float[3])); + else + gl_state.preparevertices_dynamicvertexbuffer = R_Mesh_CreateMeshBuffer(vertex3f, numvertices * sizeof(float[3]), "temporary", false, true, false); + vertexbuffer = gl_state.preparevertices_dynamicvertexbuffer; + } + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (vertexbuffer) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (vertexbuffer) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + break; + case RENDERPATH_GL11: + if (vertexbuffer) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertex3f_d3d9decl); + if (vertexbuffer) + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, 0, sizeof(float[3])); + else + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); + gl_state.d3dvertexbuffer = (void *)vertexbuffer; + gl_state.d3dvertexdata = (void *)vertex3f; + gl_state.d3dvertexsize = sizeof(float[3]); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); + DPSOFTRAST_SetColorPointer(NULL, 0); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), NULL); + break; + } +} + + + +r_vertexgeneric_t *R_Mesh_PrepareVertices_Generic_Lock(int numvertices) +{ + size_t size; + size = sizeof(r_vertexgeneric_t) * numvertices; + if (gl_state.preparevertices_tempdatamaxsize < size) + { + gl_state.preparevertices_tempdatamaxsize = size; + gl_state.preparevertices_tempdata = Mem_Realloc(r_main_mempool, gl_state.preparevertices_tempdata, gl_state.preparevertices_tempdatamaxsize); + } + gl_state.preparevertices_vertexgeneric = (r_vertexgeneric_t *)gl_state.preparevertices_tempdata; + gl_state.preparevertices_numvertices = numvertices; + return gl_state.preparevertices_vertexgeneric; +} + +qboolean R_Mesh_PrepareVertices_Generic_Unlock(void) +{ + R_Mesh_PrepareVertices_Generic(gl_state.preparevertices_numvertices, gl_state.preparevertices_vertexgeneric, NULL); + gl_state.preparevertices_vertexgeneric = NULL; + gl_state.preparevertices_numvertices = 0; + return true; +} + +void R_Mesh_PrepareVertices_Generic_Arrays(int numvertices, const float *vertex3f, const float *color4f, const float *texcoord2f) +{ + int i; + r_vertexgeneric_t *vertex; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (!vid.useinterleavedarrays) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoord2f, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + return; + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (!vid.useinterleavedarrays) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoord2f, NULL, 0); + if (vid.texunits >= 2) + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + if (vid.texunits >= 3) + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + return; + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); + DPSOFTRAST_SetColorPointer(color4f, sizeof(float[4])); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), texcoord2f); + DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), NULL); + return; + } + + // no quick path for this case, convert to vertex structs + vertex = R_Mesh_PrepareVertices_Generic_Lock(numvertices); + for (i = 0;i < numvertices;i++) + VectorCopy(vertex3f + 3*i, vertex[i].vertex3f); + if (color4f) + { + for (i = 0;i < numvertices;i++) + Vector4Copy(color4f + 4*i, vertex[i].color4f); + } + else + { + for (i = 0;i < numvertices;i++) + Vector4Copy(gl_state.color4f, vertex[i].color4f); + } + if (texcoord2f) + for (i = 0;i < numvertices;i++) + Vector2Copy(texcoord2f + 2*i, vertex[i].texcoord2f); + R_Mesh_PrepareVertices_Generic_Unlock(); + R_Mesh_PrepareVertices_Generic(numvertices, vertex, NULL); +} + +void R_Mesh_PrepareVertices_Generic(int numvertices, const r_vertexgeneric_t *vertex, const r_meshbuffer_t *vertexbuffer) +{ + // upload temporary vertexbuffer for this rendering + if (!gl_state.usevbo_staticvertex) + vertexbuffer = NULL; + if (!vertexbuffer && gl_state.usevbo_dynamicvertex) + { + if (gl_state.preparevertices_dynamicvertexbuffer) + R_Mesh_UpdateMeshBuffer(gl_state.preparevertices_dynamicvertexbuffer, vertex, numvertices * sizeof(*vertex)); + else + gl_state.preparevertices_dynamicvertexbuffer = R_Mesh_CreateMeshBuffer(vertex, numvertices * sizeof(*vertex), "temporary", false, true, false); + vertexbuffer = gl_state.preparevertices_dynamicvertexbuffer; + } + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + break; + case RENDERPATH_GL11: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertexgeneric_d3d9decl); + if (vertexbuffer) + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, 0, sizeof(*vertex)); + else + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); + gl_state.d3dvertexbuffer = (void *)vertexbuffer; + gl_state.d3dvertexdata = (void *)vertex; + gl_state.d3dvertexsize = sizeof(*vertex); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex->vertex3f, sizeof(*vertex)); + DPSOFTRAST_SetColorPointer(vertex->color4f, sizeof(*vertex)); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(*vertex), vertex->texcoord2f); + DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(*vertex), NULL); + DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(*vertex), NULL); + DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(*vertex), NULL); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(*vertex), NULL); + break; + } +} + + + +r_vertexmesh_t *R_Mesh_PrepareVertices_Mesh_Lock(int numvertices) +{ + size_t size; + size = sizeof(r_vertexmesh_t) * numvertices; + if (gl_state.preparevertices_tempdatamaxsize < size) + { + gl_state.preparevertices_tempdatamaxsize = size; + gl_state.preparevertices_tempdata = Mem_Realloc(r_main_mempool, gl_state.preparevertices_tempdata, gl_state.preparevertices_tempdatamaxsize); + } + gl_state.preparevertices_vertexmesh = (r_vertexmesh_t *)gl_state.preparevertices_tempdata; + gl_state.preparevertices_numvertices = numvertices; + return gl_state.preparevertices_vertexmesh; +} + +qboolean R_Mesh_PrepareVertices_Mesh_Unlock(void) +{ + R_Mesh_PrepareVertices_Mesh(gl_state.preparevertices_numvertices, gl_state.preparevertices_vertexmesh, NULL); + gl_state.preparevertices_vertexmesh = NULL; + gl_state.preparevertices_numvertices = 0; + return true; +} + +void R_Mesh_PrepareVertices_Mesh_Arrays(int numvertices, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *color4f, const float *texcoordtexture2f, const float *texcoordlightmap2f) +{ + int i; + r_vertexmesh_t *vertex; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (!vid.useinterleavedarrays) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoordtexture2f, NULL, 0); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), svector3f, NULL, 0); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), tvector3f, NULL, 0); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT, sizeof(float[3]), normal3f, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), texcoordlightmap2f, NULL, 0); + return; + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (!vid.useinterleavedarrays) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoordtexture2f, NULL, 0); + if (vid.texunits >= 2) + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), texcoordlightmap2f, NULL, 0); + if (vid.texunits >= 3) + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + return; + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); + DPSOFTRAST_SetColorPointer(color4f, sizeof(float[4])); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), texcoordtexture2f); + DPSOFTRAST_SetTexCoordPointer(1, 3, sizeof(float[3]), svector3f); + DPSOFTRAST_SetTexCoordPointer(2, 3, sizeof(float[3]), tvector3f); + DPSOFTRAST_SetTexCoordPointer(3, 3, sizeof(float[3]), normal3f); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), texcoordlightmap2f); + return; + } + + vertex = R_Mesh_PrepareVertices_Mesh_Lock(numvertices); + for (i = 0;i < numvertices;i++) + VectorCopy(vertex3f + 3*i, vertex[i].vertex3f); + if (svector3f) + for (i = 0;i < numvertices;i++) + VectorCopy(svector3f + 3*i, vertex[i].svector3f); + if (tvector3f) + for (i = 0;i < numvertices;i++) + VectorCopy(tvector3f + 3*i, vertex[i].tvector3f); + if (normal3f) + for (i = 0;i < numvertices;i++) + VectorCopy(normal3f + 3*i, vertex[i].normal3f); + if (color4f) + { + for (i = 0;i < numvertices;i++) + Vector4Copy(color4f + 4*i, vertex[i].color4f); + } + else + { + for (i = 0;i < numvertices;i++) + Vector4Copy(gl_state.color4f, vertex[i].color4f); + } + if (texcoordtexture2f) + for (i = 0;i < numvertices;i++) + Vector2Copy(texcoordtexture2f + 2*i, vertex[i].texcoordtexture2f); + if (texcoordlightmap2f) + for (i = 0;i < numvertices;i++) + Vector2Copy(texcoordlightmap2f + 2*i, vertex[i].texcoordlightmap2f); + R_Mesh_PrepareVertices_Mesh_Unlock(); + R_Mesh_PrepareVertices_Mesh(numvertices, vertex, NULL); +} + +void R_Mesh_PrepareVertices_Mesh(int numvertices, const r_vertexmesh_t *vertex, const r_meshbuffer_t *vertexbuffer) +{ + // upload temporary vertexbuffer for this rendering + if (!gl_state.usevbo_staticvertex) + vertexbuffer = NULL; + if (!vertexbuffer && gl_state.usevbo_dynamicvertex) + { + if (gl_state.preparevertices_dynamicvertexbuffer) + R_Mesh_UpdateMeshBuffer(gl_state.preparevertices_dynamicvertexbuffer, vertex, numvertices * sizeof(*vertex)); + else + gl_state.preparevertices_dynamicvertexbuffer = R_Mesh_CreateMeshBuffer(vertex, numvertices * sizeof(*vertex), "temporary", false, true, false); + vertexbuffer = gl_state.preparevertices_dynamicvertexbuffer; + } + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(*vertex), vertex->svector3f , vertexbuffer, (int)((unsigned char *)vertex->svector3f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(*vertex), vertex->tvector3f , vertexbuffer, (int)((unsigned char *)vertex->tvector3f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(*vertex), vertex->normal3f , vertexbuffer, (int)((unsigned char *)vertex->normal3f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, vertexbuffer, (int)((unsigned char *)vertex->texcoordlightmap2f - (unsigned char *)vertex)); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(*vertex), vertex->svector3f , NULL, 0); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(*vertex), vertex->tvector3f , NULL, 0); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(*vertex), vertex->normal3f , NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, NULL, 0); + } + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, vertexbuffer, (int)((unsigned char *)vertex->texcoordlightmap2f - (unsigned char *)vertex)); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, NULL, 0); + } + break; + case RENDERPATH_GL11: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertexmesh_d3d9decl); + if (vertexbuffer) + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, 0, sizeof(*vertex)); + else + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); + gl_state.d3dvertexbuffer = (void *)vertexbuffer; + gl_state.d3dvertexdata = (void *)vertex; + gl_state.d3dvertexsize = sizeof(*vertex); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex->vertex3f, sizeof(*vertex)); + DPSOFTRAST_SetColorPointer(vertex->color4f, sizeof(*vertex)); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(*vertex), vertex->texcoordtexture2f); + DPSOFTRAST_SetTexCoordPointer(1, 3, sizeof(*vertex), vertex->svector3f); + DPSOFTRAST_SetTexCoordPointer(2, 3, sizeof(*vertex), vertex->tvector3f); + DPSOFTRAST_SetTexCoordPointer(3, 3, sizeof(*vertex), vertex->normal3f); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(*vertex), vertex->texcoordlightmap2f); + break; + } +} + +void GL_BlendEquationSubtract(qboolean negated) +{ + if(negated) + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglBlendEquationEXT(GL_FUNC_REVERSE_SUBTRACT_EXT); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_BLENDOP, D3DBLENDOP_SUBTRACT); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_BlendSubtract(true); + break; + } + } + else + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglBlendEquationEXT(GL_FUNC_ADD_EXT); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_BLENDOP, D3DBLENDOP_ADD); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_BlendSubtract(false); + break; + } + } +} diff --git a/misc/source/darkplaces-src/gl_backend.h b/misc/source/darkplaces-src/gl_backend.h new file mode 100644 index 00000000..0a352336 --- /dev/null +++ b/misc/source/darkplaces-src/gl_backend.h @@ -0,0 +1,126 @@ + +#ifndef GL_BACKEND_H +#define GL_BACKEND_H + +extern r_viewport_t gl_viewport; +extern matrix4x4_t gl_modelmatrix; +extern matrix4x4_t gl_viewmatrix; +extern matrix4x4_t gl_modelviewmatrix; +extern matrix4x4_t gl_projectionmatrix; +extern matrix4x4_t gl_modelviewprojectionmatrix; +extern float gl_modelview16f[16]; +extern float gl_modelviewprojection16f[16]; +extern qboolean gl_modelmatrixchanged; + +#define POLYGONELEMENTS_MAXPOINTS 258 +extern int polygonelement3i[(POLYGONELEMENTS_MAXPOINTS-2)*3]; +extern unsigned short polygonelement3s[(POLYGONELEMENTS_MAXPOINTS-2)*3]; +#define QUADELEMENTS_MAXQUADS 128 +extern int quadelement3i[QUADELEMENTS_MAXQUADS*6]; +extern unsigned short quadelement3s[QUADELEMENTS_MAXQUADS*6]; + +void R_Viewport_TransformToScreen(const r_viewport_t *v, const vec4_t in, vec4_t out); +qboolean R_ScissorForBBox(const float *mins, const float *maxs, int *scissor); +void R_Viewport_InitOrtho(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float x1, float y1, float x2, float y2, float zNear, float zFar, const float *nearplane); +void R_Viewport_InitPerspective(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float zNear, float zFar, const float *nearplane); +void R_Viewport_InitPerspectiveInfinite(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float zNear, const float *nearplane); +void R_Viewport_InitCubeSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, float nearclip, float farclip, const float *nearplane); +void R_Viewport_InitRectSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, int border, float nearclip, float farclip, const float *nearplane); +void R_SetViewport(const r_viewport_t *v); +void R_GetViewport(r_viewport_t *v); +void GL_Finish(void); + +void GL_BlendFunc(int blendfunc1, int blendfunc2); +void GL_BlendEquationSubtract(qboolean negated); +void GL_DepthMask(int state); +void GL_DepthTest(int state); +void GL_DepthFunc(int state); +void GL_DepthRange(float nearfrac, float farfrac); +void R_SetStencilSeparate(qboolean enable, int writemask, int frontfail, int frontzfail, int frontzpass, int backfail, int backzfail, int backzpass, int frontcompare, int backcompare, int comparereference, int comparemask); +void R_SetStencil(qboolean enable, int writemask, int fail, int zfail, int zpass, int compare, int comparereference, int comparemask); +void GL_PolygonOffset(float planeoffset, float depthoffset); +void GL_CullFace(int state); +void GL_AlphaTest(int state); +void GL_AlphaToCoverage(qboolean state); +void GL_ColorMask(int r, int g, int b, int a); +void GL_Color(float cr, float cg, float cb, float ca); +void GL_ActiveTexture(unsigned int num); +void GL_ClientActiveTexture(unsigned int num); +void GL_Scissor(int x, int y, int width, int height); +void GL_ScissorTest(int state); +void GL_Clear(int mask, const float *colorvalue, float depthvalue, int stencilvalue); +void GL_ReadPixelsBGRA(int x, int y, int width, int height, unsigned char *outpixels); +int R_Mesh_CreateFramebufferObject(rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4); +void R_Mesh_DestroyFramebufferObject(int fbo); +void R_Mesh_ResetRenderTargets(void); +void R_Mesh_SetMainRenderTargets(void); +void R_Mesh_SetRenderTargets(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4); + +unsigned int GL_Backend_CompileProgram(int vertexstrings_count, const char **vertexstrings_list, int geometrystrings_count, const char **geometrystrings_list, int fragmentstrings_count, const char **fragmentstrings_list); +void GL_Backend_FreeProgram(unsigned int prog); + +extern cvar_t gl_paranoid; +extern cvar_t gl_printcheckerror; + +// adds console variables and registers the render module (only call from GL_Init) +void gl_backend_init(void); + +// starts mesh rendering for the frame +void R_Mesh_Start(void); + +// ends mesh rendering for the frame +// (only valid after R_Mesh_Start) +void R_Mesh_Finish(void); + + +// vertex buffer and index buffer creation/updating/freeing +r_meshbuffer_t *R_Mesh_CreateMeshBuffer(const void *data, size_t size, const char *name, qboolean isindexbuffer, qboolean isdynamic, qboolean isindex16); +void R_Mesh_UpdateMeshBuffer(r_meshbuffer_t *buffer, const void *data, size_t size); +void R_Mesh_DestroyMeshBuffer(r_meshbuffer_t *buffer); +void GL_Mesh_ListVBOs(qboolean printeach); + +void R_Mesh_PrepareVertices_Vertex3f(int numvertices, const float *vertex3f, const r_meshbuffer_t *buffer); + +r_vertexgeneric_t *R_Mesh_PrepareVertices_Generic_Lock(int numvertices); +qboolean R_Mesh_PrepareVertices_Generic_Unlock(void); +void R_Mesh_PrepareVertices_Generic_Arrays(int numvertices, const float *vertex3f, const float *color4f, const float *texcoord2f); +void R_Mesh_PrepareVertices_Generic(int numvertices, const r_vertexgeneric_t *vertex, const r_meshbuffer_t *vertexbuffer); + +r_vertexmesh_t *R_Mesh_PrepareVertices_Mesh_Lock(int numvertices); +qboolean R_Mesh_PrepareVertices_Mesh_Unlock(void); // if this returns false, you need to prepare the mesh again! +void R_Mesh_PrepareVertices_Mesh_Arrays(int numvertices, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *color4f, const float *texcoordtexture2f, const float *texcoordlightmap2f); +void R_Mesh_PrepareVertices_Mesh(int numvertices, const r_vertexmesh_t *vertex, const r_meshbuffer_t *buffer); + +// sets up the requested vertex transform matrix +void R_EntityMatrix(const matrix4x4_t *matrix); +// sets the vertex array pointer +void R_Mesh_VertexPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); +// sets the color array pointer (GL_Color only works when this is NULL) +void R_Mesh_ColorPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); +// sets the texcoord array pointer for an array unit +void R_Mesh_TexCoordPointer(unsigned int unitnum, int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); +// returns current texture bound to this identifier +int R_Mesh_TexBound(unsigned int unitnum, int id); +// copies a section of the framebuffer to a 2D texture +void R_Mesh_CopyToTexture(rtexture_t *tex, int tx, int ty, int sx, int sy, int width, int height); +// bind a given texture to a given image unit +void R_Mesh_TexBind(unsigned int unitnum, rtexture_t *tex); +// sets the texcoord matrix for a texenv unit, can be NULL or blank (will use identity) +void R_Mesh_TexMatrix(unsigned int unitnum, const matrix4x4_t *matrix); +// sets the combine state for a texenv unit +void R_Mesh_TexCombine(unsigned int unitnum, int combinergb, int combinealpha, int rgbscale, int alphascale); +// set up a blank texture state (unbinds all textures, texcoord pointers, and resets combine settings) +void R_Mesh_ResetTextureState(void); +// before a texture is freed, make sure there are no references to it +void R_Mesh_ClearBindingsForTexture(int texnum); + +// renders a mesh +void R_Mesh_Draw(int firstvertex, int numvertices, int firsttriangle, int numtriangles, const int *element3i, const r_meshbuffer_t *element3i_indexbuffer, size_t element3i_bufferoffset, const unsigned short *element3s, const r_meshbuffer_t *element3s_indexbuffer, size_t element3s_bufferoffset); + +// saves a section of the rendered frame to a .tga or .jpg file +qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean png, qboolean gammacorrect, qboolean keep_alpha); +// used by R_Envmap_f and internally in backend, clears the frame +void R_ClearScreen(qboolean fogcolor); + +#endif + diff --git a/misc/source/darkplaces-src/gl_draw.c b/misc/source/darkplaces-src/gl_draw.c new file mode 100644 index 00000000..ef1fee21 --- /dev/null +++ b/misc/source/darkplaces-src/gl_draw.c @@ -0,0 +1,2251 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "image.h" +#include "wad.h" + +#include "cl_video.h" +#include "cl_dyntexture.h" + +#include "ft2.h" +#include "ft2_fontdefs.h" + +dp_fonts_t dp_fonts; +static mempool_t *fonts_mempool = NULL; + +cvar_t r_textshadow = {CVAR_SAVE, "r_textshadow", "0", "draws a shadow on all text to improve readability (note: value controls offset, 1 = 1 pixel, 1.5 = 1.5 pixels, etc)"}; +cvar_t r_textbrightness = {CVAR_SAVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"}; +cvar_t r_textcontrast = {CVAR_SAVE, "r_textcontrast", "1", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"}; + +cvar_t r_font_postprocess_blur = {CVAR_SAVE, "r_font_postprocess_blur", "0", "font blur amount"}; +cvar_t r_font_postprocess_outline = {CVAR_SAVE, "r_font_postprocess_outline", "0", "font outline amount"}; +cvar_t r_font_postprocess_shadow_x = {CVAR_SAVE, "r_font_postprocess_shadow_x", "0", "font shadow X shift amount, applied during outlining"}; +cvar_t r_font_postprocess_shadow_y = {CVAR_SAVE, "r_font_postprocess_shadow_y", "0", "font shadow Y shift amount, applied during outlining"}; +cvar_t r_font_postprocess_shadow_z = {CVAR_SAVE, "r_font_postprocess_shadow_z", "0", "font shadow Z shift amount, applied during blurring"}; +cvar_t r_font_hinting = {CVAR_SAVE, "r_font_hinting", "3", "0 = no hinting, 1 = light autohinting, 2 = full autohinting, 3 = full hinting"}; +cvar_t r_font_antialias = {CVAR_SAVE, "r_font_antialias", "1", "0 = monochrome, 1 = grey" /* , 2 = rgb, 3 = bgr" */}; + +extern cvar_t v_glslgamma; + +//============================================================================= +/* Support Routines */ + +#define FONT_FILESIZE 13468 +static cachepic_t *cachepichash[CACHEPICHASHSIZE]; +static cachepic_t cachepics[MAX_CACHED_PICS]; +static int numcachepics; + +rtexturepool_t *drawtexturepool; + +static const unsigned char concharimage[FONT_FILESIZE] = +{ +#include "lhfont.h" +}; + +static rtexture_t *draw_generateconchars(void) +{ + int i; + unsigned char *data; + double random; + rtexture_t *tex; + + data = LoadTGA_BGRA (concharimage, FONT_FILESIZE, NULL); +// Gold numbers + for (i = 0;i < 8192;i++) + { + random = lhrandom (0.0,1.0); + data[i*4+3] = data[i*4+0]; + data[i*4+2] = 83 + (unsigned char)(random * 64); + data[i*4+1] = 71 + (unsigned char)(random * 32); + data[i*4+0] = 23 + (unsigned char)(random * 16); + } +// White chars + for (i = 8192;i < 32768;i++) + { + random = lhrandom (0.0,1.0); + data[i*4+3] = data[i*4+0]; + data[i*4+2] = 95 + (unsigned char)(random * 64); + data[i*4+1] = 95 + (unsigned char)(random * 64); + data[i*4+0] = 95 + (unsigned char)(random * 64); + } +// Gold numbers + for (i = 32768;i < 40960;i++) + { + random = lhrandom (0.0,1.0); + data[i*4+3] = data[i*4+0]; + data[i*4+2] = 83 + (unsigned char)(random * 64); + data[i*4+1] = 71 + (unsigned char)(random * 32); + data[i*4+0] = 23 + (unsigned char)(random * 16); + } +// Red chars + for (i = 40960;i < 65536;i++) + { + random = lhrandom (0.0,1.0); + data[i*4+3] = data[i*4+0]; + data[i*4+2] = 96 + (unsigned char)(random * 64); + data[i*4+1] = 43 + (unsigned char)(random * 32); + data[i*4+0] = 27 + (unsigned char)(random * 32); + } + +#if 0 + Image_WriteTGABGRA ("gfx/generated_conchars.tga", 256, 256, data); +#endif + + tex = R_LoadTexture2D(drawtexturepool, "conchars", 256, 256, data, TEXTYPE_BGRA, TEXF_ALPHA, -1, NULL); + Mem_Free(data); + return tex; +} + +static rtexture_t *draw_generateditherpattern(void) +{ + int x, y; + unsigned char pixels[8][8]; + for (y = 0;y < 8;y++) + for (x = 0;x < 8;x++) + pixels[y][x] = ((x^y) & 4) ? 254 : 0; + return R_LoadTexture2D(drawtexturepool, "ditherpattern", 8, 8, pixels[0], TEXTYPE_PALETTE, TEXF_FORCENEAREST, -1, palette_bgra_transparent); +} + +typedef struct embeddedpic_s +{ + const char *name; + int width; + int height; + const char *pixels; +} +embeddedpic_t; + +static const embeddedpic_t embeddedpics[] = +{ + { + "gfx/prydoncursor001", 16, 16, + "477777774......." + "77.....6........" + "7.....6........." + "7....6.........." + "7.....6........." + "7..6...6........" + "7.6.6...6......." + "76...6...6......" + "4.....6.6......." + ".......6........" + "................" + "................" + "................" + "................" + "................" + "................" + }, + { + "ui/mousepointer", 16, 16, + "477777774......." + "77.....6........" + "7.....6........." + "7....6.........." + "7.....6........." + "7..6...6........" + "7.6.6...6......." + "76...6...6......" + "4.....6.6......." + ".......6........" + "................" + "................" + "................" + "................" + "................" + "................" + }, + { + "gfx/crosshair1", 16, 16, + "................" + "................" + "................" + "...33......33..." + "...355....553..." + "....577..775...." + ".....77..77....." + "................" + "................" + ".....77..77....." + "....577..775...." + "...355....553..." + "...33......33..." + "................" + "................" + "................" + }, + { + "gfx/crosshair2", 16, 16, + "................" + "................" + "................" + "...3........3..." + "....5......5...." + ".....7....7....." + "......7..7......" + "................" + "................" + "......7..7......" + ".....7....7....." + "....5......5...." + "...3........3..." + "................" + "................" + "................" + }, + { + "gfx/crosshair3", 16, 16, + "................" + ".......77......." + ".......77......." + "................" + "................" + ".......44......." + ".......44......." + ".77..44..44..77." + ".77..44..44..77." + ".......44......." + ".......44......." + "................" + "................" + ".......77......." + ".......77......." + "................" + }, + { + "gfx/crosshair4", 16, 16, + "................" + "................" + "................" + "................" + "................" + "................" + "................" + "................" + "........7777777." + "........752....." + "........72......" + "........7......." + "........7......." + "........7......." + "........7......." + "................" + }, + { + "gfx/crosshair5", 8, 8, + "........" + "........" + "....7..." + "........" + "..7.7.7." + "........" + "....7..." + "........" + }, + { + "gfx/crosshair6", 2, 2, + "77" + "77" + }, + { + "gfx/crosshair7", 16, 16, + "................" + ".3............3." + "..5...2332...5.." + "...7.3....3.7..." + "....7......7...." + "...3.7....7.3..." + "..2...7..7...2.." + "..3..........3.." + "..3..........3.." + "..2...7..7...2.." + "...3.7....7.3..." + "....7......7...." + "...7.3....3.7..." + "..5...2332...5.." + ".3............3." + "................" + }, + {NULL, 0, 0, NULL} +}; + +static rtexture_t *draw_generatepic(const char *name, qboolean quiet) +{ + const embeddedpic_t *p; + for (p = embeddedpics;p->name;p++) + if (!strcmp(name, p->name)) + return R_LoadTexture2D(drawtexturepool, p->name, p->width, p->height, (const unsigned char *)p->pixels, TEXTYPE_PALETTE, TEXF_ALPHA, -1, palette_bgra_embeddedpic); + if (!strcmp(name, "gfx/conchars")) + return draw_generateconchars(); + if (!strcmp(name, "gfx/colorcontrol/ditherpattern")) + return draw_generateditherpattern(); + if (!quiet) + Con_DPrintf("Draw_CachePic: failed to load %s\n", name); + return r_texture_notexture; +} + +int draw_frame = 1; + +/* +================ +Draw_CachePic +================ +*/ +// FIXME: move this to client somehow +cachepic_t *Draw_CachePic_Flags(const char *path, unsigned int cachepicflags) +{ + int crc, hashkey; + unsigned char *pixels = NULL; + cachepic_t *pic; + fs_offset_t lmpsize; + unsigned char *lmpdata; + char lmpname[MAX_QPATH]; + int texflags; + int j; + qboolean ddshasalpha; + float ddsavgcolor[4]; + qboolean loaded = false; + + texflags = TEXF_ALPHA; + if (!(cachepicflags & CACHEPICFLAG_NOCLAMP)) + texflags |= TEXF_CLAMP; + if (!(cachepicflags & CACHEPICFLAG_NOCOMPRESSION) && gl_texturecompression_2d.integer && gl_texturecompression.integer) + texflags |= TEXF_COMPRESS; + + // check whether the picture has already been cached + crc = CRC_Block((unsigned char *)path, strlen(path)); + hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; + for (pic = cachepichash[hashkey];pic;pic = pic->chain) + { + if (!strcmp (path, pic->name)) + { + // if it was created (or replaced) by Draw_NewPic, just return it + if(pic->flags & CACHEPICFLAG_NEWPIC) + return pic; + if (!((pic->texflags ^ texflags) & ~(TEXF_COMPRESS))) // ignore TEXF_COMPRESS when comparing, because fallback pics remove the flag + { + if(!(cachepicflags & CACHEPICFLAG_NOTPERSISTENT)) + { + if(pic->tex) + pic->autoload = false; // persist it + else + goto reload; // load it below, and then persist + } + return pic; + } + } + } + + if (numcachepics == MAX_CACHED_PICS) + { + Con_Printf ("Draw_CachePic: numcachepics == MAX_CACHED_PICS\n"); + // FIXME: support NULL in callers? + return cachepics; // return the first one + } + pic = cachepics + (numcachepics++); + strlcpy (pic->name, path, sizeof(pic->name)); + // link into list + pic->chain = cachepichash[hashkey]; + cachepichash[hashkey] = pic; + +reload: + // check whether it is an dynamic texture (if so, we can directly use its texture handler) + pic->flags = cachepicflags; + pic->tex = CL_GetDynTexture( path ); + // if so, set the width/height, too + if( pic->tex ) { + pic->width = R_TextureWidth(pic->tex); + pic->height = R_TextureHeight(pic->tex); + // we're done now (early-out) + return pic; + } + + pic->hasalpha = true; // assume alpha unless we know it has none + pic->texflags = texflags; + pic->autoload = (cachepicflags & CACHEPICFLAG_NOTPERSISTENT); + pic->lastusedframe = draw_frame; + + // load a high quality image from disk if possible + if (!loaded && r_texture_dds_load.integer != 0 && (pic->tex = R_LoadTextureDDSFile(drawtexturepool, va("dds/%s.dds", pic->name), pic->texflags, &ddshasalpha, ddsavgcolor, 0))) + { + // note this loads even if autoload is true, otherwise we can't get the width/height + loaded = true; + pic->hasalpha = ddshasalpha; + pic->width = R_TextureWidth(pic->tex); + pic->height = R_TextureHeight(pic->tex); + } + if (!loaded && ((pixels = loadimagepixelsbgra(pic->name, false, true, false, NULL)) || (!strncmp(pic->name, "gfx/", 4) && (pixels = loadimagepixelsbgra(pic->name+4, false, true, false, NULL))))) + { + loaded = true; + pic->hasalpha = false; + if (pic->texflags & TEXF_ALPHA) + { + for (j = 3;j < image_width * image_height * 4;j += 4) + { + if (pixels[j] < 255) + { + pic->hasalpha = true; + break; + } + } + } + + pic->width = image_width; + pic->height = image_height; + if (!pic->autoload) + { + pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, image_width, image_height, pixels, vid.sRGB2D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, pic->texflags & (pic->hasalpha ? ~0 : ~TEXF_ALPHA), -1, NULL); + if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) + R_SaveTextureDDSFile(pic->tex, va("dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); + } + } + if (!loaded) + { + pic->autoload = false; + // never compress the fallback images + pic->texflags &= ~TEXF_COMPRESS; + } + + // now read the low quality version (wad or lmp file), and take the pic + // size from that even if we don't upload the texture, this way the pics + // show up the right size in the menu even if they were replaced with + // higher or lower resolution versions + dpsnprintf(lmpname, sizeof(lmpname), "%s.lmp", pic->name); + if (!strncmp(pic->name, "gfx/", 4) && (lmpdata = FS_LoadFile(lmpname, tempmempool, false, &lmpsize))) + { + if (developer_loading.integer) + Con_Printf("loading lump \"%s\"\n", pic->name); + + if (lmpsize >= 9) + { + pic->width = lmpdata[0] + lmpdata[1] * 256 + lmpdata[2] * 65536 + lmpdata[3] * 16777216; + pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216; + // if no high quality replacement image was found, upload the original low quality texture + if (!loaded) + { + loaded = true; + pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, pic->width, pic->height, lmpdata + 8, vid.sRGB2D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_transparent); + } + } + Mem_Free(lmpdata); + } + else if ((lmpdata = W_GetLumpName (pic->name + 4))) + { + if (developer_loading.integer) + Con_Printf("loading gfx.wad lump \"%s\"\n", pic->name + 4); + + if (!strcmp(pic->name, "gfx/conchars")) + { + // conchars is a raw image and with color 0 as transparent instead of 255 + pic->width = 128; + pic->height = 128; + // if no high quality replacement image was found, upload the original low quality texture + if (!loaded) + { + loaded = true; + pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, 128, 128, lmpdata, vid.sRGB2D != 0 ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_font); + } + } + else + { + pic->width = lmpdata[0] + lmpdata[1] * 256 + lmpdata[2] * 65536 + lmpdata[3] * 16777216; + pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216; + // if no high quality replacement image was found, upload the original low quality texture + if (!loaded) + { + loaded = true; + pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, pic->width, pic->height, lmpdata + 8, vid.sRGB2D != 0 ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_transparent); + } + } + } + + if (pixels) + { + Mem_Free(pixels); + pixels = NULL; + } + if (!loaded) + { + // if it's not found on disk, generate an image + pic->tex = draw_generatepic(pic->name, (cachepicflags & CACHEPICFLAG_QUIET) != 0); + pic->width = R_TextureWidth(pic->tex); + pic->height = R_TextureHeight(pic->tex); + } + + return pic; +} + +cachepic_t *Draw_CachePic (const char *path) +{ + return Draw_CachePic_Flags (path, 0); // default to persistent! +} + +rtexture_t *Draw_GetPicTexture(cachepic_t *pic) +{ + if (pic->autoload && !pic->tex) + { + if (pic->tex == NULL && r_texture_dds_load.integer != 0) + { + qboolean ddshasalpha; + float ddsavgcolor[4]; + pic->tex = R_LoadTextureDDSFile(drawtexturepool, va("dds/%s.dds", pic->name), pic->texflags, &ddshasalpha, ddsavgcolor, 0); + } + if (pic->tex == NULL) + { + pic->tex = loadtextureimage(drawtexturepool, pic->name, false, pic->texflags, true, vid.sRGB2D); + if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) + R_SaveTextureDDSFile(pic->tex, va("dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); + } + if (pic->tex == NULL && !strncmp(pic->name, "gfx/", 4)) + { + pic->tex = loadtextureimage(drawtexturepool, pic->name+4, false, pic->texflags, true, vid.sRGB2D); + if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) + R_SaveTextureDDSFile(pic->tex, va("dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); + } + if (pic->tex == NULL) + pic->tex = draw_generatepic(pic->name, true); + } + pic->lastusedframe = draw_frame; + return pic->tex; +} + +void Draw_Frame(void) +{ + int i; + cachepic_t *pic; + static double nextpurgetime; + if (nextpurgetime > realtime) + return; + nextpurgetime = realtime + 0.05; + for (i = 0, pic = cachepics;i < numcachepics;i++, pic++) + { + if (pic->autoload && pic->tex && pic->lastusedframe < draw_frame) + { + R_FreeTexture(pic->tex); + pic->tex = NULL; + } + } + draw_frame++; +} + +cachepic_t *Draw_NewPic(const char *picname, int width, int height, int alpha, unsigned char *pixels_bgra) +{ + int crc, hashkey; + cachepic_t *pic; + + crc = CRC_Block((unsigned char *)picname, strlen(picname)); + hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; + for (pic = cachepichash[hashkey];pic;pic = pic->chain) + if (!strcmp (picname, pic->name)) + break; + + if (pic) + { + if (pic->flags == CACHEPICFLAG_NEWPIC && pic->tex && pic->width == width && pic->height == height) + { + R_UpdateTexture(pic->tex, pixels_bgra, 0, 0, 0, width, height, 1); + return pic; + } + } + else + { + if (pic == NULL) + { + if (numcachepics == MAX_CACHED_PICS) + { + Con_Printf ("Draw_NewPic: numcachepics == MAX_CACHED_PICS\n"); + // FIXME: support NULL in callers? + return cachepics; // return the first one + } + pic = cachepics + (numcachepics++); + strlcpy (pic->name, picname, sizeof(pic->name)); + // link into list + pic->chain = cachepichash[hashkey]; + cachepichash[hashkey] = pic; + } + } + + pic->flags = CACHEPICFLAG_NEWPIC; // disable texflags checks in Draw_CachePic + pic->width = width; + pic->height = height; + if (pic->tex) + R_FreeTexture(pic->tex); + pic->tex = R_LoadTexture2D(drawtexturepool, picname, width, height, pixels_bgra, TEXTYPE_BGRA, (alpha ? TEXF_ALPHA : 0), -1, NULL); + return pic; +} + +void Draw_FreePic(const char *picname) +{ + int crc; + int hashkey; + cachepic_t *pic; + // this doesn't really free the pic, but does free it's texture + crc = CRC_Block((unsigned char *)picname, strlen(picname)); + hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; + for (pic = cachepichash[hashkey];pic;pic = pic->chain) + { + if (!strcmp (picname, pic->name) && pic->tex) + { + R_FreeTexture(pic->tex); + pic->tex = NULL; + pic->width = 0; + pic->height = 0; + return; + } + } +} + +static float snap_to_pixel_x(float x, float roundUpAt); +extern int con_linewidth; // to force rewrapping +void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset) +{ + int i, ch; + float maxwidth; + char widthfile[MAX_QPATH]; + char *widthbuf; + fs_offset_t widthbufsize; + + if(override || !fnt->texpath[0]) + { + strlcpy(fnt->texpath, name, sizeof(fnt->texpath)); + // load the cvars when the font is FIRST loader + fnt->settings.scale = scale; + fnt->settings.voffset = voffset; + fnt->settings.antialias = r_font_antialias.integer; + fnt->settings.hinting = r_font_hinting.integer; + fnt->settings.outline = r_font_postprocess_outline.value; + fnt->settings.blur = r_font_postprocess_blur.value; + fnt->settings.shadowx = r_font_postprocess_shadow_x.value; + fnt->settings.shadowy = r_font_postprocess_shadow_y.value; + fnt->settings.shadowz = r_font_postprocess_shadow_z.value; + } + // fix bad scale + if (fnt->settings.scale <= 0) + fnt->settings.scale = 1; + + if(drawtexturepool == NULL) + return; // before gl_draw_start, so will be loaded later + + if(fnt->ft2) + { + // clear freetype font + Font_UnloadFont(fnt->ft2); + Mem_Free(fnt->ft2); + fnt->ft2 = NULL; + } + + if(fnt->req_face != -1) + { + if(!Font_LoadFont(fnt->texpath, fnt)) + Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath); + } + + fnt->tex = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION)->tex; + if(fnt->tex == r_texture_notexture) + { + for (i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + if (!fnt->fallbacks[i][0]) + break; + fnt->tex = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION)->tex; + if(fnt->tex != r_texture_notexture) + break; + } + if(fnt->tex == r_texture_notexture) + { + fnt->tex = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION)->tex; + strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile)); + } + else + dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]); + } + else + dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath); + + // unspecified width == 1 (base width) + for(ch = 0; ch < 256; ++ch) + fnt->width_of[ch] = 1; + + // FIXME load "name.width", if it fails, fill all with 1 + if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize))) + { + float extraspacing = 0; + const char *p = widthbuf; + + ch = 0; + while(ch < 256) + { + if(!COM_ParseToken_Simple(&p, false, false)) + return; + + switch(*com_token) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case '.': + fnt->width_of[ch] = atof(com_token) + extraspacing; + ch++; + break; + default: + if(!strcmp(com_token, "extraspacing")) + { + if(!COM_ParseToken_Simple(&p, false, false)) + return; + extraspacing = atof(com_token); + } + else if(!strcmp(com_token, "scale")) + { + if(!COM_ParseToken_Simple(&p, false, false)) + return; + fnt->settings.scale = atof(com_token); + } + else + { + Con_Printf("Warning: skipped unknown font property %s\n", com_token); + if(!COM_ParseToken_Simple(&p, false, false)) + return; + } + break; + } + } + + Mem_Free(widthbuf); + } + + if(fnt->ft2) + { + for (i = 0; i < MAX_FONT_SIZES; ++i) + { + ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i); + if (!map) + break; + for(ch = 0; ch < 256; ++ch) + map->width_of[ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size); + } + } + + maxwidth = fnt->width_of[0]; + for(i = 1; i < 256; ++i) + maxwidth = max(maxwidth, fnt->width_of[i]); + fnt->maxwidth = maxwidth; + + // fix up maxwidth for overlap + fnt->maxwidth *= fnt->settings.scale; + + if(fnt == FONT_CONSOLE) + con_linewidth = -1; // rewrap console in next frame +} + +extern cvar_t developer_font; +dp_font_t *FindFont(const char *title, qboolean allocate_new) +{ + int i, oldsize; + + // find font + for(i = 0; i < dp_fonts.maxsize; ++i) + if(!strcmp(dp_fonts.f[i].title, title)) + return &dp_fonts.f[i]; + // if not found - try allocate + if (allocate_new) + { + // find any font with empty title + for(i = 0; i < dp_fonts.maxsize; ++i) + { + if(!strcmp(dp_fonts.f[i].title, "")) + { + strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title)); + return &dp_fonts.f[i]; + } + } + // if no any 'free' fonts - expand buffer + oldsize = dp_fonts.maxsize; + dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND; + if (developer_font.integer) + Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize); + dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize); + // relink ft2 structures + for(i = 0; i < oldsize; ++i) + if (dp_fonts.f[i].ft2) + dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings; + // register a font in first expanded slot + strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title)); + return &dp_fonts.f[oldsize]; + } + return NULL; +} + +static float snap_to_pixel_x(float x, float roundUpAt) +{ + float pixelpos = x * vid.width / vid_conwidth.value; + int snap = (int) pixelpos; + if (pixelpos - snap >= roundUpAt) ++snap; + return ((float)snap * vid_conwidth.value / vid.width); + /* + x = (int)(x * vid.width / vid_conwidth.value); + x = (x * vid_conwidth.value / vid.width); + return x; + */ +} + +static float snap_to_pixel_y(float y, float roundUpAt) +{ + float pixelpos = y * vid.height / vid_conheight.value; + int snap = (int) pixelpos; + if (pixelpos - snap > roundUpAt) ++snap; + return ((float)snap * vid_conheight.value / vid.height); + /* + y = (int)(y * vid.height / vid_conheight.value); + y = (y * vid_conheight.value / vid.height); + return y; + */ +} + +static void LoadFont_f(void) +{ + dp_font_t *f; + int i, sizes; + const char *filelist, *c, *cm; + float sz, scale, voffset; + char mainfont[MAX_QPATH]; + + if(Cmd_Argc() < 2) + { + Con_Printf("Available font commands:\n"); + for(i = 0; i < dp_fonts.maxsize; ++i) + if (dp_fonts.f[i].title[0]) + Con_Printf(" loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title); + Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n" + "can specify multiple fonts and faces\n" + "Like this: gfx/vera-sans:2,gfx/fallback:1\n" + "to load face 2 of the font gfx/vera-sans and use face 1\n" + "of gfx/fallback as fallback font.\n" + "You can also specify a list of font sizes to load, like this:\n" + "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n" + "In many cases, 8 12 16 24 32 should be a good choice.\n" + "custom switches:\n" + " scale x : scale all characters by this amount when rendering (doesnt change line height)\n" + " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n" + ); + return; + } + f = FindFont(Cmd_Argv(1), true); + if(f == NULL) + { + Con_Printf("font function not found\n"); + return; + } + + if(Cmd_Argc() < 3) + filelist = "gfx/conchars"; + else + filelist = Cmd_Argv(2); + + memset(f->fallbacks, 0, sizeof(f->fallbacks)); + memset(f->fallback_faces, 0, sizeof(f->fallback_faces)); + + // first font is handled "normally" + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->req_face = atoi(c+1); + else + { + f->req_face = 0; + c = cm; + } + + if(!c || (c - filelist) > MAX_QPATH) + strlcpy(mainfont, filelist, sizeof(mainfont)); + else + { + memcpy(mainfont, filelist, c - filelist); + mainfont[c - filelist] = 0; + } + + for(i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + c = strchr(filelist, ','); + if(!c) + break; + filelist = c + 1; + if(!*filelist) + break; + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->fallback_faces[i] = atoi(c+1); + else + { + f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index + c = cm; + } + if(!c || (c-filelist) > MAX_QPATH) + { + strlcpy(f->fallbacks[i], filelist, sizeof(mainfont)); + } + else + { + memcpy(f->fallbacks[i], filelist, c - filelist); + f->fallbacks[i][c - filelist] = 0; + } + } + + // for now: by default load only one size: the default size + f->req_sizes[0] = 0; + for(i = 1; i < MAX_FONT_SIZES; ++i) + f->req_sizes[i] = -1; + + scale = 1; + voffset = 0; + if(Cmd_Argc() >= 4) + { + for(sizes = 0, i = 3; i < Cmd_Argc(); ++i) + { + // special switches + if (!strcmp(Cmd_Argv(i), "scale")) + { + i++; + if (i < Cmd_Argc()) + scale = atof(Cmd_Argv(i)); + continue; + } + if (!strcmp(Cmd_Argv(i), "voffset")) + { + i++; + if (i < Cmd_Argc()) + voffset = atof(Cmd_Argv(i)); + continue; + } + + if (sizes == -1) + continue; // no slot for other sizes + + // parse one of sizes + sz = atof(Cmd_Argv(i)); + if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes + { + // search for duplicated sizes + int j; + for (j=0; jreq_sizes[j] == sz) + break; + if (j != sizes) + continue; // sz already in req_sizes, don't add it again + + if (sizes == MAX_FONT_SIZES) + { + Con_Printf("Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES); + sizes = -1; + continue; + } + f->req_sizes[sizes] = sz; + sizes++; + } + } + } + + LoadFont(true, mainfont, f, scale, voffset); +} + +/* +=============== +Draw_Init +=============== +*/ +static void gl_draw_start(void) +{ + int i; + drawtexturepool = R_AllocTexturePool(); + + numcachepics = 0; + memset(cachepichash, 0, sizeof(cachepichash)); + + font_start(); + + // load default font textures + for(i = 0; i < dp_fonts.maxsize; ++i) + if (dp_fonts.f[i].title[0]) + LoadFont(false, va("gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0); + + // draw the loading screen so people have something to see in the newly opened window + SCR_UpdateLoadingScreen(true); +} + +static void gl_draw_shutdown(void) +{ + font_shutdown(); + + R_FreeTexturePool(&drawtexturepool); + + numcachepics = 0; + memset(cachepichash, 0, sizeof(cachepichash)); +} + +static void gl_draw_newmap(void) +{ + font_newmap(); +} + +void GL_Draw_Init (void) +{ + int i, j; + + Cvar_RegisterVariable(&r_font_postprocess_blur); + Cvar_RegisterVariable(&r_font_postprocess_outline); + Cvar_RegisterVariable(&r_font_postprocess_shadow_x); + Cvar_RegisterVariable(&r_font_postprocess_shadow_y); + Cvar_RegisterVariable(&r_font_postprocess_shadow_z); + Cvar_RegisterVariable(&r_font_hinting); + Cvar_RegisterVariable(&r_font_antialias); + Cvar_RegisterVariable(&r_textshadow); + Cvar_RegisterVariable(&r_textbrightness); + Cvar_RegisterVariable(&r_textcontrast); + + // allocate fonts storage + fonts_mempool = Mem_AllocPool("FONTS", 0, NULL); + dp_fonts.maxsize = MAX_FONTS; + dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize); + memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize); + + // assign starting font names + strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title)); + strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath)); + strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title)); + strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title)); + strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title)); + strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title)); + strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title)); + strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title)); + strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title)); + for(i = 0, j = 0; i < MAX_USERFONTS; ++i) + if(!FONT_USER(i)->title[0]) + dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++); + + Cmd_AddCommand ("loadfont",LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions"); + R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL); +} + +static void _DrawQ_Setup(void) +{ + r_viewport_t viewport; + if (r_refdef.draw2dstage == 1) + return; + r_refdef.draw2dstage = 1; + CHECKGLERROR + R_Viewport_InitOrtho(&viewport, &identitymatrix, r_refdef.view.x, vid.height - r_refdef.view.y - r_refdef.view.height, r_refdef.view.width, r_refdef.view.height, 0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100, NULL); + R_Mesh_ResetRenderTargets(); + R_SetViewport(&viewport); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + GL_DepthFunc(GL_LEQUAL); + GL_PolygonOffset(0,0); + GL_CullFace(GL_NONE); + R_EntityMatrix(&identitymatrix); + + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(false); + GL_Color(1,1,1,1); +} + +qboolean r_draw2d_force = false; +void _DrawQ_SetupAndProcessDrawFlag(int flags, cachepic_t *pic, float alpha) +{ + _DrawQ_Setup(); + CHECKGLERROR + if(!r_draw2d.integer && !r_draw2d_force) + return; + DrawQ_ProcessDrawFlag(flags, (alpha < 1) || (pic && pic->hasalpha)); +} +void DrawQ_ProcessDrawFlag(int flags, qboolean alpha) +{ + if(flags == DRAWFLAG_ADDITIVE) + { + GL_DepthMask(false); + GL_BlendFunc(alpha ? GL_SRC_ALPHA : GL_ONE, GL_ONE); + } + else if(flags == DRAWFLAG_MODULATE) + { + GL_DepthMask(false); + GL_BlendFunc(GL_DST_COLOR, GL_ZERO); + } + else if(flags == DRAWFLAG_2XMODULATE) + { + GL_DepthMask(false); + GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + } + else if(flags == DRAWFLAG_SCREEN) + { + GL_DepthMask(false); + GL_BlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE); + } + else if(alpha) + { + GL_DepthMask(false); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + else + { + GL_DepthMask(true); + GL_BlendFunc(GL_ONE, GL_ZERO); + } +} + +void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags) +{ + float floats[36]; + + _DrawQ_SetupAndProcessDrawFlag(flags, pic, alpha); + if(!r_draw2d.integer && !r_draw2d_force) + return; + +// R_Mesh_ResetTextureState(); + floats[12] = 0.0f;floats[13] = 0.0f; + floats[14] = 1.0f;floats[15] = 0.0f; + floats[16] = 1.0f;floats[17] = 1.0f; + floats[18] = 0.0f;floats[19] = 1.0f; + floats[20] = floats[24] = floats[28] = floats[32] = red; + floats[21] = floats[25] = floats[29] = floats[33] = green; + floats[22] = floats[26] = floats[30] = floats[34] = blue; + floats[23] = floats[27] = floats[31] = floats[35] = alpha; + if (pic) + { + if (width == 0) + width = pic->width; + if (height == 0) + height = pic->height; + R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, true); + +#if 0 + // AK07: lets be texel correct on the corners + { + float horz_offset = 0.5f / pic->width; + float vert_offset = 0.5f / pic->height; + + floats[12] = 0.0f + horz_offset;floats[13] = 0.0f + vert_offset; + floats[14] = 1.0f - horz_offset;floats[15] = 0.0f + vert_offset; + floats[16] = 1.0f - horz_offset;floats[17] = 1.0f - vert_offset; + floats[18] = 0.0f + horz_offset;floats[19] = 1.0f - vert_offset; + } +#endif + } + else + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + + floats[2] = floats[5] = floats[8] = floats[11] = 0; + floats[0] = floats[9] = x; + floats[1] = floats[4] = y; + floats[3] = floats[6] = x + width; + floats[7] = floats[10] = y + height; + + R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags) +{ + float floats[36]; + float af = DEG2RAD(-angle); // forward + float ar = DEG2RAD(-angle + 90); // right + float sinaf = sin(af); + float cosaf = cos(af); + float sinar = sin(ar); + float cosar = cos(ar); + + _DrawQ_SetupAndProcessDrawFlag(flags, pic, alpha); + if(!r_draw2d.integer && !r_draw2d_force) + return; + +// R_Mesh_ResetTextureState(); + if (pic) + { + if (width == 0) + width = pic->width; + if (height == 0) + height = pic->height; + R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, true); + } + else + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + + floats[2] = floats[5] = floats[8] = floats[11] = 0; + +// top left + floats[0] = x - cosaf*org_x - cosar*org_y; + floats[1] = y - sinaf*org_x - sinar*org_y; + +// top right + floats[3] = x + cosaf*(width-org_x) - cosar*org_y; + floats[4] = y + sinaf*(width-org_x) - sinar*org_y; + +// bottom right + floats[6] = x + cosaf*(width-org_x) + cosar*(height-org_y); + floats[7] = y + sinaf*(width-org_x) + sinar*(height-org_y); + +// bottom left + floats[9] = x - cosaf*org_x + cosar*(height-org_y); + floats[10] = y - sinaf*org_x + sinar*(height-org_y); + + floats[12] = 0.0f;floats[13] = 0.0f; + floats[14] = 1.0f;floats[15] = 0.0f; + floats[16] = 1.0f;floats[17] = 1.0f; + floats[18] = 0.0f;floats[19] = 1.0f; + floats[20] = floats[24] = floats[28] = floats[32] = red; + floats[21] = floats[25] = floats[29] = floats[33] = green; + floats[22] = floats[26] = floats[30] = floats[34] = blue; + floats[23] = floats[27] = floats[31] = floats[35] = alpha; + + R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags) +{ + float floats[36]; + + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, alpha); + if(!r_draw2d.integer && !r_draw2d_force) + return; + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + + floats[2] = floats[5] = floats[8] = floats[11] = 0; + floats[0] = floats[9] = x; + floats[1] = floats[4] = y; + floats[3] = floats[6] = x + width; + floats[7] = floats[10] = y + height; + floats[12] = 0.0f;floats[13] = 0.0f; + floats[14] = 1.0f;floats[15] = 0.0f; + floats[16] = 1.0f;floats[17] = 1.0f; + floats[18] = 0.0f;floats[19] = 1.0f; + floats[20] = floats[24] = floats[28] = floats[32] = red; + floats[21] = floats[25] = floats[29] = floats[33] = green; + floats[22] = floats[26] = floats[30] = floats[34] = blue; + floats[23] = floats[27] = floats[31] = floats[35] = alpha; + + R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +/// color tag printing +static const vec4_t string_colors[] = +{ + // Quake3 colors + // LordHavoc: why on earth is cyan before magenta in Quake3? + // LordHavoc: note: Doom3 uses white for [0] and [7] + {0.0, 0.0, 0.0, 1.0}, // black + {1.0, 0.0, 0.0, 1.0}, // red + {0.0, 1.0, 0.0, 1.0}, // green + {1.0, 1.0, 0.0, 1.0}, // yellow + {0.0, 0.0, 1.0, 1.0}, // blue + {0.0, 1.0, 1.0, 1.0}, // cyan + {1.0, 0.0, 1.0, 1.0}, // magenta + {1.0, 1.0, 1.0, 1.0}, // white + // [515]'s BX_COLOREDTEXT extension + {1.0, 1.0, 1.0, 0.5}, // half transparent + {0.5, 0.5, 0.5, 1.0} // half brightness + // Black's color table + //{1.0, 1.0, 1.0, 1.0}, + //{1.0, 0.0, 0.0, 1.0}, + //{0.0, 1.0, 0.0, 1.0}, + //{0.0, 0.0, 1.0, 1.0}, + //{1.0, 1.0, 0.0, 1.0}, + //{0.0, 1.0, 1.0, 1.0}, + //{1.0, 0.0, 1.0, 1.0}, + //{0.1, 0.1, 0.1, 1.0} +}; + +#define STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t)) + +static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qboolean shadow) +{ + float C = r_textcontrast.value; + float B = r_textbrightness.value; + if (colorindex & 0x10000) // that bit means RGB color + { + color[0] = ((colorindex >> 12) & 0xf) / 15.0; + color[1] = ((colorindex >> 8) & 0xf) / 15.0; + color[2] = ((colorindex >> 4) & 0xf) / 15.0; + color[3] = (colorindex & 0xf) / 15.0; + } + else + Vector4Copy(string_colors[colorindex], color); + Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a); + if (shadow) + { + float shadowalpha = (color[0]+color[1]+color[2]) * 0.8; + Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1)); + } +} + +// NOTE: this function always draws exactly one character if maxwidth <= 0 +float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) +{ + const char *text_start = text; + int colorindex = STRING_COLOR_DEFAULT; + size_t i; + float x = 0; + Uchar ch, mapch, nextch; + Uchar prevch = 0; // used for kerning + int tempcolorindex; + float kx; + int map_index = 0; + size_t bytes_left; + ft2_font_map_t *fontmap = NULL; + ft2_font_map_t *map = NULL; + //ft2_font_map_t *prevmap = NULL; + ft2_font_t *ft2 = fnt->ft2; + // float ftbase_x; + qboolean snap = true; + qboolean least_one = false; + float dw; // display w + //float dh; // display h + const float *width_of; + + if (!h) h = w; + if (!h) { + w = h = 1; + snap = false; + } + // do this in the end + w *= fnt->settings.scale; + h *= fnt->settings.scale; + + // find the most fitting size: + if (ft2 != NULL) + { + if (snap) + map_index = Font_IndexForSize(ft2, h, &w, &h); + else + map_index = Font_IndexForSize(ft2, h, NULL, NULL); + fontmap = Font_MapForIndex(ft2, map_index); + } + + dw = w * sw; + //dh = h * sh; + + if (*maxlen < 1) + *maxlen = 1<<30; + + if (!outcolor || *outcolor == -1) + colorindex = STRING_COLOR_DEFAULT; + else + colorindex = *outcolor; + + // maxwidth /= fnt->scale; // w and h are multiplied by it already + // ftbase_x = snap_to_pixel_x(0); + + if(maxwidth <= 0) + { + least_one = true; + maxwidth = -maxwidth; + } + + //if (snap) + // x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway + + if (fontmap) + width_of = fontmap->width_of; + else + width_of = fnt->width_of; + + for (i = 0;((bytes_left = *maxlen - (text - text_start)) > 0) && *text;) + { + size_t i0 = i; + nextch = ch = u8_getnchar(text, &text, bytes_left); + i = text - text_start; + if (!ch) + break; + if (ch == ' ' && !fontmap) + { + if(!least_one || i0) // never skip the first character + if(x + width_of[(int) ' '] * dw > maxwidth) + { + i = i0; + break; // oops, can't draw this + } + x += width_of[(int) ' '] * dw; + continue; + } + // i points to the char after ^ + if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen) + { + ch = *text; // colors are ascii, so no u8_ needed + if (ch <= '9' && ch >= '0') // ^[0-9] found + { + colorindex = ch - '0'; + ++text; + ++i; + continue; + } + // i points to the char after ^... + // i+3 points to 3 in ^x123 + // i+3 == *maxlen would mean that char is missing + else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found + { + // building colorindex... + ch = tolower(text[1]); + tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000 + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12; + else tempcolorindex = 0; + if (tempcolorindex) + { + ch = tolower(text[2]); + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8; + else tempcolorindex = 0; + if (tempcolorindex) + { + ch = tolower(text[3]); + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4; + else tempcolorindex = 0; + if (tempcolorindex) + { + colorindex = tempcolorindex | 0xf; + // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa) + i+=4; + text += 4; + continue; + } + } + } + } + else if (ch == STRING_COLOR_TAG) // ^^ found, ignore the first ^ and go to print the second + { + i++; + text++; + } + i--; + } + ch = nextch; + + if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF)) + { + if (ch > 0xE000) + ch -= 0xE000; + if (ch > 0xFF) + continue; + if (fontmap) + map = ft2_oldstyle_map; + prevch = 0; + if(!least_one || i0) // never skip the first character + if(x + width_of[ch] * dw > maxwidth) + { + i = i0; + break; // oops, can't draw this + } + x += width_of[ch] * dw; + } else { + if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP) + { + map = FontMap_FindForChar(fontmap, ch); + if (!map) + { + if (!Font_LoadMapForIndex(ft2, map_index, ch, &map)) + break; + if (!map) + break; + } + } + mapch = ch - map->start; + if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL)) + x += kx * dw; + x += map->glyphs[mapch].advance_x * dw; + //prevmap = map; + prevch = ch; + } + } + + *maxlen = i; + + if (outcolor) + *outcolor = colorindex; + + return x; +} + +float DrawQ_Color[4]; +float DrawQ_String_Scale(float startx, float starty, const char *text, size_t maxlen, float w, float h, float sw, float sh, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + int shadow, colorindex = STRING_COLOR_DEFAULT; + size_t i; + float x = startx, y, s, t, u, v, thisw; + float *av, *at, *ac; + int batchcount; + static float vertex3f[QUADELEMENTS_MAXQUADS*4*3]; + static float texcoord2f[QUADELEMENTS_MAXQUADS*4*2]; + static float color4f[QUADELEMENTS_MAXQUADS*4*4]; + Uchar ch, mapch, nextch; + Uchar prevch = 0; // used for kerning + int tempcolorindex; + int map_index = 0; + //ft2_font_map_t *prevmap = NULL; // the previous map + ft2_font_map_t *map = NULL; // the currently used map + ft2_font_map_t *fontmap = NULL; // the font map for the size + float ftbase_y; + const char *text_start = text; + float kx, ky; + ft2_font_t *ft2 = fnt->ft2; + qboolean snap = true; + float pix_x, pix_y; + size_t bytes_left; + float dw, dh; + const float *width_of; + + int tw, th; + tw = R_TextureWidth(fnt->tex); + th = R_TextureHeight(fnt->tex); + + if (!h) h = w; + if (!h) { + h = w = 1; + snap = false; + } + + starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset + w *= fnt->settings.scale; + h *= fnt->settings.scale; + + if (ft2 != NULL) + { + if (snap) + map_index = Font_IndexForSize(ft2, h, &w, &h); + else + map_index = Font_IndexForSize(ft2, h, NULL, NULL); + fontmap = Font_MapForIndex(ft2, map_index); + } + + dw = w * sw; + dh = h * sh; + + // draw the font at its baseline when using freetype + //ftbase_x = 0; + ftbase_y = dh * (4.5/6.0); + + if (maxlen < 1) + maxlen = 1<<30; + + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, 0); + if(!r_draw2d.integer && !r_draw2d_force) + return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000); + +// R_Mesh_ResetTextureState(); + if (!fontmap) + R_Mesh_TexBind(0, fnt->tex); + R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1, true); + + ac = color4f; + at = texcoord2f; + av = vertex3f; + batchcount = 0; + + //ftbase_x = snap_to_pixel_x(ftbase_x); + if(snap) + { + startx = snap_to_pixel_x(startx, 0.4); + starty = snap_to_pixel_y(starty, 0.4); + ftbase_y = snap_to_pixel_y(ftbase_y, 0.3); + } + + pix_x = vid.width / vid_conwidth.value; + pix_y = vid.height / vid_conheight.value; + + if (fontmap) + width_of = fontmap->width_of; + else + width_of = fnt->width_of; + + for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--) + { + prevch = 0; + text = text_start; + + if (!outcolor || *outcolor == -1) + colorindex = STRING_COLOR_DEFAULT; + else + colorindex = *outcolor; + + DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); + + x = startx; + y = starty; + /* + if (shadow) + { + x += r_textshadow.value * vid.width / vid_conwidth.value; + y += r_textshadow.value * vid.height / vid_conheight.value; + } + */ + for (i = 0;((bytes_left = maxlen - (text - text_start)) > 0) && *text;) + { + nextch = ch = u8_getnchar(text, &text, bytes_left); + i = text - text_start; + if (!ch) + break; + if (ch == ' ' && !fontmap) + { + x += width_of[(int) ' '] * dw; + continue; + } + if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen) + { + ch = *text; // colors are ascii, so no u8_ needed + if (ch <= '9' && ch >= '0') // ^[0-9] found + { + colorindex = ch - '0'; + DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); + ++text; + ++i; + continue; + } + else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found + { + // building colorindex... + ch = tolower(text[1]); + tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000 + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12; + else tempcolorindex = 0; + if (tempcolorindex) + { + ch = tolower(text[2]); + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8; + else tempcolorindex = 0; + if (tempcolorindex) + { + ch = tolower(text[3]); + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4; + else tempcolorindex = 0; + if (tempcolorindex) + { + colorindex = tempcolorindex | 0xf; + // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa) + //Con_Printf("^1colorindex:^7 %x\n", colorindex); + DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); + i+=4; + text+=4; + continue; + } + } + } + } + else if (ch == STRING_COLOR_TAG) + { + i++; + text++; + } + i--; + } + // get the backup + ch = nextch; + // using a value of -1 for the oldstyle map because NULL means uninitialized... + // this way we don't need to rebind fnt->tex for every old-style character + // E000..E0FF: emulate old-font characters (to still have smileys and such available) + if (shadow) + { + x += 1.0/pix_x * r_textshadow.value; + y += 1.0/pix_y * r_textshadow.value; + } + if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF)) + { + if (ch >= 0xE000) + ch -= 0xE000; + if (ch > 0xFF) + goto out; + if (fontmap) + { + if (map != ft2_oldstyle_map) + { + if (batchcount) + { + // switching from freetype to non-freetype rendering + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1, true); + map = ft2_oldstyle_map; + } + } + prevch = 0; + //num = (unsigned char) text[i]; + //thisw = fnt->width_of[num]; + thisw = fnt->width_of[ch]; + // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering + s = (ch & 15)*0.0625f + (0.5f / tw); + t = (ch >> 4)*0.0625f + (0.5f / th); + u = 0.0625f * thisw - (1.0f / tw); + v = 0.0625f - (1.0f / th); + ac[ 0] = DrawQ_Color[0];ac[ 1] = DrawQ_Color[1];ac[ 2] = DrawQ_Color[2];ac[ 3] = DrawQ_Color[3]; + ac[ 4] = DrawQ_Color[0];ac[ 5] = DrawQ_Color[1];ac[ 6] = DrawQ_Color[2];ac[ 7] = DrawQ_Color[3]; + ac[ 8] = DrawQ_Color[0];ac[ 9] = DrawQ_Color[1];ac[10] = DrawQ_Color[2];ac[11] = DrawQ_Color[3]; + ac[12] = DrawQ_Color[0];ac[13] = DrawQ_Color[1];ac[14] = DrawQ_Color[2];ac[15] = DrawQ_Color[3]; + at[ 0] = s ; at[ 1] = t ; + at[ 2] = s+u ; at[ 3] = t ; + at[ 4] = s+u ; at[ 5] = t+v ; + at[ 6] = s ; at[ 7] = t+v ; + av[ 0] = x ; av[ 1] = y ; av[ 2] = 10; + av[ 3] = x+dw*thisw ; av[ 4] = y ; av[ 5] = 10; + av[ 6] = x+dw*thisw ; av[ 7] = y+dh ; av[ 8] = 10; + av[ 9] = x ; av[10] = y+dh ; av[11] = 10; + ac += 16; + at += 8; + av += 12; + batchcount++; + if (batchcount >= QUADELEMENTS_MAXQUADS) + { + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + x += width_of[ch] * dw; + } else { + if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP) + { + // new charmap - need to render + if (batchcount) + { + // we need a different character map, render what we currently have: + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + // find the new map + map = FontMap_FindForChar(fontmap, ch); + if (!map) + { + if (!Font_LoadMapForIndex(ft2, map_index, ch, &map)) + { + shadow = -1; + break; + } + if (!map) + { + // this shouldn't happen + shadow = -1; + break; + } + } + R_SetupShader_Generic(map->pic->tex, NULL, GL_MODULATE, 1, true); + } + + mapch = ch - map->start; + thisw = map->glyphs[mapch].advance_x; + + //x += ftbase_x; + y += ftbase_y; + if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky)) + { + x += kx * dw; + y += ky * dh; + } + else + kx = ky = 0; + ac[ 0] = DrawQ_Color[0]; ac[ 1] = DrawQ_Color[1]; ac[ 2] = DrawQ_Color[2]; ac[ 3] = DrawQ_Color[3]; + ac[ 4] = DrawQ_Color[0]; ac[ 5] = DrawQ_Color[1]; ac[ 6] = DrawQ_Color[2]; ac[ 7] = DrawQ_Color[3]; + ac[ 8] = DrawQ_Color[0]; ac[ 9] = DrawQ_Color[1]; ac[10] = DrawQ_Color[2]; ac[11] = DrawQ_Color[3]; + ac[12] = DrawQ_Color[0]; ac[13] = DrawQ_Color[1]; ac[14] = DrawQ_Color[2]; ac[15] = DrawQ_Color[3]; + at[0] = map->glyphs[mapch].txmin; at[1] = map->glyphs[mapch].tymin; + at[2] = map->glyphs[mapch].txmax; at[3] = map->glyphs[mapch].tymin; + at[4] = map->glyphs[mapch].txmax; at[5] = map->glyphs[mapch].tymax; + at[6] = map->glyphs[mapch].txmin; at[7] = map->glyphs[mapch].tymax; + av[ 0] = x + dw * map->glyphs[mapch].vxmin; av[ 1] = y + dh * map->glyphs[mapch].vymin; av[ 2] = 10; + av[ 3] = x + dw * map->glyphs[mapch].vxmax; av[ 4] = y + dh * map->glyphs[mapch].vymin; av[ 5] = 10; + av[ 6] = x + dw * map->glyphs[mapch].vxmax; av[ 7] = y + dh * map->glyphs[mapch].vymax; av[ 8] = 10; + av[ 9] = x + dw * map->glyphs[mapch].vxmin; av[10] = y + dh * map->glyphs[mapch].vymax; av[11] = 10; + //x -= ftbase_x; + y -= ftbase_y; + + x += thisw * dw; + ac += 16; + at += 8; + av += 12; + batchcount++; + if (batchcount >= QUADELEMENTS_MAXQUADS) + { + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + + //prevmap = map; + prevch = ch; + } +out: + if (shadow) + { + x -= 1.0/pix_x * r_textshadow.value; + y -= 1.0/pix_y * r_textshadow.value; + } + } + } + if (batchcount > 0) + { + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + } + + if (outcolor) + *outcolor = colorindex; + + // note: this relies on the proper text (not shadow) being drawn last + return x; +} + +float DrawQ_String(float startx, float starty, const char *text, size_t maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt); +} + +float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) +{ + return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth); +} + +float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000); +} + +float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth) +{ + return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth); +} + +#if 0 +// not used +// no ^xrgb management +static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qboolean ignorecolorcodes, int *outcolor) +{ + int color, numchars = 0; + char *outputend2c = output2c + maxoutchars - 2; + if (!outcolor || *outcolor == -1) + color = STRING_COLOR_DEFAULT; + else + color = *outcolor; + if (!maxreadchars) + maxreadchars = 1<<30; + textend = text + maxreadchars; + while (text != textend && *text) + { + if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend) + { + if (text[1] == STRING_COLOR_TAG) + text++; + else if (text[1] >= '0' && text[1] <= '9') + { + color = text[1] - '0'; + text += 2; + continue; + } + } + if (output2c >= outputend2c) + break; + *output2c++ = *text++; + *output2c++ = color; + numchars++; + } + output2c[0] = output2c[1] = 0; + if (outcolor) + *outcolor = color; + return numchars; +} +#endif + +void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags) +{ + float floats[36]; + + _DrawQ_SetupAndProcessDrawFlag(flags, pic, a1*a2*a3*a4); + if(!r_draw2d.integer && !r_draw2d_force) + return; + +// R_Mesh_ResetTextureState(); + if (pic) + { + if (width == 0) + width = pic->width; + if (height == 0) + height = pic->height; + R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, true); + } + else + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + + floats[2] = floats[5] = floats[8] = floats[11] = 0; + floats[0] = floats[9] = x; + floats[1] = floats[4] = y; + floats[3] = floats[6] = x + width; + floats[7] = floats[10] = y + height; + floats[12] = s1;floats[13] = t1; + floats[14] = s2;floats[15] = t2; + floats[16] = s4;floats[17] = t4; + floats[18] = s3;floats[19] = t3; + floats[20] = r1;floats[21] = g1;floats[22] = b1;floats[23] = a1; + floats[24] = r2;floats[25] = g2;floats[26] = b2;floats[27] = a2; + floats[28] = r4;floats[29] = g4;floats[30] = b4;floats[31] = a4; + floats[32] = r3;floats[33] = g3;floats[34] = b3;floats[35] = a3; + + R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +void DrawQ_Mesh (drawqueuemesh_t *mesh, int flags, qboolean hasalpha) +{ + _DrawQ_Setup(); + CHECKGLERROR + if(!r_draw2d.integer && !r_draw2d_force) + return; + DrawQ_ProcessDrawFlag(flags, hasalpha); + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(mesh->texture, NULL, GL_MODULATE, 1, true); + + R_Mesh_PrepareVertices_Generic_Arrays(mesh->num_vertices, mesh->data_vertex3f, mesh->data_color4f, mesh->data_texcoord2f); + R_Mesh_Draw(0, mesh->num_vertices, 0, mesh->num_triangles, mesh->data_element3i, NULL, 0, mesh->data_element3s, NULL, 0); +} + +void DrawQ_LineLoop (drawqueuemesh_t *mesh, int flags) +{ + int num; + + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, 1); + if(!r_draw2d.integer && !r_draw2d_force) + return; + + GL_Color(1,1,1,1); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + CHECKGLERROR + qglBegin(GL_LINE_LOOP); + for (num = 0;num < mesh->num_vertices;num++) + { + if (mesh->data_color4f) + GL_Color(mesh->data_color4f[num*4+0], mesh->data_color4f[num*4+1], mesh->data_color4f[num*4+2], mesh->data_color4f[num*4+3]); + qglVertex2f(mesh->data_vertex3f[num*3+0], mesh->data_vertex3f[num*3+1]); + } + qglEnd(); + CHECKGLERROR + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + return; + } +} + +//[515]: this is old, delete +void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags) +{ + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, alpha); + if(!r_draw2d.integer && !r_draw2d_force) + return; + + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + CHECKGLERROR + + //qglLineWidth(width);CHECKGLERROR + + GL_Color(r,g,b,alpha); + CHECKGLERROR + qglBegin(GL_LINES); + qglVertex2f(x1, y1); + qglVertex2f(x2, y2); + qglEnd(); + CHECKGLERROR + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + return; + } +} + +void DrawQ_Lines (float width, int numlines, const float *vertex3f, const float *color4f, int flags) +{ + int i; + qboolean hasalpha = false; + for (i = 0;i < numlines*2;i++) + if (color4f[i*4+3] < 1.0f) + hasalpha = true; + + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, hasalpha ? 0.5f : 1.0f); + + if(!r_draw2d.integer && !r_draw2d_force) + return; + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + CHECKGLERROR + + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + + //qglLineWidth(width);CHECKGLERROR + + CHECKGLERROR + R_Mesh_PrepareVertices_Generic_Arrays(numlines*2, vertex3f, color4f, NULL); + qglDrawArrays(GL_LINES, 0, numlines*2); + CHECKGLERROR + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + return; + } +} + +void DrawQ_SetClipArea(float x, float y, float width, float height) +{ + int ix, iy, iw, ih; + _DrawQ_Setup(); + + // We have to convert the con coords into real coords + // OGL uses top to bottom + ix = (int)(0.5 + x * ((float)vid.width / vid_conwidth.integer)); + iy = (int)(0.5 + y * ((float) vid.height / vid_conheight.integer)); + iw = (int)(0.5 + (x+width) * ((float)vid.width / vid_conwidth.integer)) - ix; + ih = (int)(0.5 + (y+height) * ((float) vid.height / vid_conheight.integer)) - iy; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_SOFT: + GL_Scissor(ix, vid.height - iy - ih, iw, ih); + break; + case RENDERPATH_D3D9: + GL_Scissor(ix, iy, iw, ih); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + + GL_ScissorTest(true); +} + +void DrawQ_ResetClipArea(void) +{ + _DrawQ_Setup(); + GL_ScissorTest(false); +} + +void DrawQ_Finish(void) +{ + r_refdef.draw2dstage = 0; +} + +void DrawQ_RecalcView(void) +{ + if(r_refdef.draw2dstage) + r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again +} + +static float blendvertex3f[9] = {-5000, -5000, 10, 10000, -5000, 10, -5000, 10000, 10}; +void R_DrawGamma(void) +{ + float c[4]; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_GLES2: + if (vid_usinghwgamma || v_glslgamma.integer) + return; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + if (vid_usinghwgamma) + return; + break; + case RENDERPATH_GLES1: + case RENDERPATH_SOFT: + return; + } + // all the blends ignore depth +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + GL_DepthMask(true); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(false); + + // interpretation of brightness and contrast: + // color range := brightness .. (brightness + contrast) + // i.e. "c *= contrast; c += brightness" + // plausible values for brightness thus range from -contrast to 1 + + // apply pre-brightness (subtractive brightness, for where contrast was >= 1) + if (vid.support.ext_blend_subtract) + { + if (v_color_enable.integer) + { + c[0] = -v_color_black_r.value / v_color_white_r.value; + c[1] = -v_color_black_g.value / v_color_white_g.value; + c[2] = -v_color_black_b.value / v_color_white_b.value; + } + else + c[0] = c[1] = c[2] = -v_brightness.value / v_contrast.value; + if (c[0] >= 0.01f || c[1] >= 0.01f || c[2] >= 0.01f) + { + // need SUBTRACTIVE blending to do this! + GL_BlendEquationSubtract(true); + GL_BlendFunc(GL_ONE, GL_ONE); + GL_Color(c[0], c[1], c[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + GL_BlendEquationSubtract(false); + } + } + + // apply contrast + if (v_color_enable.integer) + { + c[0] = v_color_white_r.value; + c[1] = v_color_white_g.value; + c[2] = v_color_white_b.value; + } + else + c[0] = c[1] = c[2] = v_contrast.value; + if (c[0] >= 1.003f || c[1] >= 1.003f || c[2] >= 1.003f) + { + GL_BlendFunc(GL_DST_COLOR, GL_ONE); + while (c[0] >= 1.003f || c[1] >= 1.003f || c[2] >= 1.003f) + { + float cc[4]; + cc[0] = bound(0, c[0] - 1, 1); + cc[1] = bound(0, c[1] - 1, 1); + cc[2] = bound(0, c[2] - 1, 1); + GL_Color(cc[0], cc[1], cc[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + c[0] /= 1 + cc[0]; + c[1] /= 1 + cc[1]; + c[2] /= 1 + cc[2]; + } + } + if (c[0] <= 0.997f || c[1] <= 0.997f || c[2] <= 0.997f) + { + GL_BlendFunc(GL_DST_COLOR, GL_ZERO); + GL_Color(c[0], c[1], c[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } + + // apply post-brightness (additive brightness, for where contrast was <= 1) + if (v_color_enable.integer) + { + c[0] = v_color_black_r.value; + c[1] = v_color_black_g.value; + c[2] = v_color_black_b.value; + } + else + c[0] = c[1] = c[2] = v_brightness.value; + if (c[0] >= 0.01f || c[1] >= 0.01f || c[2] >= 0.01f) + { + GL_BlendFunc(GL_ONE, GL_ONE); + GL_Color(c[0], c[1], c[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } +} + diff --git a/misc/source/darkplaces-src/gl_rmain.c b/misc/source/darkplaces-src/gl_rmain.c new file mode 100644 index 00000000..04ad617f --- /dev/null +++ b/misc/source/darkplaces-src/gl_rmain.c @@ -0,0 +1,11717 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// r_main.c + +#include "quakedef.h" +#include "cl_dyntexture.h" +#include "r_shadow.h" +#include "polygon.h" +#include "image.h" +#include "ft2.h" +#include "csprogs.h" +#include "cl_video.h" +#include "dpsoftrast.h" + +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +#endif + +mempool_t *r_main_mempool; +rtexturepool_t *r_main_texturepool; + +static int r_textureframe = 0; ///< used only by R_GetCurrentTexture + +static qboolean r_loadnormalmap; +static qboolean r_loadgloss; +qboolean r_loadfog; +static qboolean r_loaddds; +static qboolean r_savedds; + +// +// screen size info +// +r_refdef_t r_refdef; + +cvar_t r_motionblur = {CVAR_SAVE, "r_motionblur", "0", "motionblur value scale - 0.5 recommended"}; +cvar_t r_damageblur = {CVAR_SAVE, "r_damageblur", "0", "motionblur based on damage"}; +cvar_t r_motionblur_vmin = {CVAR_SAVE, "r_motionblur_vmin", "300", "minimum influence from velocity"}; +cvar_t r_motionblur_vmax = {CVAR_SAVE, "r_motionblur_vmax", "600", "maximum influence from velocity"}; +cvar_t r_motionblur_bmin = {CVAR_SAVE, "r_motionblur_bmin", "0.5", "velocity at which there is no blur yet (may be negative to always have some blur)"}; +cvar_t r_motionblur_vcoeff = {CVAR_SAVE, "r_motionblur_vcoeff", "0.05", "sliding average reaction time for velocity"}; +cvar_t r_motionblur_maxblur = {CVAR_SAVE, "r_motionblur_maxblur", "0.88", "cap for motionblur alpha value"}; +cvar_t r_motionblur_randomize = {CVAR_SAVE, "r_motionblur_randomize", "0.1", "randomizing coefficient to workaround ghosting"}; + +// TODO do we want a r_equalize_entities cvar that works on all ents, or would that be a cheat? +cvar_t r_equalize_entities_fullbright = {CVAR_SAVE, "r_equalize_entities_fullbright", "0", "render fullbright entities by equalizing their lightness, not by not rendering light"}; +cvar_t r_equalize_entities_minambient = {CVAR_SAVE, "r_equalize_entities_minambient", "0.5", "light equalizing: ensure at least this ambient/diffuse ratio"}; +cvar_t r_equalize_entities_by = {CVAR_SAVE, "r_equalize_entities_by", "0.7", "light equalizing: exponent of dynamics compression (0 = no compression, 1 = full compression)"}; +cvar_t r_equalize_entities_to = {CVAR_SAVE, "r_equalize_entities_to", "0.8", "light equalizing: target light level"}; + +cvar_t r_depthfirst = {CVAR_SAVE, "r_depthfirst", "0", "renders a depth-only version of the scene before normal rendering begins to eliminate overdraw, values: 0 = off, 1 = world depth, 2 = world and model depth"}; +cvar_t r_useinfinitefarclip = {CVAR_SAVE, "r_useinfinitefarclip", "1", "enables use of a special kind of projection matrix that has an extremely large farclip"}; +cvar_t r_farclip_base = {0, "r_farclip_base", "65536", "farclip (furthest visible distance) for rendering when r_useinfinitefarclip is 0"}; +cvar_t r_farclip_world = {0, "r_farclip_world", "2", "adds map size to farclip multiplied by this value"}; +cvar_t r_nearclip = {0, "r_nearclip", "1", "distance from camera of nearclip plane" }; +cvar_t r_deformvertexes = {0, "r_deformvertexes", "1", "allows use of deformvertexes in shader files (can be turned off to check performance impact)"}; +cvar_t r_transparent = {0, "r_transparent", "1", "allows use of transparent surfaces (can be turned off to check performance impact)"}; +cvar_t r_transparent_alphatocoverage = {0, "r_transparent_alphatocoverage", "1", "enables GL_ALPHA_TO_COVERAGE antialiasing technique on alphablend and alphatest surfaces when using vid_samples 2 or higher"}; +cvar_t r_showoverdraw = {0, "r_showoverdraw", "0", "shows overlapping geometry"}; +cvar_t r_showbboxes = {0, "r_showbboxes", "0", "shows bounding boxes of server entities, value controls opacity scaling (1 = 10%, 10 = 100%)"}; +cvar_t r_showsurfaces = {0, "r_showsurfaces", "0", "1 shows surfaces as different colors, or a value of 2 shows triangle draw order (for analyzing whether meshes are optimized for vertex cache)"}; +cvar_t r_showtris = {0, "r_showtris", "0", "shows triangle outlines, value controls brightness (can be above 1)"}; +cvar_t r_shownormals = {0, "r_shownormals", "0", "shows per-vertex surface normals and tangent vectors for bumpmapped lighting"}; +cvar_t r_showlighting = {0, "r_showlighting", "0", "shows areas lit by lights, useful for finding out why some areas of a map render slowly (bright orange = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful"}; +cvar_t r_showshadowvolumes = {0, "r_showshadowvolumes", "0", "shows areas shadowed by lights, useful for finding out why some areas of a map render slowly (bright blue = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful"}; +cvar_t r_showcollisionbrushes = {0, "r_showcollisionbrushes", "0", "draws collision brushes in quake3 maps (mode 1), mode 2 disables rendering of world (trippy!)"}; +cvar_t r_showcollisionbrushes_polygonfactor = {0, "r_showcollisionbrushes_polygonfactor", "-1", "expands outward the brush polygons a little bit, used to make collision brushes appear infront of walls"}; +cvar_t r_showcollisionbrushes_polygonoffset = {0, "r_showcollisionbrushes_polygonoffset", "0", "nudges brush polygon depth in hardware depth units, used to make collision brushes appear infront of walls"}; +cvar_t r_showdisabledepthtest = {0, "r_showdisabledepthtest", "0", "disables depth testing on r_show* cvars, allowing you to see what hidden geometry the graphics card is processing"}; +cvar_t r_drawportals = {0, "r_drawportals", "0", "shows portals (separating polygons) in world interior in quake1 maps"}; +cvar_t r_drawentities = {0, "r_drawentities","1", "draw entities (doors, players, projectiles, etc)"}; +cvar_t r_draw2d = {0, "r_draw2d","1", "draw 2D stuff (dangerous to turn off)"}; +cvar_t r_drawworld = {0, "r_drawworld","1", "draw world (most static stuff)"}; +cvar_t r_drawviewmodel = {0, "r_drawviewmodel","1", "draw your weapon model"}; +cvar_t r_drawexteriormodel = {0, "r_drawexteriormodel","1", "draw your player model (e.g. in chase cam, reflections)"}; +cvar_t r_cullentities_trace = {0, "r_cullentities_trace", "1", "probabistically cull invisible entities"}; +cvar_t r_cullentities_trace_samples = {0, "r_cullentities_trace_samples", "2", "number of samples to test for entity culling (in addition to center sample)"}; +cvar_t r_cullentities_trace_tempentitysamples = {0, "r_cullentities_trace_tempentitysamples", "-1", "number of samples to test for entity culling of temp entities (including all CSQC entities), -1 disables trace culling on these entities to prevent flicker (pvs still applies)"}; +cvar_t r_cullentities_trace_enlarge = {0, "r_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; +cvar_t r_cullentities_trace_delay = {0, "r_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; +cvar_t r_speeds = {0, "r_speeds","0", "displays rendering statistics and per-subsystem timings"}; +cvar_t r_fullbright = {0, "r_fullbright","0", "makes map very bright and renders faster"}; + +cvar_t r_fakelight = {0, "r_fakelight","0", "render 'fake' lighting instead of real lightmaps"}; +cvar_t r_fakelight_intensity = {0, "r_fakelight_intensity","0.75", "fakelight intensity modifier"}; +#define FAKELIGHT_ENABLED (r_fakelight.integer >= 2 || (r_fakelight.integer && r_refdef.scene.worldmodel && !r_refdef.scene.worldmodel->lit)) + +cvar_t r_wateralpha = {CVAR_SAVE, "r_wateralpha","1", "opacity of water polygons"}; +cvar_t r_dynamic = {CVAR_SAVE, "r_dynamic","1", "enables dynamic lights (rocket glow and such)"}; +cvar_t r_fullbrights = {CVAR_SAVE, "r_fullbrights", "1", "enables glowing pixels in quake textures (changes need r_restart to take effect)"}; +cvar_t r_shadows = {CVAR_SAVE, "r_shadows", "0", "casts fake stencil shadows from models onto the world (rtlights are unaffected by this); when set to 2, always cast the shadows in the direction set by r_shadows_throwdirection, otherwise use the model lighting."}; +cvar_t r_shadows_darken = {CVAR_SAVE, "r_shadows_darken", "0.5", "how much shadowed areas will be darkened"}; +cvar_t r_shadows_throwdistance = {CVAR_SAVE, "r_shadows_throwdistance", "500", "how far to cast shadows from models"}; +cvar_t r_shadows_throwdirection = {CVAR_SAVE, "r_shadows_throwdirection", "0 0 -1", "override throwing direction for r_shadows 2"}; +cvar_t r_shadows_drawafterrtlighting = {CVAR_SAVE, "r_shadows_drawafterrtlighting", "0", "draw fake shadows AFTER realtime lightning is drawn. May be useful for simulating fast sunlight on large outdoor maps with only one noshadow rtlight. The price is less realistic appearance of dynamic light shadows."}; +cvar_t r_shadows_castfrombmodels = {CVAR_SAVE, "r_shadows_castfrombmodels", "0", "do cast shadows from bmodels"}; +cvar_t r_shadows_focus = {CVAR_SAVE, "r_shadows_focus", "0 0 0", "offset the shadowed area focus"}; +cvar_t r_shadows_shadowmapscale = {CVAR_SAVE, "r_shadows_shadowmapscale", "1", "increases shadowmap quality (multiply global shadowmap precision) for fake shadows. Needs shadowmapping ON."}; +cvar_t r_q1bsp_skymasking = {0, "r_q1bsp_skymasking", "1", "allows sky polygons in quake1 maps to obscure other geometry"}; +cvar_t r_polygonoffset_submodel_factor = {0, "r_polygonoffset_submodel_factor", "0", "biases depth values of world submodels such as doors, to prevent z-fighting artifacts in Quake maps"}; +cvar_t r_polygonoffset_submodel_offset = {0, "r_polygonoffset_submodel_offset", "14", "biases depth values of world submodels such as doors, to prevent z-fighting artifacts in Quake maps"}; +cvar_t r_polygonoffset_decals_factor = {0, "r_polygonoffset_decals_factor", "0", "biases depth values of decals to prevent z-fighting artifacts"}; +cvar_t r_polygonoffset_decals_offset = {0, "r_polygonoffset_decals_offset", "-14", "biases depth values of decals to prevent z-fighting artifacts"}; +cvar_t r_fog_exp2 = {0, "r_fog_exp2", "0", "uses GL_EXP2 fog (as in Nehahra) rather than realistic GL_EXP fog"}; +cvar_t r_fog_clear = {0, "r_fog_clear", "1", "clears renderbuffer with fog color before render starts"}; +cvar_t r_drawfog = {CVAR_SAVE, "r_drawfog", "1", "allows one to disable fog rendering"}; +cvar_t r_transparentdepthmasking = {CVAR_SAVE, "r_transparentdepthmasking", "0", "enables depth writes on transparent meshes whose materially is normally opaque, this prevents seeing the inside of a transparent mesh"}; +cvar_t r_transparent_sortmaxdist = {CVAR_SAVE, "r_transparent_sortmaxdist", "32768", "upper distance limit for transparent sorting"}; +cvar_t r_transparent_sortarraysize = {CVAR_SAVE, "r_transparent_sortarraysize", "4096", "number of distance-sorting layers"}; + +cvar_t gl_fogenable = {0, "gl_fogenable", "0", "nehahra fog enable (for Nehahra compatibility only)"}; +cvar_t gl_fogdensity = {0, "gl_fogdensity", "0.25", "nehahra fog density (recommend values below 0.1) (for Nehahra compatibility only)"}; +cvar_t gl_fogred = {0, "gl_fogred","0.3", "nehahra fog color red value (for Nehahra compatibility only)"}; +cvar_t gl_foggreen = {0, "gl_foggreen","0.3", "nehahra fog color green value (for Nehahra compatibility only)"}; +cvar_t gl_fogblue = {0, "gl_fogblue","0.3", "nehahra fog color blue value (for Nehahra compatibility only)"}; +cvar_t gl_fogstart = {0, "gl_fogstart", "0", "nehahra fog start distance (for Nehahra compatibility only)"}; +cvar_t gl_fogend = {0, "gl_fogend","0", "nehahra fog end distance (for Nehahra compatibility only)"}; +cvar_t gl_skyclip = {0, "gl_skyclip", "4608", "nehahra farclip distance - the real fog end (for Nehahra compatibility only)"}; + +cvar_t r_texture_dds_load = {CVAR_SAVE, "r_texture_dds_load", "0", "load compressed dds/filename.dds texture instead of filename.tga, if the file exists (requires driver support)"}; +cvar_t r_texture_dds_save = {CVAR_SAVE, "r_texture_dds_save", "0", "save compressed dds/filename.dds texture when filename.tga is loaded, so that it can be loaded instead next time"}; + +cvar_t r_textureunits = {0, "r_textureunits", "32", "number of texture units to use in GL 1.1 and GL 1.3 rendering paths"}; +static cvar_t gl_combine = {CVAR_READONLY, "gl_combine", "1", "indicates whether the OpenGL 1.3 rendering path is active"}; +static cvar_t r_glsl = {CVAR_READONLY, "r_glsl", "1", "indicates whether the OpenGL 2.0 rendering path is active"}; + +cvar_t r_viewfbo = {CVAR_SAVE, "r_viewfbo", "0", "enables use of an 8bit (1) or 16bit (2) or 32bit (3) per component float framebuffer render, which may be at a different resolution than the video mode"}; +cvar_t r_viewscale = {CVAR_SAVE, "r_viewscale", "1", "scaling factor for resolution of the fbo rendering method, must be > 0, can be above 1 for a costly antialiasing behavior, typical values are 0.5 for 1/4th as many pixels rendered, or 1 for normal rendering"}; +cvar_t r_viewscale_fpsscaling = {CVAR_SAVE, "r_viewscale_fpsscaling", "0", "change resolution based on framerate"}; +cvar_t r_viewscale_fpsscaling_min = {CVAR_SAVE, "r_viewscale_fpsscaling_min", "0.0625", "worst acceptable quality"}; +cvar_t r_viewscale_fpsscaling_multiply = {CVAR_SAVE, "r_viewscale_fpsscaling_multiply", "5", "adjust quality up or down by the frametime difference from 1.0/target, multiplied by this factor"}; +cvar_t r_viewscale_fpsscaling_stepsize = {CVAR_SAVE, "r_viewscale_fpsscaling_stepsize", "0.01", "smallest adjustment to hit the target framerate (this value prevents minute oscillations)"}; +cvar_t r_viewscale_fpsscaling_stepmax = {CVAR_SAVE, "r_viewscale_fpsscaling_stepmax", "1.00", "largest adjustment to hit the target framerate (this value prevents wild overshooting of the estimate)"}; +cvar_t r_viewscale_fpsscaling_target = {CVAR_SAVE, "r_viewscale_fpsscaling_target", "70", "desired framerate"}; + +cvar_t r_glsl_deluxemapping = {CVAR_SAVE, "r_glsl_deluxemapping", "1", "use per pixel lighting on deluxemap-compiled q3bsp maps (or a value of 2 forces deluxemap shading even without deluxemaps)"}; +cvar_t r_glsl_offsetmapping = {CVAR_SAVE, "r_glsl_offsetmapping", "0", "offset mapping effect (also known as parallax mapping or virtual displacement mapping)"}; +cvar_t r_glsl_offsetmapping_steps = {CVAR_SAVE, "r_glsl_offsetmapping_steps", "2", "offset mapping steps (note: too high values may be not supported by your GPU)"}; +cvar_t r_glsl_offsetmapping_reliefmapping = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping", "0", "relief mapping effect (higher quality)"}; +cvar_t r_glsl_offsetmapping_reliefmapping_steps = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping_steps", "10", "relief mapping steps (note: too high values may be not supported by your GPU)"}; +cvar_t r_glsl_offsetmapping_reliefmapping_refinesteps = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping_refinesteps", "5", "relief mapping refine steps (these are a binary search executed as the last step as given by r_glsl_offsetmapping_reliefmapping_steps)"}; +cvar_t r_glsl_offsetmapping_scale = {CVAR_SAVE, "r_glsl_offsetmapping_scale", "0.04", "how deep the offset mapping effect is"}; +cvar_t r_glsl_postprocess = {CVAR_SAVE, "r_glsl_postprocess", "0", "use a GLSL postprocessing shader"}; +cvar_t r_glsl_postprocess_uservec1 = {CVAR_SAVE, "r_glsl_postprocess_uservec1", "0 0 0 0", "a 4-component vector to pass as uservec1 to the postprocessing shader (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec2 = {CVAR_SAVE, "r_glsl_postprocess_uservec2", "0 0 0 0", "a 4-component vector to pass as uservec2 to the postprocessing shader (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec3 = {CVAR_SAVE, "r_glsl_postprocess_uservec3", "0 0 0 0", "a 4-component vector to pass as uservec3 to the postprocessing shader (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec4 = {CVAR_SAVE, "r_glsl_postprocess_uservec4", "0 0 0 0", "a 4-component vector to pass as uservec4 to the postprocessing shader (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec1_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec1_enable", "1", "enables postprocessing uservec1 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec2_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec2_enable", "1", "enables postprocessing uservec2 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec3_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec3_enable", "1", "enables postprocessing uservec3 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec4_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec4_enable", "1", "enables postprocessing uservec4 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; + +cvar_t r_water = {CVAR_SAVE, "r_water", "0", "whether to use reflections and refraction on water surfaces (note: r_wateralpha must be set below 1)"}; +cvar_t r_water_clippingplanebias = {CVAR_SAVE, "r_water_clippingplanebias", "1", "a rather technical setting which avoids black pixels around water edges"}; +cvar_t r_water_resolutionmultiplier = {CVAR_SAVE, "r_water_resolutionmultiplier", "0.5", "multiplier for screen resolution when rendering refracted/reflected scenes, 1 is full quality, lower values are faster"}; +cvar_t r_water_refractdistort = {CVAR_SAVE, "r_water_refractdistort", "0.01", "how much water refractions shimmer"}; +cvar_t r_water_reflectdistort = {CVAR_SAVE, "r_water_reflectdistort", "0.01", "how much water reflections shimmer"}; +cvar_t r_water_scissormode = {0, "r_water_scissormode", "3", "scissor (1) or cull (2) or both (3) water renders"}; +cvar_t r_water_lowquality = {0, "r_water_lowquality", "0", "special option to accelerate water rendering, 1 disables shadows and particles, 2 disables all dynamic lights"}; + +cvar_t r_lerpsprites = {CVAR_SAVE, "r_lerpsprites", "0", "enables animation smoothing on sprites"}; +cvar_t r_lerpmodels = {CVAR_SAVE, "r_lerpmodels", "1", "enables animation smoothing on models"}; +cvar_t r_lerplightstyles = {CVAR_SAVE, "r_lerplightstyles", "0", "enable animation smoothing on flickering lights"}; +cvar_t r_waterscroll = {CVAR_SAVE, "r_waterscroll", "1", "makes water scroll around, value controls how much"}; + +cvar_t r_bloom = {CVAR_SAVE, "r_bloom", "0", "enables bloom effect (makes bright pixels affect neighboring pixels)"}; +cvar_t r_bloom_colorscale = {CVAR_SAVE, "r_bloom_colorscale", "1", "how bright the glow is"}; +cvar_t r_bloom_brighten = {CVAR_SAVE, "r_bloom_brighten", "2", "how bright the glow is, after subtract/power"}; +cvar_t r_bloom_blur = {CVAR_SAVE, "r_bloom_blur", "4", "how large the glow is"}; +cvar_t r_bloom_resolution = {CVAR_SAVE, "r_bloom_resolution", "320", "what resolution to perform the bloom effect at (independent of screen resolution)"}; +cvar_t r_bloom_colorexponent = {CVAR_SAVE, "r_bloom_colorexponent", "1", "how exaggerated the glow is"}; +cvar_t r_bloom_colorsubtract = {CVAR_SAVE, "r_bloom_colorsubtract", "0.125", "reduces bloom colors by a certain amount"}; + +cvar_t r_hdr = {CVAR_SAVE, "r_hdr", "0", "enables High Dynamic Range bloom effect (higher quality version of r_bloom)"}; +cvar_t r_hdr_scenebrightness = {CVAR_SAVE, "r_hdr_scenebrightness", "1", "global rendering brightness"}; +cvar_t r_hdr_glowintensity = {CVAR_SAVE, "r_hdr_glowintensity", "1", "how bright light emitting textures should appear"}; +cvar_t r_hdr_range = {CVAR_SAVE, "r_hdr_range", "4", "how much dynamic range to render bloom with (equivalent to multiplying r_bloom_brighten by this value and dividing r_bloom_colorscale by this value)"}; +cvar_t r_hdr_irisadaptation = {CVAR_SAVE, "r_hdr_irisadaptation", "0", "adjust scene brightness according to light intensity at player location"}; +cvar_t r_hdr_irisadaptation_multiplier = {CVAR_SAVE, "r_hdr_irisadaptation_multiplier", "2", "brightness at which value will be 1.0"}; +cvar_t r_hdr_irisadaptation_minvalue = {CVAR_SAVE, "r_hdr_irisadaptation_minvalue", "0.5", "minimum value that can result from multiplier / brightness"}; +cvar_t r_hdr_irisadaptation_maxvalue = {CVAR_SAVE, "r_hdr_irisadaptation_maxvalue", "4", "maximum value that can result from multiplier / brightness"}; +cvar_t r_hdr_irisadaptation_value = {0, "r_hdr_irisadaptation_value", "1", "current value as scenebrightness multiplier, changes continuously when irisadaptation is active"}; +cvar_t r_hdr_irisadaptation_fade = {CVAR_SAVE, "r_hdr_irisadaptation_fade", "1", "fade rate at which value adjusts"}; + +cvar_t r_smoothnormals_areaweighting = {0, "r_smoothnormals_areaweighting", "1", "uses significantly faster (and supposedly higher quality) area-weighted vertex normals and tangent vectors rather than summing normalized triangle normals and tangents"}; + +cvar_t developer_texturelogging = {0, "developer_texturelogging", "0", "produces a textures.log file containing names of skins and map textures the engine tried to load"}; + +cvar_t gl_lightmaps = {0, "gl_lightmaps", "0", "draws only lightmaps, no texture (for level designers)"}; + +cvar_t r_test = {0, "r_test", "0", "internal development use only, leave it alone (usually does nothing anyway)"}; + +cvar_t r_glsl_saturation = {CVAR_SAVE, "r_glsl_saturation", "1", "saturation multiplier (only working in glsl!)"}; +cvar_t r_glsl_saturation_redcompensate = {CVAR_SAVE, "r_glsl_saturation_redcompensate", "0", "a 'vampire sight' addition to desaturation effect, does compensation for red color, r_glsl_restart is required"}; + +cvar_t r_glsl_vertextextureblend_usebothalphas = {CVAR_SAVE, "r_glsl_vertextextureblend_usebothalphas", "0", "use both alpha layers on vertex blended surfaces, each alpha layer sets amount of 'blend leak' on another layer."}; + +cvar_t r_framedatasize = {CVAR_SAVE, "r_framedatasize", "0.5", "size of renderer data cache used during one frame (for skeletal animation caching, light processing, etc)"}; + +extern cvar_t v_glslgamma; + +extern qboolean v_flipped_state; + +static struct r_bloomstate_s +{ + qboolean enabled; + qboolean hdr; + + int bloomwidth, bloomheight; + + textype_t texturetype; + int viewfbo; // used to check if r_viewfbo cvar has changed + + int fbo_framebuffer; // non-zero if r_viewfbo is enabled and working + rtexture_t *texture_framebuffercolor; // non-NULL if fbo_screen is non-zero + rtexture_t *texture_framebufferdepth; // non-NULL if fbo_screen is non-zero + + int screentexturewidth, screentextureheight; + rtexture_t *texture_screen; /// \note also used for motion blur if enabled! + + int bloomtexturewidth, bloomtextureheight; + rtexture_t *texture_bloom; + + // arrays for rendering the screen passes + float screentexcoord2f[8]; + float bloomtexcoord2f[8]; + float offsettexcoord2f[8]; + + r_viewport_t viewport; +} +r_bloomstate; + +r_waterstate_t r_waterstate; + +/// shadow volume bsp struct with automatically growing nodes buffer +svbsp_t r_svbsp; + +rtexture_t *r_texture_blanknormalmap; +rtexture_t *r_texture_white; +rtexture_t *r_texture_grey128; +rtexture_t *r_texture_black; +rtexture_t *r_texture_notexture; +rtexture_t *r_texture_whitecube; +rtexture_t *r_texture_normalizationcube; +rtexture_t *r_texture_fogattenuation; +rtexture_t *r_texture_fogheighttexture; +rtexture_t *r_texture_gammaramps; +unsigned int r_texture_gammaramps_serial; +//rtexture_t *r_texture_fogintensity; +rtexture_t *r_texture_reflectcube; + +// TODO: hash lookups? +typedef struct cubemapinfo_s +{ + char basename[64]; + rtexture_t *texture; +} +cubemapinfo_t; + +int r_texture_numcubemaps; +cubemapinfo_t *r_texture_cubemaps[MAX_CUBEMAPS]; + +unsigned int r_queries[MAX_OCCLUSION_QUERIES]; +unsigned int r_numqueries; +unsigned int r_maxqueries; + +typedef struct r_qwskincache_s +{ + char name[MAX_QPATH]; + skinframe_t *skinframe; +} +r_qwskincache_t; + +static r_qwskincache_t *r_qwskincache; +static int r_qwskincache_size; + +/// vertex coordinates for a quad that covers the screen exactly +extern const float r_screenvertex3f[12]; +extern const float r_d3dscreenvertex3f[12]; +const float r_screenvertex3f[12] = +{ + 0, 0, 0, + 1, 0, 0, + 1, 1, 0, + 0, 1, 0 +}; +const float r_d3dscreenvertex3f[12] = +{ + 0, 1, 0, + 1, 1, 0, + 1, 0, 0, + 0, 0, 0 +}; + +void R_ModulateColors(float *in, float *out, int verts, float r, float g, float b) +{ + int i; + for (i = 0;i < verts;i++) + { + out[0] = in[0] * r; + out[1] = in[1] * g; + out[2] = in[2] * b; + out[3] = in[3]; + in += 4; + out += 4; + } +} + +void R_FillColors(float *out, int verts, float r, float g, float b, float a) +{ + int i; + for (i = 0;i < verts;i++) + { + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = a; + out += 4; + } +} + +// FIXME: move this to client? +void FOG_clear(void) +{ + if (gamemode == GAME_NEHAHRA) + { + Cvar_Set("gl_fogenable", "0"); + Cvar_Set("gl_fogdensity", "0.2"); + Cvar_Set("gl_fogred", "0.3"); + Cvar_Set("gl_foggreen", "0.3"); + Cvar_Set("gl_fogblue", "0.3"); + } + r_refdef.fog_density = 0; + r_refdef.fog_red = 0; + r_refdef.fog_green = 0; + r_refdef.fog_blue = 0; + r_refdef.fog_alpha = 1; + r_refdef.fog_start = 0; + r_refdef.fog_end = 16384; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; + memset(r_refdef.fog_height_texturename, 0, sizeof(r_refdef.fog_height_texturename)); +} + +static void R_BuildBlankTextures(void) +{ + unsigned char data[4]; + data[2] = 128; // normal X + data[1] = 128; // normal Y + data[0] = 255; // normal Z + data[3] = 128; // height + r_texture_blanknormalmap = R_LoadTexture2D(r_main_texturepool, "blankbump", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); + data[0] = 255; + data[1] = 255; + data[2] = 255; + data[3] = 255; + r_texture_white = R_LoadTexture2D(r_main_texturepool, "blankwhite", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); + data[0] = 128; + data[1] = 128; + data[2] = 128; + data[3] = 255; + r_texture_grey128 = R_LoadTexture2D(r_main_texturepool, "blankgrey128", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 255; + r_texture_black = R_LoadTexture2D(r_main_texturepool, "blankblack", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); +} + +static void R_BuildNoTexture(void) +{ + int x, y; + unsigned char pix[16][16][4]; + // this makes a light grey/dark grey checkerboard texture + for (y = 0;y < 16;y++) + { + for (x = 0;x < 16;x++) + { + if ((y < 8) ^ (x < 8)) + { + pix[y][x][0] = 128; + pix[y][x][1] = 128; + pix[y][x][2] = 128; + pix[y][x][3] = 255; + } + else + { + pix[y][x][0] = 64; + pix[y][x][1] = 64; + pix[y][x][2] = 64; + pix[y][x][3] = 255; + } + } + } + r_texture_notexture = R_LoadTexture2D(r_main_texturepool, "notexture", 16, 16, &pix[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_PERSISTENT, -1, NULL); +} + +static void R_BuildWhiteCube(void) +{ + unsigned char data[6*1*1*4]; + memset(data, 255, sizeof(data)); + r_texture_whitecube = R_LoadTextureCubeMap(r_main_texturepool, "whitecube", 1, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); +} + +static void R_BuildNormalizationCube(void) +{ + int x, y, side; + vec3_t v; + vec_t s, t, intensity; +#define NORMSIZE 64 + unsigned char *data; + data = (unsigned char *)Mem_Alloc(tempmempool, 6*NORMSIZE*NORMSIZE*4); + for (side = 0;side < 6;side++) + { + for (y = 0;y < NORMSIZE;y++) + { + for (x = 0;x < NORMSIZE;x++) + { + s = (x + 0.5f) * (2.0f / NORMSIZE) - 1.0f; + t = (y + 0.5f) * (2.0f / NORMSIZE) - 1.0f; + switch(side) + { + default: + case 0: + v[0] = 1; + v[1] = -t; + v[2] = -s; + break; + case 1: + v[0] = -1; + v[1] = -t; + v[2] = s; + break; + case 2: + v[0] = s; + v[1] = 1; + v[2] = t; + break; + case 3: + v[0] = s; + v[1] = -1; + v[2] = -t; + break; + case 4: + v[0] = s; + v[1] = -t; + v[2] = 1; + break; + case 5: + v[0] = -s; + v[1] = -t; + v[2] = -1; + break; + } + intensity = 127.0f / sqrt(DotProduct(v, v)); + data[((side*64+y)*64+x)*4+2] = (unsigned char)(128.0f + intensity * v[0]); + data[((side*64+y)*64+x)*4+1] = (unsigned char)(128.0f + intensity * v[1]); + data[((side*64+y)*64+x)*4+0] = (unsigned char)(128.0f + intensity * v[2]); + data[((side*64+y)*64+x)*4+3] = 255; + } + } + } + r_texture_normalizationcube = R_LoadTextureCubeMap(r_main_texturepool, "normalcube", NORMSIZE, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); + Mem_Free(data); +} + +static void R_BuildFogTexture(void) +{ + int x, b; +#define FOGWIDTH 256 + unsigned char data1[FOGWIDTH][4]; + //unsigned char data2[FOGWIDTH][4]; + double d, r, alpha; + + r_refdef.fogmasktable_start = r_refdef.fog_start; + r_refdef.fogmasktable_alpha = r_refdef.fog_alpha; + r_refdef.fogmasktable_range = r_refdef.fogrange; + r_refdef.fogmasktable_density = r_refdef.fog_density; + + r = r_refdef.fogmasktable_range / FOGMASKTABLEWIDTH; + for (x = 0;x < FOGMASKTABLEWIDTH;x++) + { + d = (x * r - r_refdef.fogmasktable_start); + if(developer_extra.integer) + Con_DPrintf("%f ", d); + d = max(0, d); + if (r_fog_exp2.integer) + alpha = exp(-r_refdef.fogmasktable_density * r_refdef.fogmasktable_density * 0.0001 * d * d); + else + alpha = exp(-r_refdef.fogmasktable_density * 0.004 * d); + if(developer_extra.integer) + Con_DPrintf(" : %f ", alpha); + alpha = 1 - (1 - alpha) * r_refdef.fogmasktable_alpha; + if(developer_extra.integer) + Con_DPrintf(" = %f\n", alpha); + r_refdef.fogmasktable[x] = bound(0, alpha, 1); + } + + for (x = 0;x < FOGWIDTH;x++) + { + b = (int)(r_refdef.fogmasktable[x * (FOGMASKTABLEWIDTH - 1) / (FOGWIDTH - 1)] * 255); + data1[x][0] = b; + data1[x][1] = b; + data1[x][2] = b; + data1[x][3] = 255; + //data2[x][0] = 255 - b; + //data2[x][1] = 255 - b; + //data2[x][2] = 255 - b; + //data2[x][3] = 255; + } + if (r_texture_fogattenuation) + { + R_UpdateTexture(r_texture_fogattenuation, &data1[0][0], 0, 0, 0, FOGWIDTH, 1, 1); + //R_UpdateTexture(r_texture_fogattenuation, &data2[0][0], 0, 0, 0, FOGWIDTH, 1, 1); + } + else + { + r_texture_fogattenuation = R_LoadTexture2D(r_main_texturepool, "fogattenuation", FOGWIDTH, 1, &data1[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); + //r_texture_fogintensity = R_LoadTexture2D(r_main_texturepool, "fogintensity", FOGWIDTH, 1, &data2[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP, NULL); + } +} + +static void R_BuildFogHeightTexture(void) +{ + unsigned char *inpixels; + int size; + int x; + int y; + int j; + float c[4]; + float f; + inpixels = NULL; + strlcpy(r_refdef.fogheighttexturename, r_refdef.fog_height_texturename, sizeof(r_refdef.fogheighttexturename)); + if (r_refdef.fogheighttexturename[0]) + inpixels = loadimagepixelsbgra(r_refdef.fogheighttexturename, true, false, false, NULL); + if (!inpixels) + { + r_refdef.fog_height_tablesize = 0; + if (r_texture_fogheighttexture) + R_FreeTexture(r_texture_fogheighttexture); + r_texture_fogheighttexture = NULL; + if (r_refdef.fog_height_table2d) + Mem_Free(r_refdef.fog_height_table2d); + r_refdef.fog_height_table2d = NULL; + if (r_refdef.fog_height_table1d) + Mem_Free(r_refdef.fog_height_table1d); + r_refdef.fog_height_table1d = NULL; + return; + } + size = image_width; + r_refdef.fog_height_tablesize = size; + r_refdef.fog_height_table1d = (unsigned char *)Mem_Alloc(r_main_mempool, size * 4); + r_refdef.fog_height_table2d = (unsigned char *)Mem_Alloc(r_main_mempool, size * size * 4); + memcpy(r_refdef.fog_height_table1d, inpixels, size * 4); + Mem_Free(inpixels); + // LordHavoc: now the magic - what is that table2d for? it is a cooked + // average fog color table accounting for every fog layer between a point + // and the camera. (Note: attenuation is handled separately!) + for (y = 0;y < size;y++) + { + for (x = 0;x < size;x++) + { + Vector4Clear(c); + f = 0; + if (x < y) + { + for (j = x;j <= y;j++) + { + Vector4Add(c, r_refdef.fog_height_table1d + j*4, c); + f++; + } + } + else + { + for (j = x;j >= y;j--) + { + Vector4Add(c, r_refdef.fog_height_table1d + j*4, c); + f++; + } + } + f = 1.0f / f; + r_refdef.fog_height_table2d[(y*size+x)*4+0] = (unsigned char)(c[0] * f); + r_refdef.fog_height_table2d[(y*size+x)*4+1] = (unsigned char)(c[1] * f); + r_refdef.fog_height_table2d[(y*size+x)*4+2] = (unsigned char)(c[2] * f); + r_refdef.fog_height_table2d[(y*size+x)*4+3] = (unsigned char)(c[3] * f); + } + } + r_texture_fogheighttexture = R_LoadTexture2D(r_main_texturepool, "fogheighttable", size, size, r_refdef.fog_height_table2d, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_CLAMP, -1, NULL); +} + +//======================================================================================================================================================= + +static const char *builtinshaderstring = +#include "shader_glsl.h" +; + +const char *builtinhlslshaderstring = +#include "shader_hlsl.h" +; + +char *glslshaderstring = NULL; +char *hlslshaderstring = NULL; + +//======================================================================================================================================================= + +typedef struct shaderpermutationinfo_s +{ + const char *pretext; + const char *name; +} +shaderpermutationinfo_t; + +typedef struct shadermodeinfo_s +{ + const char *vertexfilename; + const char *geometryfilename; + const char *fragmentfilename; + const char *pretext; + const char *name; +} +shadermodeinfo_t; + +// NOTE: MUST MATCH ORDER OF SHADERPERMUTATION_* DEFINES! +shaderpermutationinfo_t shaderpermutationinfo[SHADERPERMUTATION_COUNT] = +{ + {"#define USEDIFFUSE\n", " diffuse"}, + {"#define USEVERTEXTEXTUREBLEND\n", " vertextextureblend"}, + {"#define USEVIEWTINT\n", " viewtint"}, + {"#define USECOLORMAPPING\n", " colormapping"}, + {"#define USESATURATION\n", " saturation"}, + {"#define USEFOGINSIDE\n", " foginside"}, + {"#define USEFOGOUTSIDE\n", " fogoutside"}, + {"#define USEFOGHEIGHTTEXTURE\n", " fogheighttexture"}, + {"#define USEFOGALPHAHACK\n", " fogalphahack"}, + {"#define USEGAMMARAMPS\n", " gammaramps"}, + {"#define USECUBEFILTER\n", " cubefilter"}, + {"#define USEGLOW\n", " glow"}, + {"#define USEBLOOM\n", " bloom"}, + {"#define USESPECULAR\n", " specular"}, + {"#define USEPOSTPROCESSING\n", " postprocessing"}, + {"#define USEREFLECTION\n", " reflection"}, + {"#define USEOFFSETMAPPING\n", " offsetmapping"}, + {"#define USEOFFSETMAPPING_RELIEFMAPPING\n", " reliefmapping"}, + {"#define USESHADOWMAP2D\n", " shadowmap2d"}, + {"#define USESHADOWMAPPCF 1\n", " shadowmappcf"}, + {"#define USESHADOWMAPPCF 2\n", " shadowmappcf2"}, + {"#define USESHADOWSAMPLER\n", " shadowsampler"}, + {"#define USESHADOWMAPVSDCT\n", " shadowmapvsdct"}, + {"#define USESHADOWMAPORTHO\n", " shadowmaportho"}, + {"#define USEDEFERREDLIGHTMAP\n", " deferredlightmap"}, + {"#define USEALPHAKILL\n", " alphakill"}, + {"#define USEREFLECTCUBE\n", " reflectcube"}, + {"#define USENORMALMAPSCROLLBLEND\n", " normalmapscrollblend"}, + {"#define USEBOUNCEGRID\n", " bouncegrid"}, + {"#define USEBOUNCEGRIDDIRECTIONAL\n", " bouncegriddirectional"}, + {"#define USETRIPPY\n", " trippy"}, +}; + +// NOTE: MUST MATCH ORDER OF SHADERMODE_* ENUMS! +shadermodeinfo_t glslshadermodeinfo[SHADERMODE_COUNT] = +{ + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_GENERIC\n", " generic"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_POSTPROCESS\n", " postprocess"}, + {"glsl/default.glsl", NULL, NULL , "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_FLATCOLOR\n", " flatcolor"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTMAP\n", " lightmap"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_FAKELIGHT\n", " fakelight"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTSOURCE\n", " lightsource"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_REFRACTION\n", " refraction"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_WATER\n", " water"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_SHOWDEPTH\n", " showdepth"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"}, + {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"}, +}; + +shadermodeinfo_t hlslshadermodeinfo[SHADERMODE_COUNT] = +{ + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_GENERIC\n", " generic"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_POSTPROCESS\n", " postprocess"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_FLATCOLOR\n", " flatcolor"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTMAP\n", " lightmap"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_FAKELIGHT\n", " fakelight"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTSOURCE\n", " lightsource"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_REFRACTION\n", " refraction"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_WATER\n", " water"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_SHOWDEPTH\n", " showdepth"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"}, + {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"}, +}; + +struct r_glsl_permutation_s; +typedef struct r_glsl_permutation_s +{ + /// hash lookup data + struct r_glsl_permutation_s *hashnext; + unsigned int mode; + unsigned int permutation; + + /// indicates if we have tried compiling this permutation already + qboolean compiled; + /// 0 if compilation failed + int program; + // texture units assigned to each detected uniform + int tex_Texture_First; + int tex_Texture_Second; + int tex_Texture_GammaRamps; + int tex_Texture_Normal; + int tex_Texture_Color; + int tex_Texture_Gloss; + int tex_Texture_Glow; + int tex_Texture_SecondaryNormal; + int tex_Texture_SecondaryColor; + int tex_Texture_SecondaryGloss; + int tex_Texture_SecondaryGlow; + int tex_Texture_Pants; + int tex_Texture_Shirt; + int tex_Texture_FogHeightTexture; + int tex_Texture_FogMask; + int tex_Texture_Lightmap; + int tex_Texture_Deluxemap; + int tex_Texture_Attenuation; + int tex_Texture_Cube; + int tex_Texture_Refraction; + int tex_Texture_Reflection; + int tex_Texture_ShadowMap2D; + int tex_Texture_CubeProjection; + int tex_Texture_ScreenDepth; + int tex_Texture_ScreenNormalMap; + int tex_Texture_ScreenDiffuse; + int tex_Texture_ScreenSpecular; + int tex_Texture_ReflectMask; + int tex_Texture_ReflectCube; + int tex_Texture_BounceGrid; + /// locations of detected uniforms in program object, or -1 if not found + int loc_Texture_First; + int loc_Texture_Second; + int loc_Texture_GammaRamps; + int loc_Texture_Normal; + int loc_Texture_Color; + int loc_Texture_Gloss; + int loc_Texture_Glow; + int loc_Texture_SecondaryNormal; + int loc_Texture_SecondaryColor; + int loc_Texture_SecondaryGloss; + int loc_Texture_SecondaryGlow; + int loc_Texture_Pants; + int loc_Texture_Shirt; + int loc_Texture_FogHeightTexture; + int loc_Texture_FogMask; + int loc_Texture_Lightmap; + int loc_Texture_Deluxemap; + int loc_Texture_Attenuation; + int loc_Texture_Cube; + int loc_Texture_Refraction; + int loc_Texture_Reflection; + int loc_Texture_ShadowMap2D; + int loc_Texture_CubeProjection; + int loc_Texture_ScreenDepth; + int loc_Texture_ScreenNormalMap; + int loc_Texture_ScreenDiffuse; + int loc_Texture_ScreenSpecular; + int loc_Texture_ReflectMask; + int loc_Texture_ReflectCube; + int loc_Texture_BounceGrid; + int loc_Alpha; + int loc_BloomBlur_Parameters; + int loc_ClientTime; + int loc_Color_Ambient; + int loc_Color_Diffuse; + int loc_Color_Specular; + int loc_Color_Glow; + int loc_Color_Pants; + int loc_Color_Shirt; + int loc_DeferredColor_Ambient; + int loc_DeferredColor_Diffuse; + int loc_DeferredColor_Specular; + int loc_DeferredMod_Diffuse; + int loc_DeferredMod_Specular; + int loc_DistortScaleRefractReflect; + int loc_EyePosition; + int loc_FogColor; + int loc_FogHeightFade; + int loc_FogPlane; + int loc_FogPlaneViewDist; + int loc_FogRangeRecip; + int loc_LightColor; + int loc_LightDir; + int loc_LightPosition; + int loc_OffsetMapping_ScaleSteps; + int loc_PixelSize; + int loc_ReflectColor; + int loc_ReflectFactor; + int loc_ReflectOffset; + int loc_RefractColor; + int loc_Saturation; + int loc_ScreenCenterRefractReflect; + int loc_ScreenScaleRefractReflect; + int loc_ScreenToDepth; + int loc_ShadowMap_Parameters; + int loc_ShadowMap_TextureScale; + int loc_SpecularPower; + int loc_UserVec1; + int loc_UserVec2; + int loc_UserVec3; + int loc_UserVec4; + int loc_ViewTintColor; + int loc_ViewToLight; + int loc_ModelToLight; + int loc_TexMatrix; + int loc_BackgroundTexMatrix; + int loc_ModelViewProjectionMatrix; + int loc_ModelViewMatrix; + int loc_PixelToScreenTexCoord; + int loc_ModelToReflectCube; + int loc_ShadowMapMatrix; + int loc_BloomColorSubtract; + int loc_NormalmapScrollBlend; + int loc_BounceGridMatrix; + int loc_BounceGridIntensity; +} +r_glsl_permutation_t; + +#define SHADERPERMUTATION_HASHSIZE 256 + + +// non-degradable "lightweight" shader parameters to keep the permutations simpler +// these can NOT degrade! only use for simple stuff +enum +{ + SHADERSTATICPARM_SATURATION_REDCOMPENSATE = 0, ///< red compensation filter for saturation + SHADERSTATICPARM_EXACTSPECULARMATH = 1, ///< (lightsource or deluxemapping) use exact reflection map for specular effects, as opposed to the usual OpenGL approximation + SHADERSTATICPARM_POSTPROCESS_USERVEC1 = 2, ///< postprocess uservec1 is enabled + SHADERSTATICPARM_POSTPROCESS_USERVEC2 = 3, ///< postprocess uservec2 is enabled + SHADERSTATICPARM_POSTPROCESS_USERVEC3 = 4, ///< postprocess uservec3 is enabled + SHADERSTATICPARM_POSTPROCESS_USERVEC4 = 5, ///< postprocess uservec4 is enabled + SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS = 6 // use both alpha layers while blending materials, allows more advanced microblending +}; +#define SHADERSTATICPARMS_COUNT 7 + +static const char *shaderstaticparmstrings_list[SHADERSTATICPARMS_COUNT]; +static int shaderstaticparms_count = 0; + +static unsigned int r_compileshader_staticparms[(SHADERSTATICPARMS_COUNT + 0x1F) >> 5] = {0}; +#define R_COMPILESHADER_STATICPARM_ENABLE(p) r_compileshader_staticparms[(p) >> 5] |= (1 << ((p) & 0x1F)) +qboolean R_CompileShader_CheckStaticParms(void) +{ + static int r_compileshader_staticparms_save[1]; + memcpy(r_compileshader_staticparms_save, r_compileshader_staticparms, sizeof(r_compileshader_staticparms)); + memset(r_compileshader_staticparms, 0, sizeof(r_compileshader_staticparms)); + + // detect all + if (r_glsl_saturation_redcompensate.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SATURATION_REDCOMPENSATE); + if (r_glsl_vertextextureblend_usebothalphas.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS); + if (r_shadow_glossexact.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_EXACTSPECULARMATH); + if (r_glsl_postprocess.integer) + { + if (r_glsl_postprocess_uservec1_enable.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC1); + if (r_glsl_postprocess_uservec2_enable.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC2); + if (r_glsl_postprocess_uservec3_enable.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC3); + if (r_glsl_postprocess_uservec4_enable.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC4); + } + return memcmp(r_compileshader_staticparms, r_compileshader_staticparms_save, sizeof(r_compileshader_staticparms)) != 0; +} + +#define R_COMPILESHADER_STATICPARM_EMIT(p, n) \ + if(r_compileshader_staticparms[(p) >> 5] & (1 << ((p) & 0x1F))) \ + shaderstaticparmstrings_list[shaderstaticparms_count++] = "#define " n "\n"; \ + else \ + shaderstaticparmstrings_list[shaderstaticparms_count++] = "\n" +void R_CompileShader_AddStaticParms(unsigned int mode, unsigned int permutation) +{ + shaderstaticparms_count = 0; + + // emit all + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SATURATION_REDCOMPENSATE, "SATURATION_REDCOMPENSATE"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_EXACTSPECULARMATH, "USEEXACTSPECULARMATH"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC1, "USERVEC1"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC2, "USERVEC2"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC3, "USERVEC3"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC4, "USERVEC4"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS, "USEBOTHALPHAS"); +} + +/// information about each possible shader permutation +r_glsl_permutation_t *r_glsl_permutationhash[SHADERMODE_COUNT][SHADERPERMUTATION_HASHSIZE]; +/// currently selected permutation +r_glsl_permutation_t *r_glsl_permutation; +/// storage for permutations linked in the hash table +memexpandablearray_t r_glsl_permutationarray; + +static r_glsl_permutation_t *R_GLSL_FindPermutation(unsigned int mode, unsigned int permutation) +{ + //unsigned int hashdepth = 0; + unsigned int hashindex = (permutation * 0x1021) & (SHADERPERMUTATION_HASHSIZE - 1); + r_glsl_permutation_t *p; + for (p = r_glsl_permutationhash[mode][hashindex];p;p = p->hashnext) + { + if (p->mode == mode && p->permutation == permutation) + { + //if (hashdepth > 10) + // Con_Printf("R_GLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); + return p; + } + //hashdepth++; + } + p = (r_glsl_permutation_t*)Mem_ExpandableArray_AllocRecord(&r_glsl_permutationarray); + p->mode = mode; + p->permutation = permutation; + p->hashnext = r_glsl_permutationhash[mode][hashindex]; + r_glsl_permutationhash[mode][hashindex] = p; + //if (hashdepth > 10) + // Con_Printf("R_GLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); + return p; +} + +static char *R_GLSL_GetText(const char *filename, qboolean printfromdisknotice) +{ + char *shaderstring; + if (!filename || !filename[0]) + return NULL; + if (!strcmp(filename, "glsl/default.glsl")) + { + if (!glslshaderstring) + { + glslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); + if (glslshaderstring) + Con_DPrintf("Loading shaders from file %s...\n", filename); + else + glslshaderstring = (char *)builtinshaderstring; + } + shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(glslshaderstring) + 1); + memcpy(shaderstring, glslshaderstring, strlen(glslshaderstring) + 1); + return shaderstring; + } + shaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); + if (shaderstring) + { + if (printfromdisknotice) + Con_DPrintf("from disk %s... ", filename); + return shaderstring; + } + return shaderstring; +} + +static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode, unsigned int permutation) +{ + int i; + int sampler; + shadermodeinfo_t *modeinfo = glslshadermodeinfo + mode; + char *vertexstring, *geometrystring, *fragmentstring; + char permutationname[256]; + int vertstrings_count = 0; + int geomstrings_count = 0; + int fragstrings_count = 0; + const char *vertstrings_list[32+3+SHADERSTATICPARMS_COUNT+1]; + const char *geomstrings_list[32+3+SHADERSTATICPARMS_COUNT+1]; + const char *fragstrings_list[32+3+SHADERSTATICPARMS_COUNT+1]; + + if (p->compiled) + return; + p->compiled = true; + p->program = 0; + + permutationname[0] = 0; + vertexstring = R_GLSL_GetText(modeinfo->vertexfilename, true); + geometrystring = R_GLSL_GetText(modeinfo->geometryfilename, false); + fragmentstring = R_GLSL_GetText(modeinfo->fragmentfilename, false); + + strlcat(permutationname, modeinfo->vertexfilename, sizeof(permutationname)); + + // if we can do #version 130, we should (this improves quality of offset/reliefmapping thanks to textureGrad) + if(vid.support.gl20shaders130) + { + vertstrings_list[vertstrings_count++] = "#version 130\n"; + geomstrings_list[geomstrings_count++] = "#version 130\n"; + fragstrings_list[fragstrings_count++] = "#version 130\n"; + vertstrings_list[vertstrings_count++] = "#define GLSL130\n"; + geomstrings_list[geomstrings_count++] = "#define GLSL130\n"; + fragstrings_list[fragstrings_count++] = "#define GLSL130\n"; + } + + // the first pretext is which type of shader to compile as + // (later these will all be bound together as a program object) + vertstrings_list[vertstrings_count++] = "#define VERTEX_SHADER\n"; + geomstrings_list[geomstrings_count++] = "#define GEOMETRY_SHADER\n"; + fragstrings_list[fragstrings_count++] = "#define FRAGMENT_SHADER\n"; + + // the second pretext is the mode (for example a light source) + vertstrings_list[vertstrings_count++] = modeinfo->pretext; + geomstrings_list[geomstrings_count++] = modeinfo->pretext; + fragstrings_list[fragstrings_count++] = modeinfo->pretext; + strlcat(permutationname, modeinfo->name, sizeof(permutationname)); + + // now add all the permutation pretexts + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + { + if (permutation & (1<program = GL_Backend_CompileProgram(vertstrings_count, vertstrings_list, geomstrings_count, geomstrings_list, fragstrings_count, fragstrings_list); + if (p->program) + { + CHECKGLERROR + qglUseProgram(p->program);CHECKGLERROR + // look up all the uniform variable names we care about, so we don't + // have to look them up every time we set them + + p->loc_Texture_First = qglGetUniformLocation(p->program, "Texture_First"); + p->loc_Texture_Second = qglGetUniformLocation(p->program, "Texture_Second"); + p->loc_Texture_GammaRamps = qglGetUniformLocation(p->program, "Texture_GammaRamps"); + p->loc_Texture_Normal = qglGetUniformLocation(p->program, "Texture_Normal"); + p->loc_Texture_Color = qglGetUniformLocation(p->program, "Texture_Color"); + p->loc_Texture_Gloss = qglGetUniformLocation(p->program, "Texture_Gloss"); + p->loc_Texture_Glow = qglGetUniformLocation(p->program, "Texture_Glow"); + p->loc_Texture_SecondaryNormal = qglGetUniformLocation(p->program, "Texture_SecondaryNormal"); + p->loc_Texture_SecondaryColor = qglGetUniformLocation(p->program, "Texture_SecondaryColor"); + p->loc_Texture_SecondaryGloss = qglGetUniformLocation(p->program, "Texture_SecondaryGloss"); + p->loc_Texture_SecondaryGlow = qglGetUniformLocation(p->program, "Texture_SecondaryGlow"); + p->loc_Texture_Pants = qglGetUniformLocation(p->program, "Texture_Pants"); + p->loc_Texture_Shirt = qglGetUniformLocation(p->program, "Texture_Shirt"); + p->loc_Texture_FogHeightTexture = qglGetUniformLocation(p->program, "Texture_FogHeightTexture"); + p->loc_Texture_FogMask = qglGetUniformLocation(p->program, "Texture_FogMask"); + p->loc_Texture_Lightmap = qglGetUniformLocation(p->program, "Texture_Lightmap"); + p->loc_Texture_Deluxemap = qglGetUniformLocation(p->program, "Texture_Deluxemap"); + p->loc_Texture_Attenuation = qglGetUniformLocation(p->program, "Texture_Attenuation"); + p->loc_Texture_Cube = qglGetUniformLocation(p->program, "Texture_Cube"); + p->loc_Texture_Refraction = qglGetUniformLocation(p->program, "Texture_Refraction"); + p->loc_Texture_Reflection = qglGetUniformLocation(p->program, "Texture_Reflection"); + p->loc_Texture_ShadowMap2D = qglGetUniformLocation(p->program, "Texture_ShadowMap2D"); + p->loc_Texture_CubeProjection = qglGetUniformLocation(p->program, "Texture_CubeProjection"); + p->loc_Texture_ScreenDepth = qglGetUniformLocation(p->program, "Texture_ScreenDepth"); + p->loc_Texture_ScreenNormalMap = qglGetUniformLocation(p->program, "Texture_ScreenNormalMap"); + p->loc_Texture_ScreenDiffuse = qglGetUniformLocation(p->program, "Texture_ScreenDiffuse"); + p->loc_Texture_ScreenSpecular = qglGetUniformLocation(p->program, "Texture_ScreenSpecular"); + p->loc_Texture_ReflectMask = qglGetUniformLocation(p->program, "Texture_ReflectMask"); + p->loc_Texture_ReflectCube = qglGetUniformLocation(p->program, "Texture_ReflectCube"); + p->loc_Texture_BounceGrid = qglGetUniformLocation(p->program, "Texture_BounceGrid"); + p->loc_Alpha = qglGetUniformLocation(p->program, "Alpha"); + p->loc_BloomBlur_Parameters = qglGetUniformLocation(p->program, "BloomBlur_Parameters"); + p->loc_ClientTime = qglGetUniformLocation(p->program, "ClientTime"); + p->loc_Color_Ambient = qglGetUniformLocation(p->program, "Color_Ambient"); + p->loc_Color_Diffuse = qglGetUniformLocation(p->program, "Color_Diffuse"); + p->loc_Color_Specular = qglGetUniformLocation(p->program, "Color_Specular"); + p->loc_Color_Glow = qglGetUniformLocation(p->program, "Color_Glow"); + p->loc_Color_Pants = qglGetUniformLocation(p->program, "Color_Pants"); + p->loc_Color_Shirt = qglGetUniformLocation(p->program, "Color_Shirt"); + p->loc_DeferredColor_Ambient = qglGetUniformLocation(p->program, "DeferredColor_Ambient"); + p->loc_DeferredColor_Diffuse = qglGetUniformLocation(p->program, "DeferredColor_Diffuse"); + p->loc_DeferredColor_Specular = qglGetUniformLocation(p->program, "DeferredColor_Specular"); + p->loc_DeferredMod_Diffuse = qglGetUniformLocation(p->program, "DeferredMod_Diffuse"); + p->loc_DeferredMod_Specular = qglGetUniformLocation(p->program, "DeferredMod_Specular"); + p->loc_DistortScaleRefractReflect = qglGetUniformLocation(p->program, "DistortScaleRefractReflect"); + p->loc_EyePosition = qglGetUniformLocation(p->program, "EyePosition"); + p->loc_FogColor = qglGetUniformLocation(p->program, "FogColor"); + p->loc_FogHeightFade = qglGetUniformLocation(p->program, "FogHeightFade"); + p->loc_FogPlane = qglGetUniformLocation(p->program, "FogPlane"); + p->loc_FogPlaneViewDist = qglGetUniformLocation(p->program, "FogPlaneViewDist"); + p->loc_FogRangeRecip = qglGetUniformLocation(p->program, "FogRangeRecip"); + p->loc_LightColor = qglGetUniformLocation(p->program, "LightColor"); + p->loc_LightDir = qglGetUniformLocation(p->program, "LightDir"); + p->loc_LightPosition = qglGetUniformLocation(p->program, "LightPosition"); + p->loc_OffsetMapping_ScaleSteps = qglGetUniformLocation(p->program, "OffsetMapping_ScaleSteps"); + p->loc_PixelSize = qglGetUniformLocation(p->program, "PixelSize"); + p->loc_ReflectColor = qglGetUniformLocation(p->program, "ReflectColor"); + p->loc_ReflectFactor = qglGetUniformLocation(p->program, "ReflectFactor"); + p->loc_ReflectOffset = qglGetUniformLocation(p->program, "ReflectOffset"); + p->loc_RefractColor = qglGetUniformLocation(p->program, "RefractColor"); + p->loc_Saturation = qglGetUniformLocation(p->program, "Saturation"); + p->loc_ScreenCenterRefractReflect = qglGetUniformLocation(p->program, "ScreenCenterRefractReflect"); + p->loc_ScreenScaleRefractReflect = qglGetUniformLocation(p->program, "ScreenScaleRefractReflect"); + p->loc_ScreenToDepth = qglGetUniformLocation(p->program, "ScreenToDepth"); + p->loc_ShadowMap_Parameters = qglGetUniformLocation(p->program, "ShadowMap_Parameters"); + p->loc_ShadowMap_TextureScale = qglGetUniformLocation(p->program, "ShadowMap_TextureScale"); + p->loc_SpecularPower = qglGetUniformLocation(p->program, "SpecularPower"); + p->loc_UserVec1 = qglGetUniformLocation(p->program, "UserVec1"); + p->loc_UserVec2 = qglGetUniformLocation(p->program, "UserVec2"); + p->loc_UserVec3 = qglGetUniformLocation(p->program, "UserVec3"); + p->loc_UserVec4 = qglGetUniformLocation(p->program, "UserVec4"); + p->loc_ViewTintColor = qglGetUniformLocation(p->program, "ViewTintColor"); + p->loc_ViewToLight = qglGetUniformLocation(p->program, "ViewToLight"); + p->loc_ModelToLight = qglGetUniformLocation(p->program, "ModelToLight"); + p->loc_TexMatrix = qglGetUniformLocation(p->program, "TexMatrix"); + p->loc_BackgroundTexMatrix = qglGetUniformLocation(p->program, "BackgroundTexMatrix"); + p->loc_ModelViewMatrix = qglGetUniformLocation(p->program, "ModelViewMatrix"); + p->loc_ModelViewProjectionMatrix = qglGetUniformLocation(p->program, "ModelViewProjectionMatrix"); + p->loc_PixelToScreenTexCoord = qglGetUniformLocation(p->program, "PixelToScreenTexCoord"); + p->loc_ModelToReflectCube = qglGetUniformLocation(p->program, "ModelToReflectCube"); + p->loc_ShadowMapMatrix = qglGetUniformLocation(p->program, "ShadowMapMatrix"); + p->loc_BloomColorSubtract = qglGetUniformLocation(p->program, "BloomColorSubtract"); + p->loc_NormalmapScrollBlend = qglGetUniformLocation(p->program, "NormalmapScrollBlend"); + p->loc_BounceGridMatrix = qglGetUniformLocation(p->program, "BounceGridMatrix"); + p->loc_BounceGridIntensity = qglGetUniformLocation(p->program, "BounceGridIntensity"); + // initialize the samplers to refer to the texture units we use + p->tex_Texture_First = -1; + p->tex_Texture_Second = -1; + p->tex_Texture_GammaRamps = -1; + p->tex_Texture_Normal = -1; + p->tex_Texture_Color = -1; + p->tex_Texture_Gloss = -1; + p->tex_Texture_Glow = -1; + p->tex_Texture_SecondaryNormal = -1; + p->tex_Texture_SecondaryColor = -1; + p->tex_Texture_SecondaryGloss = -1; + p->tex_Texture_SecondaryGlow = -1; + p->tex_Texture_Pants = -1; + p->tex_Texture_Shirt = -1; + p->tex_Texture_FogHeightTexture = -1; + p->tex_Texture_FogMask = -1; + p->tex_Texture_Lightmap = -1; + p->tex_Texture_Deluxemap = -1; + p->tex_Texture_Attenuation = -1; + p->tex_Texture_Cube = -1; + p->tex_Texture_Refraction = -1; + p->tex_Texture_Reflection = -1; + p->tex_Texture_ShadowMap2D = -1; + p->tex_Texture_CubeProjection = -1; + p->tex_Texture_ScreenDepth = -1; + p->tex_Texture_ScreenNormalMap = -1; + p->tex_Texture_ScreenDiffuse = -1; + p->tex_Texture_ScreenSpecular = -1; + p->tex_Texture_ReflectMask = -1; + p->tex_Texture_ReflectCube = -1; + p->tex_Texture_BounceGrid = -1; + sampler = 0; + if (p->loc_Texture_First >= 0) {p->tex_Texture_First = sampler;qglUniform1i(p->loc_Texture_First , sampler);sampler++;} + if (p->loc_Texture_Second >= 0) {p->tex_Texture_Second = sampler;qglUniform1i(p->loc_Texture_Second , sampler);sampler++;} + if (p->loc_Texture_GammaRamps >= 0) {p->tex_Texture_GammaRamps = sampler;qglUniform1i(p->loc_Texture_GammaRamps , sampler);sampler++;} + if (p->loc_Texture_Normal >= 0) {p->tex_Texture_Normal = sampler;qglUniform1i(p->loc_Texture_Normal , sampler);sampler++;} + if (p->loc_Texture_Color >= 0) {p->tex_Texture_Color = sampler;qglUniform1i(p->loc_Texture_Color , sampler);sampler++;} + if (p->loc_Texture_Gloss >= 0) {p->tex_Texture_Gloss = sampler;qglUniform1i(p->loc_Texture_Gloss , sampler);sampler++;} + if (p->loc_Texture_Glow >= 0) {p->tex_Texture_Glow = sampler;qglUniform1i(p->loc_Texture_Glow , sampler);sampler++;} + if (p->loc_Texture_SecondaryNormal >= 0) {p->tex_Texture_SecondaryNormal = sampler;qglUniform1i(p->loc_Texture_SecondaryNormal , sampler);sampler++;} + if (p->loc_Texture_SecondaryColor >= 0) {p->tex_Texture_SecondaryColor = sampler;qglUniform1i(p->loc_Texture_SecondaryColor , sampler);sampler++;} + if (p->loc_Texture_SecondaryGloss >= 0) {p->tex_Texture_SecondaryGloss = sampler;qglUniform1i(p->loc_Texture_SecondaryGloss , sampler);sampler++;} + if (p->loc_Texture_SecondaryGlow >= 0) {p->tex_Texture_SecondaryGlow = sampler;qglUniform1i(p->loc_Texture_SecondaryGlow , sampler);sampler++;} + if (p->loc_Texture_Pants >= 0) {p->tex_Texture_Pants = sampler;qglUniform1i(p->loc_Texture_Pants , sampler);sampler++;} + if (p->loc_Texture_Shirt >= 0) {p->tex_Texture_Shirt = sampler;qglUniform1i(p->loc_Texture_Shirt , sampler);sampler++;} + if (p->loc_Texture_FogHeightTexture>= 0) {p->tex_Texture_FogHeightTexture = sampler;qglUniform1i(p->loc_Texture_FogHeightTexture, sampler);sampler++;} + if (p->loc_Texture_FogMask >= 0) {p->tex_Texture_FogMask = sampler;qglUniform1i(p->loc_Texture_FogMask , sampler);sampler++;} + if (p->loc_Texture_Lightmap >= 0) {p->tex_Texture_Lightmap = sampler;qglUniform1i(p->loc_Texture_Lightmap , sampler);sampler++;} + if (p->loc_Texture_Deluxemap >= 0) {p->tex_Texture_Deluxemap = sampler;qglUniform1i(p->loc_Texture_Deluxemap , sampler);sampler++;} + if (p->loc_Texture_Attenuation >= 0) {p->tex_Texture_Attenuation = sampler;qglUniform1i(p->loc_Texture_Attenuation , sampler);sampler++;} + if (p->loc_Texture_Cube >= 0) {p->tex_Texture_Cube = sampler;qglUniform1i(p->loc_Texture_Cube , sampler);sampler++;} + if (p->loc_Texture_Refraction >= 0) {p->tex_Texture_Refraction = sampler;qglUniform1i(p->loc_Texture_Refraction , sampler);sampler++;} + if (p->loc_Texture_Reflection >= 0) {p->tex_Texture_Reflection = sampler;qglUniform1i(p->loc_Texture_Reflection , sampler);sampler++;} + if (p->loc_Texture_ShadowMap2D >= 0) {p->tex_Texture_ShadowMap2D = sampler;qglUniform1i(p->loc_Texture_ShadowMap2D , sampler);sampler++;} + if (p->loc_Texture_CubeProjection >= 0) {p->tex_Texture_CubeProjection = sampler;qglUniform1i(p->loc_Texture_CubeProjection , sampler);sampler++;} + if (p->loc_Texture_ScreenDepth >= 0) {p->tex_Texture_ScreenDepth = sampler;qglUniform1i(p->loc_Texture_ScreenDepth , sampler);sampler++;} + if (p->loc_Texture_ScreenNormalMap >= 0) {p->tex_Texture_ScreenNormalMap = sampler;qglUniform1i(p->loc_Texture_ScreenNormalMap , sampler);sampler++;} + if (p->loc_Texture_ScreenDiffuse >= 0) {p->tex_Texture_ScreenDiffuse = sampler;qglUniform1i(p->loc_Texture_ScreenDiffuse , sampler);sampler++;} + if (p->loc_Texture_ScreenSpecular >= 0) {p->tex_Texture_ScreenSpecular = sampler;qglUniform1i(p->loc_Texture_ScreenSpecular , sampler);sampler++;} + if (p->loc_Texture_ReflectMask >= 0) {p->tex_Texture_ReflectMask = sampler;qglUniform1i(p->loc_Texture_ReflectMask , sampler);sampler++;} + if (p->loc_Texture_ReflectCube >= 0) {p->tex_Texture_ReflectCube = sampler;qglUniform1i(p->loc_Texture_ReflectCube , sampler);sampler++;} + if (p->loc_Texture_BounceGrid >= 0) {p->tex_Texture_BounceGrid = sampler;qglUniform1i(p->loc_Texture_BounceGrid , sampler);sampler++;} + CHECKGLERROR + Con_DPrintf("^5GLSL shader %s compiled (%i textures).\n", permutationname, sampler); + } + else + Con_Printf("^1GLSL shader %s failed! some features may not work properly.\n", permutationname); + + // free the strings + if (vertexstring) + Mem_Free(vertexstring); + if (geometrystring) + Mem_Free(geometrystring); + if (fragmentstring) + Mem_Free(fragmentstring); +} + +void R_SetupShader_SetPermutationGLSL(unsigned int mode, unsigned int permutation) +{ + r_glsl_permutation_t *perm = R_GLSL_FindPermutation(mode, permutation); + if (r_glsl_permutation != perm) + { + r_glsl_permutation = perm; + if (!r_glsl_permutation->program) + { + if (!r_glsl_permutation->compiled) + R_GLSL_CompilePermutation(perm, mode, permutation); + if (!r_glsl_permutation->program) + { + // remove features until we find a valid permutation + int i; + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + { + // reduce i more quickly whenever it would not remove any bits + int j = 1<<(SHADERPERMUTATION_COUNT-1-i); + if (!(permutation & j)) + continue; + permutation -= j; + r_glsl_permutation = R_GLSL_FindPermutation(mode, permutation); + if (!r_glsl_permutation->compiled) + R_GLSL_CompilePermutation(perm, mode, permutation); + if (r_glsl_permutation->program) + break; + } + if (i >= SHADERPERMUTATION_COUNT) + { + //Con_Printf("Could not find a working OpenGL 2.0 shader for permutation %s %s\n", shadermodeinfo[mode].vertexfilename, shadermodeinfo[mode].pretext); + r_glsl_permutation = R_GLSL_FindPermutation(mode, permutation); + qglUseProgram(0);CHECKGLERROR + return; // no bit left to clear, entire mode is broken + } + } + } + CHECKGLERROR + qglUseProgram(r_glsl_permutation->program);CHECKGLERROR + } + if (r_glsl_permutation->loc_ModelViewProjectionMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewProjectionMatrix, 1, false, gl_modelviewprojection16f); + if (r_glsl_permutation->loc_ModelViewMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewMatrix, 1, false, gl_modelview16f); + if (r_glsl_permutation->loc_ClientTime >= 0) qglUniform1f(r_glsl_permutation->loc_ClientTime, cl.time); +} + +#ifdef SUPPORTD3D + +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +extern D3DCAPS9 vid_d3d9caps; +#endif + +struct r_hlsl_permutation_s; +typedef struct r_hlsl_permutation_s +{ + /// hash lookup data + struct r_hlsl_permutation_s *hashnext; + unsigned int mode; + unsigned int permutation; + + /// indicates if we have tried compiling this permutation already + qboolean compiled; + /// NULL if compilation failed + IDirect3DVertexShader9 *vertexshader; + IDirect3DPixelShader9 *pixelshader; +} +r_hlsl_permutation_t; + +typedef enum D3DVSREGISTER_e +{ + D3DVSREGISTER_TexMatrix = 0, // float4x4 + D3DVSREGISTER_BackgroundTexMatrix = 4, // float4x4 + D3DVSREGISTER_ModelViewProjectionMatrix = 8, // float4x4 + D3DVSREGISTER_ModelViewMatrix = 12, // float4x4 + D3DVSREGISTER_ShadowMapMatrix = 16, // float4x4 + D3DVSREGISTER_ModelToLight = 20, // float4x4 + D3DVSREGISTER_EyePosition = 24, + D3DVSREGISTER_FogPlane = 25, + D3DVSREGISTER_LightDir = 26, + D3DVSREGISTER_LightPosition = 27, +} +D3DVSREGISTER_t; + +typedef enum D3DPSREGISTER_e +{ + D3DPSREGISTER_Alpha = 0, + D3DPSREGISTER_BloomBlur_Parameters = 1, + D3DPSREGISTER_ClientTime = 2, + D3DPSREGISTER_Color_Ambient = 3, + D3DPSREGISTER_Color_Diffuse = 4, + D3DPSREGISTER_Color_Specular = 5, + D3DPSREGISTER_Color_Glow = 6, + D3DPSREGISTER_Color_Pants = 7, + D3DPSREGISTER_Color_Shirt = 8, + D3DPSREGISTER_DeferredColor_Ambient = 9, + D3DPSREGISTER_DeferredColor_Diffuse = 10, + D3DPSREGISTER_DeferredColor_Specular = 11, + D3DPSREGISTER_DeferredMod_Diffuse = 12, + D3DPSREGISTER_DeferredMod_Specular = 13, + D3DPSREGISTER_DistortScaleRefractReflect = 14, + D3DPSREGISTER_EyePosition = 15, // unused + D3DPSREGISTER_FogColor = 16, + D3DPSREGISTER_FogHeightFade = 17, + D3DPSREGISTER_FogPlane = 18, + D3DPSREGISTER_FogPlaneViewDist = 19, + D3DPSREGISTER_FogRangeRecip = 20, + D3DPSREGISTER_LightColor = 21, + D3DPSREGISTER_LightDir = 22, // unused + D3DPSREGISTER_LightPosition = 23, + D3DPSREGISTER_OffsetMapping_ScaleSteps = 24, + D3DPSREGISTER_PixelSize = 25, + D3DPSREGISTER_ReflectColor = 26, + D3DPSREGISTER_ReflectFactor = 27, + D3DPSREGISTER_ReflectOffset = 28, + D3DPSREGISTER_RefractColor = 29, + D3DPSREGISTER_Saturation = 30, + D3DPSREGISTER_ScreenCenterRefractReflect = 31, + D3DPSREGISTER_ScreenScaleRefractReflect = 32, + D3DPSREGISTER_ScreenToDepth = 33, + D3DPSREGISTER_ShadowMap_Parameters = 34, + D3DPSREGISTER_ShadowMap_TextureScale = 35, + D3DPSREGISTER_SpecularPower = 36, + D3DPSREGISTER_UserVec1 = 37, + D3DPSREGISTER_UserVec2 = 38, + D3DPSREGISTER_UserVec3 = 39, + D3DPSREGISTER_UserVec4 = 40, + D3DPSREGISTER_ViewTintColor = 41, + D3DPSREGISTER_PixelToScreenTexCoord = 42, + D3DPSREGISTER_BloomColorSubtract = 43, + D3DPSREGISTER_ViewToLight = 44, // float4x4 + D3DPSREGISTER_ModelToReflectCube = 48, // float4x4 + D3DPSREGISTER_NormalmapScrollBlend = 52, + // next at 53 +} +D3DPSREGISTER_t; + +/// information about each possible shader permutation +r_hlsl_permutation_t *r_hlsl_permutationhash[SHADERMODE_COUNT][SHADERPERMUTATION_HASHSIZE]; +/// currently selected permutation +r_hlsl_permutation_t *r_hlsl_permutation; +/// storage for permutations linked in the hash table +memexpandablearray_t r_hlsl_permutationarray; + +static r_hlsl_permutation_t *R_HLSL_FindPermutation(unsigned int mode, unsigned int permutation) +{ + //unsigned int hashdepth = 0; + unsigned int hashindex = (permutation * 0x1021) & (SHADERPERMUTATION_HASHSIZE - 1); + r_hlsl_permutation_t *p; + for (p = r_hlsl_permutationhash[mode][hashindex];p;p = p->hashnext) + { + if (p->mode == mode && p->permutation == permutation) + { + //if (hashdepth > 10) + // Con_Printf("R_HLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); + return p; + } + //hashdepth++; + } + p = (r_hlsl_permutation_t*)Mem_ExpandableArray_AllocRecord(&r_hlsl_permutationarray); + p->mode = mode; + p->permutation = permutation; + p->hashnext = r_hlsl_permutationhash[mode][hashindex]; + r_hlsl_permutationhash[mode][hashindex] = p; + //if (hashdepth > 10) + // Con_Printf("R_HLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); + return p; +} + +static char *R_HLSL_GetText(const char *filename, qboolean printfromdisknotice) +{ + char *shaderstring; + if (!filename || !filename[0]) + return NULL; + if (!strcmp(filename, "hlsl/default.hlsl")) + { + if (!hlslshaderstring) + { + hlslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); + if (hlslshaderstring) + Con_DPrintf("Loading shaders from file %s...\n", filename); + else + hlslshaderstring = (char *)builtinhlslshaderstring; + } + shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(hlslshaderstring) + 1); + memcpy(shaderstring, hlslshaderstring, strlen(hlslshaderstring) + 1); + return shaderstring; + } + shaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); + if (shaderstring) + { + if (printfromdisknotice) + Con_DPrintf("from disk %s... ", filename); + return shaderstring; + } + return shaderstring; +} + +#include +//#include +//#include + +static void R_HLSL_CacheShader(r_hlsl_permutation_t *p, const char *cachename, const char *vertstring, const char *fragstring) +{ + DWORD *vsbin = NULL; + DWORD *psbin = NULL; + fs_offset_t vsbinsize; + fs_offset_t psbinsize; +// IDirect3DVertexShader9 *vs = NULL; +// IDirect3DPixelShader9 *ps = NULL; + ID3DXBuffer *vslog = NULL; + ID3DXBuffer *vsbuffer = NULL; + ID3DXConstantTable *vsconstanttable = NULL; + ID3DXBuffer *pslog = NULL; + ID3DXBuffer *psbuffer = NULL; + ID3DXConstantTable *psconstanttable = NULL; + int vsresult = 0; + int psresult = 0; + char temp[MAX_INPUTLINE]; + const char *vsversion = "vs_3_0", *psversion = "ps_3_0"; + qboolean debugshader = gl_paranoid.integer != 0; + if (p->permutation & SHADERPERMUTATION_OFFSETMAPPING) {vsversion = "vs_3_0";psversion = "ps_3_0";} + if (p->permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) {vsversion = "vs_3_0";psversion = "ps_3_0";} + if (!debugshader) + { + vsbin = (DWORD *)FS_LoadFile(va("%s.vsbin", cachename), r_main_mempool, true, &vsbinsize); + psbin = (DWORD *)FS_LoadFile(va("%s.psbin", cachename), r_main_mempool, true, &psbinsize); + } + if ((!vsbin && vertstring) || (!psbin && fragstring)) + { + const char* dllnames_d3dx9 [] = + { + "d3dx9_43.dll", + "d3dx9_42.dll", + "d3dx9_41.dll", + "d3dx9_40.dll", + "d3dx9_39.dll", + "d3dx9_38.dll", + "d3dx9_37.dll", + "d3dx9_36.dll", + "d3dx9_35.dll", + "d3dx9_34.dll", + "d3dx9_33.dll", + "d3dx9_32.dll", + "d3dx9_31.dll", + "d3dx9_30.dll", + "d3dx9_29.dll", + "d3dx9_28.dll", + "d3dx9_27.dll", + "d3dx9_26.dll", + "d3dx9_25.dll", + "d3dx9_24.dll", + NULL + }; + dllhandle_t d3dx9_dll = NULL; + HRESULT (WINAPI *qD3DXCompileShaderFromFileA)(LPCSTR pSrcFile, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPCSTR pFunctionName, LPCSTR pProfile, DWORD Flags, LPD3DXBUFFER* ppShader, LPD3DXBUFFER* ppErrorMsgs, LPD3DXCONSTANTTABLE* ppConstantTable); + HRESULT (WINAPI *qD3DXPreprocessShader)(LPCSTR pSrcData, UINT SrcDataSize, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPD3DXBUFFER* ppShaderText, LPD3DXBUFFER* ppErrorMsgs); + HRESULT (WINAPI *qD3DXCompileShader)(LPCSTR pSrcData, UINT SrcDataLen, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPCSTR pFunctionName, LPCSTR pProfile, DWORD Flags, LPD3DXBUFFER* ppShader, LPD3DXBUFFER* ppErrorMsgs, LPD3DXCONSTANTTABLE* ppConstantTable); + dllfunction_t d3dx9_dllfuncs[] = + { + {"D3DXCompileShaderFromFileA", (void **) &qD3DXCompileShaderFromFileA}, + {"D3DXPreprocessShader", (void **) &qD3DXPreprocessShader}, + {"D3DXCompileShader", (void **) &qD3DXCompileShader}, + {NULL, NULL} + }; + if (Sys_LoadLibrary(dllnames_d3dx9, &d3dx9_dll, d3dx9_dllfuncs)) + { + DWORD shaderflags = 0; + if (debugshader) + shaderflags = D3DXSHADER_DEBUG | D3DXSHADER_SKIPOPTIMIZATION; + vsbin = (DWORD *)Mem_Realloc(tempmempool, vsbin, 0); + psbin = (DWORD *)Mem_Realloc(tempmempool, psbin, 0); + if (vertstring && vertstring[0]) + { + if (debugshader) + { +// vsresult = qD3DXPreprocessShader(vertstring, strlen(vertstring), NULL, NULL, &vsbuffer, &vslog); +// FS_WriteFile(va("%s_vs.fx", cachename), vsbuffer->GetBufferPointer(), vsbuffer->GetBufferSize()); + FS_WriteFile(va("%s_vs.fx", cachename), vertstring, strlen(vertstring)); + vsresult = qD3DXCompileShaderFromFileA(va("%s/%s_vs.fx", fs_gamedir, cachename), NULL, NULL, "main", vsversion, shaderflags, &vsbuffer, &vslog, &vsconstanttable); + } + else + vsresult = qD3DXCompileShader(vertstring, strlen(vertstring), NULL, NULL, "main", vsversion, shaderflags, &vsbuffer, &vslog, &vsconstanttable); + if (vsbuffer) + { + vsbinsize = vsbuffer->GetBufferSize(); + vsbin = (DWORD *)Mem_Alloc(tempmempool, vsbinsize); + memcpy(vsbin, vsbuffer->GetBufferPointer(), vsbinsize); + vsbuffer->Release(); + } + if (vslog) + { + strlcpy(temp, (const char *)vslog->GetBufferPointer(), min(sizeof(temp), vslog->GetBufferSize())); + Con_Printf("HLSL vertex shader compile output for %s follows:\n%s\n", cachename, temp); + vslog->Release(); + } + } + if (fragstring && fragstring[0]) + { + if (debugshader) + { +// psresult = qD3DXPreprocessShader(fragstring, strlen(fragstring), NULL, NULL, &psbuffer, &pslog); +// FS_WriteFile(va("%s_ps.fx", cachename), psbuffer->GetBufferPointer(), psbuffer->GetBufferSize()); + FS_WriteFile(va("%s_ps.fx", cachename), fragstring, strlen(fragstring)); + psresult = qD3DXCompileShaderFromFileA(va("%s/%s_ps.fx", fs_gamedir, cachename), NULL, NULL, "main", psversion, shaderflags, &psbuffer, &pslog, &psconstanttable); + } + else + psresult = qD3DXCompileShader(fragstring, strlen(fragstring), NULL, NULL, "main", psversion, shaderflags, &psbuffer, &pslog, &psconstanttable); + if (psbuffer) + { + psbinsize = psbuffer->GetBufferSize(); + psbin = (DWORD *)Mem_Alloc(tempmempool, psbinsize); + memcpy(psbin, psbuffer->GetBufferPointer(), psbinsize); + psbuffer->Release(); + } + if (pslog) + { + strlcpy(temp, (const char *)pslog->GetBufferPointer(), min(sizeof(temp), pslog->GetBufferSize())); + Con_Printf("HLSL pixel shader compile output for %s follows:\n%s\n", cachename, temp); + pslog->Release(); + } + } + Sys_UnloadLibrary(&d3dx9_dll); + } + else + Con_Printf("Unable to compile shader - D3DXCompileShader function not found\n"); + } + if (vsbin && psbin) + { + vsresult = IDirect3DDevice9_CreateVertexShader(vid_d3d9dev, vsbin, &p->vertexshader); + if (FAILED(vsresult)) + Con_Printf("HLSL CreateVertexShader failed for %s (hresult = %8x)\n", cachename, vsresult); + psresult = IDirect3DDevice9_CreatePixelShader(vid_d3d9dev, psbin, &p->pixelshader); + if (FAILED(psresult)) + Con_Printf("HLSL CreatePixelShader failed for %s (hresult = %8x)\n", cachename, psresult); + } + // free the shader data + vsbin = (DWORD *)Mem_Realloc(tempmempool, vsbin, 0); + psbin = (DWORD *)Mem_Realloc(tempmempool, psbin, 0); +} + +static void R_HLSL_CompilePermutation(r_hlsl_permutation_t *p, unsigned int mode, unsigned int permutation) +{ + int i; + shadermodeinfo_t *modeinfo = hlslshadermodeinfo + mode; + int vertstring_length = 0; + int geomstring_length = 0; + int fragstring_length = 0; + char *t; + char *vertexstring, *geometrystring, *fragmentstring; + char *vertstring, *geomstring, *fragstring; + char permutationname[256]; + char cachename[256]; + int vertstrings_count = 0; + int geomstrings_count = 0; + int fragstrings_count = 0; + const char *vertstrings_list[32+3+SHADERSTATICPARMS_COUNT+1]; + const char *geomstrings_list[32+3+SHADERSTATICPARMS_COUNT+1]; + const char *fragstrings_list[32+3+SHADERSTATICPARMS_COUNT+1]; + + if (p->compiled) + return; + p->compiled = true; + p->vertexshader = NULL; + p->pixelshader = NULL; + + permutationname[0] = 0; + cachename[0] = 0; + vertexstring = R_HLSL_GetText(modeinfo->vertexfilename, true); + geometrystring = R_HLSL_GetText(modeinfo->geometryfilename, false); + fragmentstring = R_HLSL_GetText(modeinfo->fragmentfilename, false); + + strlcat(permutationname, modeinfo->vertexfilename, sizeof(permutationname)); + strlcat(cachename, "hlsl/", sizeof(cachename)); + + // define HLSL so that the shader can tell apart the HLSL compiler and the Cg compiler + vertstrings_count = 0; + geomstrings_count = 0; + fragstrings_count = 0; + vertstrings_list[vertstrings_count++] = "#define HLSL\n"; + geomstrings_list[geomstrings_count++] = "#define HLSL\n"; + fragstrings_list[fragstrings_count++] = "#define HLSL\n"; + + // the first pretext is which type of shader to compile as + // (later these will all be bound together as a program object) + vertstrings_list[vertstrings_count++] = "#define VERTEX_SHADER\n"; + geomstrings_list[geomstrings_count++] = "#define GEOMETRY_SHADER\n"; + fragstrings_list[fragstrings_count++] = "#define FRAGMENT_SHADER\n"; + + // the second pretext is the mode (for example a light source) + vertstrings_list[vertstrings_count++] = modeinfo->pretext; + geomstrings_list[geomstrings_count++] = modeinfo->pretext; + fragstrings_list[fragstrings_count++] = modeinfo->pretext; + strlcat(permutationname, modeinfo->name, sizeof(permutationname)); + strlcat(cachename, modeinfo->name, sizeof(cachename)); + + // now add all the permutation pretexts + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + { + if (permutation & (1<vertexshader || !vertstring[0]) && (p->pixelshader || !fragstring[0])) + Con_DPrintf("^5HLSL shader %s compiled.\n", permutationname); + else + Con_Printf("^1HLSL shader %s failed! some features may not work properly.\n", permutationname); + + // free the strings + if (vertstring) + Mem_Free(vertstring); + if (geomstring) + Mem_Free(geomstring); + if (fragstring) + Mem_Free(fragstring); + if (vertexstring) + Mem_Free(vertexstring); + if (geometrystring) + Mem_Free(geometrystring); + if (fragmentstring) + Mem_Free(fragmentstring); +} + +static inline void hlslVSSetParameter16f(D3DVSREGISTER_t r, const float *a) {IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, a, 4);} +static inline void hlslVSSetParameter4fv(D3DVSREGISTER_t r, const float *a) {IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, a, 1);} +static inline void hlslVSSetParameter4f(D3DVSREGISTER_t r, float x, float y, float z, float w) {float temp[4];Vector4Set(temp, x, y, z, w);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslVSSetParameter3f(D3DVSREGISTER_t r, float x, float y, float z) {float temp[4];Vector4Set(temp, x, y, z, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslVSSetParameter2f(D3DVSREGISTER_t r, float x, float y) {float temp[4];Vector4Set(temp, x, y, 0, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslVSSetParameter1f(D3DVSREGISTER_t r, float x) {float temp[4];Vector4Set(temp, x, 0, 0, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} + +static inline void hlslPSSetParameter16f(D3DPSREGISTER_t r, const float *a) {IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, a, 4);} +static inline void hlslPSSetParameter4fv(D3DPSREGISTER_t r, const float *a) {IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, a, 1);} +static inline void hlslPSSetParameter4f(D3DPSREGISTER_t r, float x, float y, float z, float w) {float temp[4];Vector4Set(temp, x, y, z, w);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslPSSetParameter3f(D3DPSREGISTER_t r, float x, float y, float z) {float temp[4];Vector4Set(temp, x, y, z, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslPSSetParameter2f(D3DPSREGISTER_t r, float x, float y) {float temp[4];Vector4Set(temp, x, y, 0, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslPSSetParameter1f(D3DPSREGISTER_t r, float x) {float temp[4];Vector4Set(temp, x, 0, 0, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} + +void R_SetupShader_SetPermutationHLSL(unsigned int mode, unsigned int permutation) +{ + r_hlsl_permutation_t *perm = R_HLSL_FindPermutation(mode, permutation); + if (r_hlsl_permutation != perm) + { + r_hlsl_permutation = perm; + if (!r_hlsl_permutation->vertexshader && !r_hlsl_permutation->pixelshader) + { + if (!r_hlsl_permutation->compiled) + R_HLSL_CompilePermutation(perm, mode, permutation); + if (!r_hlsl_permutation->vertexshader && !r_hlsl_permutation->pixelshader) + { + // remove features until we find a valid permutation + int i; + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + { + // reduce i more quickly whenever it would not remove any bits + int j = 1<<(SHADERPERMUTATION_COUNT-1-i); + if (!(permutation & j)) + continue; + permutation -= j; + r_hlsl_permutation = R_HLSL_FindPermutation(mode, permutation); + if (!r_hlsl_permutation->compiled) + R_HLSL_CompilePermutation(perm, mode, permutation); + if (r_hlsl_permutation->vertexshader || r_hlsl_permutation->pixelshader) + break; + } + if (i >= SHADERPERMUTATION_COUNT) + { + //Con_Printf("Could not find a working HLSL shader for permutation %s %s\n", shadermodeinfo[mode].vertexfilename, shadermodeinfo[mode].pretext); + r_hlsl_permutation = R_HLSL_FindPermutation(mode, permutation); + return; // no bit left to clear, entire mode is broken + } + } + } + IDirect3DDevice9_SetVertexShader(vid_d3d9dev, r_hlsl_permutation->vertexshader); + IDirect3DDevice9_SetPixelShader(vid_d3d9dev, r_hlsl_permutation->pixelshader); + } + hlslVSSetParameter16f(D3DVSREGISTER_ModelViewProjectionMatrix, gl_modelviewprojection16f); + hlslVSSetParameter16f(D3DVSREGISTER_ModelViewMatrix, gl_modelview16f); + hlslPSSetParameter1f(D3DPSREGISTER_ClientTime, cl.time); +} +#endif + +void R_SetupShader_SetPermutationSoft(unsigned int mode, unsigned int permutation) +{ + DPSOFTRAST_SetShader(mode, permutation, r_shadow_glossexact.integer); + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, 1, false, gl_modelviewprojection16f); + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewMatrixM1, 1, false, gl_modelview16f); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ClientTime, cl.time); +} + +void R_GLSL_Restart_f(void) +{ + unsigned int i, limit; + if (glslshaderstring && glslshaderstring != builtinshaderstring) + Mem_Free(glslshaderstring); + glslshaderstring = NULL; + if (hlslshaderstring && hlslshaderstring != builtinhlslshaderstring) + Mem_Free(hlslshaderstring); + hlslshaderstring = NULL; + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + r_hlsl_permutation_t *p; + r_hlsl_permutation = NULL; + limit = Mem_ExpandableArray_IndexRange(&r_hlsl_permutationarray); + for (i = 0;i < limit;i++) + { + if ((p = (r_hlsl_permutation_t*)Mem_ExpandableArray_RecordAtIndex(&r_hlsl_permutationarray, i))) + { + if (p->vertexshader) + IDirect3DVertexShader9_Release(p->vertexshader); + if (p->pixelshader) + IDirect3DPixelShader9_Release(p->pixelshader); + Mem_ExpandableArray_FreeRecord(&r_hlsl_permutationarray, (void*)p); + } + } + memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + { + r_glsl_permutation_t *p; + r_glsl_permutation = NULL; + limit = Mem_ExpandableArray_IndexRange(&r_glsl_permutationarray); + for (i = 0;i < limit;i++) + { + if ((p = (r_glsl_permutation_t*)Mem_ExpandableArray_RecordAtIndex(&r_glsl_permutationarray, i))) + { + GL_Backend_FreeProgram(p->program); + Mem_ExpandableArray_FreeRecord(&r_glsl_permutationarray, (void*)p); + } + } + memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + case RENDERPATH_SOFT: + break; + } +} + +void R_GLSL_DumpShader_f(void) +{ + int i; + qfile_t *file; + + file = FS_OpenRealFile("glsl/default.glsl", "w", false); + if (file) + { + FS_Print(file, "/* The engine may define the following macros:\n"); + FS_Print(file, "#define VERTEX_SHADER\n#define GEOMETRY_SHADER\n#define FRAGMENT_SHADER\n"); + for (i = 0;i < SHADERMODE_COUNT;i++) + FS_Print(file, glslshadermodeinfo[i].pretext); + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + FS_Print(file, shaderpermutationinfo[i].pretext); + FS_Print(file, "*/\n"); + FS_Print(file, builtinshaderstring); + FS_Close(file); + Con_Printf("glsl/default.glsl written\n"); + } + else + Con_Printf("failed to write to glsl/default.glsl\n"); + + file = FS_OpenRealFile("hlsl/default.hlsl", "w", false); + if (file) + { + FS_Print(file, "/* The engine may define the following macros:\n"); + FS_Print(file, "#define VERTEX_SHADER\n#define GEOMETRY_SHADER\n#define FRAGMENT_SHADER\n"); + for (i = 0;i < SHADERMODE_COUNT;i++) + FS_Print(file, hlslshadermodeinfo[i].pretext); + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + FS_Print(file, shaderpermutationinfo[i].pretext); + FS_Print(file, "*/\n"); + FS_Print(file, builtinhlslshaderstring); + FS_Close(file); + Con_Printf("hlsl/default.hlsl written\n"); + } + else + Con_Printf("failed to write to hlsl/default.hlsl\n"); +} + +void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemode, int rgbscale, qboolean notrippy) +{ + unsigned int permutation = 0; + if (r_trippy.integer && !notrippy) + permutation |= SHADERPERMUTATION_TRIPPY; + permutation |= SHADERPERMUTATION_VIEWTINT; + if (first) + permutation |= SHADERPERMUTATION_DIFFUSE; + if (second) + permutation |= SHADERPERMUTATION_SPECULAR; + if (texturemode == GL_MODULATE) + permutation |= SHADERPERMUTATION_COLORMAPPING; + else if (texturemode == GL_ADD) + permutation |= SHADERPERMUTATION_GLOW; + else if (texturemode == GL_DECAL) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + if (!second) + texturemode = GL_MODULATE; + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + switch (vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + R_SetupShader_SetPermutationHLSL(SHADERMODE_GENERIC, permutation); + R_Mesh_TexBind(GL20TU_FIRST , first ); + R_Mesh_TexBind(GL20TU_SECOND, second); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_SetupShader_SetPermutationGLSL(SHADERMODE_GENERIC, permutation); + R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , first ); + R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second, second); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + R_Mesh_TexBind(0, first ); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexBind(1, second); + if (second) + R_Mesh_TexCombine(1, texturemode, texturemode, rgbscale, 1); + break; + case RENDERPATH_GL11: + R_Mesh_TexBind(0, first ); + break; + case RENDERPATH_SOFT: + R_SetupShader_SetPermutationSoft(SHADERMODE_GENERIC, permutation); + R_Mesh_TexBind(GL20TU_FIRST , first ); + R_Mesh_TexBind(GL20TU_SECOND, second); + break; + } +} + +void R_SetupShader_DepthOrShadow(qboolean notrippy) +{ + unsigned int permutation = 0; + if (r_trippy.integer && !notrippy) + permutation |= SHADERPERMUTATION_TRIPPY; + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + switch (vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + R_SetupShader_SetPermutationHLSL(SHADERMODE_DEPTH_OR_SHADOW, permutation); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_SetupShader_SetPermutationGLSL(SHADERMODE_DEPTH_OR_SHADOW, permutation); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + R_Mesh_TexBind(0, 0); + R_Mesh_TexBind(1, 0); + break; + case RENDERPATH_GL11: + R_Mesh_TexBind(0, 0); + break; + case RENDERPATH_SOFT: + R_SetupShader_SetPermutationSoft(SHADERMODE_DEPTH_OR_SHADOW, permutation); + break; + } +} + +void R_SetupShader_ShowDepth(qboolean notrippy) +{ + int permutation = 0; + if (r_trippy.integer && !notrippy) + permutation |= SHADERPERMUTATION_TRIPPY; + if (r_trippy.integer) + permutation |= SHADERPERMUTATION_TRIPPY; + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + switch (vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTHLSL + R_SetupShader_SetPermutationHLSL(SHADERMODE_SHOWDEPTH, permutation); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_SetupShader_SetPermutationGLSL(SHADERMODE_SHOWDEPTH, permutation); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + case RENDERPATH_GL11: + break; + case RENDERPATH_SOFT: + R_SetupShader_SetPermutationSoft(SHADERMODE_SHOWDEPTH, permutation); + break; + } +} + +extern qboolean r_shadow_usingdeferredprepass; +extern cvar_t r_shadow_deferred_8bitrange; +extern rtexture_t *r_shadow_attenuationgradienttexture; +extern rtexture_t *r_shadow_attenuation2dtexture; +extern rtexture_t *r_shadow_attenuation3dtexture; +extern qboolean r_shadow_usingshadowmap2d; +extern qboolean r_shadow_usingshadowmaportho; +extern float r_shadow_shadowmap_texturescale[2]; +extern float r_shadow_shadowmap_parameters[4]; +extern qboolean r_shadow_shadowmapvsdct; +extern qboolean r_shadow_shadowmapsampler; +extern int r_shadow_shadowmappcf; +extern rtexture_t *r_shadow_shadowmap2dtexture; +extern rtexture_t *r_shadow_shadowmap2dcolortexture; +extern rtexture_t *r_shadow_shadowmapvsdcttexture; +extern matrix4x4_t r_shadow_shadowmapmatrix; +extern int r_shadow_shadowmaplod; // changes for each light based on distance +extern int r_shadow_prepass_width; +extern int r_shadow_prepass_height; +extern rtexture_t *r_shadow_prepassgeometrydepthtexture; +extern rtexture_t *r_shadow_prepassgeometrynormalmaptexture; +extern rtexture_t *r_shadow_prepassgeometrydepthcolortexture; +extern rtexture_t *r_shadow_prepasslightingdiffusetexture; +extern rtexture_t *r_shadow_prepasslightingspeculartexture; + +#define BLENDFUNC_ALLOWS_COLORMOD 1 +#define BLENDFUNC_ALLOWS_FOG 2 +#define BLENDFUNC_ALLOWS_FOG_HACK0 4 +#define BLENDFUNC_ALLOWS_FOG_HACKALPHA 8 +#define BLENDFUNC_ALLOWS_ANYFOG (BLENDFUNC_ALLOWS_FOG | BLENDFUNC_ALLOWS_FOG_HACK0 | BLENDFUNC_ALLOWS_FOG_HACKALPHA) +static int R_BlendFuncFlags(int src, int dst) +{ + int r = 0; + + // a blendfunc allows colormod if: + // a) it can never keep the destination pixel invariant, or + // b) it can keep the destination pixel invariant, and still can do so if colormodded + // this is to prevent unintended side effects from colormod + + // a blendfunc allows fog if: + // blend(fog(src), fog(dst)) == fog(blend(src, dst)) + // this is to prevent unintended side effects from fog + + // these checks are the output of fogeval.pl + + r |= BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_DST_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_DST_ALPHA && dst == GL_ONE_MINUS_DST_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_DST_COLOR && dst == GL_ONE_MINUS_SRC_ALPHA) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_DST_COLOR && dst == GL_ONE_MINUS_SRC_COLOR) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_DST_COLOR && dst == GL_SRC_ALPHA) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_DST_COLOR && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_DST_COLOR && dst == GL_ZERO) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_ONE && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_ONE && dst == GL_ONE_MINUS_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG_HACKALPHA; + if(src == GL_ONE && dst == GL_ZERO) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ONE_MINUS_DST_ALPHA && dst == GL_DST_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ONE_MINUS_DST_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_ONE_MINUS_DST_COLOR && dst == GL_SRC_COLOR) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_ONE_MINUS_SRC_COLOR && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_SRC_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_SRC_ALPHA && dst == GL_ONE_MINUS_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ZERO && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ZERO && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + + return r; +} + +void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting, float ambientscale, float diffusescale, float specularscale, rsurfacepass_t rsurfacepass, int texturenumsurfaces, const msurface_t **texturesurfacelist, void *surfacewaterplane, qboolean notrippy) +{ + // select a permutation of the lighting shader appropriate to this + // combination of texture, entity, light source, and fogging, only use the + // minimum features necessary to avoid wasting rendering time in the + // fragment shader on features that are not being used + unsigned int permutation = 0; + unsigned int mode = 0; + int blendfuncflags; + static float dummy_colormod[3] = {1, 1, 1}; + float *colormod = rsurface.colormod; + float m16f[16]; + matrix4x4_t tempmatrix; + r_waterstate_waterplane_t *waterplane = (r_waterstate_waterplane_t *)surfacewaterplane; + if (r_trippy.integer && !notrippy) + permutation |= SHADERPERMUTATION_TRIPPY; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + permutation |= SHADERPERMUTATION_ALPHAKILL; + if (rsurface.texture->r_water_waterscroll[0] && rsurface.texture->r_water_waterscroll[1]) + permutation |= SHADERPERMUTATION_NORMALMAPSCROLLBLEND; // todo: make generic + if (rsurfacepass == RSURFPASS_BACKGROUND) + { + // distorted background + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERSHADER) + { + mode = SHADERMODE_WATER; + if((r_wateralpha.value < 1) && (rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERALPHA)) + { + // this is the right thing to do for wateralpha + GL_BlendFunc(GL_ONE, GL_ZERO); + blendfuncflags = R_BlendFuncFlags(GL_ONE, GL_ZERO); + } + else + { + // this is the right thing to do for entity alpha + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFRACTION) + { + mode = SHADERMODE_REFRACTION; + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + else + { + mode = SHADERMODE_GENERIC; + permutation |= SHADERPERMUTATION_DIFFUSE; + GL_BlendFunc(GL_ONE, GL_ZERO); + blendfuncflags = R_BlendFuncFlags(GL_ONE, GL_ZERO); + } + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + } + else if (rsurfacepass == RSURFPASS_DEFERREDGEOMETRY) + { + if (r_glsl_offsetmapping.integer) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + // normalmap (deferred prepass), may use alpha test on diffuse + mode = SHADERMODE_DEFERREDGEOMETRY; + GL_BlendFunc(GL_ONE, GL_ZERO); + blendfuncflags = R_BlendFuncFlags(GL_ONE, GL_ZERO); + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + } + else if (rsurfacepass == RSURFPASS_RTLIGHT) + { + if (r_glsl_offsetmapping.integer) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + // light source + mode = SHADERMODE_LIGHTSOURCE; + if (rsurface.rtlight->currentcubemap != r_texture_whitecube) + permutation |= SHADERPERMUTATION_CUBEFILTER; + if (diffusescale > 0) + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmap2d) + { + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + if(r_shadow_shadowmapvsdct) + permutation |= SHADERPERMUTATION_SHADOWMAPVSDCT; + + if (r_shadow_shadowmapsampler) + permutation |= SHADERPERMUTATION_SHADOWSAMPLER; + if (r_shadow_shadowmappcf > 1) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF2; + else if (r_shadow_shadowmappcf) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF; + } + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE); + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) + { + if (r_glsl_offsetmapping.integer) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + // unshaded geometry (fullbright or ambient model lighting) + mode = SHADERMODE_FLATCOLOR; + ambientscale = diffusescale = specularscale = 0; + if (rsurface.texture->glowtexture && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) + permutation |= SHADERPERMUTATION_GLOW; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) + { + permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + + if (r_shadow_shadowmapsampler) + permutation |= SHADERPERMUTATION_SHADOWSAMPLER; + if (r_shadow_shadowmappcf > 1) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF2; + else if (r_shadow_shadowmappcf) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF; + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) + permutation |= SHADERPERMUTATION_REFLECTION; + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + // when using alphatocoverage, we don't need alphakill + if (vid.allowalphatocoverage) + { + if (r_transparent_alphatocoverage.integer) + { + GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); + permutation &= ~SHADERPERMUTATION_ALPHAKILL; + } + else + GL_AlphaToCoverage(false); + } + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT_DIRECTIONAL) + { + if (r_glsl_offsetmapping.integer) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + // directional model lighting + mode = SHADERMODE_LIGHTDIRECTION; + if (rsurface.texture->glowtexture && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) + permutation |= SHADERPERMUTATION_GLOW; + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) + { + permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + + if (r_shadow_shadowmapsampler) + permutation |= SHADERPERMUTATION_SHADOWSAMPLER; + if (r_shadow_shadowmappcf > 1) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF2; + else if (r_shadow_shadowmappcf) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF; + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) + permutation |= SHADERPERMUTATION_REFLECTION; + if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) + permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + if (r_shadow_bouncegridtexture) + { + permutation |= SHADERPERMUTATION_BOUNCEGRID; + if (r_shadow_bouncegriddirectional) + permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; + } + GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + // when using alphatocoverage, we don't need alphakill + if (vid.allowalphatocoverage) + { + if (r_transparent_alphatocoverage.integer) + { + GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); + permutation &= ~SHADERPERMUTATION_ALPHAKILL; + } + else + GL_AlphaToCoverage(false); + } + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + { + if (r_glsl_offsetmapping.integer) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + // ambient model lighting + mode = SHADERMODE_LIGHTDIRECTION; + if (rsurface.texture->glowtexture && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) + permutation |= SHADERPERMUTATION_GLOW; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) + { + permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + + if (r_shadow_shadowmapsampler) + permutation |= SHADERPERMUTATION_SHADOWSAMPLER; + if (r_shadow_shadowmappcf > 1) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF2; + else if (r_shadow_shadowmappcf) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF; + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) + permutation |= SHADERPERMUTATION_REFLECTION; + if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) + permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + if (r_shadow_bouncegridtexture) + { + permutation |= SHADERPERMUTATION_BOUNCEGRID; + if (r_shadow_bouncegriddirectional) + permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; + } + GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + // when using alphatocoverage, we don't need alphakill + if (vid.allowalphatocoverage) + { + if (r_transparent_alphatocoverage.integer) + { + GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); + permutation &= ~SHADERPERMUTATION_ALPHAKILL; + } + else + GL_AlphaToCoverage(false); + } + } + else + { + if (r_glsl_offsetmapping.integer) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + // lightmapped wall + if (rsurface.texture->glowtexture && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) + permutation |= SHADERPERMUTATION_GLOW; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) + { + permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + + if (r_shadow_shadowmapsampler) + permutation |= SHADERPERMUTATION_SHADOWSAMPLER; + if (r_shadow_shadowmappcf > 1) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF2; + else if (r_shadow_shadowmappcf) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF; + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) + permutation |= SHADERPERMUTATION_REFLECTION; + if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) + permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + if (FAKELIGHT_ENABLED) + { + // fake lightmapping (q1bsp, q3bsp, fullbright map) + mode = SHADERMODE_FAKELIGHT; + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + } + else if (r_glsl_deluxemapping.integer >= 1 && rsurface.uselightmaptexture && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brushq3.deluxemapping) + { + // deluxemapping (light direction texture) + if (rsurface.uselightmaptexture && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brushq3.deluxemapping && r_refdef.scene.worldmodel->brushq3.deluxemapping_modelspace) + mode = SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE; + else + mode = SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE; + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + } + else if (r_glsl_deluxemapping.integer >= 2 && rsurface.uselightmaptexture) + { + // fake deluxemapping (uniform light direction in tangentspace) + mode = SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE; + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + } + else if (rsurface.uselightmaptexture) + { + // ordinary lightmapping (q1bsp, q3bsp) + mode = SHADERMODE_LIGHTMAP; + } + else + { + // ordinary vertex coloring (q3bsp) + mode = SHADERMODE_VERTEXCOLOR; + } + if (r_shadow_bouncegridtexture) + { + permutation |= SHADERPERMUTATION_BOUNCEGRID; + if (r_shadow_bouncegriddirectional) + permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; + } + GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + // when using alphatocoverage, we don't need alphakill + if (vid.allowalphatocoverage) + { + if (r_transparent_alphatocoverage.integer) + { + GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); + permutation &= ~SHADERPERMUTATION_ALPHAKILL; + } + else + GL_AlphaToCoverage(false); + } + } + if(!(blendfuncflags & BLENDFUNC_ALLOWS_COLORMOD)) + colormod = dummy_colormod; + if(!(blendfuncflags & BLENDFUNC_ALLOWS_ANYFOG)) + permutation &= ~(SHADERPERMUTATION_FOGHEIGHTTEXTURE | SHADERPERMUTATION_FOGOUTSIDE | SHADERPERMUTATION_FOGINSIDE); + if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACKALPHA) + permutation |= SHADERPERMUTATION_FOGALPHAHACK; + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + RSurf_PrepareVerticesForBatch(BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_VERTEXMESH_VERTEXCOLOR : 0) | BATCHNEED_VERTEXMESH_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_VERTEXMESH_LIGHTMAP : 0), texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmeshbuffer); + R_SetupShader_SetPermutationHLSL(mode, permutation); + Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);hlslPSSetParameter16f(D3DPSREGISTER_ModelToReflectCube, m16f); + if (mode == SHADERMODE_LIGHTSOURCE) + { + Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);hlslVSSetParameter16f(D3DVSREGISTER_ModelToLight, m16f); + hlslVSSetParameter3f(D3DVSREGISTER_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); + } + else + { + if (mode == SHADERMODE_LIGHTDIRECTION) + { + hlslVSSetParameter3f(D3DVSREGISTER_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); + } + } + Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_TexMatrix, m16f); + Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_BackgroundTexMatrix, m16f); + Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_ShadowMapMatrix, m16f); + hlslVSSetParameter3f(D3DVSREGISTER_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); + hlslVSSetParameter4f(D3DVSREGISTER_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); + + if (mode == SHADERMODE_LIGHTSOURCE) + { + hlslPSSetParameter3f(D3DPSREGISTER_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); + hlslPSSetParameter3f(D3DPSREGISTER_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); + + // additive passes are only darkened by fog, not tinted + hlslPSSetParameter3f(D3DPSREGISTER_FogColor, 0, 0, 0); + hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + } + else + { + if (mode == SHADERMODE_FLATCOLOR) + { + hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, colormod[0], colormod[1], colormod[2]); + } + else if (mode == SHADERMODE_LIGHTDIRECTION) + { + hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity) * colormod[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Diffuse, colormod[0] * r_shadow_deferred_8bitrange.value, colormod[1] * r_shadow_deferred_8bitrange.value, colormod[2] * r_shadow_deferred_8bitrange.value); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value); + hlslPSSetParameter3f(D3DPSREGISTER_LightColor, rsurface.modellight_diffuse[0], rsurface.modellight_diffuse[1], rsurface.modellight_diffuse[2]); + hlslPSSetParameter3f(D3DPSREGISTER_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); + } + else + { + hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Diffuse, colormod[0] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[1] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[2] * diffusescale * r_shadow_deferred_8bitrange.value); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value); + } + // additive passes are only darkened by fog, not tinted + if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) + hlslPSSetParameter3f(D3DPSREGISTER_FogColor, 0, 0, 0); + else + hlslPSSetParameter3f(D3DPSREGISTER_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); + hlslPSSetParameter4f(D3DPSREGISTER_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); + hlslPSSetParameter4f(D3DPSREGISTER_ScreenScaleRefractReflect, r_waterstate.screenscale[0], r_waterstate.screenscale[1], r_waterstate.screenscale[0], r_waterstate.screenscale[1]); + hlslPSSetParameter4f(D3DPSREGISTER_ScreenCenterRefractReflect, r_waterstate.screencenter[0], r_waterstate.screencenter[1], r_waterstate.screencenter[0], r_waterstate.screencenter[1]); + hlslPSSetParameter4f(D3DPSREGISTER_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); + hlslPSSetParameter4f(D3DPSREGISTER_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); + hlslPSSetParameter1f(D3DPSREGISTER_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); + hlslPSSetParameter1f(D3DPSREGISTER_ReflectOffset, rsurface.texture->reflectmin); + hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + if (mode == SHADERMODE_WATER) + hlslPSSetParameter2f(D3DPSREGISTER_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); + } + hlslPSSetParameter2f(D3DPSREGISTER_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + hlslPSSetParameter4f(D3DPSREGISTER_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); + hlslPSSetParameter1f(D3DPSREGISTER_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_waterstate.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); + hlslPSSetParameter3f(D3DPSREGISTER_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); + if (rsurface.texture->pantstexture) + hlslPSSetParameter3f(D3DPSREGISTER_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); + else + hlslPSSetParameter3f(D3DPSREGISTER_Color_Pants, 0, 0, 0); + if (rsurface.texture->shirttexture) + hlslPSSetParameter3f(D3DPSREGISTER_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); + else + hlslPSSetParameter3f(D3DPSREGISTER_Color_Shirt, 0, 0, 0); + hlslPSSetParameter4f(D3DPSREGISTER_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); + hlslPSSetParameter1f(D3DPSREGISTER_FogPlaneViewDist, rsurface.fogplaneviewdist); + hlslPSSetParameter1f(D3DPSREGISTER_FogRangeRecip, rsurface.fograngerecip); + hlslPSSetParameter1f(D3DPSREGISTER_FogHeightFade, rsurface.fogheightfade); + hlslPSSetParameter4f(D3DPSREGISTER_OffsetMapping_ScaleSteps, + r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, + max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) + ); + hlslPSSetParameter2f(D3DPSREGISTER_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); + + R_Mesh_TexBind(GL20TU_NORMAL , rsurface.texture->nmaptexture ); + R_Mesh_TexBind(GL20TU_COLOR , rsurface.texture->basetexture ); + R_Mesh_TexBind(GL20TU_GLOSS , rsurface.texture->glosstexture ); + R_Mesh_TexBind(GL20TU_GLOW , rsurface.texture->glowtexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_NORMAL , rsurface.texture->backgroundnmaptexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_COLOR , rsurface.texture->backgroundbasetexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOSS , rsurface.texture->backgroundglosstexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOW , rsurface.texture->backgroundglowtexture ); + if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_PANTS , rsurface.texture->pantstexture ); + if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_SHIRT , rsurface.texture->shirttexture ); + if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTMASK , rsurface.texture->reflectmasktexture ); + if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTCUBE , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); + if (permutation & SHADERPERMUTATION_FOGHEIGHTTEXTURE) R_Mesh_TexBind(GL20TU_FOGHEIGHTTEXTURE , r_texture_fogheighttexture ); + if (permutation & (SHADERPERMUTATION_FOGINSIDE | SHADERPERMUTATION_FOGOUTSIDE)) R_Mesh_TexBind(GL20TU_FOGMASK , r_texture_fogattenuation ); + R_Mesh_TexBind(GL20TU_LIGHTMAP , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); + R_Mesh_TexBind(GL20TU_DELUXEMAP , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); + if (rsurface.rtlight ) R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); + if (rsurfacepass == RSURFPASS_BACKGROUND) + { + R_Mesh_TexBind(GL20TU_REFRACTION , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); + if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); + R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } + else + { + if (permutation & SHADERPERMUTATION_REFLECTION ) R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } +// if (rsurfacepass == RSURFPASS_DEFERREDLIGHT ) R_Mesh_TexBind(GL20TU_SCREENDEPTH , r_shadow_prepassgeometrydepthtexture ); +// if (rsurfacepass == RSURFPASS_DEFERREDLIGHT ) R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); + if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENDIFFUSE , r_shadow_prepasslightingdiffusetexture ); + if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENSPECULAR , r_shadow_prepasslightingspeculartexture ); + if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) + { + R_Mesh_TexBind(GL20TU_SHADOWMAP2D, r_shadow_shadowmap2dcolortexture); + if (rsurface.rtlight) + { + if (permutation & SHADERPERMUTATION_CUBEFILTER ) R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); + if (permutation & SHADERPERMUTATION_SHADOWMAPVSDCT ) R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (!vid.useinterleavedarrays) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0), texturenumsurfaces, texturesurfacelist); + R_Mesh_VertexPointer( 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + R_Mesh_ColorPointer( 4, GL_FLOAT, sizeof(float[4]), rsurface.batchlightmapcolor4f, rsurface.batchlightmapcolor4f_vertexbuffer, rsurface.batchlightmapcolor4f_bufferoffset); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchsvector3f, rsurface.batchsvector3f_vertexbuffer, rsurface.batchsvector3f_bufferoffset); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchtvector3f, rsurface.batchtvector3f_vertexbuffer, rsurface.batchtvector3f_bufferoffset); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchnormal3f, rsurface.batchnormal3f_vertexbuffer, rsurface.batchnormal3f_bufferoffset); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); + } + else + { + RSurf_PrepareVerticesForBatch(BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_VERTEXMESH_VERTEXCOLOR : 0) | BATCHNEED_VERTEXMESH_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_VERTEXMESH_LIGHTMAP : 0), texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmeshbuffer); + } + R_SetupShader_SetPermutationGLSL(mode, permutation); + if (r_glsl_permutation->loc_ModelToReflectCube >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ModelToReflectCube, 1, false, m16f);} + if (mode == SHADERMODE_LIGHTSOURCE) + { + if (r_glsl_permutation->loc_ModelToLight >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ModelToLight, 1, false, m16f);} + if (r_glsl_permutation->loc_LightPosition >= 0) qglUniform3f(r_glsl_permutation->loc_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); + if (r_glsl_permutation->loc_LightColor >= 0) qglUniform3f(r_glsl_permutation->loc_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); + if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); + if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); + + // additive passes are only darkened by fog, not tinted + if (r_glsl_permutation->loc_FogColor >= 0) + qglUniform3f(r_glsl_permutation->loc_FogColor, 0, 0, 0); + if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f(r_glsl_permutation->loc_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + } + else + { + if (mode == SHADERMODE_FLATCOLOR) + { + if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, colormod[0], colormod[1], colormod[2]); + } + else if (mode == SHADERMODE_LIGHTDIRECTION) + { + if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[2]); + if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); + if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Diffuse, colormod[0] * r_shadow_deferred_8bitrange.value, colormod[1] * r_shadow_deferred_8bitrange.value, colormod[2] * r_shadow_deferred_8bitrange.value); + if (r_glsl_permutation->loc_DeferredMod_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value); + if (r_glsl_permutation->loc_LightColor >= 0) qglUniform3f(r_glsl_permutation->loc_LightColor, rsurface.modellight_diffuse[0] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[1] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[2] * r_refdef.scene.rtlightstylevalue[0]); + if (r_glsl_permutation->loc_LightDir >= 0) qglUniform3f(r_glsl_permutation->loc_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); + } + else + { + if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); + if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); + if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Diffuse, colormod[0] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[1] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[2] * diffusescale * r_shadow_deferred_8bitrange.value); + if (r_glsl_permutation->loc_DeferredMod_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value); + } + // additive passes are only darkened by fog, not tinted + if (r_glsl_permutation->loc_FogColor >= 0) + { + if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) + qglUniform3f(r_glsl_permutation->loc_FogColor, 0, 0, 0); + else + qglUniform3f(r_glsl_permutation->loc_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); + } + if (r_glsl_permutation->loc_DistortScaleRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); + if (r_glsl_permutation->loc_ScreenScaleRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_ScreenScaleRefractReflect, r_waterstate.screenscale[0], r_waterstate.screenscale[1], r_waterstate.screenscale[0], r_waterstate.screenscale[1]); + if (r_glsl_permutation->loc_ScreenCenterRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_ScreenCenterRefractReflect, r_waterstate.screencenter[0], r_waterstate.screencenter[1], r_waterstate.screencenter[0], r_waterstate.screencenter[1]); + if (r_glsl_permutation->loc_RefractColor >= 0) qglUniform4f(r_glsl_permutation->loc_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); + if (r_glsl_permutation->loc_ReflectColor >= 0) qglUniform4f(r_glsl_permutation->loc_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); + if (r_glsl_permutation->loc_ReflectFactor >= 0) qglUniform1f(r_glsl_permutation->loc_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); + if (r_glsl_permutation->loc_ReflectOffset >= 0) qglUniform1f(r_glsl_permutation->loc_ReflectOffset, rsurface.texture->reflectmin); + if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f(r_glsl_permutation->loc_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + if (r_glsl_permutation->loc_NormalmapScrollBlend >= 0) qglUniform2f(r_glsl_permutation->loc_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); + } + if (r_glsl_permutation->loc_TexMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_TexMatrix, 1, false, m16f);} + if (r_glsl_permutation->loc_BackgroundTexMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_BackgroundTexMatrix, 1, false, m16f);} + if (r_glsl_permutation->loc_ShadowMapMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ShadowMapMatrix, 1, false, m16f);} + if (r_glsl_permutation->loc_ShadowMap_TextureScale >= 0) qglUniform2f(r_glsl_permutation->loc_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + if (r_glsl_permutation->loc_ShadowMap_Parameters >= 0) qglUniform4f(r_glsl_permutation->loc_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + + if (r_glsl_permutation->loc_Color_Glow >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); + if (r_glsl_permutation->loc_Alpha >= 0) qglUniform1f(r_glsl_permutation->loc_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_waterstate.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); + if (r_glsl_permutation->loc_EyePosition >= 0) qglUniform3f(r_glsl_permutation->loc_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); + if (r_glsl_permutation->loc_Color_Pants >= 0) + { + if (rsurface.texture->pantstexture) + qglUniform3f(r_glsl_permutation->loc_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); + else + qglUniform3f(r_glsl_permutation->loc_Color_Pants, 0, 0, 0); + } + if (r_glsl_permutation->loc_Color_Shirt >= 0) + { + if (rsurface.texture->shirttexture) + qglUniform3f(r_glsl_permutation->loc_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); + else + qglUniform3f(r_glsl_permutation->loc_Color_Shirt, 0, 0, 0); + } + if (r_glsl_permutation->loc_FogPlane >= 0) qglUniform4f(r_glsl_permutation->loc_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); + if (r_glsl_permutation->loc_FogPlaneViewDist >= 0) qglUniform1f(r_glsl_permutation->loc_FogPlaneViewDist, rsurface.fogplaneviewdist); + if (r_glsl_permutation->loc_FogRangeRecip >= 0) qglUniform1f(r_glsl_permutation->loc_FogRangeRecip, rsurface.fograngerecip); + if (r_glsl_permutation->loc_FogHeightFade >= 0) qglUniform1f(r_glsl_permutation->loc_FogHeightFade, rsurface.fogheightfade); + if (r_glsl_permutation->loc_OffsetMapping_ScaleSteps >= 0) qglUniform4f(r_glsl_permutation->loc_OffsetMapping_ScaleSteps, + r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, + max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) + ); + if (r_glsl_permutation->loc_ScreenToDepth >= 0) qglUniform2f(r_glsl_permutation->loc_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f(r_glsl_permutation->loc_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + if (r_glsl_permutation->loc_BounceGridMatrix >= 0) {Matrix4x4_Concat(&tempmatrix, &r_shadow_bouncegridmatrix, &rsurface.matrix);Matrix4x4_ToArrayFloatGL(&tempmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_BounceGridMatrix, 1, false, m16f);} + if (r_glsl_permutation->loc_BounceGridIntensity >= 0) qglUniform1f(r_glsl_permutation->loc_BounceGridIntensity, r_shadow_bouncegridintensity*r_refdef.view.colorscale); + + if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , r_texture_white ); + if (r_glsl_permutation->tex_Texture_Second >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second , r_texture_white ); + if (r_glsl_permutation->tex_Texture_GammaRamps >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps , r_texture_gammaramps ); + if (r_glsl_permutation->tex_Texture_Normal >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Normal , rsurface.texture->nmaptexture ); + if (r_glsl_permutation->tex_Texture_Color >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Color , rsurface.texture->basetexture ); + if (r_glsl_permutation->tex_Texture_Gloss >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Gloss , rsurface.texture->glosstexture ); + if (r_glsl_permutation->tex_Texture_Glow >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Glow , rsurface.texture->glowtexture ); + if (r_glsl_permutation->tex_Texture_SecondaryNormal >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryNormal , rsurface.texture->backgroundnmaptexture ); + if (r_glsl_permutation->tex_Texture_SecondaryColor >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryColor , rsurface.texture->backgroundbasetexture ); + if (r_glsl_permutation->tex_Texture_SecondaryGloss >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryGloss , rsurface.texture->backgroundglosstexture ); + if (r_glsl_permutation->tex_Texture_SecondaryGlow >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryGlow , rsurface.texture->backgroundglowtexture ); + if (r_glsl_permutation->tex_Texture_Pants >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Pants , rsurface.texture->pantstexture ); + if (r_glsl_permutation->tex_Texture_Shirt >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Shirt , rsurface.texture->shirttexture ); + if (r_glsl_permutation->tex_Texture_ReflectMask >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ReflectMask , rsurface.texture->reflectmasktexture ); + if (r_glsl_permutation->tex_Texture_ReflectCube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ReflectCube , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); + if (r_glsl_permutation->tex_Texture_FogHeightTexture>= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_FogHeightTexture , r_texture_fogheighttexture ); + if (r_glsl_permutation->tex_Texture_FogMask >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_FogMask , r_texture_fogattenuation ); + if (r_glsl_permutation->tex_Texture_Lightmap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Lightmap , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); + if (r_glsl_permutation->tex_Texture_Deluxemap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Deluxemap , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); + if (r_glsl_permutation->tex_Texture_Attenuation >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Attenuation , r_shadow_attenuationgradienttexture ); + if (rsurfacepass == RSURFPASS_BACKGROUND) + { + if (r_glsl_permutation->tex_Texture_Refraction >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Refraction , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); + if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); + if (r_glsl_permutation->tex_Texture_Reflection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } + else + { + if (r_glsl_permutation->tex_Texture_Reflection >= 0 && waterplane) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } + if (r_glsl_permutation->tex_Texture_ScreenDepth >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenDepth , r_shadow_prepassgeometrydepthtexture ); + if (r_glsl_permutation->tex_Texture_ScreenNormalMap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenNormalMap , r_shadow_prepassgeometrynormalmaptexture ); + if (r_glsl_permutation->tex_Texture_ScreenDiffuse >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenDiffuse , r_shadow_prepasslightingdiffusetexture ); + if (r_glsl_permutation->tex_Texture_ScreenSpecular >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenSpecular , r_shadow_prepasslightingspeculartexture ); + if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) + { + if (r_glsl_permutation->tex_Texture_ShadowMap2D >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ShadowMap2D, r_shadow_shadowmap2dtexture ); + if (rsurface.rtlight) + { + if (r_glsl_permutation->tex_Texture_Cube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Cube , rsurface.rtlight->currentcubemap ); + if (r_glsl_permutation->tex_Texture_CubeProjection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_CubeProjection , r_shadow_shadowmapvsdcttexture ); + } + } + if (r_glsl_permutation->tex_Texture_BounceGrid >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_BounceGrid, r_shadow_bouncegridtexture); + CHECKGLERROR + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + case RENDERPATH_SOFT: + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0), texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Mesh_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchsvector3f, rsurface.batchtvector3f, rsurface.batchnormal3f, rsurface.batchlightmapcolor4f, rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordlightmap2f); + R_SetupShader_SetPermutationSoft(mode, permutation); + {Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelToReflectCubeM1, 1, false, m16f);} + if (mode == SHADERMODE_LIGHTSOURCE) + { + {Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelToLightM1, 1, false, m16f);} + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); + + // additive passes are only darkened by fog, not tinted + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, 0, 0, 0); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + } + else + { + if (mode == SHADERMODE_FLATCOLOR) + { + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, colormod[0], colormod[1], colormod[2]); + } + else if (mode == SHADERMODE_LIGHTDIRECTION) + { + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, colormod[0] * r_shadow_deferred_8bitrange.value, colormod[1] * r_shadow_deferred_8bitrange.value, colormod[2] * r_shadow_deferred_8bitrange.value); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightColor, rsurface.modellight_diffuse[0] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[1] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[2] * r_refdef.scene.rtlightstylevalue[0]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); + } + else + { + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, colormod[0] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[1] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[2] * diffusescale * r_shadow_deferred_8bitrange.value); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value); + } + // additive passes are only darkened by fog, not tinted + if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, 0, 0, 0); + else + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect, r_waterstate.screenscale[0], r_waterstate.screenscale[1], r_waterstate.screenscale[0], r_waterstate.screenscale[1]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect, r_waterstate.screencenter[0], r_waterstate.screencenter[1], r_waterstate.screencenter[0], r_waterstate.screencenter[1]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ReflectOffset, rsurface.texture->reflectmin); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); + } + {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_TexMatrixM1, 1, false, m16f);} + {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_BackgroundTexMatrixM1, 1, false, m16f);} + {Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ShadowMapMatrixM1, 1, false, m16f);} + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_waterstate.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); + if (DPSOFTRAST_UNIFORM_Color_Pants >= 0) + { + if (rsurface.texture->pantstexture) + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); + else + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Pants, 0, 0, 0); + } + if (DPSOFTRAST_UNIFORM_Color_Shirt >= 0) + { + if (rsurface.texture->shirttexture) + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); + else + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Shirt, 0, 0, 0); + } + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogPlaneViewDist, rsurface.fogplaneviewdist); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogRangeRecip, rsurface.fograngerecip); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogHeightFade, rsurface.fogheightfade); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_OffsetMapping_ScaleSteps, + r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, + max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) + ); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + + R_Mesh_TexBind(GL20TU_NORMAL , rsurface.texture->nmaptexture ); + R_Mesh_TexBind(GL20TU_COLOR , rsurface.texture->basetexture ); + R_Mesh_TexBind(GL20TU_GLOSS , rsurface.texture->glosstexture ); + R_Mesh_TexBind(GL20TU_GLOW , rsurface.texture->glowtexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_NORMAL , rsurface.texture->backgroundnmaptexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_COLOR , rsurface.texture->backgroundbasetexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOSS , rsurface.texture->backgroundglosstexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOW , rsurface.texture->backgroundglowtexture ); + if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_PANTS , rsurface.texture->pantstexture ); + if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_SHIRT , rsurface.texture->shirttexture ); + if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTMASK , rsurface.texture->reflectmasktexture ); + if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTCUBE , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); + if (permutation & SHADERPERMUTATION_FOGHEIGHTTEXTURE) R_Mesh_TexBind(GL20TU_FOGHEIGHTTEXTURE , r_texture_fogheighttexture ); + if (permutation & (SHADERPERMUTATION_FOGINSIDE | SHADERPERMUTATION_FOGOUTSIDE)) R_Mesh_TexBind(GL20TU_FOGMASK , r_texture_fogattenuation ); + R_Mesh_TexBind(GL20TU_LIGHTMAP , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); + R_Mesh_TexBind(GL20TU_DELUXEMAP , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); + if (rsurface.rtlight ) R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); + if (rsurfacepass == RSURFPASS_BACKGROUND) + { + R_Mesh_TexBind(GL20TU_REFRACTION , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); + if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); + R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } + else + { + if (permutation & SHADERPERMUTATION_REFLECTION ) R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } +// if (rsurfacepass == RSURFPASS_DEFERREDLIGHT ) R_Mesh_TexBind(GL20TU_SCREENDEPTH , r_shadow_prepassgeometrydepthtexture ); +// if (rsurfacepass == RSURFPASS_DEFERREDLIGHT ) R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); + if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENDIFFUSE , r_shadow_prepasslightingdiffusetexture ); + if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENSPECULAR , r_shadow_prepasslightingspeculartexture ); + if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) + { + R_Mesh_TexBind(GL20TU_SHADOWMAP2D, r_shadow_shadowmap2dcolortexture); + if (rsurface.rtlight) + { + if (permutation & SHADERPERMUTATION_CUBEFILTER ) R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); + if (permutation & SHADERPERMUTATION_SHADOWMAPVSDCT ) R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); + } + } + break; + } +} + +void R_SetupShader_DeferredLight(const rtlight_t *rtlight) +{ + // select a permutation of the lighting shader appropriate to this + // combination of texture, entity, light source, and fogging, only use the + // minimum features necessary to avoid wasting rendering time in the + // fragment shader on features that are not being used + unsigned int permutation = 0; + unsigned int mode = 0; + const float *lightcolorbase = rtlight->currentcolor; + float ambientscale = rtlight->ambientscale; + float diffusescale = rtlight->diffusescale; + float specularscale = rtlight->specularscale; + // this is the location of the light in view space + vec3_t viewlightorigin; + // this transforms from view space (camera) to light space (cubemap) + matrix4x4_t viewtolight; + matrix4x4_t lighttoview; + float viewtolight16f[16]; + float range = 1.0f / r_shadow_deferred_8bitrange.value; + // light source + mode = SHADERMODE_DEFERREDLIGHTSOURCE; + if (rtlight->currentcubemap != r_texture_whitecube) + permutation |= SHADERPERMUTATION_CUBEFILTER; + if (diffusescale > 0) + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0 && r_shadow_gloss.integer > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + if (r_shadow_usingshadowmap2d) + { + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + if (r_shadow_shadowmapvsdct) + permutation |= SHADERPERMUTATION_SHADOWMAPVSDCT; + + if (r_shadow_shadowmapsampler) + permutation |= SHADERPERMUTATION_SHADOWSAMPLER; + if (r_shadow_shadowmappcf > 1) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF2; + else if (r_shadow_shadowmappcf) + permutation |= SHADERPERMUTATION_SHADOWMAPPCF; + } + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + Matrix4x4_Transform(&r_refdef.view.viewport.viewmatrix, rtlight->shadoworigin, viewlightorigin); + Matrix4x4_Concat(&lighttoview, &r_refdef.view.viewport.viewmatrix, &rtlight->matrix_lighttoworld); + Matrix4x4_Invert_Simple(&viewtolight, &lighttoview); + Matrix4x4_ToArrayFloatGL(&viewtolight, viewtolight16f); + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + R_SetupShader_SetPermutationHLSL(mode, permutation); + hlslPSSetParameter3f(D3DPSREGISTER_LightPosition, viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); + hlslPSSetParameter16f(D3DPSREGISTER_ViewToLight, viewtolight16f); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Ambient , lightcolorbase[0] * ambientscale * range, lightcolorbase[1] * ambientscale * range, lightcolorbase[2] * ambientscale * range); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale * range, lightcolorbase[1] * diffusescale * range, lightcolorbase[2] * diffusescale * range); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Specular, lightcolorbase[0] * specularscale * range, lightcolorbase[1] * specularscale * range, lightcolorbase[2] * specularscale * range); + hlslPSSetParameter2f(D3DPSREGISTER_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + hlslPSSetParameter4f(D3DPSREGISTER_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + hlslPSSetParameter2f(D3DPSREGISTER_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); + + R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); + R_Mesh_TexBind(GL20TU_SCREENDEPTH , r_shadow_prepassgeometrydepthcolortexture ); + R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); + R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); + R_Mesh_TexBind(GL20TU_SHADOWMAP2D , r_shadow_shadowmap2dcolortexture ); + R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_SetupShader_SetPermutationGLSL(mode, permutation); + if (r_glsl_permutation->loc_LightPosition >= 0) qglUniform3f( r_glsl_permutation->loc_LightPosition , viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); + if (r_glsl_permutation->loc_ViewToLight >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ViewToLight , 1, false, viewtolight16f); + if (r_glsl_permutation->loc_DeferredColor_Ambient >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Ambient , lightcolorbase[0] * ambientscale * range, lightcolorbase[1] * ambientscale * range, lightcolorbase[2] * ambientscale * range); + if (r_glsl_permutation->loc_DeferredColor_Diffuse >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale * range, lightcolorbase[1] * diffusescale * range, lightcolorbase[2] * diffusescale * range); + if (r_glsl_permutation->loc_DeferredColor_Specular >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Specular , lightcolorbase[0] * specularscale * range, lightcolorbase[1] * specularscale * range, lightcolorbase[2] * specularscale * range); + if (r_glsl_permutation->loc_ShadowMap_TextureScale >= 0) qglUniform2f( r_glsl_permutation->loc_ShadowMap_TextureScale , r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + if (r_glsl_permutation->loc_ShadowMap_Parameters >= 0) qglUniform4f( r_glsl_permutation->loc_ShadowMap_Parameters , r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f( r_glsl_permutation->loc_SpecularPower , (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + if (r_glsl_permutation->loc_ScreenToDepth >= 0) qglUniform2f( r_glsl_permutation->loc_ScreenToDepth , r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f( r_glsl_permutation->loc_PixelToScreenTexCoord , 1.0f/vid.width, 1.0f/vid.height); + + if (r_glsl_permutation->tex_Texture_Attenuation >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Attenuation , r_shadow_attenuationgradienttexture ); + if (r_glsl_permutation->tex_Texture_ScreenDepth >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenDepth , r_shadow_prepassgeometrydepthtexture ); + if (r_glsl_permutation->tex_Texture_ScreenNormalMap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenNormalMap , r_shadow_prepassgeometrynormalmaptexture ); + if (r_glsl_permutation->tex_Texture_Cube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Cube , rsurface.rtlight->currentcubemap ); + if (r_glsl_permutation->tex_Texture_ShadowMap2D >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ShadowMap2D , r_shadow_shadowmap2dtexture ); + if (r_glsl_permutation->tex_Texture_CubeProjection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_CubeProjection , r_shadow_shadowmapvsdcttexture ); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + case RENDERPATH_SOFT: + R_SetupShader_SetPermutationGLSL(mode, permutation); + DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_LightPosition , viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ViewToLightM1 , 1, false, viewtolight16f); + DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Ambient , lightcolorbase[0] * ambientscale * range, lightcolorbase[1] * ambientscale * range, lightcolorbase[2] * ambientscale * range); + DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale * range, lightcolorbase[1] * diffusescale * range, lightcolorbase[2] * diffusescale * range); + DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Specular , lightcolorbase[0] * specularscale * range, lightcolorbase[1] * specularscale * range, lightcolorbase[2] * specularscale * range); + DPSOFTRAST_Uniform2f( DPSOFTRAST_UNIFORM_ShadowMap_TextureScale , r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + DPSOFTRAST_Uniform4f( DPSOFTRAST_UNIFORM_ShadowMap_Parameters , r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + DPSOFTRAST_Uniform1f( DPSOFTRAST_UNIFORM_SpecularPower , (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + DPSOFTRAST_Uniform2f( DPSOFTRAST_UNIFORM_ScreenToDepth , r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + + R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); + R_Mesh_TexBind(GL20TU_SCREENDEPTH , r_shadow_prepassgeometrydepthtexture ); + R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); + R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); + R_Mesh_TexBind(GL20TU_SHADOWMAP2D , r_shadow_shadowmap2dtexture ); + R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); + break; + } +} + +#define SKINFRAME_HASH 1024 + +typedef struct +{ + int loadsequence; // incremented each level change + memexpandablearray_t array; + skinframe_t *hash[SKINFRAME_HASH]; +} +r_skinframe_t; +r_skinframe_t r_skinframe; + +void R_SkinFrame_PrepareForPurge(void) +{ + r_skinframe.loadsequence++; + // wrap it without hitting zero + if (r_skinframe.loadsequence >= 200) + r_skinframe.loadsequence = 1; +} + +void R_SkinFrame_MarkUsed(skinframe_t *skinframe) +{ + if (!skinframe) + return; + // mark the skinframe as used for the purging code + skinframe->loadsequence = r_skinframe.loadsequence; +} + +void R_SkinFrame_Purge(void) +{ + int i; + skinframe_t *s; + for (i = 0;i < SKINFRAME_HASH;i++) + { + for (s = r_skinframe.hash[i];s;s = s->next) + { + if (s->loadsequence && s->loadsequence != r_skinframe.loadsequence) + { + if (s->merged == s->base) + s->merged = NULL; + // FIXME: maybe pass a pointer to the pointer to R_PurgeTexture and reset it to NULL inside? [11/29/2007 Black] + R_PurgeTexture(s->stain );s->stain = NULL; + R_PurgeTexture(s->merged);s->merged = NULL; + R_PurgeTexture(s->base );s->base = NULL; + R_PurgeTexture(s->pants );s->pants = NULL; + R_PurgeTexture(s->shirt );s->shirt = NULL; + R_PurgeTexture(s->nmap );s->nmap = NULL; + R_PurgeTexture(s->gloss );s->gloss = NULL; + R_PurgeTexture(s->glow );s->glow = NULL; + R_PurgeTexture(s->fog );s->fog = NULL; + R_PurgeTexture(s->reflect);s->reflect = NULL; + s->loadsequence = 0; + } + } + } +} + +skinframe_t *R_SkinFrame_FindNextByName( skinframe_t *last, const char *name ) { + skinframe_t *item; + char basename[MAX_QPATH]; + + Image_StripImageExtension(name, basename, sizeof(basename)); + + if( last == NULL ) { + int hashindex; + hashindex = CRC_Block((unsigned char *)basename, strlen(basename)) & (SKINFRAME_HASH - 1); + item = r_skinframe.hash[hashindex]; + } else { + item = last->next; + } + + // linearly search through the hash bucket + for( ; item ; item = item->next ) { + if( !strcmp( item->basename, basename ) ) { + return item; + } + } + return NULL; +} + +skinframe_t *R_SkinFrame_Find(const char *name, int textureflags, int comparewidth, int compareheight, int comparecrc, qboolean add) +{ + skinframe_t *item; + int hashindex; + char basename[MAX_QPATH]; + + Image_StripImageExtension(name, basename, sizeof(basename)); + + hashindex = CRC_Block((unsigned char *)basename, strlen(basename)) & (SKINFRAME_HASH - 1); + for (item = r_skinframe.hash[hashindex];item;item = item->next) + if (!strcmp(item->basename, basename) && item->textureflags == textureflags && item->comparewidth == comparewidth && item->compareheight == compareheight && item->comparecrc == comparecrc) + break; + + if (!item) { + rtexture_t *dyntexture; + // check whether its a dynamic texture + dyntexture = CL_GetDynTexture( basename ); + if (!add && !dyntexture) + return NULL; + item = (skinframe_t *)Mem_ExpandableArray_AllocRecord(&r_skinframe.array); + memset(item, 0, sizeof(*item)); + strlcpy(item->basename, basename, sizeof(item->basename)); + item->base = dyntexture; // either NULL or dyntexture handle + item->textureflags = textureflags; + item->comparewidth = comparewidth; + item->compareheight = compareheight; + item->comparecrc = comparecrc; + item->next = r_skinframe.hash[hashindex]; + r_skinframe.hash[hashindex] = item; + } + else if( item->base == NULL ) + { + rtexture_t *dyntexture; + // check whether its a dynamic texture + // this only needs to be done because Purge doesnt delete skinframes - only sets the texture pointers to NULL and we need to restore it before returing.. [11/29/2007 Black] + dyntexture = CL_GetDynTexture( basename ); + item->base = dyntexture; // either NULL or dyntexture handle + } + + R_SkinFrame_MarkUsed(item); + return item; +} + +#define R_SKINFRAME_LOAD_AVERAGE_COLORS(cnt, getpixel) \ + { \ + unsigned long long avgcolor[5], wsum; \ + int pix, comp, w; \ + avgcolor[0] = 0; \ + avgcolor[1] = 0; \ + avgcolor[2] = 0; \ + avgcolor[3] = 0; \ + avgcolor[4] = 0; \ + wsum = 0; \ + for(pix = 0; pix < cnt; ++pix) \ + { \ + w = 0; \ + for(comp = 0; comp < 3; ++comp) \ + w += getpixel; \ + if(w) /* ignore perfectly black pixels because that is better for model skins */ \ + { \ + ++wsum; \ + /* comp = 3; -- not needed, comp is always 3 when we get here */ \ + w = getpixel; \ + for(comp = 0; comp < 3; ++comp) \ + avgcolor[comp] += getpixel * w; \ + avgcolor[3] += w; \ + } \ + /* comp = 3; -- not needed, comp is always 3 when we get here */ \ + avgcolor[4] += getpixel; \ + } \ + if(avgcolor[3] == 0) /* no pixels seen? even worse */ \ + avgcolor[3] = 1; \ + skinframe->avgcolor[0] = avgcolor[2] / (255.0 * avgcolor[3]); \ + skinframe->avgcolor[1] = avgcolor[1] / (255.0 * avgcolor[3]); \ + skinframe->avgcolor[2] = avgcolor[0] / (255.0 * avgcolor[3]); \ + skinframe->avgcolor[3] = avgcolor[4] / (255.0 * cnt); \ + } + +extern cvar_t gl_picmip; +skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboolean complain) +{ + int j; + unsigned char *pixels; + unsigned char *bumppixels; + unsigned char *basepixels = NULL; + int basepixels_width = 0; + int basepixels_height = 0; + skinframe_t *skinframe; + rtexture_t *ddsbase = NULL; + qboolean ddshasalpha = false; + float ddsavgcolor[4]; + char basename[MAX_QPATH]; + int miplevel = R_PicmipForFlags(textureflags); + int savemiplevel = miplevel; + int mymiplevel; + + if (cls.state == ca_dedicated) + return NULL; + + // return an existing skinframe if already loaded + // if loading of the first image fails, don't make a new skinframe as it + // would cause all future lookups of this to be missing + skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, false); + if (skinframe && skinframe->base) + return skinframe; + + Image_StripImageExtension(name, basename, sizeof(basename)); + + // check for DDS texture file first + if (!r_loaddds || !(ddsbase = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s.dds", basename), textureflags, &ddshasalpha, ddsavgcolor, miplevel))) + { + basepixels = loadimagepixelsbgra(name, complain, true, false, &miplevel); + if (basepixels == NULL) + return NULL; + } + + // FIXME handle miplevel + + if (developer_loading.integer) + Con_Printf("loading skin \"%s\"\n", name); + + // we've got some pixels to store, so really allocate this new texture now + if (!skinframe) + skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, true); + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + if (ddsbase) + { + skinframe->base = ddsbase; + skinframe->hasalpha = ddshasalpha; + VectorCopy(ddsavgcolor, skinframe->avgcolor); + if (r_loadfog && skinframe->hasalpha) + skinframe->fog = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_mask.dds", skinframe->basename), textureflags | TEXF_ALPHA, NULL, NULL, miplevel); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + } + else + { + basepixels_width = image_width; + basepixels_height = image_height; + skinframe->base = R_LoadTexture2D (r_main_texturepool, skinframe->basename, basepixels_width, basepixels_height, basepixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), miplevel, NULL); + if (textureflags & TEXF_ALPHA) + { + for (j = 3;j < basepixels_width * basepixels_height * 4;j += 4) + { + if (basepixels[j] < 255) + { + skinframe->hasalpha = true; + break; + } + } + if (r_loadfog && skinframe->hasalpha) + { + // has transparent pixels + pixels = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + for (j = 0;j < image_width * image_height * 4;j += 4) + { + pixels[j+0] = 255; + pixels[j+1] = 255; + pixels[j+2] = 255; + pixels[j+3] = basepixels[j+3]; + } + skinframe->fog = R_LoadTexture2D (r_main_texturepool, va("%s_mask", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), miplevel, NULL); + Mem_Free(pixels); + } + } + R_SKINFRAME_LOAD_AVERAGE_COLORS(basepixels_width * basepixels_height, basepixels[4 * pix + comp]); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->base) + R_SaveTextureDDSFile(skinframe->base, va("dds/%s.dds", skinframe->basename), r_texture_dds_save.integer < 2, skinframe->hasalpha); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->fog) + R_SaveTextureDDSFile(skinframe->fog, va("dds/%s_mask.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); + } + + if (r_loaddds) + { + mymiplevel = savemiplevel; + if (r_loadnormalmap) + skinframe->nmap = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_norm.dds", skinframe->basename), (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), NULL, NULL, mymiplevel); + skinframe->glow = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_glow.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel); + if (r_loadgloss) + skinframe->gloss = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_gloss.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel); + skinframe->pants = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_pants.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel); + skinframe->shirt = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_shirt.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel); + skinframe->reflect = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_reflect.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel); + } + + // _norm is the name used by tenebrae and has been adopted as standard + if (r_loadnormalmap && skinframe->nmap == NULL) + { + mymiplevel = savemiplevel; + if ((pixels = loadimagepixelsbgra(va("%s_norm", skinframe->basename), false, false, false, &mymiplevel)) != NULL) + { + skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + Mem_Free(pixels); + pixels = NULL; + } + else if (r_shadow_bumpscale_bumpmap.value > 0 && (bumppixels = loadimagepixelsbgra(va("%s_bump", skinframe->basename), false, false, false, &mymiplevel)) != NULL) + { + pixels = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + Image_HeightmapToNormalmap_BGRA(bumppixels, pixels, image_width, image_height, false, r_shadow_bumpscale_bumpmap.value); + skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + Mem_Free(pixels); + Mem_Free(bumppixels); + } + else if (r_shadow_bumpscale_basetexture.value > 0) + { + pixels = (unsigned char *)Mem_Alloc(tempmempool, basepixels_width * basepixels_height * 4); + Image_HeightmapToNormalmap_BGRA(basepixels, pixels, basepixels_width, basepixels_height, false, r_shadow_bumpscale_basetexture.value); + skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), basepixels_width, basepixels_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + Mem_Free(pixels); + } + if (r_savedds && qglGetCompressedTexImageARB && skinframe->nmap) + R_SaveTextureDDSFile(skinframe->nmap, va("dds/%s_norm.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); + } + + // _luma is supported only for tenebrae compatibility + // _glow is the preferred name + mymiplevel = savemiplevel; + if (skinframe->glow == NULL && ((pixels = loadimagepixelsbgra(va("%s_glow", skinframe->basename), false, false, false, &mymiplevel)) || (pixels = loadimagepixelsbgra(va("%s_luma", skinframe->basename), false, false, false, &mymiplevel)))) + { + skinframe->glow = R_LoadTexture2D (r_main_texturepool, va("%s_glow", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_glow.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->glow) + R_SaveTextureDDSFile(skinframe->glow, va("dds/%s_glow.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); + Mem_Free(pixels);pixels = NULL; + } + + mymiplevel = savemiplevel; + if (skinframe->gloss == NULL && r_loadgloss && (pixels = loadimagepixelsbgra(va("%s_gloss", skinframe->basename), false, false, false, &mymiplevel))) + { + skinframe->gloss = R_LoadTexture2D (r_main_texturepool, va("%s_gloss", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_gloss.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->gloss) + R_SaveTextureDDSFile(skinframe->gloss, va("dds/%s_gloss.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); + Mem_Free(pixels); + pixels = NULL; + } + + mymiplevel = savemiplevel; + if (skinframe->pants == NULL && (pixels = loadimagepixelsbgra(va("%s_pants", skinframe->basename), false, false, false, &mymiplevel))) + { + skinframe->pants = R_LoadTexture2D (r_main_texturepool, va("%s_pants", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->pants) + R_SaveTextureDDSFile(skinframe->pants, va("dds/%s_pants.dds", skinframe->basename), r_texture_dds_save.integer < 2, false); + Mem_Free(pixels); + pixels = NULL; + } + + mymiplevel = savemiplevel; + if (skinframe->shirt == NULL && (pixels = loadimagepixelsbgra(va("%s_shirt", skinframe->basename), false, false, false, &mymiplevel))) + { + skinframe->shirt = R_LoadTexture2D (r_main_texturepool, va("%s_shirt", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->shirt) + R_SaveTextureDDSFile(skinframe->shirt, va("dds/%s_shirt.dds", skinframe->basename), r_texture_dds_save.integer < 2, false); + Mem_Free(pixels); + pixels = NULL; + } + + mymiplevel = savemiplevel; + if (skinframe->reflect == NULL && (pixels = loadimagepixelsbgra(va("%s_reflect", skinframe->basename), false, false, false, &mymiplevel))) + { + skinframe->reflect = R_LoadTexture2D (r_main_texturepool, va("%s_reflect", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_reflectmask.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->reflect) + R_SaveTextureDDSFile(skinframe->reflect, va("dds/%s_reflect.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); + Mem_Free(pixels); + pixels = NULL; + } + + if (basepixels) + Mem_Free(basepixels); + + return skinframe; +} + +// this is only used by .spr32 sprites, HL .spr files, HL .bsp files +skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, const unsigned char *skindata, int width, int height, qboolean sRGB) +{ + int i; + unsigned char *temp1, *temp2; + skinframe_t *skinframe; + + if (cls.state == ca_dedicated) + return NULL; + + // if already loaded just return it, otherwise make a new skinframe + skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height*4) : 0, true); + if (skinframe && skinframe->base) + return skinframe; + + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + // if no data was provided, then clearly the caller wanted to get a blank skinframe + if (!skindata) + return NULL; + + if (developer_loading.integer) + Con_Printf("loading 32bit skin \"%s\"\n", name); + + if (r_loadnormalmap && r_shadow_bumpscale_basetexture.value > 0) + { + temp1 = (unsigned char *)Mem_Alloc(tempmempool, width * height * 8); + temp2 = temp1 + width * height * 4; + Image_HeightmapToNormalmap_BGRA(skindata, temp2, width, height, false, r_shadow_bumpscale_basetexture.value); + skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va("%s_nmap", skinframe->basename), width, height, temp2, TEXTYPE_BGRA, (textureflags | TEXF_ALPHA) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), -1, NULL); + Mem_Free(temp1); + } + skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, sRGB ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags, -1, NULL); + if (textureflags & TEXF_ALPHA) + { + for (i = 3;i < width * height * 4;i += 4) + { + if (skindata[i] < 255) + { + skinframe->hasalpha = true; + break; + } + } + if (r_loadfog && skinframe->hasalpha) + { + unsigned char *fogpixels = (unsigned char *)Mem_Alloc(tempmempool, width * height * 4); + memcpy(fogpixels, skindata, width * height * 4); + for (i = 0;i < width * height * 4;i += 4) + fogpixels[i] = fogpixels[i+1] = fogpixels[i+2] = 255; + skinframe->fog = R_LoadTexture2D(r_main_texturepool, va("%s_fog", skinframe->basename), width, height, fogpixels, TEXTYPE_BGRA, textureflags, -1, NULL); + Mem_Free(fogpixels); + } + } + + R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, skindata[4 * pix + comp]); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + + return skinframe; +} + +skinframe_t *R_SkinFrame_LoadInternalQuake(const char *name, int textureflags, int loadpantsandshirt, int loadglowtexture, const unsigned char *skindata, int width, int height) +{ + int i; + int featuresmask; + skinframe_t *skinframe; + + if (cls.state == ca_dedicated) + return NULL; + + // if already loaded just return it, otherwise make a new skinframe + skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height) : 0, true); + if (skinframe && skinframe->base) + return skinframe; + + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + // if no data was provided, then clearly the caller wanted to get a blank skinframe + if (!skindata) + return NULL; + + if (developer_loading.integer) + Con_Printf("loading quake skin \"%s\"\n", name); + + // we actually don't upload anything until the first use, because mdl skins frequently go unused, and are almost never used in both modes (colormapped and non-colormapped) + skinframe->qpixels = (unsigned char *)Mem_Alloc(r_main_mempool, width*height); // FIXME LEAK + memcpy(skinframe->qpixels, skindata, width*height); + skinframe->qwidth = width; + skinframe->qheight = height; + + featuresmask = 0; + for (i = 0;i < width * height;i++) + featuresmask |= palette_featureflags[skindata[i]]; + + skinframe->hasalpha = false; + skinframe->qhascolormapping = loadpantsandshirt && (featuresmask & (PALETTEFEATURE_PANTS | PALETTEFEATURE_SHIRT)); + skinframe->qgeneratenmap = r_shadow_bumpscale_basetexture.value > 0; + skinframe->qgeneratemerged = true; + skinframe->qgeneratebase = skinframe->qhascolormapping; + skinframe->qgenerateglow = loadglowtexture && (featuresmask & PALETTEFEATURE_GLOW); + + R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, ((unsigned char *)palette_bgra_complete)[skindata[pix]*4 + comp]); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + + return skinframe; +} + +static void R_SkinFrame_GenerateTexturesFromQPixels(skinframe_t *skinframe, qboolean colormapped) +{ + int width; + int height; + unsigned char *skindata; + + if (!skinframe->qpixels) + return; + + if (!skinframe->qhascolormapping) + colormapped = false; + + if (colormapped) + { + if (!skinframe->qgeneratebase) + return; + } + else + { + if (!skinframe->qgeneratemerged) + return; + } + + width = skinframe->qwidth; + height = skinframe->qheight; + skindata = skinframe->qpixels; + + if (skinframe->qgeneratenmap) + { + unsigned char *temp1, *temp2; + skinframe->qgeneratenmap = false; + temp1 = (unsigned char *)Mem_Alloc(tempmempool, width * height * 8); + temp2 = temp1 + width * height * 4; + // use either a custom palette or the quake palette + Image_Copy8bitBGRA(skindata, temp1, width * height, palette_bgra_complete); + Image_HeightmapToNormalmap_BGRA(temp1, temp2, width, height, false, r_shadow_bumpscale_basetexture.value); + skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va("%s_nmap", skinframe->basename), width, height, temp2, TEXTYPE_BGRA, (skinframe->textureflags | TEXF_ALPHA) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), -1, NULL); + Mem_Free(temp1); + } + + if (skinframe->qgenerateglow) + { + skinframe->qgenerateglow = false; + skinframe->glow = R_LoadTexture2D(r_main_texturepool, va("%s_glow", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_onlyfullbrights); // glow + } + + if (colormapped) + { + skinframe->qgeneratebase = false; + skinframe->base = R_LoadTexture2D(r_main_texturepool, va("%s_nospecial", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, skinframe->glow ? palette_bgra_nocolormapnofullbrights : palette_bgra_nocolormap); + skinframe->pants = R_LoadTexture2D(r_main_texturepool, va("%s_pants", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_pantsaswhite); + skinframe->shirt = R_LoadTexture2D(r_main_texturepool, va("%s_shirt", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_shirtaswhite); + } + else + { + skinframe->qgeneratemerged = false; + skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, skinframe->glow ? palette_bgra_nofullbrights : palette_bgra_complete); + } + + if (!skinframe->qgeneratemerged && !skinframe->qgeneratebase) + { + Mem_Free(skinframe->qpixels); + skinframe->qpixels = NULL; + } +} + +skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, const unsigned char *skindata, int width, int height, const unsigned int *palette, const unsigned int *alphapalette) +{ + int i; + skinframe_t *skinframe; + + if (cls.state == ca_dedicated) + return NULL; + + // if already loaded just return it, otherwise make a new skinframe + skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height) : 0, true); + if (skinframe && skinframe->base) + return skinframe; + + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + // if no data was provided, then clearly the caller wanted to get a blank skinframe + if (!skindata) + return NULL; + + if (developer_loading.integer) + Con_Printf("loading embedded 8bit image \"%s\"\n", name); + + skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_PALETTE, textureflags, -1, palette); + if (textureflags & TEXF_ALPHA) + { + for (i = 0;i < width * height;i++) + { + if (((unsigned char *)palette)[skindata[i]*4+3] < 255) + { + skinframe->hasalpha = true; + break; + } + } + if (r_loadfog && skinframe->hasalpha) + skinframe->fog = R_LoadTexture2D(r_main_texturepool, va("%s_fog", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, textureflags, -1, alphapalette); + } + + R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, ((unsigned char *)palette)[skindata[pix]*4 + comp]); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + + return skinframe; +} + +skinframe_t *R_SkinFrame_LoadMissing(void) +{ + skinframe_t *skinframe; + + if (cls.state == ca_dedicated) + return NULL; + + skinframe = R_SkinFrame_Find("missing", TEXF_FORCENEAREST, 0, 0, 0, true); + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + skinframe->avgcolor[0] = rand() / RAND_MAX; + skinframe->avgcolor[1] = rand() / RAND_MAX; + skinframe->avgcolor[2] = rand() / RAND_MAX; + skinframe->avgcolor[3] = 1; + + return skinframe; +} + +//static char *suffix[6] = {"ft", "bk", "rt", "lf", "up", "dn"}; +typedef struct suffixinfo_s +{ + const char *suffix; + qboolean flipx, flipy, flipdiagonal; +} +suffixinfo_t; +static suffixinfo_t suffix[3][6] = +{ + { + {"px", false, false, false}, + {"nx", false, false, false}, + {"py", false, false, false}, + {"ny", false, false, false}, + {"pz", false, false, false}, + {"nz", false, false, false} + }, + { + {"posx", false, false, false}, + {"negx", false, false, false}, + {"posy", false, false, false}, + {"negy", false, false, false}, + {"posz", false, false, false}, + {"negz", false, false, false} + }, + { + {"rt", true, false, true}, + {"lf", false, true, true}, + {"ft", true, true, false}, + {"bk", false, false, false}, + {"up", true, false, true}, + {"dn", true, false, true} + } +}; + +static int componentorder[4] = {0, 1, 2, 3}; + +rtexture_t *R_LoadCubemap(const char *basename) +{ + int i, j, cubemapsize; + unsigned char *cubemappixels, *image_buffer; + rtexture_t *cubemaptexture; + char name[256]; + // must start 0 so the first loadimagepixels has no requested width/height + cubemapsize = 0; + cubemappixels = NULL; + cubemaptexture = NULL; + // keep trying different suffix groups (posx, px, rt) until one loads + for (j = 0;j < 3 && !cubemappixels;j++) + { + // load the 6 images in the suffix group + for (i = 0;i < 6;i++) + { + // generate an image name based on the base and and suffix + dpsnprintf(name, sizeof(name), "%s%s", basename, suffix[j][i].suffix); + // load it + if ((image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + { + // an image loaded, make sure width and height are equal + if (image_width == image_height && (!cubemappixels || image_width == cubemapsize)) + { + // if this is the first image to load successfully, allocate the cubemap memory + if (!cubemappixels && image_width >= 1) + { + cubemapsize = image_width; + // note this clears to black, so unavailable sides are black + cubemappixels = (unsigned char *)Mem_Alloc(tempmempool, 6*cubemapsize*cubemapsize*4); + } + // copy the image with any flipping needed by the suffix (px and posx types don't need flipping) + if (cubemappixels) + Image_CopyMux(cubemappixels+i*cubemapsize*cubemapsize*4, image_buffer, cubemapsize, cubemapsize, suffix[j][i].flipx, suffix[j][i].flipy, suffix[j][i].flipdiagonal, 4, 4, componentorder); + } + else + Con_Printf("Cubemap image \"%s\" (%ix%i) is not square, OpenGL requires square cubemaps.\n", name, image_width, image_height); + // free the image + Mem_Free(image_buffer); + } + } + } + // if a cubemap loaded, upload it + if (cubemappixels) + { + if (developer_loading.integer) + Con_Printf("loading cubemap \"%s\"\n", basename); + + cubemaptexture = R_LoadTextureCubeMap(r_main_texturepool, basename, cubemapsize, cubemappixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, (gl_texturecompression_lightcubemaps.integer && gl_texturecompression.integer ? TEXF_COMPRESS : 0) | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + Mem_Free(cubemappixels); + } + else + { + Con_DPrintf("failed to load cubemap \"%s\"\n", basename); + if (developer_loading.integer) + { + Con_Printf("(tried tried images "); + for (j = 0;j < 3;j++) + for (i = 0;i < 6;i++) + Con_Printf("%s\"%s%s.tga\"", j + i > 0 ? ", " : "", basename, suffix[j][i].suffix); + Con_Print(" and was unable to find any of them).\n"); + } + } + return cubemaptexture; +} + +rtexture_t *R_GetCubemap(const char *basename) +{ + int i; + for (i = 0;i < r_texture_numcubemaps;i++) + if (r_texture_cubemaps[i] != NULL) + if (!strcasecmp(r_texture_cubemaps[i]->basename, basename)) + return r_texture_cubemaps[i]->texture ? r_texture_cubemaps[i]->texture : r_texture_whitecube; + if (i >= MAX_CUBEMAPS || !r_main_mempool) + return r_texture_whitecube; + r_texture_numcubemaps++; + r_texture_cubemaps[i] = (cubemapinfo_t *)Mem_Alloc(r_main_mempool, sizeof(cubemapinfo_t)); + strlcpy(r_texture_cubemaps[i]->basename, basename, sizeof(r_texture_cubemaps[i]->basename)); + r_texture_cubemaps[i]->texture = R_LoadCubemap(r_texture_cubemaps[i]->basename); + return r_texture_cubemaps[i]->texture; +} + +void R_FreeCubemap(const char *basename) +{ + int i; + + for (i = 0;i < r_texture_numcubemaps;i++) + { + if (r_texture_cubemaps[i] != NULL) + { + if (r_texture_cubemaps[i]->texture) + { + if (developer_loading.integer) + Con_DPrintf("unloading cubemap \"%s\"\n", r_texture_cubemaps[i]->basename); + R_FreeTexture(r_texture_cubemaps[i]->texture); + Mem_Free(r_texture_cubemaps[i]); + r_texture_cubemaps[i] = NULL; + } + } + } +} + +void R_FreeCubemaps(void) +{ + int i; + for (i = 0;i < r_texture_numcubemaps;i++) + { + if (developer_loading.integer) + Con_DPrintf("unloading cubemap \"%s\"\n", r_texture_cubemaps[i]->basename); + if (r_texture_cubemaps[i] != NULL) + { + if (r_texture_cubemaps[i]->texture) + R_FreeTexture(r_texture_cubemaps[i]->texture); + Mem_Free(r_texture_cubemaps[i]); + } + } + r_texture_numcubemaps = 0; +} + +void R_Main_FreeViewCache(void) +{ + if (r_refdef.viewcache.entityvisible) + Mem_Free(r_refdef.viewcache.entityvisible); + if (r_refdef.viewcache.world_pvsbits) + Mem_Free(r_refdef.viewcache.world_pvsbits); + if (r_refdef.viewcache.world_leafvisible) + Mem_Free(r_refdef.viewcache.world_leafvisible); + if (r_refdef.viewcache.world_surfacevisible) + Mem_Free(r_refdef.viewcache.world_surfacevisible); + memset(&r_refdef.viewcache, 0, sizeof(r_refdef.viewcache)); +} + +void R_Main_ResizeViewCache(void) +{ + int numentities = r_refdef.scene.numentities; + int numclusters = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_pvsclusters : 1; + int numclusterbytes = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_pvsclusterbytes : 1; + int numleafs = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_leafs : 1; + int numsurfaces = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->num_surfaces : 1; + if (r_refdef.viewcache.maxentities < numentities) + { + r_refdef.viewcache.maxentities = numentities; + if (r_refdef.viewcache.entityvisible) + Mem_Free(r_refdef.viewcache.entityvisible); + r_refdef.viewcache.entityvisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.maxentities); + } + if (r_refdef.viewcache.world_numclusters != numclusters) + { + r_refdef.viewcache.world_numclusters = numclusters; + r_refdef.viewcache.world_numclusterbytes = numclusterbytes; + if (r_refdef.viewcache.world_pvsbits) + Mem_Free(r_refdef.viewcache.world_pvsbits); + r_refdef.viewcache.world_pvsbits = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numclusterbytes); + } + if (r_refdef.viewcache.world_numleafs != numleafs) + { + r_refdef.viewcache.world_numleafs = numleafs; + if (r_refdef.viewcache.world_leafvisible) + Mem_Free(r_refdef.viewcache.world_leafvisible); + r_refdef.viewcache.world_leafvisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numleafs); + } + if (r_refdef.viewcache.world_numsurfaces != numsurfaces) + { + r_refdef.viewcache.world_numsurfaces = numsurfaces; + if (r_refdef.viewcache.world_surfacevisible) + Mem_Free(r_refdef.viewcache.world_surfacevisible); + r_refdef.viewcache.world_surfacevisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numsurfaces); + } +} + +extern rtexture_t *loadingscreentexture; +void gl_main_start(void) +{ + loadingscreentexture = NULL; + r_texture_blanknormalmap = NULL; + r_texture_white = NULL; + r_texture_grey128 = NULL; + r_texture_black = NULL; + r_texture_whitecube = NULL; + r_texture_normalizationcube = NULL; + r_texture_fogattenuation = NULL; + r_texture_fogheighttexture = NULL; + r_texture_gammaramps = NULL; + r_texture_numcubemaps = 0; + + r_loaddds = r_texture_dds_load.integer != 0; + r_savedds = vid.support.arb_texture_compression && vid.support.ext_texture_compression_s3tc && r_texture_dds_save.integer; + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + Cvar_SetValueQuick(&r_textureunits, vid.texunits); + Cvar_SetValueQuick(&gl_combine, 1); + Cvar_SetValueQuick(&r_glsl, 1); + r_loadnormalmap = true; + r_loadgloss = true; + r_loadfog = false; + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + Cvar_SetValueQuick(&r_textureunits, vid.texunits); + Cvar_SetValueQuick(&gl_combine, 1); + Cvar_SetValueQuick(&r_glsl, 0); + r_loadnormalmap = false; + r_loadgloss = false; + r_loadfog = true; + break; + case RENDERPATH_GL11: + Cvar_SetValueQuick(&r_textureunits, vid.texunits); + Cvar_SetValueQuick(&gl_combine, 0); + Cvar_SetValueQuick(&r_glsl, 0); + r_loadnormalmap = false; + r_loadgloss = false; + r_loadfog = true; + break; + } + + R_AnimCache_Free(); + R_FrameData_Reset(); + + r_numqueries = 0; + r_maxqueries = 0; + memset(r_queries, 0, sizeof(r_queries)); + + r_qwskincache = NULL; + r_qwskincache_size = 0; + + // due to caching of texture_t references, the collision cache must be reset + Collision_Cache_Reset(true); + + // set up r_skinframe loading system for textures + memset(&r_skinframe, 0, sizeof(r_skinframe)); + r_skinframe.loadsequence = 1; + Mem_ExpandableArray_NewArray(&r_skinframe.array, r_main_mempool, sizeof(skinframe_t), 256); + + r_main_texturepool = R_AllocTexturePool(); + R_BuildBlankTextures(); + R_BuildNoTexture(); + if (vid.support.arb_texture_cube_map) + { + R_BuildWhiteCube(); + R_BuildNormalizationCube(); + } + r_texture_fogattenuation = NULL; + r_texture_fogheighttexture = NULL; + r_texture_gammaramps = NULL; + //r_texture_fogintensity = NULL; + memset(&r_bloomstate, 0, sizeof(r_bloomstate)); + memset(&r_waterstate, 0, sizeof(r_waterstate)); + r_glsl_permutation = NULL; + memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); + Mem_ExpandableArray_NewArray(&r_glsl_permutationarray, r_main_mempool, sizeof(r_glsl_permutation_t), 256); + glslshaderstring = NULL; +#ifdef SUPPORTD3D + r_hlsl_permutation = NULL; + memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); + Mem_ExpandableArray_NewArray(&r_hlsl_permutationarray, r_main_mempool, sizeof(r_hlsl_permutation_t), 256); +#endif + hlslshaderstring = NULL; + memset(&r_svbsp, 0, sizeof (r_svbsp)); + + memset(r_texture_cubemaps, 0, sizeof(r_texture_cubemaps)); + r_texture_numcubemaps = 0; + + r_refdef.fogmasktable_density = 0; +} + +void gl_main_shutdown(void) +{ + R_AnimCache_Free(); + R_FrameData_Reset(); + + R_Main_FreeViewCache(); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (r_maxqueries) + qglDeleteQueriesARB(r_maxqueries, r_queries); + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + + r_numqueries = 0; + r_maxqueries = 0; + memset(r_queries, 0, sizeof(r_queries)); + + r_qwskincache = NULL; + r_qwskincache_size = 0; + + // clear out the r_skinframe state + Mem_ExpandableArray_FreeArray(&r_skinframe.array); + memset(&r_skinframe, 0, sizeof(r_skinframe)); + + if (r_svbsp.nodes) + Mem_Free(r_svbsp.nodes); + memset(&r_svbsp, 0, sizeof (r_svbsp)); + R_FreeTexturePool(&r_main_texturepool); + loadingscreentexture = NULL; + r_texture_blanknormalmap = NULL; + r_texture_white = NULL; + r_texture_grey128 = NULL; + r_texture_black = NULL; + r_texture_whitecube = NULL; + r_texture_normalizationcube = NULL; + r_texture_fogattenuation = NULL; + r_texture_fogheighttexture = NULL; + r_texture_gammaramps = NULL; + r_texture_numcubemaps = 0; + //r_texture_fogintensity = NULL; + memset(&r_bloomstate, 0, sizeof(r_bloomstate)); + memset(&r_waterstate, 0, sizeof(r_waterstate)); + R_GLSL_Restart_f(); + + r_glsl_permutation = NULL; + memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); + Mem_ExpandableArray_FreeArray(&r_glsl_permutationarray); + glslshaderstring = NULL; +#ifdef SUPPORTD3D + r_hlsl_permutation = NULL; + memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); + Mem_ExpandableArray_FreeArray(&r_hlsl_permutationarray); +#endif + hlslshaderstring = NULL; +} + +extern void CL_ParseEntityLump(char *entitystring); +void gl_main_newmap(void) +{ + // FIXME: move this code to client + char *entities, entname[MAX_QPATH]; + if (r_qwskincache) + Mem_Free(r_qwskincache); + r_qwskincache = NULL; + r_qwskincache_size = 0; + if (cl.worldmodel) + { + dpsnprintf(entname, sizeof(entname), "%s.ent", cl.worldnamenoextension); + if ((entities = (char *)FS_LoadFile(entname, tempmempool, true, NULL))) + { + CL_ParseEntityLump(entities); + Mem_Free(entities); + return; + } + if (cl.worldmodel->brush.entities) + CL_ParseEntityLump(cl.worldmodel->brush.entities); + } + R_Main_FreeViewCache(); + + R_FrameData_Reset(); +} + +void GL_Main_Init(void) +{ + r_main_mempool = Mem_AllocPool("Renderer", 0, NULL); + + Cmd_AddCommand("r_glsl_restart", R_GLSL_Restart_f, "unloads GLSL shaders, they will then be reloaded as needed"); + Cmd_AddCommand("r_glsl_dumpshader", R_GLSL_DumpShader_f, "dumps the engine internal default.glsl shader into glsl/default.glsl"); + // FIXME: the client should set up r_refdef.fog stuff including the fogmasktable + if (gamemode == GAME_NEHAHRA) + { + Cvar_RegisterVariable (&gl_fogenable); + Cvar_RegisterVariable (&gl_fogdensity); + Cvar_RegisterVariable (&gl_fogred); + Cvar_RegisterVariable (&gl_foggreen); + Cvar_RegisterVariable (&gl_fogblue); + Cvar_RegisterVariable (&gl_fogstart); + Cvar_RegisterVariable (&gl_fogend); + Cvar_RegisterVariable (&gl_skyclip); + } + Cvar_RegisterVariable(&r_motionblur); + Cvar_RegisterVariable(&r_motionblur_maxblur); + Cvar_RegisterVariable(&r_motionblur_bmin); + Cvar_RegisterVariable(&r_motionblur_vmin); + Cvar_RegisterVariable(&r_motionblur_vmax); + Cvar_RegisterVariable(&r_motionblur_vcoeff); + Cvar_RegisterVariable(&r_motionblur_randomize); + Cvar_RegisterVariable(&r_damageblur); + Cvar_RegisterVariable(&r_equalize_entities_fullbright); + Cvar_RegisterVariable(&r_equalize_entities_minambient); + Cvar_RegisterVariable(&r_equalize_entities_by); + Cvar_RegisterVariable(&r_equalize_entities_to); + Cvar_RegisterVariable(&r_depthfirst); + Cvar_RegisterVariable(&r_useinfinitefarclip); + Cvar_RegisterVariable(&r_farclip_base); + Cvar_RegisterVariable(&r_farclip_world); + Cvar_RegisterVariable(&r_nearclip); + Cvar_RegisterVariable(&r_deformvertexes); + Cvar_RegisterVariable(&r_transparent); + Cvar_RegisterVariable(&r_transparent_alphatocoverage); + Cvar_RegisterVariable(&r_showoverdraw); + Cvar_RegisterVariable(&r_showbboxes); + Cvar_RegisterVariable(&r_showsurfaces); + Cvar_RegisterVariable(&r_showtris); + Cvar_RegisterVariable(&r_shownormals); + Cvar_RegisterVariable(&r_showlighting); + Cvar_RegisterVariable(&r_showshadowvolumes); + Cvar_RegisterVariable(&r_showcollisionbrushes); + Cvar_RegisterVariable(&r_showcollisionbrushes_polygonfactor); + Cvar_RegisterVariable(&r_showcollisionbrushes_polygonoffset); + Cvar_RegisterVariable(&r_showdisabledepthtest); + Cvar_RegisterVariable(&r_drawportals); + Cvar_RegisterVariable(&r_drawentities); + Cvar_RegisterVariable(&r_draw2d); + Cvar_RegisterVariable(&r_drawworld); + Cvar_RegisterVariable(&r_cullentities_trace); + Cvar_RegisterVariable(&r_cullentities_trace_samples); + Cvar_RegisterVariable(&r_cullentities_trace_tempentitysamples); + Cvar_RegisterVariable(&r_cullentities_trace_enlarge); + Cvar_RegisterVariable(&r_cullentities_trace_delay); + Cvar_RegisterVariable(&r_drawviewmodel); + Cvar_RegisterVariable(&r_drawexteriormodel); + Cvar_RegisterVariable(&r_speeds); + Cvar_RegisterVariable(&r_fullbrights); + Cvar_RegisterVariable(&r_wateralpha); + Cvar_RegisterVariable(&r_dynamic); + Cvar_RegisterVariable(&r_fakelight); + Cvar_RegisterVariable(&r_fakelight_intensity); + Cvar_RegisterVariable(&r_fullbright); + Cvar_RegisterVariable(&r_shadows); + Cvar_RegisterVariable(&r_shadows_darken); + Cvar_RegisterVariable(&r_shadows_drawafterrtlighting); + Cvar_RegisterVariable(&r_shadows_castfrombmodels); + Cvar_RegisterVariable(&r_shadows_throwdistance); + Cvar_RegisterVariable(&r_shadows_throwdirection); + Cvar_RegisterVariable(&r_shadows_focus); + Cvar_RegisterVariable(&r_shadows_shadowmapscale); + Cvar_RegisterVariable(&r_q1bsp_skymasking); + Cvar_RegisterVariable(&r_polygonoffset_submodel_factor); + Cvar_RegisterVariable(&r_polygonoffset_submodel_offset); + Cvar_RegisterVariable(&r_polygonoffset_decals_factor); + Cvar_RegisterVariable(&r_polygonoffset_decals_offset); + Cvar_RegisterVariable(&r_fog_exp2); + Cvar_RegisterVariable(&r_fog_clear); + Cvar_RegisterVariable(&r_drawfog); + Cvar_RegisterVariable(&r_transparentdepthmasking); + Cvar_RegisterVariable(&r_transparent_sortmaxdist); + Cvar_RegisterVariable(&r_transparent_sortarraysize); + Cvar_RegisterVariable(&r_texture_dds_load); + Cvar_RegisterVariable(&r_texture_dds_save); + Cvar_RegisterVariable(&r_textureunits); + Cvar_RegisterVariable(&gl_combine); + Cvar_RegisterVariable(&r_viewfbo); + Cvar_RegisterVariable(&r_viewscale); + Cvar_RegisterVariable(&r_viewscale_fpsscaling); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_min); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_multiply); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_stepsize); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_stepmax); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_target); + Cvar_RegisterVariable(&r_glsl); + Cvar_RegisterVariable(&r_glsl_deluxemapping); + Cvar_RegisterVariable(&r_glsl_offsetmapping); + Cvar_RegisterVariable(&r_glsl_offsetmapping_steps); + Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping); + Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping_steps); + Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping_refinesteps); + Cvar_RegisterVariable(&r_glsl_offsetmapping_scale); + Cvar_RegisterVariable(&r_glsl_postprocess); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec1); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec2); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec3); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec4); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec1_enable); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec2_enable); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec3_enable); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec4_enable); + + Cvar_RegisterVariable(&r_water); + Cvar_RegisterVariable(&r_water_resolutionmultiplier); + Cvar_RegisterVariable(&r_water_clippingplanebias); + Cvar_RegisterVariable(&r_water_refractdistort); + Cvar_RegisterVariable(&r_water_reflectdistort); + Cvar_RegisterVariable(&r_water_scissormode); + Cvar_RegisterVariable(&r_water_lowquality); + + Cvar_RegisterVariable(&r_lerpsprites); + Cvar_RegisterVariable(&r_lerpmodels); + Cvar_RegisterVariable(&r_lerplightstyles); + Cvar_RegisterVariable(&r_waterscroll); + Cvar_RegisterVariable(&r_bloom); + Cvar_RegisterVariable(&r_bloom_colorscale); + Cvar_RegisterVariable(&r_bloom_brighten); + Cvar_RegisterVariable(&r_bloom_blur); + Cvar_RegisterVariable(&r_bloom_resolution); + Cvar_RegisterVariable(&r_bloom_colorexponent); + Cvar_RegisterVariable(&r_bloom_colorsubtract); + Cvar_RegisterVariable(&r_hdr); + Cvar_RegisterVariable(&r_hdr_scenebrightness); + Cvar_RegisterVariable(&r_hdr_glowintensity); + Cvar_RegisterVariable(&r_hdr_range); + Cvar_RegisterVariable(&r_hdr_irisadaptation); + Cvar_RegisterVariable(&r_hdr_irisadaptation_multiplier); + Cvar_RegisterVariable(&r_hdr_irisadaptation_minvalue); + Cvar_RegisterVariable(&r_hdr_irisadaptation_maxvalue); + Cvar_RegisterVariable(&r_hdr_irisadaptation_value); + Cvar_RegisterVariable(&r_hdr_irisadaptation_fade); + Cvar_RegisterVariable(&r_smoothnormals_areaweighting); + Cvar_RegisterVariable(&developer_texturelogging); + Cvar_RegisterVariable(&gl_lightmaps); + Cvar_RegisterVariable(&r_test); + Cvar_RegisterVariable(&r_glsl_saturation); + Cvar_RegisterVariable(&r_glsl_saturation_redcompensate); + Cvar_RegisterVariable(&r_glsl_vertextextureblend_usebothalphas); + Cvar_RegisterVariable(&r_framedatasize); + if (gamemode == GAME_NEHAHRA || gamemode == GAME_TENEBRAE) + Cvar_SetValue("r_fullbrights", 0); + R_RegisterModule("GL_Main", gl_main_start, gl_main_shutdown, gl_main_newmap, NULL, NULL); +} + +extern void R_Textures_Init(void); +extern void GL_Draw_Init(void); +extern void GL_Main_Init(void); +extern void R_Shadow_Init(void); +extern void R_Sky_Init(void); +extern void GL_Surf_Init(void); +extern void R_Particles_Init(void); +extern void R_Explosion_Init(void); +extern void gl_backend_init(void); +extern void Sbar_Init(void); +extern void R_LightningBeams_Init(void); +extern void Mod_RenderInit(void); +extern void Font_Init(void); + +void Render_Init(void) +{ + gl_backend_init(); + R_Textures_Init(); + GL_Main_Init(); + Font_Init(); + GL_Draw_Init(); + R_Shadow_Init(); + R_Sky_Init(); + GL_Surf_Init(); + Sbar_Init(); + R_Particles_Init(); + R_Explosion_Init(); + R_LightningBeams_Init(); + Mod_RenderInit(); +} + +/* +=============== +GL_Init +=============== +*/ +extern char *ENGINE_EXTENSIONS; +void GL_Init (void) +{ + gl_renderer = (const char *)qglGetString(GL_RENDERER); + gl_vendor = (const char *)qglGetString(GL_VENDOR); + gl_version = (const char *)qglGetString(GL_VERSION); + gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); + + if (!gl_extensions) + gl_extensions = ""; + if (!gl_platformextensions) + gl_platformextensions = ""; + + Con_Printf("GL_VENDOR: %s\n", gl_vendor); + Con_Printf("GL_RENDERER: %s\n", gl_renderer); + Con_Printf("GL_VERSION: %s\n", gl_version); + Con_DPrintf("GL_EXTENSIONS: %s\n", gl_extensions); + Con_DPrintf("%s_EXTENSIONS: %s\n", gl_platform, gl_platformextensions); + + VID_CheckExtensions(); + + // LordHavoc: report supported extensions + Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); + + // clear to black (loading plaque will be seen over this) + GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128); +} + +int R_CullBox(const vec3_t mins, const vec3_t maxs) +{ + int i; + mplane_t *p; + if (r_trippy.integer) + return false; + for (i = 0;i < r_refdef.view.numfrustumplanes;i++) + { + // skip nearclip plane, it often culls portals when you are very close, and is almost never useful + if (i == 4) + continue; + p = r_refdef.view.frustum + i; + switch(p->signbits) + { + default: + case 0: + if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 1: + if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 2: + if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 3: + if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 4: + if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 5: + if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 6: + if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 7: + if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + } + } + return false; +} + +int R_CullBoxCustomPlanes(const vec3_t mins, const vec3_t maxs, int numplanes, const mplane_t *planes) +{ + int i; + const mplane_t *p; + if (r_trippy.integer) + return false; + for (i = 0;i < numplanes;i++) + { + p = planes + i; + switch(p->signbits) + { + default: + case 0: + if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 1: + if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 2: + if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 3: + if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 4: + if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 5: + if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 6: + if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 7: + if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + } + } + return false; +} + +//================================================================================== + +// LordHavoc: this stores temporary data used within the same frame + +typedef struct r_framedata_mem_s +{ + struct r_framedata_mem_s *purge; // older mem block to free on next frame + size_t size; // how much usable space + size_t current; // how much space in use + size_t mark; // last "mark" location, temporary memory can be freed by returning to this + size_t wantedsize; // how much space was allocated + unsigned char *data; // start of real data (16byte aligned) +} +r_framedata_mem_t; + +static r_framedata_mem_t *r_framedata_mem; + +void R_FrameData_Reset(void) +{ + while (r_framedata_mem) + { + r_framedata_mem_t *next = r_framedata_mem->purge; + Mem_Free(r_framedata_mem); + r_framedata_mem = next; + } +} + +void R_FrameData_Resize(void) +{ + size_t wantedsize; + wantedsize = (size_t)(r_framedatasize.value * 1024*1024); + wantedsize = bound(65536, wantedsize, 1000*1024*1024); + if (!r_framedata_mem || r_framedata_mem->wantedsize != wantedsize) + { + r_framedata_mem_t *newmem = (r_framedata_mem_t *)Mem_Alloc(r_main_mempool, wantedsize); + newmem->wantedsize = wantedsize; + newmem->data = (unsigned char *)(((size_t)(newmem+1) + 15) & ~15); + newmem->size = (unsigned char *)newmem + wantedsize - newmem->data; + newmem->current = 0; + newmem->mark = 0; + newmem->purge = r_framedata_mem; + r_framedata_mem = newmem; + } +} + +void R_FrameData_NewFrame(void) +{ + R_FrameData_Resize(); + if (!r_framedata_mem) + return; + // if we ran out of space on the last frame, free the old memory now + while (r_framedata_mem->purge) + { + // repeatedly remove the second item in the list, leaving only head + r_framedata_mem_t *next = r_framedata_mem->purge->purge; + Mem_Free(r_framedata_mem->purge); + r_framedata_mem->purge = next; + } + // reset the current mem pointer + r_framedata_mem->current = 0; + r_framedata_mem->mark = 0; +} + +void *R_FrameData_Alloc(size_t size) +{ + void *data; + + // align to 16 byte boundary - the data pointer is already aligned, so we + // only need to ensure the size of every allocation is also aligned + size = (size + 15) & ~15; + + while (!r_framedata_mem || r_framedata_mem->current + size > r_framedata_mem->size) + { + // emergency - we ran out of space, allocate more memory + Cvar_SetValueQuick(&r_framedatasize, bound(0.25f, r_framedatasize.value * 2.0f, 128.0f)); + R_FrameData_Resize(); + } + + data = r_framedata_mem->data + r_framedata_mem->current; + r_framedata_mem->current += size; + + // count the usage for stats + r_refdef.stats.framedatacurrent = max(r_refdef.stats.framedatacurrent, (int)r_framedata_mem->current); + r_refdef.stats.framedatasize = max(r_refdef.stats.framedatasize, (int)r_framedata_mem->size); + + return (void *)data; +} + +void *R_FrameData_Store(size_t size, void *data) +{ + void *d = R_FrameData_Alloc(size); + if (d && data) + memcpy(d, data, size); + return d; +} + +void R_FrameData_SetMark(void) +{ + if (!r_framedata_mem) + return; + r_framedata_mem->mark = r_framedata_mem->current; +} + +void R_FrameData_ReturnToMark(void) +{ + if (!r_framedata_mem) + return; + r_framedata_mem->current = r_framedata_mem->mark; +} + +//================================================================================== + +// LordHavoc: animcache originally written by Echon, rewritten since then + +/** + * Animation cache prevents re-generating mesh data for an animated model + * multiple times in one frame for lighting, shadowing, reflections, etc. + */ + +void R_AnimCache_Free(void) +{ +} + +void R_AnimCache_ClearCache(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + ent->animcache_vertex3f = NULL; + ent->animcache_normal3f = NULL; + ent->animcache_svector3f = NULL; + ent->animcache_tvector3f = NULL; + ent->animcache_vertexmesh = NULL; + ent->animcache_vertex3fbuffer = NULL; + ent->animcache_vertexmeshbuffer = NULL; + } +} + +void R_AnimCache_UpdateEntityMeshBuffers(entity_render_t *ent, int numvertices) +{ + int i; + + // check if we need the meshbuffers + if (!vid.useinterleavedarrays) + return; + + if (!ent->animcache_vertexmesh && ent->animcache_normal3f) + ent->animcache_vertexmesh = (r_vertexmesh_t *)R_FrameData_Alloc(sizeof(r_vertexmesh_t)*numvertices); + // TODO: upload vertex3f buffer? + if (ent->animcache_vertexmesh) + { + memcpy(ent->animcache_vertexmesh, ent->model->surfmesh.vertexmesh, sizeof(r_vertexmesh_t)*numvertices); + for (i = 0;i < numvertices;i++) + memcpy(ent->animcache_vertexmesh[i].vertex3f, ent->animcache_vertex3f + 3*i, sizeof(float[3])); + if (ent->animcache_svector3f) + for (i = 0;i < numvertices;i++) + memcpy(ent->animcache_vertexmesh[i].svector3f, ent->animcache_svector3f + 3*i, sizeof(float[3])); + if (ent->animcache_tvector3f) + for (i = 0;i < numvertices;i++) + memcpy(ent->animcache_vertexmesh[i].tvector3f, ent->animcache_tvector3f + 3*i, sizeof(float[3])); + if (ent->animcache_normal3f) + for (i = 0;i < numvertices;i++) + memcpy(ent->animcache_vertexmesh[i].normal3f, ent->animcache_normal3f + 3*i, sizeof(float[3])); + // TODO: upload vertexmeshbuffer? + } +} + +qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qboolean wanttangents) +{ + dp_model_t *model = ent->model; + int numvertices; + // see if it's already cached this frame + if (ent->animcache_vertex3f) + { + // add normals/tangents if needed (this only happens with multiple views, reflections, cameras, etc) + if (wantnormals || wanttangents) + { + if (ent->animcache_normal3f) + wantnormals = false; + if (ent->animcache_svector3f) + wanttangents = false; + if (wantnormals || wanttangents) + { + numvertices = model->surfmesh.num_vertices; + if (wantnormals) + ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + if (wanttangents) + { + ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + } + model->AnimateVertices(model, ent->frameblend, ent->skeleton, NULL, wantnormals ? ent->animcache_normal3f : NULL, wanttangents ? ent->animcache_svector3f : NULL, wanttangents ? ent->animcache_tvector3f : NULL); + R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices); + } + } + } + else + { + // see if this ent is worth caching + if (!model || !model->Draw || !model->surfmesh.isanimated || !model->AnimateVertices) + return false; + // get some memory for this entity and generate mesh data + numvertices = model->surfmesh.num_vertices; + ent->animcache_vertex3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + if (wantnormals) + ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + if (wanttangents) + { + ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + } + model->AnimateVertices(model, ent->frameblend, ent->skeleton, ent->animcache_vertex3f, ent->animcache_normal3f, ent->animcache_svector3f, ent->animcache_tvector3f); + R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices); + } + return true; +} + +void R_AnimCache_CacheVisibleEntities(void) +{ + int i; + qboolean wantnormals = true; + qboolean wanttangents = !r_showsurfaces.integer; + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_GLES2: + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + wanttangents = false; + break; + case RENDERPATH_SOFT: + break; + } + + if (r_shownormals.integer) + wanttangents = wantnormals = true; + + // TODO: thread this + // NOTE: R_PrepareRTLights() also caches entities + + for (i = 0;i < r_refdef.scene.numentities;i++) + if (r_refdef.viewcache.entityvisible[i]) + R_AnimCache_GetEntity(r_refdef.scene.entities[i], wantnormals, wanttangents); +} + +//================================================================================== + +extern cvar_t r_overheadsprites_pushback; + +static void R_View_UpdateEntityLighting (void) +{ + int i; + entity_render_t *ent; + vec3_t tempdiffusenormal, avg; + vec_t f, fa, fd, fdd; + qboolean skipunseen = r_shadows.integer != 1; //|| R_Shadow_ShadowMappingEnabled(); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + + // skip unseen models + if (!r_refdef.viewcache.entityvisible[i] && skipunseen) + continue; + + // skip bsp models + if (ent->model && ent->model->brush.num_leafs) + { + // TODO: use modellight for r_ambient settings on world? + VectorSet(ent->modellight_ambient, 0, 0, 0); + VectorSet(ent->modellight_diffuse, 0, 0, 0); + VectorSet(ent->modellight_lightdir, 0, 0, 1); + continue; + } + + // fetch the lighting from the worldmodel data + VectorClear(ent->modellight_ambient); + VectorClear(ent->modellight_diffuse); + VectorClear(tempdiffusenormal); + if (ent->flags & RENDER_LIGHT) + { + vec3_t org; + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + + // complete lightning for lit sprites + // todo: make a EF_ field so small ents could be lit purely by modellight and skipping real rtlight pass (like EF_NORTLIGHT)? + if (ent->model->type == mod_sprite && !(ent->model->data_textures[0].basematerialflags & MATERIALFLAG_FULLBRIGHT)) + { + if (ent->model->sprite.sprnum_type == SPR_OVERHEAD) // apply offset for overhead sprites + org[2] = org[2] + r_overheadsprites_pushback.value; + R_LightPoint(ent->modellight_ambient, org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); + } + else + R_CompleteLightPoint(ent->modellight_ambient, ent->modellight_diffuse, tempdiffusenormal, org, LP_LIGHTMAP); + + if(ent->flags & RENDER_EQUALIZE) + { + // first fix up ambient lighting... + if(r_equalize_entities_minambient.value > 0) + { + fd = 0.299f * ent->modellight_diffuse[0] + 0.587f * ent->modellight_diffuse[1] + 0.114f * ent->modellight_diffuse[2]; + if(fd > 0) + { + fa = (0.299f * ent->modellight_ambient[0] + 0.587f * ent->modellight_ambient[1] + 0.114f * ent->modellight_ambient[2]); + if(fa < r_equalize_entities_minambient.value * fd) + { + // solve: + // fa'/fd' = minambient + // fa'+0.25*fd' = fa+0.25*fd + // ... + // fa' = fd' * minambient + // fd'*(0.25+minambient) = fa+0.25*fd + // ... + // fd' = (fa+0.25*fd) * 1 / (0.25+minambient) + // fa' = (fa+0.25*fd) * minambient / (0.25+minambient) + // ... + fdd = (fa + 0.25f * fd) / (0.25f + r_equalize_entities_minambient.value); + f = fdd / fd; // f>0 because all this is additive; f<1 because fddmodellight_ambient, (1-f)*0.25f, ent->modellight_diffuse, ent->modellight_ambient); + VectorScale(ent->modellight_diffuse, f, ent->modellight_diffuse); + } + } + } + + if(r_equalize_entities_to.value > 0 && r_equalize_entities_by.value != 0) + { + fa = 0.299f * ent->modellight_ambient[0] + 0.587f * ent->modellight_ambient[1] + 0.114f * ent->modellight_ambient[2]; + fd = 0.299f * ent->modellight_diffuse[0] + 0.587f * ent->modellight_diffuse[1] + 0.114f * ent->modellight_diffuse[2]; + f = fa + 0.25 * fd; + if(f > 0) + { + // adjust brightness and saturation to target + avg[0] = avg[1] = avg[2] = fa / f; + VectorLerp(ent->modellight_ambient, r_equalize_entities_by.value, avg, ent->modellight_ambient); + avg[0] = avg[1] = avg[2] = fd / f; + VectorLerp(ent->modellight_diffuse, r_equalize_entities_by.value, avg, ent->modellight_diffuse); + } + } + } + } + else // highly rare + VectorSet(ent->modellight_ambient, 1, 1, 1); + + // move the light direction into modelspace coordinates for lighting code + Matrix4x4_Transform3x3(&ent->inversematrix, tempdiffusenormal, ent->modellight_lightdir); + if(VectorLength2(ent->modellight_lightdir) == 0) + VectorSet(ent->modellight_lightdir, 0, 0, 1); // have to set SOME valid vector here + VectorNormalize(ent->modellight_lightdir); + } +} + +#define MAX_LINEOFSIGHTTRACES 64 + +static qboolean R_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs) +{ + int i; + vec3_t boxmins, boxmaxs; + vec3_t start; + vec3_t end; + dp_model_t *model = r_refdef.scene.worldmodel; + + if (!model || !model->brush.TraceLineOfSight) + return true; + + // expand the box a little + boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0]; + boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0]; + boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1]; + boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1]; + boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2]; + boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2]; + + // return true if eye is inside enlarged box + if (BoxesOverlap(boxmins, boxmaxs, eye, eye)) + return true; + + // try center + VectorCopy(eye, start); + VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, end); + if (model->brush.TraceLineOfSight(model, start, end)) + return true; + + // try various random positions + for (i = 0;i < numsamples;i++) + { + VectorSet(end, lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2])); + if (model->brush.TraceLineOfSight(model, start, end)) + return true; + } + + return false; +} + + +static void R_View_UpdateEntityVisible (void) +{ + int i; + int renderimask; + int samples; + entity_render_t *ent; + + renderimask = r_refdef.envmap ? (RENDER_EXTERIORMODEL | RENDER_VIEWMODEL) + : r_waterstate.renderingrefraction ? (RENDER_EXTERIORMODEL | RENDER_VIEWMODEL) + : (chase_active.integer || r_waterstate.renderingscene) ? RENDER_VIEWMODEL + : RENDER_EXTERIORMODEL; + if (!r_drawviewmodel.integer) + renderimask |= RENDER_VIEWMODEL; + if (!r_drawexteriormodel.integer) + renderimask |= RENDER_EXTERIORMODEL; + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs) + { + // worldmodel can check visibility + memset(r_refdef.viewcache.entityvisible, 0, r_refdef.scene.numentities); + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + if (!(ent->flags & renderimask)) + if (!R_CullBox(ent->mins, ent->maxs) || (ent->model && ent->model->type == mod_sprite && (ent->model->sprite.sprnum_type == SPR_LABEL || ent->model->sprite.sprnum_type == SPR_LABEL_SCALE))) + if ((ent->flags & (RENDER_NODEPTHTEST | RENDER_VIEWMODEL)) || r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs(r_refdef.scene.worldmodel, r_refdef.viewcache.world_leafvisible, ent->mins, ent->maxs)) + r_refdef.viewcache.entityvisible[i] = true; + } + } + else + { + // no worldmodel or it can't check visibility + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + r_refdef.viewcache.entityvisible[i] = !(ent->flags & renderimask) && ((ent->model && ent->model->type == mod_sprite && (ent->model->sprite.sprnum_type == SPR_LABEL || ent->model->sprite.sprnum_type == SPR_LABEL_SCALE)) || !R_CullBox(ent->mins, ent->maxs)); + } + } + if(r_cullentities_trace.integer && r_refdef.scene.worldmodel->brush.TraceLineOfSight && !r_refdef.view.useclipplane && !r_trippy.integer) + // sorry, this check doesn't work for portal/reflection/refraction renders as the view origin is not useful for culling + { + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if(!(ent->flags & (RENDER_VIEWMODEL | RENDER_NOCULL | RENDER_NODEPTHTEST)) && !(ent->model && (ent->model->name[0] == '*'))) + { + samples = ent->entitynumber ? r_cullentities_trace_samples.integer : r_cullentities_trace_tempentitysamples.integer; + if (samples < 0) + continue; // temp entities do pvs only + if(R_CanSeeBox(samples, r_cullentities_trace_enlarge.value, r_refdef.view.origin, ent->mins, ent->maxs)) + ent->last_trace_visibility = realtime; + if(ent->last_trace_visibility < realtime - r_cullentities_trace_delay.value) + r_refdef.viewcache.entityvisible[i] = 0; + } + } + } +} + +/// only used if skyrendermasked, and normally returns false +int R_DrawBrushModelsSky (void) +{ + int i, sky; + entity_render_t *ent; + + sky = false; + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (!ent->model || !ent->model->DrawSky) + continue; + ent->model->DrawSky(ent); + sky = true; + } + return sky; +} + +static void R_DrawNoModel(entity_render_t *ent); +static void R_DrawModels(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + r_refdef.stats.entities++; + /* + if (ent->model && !strncmp(ent->model->name, "models/proto_", 13)) + { + vec3_t f, l, u, o; + Matrix4x4_ToVectors(&ent->matrix, f, l, u, o); + Con_Printf("R_DrawModels\n"); + Con_Printf("model %s O %f %f %f F %f %f %f L %f %f %f U %f %f %f\n", ent->model->name, o[0], o[1], o[2], f[0], f[1], f[2], l[0], l[1], l[2], u[0], u[1], u[2]); + Con_Printf("group: %i %f %i %f %i %f %i %f\n", ent->framegroupblend[0].frame, ent->framegroupblend[0].lerp, ent->framegroupblend[1].frame, ent->framegroupblend[1].lerp, ent->framegroupblend[2].frame, ent->framegroupblend[2].lerp, ent->framegroupblend[3].frame, ent->framegroupblend[3].lerp); + Con_Printf("blend: %i %f %i %f %i %f %i %f %i %f %i %f %i %f %i %f\n", ent->frameblend[0].subframe, ent->frameblend[0].lerp, ent->frameblend[1].subframe, ent->frameblend[1].lerp, ent->frameblend[2].subframe, ent->frameblend[2].lerp, ent->frameblend[3].subframe, ent->frameblend[3].lerp, ent->frameblend[4].subframe, ent->frameblend[4].lerp, ent->frameblend[5].subframe, ent->frameblend[5].lerp, ent->frameblend[6].subframe, ent->frameblend[6].lerp, ent->frameblend[7].subframe, ent->frameblend[7].lerp); + } + */ + if (ent->model && ent->model->Draw != NULL) + ent->model->Draw(ent); + else + R_DrawNoModel(ent); + } +} + +static void R_DrawModelsDepth(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawDepth != NULL) + ent->model->DrawDepth(ent); + } +} + +static void R_DrawModelsDebug(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawDebug != NULL) + ent->model->DrawDebug(ent); + } +} + +static void R_DrawModelsAddWaterPlanes(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawAddWaterPlanes != NULL) + ent->model->DrawAddWaterPlanes(ent); + } +} + +void R_HDR_UpdateIrisAdaptation(const vec3_t point) +{ + if (r_hdr_irisadaptation.integer) + { + vec3_t ambient; + vec3_t diffuse; + vec3_t diffusenormal; + vec_t brightness; + vec_t goal; + vec_t adjust; + vec_t current; + R_CompleteLightPoint(ambient, diffuse, diffusenormal, point, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); + brightness = (ambient[0] + ambient[1] + ambient[2] + diffuse[0] + diffuse[1] + diffuse[2]) * (1.0f / 3.0f); + brightness = max(0.0000001f, brightness); + goal = r_hdr_irisadaptation_multiplier.value / brightness; + goal = bound(r_hdr_irisadaptation_minvalue.value, goal, r_hdr_irisadaptation_maxvalue.value); + adjust = r_hdr_irisadaptation_fade.value * cl.realframetime; + current = r_hdr_irisadaptation_value.value; + if (current < goal) + current = min(current + adjust, goal); + else if (current > goal) + current = max(current - adjust, goal); + if (fabs(r_hdr_irisadaptation_value.value - current) > 0.0001f) + Cvar_SetValueQuick(&r_hdr_irisadaptation_value, current); + } + else if (r_hdr_irisadaptation_value.value != 1.0f) + Cvar_SetValueQuick(&r_hdr_irisadaptation_value, 1.0f); +} + +static void R_View_SetFrustum(const int *scissor) +{ + int i; + double fpx = +1, fnx = -1, fpy = +1, fny = -1; + vec3_t forward, left, up, origin, v; + + if(scissor) + { + // flipped x coordinates (because x points left here) + fpx = 1.0 - 2.0 * (scissor[0] - r_refdef.view.viewport.x) / (double) (r_refdef.view.viewport.width); + fnx = 1.0 - 2.0 * (scissor[0] + scissor[2] - r_refdef.view.viewport.x) / (double) (r_refdef.view.viewport.width); + + // D3D Y coordinate is top to bottom, OpenGL is bottom to top, fix the D3D one + switch(vid.renderpath) + { + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + // non-flipped y coordinates + fny = -1.0 + 2.0 * (vid.height - scissor[1] - scissor[3] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); + fpy = -1.0 + 2.0 * (vid.height - scissor[1] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); + break; + case RENDERPATH_SOFT: + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // non-flipped y coordinates + fny = -1.0 + 2.0 * (scissor[1] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); + fpy = -1.0 + 2.0 * (scissor[1] + scissor[3] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); + break; + } + } + + // we can't trust r_refdef.view.forward and friends in reflected scenes + Matrix4x4_ToVectors(&r_refdef.view.matrix, forward, left, up, origin); + +#if 0 + r_refdef.view.frustum[0].normal[0] = 0 - 1.0 / r_refdef.view.frustum_x; + r_refdef.view.frustum[0].normal[1] = 0 - 0; + r_refdef.view.frustum[0].normal[2] = -1 - 0; + r_refdef.view.frustum[1].normal[0] = 0 + 1.0 / r_refdef.view.frustum_x; + r_refdef.view.frustum[1].normal[1] = 0 + 0; + r_refdef.view.frustum[1].normal[2] = -1 + 0; + r_refdef.view.frustum[2].normal[0] = 0 - 0; + r_refdef.view.frustum[2].normal[1] = 0 - 1.0 / r_refdef.view.frustum_y; + r_refdef.view.frustum[2].normal[2] = -1 - 0; + r_refdef.view.frustum[3].normal[0] = 0 + 0; + r_refdef.view.frustum[3].normal[1] = 0 + 1.0 / r_refdef.view.frustum_y; + r_refdef.view.frustum[3].normal[2] = -1 + 0; +#endif + +#if 0 + zNear = r_refdef.nearclip; + nudge = 1.0 - 1.0 / (1<<23); + r_refdef.view.frustum[4].normal[0] = 0 - 0; + r_refdef.view.frustum[4].normal[1] = 0 - 0; + r_refdef.view.frustum[4].normal[2] = -1 - -nudge; + r_refdef.view.frustum[4].dist = 0 - -2 * zNear * nudge; + r_refdef.view.frustum[5].normal[0] = 0 + 0; + r_refdef.view.frustum[5].normal[1] = 0 + 0; + r_refdef.view.frustum[5].normal[2] = -1 + -nudge; + r_refdef.view.frustum[5].dist = 0 + -2 * zNear * nudge; +#endif + + + +#if 0 + r_refdef.view.frustum[0].normal[0] = m[3] - m[0]; + r_refdef.view.frustum[0].normal[1] = m[7] - m[4]; + r_refdef.view.frustum[0].normal[2] = m[11] - m[8]; + r_refdef.view.frustum[0].dist = m[15] - m[12]; + + r_refdef.view.frustum[1].normal[0] = m[3] + m[0]; + r_refdef.view.frustum[1].normal[1] = m[7] + m[4]; + r_refdef.view.frustum[1].normal[2] = m[11] + m[8]; + r_refdef.view.frustum[1].dist = m[15] + m[12]; + + r_refdef.view.frustum[2].normal[0] = m[3] - m[1]; + r_refdef.view.frustum[2].normal[1] = m[7] - m[5]; + r_refdef.view.frustum[2].normal[2] = m[11] - m[9]; + r_refdef.view.frustum[2].dist = m[15] - m[13]; + + r_refdef.view.frustum[3].normal[0] = m[3] + m[1]; + r_refdef.view.frustum[3].normal[1] = m[7] + m[5]; + r_refdef.view.frustum[3].normal[2] = m[11] + m[9]; + r_refdef.view.frustum[3].dist = m[15] + m[13]; + + r_refdef.view.frustum[4].normal[0] = m[3] - m[2]; + r_refdef.view.frustum[4].normal[1] = m[7] - m[6]; + r_refdef.view.frustum[4].normal[2] = m[11] - m[10]; + r_refdef.view.frustum[4].dist = m[15] - m[14]; + + r_refdef.view.frustum[5].normal[0] = m[3] + m[2]; + r_refdef.view.frustum[5].normal[1] = m[7] + m[6]; + r_refdef.view.frustum[5].normal[2] = m[11] + m[10]; + r_refdef.view.frustum[5].dist = m[15] + m[14]; +#endif + + if (r_refdef.view.useperspective) + { + // calculate frustum corners, which are used to calculate deformed frustum planes for shadow caster culling + VectorMAMAM(1024, forward, fnx * 1024.0 * r_refdef.view.frustum_x, left, fny * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[0]); + VectorMAMAM(1024, forward, fpx * 1024.0 * r_refdef.view.frustum_x, left, fny * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[1]); + VectorMAMAM(1024, forward, fnx * 1024.0 * r_refdef.view.frustum_x, left, fpy * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[2]); + VectorMAMAM(1024, forward, fpx * 1024.0 * r_refdef.view.frustum_x, left, fpy * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[3]); + + // then the normals from the corners relative to origin + CrossProduct(r_refdef.view.frustumcorner[2], r_refdef.view.frustumcorner[0], r_refdef.view.frustum[0].normal); + CrossProduct(r_refdef.view.frustumcorner[1], r_refdef.view.frustumcorner[3], r_refdef.view.frustum[1].normal); + CrossProduct(r_refdef.view.frustumcorner[0], r_refdef.view.frustumcorner[1], r_refdef.view.frustum[2].normal); + CrossProduct(r_refdef.view.frustumcorner[3], r_refdef.view.frustumcorner[2], r_refdef.view.frustum[3].normal); + + // in a NORMAL view, forward cross left == up + // in a REFLECTED view, forward cross left == down + // so our cross products above need to be adjusted for a left handed coordinate system + CrossProduct(forward, left, v); + if(DotProduct(v, up) < 0) + { + VectorNegate(r_refdef.view.frustum[0].normal, r_refdef.view.frustum[0].normal); + VectorNegate(r_refdef.view.frustum[1].normal, r_refdef.view.frustum[1].normal); + VectorNegate(r_refdef.view.frustum[2].normal, r_refdef.view.frustum[2].normal); + VectorNegate(r_refdef.view.frustum[3].normal, r_refdef.view.frustum[3].normal); + } + + // Leaving those out was a mistake, those were in the old code, and they + // fix a reproducable bug in this one: frustum culling got fucked up when viewmatrix was an identity matrix + // I couldn't reproduce it after adding those normalizations. --blub + VectorNormalize(r_refdef.view.frustum[0].normal); + VectorNormalize(r_refdef.view.frustum[1].normal); + VectorNormalize(r_refdef.view.frustum[2].normal); + VectorNormalize(r_refdef.view.frustum[3].normal); + + // make the corners absolute + VectorAdd(r_refdef.view.frustumcorner[0], r_refdef.view.origin, r_refdef.view.frustumcorner[0]); + VectorAdd(r_refdef.view.frustumcorner[1], r_refdef.view.origin, r_refdef.view.frustumcorner[1]); + VectorAdd(r_refdef.view.frustumcorner[2], r_refdef.view.origin, r_refdef.view.frustumcorner[2]); + VectorAdd(r_refdef.view.frustumcorner[3], r_refdef.view.origin, r_refdef.view.frustumcorner[3]); + + // one more normal + VectorCopy(forward, r_refdef.view.frustum[4].normal); + + r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[0].normal); + r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[1].normal); + r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[2].normal); + r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[3].normal); + r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[4].normal) + r_refdef.nearclip; + } + else + { + VectorScale(left, -r_refdef.view.ortho_x, r_refdef.view.frustum[0].normal); + VectorScale(left, r_refdef.view.ortho_x, r_refdef.view.frustum[1].normal); + VectorScale(up, -r_refdef.view.ortho_y, r_refdef.view.frustum[2].normal); + VectorScale(up, r_refdef.view.ortho_y, r_refdef.view.frustum[3].normal); + VectorCopy(forward, r_refdef.view.frustum[4].normal); + r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[0].normal) + r_refdef.view.ortho_x; + r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[1].normal) + r_refdef.view.ortho_x; + r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[2].normal) + r_refdef.view.ortho_y; + r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[3].normal) + r_refdef.view.ortho_y; + r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[4].normal) + r_refdef.nearclip; + } + r_refdef.view.numfrustumplanes = 5; + + if (r_refdef.view.useclipplane) + { + r_refdef.view.numfrustumplanes = 6; + r_refdef.view.frustum[5] = r_refdef.view.clipplane; + } + + for (i = 0;i < r_refdef.view.numfrustumplanes;i++) + PlaneClassify(r_refdef.view.frustum + i); + + // LordHavoc: note to all quake engine coders, Quake had a special case + // for 90 degrees which assumed a square view (wrong), so I removed it, + // Quake2 has it disabled as well. + + // rotate R_VIEWFORWARD right by FOV_X/2 degrees + //RotatePointAroundVector( r_refdef.view.frustum[0].normal, up, forward, -(90 - r_refdef.fov_x / 2)); + //r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, frustum[0].normal); + //PlaneClassify(&frustum[0]); + + // rotate R_VIEWFORWARD left by FOV_X/2 degrees + //RotatePointAroundVector( r_refdef.view.frustum[1].normal, up, forward, (90 - r_refdef.fov_x / 2)); + //r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, frustum[1].normal); + //PlaneClassify(&frustum[1]); + + // rotate R_VIEWFORWARD up by FOV_X/2 degrees + //RotatePointAroundVector( r_refdef.view.frustum[2].normal, left, forward, -(90 - r_refdef.fov_y / 2)); + //r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, frustum[2].normal); + //PlaneClassify(&frustum[2]); + + // rotate R_VIEWFORWARD down by FOV_X/2 degrees + //RotatePointAroundVector( r_refdef.view.frustum[3].normal, left, forward, (90 - r_refdef.fov_y / 2)); + //r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, frustum[3].normal); + //PlaneClassify(&frustum[3]); + + // nearclip plane + //VectorCopy(forward, r_refdef.view.frustum[4].normal); + //r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, frustum[4].normal) + r_nearclip.value; + //PlaneClassify(&frustum[4]); +} + +void R_View_UpdateWithScissor(const int *myscissor) +{ + R_Main_ResizeViewCache(); + R_View_SetFrustum(myscissor); + R_View_WorldVisibility(r_refdef.view.useclipplane); + R_View_UpdateEntityVisible(); + R_View_UpdateEntityLighting(); +} + +void R_View_Update(void) +{ + R_Main_ResizeViewCache(); + R_View_SetFrustum(NULL); + R_View_WorldVisibility(r_refdef.view.useclipplane); + R_View_UpdateEntityVisible(); + R_View_UpdateEntityLighting(); +} + +float viewscalefpsadjusted = 1.0f; + +void R_GetScaledViewSize(int width, int height, int *outwidth, int *outheight) +{ + float scale = r_viewscale.value * sqrt(viewscalefpsadjusted); + scale = bound(0.03125f, scale, 1.0f); + *outwidth = (int)ceil(width * scale); + *outheight = (int)ceil(height * scale); +} + +void R_Mesh_SetMainRenderTargets(void) +{ + if (r_bloomstate.fbo_framebuffer) + R_Mesh_SetRenderTargets(r_bloomstate.fbo_framebuffer, r_bloomstate.texture_framebufferdepth, r_bloomstate.texture_framebuffercolor, NULL, NULL, NULL); + else + R_Mesh_ResetRenderTargets(); +} + +void R_SetupView(qboolean allowwaterclippingplane) +{ + const float *customclipplane = NULL; + float plane[4]; + int scaledwidth, scaledheight; + if (r_refdef.view.useclipplane && allowwaterclippingplane) + { + // LordHavoc: couldn't figure out how to make this approach the + vec_t dist = r_refdef.view.clipplane.dist - r_water_clippingplanebias.value; + vec_t viewdist = DotProduct(r_refdef.view.origin, r_refdef.view.clipplane.normal); + if (viewdist < r_refdef.view.clipplane.dist + r_water_clippingplanebias.value) + dist = r_refdef.view.clipplane.dist; + plane[0] = r_refdef.view.clipplane.normal[0]; + plane[1] = r_refdef.view.clipplane.normal[1]; + plane[2] = r_refdef.view.clipplane.normal[2]; + plane[3] = -dist; + if(vid.renderpath != RENDERPATH_SOFT) customclipplane = plane; + } + + R_GetScaledViewSize(r_refdef.view.width, r_refdef.view.height, &scaledwidth, &scaledheight); + if (!r_refdef.view.useperspective) + R_Viewport_InitOrtho(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, vid.height - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, -r_refdef.view.ortho_x, -r_refdef.view.ortho_y, r_refdef.view.ortho_x, r_refdef.view.ortho_y, -r_refdef.farclip, r_refdef.farclip, customclipplane); + else if (vid.stencil && r_useinfinitefarclip.integer) + R_Viewport_InitPerspectiveInfinite(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, vid.height - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, r_refdef.view.frustum_x, r_refdef.view.frustum_y, r_refdef.nearclip, customclipplane); + else + R_Viewport_InitPerspective(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, vid.height - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, r_refdef.view.frustum_x, r_refdef.view.frustum_y, r_refdef.nearclip, r_refdef.farclip, customclipplane); + R_Mesh_SetMainRenderTargets(); + R_SetViewport(&r_refdef.view.viewport); + if (r_refdef.view.useclipplane && allowwaterclippingplane && vid.renderpath == RENDERPATH_SOFT) + { + matrix4x4_t mvpmatrix, invmvpmatrix, invtransmvpmatrix; + float screenplane[4]; + Matrix4x4_Concat(&mvpmatrix, &r_refdef.view.viewport.projectmatrix, &r_refdef.view.viewport.viewmatrix); + Matrix4x4_Invert_Full(&invmvpmatrix, &mvpmatrix); + Matrix4x4_Transpose(&invtransmvpmatrix, &invmvpmatrix); + Matrix4x4_Transform4(&invtransmvpmatrix, plane, screenplane); + DPSOFTRAST_ClipPlane(screenplane[0], screenplane[1], screenplane[2], screenplane[3]); + } +} + +void R_EntityMatrix(const matrix4x4_t *matrix) +{ + if (gl_modelmatrixchanged || memcmp(matrix, &gl_modelmatrix, sizeof(matrix4x4_t))) + { + gl_modelmatrixchanged = false; + gl_modelmatrix = *matrix; + Matrix4x4_Concat(&gl_modelviewmatrix, &gl_viewmatrix, &gl_modelmatrix); + Matrix4x4_Concat(&gl_modelviewprojectionmatrix, &gl_projectionmatrix, &gl_modelviewmatrix); + Matrix4x4_ToArrayFloatGL(&gl_modelviewmatrix, gl_modelview16f); + Matrix4x4_ToArrayFloatGL(&gl_modelviewprojectionmatrix, gl_modelviewprojection16f); + CHECKGLERROR + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + hlslVSSetParameter16f(D3DVSREGISTER_ModelViewProjectionMatrix, gl_modelviewprojection16f); + hlslVSSetParameter16f(D3DVSREGISTER_ModelViewMatrix, gl_modelview16f); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 shader %s:%i\n", __FILE__, __LINE__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 shader %s:%i\n", __FILE__, __LINE__); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + qglLoadMatrixf(gl_modelview16f);CHECKGLERROR + break; + case RENDERPATH_SOFT: + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, 1, false, gl_modelviewprojection16f); + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewMatrixM1, 1, false, gl_modelview16f); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (r_glsl_permutation && r_glsl_permutation->loc_ModelViewProjectionMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewProjectionMatrix, 1, false, gl_modelviewprojection16f); + if (r_glsl_permutation && r_glsl_permutation->loc_ModelViewMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewMatrix, 1, false, gl_modelview16f); + break; + } + } +} + +void R_ResetViewRendering2D(void) +{ + r_viewport_t viewport; + DrawQ_Finish(); + + // GL is weird because it's bottom to top, r_refdef.view.y is top to bottom + R_Viewport_InitOrtho(&viewport, &identitymatrix, r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height, 0, 0, 1, 1, -10, 100, NULL); + R_Mesh_ResetRenderTargets(); + R_SetViewport(&viewport); + GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); + GL_Color(1, 1, 1, 1); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_ScissorTest(false); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_DepthTest(false); + GL_DepthFunc(GL_LEQUAL); + R_EntityMatrix(&identitymatrix); + R_Mesh_ResetTextureState(); + GL_PolygonOffset(0, 0); + R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglEnable(GL_POLYGON_OFFSET_FILL);CHECKGLERROR + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } + GL_CullFace(GL_NONE); +} + +void R_ResetViewRendering3D(void) +{ + DrawQ_Finish(); + + R_SetupView(true); + GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + GL_Color(1, 1, 1, 1); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_ScissorTest(true); + GL_DepthMask(true); + GL_DepthRange(0, 1); + GL_DepthTest(true); + GL_DepthFunc(GL_LEQUAL); + R_EntityMatrix(&identitymatrix); + R_Mesh_ResetTextureState(); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglEnable(GL_POLYGON_OFFSET_FILL);CHECKGLERROR + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } + GL_CullFace(r_refdef.view.cullface_back); +} + +/* +================ +R_RenderView_UpdateViewVectors +================ +*/ +static void R_RenderView_UpdateViewVectors(void) +{ + // break apart the view matrix into vectors for various purposes + // it is important that this occurs outside the RenderScene function because that can be called from reflection renders, where the vectors come out wrong + // however the r_refdef.view.origin IS updated in RenderScene intentionally - otherwise the sky renders at the wrong origin, etc + Matrix4x4_ToVectors(&r_refdef.view.matrix, r_refdef.view.forward, r_refdef.view.left, r_refdef.view.up, r_refdef.view.origin); + VectorNegate(r_refdef.view.left, r_refdef.view.right); + // make an inverted copy of the view matrix for tracking sprites + Matrix4x4_Invert_Simple(&r_refdef.view.inverse_matrix, &r_refdef.view.matrix); +} + +void R_RenderScene(void); +void R_RenderWaterPlanes(void); + +static void R_Water_StartFrame(void) +{ + int i; + int waterwidth, waterheight, texturewidth, textureheight, camerawidth, cameraheight; + r_waterstate_waterplane_t *p; + + if (vid.width > (int)vid.maxtexturesize_2d || vid.height > (int)vid.maxtexturesize_2d) + return; + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + return; + } + + // set waterwidth and waterheight to the water resolution that will be + // used (often less than the screen resolution for faster rendering) + R_GetScaledViewSize(bound(1, vid.width * r_water_resolutionmultiplier.value, vid.width), bound(1, vid.height * r_water_resolutionmultiplier.value, vid.height), &waterwidth, &waterheight); + + // calculate desired texture sizes + // can't use water if the card does not support the texture size + if (!r_water.integer || r_showsurfaces.integer) + texturewidth = textureheight = waterwidth = waterheight = camerawidth = cameraheight = 0; + else if (vid.support.arb_texture_non_power_of_two) + { + texturewidth = waterwidth; + textureheight = waterheight; + camerawidth = waterwidth; + cameraheight = waterheight; + } + else + { + for (texturewidth = 1;texturewidth < waterwidth ;texturewidth *= 2); + for (textureheight = 1;textureheight < waterheight;textureheight *= 2); + for (camerawidth = 1;camerawidth <= waterwidth; camerawidth *= 2); camerawidth /= 2; + for (cameraheight = 1;cameraheight <= waterheight;cameraheight *= 2); cameraheight /= 2; + } + + // allocate textures as needed + if (r_waterstate.texturewidth != texturewidth || r_waterstate.textureheight != textureheight || r_waterstate.camerawidth != camerawidth || r_waterstate.cameraheight != cameraheight) + { + r_waterstate.maxwaterplanes = MAX_WATERPLANES; + for (i = 0, p = r_waterstate.waterplanes;i < r_waterstate.maxwaterplanes;i++, p++) + { + if (p->texture_refraction) + R_FreeTexture(p->texture_refraction); + p->texture_refraction = NULL; + if (p->texture_reflection) + R_FreeTexture(p->texture_reflection); + p->texture_reflection = NULL; + if (p->texture_camera) + R_FreeTexture(p->texture_camera); + p->texture_camera = NULL; + } + memset(&r_waterstate, 0, sizeof(r_waterstate)); + r_waterstate.texturewidth = texturewidth; + r_waterstate.textureheight = textureheight; + r_waterstate.camerawidth = camerawidth; + r_waterstate.cameraheight = cameraheight; + } + + if (r_waterstate.texturewidth) + { + int scaledwidth, scaledheight; + + r_waterstate.enabled = true; + + // when doing a reduced render (HDR) we want to use a smaller area + r_waterstate.waterwidth = (int)bound(1, r_refdef.view.width * r_water_resolutionmultiplier.value, r_refdef.view.width); + r_waterstate.waterheight = (int)bound(1, r_refdef.view.height * r_water_resolutionmultiplier.value, r_refdef.view.height); + R_GetScaledViewSize(r_waterstate.waterwidth, r_waterstate.waterheight, &scaledwidth, &scaledheight); + + // set up variables that will be used in shader setup + r_waterstate.screenscale[0] = 0.5f * (float)scaledwidth / (float)r_waterstate.texturewidth; + r_waterstate.screenscale[1] = 0.5f * (float)scaledheight / (float)r_waterstate.textureheight; + r_waterstate.screencenter[0] = 0.5f * (float)scaledwidth / (float)r_waterstate.texturewidth; + r_waterstate.screencenter[1] = 0.5f * (float)scaledheight / (float)r_waterstate.textureheight; + } + + r_waterstate.maxwaterplanes = MAX_WATERPLANES; + r_waterstate.numwaterplanes = 0; +} + +void R_Water_AddWaterPlane(msurface_t *surface, int entno) +{ + int triangleindex, planeindex; + const int *e; + vec3_t vert[3]; + vec3_t normal; + vec3_t center; + mplane_t plane; + r_waterstate_waterplane_t *p; + texture_t *t = R_GetCurrentTexture(surface->texture); + + // just use the first triangle with a valid normal for any decisions + VectorClear(normal); + for (triangleindex = 0, e = rsurface.modelelement3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + Matrix4x4_Transform(&rsurface.matrix, rsurface.modelvertex3f + e[0]*3, vert[0]); + Matrix4x4_Transform(&rsurface.matrix, rsurface.modelvertex3f + e[1]*3, vert[1]); + Matrix4x4_Transform(&rsurface.matrix, rsurface.modelvertex3f + e[2]*3, vert[2]); + TriangleNormal(vert[0], vert[1], vert[2], normal); + if (VectorLength2(normal) >= 0.001) + break; + } + + VectorCopy(normal, plane.normal); + VectorNormalize(plane.normal); + plane.dist = DotProduct(vert[0], plane.normal); + PlaneClassify(&plane); + if (PlaneDiff(r_refdef.view.origin, &plane) < 0) + { + // skip backfaces (except if nocullface is set) + if (!(t->currentmaterialflags & MATERIALFLAG_NOCULLFACE)) + return; + VectorNegate(plane.normal, plane.normal); + plane.dist *= -1; + PlaneClassify(&plane); + } + + + // find a matching plane if there is one + for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++) + if(p->camera_entity == t->camera_entity) + if (fabs(PlaneDiff(vert[0], &p->plane)) < 1 && fabs(PlaneDiff(vert[1], &p->plane)) < 1 && fabs(PlaneDiff(vert[2], &p->plane)) < 1) + break; + if (planeindex >= r_waterstate.maxwaterplanes) + return; // nothing we can do, out of planes + + // if this triangle does not fit any known plane rendered this frame, add one + if (planeindex >= r_waterstate.numwaterplanes) + { + // store the new plane + r_waterstate.numwaterplanes++; + p->plane = plane; + // clear materialflags and pvs + p->materialflags = 0; + p->pvsvalid = false; + p->camera_entity = t->camera_entity; + VectorCopy(surface->mins, p->mins); + VectorCopy(surface->maxs, p->maxs); + } + else + { + // merge mins/maxs + p->mins[0] = min(p->mins[0], surface->mins[0]); + p->mins[1] = min(p->mins[1], surface->mins[1]); + p->mins[2] = min(p->mins[2], surface->mins[2]); + p->maxs[0] = max(p->maxs[0], surface->maxs[0]); + p->maxs[1] = max(p->maxs[1], surface->maxs[1]); + p->maxs[2] = max(p->maxs[2], surface->maxs[2]); + } + // merge this surface's materialflags into the waterplane + p->materialflags |= t->currentmaterialflags; + if(!(p->materialflags & MATERIALFLAG_CAMERA)) + { + // merge this surface's PVS into the waterplane + VectorMAM(0.5f, surface->mins, 0.5f, surface->maxs, center); + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS + && r_refdef.scene.worldmodel->brush.PointInLeaf && r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, center)->clusterindex >= 0) + { + r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, center, 2, p->pvsbits, sizeof(p->pvsbits), p->pvsvalid); + p->pvsvalid = true; + } + } +} + +extern cvar_t r_drawparticles; +extern cvar_t r_drawdecals; + +static void R_Water_ProcessPlanes(void) +{ + int myscissor[4]; + r_refdef_view_t originalview; + r_refdef_view_t myview; + int planeindex, qualityreduction = 0, old_r_dynamic = 0, old_r_shadows = 0, old_r_worldrtlight = 0, old_r_dlight = 0, old_r_particles = 0, old_r_decals = 0; + r_waterstate_waterplane_t *p; + vec3_t visorigin; + + originalview = r_refdef.view; + + // lowquality hack, temporarily shut down some cvars and restore afterwards + qualityreduction = r_water_lowquality.integer; + if (qualityreduction > 0) + { + if (qualityreduction >= 1) + { + old_r_shadows = r_shadows.integer; + old_r_worldrtlight = r_shadow_realtime_world.integer; + old_r_dlight = r_shadow_realtime_dlight.integer; + Cvar_SetValueQuick(&r_shadows, 0); + Cvar_SetValueQuick(&r_shadow_realtime_world, 0); + Cvar_SetValueQuick(&r_shadow_realtime_dlight, 0); + } + if (qualityreduction >= 2) + { + old_r_dynamic = r_dynamic.integer; + old_r_particles = r_drawparticles.integer; + old_r_decals = r_drawdecals.integer; + Cvar_SetValueQuick(&r_dynamic, 0); + Cvar_SetValueQuick(&r_drawparticles, 0); + Cvar_SetValueQuick(&r_drawdecals, 0); + } + } + + // make sure enough textures are allocated + for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++) + { + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) + { + if (!p->texture_refraction) + p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_refraction", planeindex), r_waterstate.texturewidth, r_waterstate.textureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + if (!p->texture_refraction) + goto error; + } + else if (p->materialflags & MATERIALFLAG_CAMERA) + { + if (!p->texture_camera) + p->texture_camera = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_camera", planeindex), r_waterstate.camerawidth, r_waterstate.cameraheight, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCELINEAR, -1, NULL); + if (!p->texture_camera) + goto error; + } + + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION)) + { + if (!p->texture_reflection) + p->texture_reflection = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_reflection", planeindex), r_waterstate.texturewidth, r_waterstate.textureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + if (!p->texture_reflection) + goto error; + } + } + + // render views + r_refdef.view = originalview; + r_refdef.view.showdebug = false; + r_refdef.view.width = r_waterstate.waterwidth; + r_refdef.view.height = r_waterstate.waterheight; + r_refdef.view.useclipplane = true; + myview = r_refdef.view; + r_waterstate.renderingscene = true; + for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++) + { + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION)) + { + r_refdef.view = myview; + if(r_water_scissormode.integer) + { + R_SetupView(true); + if(R_ScissorForBBox(p->mins, p->maxs, myscissor)) + continue; // FIXME the plane then still may get rendered but with broken texture, but it sure won't be visible + } + + // render reflected scene and copy into texture + Matrix4x4_Reflect(&r_refdef.view.matrix, p->plane.normal[0], p->plane.normal[1], p->plane.normal[2], p->plane.dist, -2); + // update the r_refdef.view.origin because otherwise the sky renders at the wrong location (amongst other problems) + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, r_refdef.view.origin); + r_refdef.view.clipplane = p->plane; + // reverse the cullface settings for this render + r_refdef.view.cullface_front = GL_FRONT; + r_refdef.view.cullface_back = GL_BACK; + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.num_pvsclusterbytes) + { + r_refdef.view.usecustompvs = true; + if (p->pvsvalid) + memcpy(r_refdef.viewcache.world_pvsbits, p->pvsbits, r_refdef.scene.worldmodel->brush.num_pvsclusterbytes); + else + memset(r_refdef.viewcache.world_pvsbits, 0xFF, r_refdef.scene.worldmodel->brush.num_pvsclusterbytes); + } + + R_ResetViewRendering3D(); + R_ClearScreen(r_refdef.fogenabled); + if(r_water_scissormode.integer & 2) + R_View_UpdateWithScissor(myscissor); + else + R_View_Update(); + if(r_water_scissormode.integer & 1) + GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]); + R_RenderScene(); + + R_Mesh_CopyToTexture(p->texture_reflection, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + } + + // render the normal view scene and copy into texture + // (except that a clipping plane should be used to hide everything on one side of the water, and the viewer's weapon model should be omitted) + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) + { + r_refdef.view = myview; + if(r_water_scissormode.integer) + { + R_SetupView(true); + if(R_ScissorForBBox(p->mins, p->maxs, myscissor)) + continue; // FIXME the plane then still may get rendered but with broken texture, but it sure won't be visible + } + + r_waterstate.renderingrefraction = true; + + r_refdef.view.clipplane = p->plane; + VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal); + r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist; + + if((p->materialflags & MATERIALFLAG_CAMERA) && p->camera_entity) + { + // we need to perform a matrix transform to render the view... so let's get the transformation matrix + r_waterstate.renderingrefraction = false; // we don't want to hide the player model from these ones + CL_VM_TransformView(p->camera_entity - MAX_EDICTS, &r_refdef.view.matrix, &r_refdef.view.clipplane, visorigin); + R_RenderView_UpdateViewVectors(); + if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS) + { + r_refdef.view.usecustompvs = true; + r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, visorigin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); + } + } + + PlaneClassify(&r_refdef.view.clipplane); + + R_ResetViewRendering3D(); + R_ClearScreen(r_refdef.fogenabled); + if(r_water_scissormode.integer & 2) + R_View_UpdateWithScissor(myscissor); + else + R_View_Update(); + if(r_water_scissormode.integer & 1) + GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]); + R_RenderScene(); + + R_Mesh_CopyToTexture(p->texture_refraction, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_waterstate.renderingrefraction = false; + } + else if (p->materialflags & MATERIALFLAG_CAMERA) + { + r_refdef.view = myview; + + r_refdef.view.clipplane = p->plane; + VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal); + r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist; + + r_refdef.view.width = r_waterstate.camerawidth; + r_refdef.view.height = r_waterstate.cameraheight; + r_refdef.view.frustum_x = 1; // tan(45 * M_PI / 180.0); + r_refdef.view.frustum_y = 1; // tan(45 * M_PI / 180.0); + + if(p->camera_entity) + { + // we need to perform a matrix transform to render the view... so let's get the transformation matrix + CL_VM_TransformView(p->camera_entity - MAX_EDICTS, &r_refdef.view.matrix, &r_refdef.view.clipplane, visorigin); + } + + // note: all of the view is used for displaying... so + // there is no use in scissoring + + // reverse the cullface settings for this render + r_refdef.view.cullface_front = GL_FRONT; + r_refdef.view.cullface_back = GL_BACK; + // also reverse the view matrix + Matrix4x4_ConcatScale3(&r_refdef.view.matrix, 1, 1, -1); // this serves to invert texcoords in the result, as the copied texture is mapped the wrong way round + R_RenderView_UpdateViewVectors(); + if(p->camera_entity && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS) + { + r_refdef.view.usecustompvs = true; + r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, visorigin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); + } + + // camera needs no clipplane + r_refdef.view.useclipplane = false; + + PlaneClassify(&r_refdef.view.clipplane); + + R_ResetViewRendering3D(); + R_ClearScreen(r_refdef.fogenabled); + R_View_Update(); + R_RenderScene(); + + R_Mesh_CopyToTexture(p->texture_camera, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_waterstate.renderingrefraction = false; + } + + } + if(vid.renderpath==RENDERPATH_SOFT) DPSOFTRAST_ClipPlane(0, 0, 0, 1); + r_waterstate.renderingscene = false; + r_refdef.view = originalview; + R_ResetViewRendering3D(); + R_ClearScreen(r_refdef.fogenabled); + R_View_Update(); + goto finish; +error: + r_refdef.view = originalview; + r_waterstate.renderingscene = false; + Cvar_SetValueQuick(&r_water, 0); + Con_Printf("R_Water_ProcessPlanes: Error: texture creation failed! Turned off r_water.\n"); +finish: + // lowquality hack, restore cvars + if (qualityreduction > 0) + { + if (qualityreduction >= 1) + { + Cvar_SetValueQuick(&r_shadows, old_r_shadows); + Cvar_SetValueQuick(&r_shadow_realtime_world, old_r_worldrtlight); + Cvar_SetValueQuick(&r_shadow_realtime_dlight, old_r_dlight); + } + if (qualityreduction >= 2) + { + Cvar_SetValueQuick(&r_dynamic, old_r_dynamic); + Cvar_SetValueQuick(&r_drawparticles, old_r_particles); + Cvar_SetValueQuick(&r_drawdecals, old_r_decals); + } + } +} + +void R_Bloom_StartFrame(void) +{ + int bloomtexturewidth, bloomtextureheight, screentexturewidth, screentextureheight; + int viewwidth, viewheight; + textype_t textype; + + if (r_viewscale_fpsscaling.integer) + { + double actualframetime; + double targetframetime; + double adjust; + actualframetime = r_refdef.lastdrawscreentime; + targetframetime = (1.0 / r_viewscale_fpsscaling_target.value); + adjust = (targetframetime - actualframetime) * r_viewscale_fpsscaling_multiply.value; + adjust = bound(-r_viewscale_fpsscaling_stepmax.value, adjust, r_viewscale_fpsscaling_stepmax.value); + if (r_viewscale_fpsscaling_stepsize.value > 0) + adjust = (int)(adjust / r_viewscale_fpsscaling_stepsize.value) * r_viewscale_fpsscaling_stepsize.value; + viewscalefpsadjusted += adjust; + viewscalefpsadjusted = bound(r_viewscale_fpsscaling_min.value, viewscalefpsadjusted, 1.0f); + } + else + viewscalefpsadjusted = 1.0f; + + R_GetScaledViewSize(r_refdef.view.width, r_refdef.view.height, &viewwidth, &viewheight); + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + return; + } + + // set bloomwidth and bloomheight to the bloom resolution that will be + // used (often less than the screen resolution for faster rendering) + r_bloomstate.bloomwidth = bound(1, r_bloom_resolution.integer, vid.height); + r_bloomstate.bloomheight = r_bloomstate.bloomwidth * vid.height / vid.width; + r_bloomstate.bloomheight = bound(1, r_bloomstate.bloomheight, vid.height); + r_bloomstate.bloomwidth = bound(1, r_bloomstate.bloomwidth, (int)vid.maxtexturesize_2d); + r_bloomstate.bloomheight = bound(1, r_bloomstate.bloomheight, (int)vid.maxtexturesize_2d); + + // calculate desired texture sizes + if (vid.support.arb_texture_non_power_of_two) + { + screentexturewidth = vid.width; + screentextureheight = vid.height; + bloomtexturewidth = r_bloomstate.bloomwidth; + bloomtextureheight = r_bloomstate.bloomheight; + } + else + { + for (screentexturewidth = 1;screentexturewidth < vid.width ;screentexturewidth *= 2); + for (screentextureheight = 1;screentextureheight < vid.height ;screentextureheight *= 2); + for (bloomtexturewidth = 1;bloomtexturewidth < r_bloomstate.bloomwidth ;bloomtexturewidth *= 2); + for (bloomtextureheight = 1;bloomtextureheight < r_bloomstate.bloomheight;bloomtextureheight *= 2); + } + + if ((r_hdr.integer || r_bloom.integer || (!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0))) && ((r_bloom_resolution.integer < 4 || r_bloom_blur.value < 1 || r_bloom_blur.value >= 512) || r_refdef.view.width > (int)vid.maxtexturesize_2d || r_refdef.view.height > (int)vid.maxtexturesize_2d)) + { + Cvar_SetValueQuick(&r_hdr, 0); + Cvar_SetValueQuick(&r_bloom, 0); + Cvar_SetValueQuick(&r_motionblur, 0); + Cvar_SetValueQuick(&r_damageblur, 0); + } + + if (!(r_glsl_postprocess.integer || (!R_Stereo_ColorMasking() && r_glsl_saturation.value != 1) || (v_glslgamma.integer && !vid_gammatables_trivial)) && !r_bloom.integer && !r_hdr.integer && (R_Stereo_Active() || (r_motionblur.value <= 0 && r_damageblur.value <= 0)) && r_viewfbo.integer < 1 && r_viewscale.value == 1.0f && !r_viewscale_fpsscaling.integer) + screentexturewidth = screentextureheight = 0; + if (!r_hdr.integer && !r_bloom.integer) + bloomtexturewidth = bloomtextureheight = 0; + + textype = TEXTYPE_COLORBUFFER; + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (vid.support.ext_framebuffer_object) + { + if (r_viewfbo.integer == 2) textype = TEXTYPE_COLORBUFFER16F; + if (r_viewfbo.integer == 3) textype = TEXTYPE_COLORBUFFER32F; + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } + + // allocate textures as needed + if (r_bloomstate.screentexturewidth != screentexturewidth + || r_bloomstate.screentextureheight != screentextureheight + || r_bloomstate.bloomtexturewidth != bloomtexturewidth + || r_bloomstate.bloomtextureheight != bloomtextureheight + || r_bloomstate.texturetype != textype + || r_bloomstate.viewfbo != r_viewfbo.integer) + { + if (r_bloomstate.texture_bloom) + R_FreeTexture(r_bloomstate.texture_bloom); + r_bloomstate.texture_bloom = NULL; + if (r_bloomstate.texture_screen) + R_FreeTexture(r_bloomstate.texture_screen); + r_bloomstate.texture_screen = NULL; + if (r_bloomstate.fbo_framebuffer) + R_Mesh_DestroyFramebufferObject(r_bloomstate.fbo_framebuffer); + r_bloomstate.fbo_framebuffer = 0; + if (r_bloomstate.texture_framebuffercolor) + R_FreeTexture(r_bloomstate.texture_framebuffercolor); + r_bloomstate.texture_framebuffercolor = NULL; + if (r_bloomstate.texture_framebufferdepth) + R_FreeTexture(r_bloomstate.texture_framebufferdepth); + r_bloomstate.texture_framebufferdepth = NULL; + r_bloomstate.screentexturewidth = screentexturewidth; + r_bloomstate.screentextureheight = screentextureheight; + if (r_bloomstate.screentexturewidth && r_bloomstate.screentextureheight) + r_bloomstate.texture_screen = R_LoadTexture2D(r_main_texturepool, "screen", r_bloomstate.screentexturewidth, r_bloomstate.screentextureheight, NULL, textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + if (r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object) + { + // FIXME: choose depth bits based on a cvar + r_bloomstate.texture_framebufferdepth = R_LoadTextureShadowMap2D(r_main_texturepool, "framebufferdepth", r_bloomstate.screentexturewidth, r_bloomstate.screentextureheight, 24, false); + r_bloomstate.texture_framebuffercolor = R_LoadTexture2D(r_main_texturepool, "framebuffercolor", r_bloomstate.screentexturewidth, r_bloomstate.screentextureheight, NULL, textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + r_bloomstate.fbo_framebuffer = R_Mesh_CreateFramebufferObject(r_bloomstate.texture_framebufferdepth, r_bloomstate.texture_framebuffercolor, NULL, NULL, NULL); + R_Mesh_SetRenderTargets(r_bloomstate.fbo_framebuffer, r_bloomstate.texture_framebufferdepth, r_bloomstate.texture_framebuffercolor, NULL, NULL, NULL); + // render depth into one texture and normalmap into the other + if (qglDrawBuffer) + { + int status; + qglDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);CHECKGLERROR + qglReadBuffer(GL_COLOR_ATTACHMENT0_EXT);CHECKGLERROR + status = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);CHECKGLERROR + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) + Con_Printf("R_Bloom_StartFrame: glCheckFramebufferStatusEXT returned %i\n", status); + } + } + r_bloomstate.bloomtexturewidth = bloomtexturewidth; + r_bloomstate.bloomtextureheight = bloomtextureheight; + if (r_bloomstate.bloomtexturewidth && r_bloomstate.bloomtextureheight) + r_bloomstate.texture_bloom = R_LoadTexture2D(r_main_texturepool, "bloom", r_bloomstate.bloomtexturewidth, r_bloomstate.bloomtextureheight, NULL, textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + r_bloomstate.viewfbo = r_viewfbo.integer; + r_bloomstate.texturetype = textype; + } + + // when doing a reduced render (HDR) we want to use a smaller area + r_bloomstate.bloomwidth = bound(1, r_bloom_resolution.integer, r_refdef.view.height); + r_bloomstate.bloomheight = r_bloomstate.bloomwidth * r_refdef.view.height / r_refdef.view.width; + r_bloomstate.bloomheight = bound(1, r_bloomstate.bloomheight, r_refdef.view.height); + r_bloomstate.bloomwidth = bound(1, r_bloomstate.bloomwidth, r_bloomstate.bloomtexturewidth); + r_bloomstate.bloomheight = bound(1, r_bloomstate.bloomheight, r_bloomstate.bloomtextureheight); + + // set up a texcoord array for the full resolution screen image + // (we have to keep this around to copy back during final render) + r_bloomstate.screentexcoord2f[0] = 0; + r_bloomstate.screentexcoord2f[1] = (float)viewheight / (float)r_bloomstate.screentextureheight; + r_bloomstate.screentexcoord2f[2] = (float)viewwidth / (float)r_bloomstate.screentexturewidth; + r_bloomstate.screentexcoord2f[3] = (float)viewheight / (float)r_bloomstate.screentextureheight; + r_bloomstate.screentexcoord2f[4] = (float)viewwidth / (float)r_bloomstate.screentexturewidth; + r_bloomstate.screentexcoord2f[5] = 0; + r_bloomstate.screentexcoord2f[6] = 0; + r_bloomstate.screentexcoord2f[7] = 0; + + // set up a texcoord array for the reduced resolution bloom image + // (which will be additive blended over the screen image) + r_bloomstate.bloomtexcoord2f[0] = 0; + r_bloomstate.bloomtexcoord2f[1] = (float)r_bloomstate.bloomheight / (float)r_bloomstate.bloomtextureheight; + r_bloomstate.bloomtexcoord2f[2] = (float)r_bloomstate.bloomwidth / (float)r_bloomstate.bloomtexturewidth; + r_bloomstate.bloomtexcoord2f[3] = (float)r_bloomstate.bloomheight / (float)r_bloomstate.bloomtextureheight; + r_bloomstate.bloomtexcoord2f[4] = (float)r_bloomstate.bloomwidth / (float)r_bloomstate.bloomtexturewidth; + r_bloomstate.bloomtexcoord2f[5] = 0; + r_bloomstate.bloomtexcoord2f[6] = 0; + r_bloomstate.bloomtexcoord2f[7] = 0; + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + { + int i; + for (i = 0;i < 4;i++) + { + r_bloomstate.screentexcoord2f[i*2+0] += 0.5f / (float)r_bloomstate.screentexturewidth; + r_bloomstate.screentexcoord2f[i*2+1] += 0.5f / (float)r_bloomstate.screentextureheight; + r_bloomstate.bloomtexcoord2f[i*2+0] += 0.5f / (float)r_bloomstate.bloomtexturewidth; + r_bloomstate.bloomtexcoord2f[i*2+1] += 0.5f / (float)r_bloomstate.bloomtextureheight; + } + } + break; + } + + if ((r_hdr.integer || r_bloom.integer) && r_bloomstate.bloomwidth) + { + r_bloomstate.enabled = true; + r_bloomstate.hdr = r_hdr.integer != 0 && !r_bloomstate.fbo_framebuffer; + } + + R_Viewport_InitOrtho(&r_bloomstate.viewport, &identitymatrix, r_refdef.view.x, vid.height - r_bloomstate.bloomheight - r_refdef.view.y, r_bloomstate.bloomwidth, r_bloomstate.bloomheight, 0, 0, 1, 1, -10, 100, NULL); + + if (r_bloomstate.fbo_framebuffer) + r_refdef.view.clear = true; +} + +void R_Bloom_CopyBloomTexture(float colorscale) +{ + r_refdef.stats.bloom++; + + // scale down screen texture to the bloom texture size + CHECKGLERROR + R_Mesh_SetMainRenderTargets(); + R_SetViewport(&r_bloomstate.viewport); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_Color(colorscale, colorscale, colorscale, 1); + // D3D has upside down Y coords, the easiest way to flip this is to flip the screen vertices rather than the texcoords, so we just use a different array for that... + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_SOFT: + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_bloomstate.screentexcoord2f); + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_bloomstate.screentexcoord2f); + break; + } + // TODO: do boxfilter scale-down in shader? + R_SetupShader_Generic(r_bloomstate.texture_screen, NULL, GL_MODULATE, 1, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats.bloom_drawpixels += r_bloomstate.bloomwidth * r_bloomstate.bloomheight; + + // we now have a bloom image in the framebuffer + // copy it into the bloom image texture for later processing + R_Mesh_CopyToTexture(r_bloomstate.texture_bloom, 0, 0, r_bloomstate.viewport.x, r_bloomstate.viewport.y, r_bloomstate.viewport.width, r_bloomstate.viewport.height); + r_refdef.stats.bloom_copypixels += r_bloomstate.viewport.width * r_bloomstate.viewport.height; +} + +void R_Bloom_CopyHDRTexture(void) +{ + R_Mesh_CopyToTexture(r_bloomstate.texture_bloom, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height; +} + +void R_Bloom_MakeTexture(void) +{ + int x, range, dir; + float xoffset, yoffset, r, brighten; + + r_refdef.stats.bloom++; + + R_ResetViewRendering2D(); + + // we have a bloom image in the framebuffer + CHECKGLERROR + R_SetViewport(&r_bloomstate.viewport); + + for (x = 1;x < min(r_bloom_colorexponent.value, 32);) + { + x *= 2; + r = bound(0, r_bloom_colorexponent.value / x, 1); + GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + GL_Color(r,r,r,1); + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_bloomstate.bloomtexcoord2f); + R_SetupShader_Generic(r_bloomstate.texture_bloom, NULL, GL_MODULATE, 1, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats.bloom_drawpixels += r_bloomstate.bloomwidth * r_bloomstate.bloomheight; + + // copy the vertically blurred bloom view to a texture + R_Mesh_CopyToTexture(r_bloomstate.texture_bloom, 0, 0, r_bloomstate.viewport.x, r_bloomstate.viewport.y, r_bloomstate.viewport.width, r_bloomstate.viewport.height); + r_refdef.stats.bloom_copypixels += r_bloomstate.viewport.width * r_bloomstate.viewport.height; + } + + range = r_bloom_blur.integer * r_bloomstate.bloomwidth / 320; + brighten = r_bloom_brighten.value; + if (r_bloomstate.hdr) + brighten *= r_hdr_range.value; + brighten = sqrt(brighten); + if(range >= 1) + brighten *= (3 * range) / (2 * range - 1); // compensate for the "dot particle" + R_SetupShader_Generic(r_bloomstate.texture_bloom, NULL, GL_MODULATE, 1, true); + + for (dir = 0;dir < 2;dir++) + { + // blend on at multiple vertical offsets to achieve a vertical blur + // TODO: do offset blends using GLSL + // TODO instead of changing the texcoords, change the target positions to prevent artifacts at edges + GL_BlendFunc(GL_ONE, GL_ZERO); + for (x = -range;x <= range;x++) + { + if (!dir){xoffset = 0;yoffset = x;} + else {xoffset = x;yoffset = 0;} + xoffset /= (float)r_bloomstate.bloomtexturewidth; + yoffset /= (float)r_bloomstate.bloomtextureheight; + // compute a texcoord array with the specified x and y offset + r_bloomstate.offsettexcoord2f[0] = xoffset+0; + r_bloomstate.offsettexcoord2f[1] = yoffset+(float)r_bloomstate.bloomheight / (float)r_bloomstate.bloomtextureheight; + r_bloomstate.offsettexcoord2f[2] = xoffset+(float)r_bloomstate.bloomwidth / (float)r_bloomstate.bloomtexturewidth; + r_bloomstate.offsettexcoord2f[3] = yoffset+(float)r_bloomstate.bloomheight / (float)r_bloomstate.bloomtextureheight; + r_bloomstate.offsettexcoord2f[4] = xoffset+(float)r_bloomstate.bloomwidth / (float)r_bloomstate.bloomtexturewidth; + r_bloomstate.offsettexcoord2f[5] = yoffset+0; + r_bloomstate.offsettexcoord2f[6] = xoffset+0; + r_bloomstate.offsettexcoord2f[7] = yoffset+0; + // this r value looks like a 'dot' particle, fading sharply to + // black at the edges + // (probably not realistic but looks good enough) + //r = ((range*range+1)/((float)(x*x+1)))/(range*2+1); + //r = brighten/(range*2+1); + r = brighten / (range * 2 + 1); + if(range >= 1) + r *= (1 - x*x/(float)(range*range)); + GL_Color(r, r, r, 1); + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_bloomstate.offsettexcoord2f); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats.bloom_drawpixels += r_bloomstate.bloomwidth * r_bloomstate.bloomheight; + GL_BlendFunc(GL_ONE, GL_ONE); + } + + // copy the vertically blurred bloom view to a texture + R_Mesh_CopyToTexture(r_bloomstate.texture_bloom, 0, 0, r_bloomstate.viewport.x, r_bloomstate.viewport.y, r_bloomstate.viewport.width, r_bloomstate.viewport.height); + r_refdef.stats.bloom_copypixels += r_bloomstate.viewport.width * r_bloomstate.viewport.height; + } +} + +void R_HDR_RenderBloomTexture(void) +{ + int oldwidth, oldheight; + float oldcolorscale; + qboolean oldwaterstate; + + oldwaterstate = r_waterstate.enabled; + oldcolorscale = r_refdef.view.colorscale; + oldwidth = r_refdef.view.width; + oldheight = r_refdef.view.height; + r_refdef.view.width = r_bloomstate.bloomwidth; + r_refdef.view.height = r_bloomstate.bloomheight; + + if(r_hdr.integer < 2) + r_waterstate.enabled = false; + + // TODO: support GL_EXT_framebuffer_object rather than reusing the framebuffer? it might improve SLI performance. + // TODO: add exposure compensation features + // TODO: add fp16 framebuffer support (using GL_EXT_framebuffer_object) + + r_refdef.view.showdebug = false; + r_refdef.view.colorscale *= r_bloom_colorscale.value / bound(1, r_hdr_range.value, 16); + + R_ResetViewRendering3D(); + + R_ClearScreen(r_refdef.fogenabled); + if (r_timereport_active) + R_TimeReport("HDRclear"); + + R_View_Update(); + if (r_timereport_active) + R_TimeReport("visibility"); + + // only do secondary renders with HDR if r_hdr is 2 or higher + r_waterstate.numwaterplanes = 0; + if (r_waterstate.enabled) + R_RenderWaterPlanes(); + + r_refdef.view.showdebug = true; + R_RenderScene(); + r_waterstate.numwaterplanes = 0; + + R_ResetViewRendering2D(); + + R_Bloom_CopyHDRTexture(); + R_Bloom_MakeTexture(); + + // restore the view settings + r_waterstate.enabled = oldwaterstate; + r_refdef.view.width = oldwidth; + r_refdef.view.height = oldheight; + r_refdef.view.colorscale = oldcolorscale; + + R_ResetViewRendering3D(); + + R_ClearScreen(r_refdef.fogenabled); + if (r_timereport_active) + R_TimeReport("viewclear"); +} + +static void R_BlendView(void) +{ + unsigned int permutation; + float uservecs[4][4]; + + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + permutation = + (r_bloomstate.texture_bloom ? SHADERPERMUTATION_BLOOM : 0) + | (r_refdef.viewblend[3] > 0 ? SHADERPERMUTATION_VIEWTINT : 0) + | ((v_glslgamma.value && !vid_gammatables_trivial) ? SHADERPERMUTATION_GAMMARAMPS : 0) + | (r_glsl_postprocess.integer ? SHADERPERMUTATION_POSTPROCESSING : 0) + | ((!R_Stereo_ColorMasking() && r_glsl_saturation.value != 1) ? SHADERPERMUTATION_SATURATION : 0); + + if (r_bloomstate.texture_screen) + { + // make sure the buffer is available + if (r_bloom_blur.value < 1) { Cvar_SetValueQuick(&r_bloom_blur, 1); } + + R_ResetViewRendering2D(); + R_Mesh_SetMainRenderTargets(); + + if(!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0)) + { + // declare variables + float speed; + static float avgspeed; + + speed = VectorLength(cl.movement_velocity); + + cl.motionbluralpha = bound(0, (cl.time - cl.oldtime) / max(0.001, r_motionblur_vcoeff.value), 1); + avgspeed = avgspeed * (1 - cl.motionbluralpha) + speed * cl.motionbluralpha; + + speed = (avgspeed - r_motionblur_vmin.value) / max(1, r_motionblur_vmax.value - r_motionblur_vmin.value); + speed = bound(0, speed, 1); + speed = speed * (1 - r_motionblur_bmin.value) + r_motionblur_bmin.value; + + // calculate values into a standard alpha + cl.motionbluralpha = 1 - exp(- + ( + (r_motionblur.value * speed / 80) + + + (r_damageblur.value * (cl.cshifts[CSHIFT_DAMAGE].percent / 1600)) + ) + / + max(0.0001, cl.time - cl.oldtime) // fps independent + ); + + cl.motionbluralpha *= lhrandom(1 - r_motionblur_randomize.value, 1 + r_motionblur_randomize.value); + cl.motionbluralpha = bound(0, cl.motionbluralpha, r_motionblur_maxblur.value); + // apply the blur + if (cl.motionbluralpha > 0 && !r_refdef.envmap) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_Color(1, 1, 1, cl.motionbluralpha); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_SOFT: + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_bloomstate.screentexcoord2f); + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_bloomstate.screentexcoord2f); + break; + } + R_SetupShader_Generic(r_bloomstate.texture_screen, NULL, GL_MODULATE, 1, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats.bloom_drawpixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height; + } + } + + // copy view into the screen texture + R_Mesh_CopyToTexture(r_bloomstate.texture_screen, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height; + } + else if (!r_bloomstate.texture_bloom) + { + // we may still have to do view tint... + if (r_refdef.viewblend[3] >= (1.0f / 256.0f)) + { + // apply a color tint to the whole view + R_ResetViewRendering2D(); + GL_Color(r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } + break; // no screen processing, no bloom, skip it + } + + if (r_bloomstate.texture_bloom && !r_bloomstate.hdr) + { + // render simple bloom effect + // copy the screen and shrink it and darken it for the bloom process + R_Bloom_CopyBloomTexture(r_bloom_colorscale.value); + // make the bloom texture + R_Bloom_MakeTexture(); + } + +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + memset(uservecs, 0, sizeof(uservecs)); + if (r_glsl_postprocess_uservec1_enable.integer) + sscanf(r_glsl_postprocess_uservec1.string, "%f %f %f %f", &uservecs[0][0], &uservecs[0][1], &uservecs[0][2], &uservecs[0][3]); + if (r_glsl_postprocess_uservec2_enable.integer) + sscanf(r_glsl_postprocess_uservec2.string, "%f %f %f %f", &uservecs[1][0], &uservecs[1][1], &uservecs[1][2], &uservecs[1][3]); + if (r_glsl_postprocess_uservec3_enable.integer) + sscanf(r_glsl_postprocess_uservec3.string, "%f %f %f %f", &uservecs[2][0], &uservecs[2][1], &uservecs[2][2], &uservecs[2][3]); + if (r_glsl_postprocess_uservec4_enable.integer) + sscanf(r_glsl_postprocess_uservec4.string, "%f %f %f %f", &uservecs[3][0], &uservecs[3][1], &uservecs[3][2], &uservecs[3][3]); + + R_ResetViewRendering2D(); + GL_Color(1, 1, 1, 1); + GL_BlendFunc(GL_ONE, GL_ZERO); + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_bloomstate.screentexcoord2f, r_bloomstate.bloomtexcoord2f); + R_SetupShader_SetPermutationGLSL(SHADERMODE_POSTPROCESS, permutation); + if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , r_bloomstate.texture_screen); + if (r_glsl_permutation->tex_Texture_Second >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second , r_bloomstate.texture_bloom ); + if (r_glsl_permutation->tex_Texture_GammaRamps >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps ); + if (r_glsl_permutation->loc_ViewTintColor >= 0) qglUniform4f(r_glsl_permutation->loc_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + if (r_glsl_permutation->loc_PixelSize >= 0) qglUniform2f(r_glsl_permutation->loc_PixelSize , 1.0/r_bloomstate.screentexturewidth, 1.0/r_bloomstate.screentextureheight); + if (r_glsl_permutation->loc_UserVec1 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); + if (r_glsl_permutation->loc_UserVec2 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); + if (r_glsl_permutation->loc_UserVec3 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); + if (r_glsl_permutation->loc_UserVec4 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); + if (r_glsl_permutation->loc_Saturation >= 0) qglUniform1f(r_glsl_permutation->loc_Saturation , r_glsl_saturation.value); + if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f(r_glsl_permutation->loc_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + if (r_glsl_permutation->loc_BloomColorSubtract >= 0) qglUniform4f(r_glsl_permutation->loc_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + // D3D has upside down Y coords, the easiest way to flip this is to flip the screen vertices rather than the texcoords, so we just use a different array for that... + R_Mesh_PrepareVertices_Mesh_Arrays(4, r_d3dscreenvertex3f, NULL, NULL, NULL, NULL, r_bloomstate.screentexcoord2f, r_bloomstate.bloomtexcoord2f); + R_SetupShader_SetPermutationHLSL(SHADERMODE_POSTPROCESS, permutation); + R_Mesh_TexBind(GL20TU_FIRST , r_bloomstate.texture_screen); + R_Mesh_TexBind(GL20TU_SECOND , r_bloomstate.texture_bloom ); + R_Mesh_TexBind(GL20TU_GAMMARAMPS, r_texture_gammaramps ); + hlslPSSetParameter4f(D3DPSREGISTER_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + hlslPSSetParameter2f(D3DPSREGISTER_PixelSize , 1.0/r_bloomstate.screentexturewidth, 1.0/r_bloomstate.screentextureheight); + hlslPSSetParameter4f(D3DPSREGISTER_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); + hlslPSSetParameter4f(D3DPSREGISTER_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); + hlslPSSetParameter4f(D3DPSREGISTER_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); + hlslPSSetParameter4f(D3DPSREGISTER_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); + hlslPSSetParameter1f(D3DPSREGISTER_Saturation , r_glsl_saturation.value); + hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); + hlslPSSetParameter4f(D3DPSREGISTER_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_bloomstate.screentexcoord2f, r_bloomstate.bloomtexcoord2f); + R_SetupShader_SetPermutationSoft(SHADERMODE_POSTPROCESS, permutation); + R_Mesh_TexBind(GL20TU_FIRST , r_bloomstate.texture_screen); + R_Mesh_TexBind(GL20TU_SECOND , r_bloomstate.texture_bloom ); + R_Mesh_TexBind(GL20TU_GAMMARAMPS, r_texture_gammaramps ); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelSize , 1.0/r_bloomstate.screentexturewidth, 1.0/r_bloomstate.screentextureheight); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_Saturation , r_glsl_saturation.value); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); + break; + default: + break; + } + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats.bloom_drawpixels += r_refdef.view.width * r_refdef.view.height; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (r_refdef.viewblend[3] >= (1.0f / 256.0f)) + { + // apply a color tint to the whole view + R_ResetViewRendering2D(); + GL_Color(r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } + break; + } +} + +matrix4x4_t r_waterscrollmatrix; + +void R_UpdateFogColor(void) // needs to be called before HDR subrender too, as that changes colorscale! +{ + if (r_refdef.fog_density) + { + r_refdef.fogcolor[0] = r_refdef.fog_red; + r_refdef.fogcolor[1] = r_refdef.fog_green; + r_refdef.fogcolor[2] = r_refdef.fog_blue; + + Vector4Set(r_refdef.fogplane, 0, 0, 1, -r_refdef.fog_height); + r_refdef.fogplaneviewdist = DotProduct(r_refdef.fogplane, r_refdef.view.origin) + r_refdef.fogplane[3]; + r_refdef.fogplaneviewabove = r_refdef.fogplaneviewdist >= 0; + r_refdef.fogheightfade = -0.5f/max(0.125f, r_refdef.fog_fadedepth); + + { + vec3_t fogvec; + VectorCopy(r_refdef.fogcolor, fogvec); + // color.rgb *= ContrastBoost * SceneBrightness; + VectorScale(fogvec, r_refdef.view.colorscale, fogvec); + r_refdef.fogcolor[0] = bound(0.0f, fogvec[0], 1.0f); + r_refdef.fogcolor[1] = bound(0.0f, fogvec[1], 1.0f); + r_refdef.fogcolor[2] = bound(0.0f, fogvec[2], 1.0f); + } + } +} + +void R_UpdateVariables(void) +{ + R_Textures_Frame(); + + r_refdef.scene.ambient = r_ambient.value * (1.0f / 64.0f); + + r_refdef.farclip = r_farclip_base.value; + if (r_refdef.scene.worldmodel) + r_refdef.farclip += r_refdef.scene.worldmodel->radius * r_farclip_world.value * 2; + r_refdef.nearclip = bound (0.001f, r_nearclip.value, r_refdef.farclip - 1.0f); + + if (r_shadow_frontsidecasting.integer < 0 || r_shadow_frontsidecasting.integer > 1) + Cvar_SetValueQuick(&r_shadow_frontsidecasting, 1); + r_refdef.polygonfactor = 0; + r_refdef.polygonoffset = 0; + r_refdef.shadowpolygonfactor = r_refdef.polygonfactor + r_shadow_polygonfactor.value * (r_shadow_frontsidecasting.integer ? 1 : -1); + r_refdef.shadowpolygonoffset = r_refdef.polygonoffset + r_shadow_polygonoffset.value * (r_shadow_frontsidecasting.integer ? 1 : -1); + + r_refdef.scene.rtworld = r_shadow_realtime_world.integer != 0; + r_refdef.scene.rtworldshadows = r_shadow_realtime_world_shadows.integer && vid.stencil; + r_refdef.scene.rtdlight = r_shadow_realtime_dlight.integer != 0 && !gl_flashblend.integer && r_dynamic.integer; + r_refdef.scene.rtdlightshadows = r_refdef.scene.rtdlight && r_shadow_realtime_dlight_shadows.integer && vid.stencil; + r_refdef.lightmapintensity = r_refdef.scene.rtworld ? r_shadow_realtime_world_lightmaps.value : 1; + if (FAKELIGHT_ENABLED) + { + r_refdef.lightmapintensity *= r_fakelight_intensity.value; + } + if (r_showsurfaces.integer) + { + r_refdef.scene.rtworld = false; + r_refdef.scene.rtworldshadows = false; + r_refdef.scene.rtdlight = false; + r_refdef.scene.rtdlightshadows = false; + r_refdef.lightmapintensity = 0; + } + + if (gamemode == GAME_NEHAHRA) + { + if (gl_fogenable.integer) + { + r_refdef.oldgl_fogenable = true; + r_refdef.fog_density = gl_fogdensity.value; + r_refdef.fog_red = gl_fogred.value; + r_refdef.fog_green = gl_foggreen.value; + r_refdef.fog_blue = gl_fogblue.value; + r_refdef.fog_alpha = 1; + r_refdef.fog_start = 0; + r_refdef.fog_end = gl_skyclip.value; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; + } + else if (r_refdef.oldgl_fogenable) + { + r_refdef.oldgl_fogenable = false; + r_refdef.fog_density = 0; + r_refdef.fog_red = 0; + r_refdef.fog_green = 0; + r_refdef.fog_blue = 0; + r_refdef.fog_alpha = 0; + r_refdef.fog_start = 0; + r_refdef.fog_end = 0; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; + } + } + + r_refdef.fog_alpha = bound(0, r_refdef.fog_alpha, 1); + r_refdef.fog_start = max(0, r_refdef.fog_start); + r_refdef.fog_end = max(r_refdef.fog_start + 0.01, r_refdef.fog_end); + + // R_UpdateFogColor(); // why? R_RenderScene does it anyway + + if (r_refdef.fog_density && r_drawfog.integer) + { + r_refdef.fogenabled = true; + // this is the point where the fog reaches 0.9986 alpha, which we + // consider a good enough cutoff point for the texture + // (0.9986 * 256 == 255.6) + if (r_fog_exp2.integer) + r_refdef.fogrange = 32 / (r_refdef.fog_density * r_refdef.fog_density) + r_refdef.fog_start; + else + r_refdef.fogrange = 2048 / r_refdef.fog_density + r_refdef.fog_start; + r_refdef.fogrange = bound(r_refdef.fog_start, r_refdef.fogrange, r_refdef.fog_end); + r_refdef.fograngerecip = 1.0f / r_refdef.fogrange; + r_refdef.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * r_refdef.fograngerecip; + if (strcmp(r_refdef.fogheighttexturename, r_refdef.fog_height_texturename)) + R_BuildFogHeightTexture(); + // fog color was already set + // update the fog texture + if (r_refdef.fogmasktable_start != r_refdef.fog_start || r_refdef.fogmasktable_alpha != r_refdef.fog_alpha || r_refdef.fogmasktable_density != r_refdef.fog_density || r_refdef.fogmasktable_range != r_refdef.fogrange) + R_BuildFogTexture(); + r_refdef.fog_height_texcoordscale = 1.0f / max(0.125f, r_refdef.fog_fadedepth); + r_refdef.fog_height_tablescale = r_refdef.fog_height_tablesize * r_refdef.fog_height_texcoordscale; + } + else + r_refdef.fogenabled = false; + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + if(v_glslgamma.integer && !vid_gammatables_trivial) + { + if(!r_texture_gammaramps || vid_gammatables_serial != r_texture_gammaramps_serial) + { + // build GLSL gamma texture +#define RAMPWIDTH 256 + unsigned short ramp[RAMPWIDTH * 3]; + unsigned char rampbgr[RAMPWIDTH][4]; + int i; + + r_texture_gammaramps_serial = vid_gammatables_serial; + + VID_BuildGammaTables(&ramp[0], RAMPWIDTH); + for(i = 0; i < RAMPWIDTH; ++i) + { + rampbgr[i][0] = (unsigned char) (ramp[i + 2 * RAMPWIDTH] * 255.0 / 65535.0 + 0.5); + rampbgr[i][1] = (unsigned char) (ramp[i + RAMPWIDTH] * 255.0 / 65535.0 + 0.5); + rampbgr[i][2] = (unsigned char) (ramp[i] * 255.0 / 65535.0 + 0.5); + rampbgr[i][3] = 0; + } + if (r_texture_gammaramps) + { + R_UpdateTexture(r_texture_gammaramps, &rampbgr[0][0], 0, 0, 0, RAMPWIDTH, 1, 1); + } + else + { + r_texture_gammaramps = R_LoadTexture2D(r_main_texturepool, "gammaramps", RAMPWIDTH, 1, &rampbgr[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); + } + } + } + else + { + // remove GLSL gamma texture + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + } +} + +static r_refdef_scene_type_t r_currentscenetype = RST_CLIENT; +static r_refdef_scene_t r_scenes_store[ RST_COUNT ]; +/* +================ +R_SelectScene +================ +*/ +void R_SelectScene( r_refdef_scene_type_t scenetype ) { + if( scenetype != r_currentscenetype ) { + // store the old scenetype + r_scenes_store[ r_currentscenetype ] = r_refdef.scene; + r_currentscenetype = scenetype; + // move in the new scene + r_refdef.scene = r_scenes_store[ r_currentscenetype ]; + } +} + +/* +================ +R_GetScenePointer +================ +*/ +r_refdef_scene_t * R_GetScenePointer( r_refdef_scene_type_t scenetype ) +{ + // of course, we could also add a qboolean that provides a lock state and a ReleaseScenePointer function.. + if( scenetype == r_currentscenetype ) { + return &r_refdef.scene; + } else { + return &r_scenes_store[ scenetype ]; + } +} + +/* +================ +R_RenderView +================ +*/ +int dpsoftrast_test; +extern void R_Shadow_UpdateBounceGridTexture(void); +extern cvar_t r_shadow_bouncegrid; +void R_RenderView(void) +{ + matrix4x4_t originalmatrix = r_refdef.view.matrix, offsetmatrix; + + dpsoftrast_test = r_test.integer; + + if (r_timereport_active) + R_TimeReport("start"); + r_textureframe++; // used only by R_GetCurrentTexture + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + + if(R_CompileShader_CheckStaticParms()) + R_GLSL_Restart_f(); + + if (!r_drawentities.integer) + r_refdef.scene.numentities = 0; + + R_AnimCache_ClearCache(); + R_FrameData_NewFrame(); + + /* adjust for stereo display */ + if(R_Stereo_Active()) + { + Matrix4x4_CreateFromQuakeEntity(&offsetmatrix, 0, r_stereo_separation.value * (0.5f - r_stereo_side), 0, 0, r_stereo_angle.value * (0.5f - r_stereo_side), 0, 1); + Matrix4x4_Concat(&r_refdef.view.matrix, &originalmatrix, &offsetmatrix); + } + + if (r_refdef.view.isoverlay) + { + // TODO: FIXME: move this into its own backend function maybe? [2/5/2008 Andreas] + GL_Clear(GL_DEPTH_BUFFER_BIT, NULL, 1.0f, 0); + R_TimeReport("depthclear"); + + r_refdef.view.showdebug = false; + + r_waterstate.enabled = false; + r_waterstate.numwaterplanes = 0; + + R_RenderScene(); + + r_refdef.view.matrix = originalmatrix; + + CHECKGLERROR + return; + } + + if (!r_refdef.scene.entities || r_refdef.view.width * r_refdef.view.height == 0 || !r_renderview.integer || cl_videoplaying/* || !r_refdef.scene.worldmodel*/) + { + r_refdef.view.matrix = originalmatrix; + return; //Host_Error ("R_RenderView: NULL worldmodel"); + } + + r_refdef.view.colorscale = r_hdr_scenebrightness.value * r_hdr_irisadaptation_value.value; + + R_RenderView_UpdateViewVectors(); + + R_Shadow_UpdateWorldLightSelection(); + + R_Bloom_StartFrame(); + R_Water_StartFrame(); + + CHECKGLERROR + if (r_timereport_active) + R_TimeReport("viewsetup"); + + R_ResetViewRendering3D(); + + if (r_refdef.view.clear || r_refdef.fogenabled) + { + R_ClearScreen(r_refdef.fogenabled); + if (r_timereport_active) + R_TimeReport("viewclear"); + } + r_refdef.view.clear = true; + + // this produces a bloom texture to be used in R_BlendView() later + if (r_bloomstate.hdr) + { + R_HDR_RenderBloomTexture(); + // we have to bump the texture frame again because r_refdef.view.colorscale is cached in the textures + r_textureframe++; // used only by R_GetCurrentTexture + } + + r_refdef.view.showdebug = true; + + R_View_Update(); + if (r_timereport_active) + R_TimeReport("visibility"); + + R_Shadow_UpdateBounceGridTexture(); + if (r_timereport_active && r_shadow_bouncegrid.integer) + R_TimeReport("bouncegrid"); + + r_waterstate.numwaterplanes = 0; + if (r_waterstate.enabled) + R_RenderWaterPlanes(); + + R_RenderScene(); + r_waterstate.numwaterplanes = 0; + + R_BlendView(); + if (r_timereport_active) + R_TimeReport("blendview"); + + GL_Scissor(0, 0, vid.width, vid.height); + GL_ScissorTest(false); + + r_refdef.view.matrix = originalmatrix; + + CHECKGLERROR +} + +void R_RenderWaterPlanes(void) +{ + if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawAddWaterPlanes) + { + r_refdef.scene.worldmodel->DrawAddWaterPlanes(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("waterworld"); + } + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + R_DrawModelsAddWaterPlanes(); + if (r_timereport_active) + R_TimeReport("watermodels"); + + if (r_waterstate.numwaterplanes) + { + R_Water_ProcessPlanes(); + if (r_timereport_active) + R_TimeReport("waterscenes"); + } +} + +extern void R_DrawLightningBeams (void); +extern void VM_CL_AddPolygonsToMeshQueue (void); +extern void R_DrawPortals (void); +extern cvar_t cl_locs_show; +static void R_DrawLocs(void); +static void R_DrawEntityBBoxes(void); +static void R_DrawModelDecals(void); +extern void R_DrawModelShadows(void); +extern void R_DrawModelShadowMaps(void); +extern cvar_t cl_decals_newsystem; +extern qboolean r_shadow_usingdeferredprepass; +void R_RenderScene(void) +{ + qboolean shadowmapping = false; + + if (r_timereport_active) + R_TimeReport("beginscene"); + + r_refdef.stats.renders++; + + R_UpdateFogColor(); + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + R_MeshQueue_BeginScene(); + + R_SkyStartFrame(); + + Matrix4x4_CreateTranslate(&r_waterscrollmatrix, sin(r_refdef.scene.time) * 0.025 * r_waterscroll.value, sin(r_refdef.scene.time * 0.8f) * 0.025 * r_waterscroll.value, 0); + + if (r_timereport_active) + R_TimeReport("skystartframe"); + + if (cl.csqc_vidvars.drawworld) + { + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawSky) + { + r_refdef.scene.worldmodel->DrawSky(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("worldsky"); + } + + if (R_DrawBrushModelsSky() && r_timereport_active) + R_TimeReport("bmodelsky"); + + if (skyrendermasked && skyrenderlater) + { + // we have to force off the water clipping plane while rendering sky + R_SetupView(false); + R_Sky(); + R_SetupView(true); + if (r_timereport_active) + R_TimeReport("sky"); + } + } + + R_AnimCache_CacheVisibleEntities(); + if (r_timereport_active) + R_TimeReport("animation"); + + R_Shadow_PrepareLights(); + if (r_shadows.integer > 0 && r_refdef.lightmapintensity > 0) + R_Shadow_PrepareModelShadows(); + if (r_timereport_active) + R_TimeReport("preparelights"); + + if (R_Shadow_ShadowMappingEnabled()) + shadowmapping = true; + + if (r_shadow_usingdeferredprepass) + R_Shadow_DrawPrepass(); + + if (r_depthfirst.integer >= 1 && cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawDepth) + { + r_refdef.scene.worldmodel->DrawDepth(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("worlddepth"); + } + if (r_depthfirst.integer >= 2) + { + R_DrawModelsDepth(); + if (r_timereport_active) + R_TimeReport("modeldepth"); + } + + if (r_shadows.integer >= 2 && shadowmapping && r_refdef.lightmapintensity > 0) + { + R_DrawModelShadowMaps(); + R_ResetViewRendering3D(); + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + } + + if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->Draw) + { + r_refdef.scene.worldmodel->Draw(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("world"); + } + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + R_DrawModels(); + if (r_timereport_active) + R_TimeReport("models"); + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && !r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0) + { + R_DrawModelShadows(); + R_ResetViewRendering3D(); + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + } + + if (!r_shadow_usingdeferredprepass) + { + R_Shadow_DrawLights(); + if (r_timereport_active) + R_TimeReport("rtlights"); + } + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0) + { + R_DrawModelShadows(); + R_ResetViewRendering3D(); + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + } + + if (cl.csqc_vidvars.drawworld) + { + if (cl_decals_newsystem.integer) + { + R_DrawModelDecals(); + if (r_timereport_active) + R_TimeReport("modeldecals"); + } + else + { + R_DrawDecals(); + if (r_timereport_active) + R_TimeReport("decals"); + } + + R_DrawParticles(); + if (r_timereport_active) + R_TimeReport("particles"); + + R_DrawExplosions(); + if (r_timereport_active) + R_TimeReport("explosions"); + + R_DrawLightningBeams(); + if (r_timereport_active) + R_TimeReport("lightning"); + } + + VM_CL_AddPolygonsToMeshQueue(); + + if (r_refdef.view.showdebug) + { + if (cl_locs_show.integer) + { + R_DrawLocs(); + if (r_timereport_active) + R_TimeReport("showlocs"); + } + + if (r_drawportals.integer) + { + R_DrawPortals(); + if (r_timereport_active) + R_TimeReport("portals"); + } + + if (r_showbboxes.value > 0) + { + R_DrawEntityBBoxes(); + if (r_timereport_active) + R_TimeReport("bboxes"); + } + } + + if (r_transparent.integer) + { + R_MeshQueue_RenderTransparent(); + if (r_timereport_active) + R_TimeReport("drawtrans"); + } + + if (r_refdef.view.showdebug && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawDebug && (r_showtris.value > 0 || r_shownormals.value != 0 || r_showcollisionbrushes.value > 0 || r_showoverdraw.value > 0)) + { + r_refdef.scene.worldmodel->DrawDebug(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("worlddebug"); + R_DrawModelsDebug(); + if (r_timereport_active) + R_TimeReport("modeldebug"); + } + + if (cl.csqc_vidvars.drawworld) + { + R_Shadow_DrawCoronas(); + if (r_timereport_active) + R_TimeReport("coronas"); + } + +#if 0 + { + GL_DepthTest(false); + qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + GL_Color(1, 1, 1, 1); + qglBegin(GL_POLYGON); + qglVertex3f(r_refdef.view.frustumcorner[0][0], r_refdef.view.frustumcorner[0][1], r_refdef.view.frustumcorner[0][2]); + qglVertex3f(r_refdef.view.frustumcorner[1][0], r_refdef.view.frustumcorner[1][1], r_refdef.view.frustumcorner[1][2]); + qglVertex3f(r_refdef.view.frustumcorner[3][0], r_refdef.view.frustumcorner[3][1], r_refdef.view.frustumcorner[3][2]); + qglVertex3f(r_refdef.view.frustumcorner[2][0], r_refdef.view.frustumcorner[2][1], r_refdef.view.frustumcorner[2][2]); + qglEnd(); + qglBegin(GL_POLYGON); + qglVertex3f(r_refdef.view.frustumcorner[0][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[0][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[0][2] + 1000 * r_refdef.view.forward[2]); + qglVertex3f(r_refdef.view.frustumcorner[1][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[1][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[1][2] + 1000 * r_refdef.view.forward[2]); + qglVertex3f(r_refdef.view.frustumcorner[3][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[3][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[3][2] + 1000 * r_refdef.view.forward[2]); + qglVertex3f(r_refdef.view.frustumcorner[2][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[2][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[2][2] + 1000 * r_refdef.view.forward[2]); + qglEnd(); + qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } +#endif + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + R_ResetViewRendering2D(); +} + +static const unsigned short bboxelements[36] = +{ + 5, 1, 3, 5, 3, 7, + 6, 2, 0, 6, 0, 4, + 7, 3, 2, 7, 2, 6, + 4, 0, 1, 4, 1, 5, + 4, 5, 7, 4, 7, 6, + 1, 0, 2, 1, 2, 3, +}; + +void R_DrawBBoxMesh(vec3_t mins, vec3_t maxs, float cr, float cg, float cb, float ca) +{ + int i; + float *v, *c, f1, f2, vertex3f[8*3], color4f[8*4]; + + RSurf_ActiveWorldEntity(); + + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); +// R_Mesh_ResetTextureState(); + + vertex3f[ 0] = mins[0];vertex3f[ 1] = mins[1];vertex3f[ 2] = mins[2]; // + vertex3f[ 3] = maxs[0];vertex3f[ 4] = mins[1];vertex3f[ 5] = mins[2]; + vertex3f[ 6] = mins[0];vertex3f[ 7] = maxs[1];vertex3f[ 8] = mins[2]; + vertex3f[ 9] = maxs[0];vertex3f[10] = maxs[1];vertex3f[11] = mins[2]; + vertex3f[12] = mins[0];vertex3f[13] = mins[1];vertex3f[14] = maxs[2]; + vertex3f[15] = maxs[0];vertex3f[16] = mins[1];vertex3f[17] = maxs[2]; + vertex3f[18] = mins[0];vertex3f[19] = maxs[1];vertex3f[20] = maxs[2]; + vertex3f[21] = maxs[0];vertex3f[22] = maxs[1];vertex3f[23] = maxs[2]; + R_FillColors(color4f, 8, cr, cg, cb, ca); + if (r_refdef.fogenabled) + { + for (i = 0, v = vertex3f, c = color4f;i < 8;i++, v += 3, c += 4) + { + f1 = RSurf_FogVertex(v); + f2 = 1 - f1; + c[0] = c[0] * f1 + r_refdef.fogcolor[0] * f2; + c[1] = c[1] * f1 + r_refdef.fogcolor[1] * f2; + c[2] = c[2] * f1 + r_refdef.fogcolor[2] * f2; + } + } + R_Mesh_PrepareVertices_Generic_Arrays(8, vertex3f, color4f, NULL); + R_Mesh_ResetTextureState(); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + R_Mesh_Draw(0, 8, 0, 12, NULL, NULL, 0, bboxelements, NULL, 0); +} + +static void R_DrawEntityBBoxes_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i; + float color[4]; + prvm_edict_t *edict; + prvm_prog_t *prog_save = prog; + + // this function draws bounding boxes of server entities + if (!sv.active) + return; + + GL_CullFace(GL_NONE); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + + prog = 0; + SV_VM_Begin(); + for (i = 0;i < numsurfaces;i++) + { + edict = PRVM_EDICT_NUM(surfacelist[i]); + switch ((int)PRVM_serveredictfloat(edict, solid)) + { + case SOLID_NOT: Vector4Set(color, 1, 1, 1, 0.05);break; + case SOLID_TRIGGER: Vector4Set(color, 1, 0, 1, 0.10);break; + case SOLID_BBOX: Vector4Set(color, 0, 1, 0, 0.10);break; + case SOLID_SLIDEBOX: Vector4Set(color, 1, 0, 0, 0.10);break; + case SOLID_BSP: Vector4Set(color, 0, 0, 1, 0.05);break; + default: Vector4Set(color, 0, 0, 0, 0.50);break; + } + color[3] *= r_showbboxes.value; + color[3] = bound(0, color[3], 1); + GL_DepthTest(!r_showdisabledepthtest.integer); + GL_CullFace(r_refdef.view.cullface_front); + R_DrawBBoxMesh(edict->priv.server->areamins, edict->priv.server->areamaxs, color[0], color[1], color[2], color[3]); + } + SV_VM_End(); + prog = prog_save; +} + +static void R_DrawEntityBBoxes(void) +{ + int i; + prvm_edict_t *edict; + vec3_t center; + prvm_prog_t *prog_save = prog; + + // this function draws bounding boxes of server entities + if (!sv.active) + return; + + prog = 0; + SV_VM_Begin(); + for (i = 0;i < prog->num_edicts;i++) + { + edict = PRVM_EDICT_NUM(i); + if (edict->priv.server->free) + continue; + // exclude the following for now, as they don't live in world coordinate space and can't be solid: + if(PRVM_serveredictedict(edict, tag_entity) != 0) + continue; + if(PRVM_serveredictedict(edict, viewmodelforclient) != 0) + continue; + VectorLerp(edict->priv.server->areamins, 0.5f, edict->priv.server->areamaxs, center); + R_MeshQueue_AddTransparent(center, R_DrawEntityBBoxes_Callback, (entity_render_t *)NULL, i, (rtlight_t *)NULL); + } + SV_VM_End(); + prog = prog_save; +} + +static const int nomodelelement3i[24] = +{ + 5, 2, 0, + 5, 1, 2, + 5, 0, 3, + 5, 3, 1, + 0, 2, 4, + 2, 1, 4, + 3, 0, 4, + 1, 3, 4 +}; + +static const unsigned short nomodelelement3s[24] = +{ + 5, 2, 0, + 5, 1, 2, + 5, 0, 3, + 5, 3, 1, + 0, 2, 4, + 2, 1, 4, + 3, 0, 4, + 1, 3, 4 +}; + +static const float nomodelvertex3f[6*3] = +{ + -16, 0, 0, + 16, 0, 0, + 0, -16, 0, + 0, 16, 0, + 0, 0, -16, + 0, 0, 16 +}; + +static const float nomodelcolor4f[6*4] = +{ + 0.0f, 0.0f, 0.5f, 1.0f, + 0.0f, 0.0f, 0.5f, 1.0f, + 0.0f, 0.5f, 0.0f, 1.0f, + 0.0f, 0.5f, 0.0f, 1.0f, + 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, 0.0f, 0.0f, 1.0f +}; + +void R_DrawNoModel_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i; + float f1, f2, *c; + float color4f[6*4]; + + RSurf_ActiveCustomEntity(&ent->matrix, &ent->inversematrix, ent->flags, ent->shadertime, ent->colormod[0], ent->colormod[1], ent->colormod[2], ent->alpha, 6, nomodelvertex3f, NULL, NULL, NULL, NULL, nomodelcolor4f, 8, nomodelelement3i, nomodelelement3s, false, false); + + // this is only called once per entity so numsurfaces is always 1, and + // surfacelist is always {0}, so this code does not handle batches + + if (rsurface.ent_flags & RENDER_ADDITIVE) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + GL_DepthMask(false); + } + else if (rsurface.colormod[3] < 1) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + } + else + { + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); + } + GL_DepthRange(0, (rsurface.ent_flags & RENDER_VIEWMODEL) ? 0.0625 : 1); + GL_PolygonOffset(rsurface.basepolygonfactor, rsurface.basepolygonoffset); + GL_DepthTest(!(rsurface.ent_flags & RENDER_NODEPTHTEST)); + GL_CullFace((rsurface.ent_flags & RENDER_DOUBLESIDED) ? GL_NONE : r_refdef.view.cullface_back); + memcpy(color4f, nomodelcolor4f, sizeof(float[6*4])); + for (i = 0, c = color4f;i < 6;i++, c += 4) + { + c[0] *= rsurface.colormod[0]; + c[1] *= rsurface.colormod[1]; + c[2] *= rsurface.colormod[2]; + c[3] *= rsurface.colormod[3]; + } + if (r_refdef.fogenabled) + { + for (i = 0, c = color4f;i < 6;i++, c += 4) + { + f1 = RSurf_FogVertex(nomodelvertex3f + 3*i); + f2 = 1 - f1; + c[0] = (c[0] * f1 + r_refdef.fogcolor[0] * f2); + c[1] = (c[1] * f1 + r_refdef.fogcolor[1] * f2); + c[2] = (c[2] * f1 + r_refdef.fogcolor[2] * f2); + } + } +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + R_Mesh_PrepareVertices_Generic_Arrays(6, nomodelvertex3f, color4f, NULL); + R_Mesh_Draw(0, 6, 0, 8, nomodelelement3i, NULL, 0, nomodelelement3s, NULL, 0); +} + +void R_DrawNoModel(entity_render_t *ent) +{ + vec3_t org; + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + if ((ent->flags & RENDER_ADDITIVE) || (ent->alpha < 1)) + R_MeshQueue_AddTransparent(ent->flags & RENDER_NODEPTHTEST ? r_refdef.view.origin : org, R_DrawNoModel_TransparentCallback, ent, 0, rsurface.rtlight); + else + R_DrawNoModel_TransparentCallback(ent, rsurface.rtlight, 0, NULL); +} + +void R_CalcBeam_Vertex3f (float *vert, const vec3_t org1, const vec3_t org2, float width) +{ + vec3_t right1, right2, diff, normal; + + VectorSubtract (org2, org1, normal); + + // calculate 'right' vector for start + VectorSubtract (r_refdef.view.origin, org1, diff); + CrossProduct (normal, diff, right1); + VectorNormalize (right1); + + // calculate 'right' vector for end + VectorSubtract (r_refdef.view.origin, org2, diff); + CrossProduct (normal, diff, right2); + VectorNormalize (right2); + + vert[ 0] = org1[0] + width * right1[0]; + vert[ 1] = org1[1] + width * right1[1]; + vert[ 2] = org1[2] + width * right1[2]; + vert[ 3] = org1[0] - width * right1[0]; + vert[ 4] = org1[1] - width * right1[1]; + vert[ 5] = org1[2] - width * right1[2]; + vert[ 6] = org2[0] - width * right2[0]; + vert[ 7] = org2[1] - width * right2[1]; + vert[ 8] = org2[2] - width * right2[2]; + vert[ 9] = org2[0] + width * right2[0]; + vert[10] = org2[1] + width * right2[1]; + vert[11] = org2[2] + width * right2[2]; +} + +void R_CalcSprite_Vertex3f(float *vertex3f, const vec3_t origin, const vec3_t left, const vec3_t up, float scalex1, float scalex2, float scaley1, float scaley2) +{ + vertex3f[ 0] = origin[0] + left[0] * scalex2 + up[0] * scaley1; + vertex3f[ 1] = origin[1] + left[1] * scalex2 + up[1] * scaley1; + vertex3f[ 2] = origin[2] + left[2] * scalex2 + up[2] * scaley1; + vertex3f[ 3] = origin[0] + left[0] * scalex2 + up[0] * scaley2; + vertex3f[ 4] = origin[1] + left[1] * scalex2 + up[1] * scaley2; + vertex3f[ 5] = origin[2] + left[2] * scalex2 + up[2] * scaley2; + vertex3f[ 6] = origin[0] + left[0] * scalex1 + up[0] * scaley2; + vertex3f[ 7] = origin[1] + left[1] * scalex1 + up[1] * scaley2; + vertex3f[ 8] = origin[2] + left[2] * scalex1 + up[2] * scaley2; + vertex3f[ 9] = origin[0] + left[0] * scalex1 + up[0] * scaley1; + vertex3f[10] = origin[1] + left[1] * scalex1 + up[1] * scaley1; + vertex3f[11] = origin[2] + left[2] * scalex1 + up[2] * scaley1; +} + +int R_Mesh_AddVertex(rmesh_t *mesh, float x, float y, float z) +{ + int i; + float *vertex3f; + float v[3]; + VectorSet(v, x, y, z); + for (i = 0, vertex3f = mesh->vertex3f;i < mesh->numvertices;i++, vertex3f += 3) + if (VectorDistance2(v, vertex3f) < mesh->epsilon2) + break; + if (i == mesh->numvertices) + { + if (mesh->numvertices < mesh->maxvertices) + { + VectorCopy(v, vertex3f); + mesh->numvertices++; + } + return mesh->numvertices; + } + else + return i; +} + +void R_Mesh_AddPolygon3f(rmesh_t *mesh, int numvertices, float *vertex3f) +{ + int i; + int *e, element[3]; + element[0] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]);vertex3f += 3; + element[1] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]);vertex3f += 3; + e = mesh->element3i + mesh->numtriangles * 3; + for (i = 0;i < numvertices - 2;i++, vertex3f += 3) + { + element[2] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]); + if (mesh->numtriangles < mesh->maxtriangles) + { + *e++ = element[0]; + *e++ = element[1]; + *e++ = element[2]; + mesh->numtriangles++; + } + element[1] = element[2]; + } +} + +void R_Mesh_AddPolygon3d(rmesh_t *mesh, int numvertices, double *vertex3d) +{ + int i; + int *e, element[3]; + element[0] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]);vertex3d += 3; + element[1] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]);vertex3d += 3; + e = mesh->element3i + mesh->numtriangles * 3; + for (i = 0;i < numvertices - 2;i++, vertex3d += 3) + { + element[2] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]); + if (mesh->numtriangles < mesh->maxtriangles) + { + *e++ = element[0]; + *e++ = element[1]; + *e++ = element[2]; + mesh->numtriangles++; + } + element[1] = element[2]; + } +} + +#define R_MESH_PLANE_DIST_EPSILON (1.0 / 32.0) +void R_Mesh_AddBrushMeshFromPlanes(rmesh_t *mesh, int numplanes, mplane_t *planes) +{ + int planenum, planenum2; + int w; + int tempnumpoints; + mplane_t *plane, *plane2; + double maxdist; + double temppoints[2][256*3]; + // figure out how large a bounding box we need to properly compute this brush + maxdist = 0; + for (w = 0;w < numplanes;w++) + maxdist = max(maxdist, fabs(planes[w].dist)); + // now make it large enough to enclose the entire brush, and round it off to a reasonable multiple of 1024 + maxdist = floor(maxdist * (4.0 / 1024.0) + 1) * 1024.0; + for (planenum = 0, plane = planes;planenum < numplanes;planenum++, plane++) + { + w = 0; + tempnumpoints = 4; + PolygonD_QuadForPlane(temppoints[w], plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, maxdist); + for (planenum2 = 0, plane2 = planes;planenum2 < numplanes && tempnumpoints >= 3;planenum2++, plane2++) + { + if (planenum2 == planenum) + continue; + PolygonD_Divide(tempnumpoints, temppoints[w], plane2->normal[0], plane2->normal[1], plane2->normal[2], plane2->dist, R_MESH_PLANE_DIST_EPSILON, 0, NULL, NULL, 256, temppoints[!w], &tempnumpoints, NULL); + w = !w; + } + if (tempnumpoints < 3) + continue; + // generate elements forming a triangle fan for this polygon + R_Mesh_AddPolygon3d(mesh, tempnumpoints, temppoints[w]); + } +} + +static void R_Texture_AddLayer(texture_t *t, qboolean depthmask, int blendfunc1, int blendfunc2, texturelayertype_t type, rtexture_t *texture, const matrix4x4_t *matrix, float r, float g, float b, float a) +{ + texturelayer_t *layer; + layer = t->currentlayers + t->currentnumlayers++; + layer->type = type; + layer->depthmask = depthmask; + layer->blendfunc1 = blendfunc1; + layer->blendfunc2 = blendfunc2; + layer->texture = texture; + layer->texmatrix = *matrix; + layer->color[0] = r; + layer->color[1] = g; + layer->color[2] = b; + layer->color[3] = a; +} + +static qboolean R_TestQ3WaveFunc(q3wavefunc_t func, const float *parms) +{ + if(parms[0] == 0 && parms[1] == 0) + return false; + if(func >> Q3WAVEFUNC_USER_SHIFT) // assumes rsurface to be set! + if(rsurface.userwavefunc_param[bound(0, (func >> Q3WAVEFUNC_USER_SHIFT) - 1, Q3WAVEFUNC_USER_COUNT)] == 0) + return false; + return true; +} + +static float R_EvaluateQ3WaveFunc(q3wavefunc_t func, const float *parms) +{ + double index, f; + index = parms[2] + rsurface.shadertime * parms[3]; + index -= floor(index); + switch (func & ((1 << Q3WAVEFUNC_USER_SHIFT) - 1)) + { + default: + case Q3WAVEFUNC_NONE: + case Q3WAVEFUNC_NOISE: + case Q3WAVEFUNC_COUNT: + f = 0; + break; + case Q3WAVEFUNC_SIN: f = sin(index * M_PI * 2);break; + case Q3WAVEFUNC_SQUARE: f = index < 0.5 ? 1 : -1;break; + case Q3WAVEFUNC_SAWTOOTH: f = index;break; + case Q3WAVEFUNC_INVERSESAWTOOTH: f = 1 - index;break; + case Q3WAVEFUNC_TRIANGLE: + index *= 4; + f = index - floor(index); + if (index < 1) + { + // f = f; + } + else if (index < 2) + f = 1 - f; + else if (index < 3) + f = -f; + else + f = -(1 - f); + break; + } + f = parms[0] + parms[1] * f; + if(func >> Q3WAVEFUNC_USER_SHIFT) // assumes rsurface to be set! + f *= rsurface.userwavefunc_param[bound(0, (func >> Q3WAVEFUNC_USER_SHIFT) - 1, Q3WAVEFUNC_USER_COUNT)]; + return (float) f; +} + +void R_tcMod_ApplyToMatrix(matrix4x4_t *texmatrix, q3shaderinfo_layer_tcmod_t *tcmod, int currentmaterialflags) +{ + int w, h, idx; + double f; + double offsetd[2]; + float tcmat[12]; + matrix4x4_t matrix, temp; + switch(tcmod->tcmod) + { + case Q3TCMOD_COUNT: + case Q3TCMOD_NONE: + if (currentmaterialflags & MATERIALFLAG_WATERSCROLL) + matrix = r_waterscrollmatrix; + else + matrix = identitymatrix; + break; + case Q3TCMOD_ENTITYTRANSLATE: + // this is used in Q3 to allow the gamecode to control texcoord + // scrolling on the entity, which is not supported in darkplaces yet. + Matrix4x4_CreateTranslate(&matrix, 0, 0, 0); + break; + case Q3TCMOD_ROTATE: + Matrix4x4_CreateTranslate(&matrix, 0.5, 0.5, 0); + Matrix4x4_ConcatRotate(&matrix, tcmod->parms[0] * rsurface.shadertime, 0, 0, 1); + Matrix4x4_ConcatTranslate(&matrix, -0.5, -0.5, 0); + break; + case Q3TCMOD_SCALE: + Matrix4x4_CreateScale3(&matrix, tcmod->parms[0], tcmod->parms[1], 1); + break; + case Q3TCMOD_SCROLL: + // extra care is needed because of precision breakdown with large values of time + offsetd[0] = tcmod->parms[0] * rsurface.shadertime; + offsetd[1] = tcmod->parms[1] * rsurface.shadertime; + Matrix4x4_CreateTranslate(&matrix, offsetd[0] - floor(offsetd[0]), offsetd[1] - floor(offsetd[1]), 0); + break; + case Q3TCMOD_PAGE: // poor man's animmap (to store animations into a single file, useful for HTTP downloaded textures) + w = (int) tcmod->parms[0]; + h = (int) tcmod->parms[1]; + f = rsurface.shadertime / (tcmod->parms[2] * w * h); + f = f - floor(f); + idx = (int) floor(f * w * h); + Matrix4x4_CreateTranslate(&matrix, (idx % w) / tcmod->parms[0], (idx / w) / tcmod->parms[1], 0); + break; + case Q3TCMOD_STRETCH: + f = 1.0f / R_EvaluateQ3WaveFunc(tcmod->wavefunc, tcmod->waveparms); + Matrix4x4_CreateFromQuakeEntity(&matrix, 0.5f * (1 - f), 0.5 * (1 - f), 0, 0, 0, 0, f); + break; + case Q3TCMOD_TRANSFORM: + VectorSet(tcmat + 0, tcmod->parms[0], tcmod->parms[1], 0); + VectorSet(tcmat + 3, tcmod->parms[2], tcmod->parms[3], 0); + VectorSet(tcmat + 6, 0 , 0 , 1); + VectorSet(tcmat + 9, tcmod->parms[4], tcmod->parms[5], 0); + Matrix4x4_FromArray12FloatGL(&matrix, tcmat); + break; + case Q3TCMOD_TURBULENT: + // this is handled in the RSurf_PrepareVertices function + matrix = identitymatrix; + break; + } + temp = *texmatrix; + Matrix4x4_Concat(texmatrix, &matrix, &temp); +} + +void R_LoadQWSkin(r_qwskincache_t *cache, const char *skinname) +{ + int textureflags = (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_PICMIP; + char name[MAX_QPATH]; + skinframe_t *skinframe; + unsigned char pixels[296*194]; + strlcpy(cache->name, skinname, sizeof(cache->name)); + dpsnprintf(name, sizeof(name), "skins/%s.pcx", cache->name); + if (developer_loading.integer) + Con_Printf("loading %s\n", name); + skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, false); + if (!skinframe || !skinframe->base) + { + unsigned char *f; + fs_offset_t filesize; + skinframe = NULL; + f = FS_LoadFile(name, tempmempool, true, &filesize); + if (f) + { + if (LoadPCX_QWSkin(f, (int)filesize, pixels, 296, 194)) + skinframe = R_SkinFrame_LoadInternalQuake(name, textureflags, true, r_fullbrights.integer, pixels, image_width, image_height); + Mem_Free(f); + } + } + cache->skinframe = skinframe; +} + +texture_t *R_GetCurrentTexture(texture_t *t) +{ + int i; + const entity_render_t *ent = rsurface.entity; + dp_model_t *model = ent->model; + q3shaderinfo_layer_tcmod_t *tcmod; + + if (t->update_lastrenderframe == r_textureframe && t->update_lastrenderentity == (void *)ent) + return t->currentframe; + t->update_lastrenderframe = r_textureframe; + t->update_lastrenderentity = (void *)ent; + + if(ent && ent->entitynumber >= MAX_EDICTS && ent->entitynumber < 2 * MAX_EDICTS) + t->camera_entity = ent->entitynumber; + else + t->camera_entity = 0; + + // switch to an alternate material if this is a q1bsp animated material + { + texture_t *texture = t; + int s = rsurface.ent_skinnum; + if ((unsigned int)s >= (unsigned int)model->numskins) + s = 0; + if (model->skinscenes) + { + if (model->skinscenes[s].framecount > 1) + s = model->skinscenes[s].firstframe + (unsigned int) (rsurface.shadertime * model->skinscenes[s].framerate) % model->skinscenes[s].framecount; + else + s = model->skinscenes[s].firstframe; + } + if (s > 0) + t = t + s * model->num_surfaces; + if (t->animated) + { + // use an alternate animation if the entity's frame is not 0, + // and only if the texture has an alternate animation + if (rsurface.ent_alttextures && t->anim_total[1]) + t = t->anim_frames[1][(t->anim_total[1] >= 2) ? ((int)(rsurface.shadertime * 5.0f) % t->anim_total[1]) : 0]; + else + t = t->anim_frames[0][(t->anim_total[0] >= 2) ? ((int)(rsurface.shadertime * 5.0f) % t->anim_total[0]) : 0]; + } + texture->currentframe = t; + } + + // update currentskinframe to be a qw skin or animation frame + if (rsurface.ent_qwskin >= 0) + { + i = rsurface.ent_qwskin; + if (!r_qwskincache || r_qwskincache_size != cl.maxclients) + { + r_qwskincache_size = cl.maxclients; + if (r_qwskincache) + Mem_Free(r_qwskincache); + r_qwskincache = (r_qwskincache_t *)Mem_Alloc(r_main_mempool, sizeof(*r_qwskincache) * r_qwskincache_size); + } + if (strcmp(r_qwskincache[i].name, cl.scores[i].qw_skin)) + R_LoadQWSkin(&r_qwskincache[i], cl.scores[i].qw_skin); + t->currentskinframe = r_qwskincache[i].skinframe; + if (t->currentskinframe == NULL) + t->currentskinframe = t->skinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->skinframerate, t->numskinframes)]; + } + else if (t->numskinframes >= 2) + t->currentskinframe = t->skinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->skinframerate, t->numskinframes)]; + if (t->backgroundnumskinframes >= 2) + t->backgroundcurrentskinframe = t->backgroundskinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->backgroundskinframerate, t->backgroundnumskinframes)]; + + t->currentmaterialflags = t->basematerialflags; + t->currentalpha = rsurface.colormod[3]; + if (t->basematerialflags & MATERIALFLAG_WATERALPHA && (model->brush.supportwateralpha || r_novis.integer || r_trippy.integer)) + t->currentalpha *= r_wateralpha.value; + if(t->basematerialflags & MATERIALFLAG_WATERSHADER && r_waterstate.enabled && !r_refdef.view.isoverlay) + t->currentmaterialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; // we apply wateralpha later + if(!r_waterstate.enabled || r_refdef.view.isoverlay) + t->currentmaterialflags &= ~(MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA); + if (!(rsurface.ent_flags & RENDER_LIGHT)) + t->currentmaterialflags |= MATERIALFLAG_FULLBRIGHT; + else if (FAKELIGHT_ENABLED) + { + // no modellight if using fakelight for the map + } + else if (rsurface.modeltexcoordlightmap2f == NULL && !(t->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) + { + // pick a model lighting mode + if (VectorLength2(rsurface.modellight_diffuse) >= (1.0f / 256.0f)) + t->currentmaterialflags |= MATERIALFLAG_MODELLIGHT | MATERIALFLAG_MODELLIGHT_DIRECTIONAL; + else + t->currentmaterialflags |= MATERIALFLAG_MODELLIGHT; + } + if (rsurface.ent_flags & RENDER_ADDITIVE) + t->currentmaterialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else if (t->currentalpha < 1) + t->currentmaterialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + if (rsurface.ent_flags & RENDER_DOUBLESIDED) + t->currentmaterialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_NOCULLFACE; + if (rsurface.ent_flags & (RENDER_NODEPTHTEST | RENDER_VIEWMODEL)) + t->currentmaterialflags |= MATERIALFLAG_SHORTDEPTHRANGE; + if (t->backgroundnumskinframes) + t->currentmaterialflags |= MATERIALFLAG_VERTEXTEXTUREBLEND; + if (t->currentmaterialflags & MATERIALFLAG_BLENDED) + { + if (t->currentmaterialflags & (MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER | MATERIALFLAG_CAMERA)) + t->currentmaterialflags &= ~MATERIALFLAG_BLENDED; + } + else + t->currentmaterialflags &= ~(MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER | MATERIALFLAG_CAMERA); + if (vid.allowalphatocoverage && r_transparent_alphatocoverage.integer >= 2 && ((t->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA | MATERIALFLAG_ADD | MATERIALFLAG_CUSTOMBLEND)) == (MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA))) + { + // promote alphablend to alphatocoverage (a type of alphatest) if antialiasing is on + t->currentmaterialflags = (t->currentmaterialflags & ~(MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA)) | MATERIALFLAG_ALPHATEST; + } + if ((t->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST)) == MATERIALFLAG_BLENDED && r_transparentdepthmasking.integer && !(t->basematerialflags & MATERIALFLAG_BLENDED)) + t->currentmaterialflags |= MATERIALFLAG_TRANSDEPTH; + + // there is no tcmod + if (t->currentmaterialflags & MATERIALFLAG_WATERSCROLL) + { + t->currenttexmatrix = r_waterscrollmatrix; + t->currentbackgroundtexmatrix = r_waterscrollmatrix; + } + else if (!(t->currentmaterialflags & MATERIALFLAG_CUSTOMSURFACE)) + { + Matrix4x4_CreateIdentity(&t->currenttexmatrix); + Matrix4x4_CreateIdentity(&t->currentbackgroundtexmatrix); + } + + for (i = 0, tcmod = t->tcmods;i < Q3MAXTCMODS && tcmod->tcmod;i++, tcmod++) + R_tcMod_ApplyToMatrix(&t->currenttexmatrix, tcmod, t->currentmaterialflags); + for (i = 0, tcmod = t->backgroundtcmods;i < Q3MAXTCMODS && tcmod->tcmod;i++, tcmod++) + R_tcMod_ApplyToMatrix(&t->currentbackgroundtexmatrix, tcmod, t->currentmaterialflags); + + t->colormapping = VectorLength2(rsurface.colormap_pantscolor) + VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f); + if (t->currentskinframe->qpixels) + R_SkinFrame_GenerateTexturesFromQPixels(t->currentskinframe, t->colormapping); + t->basetexture = (!t->colormapping && t->currentskinframe->merged) ? t->currentskinframe->merged : t->currentskinframe->base; + if (!t->basetexture) + t->basetexture = r_texture_notexture; + t->pantstexture = t->colormapping ? t->currentskinframe->pants : NULL; + t->shirttexture = t->colormapping ? t->currentskinframe->shirt : NULL; + t->nmaptexture = t->currentskinframe->nmap; + if (!t->nmaptexture) + t->nmaptexture = r_texture_blanknormalmap; + t->glosstexture = r_texture_black; + t->glowtexture = t->currentskinframe->glow; + t->fogtexture = t->currentskinframe->fog; + t->reflectmasktexture = t->currentskinframe->reflect; + if (t->backgroundnumskinframes) + { + t->backgroundbasetexture = (!t->colormapping && t->backgroundcurrentskinframe->merged) ? t->backgroundcurrentskinframe->merged : t->backgroundcurrentskinframe->base; + t->backgroundnmaptexture = t->backgroundcurrentskinframe->nmap; + t->backgroundglosstexture = r_texture_black; + t->backgroundglowtexture = t->backgroundcurrentskinframe->glow; + if (!t->backgroundnmaptexture) + t->backgroundnmaptexture = r_texture_blanknormalmap; + } + else + { + t->backgroundbasetexture = r_texture_white; + t->backgroundnmaptexture = r_texture_blanknormalmap; + t->backgroundglosstexture = r_texture_black; + t->backgroundglowtexture = NULL; + } + t->specularpower = r_shadow_glossexponent.value; + // TODO: store reference values for these in the texture? + t->specularscale = 0; + if (r_shadow_gloss.integer > 0) + { + if (t->currentskinframe->gloss || (t->backgroundcurrentskinframe && t->backgroundcurrentskinframe->gloss)) + { + if (r_shadow_glossintensity.value > 0) + { + t->glosstexture = t->currentskinframe->gloss ? t->currentskinframe->gloss : r_texture_white; + t->backgroundglosstexture = (t->backgroundcurrentskinframe && t->backgroundcurrentskinframe->gloss) ? t->backgroundcurrentskinframe->gloss : r_texture_white; + t->specularscale = r_shadow_glossintensity.value; + } + } + else if (r_shadow_gloss.integer >= 2 && r_shadow_gloss2intensity.value > 0) + { + t->glosstexture = r_texture_white; + t->backgroundglosstexture = r_texture_white; + t->specularscale = r_shadow_gloss2intensity.value; + t->specularpower = r_shadow_gloss2exponent.value; + } + } + t->specularscale *= t->specularscalemod; + t->specularpower *= t->specularpowermod; + + // lightmaps mode looks bad with dlights using actual texturing, so turn + // off the colormap and glossmap, but leave the normalmap on as it still + // accurately represents the shading involved + if (gl_lightmaps.integer) + { + t->basetexture = r_texture_grey128; + t->pantstexture = r_texture_black; + t->shirttexture = r_texture_black; + t->nmaptexture = r_texture_blanknormalmap; + t->glosstexture = r_texture_black; + t->glowtexture = NULL; + t->fogtexture = NULL; + t->reflectmasktexture = NULL; + t->backgroundbasetexture = NULL; + t->backgroundnmaptexture = r_texture_blanknormalmap; + t->backgroundglosstexture = r_texture_black; + t->backgroundglowtexture = NULL; + t->specularscale = 0; + t->currentmaterialflags = MATERIALFLAG_WALL | (t->currentmaterialflags & (MATERIALFLAG_NOCULLFACE | MATERIALFLAG_MODELLIGHT | MATERIALFLAG_MODELLIGHT_DIRECTIONAL | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SHORTDEPTHRANGE)); + } + + Vector4Set(t->lightmapcolor, rsurface.colormod[0], rsurface.colormod[1], rsurface.colormod[2], t->currentalpha); + VectorClear(t->dlightcolor); + t->currentnumlayers = 0; + if (t->currentmaterialflags & MATERIALFLAG_WALL) + { + int blendfunc1, blendfunc2; + qboolean depthmask; + if (t->currentmaterialflags & MATERIALFLAG_ADD) + { + blendfunc1 = GL_SRC_ALPHA; + blendfunc2 = GL_ONE; + } + else if (t->currentmaterialflags & MATERIALFLAG_ALPHA) + { + blendfunc1 = GL_SRC_ALPHA; + blendfunc2 = GL_ONE_MINUS_SRC_ALPHA; + } + else if (t->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) + { + blendfunc1 = t->customblendfunc[0]; + blendfunc2 = t->customblendfunc[1]; + } + else + { + blendfunc1 = GL_ONE; + blendfunc2 = GL_ZERO; + } + // don't colormod evilblend textures + if(!R_BlendFuncFlags(blendfunc1, blendfunc2) & BLENDFUNC_ALLOWS_COLORMOD) + VectorSet(t->lightmapcolor, 1, 1, 1); + depthmask = !(t->currentmaterialflags & MATERIALFLAG_BLENDED); + if (t->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) + { + // fullbright is not affected by r_refdef.lightmapintensity + R_Texture_AddLayer(t, depthmask, blendfunc1, blendfunc2, TEXTURELAYERTYPE_TEXTURE, t->basetexture, &t->currenttexmatrix, t->lightmapcolor[0], t->lightmapcolor[1], t->lightmapcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * t->lightmapcolor[0], rsurface.colormap_pantscolor[1] * t->lightmapcolor[1], rsurface.colormap_pantscolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * t->lightmapcolor[0], rsurface.colormap_shirtcolor[1] * t->lightmapcolor[1], rsurface.colormap_shirtcolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); + } + else + { + vec3_t ambientcolor; + float colorscale; + // set the color tint used for lights affecting this surface + VectorSet(t->dlightcolor, t->lightmapcolor[0] * t->lightmapcolor[3], t->lightmapcolor[1] * t->lightmapcolor[3], t->lightmapcolor[2] * t->lightmapcolor[3]); + colorscale = 2; + // q3bsp has no lightmap updates, so the lightstylevalue that + // would normally be baked into the lightmap must be + // applied to the color + // FIXME: r_glsl 1 rendering doesn't support overbright lightstyles with this (the default light style is not overbright) + if (model->type == mod_brushq3) + colorscale *= r_refdef.scene.rtlightstylevalue[0]; + colorscale *= r_refdef.lightmapintensity; + VectorScale(t->lightmapcolor, r_refdef.scene.ambient, ambientcolor); + VectorScale(t->lightmapcolor, colorscale, t->lightmapcolor); + // basic lit geometry + R_Texture_AddLayer(t, depthmask, blendfunc1, blendfunc2, TEXTURELAYERTYPE_LITTEXTURE, t->basetexture, &t->currenttexmatrix, t->lightmapcolor[0], t->lightmapcolor[1], t->lightmapcolor[2], t->lightmapcolor[3]); + // add pants/shirt if needed + if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_LITTEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * t->lightmapcolor[0], rsurface.colormap_pantscolor[1] * t->lightmapcolor[1], rsurface.colormap_pantscolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_LITTEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * t->lightmapcolor[0], rsurface.colormap_shirtcolor[1] * t->lightmapcolor[1], rsurface.colormap_shirtcolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); + // now add ambient passes if needed + if (VectorLength2(ambientcolor) >= (1.0f/1048576.0f)) + { + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->basetexture, &t->currenttexmatrix, ambientcolor[0], ambientcolor[1], ambientcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * ambientcolor[0], rsurface.colormap_pantscolor[1] * ambientcolor[1], rsurface.colormap_pantscolor[2] * ambientcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * ambientcolor[0], rsurface.colormap_shirtcolor[1] * ambientcolor[1], rsurface.colormap_shirtcolor[2] * ambientcolor[2], t->lightmapcolor[3]); + } + } + if (t->glowtexture != NULL && !gl_lightmaps.integer) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->glowtexture, &t->currenttexmatrix, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2], t->lightmapcolor[3]); + if (r_refdef.fogenabled && !(t->currentmaterialflags & MATERIALFLAG_ADD)) + { + // if this is opaque use alpha blend which will darken the earlier + // passes cheaply. + // + // if this is an alpha blended material, all the earlier passes + // were darkened by fog already, so we only need to add the fog + // color ontop through the fog mask texture + // + // if this is an additive blended material, all the earlier passes + // were darkened by fog already, and we should not add fog color + // (because the background was not darkened, there is no fog color + // that was lost behind it). + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, (t->currentmaterialflags & MATERIALFLAG_BLENDED) ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA, TEXTURELAYERTYPE_FOG, t->fogtexture, &t->currenttexmatrix, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2], t->lightmapcolor[3]); + } + } + + return t->currentframe; +} + +rsurfacestate_t rsurface; + +void RSurf_ActiveWorldEntity(void) +{ + dp_model_t *model = r_refdef.scene.worldmodel; + //if (rsurface.entity == r_refdef.scene.worldentity) + // return; + rsurface.entity = r_refdef.scene.worldentity; + rsurface.skeleton = NULL; + memset(rsurface.userwavefunc_param, 0, sizeof(rsurface.userwavefunc_param)); + rsurface.ent_skinnum = 0; + rsurface.ent_qwskin = -1; + rsurface.ent_flags = r_refdef.scene.worldentity->flags; + rsurface.shadertime = r_refdef.scene.time; + rsurface.matrix = identitymatrix; + rsurface.inversematrix = identitymatrix; + rsurface.matrixscale = 1; + rsurface.inversematrixscale = 1; + R_EntityMatrix(&identitymatrix); + VectorCopy(r_refdef.view.origin, rsurface.localvieworigin); + Vector4Copy(r_refdef.fogplane, rsurface.fogplane); + rsurface.fograngerecip = r_refdef.fograngerecip; + rsurface.fogheightfade = r_refdef.fogheightfade; + rsurface.fogplaneviewdist = r_refdef.fogplaneviewdist; + rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; + VectorSet(rsurface.modellight_ambient, 0, 0, 0); + VectorSet(rsurface.modellight_diffuse, 0, 0, 0); + VectorSet(rsurface.modellight_lightdir, 0, 0, 1); + VectorSet(rsurface.colormap_pantscolor, 0, 0, 0); + VectorSet(rsurface.colormap_shirtcolor, 0, 0, 0); + VectorSet(rsurface.colormod, r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale); + rsurface.colormod[3] = 1; + VectorSet(rsurface.glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value); + memset(rsurface.frameblend, 0, sizeof(rsurface.frameblend)); + rsurface.frameblend[0].lerp = 1; + rsurface.ent_alttextures = false; + rsurface.basepolygonfactor = r_refdef.polygonfactor; + rsurface.basepolygonoffset = r_refdef.polygonoffset; + rsurface.modelvertex3f = model->surfmesh.data_vertex3f; + rsurface.modelvertex3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelvertex3f_bufferoffset = model->surfmesh.vbooffset_vertex3f; + rsurface.modelsvector3f = model->surfmesh.data_svector3f; + rsurface.modelsvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelsvector3f_bufferoffset = model->surfmesh.vbooffset_svector3f; + rsurface.modeltvector3f = model->surfmesh.data_tvector3f; + rsurface.modeltvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltvector3f_bufferoffset = model->surfmesh.vbooffset_tvector3f; + rsurface.modelnormal3f = model->surfmesh.data_normal3f; + rsurface.modelnormal3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelnormal3f_bufferoffset = model->surfmesh.vbooffset_normal3f; + rsurface.modellightmapcolor4f = model->surfmesh.data_lightmapcolor4f; + rsurface.modellightmapcolor4f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modellightmapcolor4f_bufferoffset = model->surfmesh.vbooffset_lightmapcolor4f; + rsurface.modeltexcoordtexture2f = model->surfmesh.data_texcoordtexture2f; + rsurface.modeltexcoordtexture2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltexcoordtexture2f_bufferoffset = model->surfmesh.vbooffset_texcoordtexture2f; + rsurface.modeltexcoordlightmap2f = model->surfmesh.data_texcoordlightmap2f; + rsurface.modeltexcoordlightmap2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltexcoordlightmap2f_bufferoffset = model->surfmesh.vbooffset_texcoordlightmap2f; + rsurface.modelelement3i = model->surfmesh.data_element3i; + rsurface.modelelement3i_indexbuffer = model->surfmesh.data_element3i_indexbuffer; + rsurface.modelelement3i_bufferoffset = model->surfmesh.data_element3i_bufferoffset; + rsurface.modelelement3s = model->surfmesh.data_element3s; + rsurface.modelelement3s_indexbuffer = model->surfmesh.data_element3s_indexbuffer; + rsurface.modelelement3s_bufferoffset = model->surfmesh.data_element3s_bufferoffset; + rsurface.modellightmapoffsets = model->surfmesh.data_lightmapoffsets; + rsurface.modelnumvertices = model->surfmesh.num_vertices; + rsurface.modelnumtriangles = model->surfmesh.num_triangles; + rsurface.modelsurfaces = model->data_surfaces; + rsurface.modelvertexmesh = model->surfmesh.vertexmesh; + rsurface.modelvertexmeshbuffer = model->surfmesh.vertexmeshbuffer; + rsurface.modelvertex3fbuffer = model->surfmesh.vertex3fbuffer; + rsurface.modelgeneratedvertex = false; + rsurface.batchgeneratedvertex = false; + rsurface.batchfirstvertex = 0; + rsurface.batchnumvertices = 0; + rsurface.batchfirsttriangle = 0; + rsurface.batchnumtriangles = 0; + rsurface.batchvertex3f = NULL; + rsurface.batchvertex3f_vertexbuffer = NULL; + rsurface.batchvertex3f_bufferoffset = 0; + rsurface.batchsvector3f = NULL; + rsurface.batchsvector3f_vertexbuffer = NULL; + rsurface.batchsvector3f_bufferoffset = 0; + rsurface.batchtvector3f = NULL; + rsurface.batchtvector3f_vertexbuffer = NULL; + rsurface.batchtvector3f_bufferoffset = 0; + rsurface.batchnormal3f = NULL; + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + rsurface.batchlightmapcolor4f = NULL; + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + rsurface.batchtexcoordtexture2f = NULL; + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + rsurface.batchtexcoordlightmap2f = NULL; + rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; + rsurface.batchtexcoordlightmap2f_bufferoffset = 0; + rsurface.batchvertexmesh = NULL; + rsurface.batchvertexmeshbuffer = NULL; + rsurface.batchvertex3fbuffer = NULL; + rsurface.batchelement3i = NULL; + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = NULL; + rsurface.passcolor4f_bufferoffset = 0; +} + +void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, qboolean wanttangents, qboolean prepass) +{ + dp_model_t *model = ent->model; + //if (rsurface.entity == ent && (!model->surfmesh.isanimated || (!wantnormals && !wanttangents))) + // return; + rsurface.entity = (entity_render_t *)ent; + rsurface.skeleton = ent->skeleton; + memcpy(rsurface.userwavefunc_param, ent->userwavefunc_param, sizeof(rsurface.userwavefunc_param)); + rsurface.ent_skinnum = ent->skinnum; + rsurface.ent_qwskin = (ent->entitynumber <= cl.maxclients && ent->entitynumber >= 1 && cls.protocol == PROTOCOL_QUAKEWORLD && cl.scores[ent->entitynumber - 1].qw_skin[0] && !strcmp(ent->model->name, "progs/player.mdl")) ? (ent->entitynumber - 1) : -1; + rsurface.ent_flags = ent->flags; + rsurface.shadertime = r_refdef.scene.time - ent->shadertime; + rsurface.matrix = ent->matrix; + rsurface.inversematrix = ent->inversematrix; + rsurface.matrixscale = Matrix4x4_ScaleFromMatrix(&rsurface.matrix); + rsurface.inversematrixscale = 1.0f / rsurface.matrixscale; + R_EntityMatrix(&rsurface.matrix); + Matrix4x4_Transform(&rsurface.inversematrix, r_refdef.view.origin, rsurface.localvieworigin); + Matrix4x4_TransformStandardPlane(&rsurface.inversematrix, r_refdef.fogplane[0], r_refdef.fogplane[1], r_refdef.fogplane[2], r_refdef.fogplane[3], rsurface.fogplane); + rsurface.fogplaneviewdist *= rsurface.inversematrixscale; + rsurface.fograngerecip = r_refdef.fograngerecip * rsurface.matrixscale; + rsurface.fogheightfade = r_refdef.fogheightfade * rsurface.matrixscale; + rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; + VectorCopy(ent->modellight_ambient, rsurface.modellight_ambient); + VectorCopy(ent->modellight_diffuse, rsurface.modellight_diffuse); + VectorCopy(ent->modellight_lightdir, rsurface.modellight_lightdir); + VectorCopy(ent->colormap_pantscolor, rsurface.colormap_pantscolor); + VectorCopy(ent->colormap_shirtcolor, rsurface.colormap_shirtcolor); + VectorScale(ent->colormod, r_refdef.view.colorscale, rsurface.colormod); + rsurface.colormod[3] = ent->alpha; + VectorScale(ent->glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, rsurface.glowmod); + memcpy(rsurface.frameblend, ent->frameblend, sizeof(ent->frameblend)); + rsurface.ent_alttextures = ent->framegroupblend[0].frame != 0; + rsurface.basepolygonfactor = r_refdef.polygonfactor; + rsurface.basepolygonoffset = r_refdef.polygonoffset; + if (ent->model->brush.submodel && !prepass) + { + rsurface.basepolygonfactor += r_polygonoffset_submodel_factor.value; + rsurface.basepolygonoffset += r_polygonoffset_submodel_offset.value; + } + if (model->surfmesh.isanimated && model->AnimateVertices) + { + if (ent->animcache_vertex3f) + { + rsurface.modelvertex3f = ent->animcache_vertex3f; + rsurface.modelsvector3f = wanttangents ? ent->animcache_svector3f : NULL; + rsurface.modeltvector3f = wanttangents ? ent->animcache_tvector3f : NULL; + rsurface.modelnormal3f = wantnormals ? ent->animcache_normal3f : NULL; + rsurface.modelvertexmesh = ent->animcache_vertexmesh; + rsurface.modelvertexmeshbuffer = ent->animcache_vertexmeshbuffer; + rsurface.modelvertex3fbuffer = ent->animcache_vertex3fbuffer; + } + else if (wanttangents) + { + rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modelsvector3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modeltvector3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modelnormal3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, rsurface.modelnormal3f, rsurface.modelsvector3f, rsurface.modeltvector3f); + rsurface.modelvertexmesh = NULL; + rsurface.modelvertexmeshbuffer = NULL; + rsurface.modelvertex3fbuffer = NULL; + } + else if (wantnormals) + { + rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modelsvector3f = NULL; + rsurface.modeltvector3f = NULL; + rsurface.modelnormal3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, rsurface.modelnormal3f, NULL, NULL); + rsurface.modelvertexmesh = NULL; + rsurface.modelvertexmeshbuffer = NULL; + rsurface.modelvertex3fbuffer = NULL; + } + else + { + rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modelsvector3f = NULL; + rsurface.modeltvector3f = NULL; + rsurface.modelnormal3f = NULL; + model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, NULL, NULL, NULL); + rsurface.modelvertexmesh = NULL; + rsurface.modelvertexmeshbuffer = NULL; + rsurface.modelvertex3fbuffer = NULL; + } + rsurface.modelvertex3f_vertexbuffer = 0; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelsvector3f_vertexbuffer = 0; + rsurface.modelsvector3f_bufferoffset = 0; + rsurface.modeltvector3f_vertexbuffer = 0; + rsurface.modeltvector3f_bufferoffset = 0; + rsurface.modelnormal3f_vertexbuffer = 0; + rsurface.modelnormal3f_bufferoffset = 0; + rsurface.modelgeneratedvertex = true; + } + else + { + rsurface.modelvertex3f = model->surfmesh.data_vertex3f; + rsurface.modelvertex3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelvertex3f_bufferoffset = model->surfmesh.vbooffset_vertex3f; + rsurface.modelsvector3f = model->surfmesh.data_svector3f; + rsurface.modelsvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelsvector3f_bufferoffset = model->surfmesh.vbooffset_svector3f; + rsurface.modeltvector3f = model->surfmesh.data_tvector3f; + rsurface.modeltvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltvector3f_bufferoffset = model->surfmesh.vbooffset_tvector3f; + rsurface.modelnormal3f = model->surfmesh.data_normal3f; + rsurface.modelnormal3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelnormal3f_bufferoffset = model->surfmesh.vbooffset_normal3f; + rsurface.modelvertexmesh = model->surfmesh.vertexmesh; + rsurface.modelvertexmeshbuffer = model->surfmesh.vertexmeshbuffer; + rsurface.modelvertex3fbuffer = model->surfmesh.vertex3fbuffer; + rsurface.modelgeneratedvertex = false; + } + rsurface.modellightmapcolor4f = model->surfmesh.data_lightmapcolor4f; + rsurface.modellightmapcolor4f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modellightmapcolor4f_bufferoffset = model->surfmesh.vbooffset_lightmapcolor4f; + rsurface.modeltexcoordtexture2f = model->surfmesh.data_texcoordtexture2f; + rsurface.modeltexcoordtexture2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltexcoordtexture2f_bufferoffset = model->surfmesh.vbooffset_texcoordtexture2f; + rsurface.modeltexcoordlightmap2f = model->surfmesh.data_texcoordlightmap2f; + rsurface.modeltexcoordlightmap2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltexcoordlightmap2f_bufferoffset = model->surfmesh.vbooffset_texcoordlightmap2f; + rsurface.modelelement3i = model->surfmesh.data_element3i; + rsurface.modelelement3i_indexbuffer = model->surfmesh.data_element3i_indexbuffer; + rsurface.modelelement3i_bufferoffset = model->surfmesh.data_element3i_bufferoffset; + rsurface.modelelement3s = model->surfmesh.data_element3s; + rsurface.modelelement3s_indexbuffer = model->surfmesh.data_element3s_indexbuffer; + rsurface.modelelement3s_bufferoffset = model->surfmesh.data_element3s_bufferoffset; + rsurface.modellightmapoffsets = model->surfmesh.data_lightmapoffsets; + rsurface.modelnumvertices = model->surfmesh.num_vertices; + rsurface.modelnumtriangles = model->surfmesh.num_triangles; + rsurface.modelsurfaces = model->data_surfaces; + rsurface.batchgeneratedvertex = false; + rsurface.batchfirstvertex = 0; + rsurface.batchnumvertices = 0; + rsurface.batchfirsttriangle = 0; + rsurface.batchnumtriangles = 0; + rsurface.batchvertex3f = NULL; + rsurface.batchvertex3f_vertexbuffer = NULL; + rsurface.batchvertex3f_bufferoffset = 0; + rsurface.batchsvector3f = NULL; + rsurface.batchsvector3f_vertexbuffer = NULL; + rsurface.batchsvector3f_bufferoffset = 0; + rsurface.batchtvector3f = NULL; + rsurface.batchtvector3f_vertexbuffer = NULL; + rsurface.batchtvector3f_bufferoffset = 0; + rsurface.batchnormal3f = NULL; + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + rsurface.batchlightmapcolor4f = NULL; + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + rsurface.batchtexcoordtexture2f = NULL; + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + rsurface.batchtexcoordlightmap2f = NULL; + rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; + rsurface.batchtexcoordlightmap2f_bufferoffset = 0; + rsurface.batchvertexmesh = NULL; + rsurface.batchvertexmeshbuffer = NULL; + rsurface.batchvertex3fbuffer = NULL; + rsurface.batchelement3i = NULL; + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = NULL; + rsurface.passcolor4f_bufferoffset = 0; +} + +void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, int entflags, double shadertime, float r, float g, float b, float a, int numvertices, const float *vertex3f, const float *texcoord2f, const float *normal3f, const float *svector3f, const float *tvector3f, const float *color4f, int numtriangles, const int *element3i, const unsigned short *element3s, qboolean wantnormals, qboolean wanttangents) +{ + rsurface.entity = r_refdef.scene.worldentity; + rsurface.skeleton = NULL; + rsurface.ent_skinnum = 0; + rsurface.ent_qwskin = -1; + rsurface.ent_flags = entflags; + rsurface.shadertime = r_refdef.scene.time - shadertime; + rsurface.modelnumvertices = numvertices; + rsurface.modelnumtriangles = numtriangles; + rsurface.matrix = *matrix; + rsurface.inversematrix = *inversematrix; + rsurface.matrixscale = Matrix4x4_ScaleFromMatrix(&rsurface.matrix); + rsurface.inversematrixscale = 1.0f / rsurface.matrixscale; + R_EntityMatrix(&rsurface.matrix); + Matrix4x4_Transform(&rsurface.inversematrix, r_refdef.view.origin, rsurface.localvieworigin); + Matrix4x4_TransformStandardPlane(&rsurface.inversematrix, r_refdef.fogplane[0], r_refdef.fogplane[1], r_refdef.fogplane[2], r_refdef.fogplane[3], rsurface.fogplane); + rsurface.fogplaneviewdist *= rsurface.inversematrixscale; + rsurface.fograngerecip = r_refdef.fograngerecip * rsurface.matrixscale; + rsurface.fogheightfade = r_refdef.fogheightfade * rsurface.matrixscale; + rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; + VectorSet(rsurface.modellight_ambient, 0, 0, 0); + VectorSet(rsurface.modellight_diffuse, 0, 0, 0); + VectorSet(rsurface.modellight_lightdir, 0, 0, 1); + VectorSet(rsurface.colormap_pantscolor, 0, 0, 0); + VectorSet(rsurface.colormap_shirtcolor, 0, 0, 0); + Vector4Set(rsurface.colormod, r * r_refdef.view.colorscale, g * r_refdef.view.colorscale, b * r_refdef.view.colorscale, a); + VectorSet(rsurface.glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value); + memset(rsurface.frameblend, 0, sizeof(rsurface.frameblend)); + rsurface.frameblend[0].lerp = 1; + rsurface.ent_alttextures = false; + rsurface.basepolygonfactor = r_refdef.polygonfactor; + rsurface.basepolygonoffset = r_refdef.polygonoffset; + if (wanttangents) + { + rsurface.modelvertex3f = (float *)vertex3f; + rsurface.modelsvector3f = svector3f ? (float *)svector3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + rsurface.modeltvector3f = tvector3f ? (float *)tvector3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + rsurface.modelnormal3f = normal3f ? (float *)normal3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + } + else if (wantnormals) + { + rsurface.modelvertex3f = (float *)vertex3f; + rsurface.modelsvector3f = NULL; + rsurface.modeltvector3f = NULL; + rsurface.modelnormal3f = normal3f ? (float *)normal3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + } + else + { + rsurface.modelvertex3f = (float *)vertex3f; + rsurface.modelsvector3f = NULL; + rsurface.modeltvector3f = NULL; + rsurface.modelnormal3f = NULL; + } + rsurface.modelvertexmesh = NULL; + rsurface.modelvertexmeshbuffer = NULL; + rsurface.modelvertex3fbuffer = NULL; + rsurface.modelvertex3f_vertexbuffer = 0; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelsvector3f_vertexbuffer = 0; + rsurface.modelsvector3f_bufferoffset = 0; + rsurface.modeltvector3f_vertexbuffer = 0; + rsurface.modeltvector3f_bufferoffset = 0; + rsurface.modelnormal3f_vertexbuffer = 0; + rsurface.modelnormal3f_bufferoffset = 0; + rsurface.modelgeneratedvertex = true; + rsurface.modellightmapcolor4f = (float *)color4f; + rsurface.modellightmapcolor4f_vertexbuffer = 0; + rsurface.modellightmapcolor4f_bufferoffset = 0; + rsurface.modeltexcoordtexture2f = (float *)texcoord2f; + rsurface.modeltexcoordtexture2f_vertexbuffer = 0; + rsurface.modeltexcoordtexture2f_bufferoffset = 0; + rsurface.modeltexcoordlightmap2f = NULL; + rsurface.modeltexcoordlightmap2f_vertexbuffer = 0; + rsurface.modeltexcoordlightmap2f_bufferoffset = 0; + rsurface.modelelement3i = (int *)element3i; + rsurface.modelelement3i_indexbuffer = NULL; + rsurface.modelelement3i_bufferoffset = 0; + rsurface.modelelement3s = (unsigned short *)element3s; + rsurface.modelelement3s_indexbuffer = NULL; + rsurface.modelelement3s_bufferoffset = 0; + rsurface.modellightmapoffsets = NULL; + rsurface.modelsurfaces = NULL; + rsurface.batchgeneratedvertex = false; + rsurface.batchfirstvertex = 0; + rsurface.batchnumvertices = 0; + rsurface.batchfirsttriangle = 0; + rsurface.batchnumtriangles = 0; + rsurface.batchvertex3f = NULL; + rsurface.batchvertex3f_vertexbuffer = NULL; + rsurface.batchvertex3f_bufferoffset = 0; + rsurface.batchsvector3f = NULL; + rsurface.batchsvector3f_vertexbuffer = NULL; + rsurface.batchsvector3f_bufferoffset = 0; + rsurface.batchtvector3f = NULL; + rsurface.batchtvector3f_vertexbuffer = NULL; + rsurface.batchtvector3f_bufferoffset = 0; + rsurface.batchnormal3f = NULL; + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + rsurface.batchlightmapcolor4f = NULL; + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + rsurface.batchtexcoordtexture2f = NULL; + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + rsurface.batchtexcoordlightmap2f = NULL; + rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; + rsurface.batchtexcoordlightmap2f_bufferoffset = 0; + rsurface.batchvertexmesh = NULL; + rsurface.batchvertexmeshbuffer = NULL; + rsurface.batchvertex3fbuffer = NULL; + rsurface.batchelement3i = NULL; + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = NULL; + rsurface.passcolor4f_bufferoffset = 0; + + if (rsurface.modelnumvertices && rsurface.modelelement3i) + { + if ((wantnormals || wanttangents) && !normal3f) + { + rsurface.modelnormal3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + Mod_BuildNormals(0, rsurface.modelnumvertices, rsurface.modelnumtriangles, rsurface.modelvertex3f, rsurface.modelelement3i, rsurface.modelnormal3f, r_smoothnormals_areaweighting.integer != 0); + } + if (wanttangents && !svector3f) + { + rsurface.modelsvector3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + rsurface.modeltvector3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + Mod_BuildTextureVectorsFromNormals(0, rsurface.modelnumvertices, rsurface.modelnumtriangles, rsurface.modelvertex3f, rsurface.modeltexcoordtexture2f, rsurface.modelnormal3f, rsurface.modelelement3i, rsurface.modelsvector3f, rsurface.modeltvector3f, r_smoothnormals_areaweighting.integer != 0); + } + } +} + +float RSurf_FogPoint(const float *v) +{ + // this code is identical to the USEFOGINSIDE/USEFOGOUTSIDE code in the shader + float FogPlaneViewDist = r_refdef.fogplaneviewdist; + float FogPlaneVertexDist = DotProduct(r_refdef.fogplane, v) + r_refdef.fogplane[3]; + float FogHeightFade = r_refdef.fogheightfade; + float fogfrac; + unsigned int fogmasktableindex; + if (r_refdef.fogplaneviewabove) + fogfrac = min(0.0f, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0f, min(0.0f, FogPlaneVertexDist) * FogHeightFade); + else + fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0f, FogPlaneVertexDist)) * min(1.0f, (min(0.0f, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade); + fogmasktableindex = (unsigned int)(VectorDistance(r_refdef.view.origin, v) * fogfrac * r_refdef.fogmasktabledistmultiplier); + return r_refdef.fogmasktable[min(fogmasktableindex, FOGMASKTABLEWIDTH - 1)]; +} + +float RSurf_FogVertex(const float *v) +{ + // this code is identical to the USEFOGINSIDE/USEFOGOUTSIDE code in the shader + float FogPlaneViewDist = rsurface.fogplaneviewdist; + float FogPlaneVertexDist = DotProduct(rsurface.fogplane, v) + rsurface.fogplane[3]; + float FogHeightFade = rsurface.fogheightfade; + float fogfrac; + unsigned int fogmasktableindex; + if (r_refdef.fogplaneviewabove) + fogfrac = min(0.0f, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0f, min(0.0f, FogPlaneVertexDist) * FogHeightFade); + else + fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0f, FogPlaneVertexDist)) * min(1.0f, (min(0.0f, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade); + fogmasktableindex = (unsigned int)(VectorDistance(rsurface.localvieworigin, v) * fogfrac * rsurface.fogmasktabledistmultiplier); + return r_refdef.fogmasktable[min(fogmasktableindex, FOGMASKTABLEWIDTH - 1)]; +} + +void RSurf_RenumberElements(const int *inelement3i, int *outelement3i, int numelements, int adjust) +{ + int i; + for (i = 0;i < numelements;i++) + outelement3i[i] = inelement3i[i] + adjust; +} + +static const int quadedges[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}}; +extern cvar_t gl_vbo; +void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + int deformindex; + int firsttriangle; + int numtriangles; + int firstvertex; + int endvertex; + int numvertices; + int surfacefirsttriangle; + int surfacenumtriangles; + int surfacefirstvertex; + int surfaceendvertex; + int surfacenumvertices; + int batchnumvertices; + int batchnumtriangles; + int needsupdate; + int i, j; + qboolean gaps; + qboolean dynamicvertex; + float amplitude; + float animpos; + float scale; + float center[3], forward[3], right[3], up[3], v[3], newforward[3], newright[3], newup[3]; + float waveparms[4]; + q3shaderinfo_deform_t *deform; + const msurface_t *surface, *firstsurface; + r_vertexmesh_t *vertexmesh; + if (!texturenumsurfaces) + return; + // find vertex range of this surface batch + gaps = false; + firstsurface = texturesurfacelist[0]; + firsttriangle = firstsurface->num_firsttriangle; + batchnumvertices = 0; + batchnumtriangles = 0; + firstvertex = endvertex = firstsurface->num_firstvertex; + for (i = 0;i < texturenumsurfaces;i++) + { + surface = texturesurfacelist[i]; + if (surface != firstsurface + i) + gaps = true; + surfacefirstvertex = surface->num_firstvertex; + surfaceendvertex = surfacefirstvertex + surface->num_vertices; + surfacenumvertices = surface->num_vertices; + surfacenumtriangles = surface->num_triangles; + if (firstvertex > surfacefirstvertex) + firstvertex = surfacefirstvertex; + if (endvertex < surfaceendvertex) + endvertex = surfaceendvertex; + batchnumvertices += surfacenumvertices; + batchnumtriangles += surfacenumtriangles; + } + + // we now know the vertex range used, and if there are any gaps in it + rsurface.batchfirstvertex = firstvertex; + rsurface.batchnumvertices = endvertex - firstvertex; + rsurface.batchfirsttriangle = firsttriangle; + rsurface.batchnumtriangles = batchnumtriangles; + + // this variable holds flags for which properties have been updated that + // may require regenerating vertexmesh array... + needsupdate = 0; + + // check if any dynamic vertex processing must occur + dynamicvertex = false; + + // if there is a chance of animated vertex colors, it's a dynamic batch + if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_ARRAY_VERTEXCOLOR)) && texturesurfacelist[0]->lightmapinfo) + { + dynamicvertex = true; + batchneed |= BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEXCOLOR; + } + + for (deformindex = 0, deform = rsurface.texture->deforms;deformindex < Q3MAXDEFORMS && deform->deform && r_deformvertexes.integer;deformindex++, deform++) + { + switch (deform->deform) + { + default: + case Q3DEFORM_PROJECTIONSHADOW: + case Q3DEFORM_TEXT0: + case Q3DEFORM_TEXT1: + case Q3DEFORM_TEXT2: + case Q3DEFORM_TEXT3: + case Q3DEFORM_TEXT4: + case Q3DEFORM_TEXT5: + case Q3DEFORM_TEXT6: + case Q3DEFORM_TEXT7: + case Q3DEFORM_NONE: + break; + case Q3DEFORM_AUTOSPRITE: + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_AUTOSPRITE2: + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_NORMAL: + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_WAVE: + if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) + break; // if wavefunc is a nop, ignore this transform + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_BULGE: + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_MOVE: + if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) + break; // if wavefunc is a nop, ignore this transform + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX; + break; + } + } + switch(rsurface.texture->tcgen.tcgen) + { + default: + case Q3TCGEN_TEXTURE: + break; + case Q3TCGEN_LIGHTMAP: + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_LIGHTMAP | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_LIGHTMAP; + break; + case Q3TCGEN_VECTOR: + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; + break; + case Q3TCGEN_ENVIRONMENT: + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; + break; + } + if (rsurface.texture->tcmods[0].tcmod == Q3TCMOD_TURBULENT) + { + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS; + needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; + } + + if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP))) + { + dynamicvertex = true; + batchneed |= BATCHNEED_NOGAPS; + needsupdate |= (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)); + } + + if (dynamicvertex || gaps || rsurface.batchfirstvertex) + { + // when copying, we need to consider the regeneration of vertexmesh, any dependencies it may have must be set... + if (batchneed & BATCHNEED_VERTEXMESH_VERTEX) batchneed |= BATCHNEED_ARRAY_VERTEX; + if (batchneed & BATCHNEED_VERTEXMESH_NORMAL) batchneed |= BATCHNEED_ARRAY_NORMAL; + if (batchneed & BATCHNEED_VERTEXMESH_VECTOR) batchneed |= BATCHNEED_ARRAY_VECTOR; + if (batchneed & BATCHNEED_VERTEXMESH_VERTEXCOLOR) batchneed |= BATCHNEED_ARRAY_VERTEXCOLOR; + if (batchneed & BATCHNEED_VERTEXMESH_TEXCOORD) batchneed |= BATCHNEED_ARRAY_TEXCOORD; + if (batchneed & BATCHNEED_VERTEXMESH_LIGHTMAP) batchneed |= BATCHNEED_ARRAY_LIGHTMAP; + } + + // when the model data has no vertex buffer (dynamic mesh), we need to + // eliminate gaps + if (vid.useinterleavedarrays ? !rsurface.modelvertexmeshbuffer : !rsurface.modelvertex3f_vertexbuffer) + batchneed |= BATCHNEED_NOGAPS; + + // if needsupdate, we have to do a dynamic vertex batch for sure + if (needsupdate & batchneed) + dynamicvertex = true; + + // see if we need to build vertexmesh from arrays + if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP))) + dynamicvertex = true; + + // if gaps are unacceptable, and there are gaps, it's a dynamic batch... + // also some drivers strongly dislike firstvertex + if ((batchneed & BATCHNEED_NOGAPS) && (gaps || firstvertex)) + dynamicvertex = true; + + rsurface.batchvertex3f = rsurface.modelvertex3f; + rsurface.batchvertex3f_vertexbuffer = rsurface.modelvertex3f_vertexbuffer; + rsurface.batchvertex3f_bufferoffset = rsurface.modelvertex3f_bufferoffset; + rsurface.batchsvector3f = rsurface.modelsvector3f; + rsurface.batchsvector3f_vertexbuffer = rsurface.modelsvector3f_vertexbuffer; + rsurface.batchsvector3f_bufferoffset = rsurface.modelsvector3f_bufferoffset; + rsurface.batchtvector3f = rsurface.modeltvector3f; + rsurface.batchtvector3f_vertexbuffer = rsurface.modeltvector3f_vertexbuffer; + rsurface.batchtvector3f_bufferoffset = rsurface.modeltvector3f_bufferoffset; + rsurface.batchnormal3f = rsurface.modelnormal3f; + rsurface.batchnormal3f_vertexbuffer = rsurface.modelnormal3f_vertexbuffer; + rsurface.batchnormal3f_bufferoffset = rsurface.modelnormal3f_bufferoffset; + rsurface.batchlightmapcolor4f = rsurface.modellightmapcolor4f; + rsurface.batchlightmapcolor4f_vertexbuffer = rsurface.modellightmapcolor4f_vertexbuffer; + rsurface.batchlightmapcolor4f_bufferoffset = rsurface.modellightmapcolor4f_bufferoffset; + rsurface.batchtexcoordtexture2f = rsurface.modeltexcoordtexture2f; + rsurface.batchtexcoordtexture2f_vertexbuffer = rsurface.modeltexcoordtexture2f_vertexbuffer; + rsurface.batchtexcoordtexture2f_bufferoffset = rsurface.modeltexcoordtexture2f_bufferoffset; + rsurface.batchtexcoordlightmap2f = rsurface.modeltexcoordlightmap2f; + rsurface.batchtexcoordlightmap2f_vertexbuffer = rsurface.modeltexcoordlightmap2f_vertexbuffer; + rsurface.batchtexcoordlightmap2f_bufferoffset = rsurface.modeltexcoordlightmap2f_bufferoffset; + rsurface.batchvertex3fbuffer = rsurface.modelvertex3fbuffer; + rsurface.batchvertexmesh = rsurface.modelvertexmesh; + rsurface.batchvertexmeshbuffer = rsurface.modelvertexmeshbuffer; + rsurface.batchelement3i = rsurface.modelelement3i; + rsurface.batchelement3i_indexbuffer = rsurface.modelelement3i_indexbuffer; + rsurface.batchelement3i_bufferoffset = rsurface.modelelement3i_bufferoffset; + rsurface.batchelement3s = rsurface.modelelement3s; + rsurface.batchelement3s_indexbuffer = rsurface.modelelement3s_indexbuffer; + rsurface.batchelement3s_bufferoffset = rsurface.modelelement3s_bufferoffset; + + // if any dynamic vertex processing has to occur in software, we copy the + // entire surface list together before processing to rebase the vertices + // to start at 0 (otherwise we waste a lot of room in a vertex buffer). + // + // if any gaps exist and we do not have a static vertex buffer, we have to + // copy the surface list together to avoid wasting upload bandwidth on the + // vertices in the gaps. + // + // if gaps exist and we have a static vertex buffer, we still have to + // combine the index buffer ranges into one dynamic index buffer. + // + // in all cases we end up with data that can be drawn in one call. + + if (!dynamicvertex) + { + // static vertex data, just set pointers... + rsurface.batchgeneratedvertex = false; + // if there are gaps, we want to build a combined index buffer, + // otherwise use the original static buffer with an appropriate offset + if (gaps) + { + // build a new triangle elements array for this batch + rsurface.batchelement3i = (int *)R_FrameData_Alloc(batchnumtriangles * sizeof(int[3])); + rsurface.batchfirsttriangle = 0; + numtriangles = 0; + for (i = 0;i < texturenumsurfaces;i++) + { + surfacefirsttriangle = texturesurfacelist[i]->num_firsttriangle; + surfacenumtriangles = texturesurfacelist[i]->num_triangles; + memcpy(rsurface.batchelement3i + 3*numtriangles, rsurface.modelelement3i + 3*surfacefirsttriangle, surfacenumtriangles*sizeof(int[3])); + numtriangles += surfacenumtriangles; + } + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + if (endvertex <= 65536) + { + // make a 16bit (unsigned short) index array if possible + rsurface.batchelement3s = (unsigned short *)R_FrameData_Alloc(batchnumtriangles * sizeof(unsigned short[3])); + for (i = 0;i < numtriangles*3;i++) + rsurface.batchelement3s[i] = rsurface.batchelement3i[i]; + } + } + return; + } + + // something needs software processing, do it for real... + // we only directly handle separate array data in this case and then + // generate interleaved data if needed... + rsurface.batchgeneratedvertex = true; + + // now copy the vertex data into a combined array and make an index array + // (this is what Quake3 does all the time) + //if (gaps || rsurface.batchfirstvertex) + { + rsurface.batchvertex3fbuffer = NULL; + rsurface.batchvertexmesh = NULL; + rsurface.batchvertexmeshbuffer = NULL; + rsurface.batchvertex3f = NULL; + rsurface.batchvertex3f_vertexbuffer = NULL; + rsurface.batchvertex3f_bufferoffset = 0; + rsurface.batchsvector3f = NULL; + rsurface.batchsvector3f_vertexbuffer = NULL; + rsurface.batchsvector3f_bufferoffset = 0; + rsurface.batchtvector3f = NULL; + rsurface.batchtvector3f_vertexbuffer = NULL; + rsurface.batchtvector3f_bufferoffset = 0; + rsurface.batchnormal3f = NULL; + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + rsurface.batchlightmapcolor4f = NULL; + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + rsurface.batchtexcoordtexture2f = NULL; + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + rsurface.batchtexcoordlightmap2f = NULL; + rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; + rsurface.batchtexcoordlightmap2f_bufferoffset = 0; + rsurface.batchelement3i = (int *)R_FrameData_Alloc(batchnumtriangles * sizeof(int[3])); + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + // we'll only be setting up certain arrays as needed + if (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) + rsurface.batchvertexmesh = (r_vertexmesh_t *)R_FrameData_Alloc(batchnumvertices * sizeof(r_vertexmesh_t)); + if (batchneed & BATCHNEED_ARRAY_VERTEX) + rsurface.batchvertex3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); + if (batchneed & BATCHNEED_ARRAY_NORMAL) + rsurface.batchnormal3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); + if (batchneed & BATCHNEED_ARRAY_VECTOR) + { + rsurface.batchsvector3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); + rsurface.batchtvector3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); + } + if (batchneed & BATCHNEED_ARRAY_VERTEXCOLOR) + rsurface.batchlightmapcolor4f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[4])); + if (batchneed & BATCHNEED_ARRAY_TEXCOORD) + rsurface.batchtexcoordtexture2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); + if (batchneed & BATCHNEED_ARRAY_LIGHTMAP) + rsurface.batchtexcoordlightmap2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); + numvertices = 0; + numtriangles = 0; + for (i = 0;i < texturenumsurfaces;i++) + { + surfacefirstvertex = texturesurfacelist[i]->num_firstvertex; + surfacenumvertices = texturesurfacelist[i]->num_vertices; + surfacefirsttriangle = texturesurfacelist[i]->num_firsttriangle; + surfacenumtriangles = texturesurfacelist[i]->num_triangles; + // copy only the data requested + if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) && rsurface.modelvertexmesh) + memcpy(rsurface.batchvertexmesh + numvertices, rsurface.modelvertexmesh + surfacefirstvertex, surfacenumvertices * sizeof(rsurface.batchvertexmesh[0])); + if (batchneed & (BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_ARRAY_VERTEXCOLOR | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_ARRAY_LIGHTMAP)) + { + if (batchneed & BATCHNEED_ARRAY_VERTEX) + { + if (rsurface.batchvertex3f) + memcpy(rsurface.batchvertex3f + 3*numvertices, rsurface.modelvertex3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); + else + memset(rsurface.batchvertex3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); + } + if (batchneed & BATCHNEED_ARRAY_NORMAL) + { + if (rsurface.modelnormal3f) + memcpy(rsurface.batchnormal3f + 3*numvertices, rsurface.modelnormal3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); + else + memset(rsurface.batchnormal3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); + } + if (batchneed & BATCHNEED_ARRAY_VECTOR) + { + if (rsurface.modelsvector3f) + { + memcpy(rsurface.batchsvector3f + 3*numvertices, rsurface.modelsvector3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); + memcpy(rsurface.batchtvector3f + 3*numvertices, rsurface.modeltvector3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); + } + else + { + memset(rsurface.batchsvector3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); + memset(rsurface.batchtvector3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); + } + } + if (batchneed & BATCHNEED_ARRAY_VERTEXCOLOR) + { + if (rsurface.modellightmapcolor4f) + memcpy(rsurface.batchlightmapcolor4f + 4*numvertices, rsurface.modellightmapcolor4f + 4*surfacefirstvertex, surfacenumvertices * sizeof(float[4])); + else + memset(rsurface.batchlightmapcolor4f + 4*numvertices, 0, surfacenumvertices * sizeof(float[4])); + } + if (batchneed & BATCHNEED_ARRAY_TEXCOORD) + { + if (rsurface.modeltexcoordtexture2f) + memcpy(rsurface.batchtexcoordtexture2f + 2*numvertices, rsurface.modeltexcoordtexture2f + 2*surfacefirstvertex, surfacenumvertices * sizeof(float[2])); + else + memset(rsurface.batchtexcoordtexture2f + 2*numvertices, 0, surfacenumvertices * sizeof(float[2])); + } + if (batchneed & BATCHNEED_ARRAY_LIGHTMAP) + { + if (rsurface.modeltexcoordlightmap2f) + memcpy(rsurface.batchtexcoordlightmap2f + 2*numvertices, rsurface.modeltexcoordlightmap2f + 2*surfacefirstvertex, surfacenumvertices * sizeof(float[2])); + else + memset(rsurface.batchtexcoordlightmap2f + 2*numvertices, 0, surfacenumvertices * sizeof(float[2])); + } + } + RSurf_RenumberElements(rsurface.modelelement3i + 3*surfacefirsttriangle, rsurface.batchelement3i + 3*numtriangles, 3*surfacenumtriangles, numvertices - surfacefirstvertex); + numvertices += surfacenumvertices; + numtriangles += surfacenumtriangles; + } + + // generate a 16bit index array as well if possible + // (in general, dynamic batches fit) + if (numvertices <= 65536) + { + rsurface.batchelement3s = (unsigned short *)R_FrameData_Alloc(batchnumtriangles * sizeof(unsigned short[3])); + for (i = 0;i < numtriangles*3;i++) + rsurface.batchelement3s[i] = rsurface.batchelement3i[i]; + } + + // since we've copied everything, the batch now starts at 0 + rsurface.batchfirstvertex = 0; + rsurface.batchnumvertices = batchnumvertices; + rsurface.batchfirsttriangle = 0; + rsurface.batchnumtriangles = batchnumtriangles; + } + + // q1bsp surfaces rendered in vertex color mode have to have colors + // calculated based on lightstyles + if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_ARRAY_VERTEXCOLOR)) && texturesurfacelist[0]->lightmapinfo) + { + // generate color arrays for the surfaces in this list + int c[4]; + int scale; + int size3; + const int *offsets; + const unsigned char *lm; + rsurface.batchlightmapcolor4f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[4])); + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + numvertices = 0; + for (i = 0;i < texturenumsurfaces;i++) + { + surface = texturesurfacelist[i]; + offsets = rsurface.modellightmapoffsets + surface->num_firstvertex; + surfacenumvertices = surface->num_vertices; + if (surface->lightmapinfo->samples) + { + for (j = 0;j < surfacenumvertices;j++) + { + lm = surface->lightmapinfo->samples + offsets[j]; + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[0]]; + VectorScale(lm, scale, c); + if (surface->lightmapinfo->styles[1] != 255) + { + size3 = ((surface->lightmapinfo->extents[0]>>4)+1)*((surface->lightmapinfo->extents[1]>>4)+1)*3; + lm += size3; + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[1]]; + VectorMA(c, scale, lm, c); + if (surface->lightmapinfo->styles[2] != 255) + { + lm += size3; + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[2]]; + VectorMA(c, scale, lm, c); + if (surface->lightmapinfo->styles[3] != 255) + { + lm += size3; + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[3]]; + VectorMA(c, scale, lm, c); + } + } + } + c[0] >>= 7; + c[1] >>= 7; + c[2] >>= 7; + Vector4Set(rsurface.batchlightmapcolor4f + 4*numvertices, min(c[0], 255) * (1.0f / 255.0f), min(c[1], 255) * (1.0f / 255.0f), min(c[2], 255) * (1.0f / 255.0f), 1); + numvertices++; + } + } + else + { + for (j = 0;j < surfacenumvertices;j++) + { + Vector4Set(rsurface.batchlightmapcolor4f + 4*numvertices, 0, 0, 0, 1); + numvertices++; + } + } + } + } + + // if vertices are deformed (sprite flares and things in maps, possibly + // water waves, bulges and other deformations), modify the copied vertices + // in place + for (deformindex = 0, deform = rsurface.texture->deforms;deformindex < Q3MAXDEFORMS && deform->deform && r_deformvertexes.integer;deformindex++, deform++) + { + switch (deform->deform) + { + default: + case Q3DEFORM_PROJECTIONSHADOW: + case Q3DEFORM_TEXT0: + case Q3DEFORM_TEXT1: + case Q3DEFORM_TEXT2: + case Q3DEFORM_TEXT3: + case Q3DEFORM_TEXT4: + case Q3DEFORM_TEXT5: + case Q3DEFORM_TEXT6: + case Q3DEFORM_TEXT7: + case Q3DEFORM_NONE: + break; + case Q3DEFORM_AUTOSPRITE: + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, newforward); + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.right, newright); + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.up, newup); + VectorNormalize(newforward); + VectorNormalize(newright); + VectorNormalize(newup); +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; +// rsurface.batchsvector3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchsvector3f); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchtvector3f); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; +// rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); +// rsurface.batchnormal3f_vertexbuffer = NULL; +// rsurface.batchnormal3f_bufferoffset = 0; + // sometimes we're on a renderpath that does not use vectors (GL11/GL13/GLES1) + if (!VectorLength2(rsurface.batchnormal3f + 3*rsurface.batchfirstvertex)) + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + if (!VectorLength2(rsurface.batchsvector3f + 3*rsurface.batchfirstvertex)) + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + // a single autosprite surface can contain multiple sprites... + for (j = 0;j < batchnumvertices - 3;j += 4) + { + VectorClear(center); + for (i = 0;i < 4;i++) + VectorAdd(center, rsurface.batchvertex3f + 3*(j+i), center); + VectorScale(center, 0.25f, center); + VectorCopy(rsurface.batchnormal3f + 3*j, forward); + VectorCopy(rsurface.batchsvector3f + 3*j, right); + VectorCopy(rsurface.batchtvector3f + 3*j, up); + for (i = 0;i < 4;i++) + { + VectorSubtract(rsurface.batchvertex3f + 3*(j+i), center, v); + VectorMAMAMAM(1, center, DotProduct(forward, v), newforward, DotProduct(right, v), newright, DotProduct(up, v), newup, rsurface.batchvertex3f + 3*(j+i)); + } + } + // if we get here, BATCHNEED_ARRAY_NORMAL and BATCHNEED_ARRAY_VECTOR are in batchneed, so no need to check + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + break; + case Q3DEFORM_AUTOSPRITE2: + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, newforward); + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.right, newright); + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.up, newup); + VectorNormalize(newforward); + VectorNormalize(newright); + VectorNormalize(newup); +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; + { + const float *v1, *v2; + vec3_t start, end; + float f, l; + struct + { + float length2; + const float *v1; + const float *v2; + } + shortest[2]; + memset(shortest, 0, sizeof(shortest)); + // a single autosprite surface can contain multiple sprites... + for (j = 0;j < batchnumvertices - 3;j += 4) + { + VectorClear(center); + for (i = 0;i < 4;i++) + VectorAdd(center, rsurface.batchvertex3f + 3*(j+i), center); + VectorScale(center, 0.25f, center); + // find the two shortest edges, then use them to define the + // axis vectors for rotating around the central axis + for (i = 0;i < 6;i++) + { + v1 = rsurface.batchvertex3f + 3*(j+quadedges[i][0]); + v2 = rsurface.batchvertex3f + 3*(j+quadedges[i][1]); + l = VectorDistance2(v1, v2); + // this length bias tries to make sense of square polygons, assuming they are meant to be upright + if (v1[2] != v2[2]) + l += (1.0f / 1024.0f); + if (shortest[0].length2 > l || i == 0) + { + shortest[1] = shortest[0]; + shortest[0].length2 = l; + shortest[0].v1 = v1; + shortest[0].v2 = v2; + } + else if (shortest[1].length2 > l || i == 1) + { + shortest[1].length2 = l; + shortest[1].v1 = v1; + shortest[1].v2 = v2; + } + } + VectorLerp(shortest[0].v1, 0.5f, shortest[0].v2, start); + VectorLerp(shortest[1].v1, 0.5f, shortest[1].v2, end); + // this calculates the right vector from the shortest edge + // and the up vector from the edge midpoints + VectorSubtract(shortest[0].v1, shortest[0].v2, right); + VectorNormalize(right); + VectorSubtract(end, start, up); + VectorNormalize(up); + // calculate a forward vector to use instead of the original plane normal (this is how we get a new right vector) + VectorSubtract(rsurface.localvieworigin, center, forward); + //Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, forward); + VectorNegate(forward, forward); + VectorReflect(forward, 0, up, forward); + VectorNormalize(forward); + CrossProduct(up, forward, newright); + VectorNormalize(newright); + // rotate the quad around the up axis vector, this is made + // especially easy by the fact we know the quad is flat, + // so we only have to subtract the center position and + // measure distance along the right vector, and then + // multiply that by the newright vector and add back the + // center position + // we also need to subtract the old position to undo the + // displacement from the center, which we do with a + // DotProduct, the subtraction/addition of center is also + // optimized into DotProducts here + l = DotProduct(right, center); + for (i = 0;i < 4;i++) + { + v1 = rsurface.batchvertex3f + 3*(j+i); + f = DotProduct(right, v1) - l; + VectorMAMAM(1, v1, -f, right, f, newright, rsurface.batchvertex3f + 3*(j+i)); + } + } + } + if(batchneed & (BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR)) // otherwise these can stay NULL + { +// rsurface.batchnormal3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchnormal3f_vertexbuffer = NULL; +// rsurface.batchnormal3f_bufferoffset = 0; + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + } + if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL + { +// rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + } + break; + case Q3DEFORM_NORMAL: + // deform the normals to make reflections wavey + rsurface.batchnormal3f = (float *)R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + float vertex[3]; + float *normal = rsurface.batchnormal3f + 3*j; + VectorScale(rsurface.batchvertex3f + 3*j, 0.98f, vertex); + normal[0] = rsurface.batchnormal3f[j*3+0] + deform->parms[0] * noise4f( vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); + normal[1] = rsurface.batchnormal3f[j*3+1] + deform->parms[0] * noise4f( 98 + vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); + normal[2] = rsurface.batchnormal3f[j*3+2] + deform->parms[0] * noise4f(196 + vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); + VectorNormalize(normal); + } + if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL + { +// rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + } + break; + case Q3DEFORM_WAVE: + // deform vertex array to make wavey water and flags and such + waveparms[0] = deform->waveparms[0]; + waveparms[1] = deform->waveparms[1]; + waveparms[2] = deform->waveparms[2]; + waveparms[3] = deform->waveparms[3]; + if(!R_TestQ3WaveFunc(deform->wavefunc, waveparms)) + break; // if wavefunc is a nop, don't make a dynamic vertex array + // this is how a divisor of vertex influence on deformation + animpos = deform->parms[0] ? 1.0f / deform->parms[0] : 100.0f; + scale = R_EvaluateQ3WaveFunc(deform->wavefunc, waveparms); +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; +// rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); +// rsurface.batchnormal3f_vertexbuffer = NULL; +// rsurface.batchnormal3f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + // if the wavefunc depends on time, evaluate it per-vertex + if (waveparms[3]) + { + waveparms[2] = deform->waveparms[2] + (rsurface.batchvertex3f[j*3+0] + rsurface.batchvertex3f[j*3+1] + rsurface.batchvertex3f[j*3+2]) * animpos; + scale = R_EvaluateQ3WaveFunc(deform->wavefunc, waveparms); + } + VectorMA(rsurface.batchvertex3f + 3*j, scale, rsurface.batchnormal3f + 3*j, rsurface.batchvertex3f + 3*j); + } + // if we get here, BATCHNEED_ARRAY_NORMAL is in batchneed, so no need to check + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL + { +// rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + } + break; + case Q3DEFORM_BULGE: + // deform vertex array to make the surface have moving bulges +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; +// rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); +// rsurface.batchnormal3f_vertexbuffer = NULL; +// rsurface.batchnormal3f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + scale = sin(rsurface.batchtexcoordtexture2f[j*2+0] * deform->parms[0] + rsurface.shadertime * deform->parms[2]) * deform->parms[1]; + VectorMA(rsurface.batchvertex3f + 3*j, scale, rsurface.batchnormal3f + 3*j, rsurface.batchvertex3f + 3*j); + } + // if we get here, BATCHNEED_ARRAY_NORMAL is in batchneed, so no need to check + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL + { +// rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + } + break; + case Q3DEFORM_MOVE: + // deform vertex array + if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) + break; // if wavefunc is a nop, don't make a dynamic vertex array + scale = R_EvaluateQ3WaveFunc(deform->wavefunc, deform->waveparms); + VectorScale(deform->parms, scale, waveparms); +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + VectorAdd(rsurface.batchvertex3f + 3*j, waveparms, rsurface.batchvertex3f + 3*j); + break; + } + } + + // generate texcoords based on the chosen texcoord source + switch(rsurface.texture->tcgen.tcgen) + { + default: + case Q3TCGEN_TEXTURE: + break; + case Q3TCGEN_LIGHTMAP: +// rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); +// rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; +// rsurface.batchtexcoordtexture2f_bufferoffset = 0; + if (rsurface.batchtexcoordlightmap2f) + memcpy(rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordtexture2f, batchnumvertices * sizeof(float[2])); + break; + case Q3TCGEN_VECTOR: +// rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); +// rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; +// rsurface.batchtexcoordtexture2f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + rsurface.batchtexcoordtexture2f[j*2+0] = DotProduct(rsurface.batchvertex3f + 3*j, rsurface.texture->tcgen.parms); + rsurface.batchtexcoordtexture2f[j*2+1] = DotProduct(rsurface.batchvertex3f + 3*j, rsurface.texture->tcgen.parms + 3); + } + break; + case Q3TCGEN_ENVIRONMENT: + // make environment reflections using a spheremap + rsurface.batchtexcoordtexture2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + // identical to Q3A's method, but executed in worldspace so + // carried models can be shiny too + + float viewer[3], d, reflected[3], worldreflected[3]; + + VectorSubtract(rsurface.localvieworigin, rsurface.batchvertex3f + 3*j, viewer); + // VectorNormalize(viewer); + + d = DotProduct(rsurface.batchnormal3f + 3*j, viewer); + + reflected[0] = rsurface.batchnormal3f[j*3+0]*2*d - viewer[0]; + reflected[1] = rsurface.batchnormal3f[j*3+1]*2*d - viewer[1]; + reflected[2] = rsurface.batchnormal3f[j*3+2]*2*d - viewer[2]; + // note: this is proportinal to viewer, so we can normalize later + + Matrix4x4_Transform3x3(&rsurface.matrix, reflected, worldreflected); + VectorNormalize(worldreflected); + + // note: this sphere map only uses world x and z! + // so positive and negative y will LOOK THE SAME. + rsurface.batchtexcoordtexture2f[j*2+0] = 0.5 + 0.5 * worldreflected[1]; + rsurface.batchtexcoordtexture2f[j*2+1] = 0.5 - 0.5 * worldreflected[2]; + } + break; + } + // the only tcmod that needs software vertex processing is turbulent, so + // check for it here and apply the changes if needed + // and we only support that as the first one + // (handling a mixture of turbulent and other tcmods would be problematic + // without punting it entirely to a software path) + if (rsurface.texture->tcmods[0].tcmod == Q3TCMOD_TURBULENT) + { + amplitude = rsurface.texture->tcmods[0].parms[1]; + animpos = rsurface.texture->tcmods[0].parms[2] + rsurface.shadertime * rsurface.texture->tcmods[0].parms[3]; +// rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); +// rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; +// rsurface.batchtexcoordtexture2f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + rsurface.batchtexcoordtexture2f[j*2+0] += amplitude * sin(((rsurface.batchvertex3f[j*3+0] + rsurface.batchvertex3f[j*3+2]) * 1.0 / 1024.0f + animpos) * M_PI * 2); + rsurface.batchtexcoordtexture2f[j*2+1] += amplitude * sin(((rsurface.batchvertex3f[j*3+1] ) * 1.0 / 1024.0f + animpos) * M_PI * 2); + } + } + + if (needsupdate & batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) + { + // convert the modified arrays to vertex structs +// rsurface.batchvertexmesh = R_FrameData_Alloc(batchnumvertices * sizeof(r_vertexmesh_t)); +// rsurface.batchvertexmeshbuffer = NULL; + if (batchneed & BATCHNEED_VERTEXMESH_VERTEX) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + VectorCopy(rsurface.batchvertex3f + 3*j, vertexmesh->vertex3f); + if (batchneed & BATCHNEED_VERTEXMESH_NORMAL) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + VectorCopy(rsurface.batchnormal3f + 3*j, vertexmesh->normal3f); + if (batchneed & BATCHNEED_VERTEXMESH_VECTOR) + { + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + { + VectorCopy(rsurface.batchsvector3f + 3*j, vertexmesh->svector3f); + VectorCopy(rsurface.batchtvector3f + 3*j, vertexmesh->tvector3f); + } + } + if ((batchneed & BATCHNEED_VERTEXMESH_VERTEXCOLOR) && rsurface.batchlightmapcolor4f) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + Vector4Copy(rsurface.batchlightmapcolor4f + 4*j, vertexmesh->color4f); + if (batchneed & BATCHNEED_VERTEXMESH_TEXCOORD) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + Vector2Copy(rsurface.batchtexcoordtexture2f + 2*j, vertexmesh->texcoordtexture2f); + if ((batchneed & BATCHNEED_VERTEXMESH_LIGHTMAP) && rsurface.batchtexcoordlightmap2f) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + Vector2Copy(rsurface.batchtexcoordlightmap2f + 2*j, vertexmesh->texcoordlightmap2f); + } +} + +void RSurf_DrawBatch(void) +{ + // sometimes a zero triangle surface (usually a degenerate patch) makes it + // through the pipeline, killing it earlier in the pipeline would have + // per-surface overhead rather than per-batch overhead, so it's best to + // reject it here, before it hits glDraw. + if (rsurface.batchnumtriangles == 0) + return; +#if 0 + // batch debugging code + if (r_test.integer && rsurface.entity == r_refdef.scene.worldentity && rsurface.batchvertex3f == r_refdef.scene.worldentity->model->surfmesh.data_vertex3f) + { + int i; + int j; + int c; + const int *e; + e = rsurface.batchelement3i + rsurface.batchfirsttriangle*3; + for (i = 0;i < rsurface.batchnumtriangles*3;i++) + { + c = e[i]; + for (j = 0;j < rsurface.entity->model->num_surfaces;j++) + { + if (c >= rsurface.modelsurfaces[j].num_firstvertex && c < (rsurface.modelsurfaces[j].num_firstvertex + rsurface.modelsurfaces[j].num_vertices)) + { + if (rsurface.modelsurfaces[j].texture != rsurface.texture) + Sys_Error("RSurf_DrawBatch: index %i uses different texture (%s) than surface %i which it belongs to (which uses %s)\n", c, rsurface.texture->name, j, rsurface.modelsurfaces[j].texture->name); + break; + } + } + } + } +#endif + R_Mesh_Draw(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchfirsttriangle, rsurface.batchnumtriangles, rsurface.batchelement3i, rsurface.batchelement3i_indexbuffer, rsurface.batchelement3i_bufferoffset, rsurface.batchelement3s, rsurface.batchelement3s_indexbuffer, rsurface.batchelement3s_bufferoffset); +} + +static int RSurf_FindWaterPlaneForSurface(const msurface_t *surface) +{ + // pick the closest matching water plane + int planeindex, vertexindex, bestplaneindex = -1; + float d, bestd; + vec3_t vert; + const float *v; + r_waterstate_waterplane_t *p; + qboolean prepared = false; + bestd = 0; + for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++) + { + if(p->camera_entity != rsurface.texture->camera_entity) + continue; + d = 0; + if(!prepared) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, 1, &surface); + prepared = true; + if(rsurface.batchnumvertices == 0) + break; + } + for (vertexindex = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3;vertexindex < rsurface.batchnumvertices;vertexindex++, v += 3) + { + Matrix4x4_Transform(&rsurface.matrix, v, vert); + d += fabs(PlaneDiff(vert, &p->plane)); + } + if (bestd > d || bestplaneindex < 0) + { + bestd = d; + bestplaneindex = planeindex; + } + } + return bestplaneindex; + // NOTE: this MAY return a totally unrelated water plane; we can ignore + // this situation though, as it might be better to render single larger + // batches with useless stuff (backface culled for example) than to + // render multiple smaller batches +} + +static void RSurf_DrawBatch_GL11_MakeFullbrightLightmapColorArray(void) +{ + int i; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0;i < rsurface.batchnumvertices;i++) + Vector4Set(rsurface.passcolor4f + 4*i, 0.5f, 0.5f, 0.5f, 1.0f); +} + +static void RSurf_DrawBatch_GL11_ApplyFog(void) +{ + int i; + float f; + const float *v; + const float *c; + float *c2; + if (rsurface.passcolor4f) + { + // generate color arrays + c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4, c2 += 4) + { + f = RSurf_FogVertex(v); + c2[0] = c[0] * f; + c2[1] = c[1] * f; + c2[2] = c[2] * f; + c2[3] = c[3]; + } + } + else + { + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c2 += 4) + { + f = RSurf_FogVertex(v); + c2[0] = f; + c2[1] = f; + c2[2] = f; + c2[3] = 1; + } + } +} + +static void RSurf_DrawBatch_GL11_ApplyFogToFinishedVertexColors(void) +{ + int i; + float f; + const float *v; + const float *c; + float *c2; + if (!rsurface.passcolor4f) + return; + c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4, c2 += 4) + { + f = RSurf_FogVertex(v); + c2[0] = c[0] * f + r_refdef.fogcolor[0] * (1 - f); + c2[1] = c[1] * f + r_refdef.fogcolor[1] * (1 - f); + c2[2] = c[2] * f + r_refdef.fogcolor[2] * (1 - f); + c2[3] = c[3]; + } +} + +static void RSurf_DrawBatch_GL11_ApplyColor(float r, float g, float b, float a) +{ + int i; + const float *c; + float *c2; + if (!rsurface.passcolor4f) + return; + c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, c += 4, c2 += 4) + { + c2[0] = c[0] * r; + c2[1] = c[1] * g; + c2[2] = c[2] * b; + c2[3] = c[3] * a; + } +} + +static void RSurf_DrawBatch_GL11_ApplyAmbient(void) +{ + int i; + const float *c; + float *c2; + if (!rsurface.passcolor4f) + return; + c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, c += 4, c2 += 4) + { + c2[0] = c[0] + r_refdef.scene.ambient; + c2[1] = c[1] + r_refdef.scene.ambient; + c2[2] = c[2] + r_refdef.scene.ambient; + c2[3] = c[3]; + } +} + +static void RSurf_DrawBatch_GL11_Lightmap(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + // TODO: optimize + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + R_Mesh_TexBind(0, rsurface.lightmaptexture); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_Unlit(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + // TODO: optimize applyfog && applycolor case + // just apply fog if necessary, and tint the fog color array if necessary + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_VertexColor(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + // TODO: optimize + rsurface.passcolor4f = rsurface.batchlightmapcolor4f; + rsurface.passcolor4f_vertexbuffer = rsurface.batchlightmapcolor4f_vertexbuffer; + rsurface.passcolor4f_bufferoffset = rsurface.batchlightmapcolor4f_bufferoffset; + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_ClampColor(void) +{ + int i; + const float *c1; + float *c2; + if (!rsurface.passcolor4f) + return; + for (i = 0, c1 = rsurface.passcolor4f + 4*rsurface.batchfirstvertex, c2 = rsurface.passcolor4f + 4*rsurface.batchfirstvertex;i < rsurface.batchnumvertices;i++, c1 += 4, c2 += 4) + { + c2[0] = bound(0.0f, c1[0], 1.0f); + c2[1] = bound(0.0f, c1[1], 1.0f); + c2[2] = bound(0.0f, c1[2], 1.0f); + c2[3] = bound(0.0f, c1[3], 1.0f); + } +} + +static void RSurf_DrawBatch_GL11_ApplyFakeLight(void) +{ + int i; + float f; + const float *v; + const float *n; + float *c; + //vec3_t eyedir; + + // fake shading + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, n = rsurface.batchnormal3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, n += 3, c += 4) + { + f = -DotProduct(r_refdef.view.forward, n); + f = max(0, f); + f = f * 0.85 + 0.15; // work around so stuff won't get black + f *= r_refdef.lightmapintensity; + Vector4Set(c, f, f, f, 1); + } +} + +static void RSurf_DrawBatch_GL11_FakeLight(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + RSurf_DrawBatch_GL11_ApplyFakeLight(); + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_ApplyVertexShade(float *r, float *g, float *b, float *a, qboolean *applycolor) +{ + int i; + float f; + float alpha; + const float *v; + const float *n; + float *c; + vec3_t ambientcolor; + vec3_t diffusecolor; + vec3_t lightdir; + // TODO: optimize + // model lighting + VectorCopy(rsurface.modellight_lightdir, lightdir); + f = 0.5f * r_refdef.lightmapintensity; + ambientcolor[0] = rsurface.modellight_ambient[0] * *r * f; + ambientcolor[1] = rsurface.modellight_ambient[1] * *g * f; + ambientcolor[2] = rsurface.modellight_ambient[2] * *b * f; + diffusecolor[0] = rsurface.modellight_diffuse[0] * *r * f; + diffusecolor[1] = rsurface.modellight_diffuse[1] * *g * f; + diffusecolor[2] = rsurface.modellight_diffuse[2] * *b * f; + alpha = *a; + if (VectorLength2(diffusecolor) > 0) + { + // q3-style directional shading + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, n = rsurface.batchnormal3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, n += 3, c += 4) + { + if ((f = DotProduct(n, lightdir)) > 0) + VectorMA(ambientcolor, f, diffusecolor, c); + else + VectorCopy(ambientcolor, c); + c[3] = alpha; + } + *r = 1; + *g = 1; + *b = 1; + *a = 1; + *applycolor = false; + } + else + { + *r = ambientcolor[0]; + *g = ambientcolor[1]; + *b = ambientcolor[2]; + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + } +} + +static void RSurf_DrawBatch_GL11_VertexShade(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + RSurf_DrawBatch_GL11_ApplyVertexShade(&r, &g, &b, &a, &applycolor); + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_MakeFogColor(float r, float g, float b, float a) +{ + int i; + float f; + const float *v; + float *c; + + // fake shading + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4) + { + f = 1 - RSurf_FogVertex(v); + c[0] = r; + c[1] = g; + c[2] = b; + c[3] = f * a; + } +} + +void RSurf_SetupDepthAndCulling(void) +{ + // submodels are biased to avoid z-fighting with world surfaces that they + // may be exactly overlapping (avoids z-fighting artifacts on certain + // doors and things in Quake maps) + GL_DepthRange(0, (rsurface.texture->currentmaterialflags & MATERIALFLAG_SHORTDEPTHRANGE) ? 0.0625 : 1); + GL_PolygonOffset(rsurface.basepolygonfactor + rsurface.texture->biaspolygonfactor, rsurface.basepolygonoffset + rsurface.texture->biaspolygonoffset); + GL_DepthTest(!(rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST)); + GL_CullFace((rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE) ? GL_NONE : r_refdef.view.cullface_back); +} + +static void R_DrawTextureSurfaceList_Sky(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + // transparent sky would be ridiculous + if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) + return; + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + skyrenderlater = true; + RSurf_SetupDepthAndCulling(); + GL_DepthMask(true); + // LordHavoc: HalfLife maps have freaky skypolys so don't use + // skymasking on them, and Quake3 never did sky masking (unlike + // software Quake and software Quake2), so disable the sky masking + // in Quake3 maps as it causes problems with q3map2 sky tricks, + // and skymasking also looks very bad when noclipping outside the + // level, so don't use it then either. + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->type == mod_brushq1 && r_q1bsp_skymasking.integer && !r_refdef.viewcache.world_novis && !r_trippy.integer) + { + R_Mesh_ResetTextureState(); + if (skyrendermasked) + { + R_SetupShader_DepthOrShadow(false); + // depth-only (masking) + GL_ColorMask(0,0,0,0); + // just to make sure that braindead drivers don't draw + // anything despite that colormask... + GL_BlendFunc(GL_ZERO, GL_ONE); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + if (rsurface.batchvertex3fbuffer) + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer); + else + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer); + } + else + { + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + // fog sky + GL_BlendFunc(GL_ONE, GL_ZERO); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + GL_Color(r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); + } + RSurf_DrawBatch(); + if (skyrendermasked) + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + } + R_Mesh_ResetTextureState(); + GL_Color(1, 1, 1, 1); +} + +extern rtexture_t *r_shadow_prepasslightingdiffusetexture; +extern rtexture_t *r_shadow_prepasslightingspeculartexture; +static void R_DrawTextureSurfaceList_GL20(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) +{ + if (r_waterstate.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA))) + return; + if (prepass) + { + // render screenspace normalmap to texture + GL_DepthMask(true); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_DEFERREDGEOMETRY, texturenumsurfaces, texturesurfacelist, NULL, false); + RSurf_DrawBatch(); + } + + // bind lightmap texture + + // water/refraction/reflection/camera surfaces have to be handled specially + if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_CAMERA | MATERIALFLAG_REFLECTION))) + { + int start, end, startplaneindex; + for (start = 0;start < texturenumsurfaces;start = end) + { + startplaneindex = RSurf_FindWaterPlaneForSurface(texturesurfacelist[start]); + if(startplaneindex < 0) + { + // this happens if the plane e.g. got backface culled and thus didn't get a water plane. We can just ignore this. + // Con_Printf("No matching water plane for surface with material flags 0x%08x - PLEASE DEBUG THIS\n", rsurface.texture->currentmaterialflags); + end = start + 1; + continue; + } + for (end = start + 1;end < texturenumsurfaces && startplaneindex == RSurf_FindWaterPlaneForSurface(texturesurfacelist[end]);end++) + ; + // now that we have a batch using the same planeindex, render it + if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_CAMERA))) + { + // render water or distortion background + GL_DepthMask(true); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BACKGROUND, end-start, texturesurfacelist + start, (void *)(r_waterstate.waterplanes + startplaneindex), false); + RSurf_DrawBatch(); + // blend surface on top + GL_DepthMask(false); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, end-start, texturesurfacelist + start, NULL, false); + RSurf_DrawBatch(); + } + else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION)) + { + // render surface with reflection texture as input + GL_DepthMask(writedepth && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, end-start, texturesurfacelist + start, (void *)(r_waterstate.waterplanes + startplaneindex), false); + RSurf_DrawBatch(); + } + } + return; + } + + // render surface batch normally + GL_DepthMask(writedepth && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, texturenumsurfaces, texturesurfacelist, NULL, (rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) != 0); + RSurf_DrawBatch(); +} + +static void R_DrawTextureSurfaceList_GL13(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) +{ + // OpenGL 1.3 path - anything not completely ancient + qboolean applycolor; + qboolean applyfog; + int layerindex; + const texturelayer_t *layer; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | ((!rsurface.uselightmaptexture && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.modeltexcoordlightmap2f ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + + for (layerindex = 0, layer = rsurface.texture->currentlayers;layerindex < rsurface.texture->currentnumlayers;layerindex++, layer++) + { + vec4_t layercolor; + int layertexrgbscale; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + if (layerindex == 0) + GL_AlphaTest(true); + else + { + GL_AlphaTest(false); + GL_DepthFunc(GL_EQUAL); + } + } + GL_DepthMask(layer->depthmask && writedepth); + GL_BlendFunc(layer->blendfunc1, layer->blendfunc2); + if (layer->color[0] > 2 || layer->color[1] > 2 || layer->color[2] > 2) + { + layertexrgbscale = 4; + VectorScale(layer->color, 0.25f, layercolor); + } + else if (layer->color[0] > 1 || layer->color[1] > 1 || layer->color[2] > 1) + { + layertexrgbscale = 2; + VectorScale(layer->color, 0.5f, layercolor); + } + else + { + layertexrgbscale = 1; + VectorScale(layer->color, 1.0f, layercolor); + } + layercolor[3] = layer->color[3]; + applycolor = layercolor[0] != 1 || layercolor[1] != 1 || layercolor[2] != 1 || layercolor[3] != 1; + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, 0, 0); + applyfog = r_refdef.fogenabled && (rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED); + switch (layer->type) + { + case TEXTURELAYERTYPE_LITTEXTURE: + // single-pass lightmapped texture with 2x rgbscale + R_Mesh_TexBind(0, r_texture_white); + R_Mesh_TexMatrix(0, NULL); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); + R_Mesh_TexBind(1, layer->texture); + R_Mesh_TexMatrix(1, &layer->texmatrix); + R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + RSurf_DrawBatch_GL11_VertexShade(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + else if (FAKELIGHT_ENABLED) + RSurf_DrawBatch_GL11_FakeLight(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + else if (rsurface.uselightmaptexture) + RSurf_DrawBatch_GL11_Lightmap(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + else + RSurf_DrawBatch_GL11_VertexColor(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + break; + case TEXTURELAYERTYPE_TEXTURE: + // singletexture unlit texture with transparency support + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + R_Mesh_TexBind(1, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); + RSurf_DrawBatch_GL11_Unlit(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + break; + case TEXTURELAYERTYPE_FOG: + // singletexture fogging + if (layer->texture) + { + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + } + else + { + R_Mesh_TexBind(0, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); + } + R_Mesh_TexBind(1, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); + // generate a color array for the fog pass + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); + RSurf_DrawBatch_GL11_MakeFogColor(layercolor[0], layercolor[1], layercolor[2], layercolor[3]); + RSurf_DrawBatch(); + break; + default: + Con_Printf("R_DrawTextureSurfaceList: unknown layer type %i\n", layer->type); + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + GL_DepthFunc(GL_LEQUAL); + GL_AlphaTest(false); + } +} + +static void R_DrawTextureSurfaceList_GL11(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) +{ + // OpenGL 1.1 - crusty old voodoo path + qboolean applyfog; + int layerindex; + const texturelayer_t *layer; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | ((!rsurface.uselightmaptexture && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.modeltexcoordlightmap2f ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + + for (layerindex = 0, layer = rsurface.texture->currentlayers;layerindex < rsurface.texture->currentnumlayers;layerindex++, layer++) + { + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + if (layerindex == 0) + GL_AlphaTest(true); + else + { + GL_AlphaTest(false); + GL_DepthFunc(GL_EQUAL); + } + } + GL_DepthMask(layer->depthmask && writedepth); + GL_BlendFunc(layer->blendfunc1, layer->blendfunc2); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, 0, 0); + applyfog = r_refdef.fogenabled && (rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED); + switch (layer->type) + { + case TEXTURELAYERTYPE_LITTEXTURE: + if (layer->blendfunc1 == GL_ONE && layer->blendfunc2 == GL_ZERO) + { + // two-pass lit texture with 2x rgbscale + // first the lightmap pass + R_Mesh_TexBind(0, r_texture_white); + R_Mesh_TexMatrix(0, NULL); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + RSurf_DrawBatch_GL11_VertexShade(1, 1, 1, 1, false, false); + else if (FAKELIGHT_ENABLED) + RSurf_DrawBatch_GL11_FakeLight(1, 1, 1, 1, false, false); + else if (rsurface.uselightmaptexture) + RSurf_DrawBatch_GL11_Lightmap(1, 1, 1, 1, false, false); + else + RSurf_DrawBatch_GL11_VertexColor(1, 1, 1, 1, false, false); + // then apply the texture to it + GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + RSurf_DrawBatch_GL11_Unlit(layer->color[0] * 0.5f, layer->color[1] * 0.5f, layer->color[2] * 0.5f, layer->color[3], layer->color[0] != 2 || layer->color[1] != 2 || layer->color[2] != 2 || layer->color[3] != 1, false); + } + else + { + // single pass vertex-lighting-only texture with 1x rgbscale and transparency support + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + RSurf_DrawBatch_GL11_VertexShade(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); + else + RSurf_DrawBatch_GL11_VertexColor(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); + } + break; + case TEXTURELAYERTYPE_TEXTURE: + // singletexture unlit texture with transparency support + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + RSurf_DrawBatch_GL11_Unlit(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); + break; + case TEXTURELAYERTYPE_FOG: + // singletexture fogging + if (layer->texture) + { + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + } + else + { + R_Mesh_TexBind(0, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); + } + // generate a color array for the fog pass + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); + RSurf_DrawBatch_GL11_MakeFogColor(layer->color[0], layer->color[1], layer->color[2], layer->color[3]); + RSurf_DrawBatch(); + break; + default: + Con_Printf("R_DrawTextureSurfaceList: unknown layer type %i\n", layer->type); + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + GL_DepthFunc(GL_LEQUAL); + GL_AlphaTest(false); + } +} + +static void R_DrawTextureSurfaceList_ShowSurfaces(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) +{ + int vi; + int j; + r_vertexgeneric_t *batchvertex; + float c[4]; + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + + if(rsurface.texture && rsurface.texture->currentskinframe) + { + memcpy(c, rsurface.texture->currentskinframe->avgcolor, sizeof(c)); + c[3] *= rsurface.texture->currentalpha; + } + else + { + c[0] = 1; + c[1] = 0; + c[2] = 1; + c[3] = 1; + } + + if (rsurface.texture->pantstexture || rsurface.texture->shirttexture) + { + c[0] = 0.5 * (rsurface.colormap_pantscolor[0] * 0.3 + rsurface.colormap_shirtcolor[0] * 0.7); + c[1] = 0.5 * (rsurface.colormap_pantscolor[1] * 0.3 + rsurface.colormap_shirtcolor[1] * 0.7); + c[2] = 0.5 * (rsurface.colormap_pantscolor[2] * 0.3 + rsurface.colormap_shirtcolor[2] * 0.7); + } + + // brighten it up (as texture value 127 means "unlit") + c[0] *= 2 * r_refdef.view.colorscale; + c[1] *= 2 * r_refdef.view.colorscale; + c[2] *= 2 * r_refdef.view.colorscale; + + if(rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERALPHA) + c[3] *= r_wateralpha.value; + + if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHA && c[3] != 1) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + } + else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ADD) + { + GL_BlendFunc(GL_ONE, GL_ONE); + GL_DepthMask(false); + } + else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // can't do alpha test without texture, so let's blend instead + GL_DepthMask(false); + } + else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) + { + GL_BlendFunc(rsurface.texture->customblendfunc[0], rsurface.texture->customblendfunc[1]); + GL_DepthMask(false); + } + else + { + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(writedepth); + } + + if (r_showsurfaces.integer == 3) + { + rsurface.passcolor4f = NULL; + + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + { + qboolean applycolor = true; + float one = 1.0; + + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + + r_refdef.lightmapintensity = 1; + RSurf_DrawBatch_GL11_ApplyVertexShade(&one, &one, &one, &one, &applycolor); + r_refdef.lightmapintensity = 0; // we're in showsurfaces, after all + } + else if (FAKELIGHT_ENABLED) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + + r_refdef.lightmapintensity = r_fakelight_intensity.value; + RSurf_DrawBatch_GL11_ApplyFakeLight(); + r_refdef.lightmapintensity = 0; // we're in showsurfaces, after all + } + else + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_VERTEXCOLOR | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + + rsurface.passcolor4f = rsurface.batchlightmapcolor4f; + rsurface.passcolor4f_vertexbuffer = rsurface.batchlightmapcolor4f_vertexbuffer; + rsurface.passcolor4f_bufferoffset = rsurface.batchlightmapcolor4f_bufferoffset; + } + + if(!rsurface.passcolor4f) + RSurf_DrawBatch_GL11_MakeFullbrightLightmapColorArray(); + + RSurf_DrawBatch_GL11_ApplyAmbient(); + RSurf_DrawBatch_GL11_ApplyColor(c[0], c[1], c[2], c[3]); + if(r_refdef.fogenabled) + RSurf_DrawBatch_GL11_ApplyFogToFinishedVertexColors(); + RSurf_DrawBatch_GL11_ClampColor(); + + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.passcolor4f, NULL); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + RSurf_DrawBatch(); + } + else if (!r_refdef.view.showdebug) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchnumvertices); + for (j = 0, vi = rsurface.batchfirstvertex;j < rsurface.batchnumvertices;j++, vi++) + { + VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); + Vector4Set(batchvertex[vi].color4f, 0, 0, 0, 1); + } + R_Mesh_PrepareVertices_Generic_Unlock(); + RSurf_DrawBatch(); + } + else if (r_showsurfaces.integer == 4) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchnumvertices); + for (j = 0, vi = rsurface.batchfirstvertex;j < rsurface.batchnumvertices;j++, vi++) + { + unsigned char c = (vi << 3) * (1.0f / 256.0f); + VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); + Vector4Set(batchvertex[vi].color4f, c, c, c, 1); + } + R_Mesh_PrepareVertices_Generic_Unlock(); + RSurf_DrawBatch(); + } + else if (r_showsurfaces.integer == 2) + { + const int *e; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + batchvertex = R_Mesh_PrepareVertices_Generic_Lock(3*rsurface.batchnumtriangles); + for (j = 0, e = rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle;j < rsurface.batchnumtriangles;j++, e += 3) + { + unsigned char c = ((j + rsurface.batchfirsttriangle) << 3) * (1.0f / 256.0f); + VectorCopy(rsurface.batchvertex3f + 3*e[0], batchvertex[j*3+0].vertex3f); + VectorCopy(rsurface.batchvertex3f + 3*e[1], batchvertex[j*3+1].vertex3f); + VectorCopy(rsurface.batchvertex3f + 3*e[2], batchvertex[j*3+2].vertex3f); + Vector4Set(batchvertex[j*3+0].color4f, c, c, c, 1); + Vector4Set(batchvertex[j*3+1].color4f, c, c, c, 1); + Vector4Set(batchvertex[j*3+2].color4f, c, c, c, 1); + } + R_Mesh_PrepareVertices_Generic_Unlock(); + R_Mesh_Draw(0, rsurface.batchnumtriangles*3, 0, rsurface.batchnumtriangles, NULL, NULL, 0, NULL, NULL, 0); + } + else + { + int texturesurfaceindex; + int k; + const msurface_t *surface; + float surfacecolor4f[4]; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchfirstvertex + rsurface.batchnumvertices); + vi = 0; + for (texturesurfaceindex = 0;texturesurfaceindex < texturenumsurfaces;texturesurfaceindex++) + { + surface = texturesurfacelist[texturesurfaceindex]; + k = (int)(((size_t)surface) / sizeof(msurface_t)); + Vector4Set(surfacecolor4f, (k & 0xF) * (1.0f / 16.0f), (k & 0xF0) * (1.0f / 256.0f), (k & 0xF00) * (1.0f / 4096.0f), 1); + for (j = 0;j < surface->num_vertices;j++) + { + VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); + Vector4Copy(surfacecolor4f, batchvertex[vi].color4f); + vi++; + } + } + R_Mesh_PrepareVertices_Generic_Unlock(); + RSurf_DrawBatch(); + } +} + +static void R_DrawWorldTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) +{ + CHECKGLERROR + RSurf_SetupDepthAndCulling(); + if (r_showsurfaces.integer) + { + R_DrawTextureSurfaceList_ShowSurfaces(texturenumsurfaces, texturesurfacelist, writedepth); + return; + } + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + R_DrawTextureSurfaceList_GL20(texturenumsurfaces, texturesurfacelist, writedepth, prepass); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + R_DrawTextureSurfaceList_GL13(texturenumsurfaces, texturesurfacelist, writedepth); + break; + case RENDERPATH_GL11: + R_DrawTextureSurfaceList_GL11(texturenumsurfaces, texturesurfacelist, writedepth); + break; + } + CHECKGLERROR +} + +static void R_DrawModelTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) +{ + CHECKGLERROR + RSurf_SetupDepthAndCulling(); + if (r_showsurfaces.integer) + { + R_DrawTextureSurfaceList_ShowSurfaces(texturenumsurfaces, texturesurfacelist, writedepth); + return; + } + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + R_DrawTextureSurfaceList_GL20(texturenumsurfaces, texturesurfacelist, writedepth, prepass); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + R_DrawTextureSurfaceList_GL13(texturenumsurfaces, texturesurfacelist, writedepth); + break; + case RENDERPATH_GL11: + R_DrawTextureSurfaceList_GL11(texturenumsurfaces, texturesurfacelist, writedepth); + break; + } + CHECKGLERROR +} + +static void R_DrawSurface_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i, j; + int texturenumsurfaces, endsurface; + texture_t *texture; + const msurface_t *surface; + const msurface_t *texturesurfacelist[MESHQUEUE_TRANSPARENT_BATCHSIZE]; + + // if the model is static it doesn't matter what value we give for + // wantnormals and wanttangents, so this logic uses only rules applicable + // to a model, knowing that they are meaningless otherwise + if (ent == r_refdef.scene.worldentity) + RSurf_ActiveWorldEntity(); + else if (r_showsurfaces.integer && r_showsurfaces.integer != 3) + RSurf_ActiveModelEntity(ent, false, false, false); + else + { + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + RSurf_ActiveModelEntity(ent, true, true, false); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + RSurf_ActiveModelEntity(ent, true, false, false); + break; + } + } + + if (r_transparentdepthmasking.integer) + { + qboolean setup = false; + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + surface = rsurface.modelsurfaces + surfacelist[i]; + texture = surface->texture; + rsurface.texture = R_GetCurrentTexture(texture); + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + // scan ahead until we find a different texture + endsurface = min(i + 1024, numsurfaces); + texturenumsurfaces = 0; + texturesurfacelist[texturenumsurfaces++] = surface; + for (;j < endsurface;j++) + { + surface = rsurface.modelsurfaces + surfacelist[j]; + if (texture != surface->texture) + break; + texturesurfacelist[texturenumsurfaces++] = surface; + } + if (!(rsurface.texture->currentmaterialflags & MATERIALFLAG_TRANSDEPTH)) + continue; + // render the range of surfaces as depth + if (!setup) + { + setup = true; + GL_ColorMask(0,0,0,0); + GL_Color(1,1,1,1); + GL_DepthTest(true); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); +// R_Mesh_ResetTextureState(); + R_SetupShader_DepthOrShadow(false); + } + RSurf_SetupDepthAndCulling(); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX, texturenumsurfaces, texturesurfacelist); + if (rsurface.batchvertex3fbuffer) + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer); + else + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer); + RSurf_DrawBatch(); + } + if (setup) + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + } + + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + surface = rsurface.modelsurfaces + surfacelist[i]; + texture = surface->texture; + rsurface.texture = R_GetCurrentTexture(texture); + // scan ahead until we find a different texture + endsurface = min(i + MESHQUEUE_TRANSPARENT_BATCHSIZE, numsurfaces); + texturenumsurfaces = 0; + texturesurfacelist[texturenumsurfaces++] = surface; + if(FAKELIGHT_ENABLED) + { + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + for (;j < endsurface;j++) + { + surface = rsurface.modelsurfaces + surfacelist[j]; + if (texture != surface->texture) + break; + texturesurfacelist[texturenumsurfaces++] = surface; + } + } + else + { + rsurface.lightmaptexture = surface->lightmaptexture; + rsurface.deluxemaptexture = surface->deluxemaptexture; + rsurface.uselightmaptexture = surface->lightmaptexture != NULL; + for (;j < endsurface;j++) + { + surface = rsurface.modelsurfaces + surfacelist[j]; + if (texture != surface->texture || rsurface.lightmaptexture != surface->lightmaptexture) + break; + texturesurfacelist[texturenumsurfaces++] = surface; + } + } + // render the range of surfaces + if (ent == r_refdef.scene.worldentity) + R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, false, false); + else + R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, false, false); + } + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +static void R_ProcessTransparentTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, const entity_render_t *queueentity) +{ + // transparent surfaces get pushed off into the transparent queue + int surfacelistindex; + const msurface_t *surface; + vec3_t tempcenter, center; + for (surfacelistindex = 0;surfacelistindex < texturenumsurfaces;surfacelistindex++) + { + surface = texturesurfacelist[surfacelistindex]; + tempcenter[0] = (surface->mins[0] + surface->maxs[0]) * 0.5f; + tempcenter[1] = (surface->mins[1] + surface->maxs[1]) * 0.5f; + tempcenter[2] = (surface->mins[2] + surface->maxs[2]) * 0.5f; + Matrix4x4_Transform(&rsurface.matrix, tempcenter, center); + if (queueentity->transparent_offset) // transparent offset + { + center[0] += r_refdef.view.forward[0]*queueentity->transparent_offset; + center[1] += r_refdef.view.forward[1]*queueentity->transparent_offset; + center[2] += r_refdef.view.forward[2]*queueentity->transparent_offset; + } + R_MeshQueue_AddTransparent(rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST ? r_refdef.view.origin : center, R_DrawSurface_TransparentCallback, queueentity, surface - rsurface.modelsurfaces, rsurface.rtlight); + } +} + +static void R_DrawTextureSurfaceList_DepthOnly(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHATEST))) + return; + if (r_waterstate.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION))) + return; + RSurf_SetupDepthAndCulling(); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX, texturenumsurfaces, texturesurfacelist); + if (rsurface.batchvertex3fbuffer) + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer); + else + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer); + RSurf_DrawBatch(); +} + +static void R_ProcessWorldTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean depthonly, qboolean prepass) +{ + const entity_render_t *queueentity = r_refdef.scene.worldentity; + CHECKGLERROR + if (depthonly) + R_DrawTextureSurfaceList_DepthOnly(texturenumsurfaces, texturesurfacelist); + else if (prepass) + { + if (!rsurface.texture->currentnumlayers) + return; + if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) + R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist, queueentity); + else + R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth, prepass); + } + else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) && (!r_showsurfaces.integer || r_showsurfaces.integer == 3)) + R_DrawTextureSurfaceList_Sky(texturenumsurfaces, texturesurfacelist); + else if (!rsurface.texture->currentnumlayers) + return; + else if (((rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) || (r_showsurfaces.integer == 3 && (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST))) && queueentity) + { + // in the deferred case, transparent surfaces were queued during prepass + if (!r_shadow_usingdeferredprepass) + R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist, queueentity); + } + else + { + // the alphatest check is to make sure we write depth for anything we skipped on the depth-only pass earlier + R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth || (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST), prepass); + } + CHECKGLERROR +} + +void R_QueueWorldSurfaceList(int numsurfaces, const msurface_t **surfacelist, int flagsmask, qboolean writedepth, qboolean depthonly, qboolean prepass) +{ + int i, j; + texture_t *texture; + R_FrameData_SetMark(); + // break the surface list down into batches by texture and use of lightmapping + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + // texture is the base texture pointer, rsurface.texture is the + // current frame/skin the texture is directing us to use (for example + // if a model has 2 skins and it is on skin 1, then skin 0 tells us to + // use skin 1 instead) + texture = surfacelist[i]->texture; + rsurface.texture = R_GetCurrentTexture(texture); + if (!(rsurface.texture->currentmaterialflags & flagsmask) || (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODRAW)) + { + // if this texture is not the kind we want, skip ahead to the next one + for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) + ; + continue; + } + if(FAKELIGHT_ENABLED || depthonly || prepass) + { + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + // simply scan ahead until we find a different texture or lightmap state + for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) + ; + } + else + { + rsurface.lightmaptexture = surfacelist[i]->lightmaptexture; + rsurface.deluxemaptexture = surfacelist[i]->deluxemaptexture; + rsurface.uselightmaptexture = surfacelist[i]->lightmaptexture != NULL; + // simply scan ahead until we find a different texture or lightmap state + for (;j < numsurfaces && texture == surfacelist[j]->texture && rsurface.lightmaptexture == surfacelist[j]->lightmaptexture;j++) + ; + } + // render the range of surfaces + R_ProcessWorldTextureSurfaceList(j - i, surfacelist + i, writedepth, depthonly, prepass); + } + R_FrameData_ReturnToMark(); +} + +static void R_ProcessModelTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean depthonly, const entity_render_t *queueentity, qboolean prepass) +{ + CHECKGLERROR + if (depthonly) + R_DrawTextureSurfaceList_DepthOnly(texturenumsurfaces, texturesurfacelist); + else if (prepass) + { + if (!rsurface.texture->currentnumlayers) + return; + if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) + R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist, queueentity); + else + R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth, prepass); + } + else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) && (!r_showsurfaces.integer || r_showsurfaces.integer == 3)) + R_DrawTextureSurfaceList_Sky(texturenumsurfaces, texturesurfacelist); + else if (!rsurface.texture->currentnumlayers) + return; + else if (((rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) || (r_showsurfaces.integer == 3 && (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST))) && queueentity) + { + // in the deferred case, transparent surfaces were queued during prepass + if (!r_shadow_usingdeferredprepass) + R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist, queueentity); + } + else + { + // the alphatest check is to make sure we write depth for anything we skipped on the depth-only pass earlier + R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth || (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST), prepass); + } + CHECKGLERROR +} + +void R_QueueModelSurfaceList(entity_render_t *ent, int numsurfaces, const msurface_t **surfacelist, int flagsmask, qboolean writedepth, qboolean depthonly, qboolean prepass) +{ + int i, j; + texture_t *texture; + R_FrameData_SetMark(); + // break the surface list down into batches by texture and use of lightmapping + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + // texture is the base texture pointer, rsurface.texture is the + // current frame/skin the texture is directing us to use (for example + // if a model has 2 skins and it is on skin 1, then skin 0 tells us to + // use skin 1 instead) + texture = surfacelist[i]->texture; + rsurface.texture = R_GetCurrentTexture(texture); + if (!(rsurface.texture->currentmaterialflags & flagsmask) || (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODRAW)) + { + // if this texture is not the kind we want, skip ahead to the next one + for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) + ; + continue; + } + if(FAKELIGHT_ENABLED || depthonly || prepass) + { + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + // simply scan ahead until we find a different texture or lightmap state + for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) + ; + } + else + { + rsurface.lightmaptexture = surfacelist[i]->lightmaptexture; + rsurface.deluxemaptexture = surfacelist[i]->deluxemaptexture; + rsurface.uselightmaptexture = surfacelist[i]->lightmaptexture != NULL; + // simply scan ahead until we find a different texture or lightmap state + for (;j < numsurfaces && texture == surfacelist[j]->texture && rsurface.lightmaptexture == surfacelist[j]->lightmaptexture;j++) + ; + } + // render the range of surfaces + R_ProcessModelTextureSurfaceList(j - i, surfacelist + i, writedepth, depthonly, ent, prepass); + } + R_FrameData_ReturnToMark(); +} + +float locboxvertex3f[6*4*3] = +{ + 1,0,1, 1,0,0, 1,1,0, 1,1,1, + 0,1,1, 0,1,0, 0,0,0, 0,0,1, + 1,1,1, 1,1,0, 0,1,0, 0,1,1, + 0,0,1, 0,0,0, 1,0,0, 1,0,1, + 0,0,1, 1,0,1, 1,1,1, 0,1,1, + 1,0,0, 0,0,0, 0,1,0, 1,1,0 +}; + +unsigned short locboxelements[6*2*3] = +{ + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9,10, 8,10,11, + 12,13,14, 12,14,15, + 16,17,18, 16,18,19, + 20,21,22, 20,22,23 +}; + +void R_DrawLoc_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i, j; + cl_locnode_t *loc = (cl_locnode_t *)ent; + vec3_t mins, size; + float vertex3f[6*4*3]; + CHECKGLERROR + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + R_EntityMatrix(&identitymatrix); + +// R_Mesh_ResetTextureState(); + + i = surfacelist[0]; + GL_Color(((i & 0x0007) >> 0) * (1.0f / 7.0f) * r_refdef.view.colorscale, + ((i & 0x0038) >> 3) * (1.0f / 7.0f) * r_refdef.view.colorscale, + ((i & 0x01C0) >> 6) * (1.0f / 7.0f) * r_refdef.view.colorscale, + surfacelist[0] < 0 ? 0.5f : 0.125f); + + if (VectorCompare(loc->mins, loc->maxs)) + { + VectorSet(size, 2, 2, 2); + VectorMA(loc->mins, -0.5f, size, mins); + } + else + { + VectorCopy(loc->mins, mins); + VectorSubtract(loc->maxs, loc->mins, size); + } + + for (i = 0;i < 6*4*3;) + for (j = 0;j < 3;j++, i++) + vertex3f[i] = mins[j] + size[j] * locboxvertex3f[i]; + + R_Mesh_PrepareVertices_Generic_Arrays(6*4, vertex3f, NULL, NULL); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + R_Mesh_Draw(0, 6*4, 0, 6*2, NULL, NULL, 0, locboxelements, NULL, 0); +} + +void R_DrawLocs(void) +{ + int index; + cl_locnode_t *loc, *nearestloc; + vec3_t center; + nearestloc = CL_Locs_FindNearest(cl.movement_origin); + for (loc = cl.locnodes, index = 0;loc;loc = loc->next, index++) + { + VectorLerp(loc->mins, 0.5f, loc->maxs, center); + R_MeshQueue_AddTransparent(center, R_DrawLoc_Callback, (entity_render_t *)loc, loc == nearestloc ? -1 : index, NULL); + } +} + +void R_DecalSystem_Reset(decalsystem_t *decalsystem) +{ + if (decalsystem->decals) + Mem_Free(decalsystem->decals); + memset(decalsystem, 0, sizeof(*decalsystem)); +} + +static void R_DecalSystem_SpawnTriangle(decalsystem_t *decalsystem, const float *v0, const float *v1, const float *v2, const float *t0, const float *t1, const float *t2, const float *c0, const float *c1, const float *c2, int triangleindex, int surfaceindex, int decalsequence) +{ + tridecal_t *decal; + tridecal_t *decals; + int i; + + // expand or initialize the system + if (decalsystem->maxdecals <= decalsystem->numdecals) + { + decalsystem_t old = *decalsystem; + qboolean useshortelements; + decalsystem->maxdecals = max(16, decalsystem->maxdecals * 2); + useshortelements = decalsystem->maxdecals * 3 <= 65536; + decalsystem->decals = (tridecal_t *)Mem_Alloc(cls.levelmempool, decalsystem->maxdecals * (sizeof(tridecal_t) + sizeof(float[3][3]) + sizeof(float[3][2]) + sizeof(float[3][4]) + sizeof(int[3]) + (useshortelements ? sizeof(unsigned short[3]) : 0))); + decalsystem->color4f = (float *)(decalsystem->decals + decalsystem->maxdecals); + decalsystem->texcoord2f = (float *)(decalsystem->color4f + decalsystem->maxdecals*12); + decalsystem->vertex3f = (float *)(decalsystem->texcoord2f + decalsystem->maxdecals*6); + decalsystem->element3i = (int *)(decalsystem->vertex3f + decalsystem->maxdecals*9); + decalsystem->element3s = (useshortelements ? ((unsigned short *)(decalsystem->element3i + decalsystem->maxdecals*3)) : NULL); + if (decalsystem->numdecals) + memcpy(decalsystem->decals, old.decals, decalsystem->numdecals * sizeof(tridecal_t)); + if (old.decals) + Mem_Free(old.decals); + for (i = 0;i < decalsystem->maxdecals*3;i++) + decalsystem->element3i[i] = i; + if (useshortelements) + for (i = 0;i < decalsystem->maxdecals*3;i++) + decalsystem->element3s[i] = i; + } + + // grab a decal and search for another free slot for the next one + decals = decalsystem->decals; + decal = decalsystem->decals + (i = decalsystem->freedecal++); + for (i = decalsystem->freedecal;i < decalsystem->numdecals && decals[i].color4f[0][3];i++) + ; + decalsystem->freedecal = i; + if (decalsystem->numdecals <= i) + decalsystem->numdecals = i + 1; + + // initialize the decal + decal->lived = 0; + decal->triangleindex = triangleindex; + decal->surfaceindex = surfaceindex; + decal->decalsequence = decalsequence; + decal->color4f[0][0] = c0[0]; + decal->color4f[0][1] = c0[1]; + decal->color4f[0][2] = c0[2]; + decal->color4f[0][3] = 1; + decal->color4f[1][0] = c1[0]; + decal->color4f[1][1] = c1[1]; + decal->color4f[1][2] = c1[2]; + decal->color4f[1][3] = 1; + decal->color4f[2][0] = c2[0]; + decal->color4f[2][1] = c2[1]; + decal->color4f[2][2] = c2[2]; + decal->color4f[2][3] = 1; + decal->vertex3f[0][0] = v0[0]; + decal->vertex3f[0][1] = v0[1]; + decal->vertex3f[0][2] = v0[2]; + decal->vertex3f[1][0] = v1[0]; + decal->vertex3f[1][1] = v1[1]; + decal->vertex3f[1][2] = v1[2]; + decal->vertex3f[2][0] = v2[0]; + decal->vertex3f[2][1] = v2[1]; + decal->vertex3f[2][2] = v2[2]; + decal->texcoord2f[0][0] = t0[0]; + decal->texcoord2f[0][1] = t0[1]; + decal->texcoord2f[1][0] = t1[0]; + decal->texcoord2f[1][1] = t1[1]; + decal->texcoord2f[2][0] = t2[0]; + decal->texcoord2f[2][1] = t2[1]; + TriangleNormal(v0, v1, v2, decal->plane); + VectorNormalize(decal->plane); + decal->plane[3] = DotProduct(v0, decal->plane); +} + +extern cvar_t cl_decals_bias; +extern cvar_t cl_decals_models; +extern cvar_t cl_decals_newsystem_intensitymultiplier; +// baseparms, parms, temps +static void R_DecalSystem_SplatTriangle(decalsystem_t *decalsystem, float r, float g, float b, float a, float s1, float t1, float s2, float t2, int decalsequence, qboolean dynamic, float (*planes)[4], matrix4x4_t *projection, int triangleindex, int surfaceindex) +{ + int cornerindex; + int index; + float v[9][3]; + const float *vertex3f; + const float *normal3f; + int numpoints; + float points[2][9][3]; + float temp[3]; + float tc[9][2]; + float f; + float c[9][4]; + const int *e; + + e = rsurface.modelelement3i + 3*triangleindex; + + vertex3f = rsurface.modelvertex3f; + normal3f = rsurface.modelnormal3f; + + if (normal3f) + { + for (cornerindex = 0;cornerindex < 3;cornerindex++) + { + index = 3*e[cornerindex]; + VectorMA(vertex3f + index, cl_decals_bias.value, normal3f + index, v[cornerindex]); + } + } + else + { + for (cornerindex = 0;cornerindex < 3;cornerindex++) + { + index = 3*e[cornerindex]; + VectorCopy(vertex3f + index, v[cornerindex]); + } + } + + // cull backfaces + //TriangleNormal(v[0], v[1], v[2], normal); + //if (DotProduct(normal, localnormal) < 0.0f) + // continue; + // clip by each of the box planes formed from the projection matrix + // if anything survives, we emit the decal + numpoints = PolygonF_Clip(3 , v[0] , planes[0][0], planes[0][1], planes[0][2], planes[0][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[1][0], planes[1][0], planes[1][1], planes[1][2], planes[1][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[0][0], planes[2][0], planes[2][1], planes[2][2], planes[2][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[1][0], planes[3][0], planes[3][1], planes[3][2], planes[3][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[0][0], planes[4][0], planes[4][1], planes[4][2], planes[4][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[1][0], planes[5][0], planes[5][1], planes[5][2], planes[5][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), v[0]); + if (numpoints < 3) + return; + // some part of the triangle survived, so we have to accept it... + if (dynamic) + { + // dynamic always uses the original triangle + numpoints = 3; + for (cornerindex = 0;cornerindex < 3;cornerindex++) + { + index = 3*e[cornerindex]; + VectorCopy(vertex3f + index, v[cornerindex]); + } + } + for (cornerindex = 0;cornerindex < numpoints;cornerindex++) + { + // convert vertex positions to texcoords + Matrix4x4_Transform(projection, v[cornerindex], temp); + tc[cornerindex][0] = (temp[1]+1.0f)*0.5f * (s2-s1) + s1; + tc[cornerindex][1] = (temp[2]+1.0f)*0.5f * (t2-t1) + t1; + // calculate distance fade from the projection origin + f = a * (1.0f-fabs(temp[0])) * cl_decals_newsystem_intensitymultiplier.value; + f = bound(0.0f, f, 1.0f); + c[cornerindex][0] = r * f; + c[cornerindex][1] = g * f; + c[cornerindex][2] = b * f; + c[cornerindex][3] = 1.0f; + //VectorMA(v[cornerindex], cl_decals_bias.value, localnormal, v[cornerindex]); + } + if (dynamic) + R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[1], v[2], tc[0], tc[1], tc[2], c[0], c[1], c[2], triangleindex, surfaceindex, decalsequence); + else + for (cornerindex = 0;cornerindex < numpoints-2;cornerindex++) + R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[cornerindex+1], v[cornerindex+2], tc[0], tc[cornerindex+1], tc[cornerindex+2], c[0], c[cornerindex+1], c[cornerindex+2], -1, surfaceindex, decalsequence); +} +static void R_DecalSystem_SplatEntity(entity_render_t *ent, const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize, int decalsequence) +{ + matrix4x4_t projection; + decalsystem_t *decalsystem; + qboolean dynamic; + dp_model_t *model; + const msurface_t *surface; + const msurface_t *surfaces; + const int *surfacelist; + const texture_t *texture; + int numtriangles; + int numsurfacelist; + int surfacelistindex; + int surfaceindex; + int triangleindex; + float localorigin[3]; + float localnormal[3]; + float localmins[3]; + float localmaxs[3]; + float localsize; + //float normal[3]; + float planes[6][4]; + float angles[3]; + bih_t *bih; + int bih_triangles_count; + int bih_triangles[256]; + int bih_surfaces[256]; + + decalsystem = &ent->decalsystem; + model = ent->model; + if (!model || !ent->allowdecals || ent->alpha < 1 || (ent->flags & (RENDER_ADDITIVE | RENDER_NODEPTHTEST))) + { + R_DecalSystem_Reset(&ent->decalsystem); + return; + } + + if (!model->brush.data_leafs && !cl_decals_models.integer) + { + if (decalsystem->model) + R_DecalSystem_Reset(decalsystem); + return; + } + + if (decalsystem->model != model) + R_DecalSystem_Reset(decalsystem); + decalsystem->model = model; + + RSurf_ActiveModelEntity(ent, true, false, false); + + Matrix4x4_Transform(&rsurface.inversematrix, worldorigin, localorigin); + Matrix4x4_Transform3x3(&rsurface.inversematrix, worldnormal, localnormal); + VectorNormalize(localnormal); + localsize = worldsize*rsurface.inversematrixscale; + localmins[0] = localorigin[0] - localsize; + localmins[1] = localorigin[1] - localsize; + localmins[2] = localorigin[2] - localsize; + localmaxs[0] = localorigin[0] + localsize; + localmaxs[1] = localorigin[1] + localsize; + localmaxs[2] = localorigin[2] + localsize; + + //VectorCopy(localnormal, planes[4]); + //VectorVectors(planes[4], planes[2], planes[0]); + AnglesFromVectors(angles, localnormal, NULL, false); + AngleVectors(angles, planes[0], planes[2], planes[4]); + VectorNegate(planes[0], planes[1]); + VectorNegate(planes[2], planes[3]); + VectorNegate(planes[4], planes[5]); + planes[0][3] = DotProduct(planes[0], localorigin) - localsize; + planes[1][3] = DotProduct(planes[1], localorigin) - localsize; + planes[2][3] = DotProduct(planes[2], localorigin) - localsize; + planes[3][3] = DotProduct(planes[3], localorigin) - localsize; + planes[4][3] = DotProduct(planes[4], localorigin) - localsize; + planes[5][3] = DotProduct(planes[5], localorigin) - localsize; + +#if 1 +// works +{ + matrix4x4_t forwardprojection; + Matrix4x4_CreateFromQuakeEntity(&forwardprojection, localorigin[0], localorigin[1], localorigin[2], angles[0], angles[1], angles[2], localsize); + Matrix4x4_Invert_Simple(&projection, &forwardprojection); +} +#else +// broken +{ + float projectionvector[4][3]; + VectorScale(planes[0], ilocalsize, projectionvector[0]); + VectorScale(planes[2], ilocalsize, projectionvector[1]); + VectorScale(planes[4], ilocalsize, projectionvector[2]); + projectionvector[0][0] = planes[0][0] * ilocalsize; + projectionvector[0][1] = planes[1][0] * ilocalsize; + projectionvector[0][2] = planes[2][0] * ilocalsize; + projectionvector[1][0] = planes[0][1] * ilocalsize; + projectionvector[1][1] = planes[1][1] * ilocalsize; + projectionvector[1][2] = planes[2][1] * ilocalsize; + projectionvector[2][0] = planes[0][2] * ilocalsize; + projectionvector[2][1] = planes[1][2] * ilocalsize; + projectionvector[2][2] = planes[2][2] * ilocalsize; + projectionvector[3][0] = -(localorigin[0]*projectionvector[0][0]+localorigin[1]*projectionvector[1][0]+localorigin[2]*projectionvector[2][0]); + projectionvector[3][1] = -(localorigin[0]*projectionvector[0][1]+localorigin[1]*projectionvector[1][1]+localorigin[2]*projectionvector[2][1]); + projectionvector[3][2] = -(localorigin[0]*projectionvector[0][2]+localorigin[1]*projectionvector[1][2]+localorigin[2]*projectionvector[2][2]); + Matrix4x4_FromVectors(&projection, projectionvector[0], projectionvector[1], projectionvector[2], projectionvector[3]); +} +#endif + + dynamic = model->surfmesh.isanimated; + numsurfacelist = model->nummodelsurfaces; + surfacelist = model->sortedmodelsurfaces; + surfaces = model->data_surfaces; + + bih = NULL; + bih_triangles_count = -1; + if(!dynamic) + { + if(model->render_bih.numleafs) + bih = &model->render_bih; + else if(model->collision_bih.numleafs) + bih = &model->collision_bih; + } + if(bih) + bih_triangles_count = BIH_GetTriangleListForBox(bih, sizeof(bih_triangles) / sizeof(*bih_triangles), bih_triangles, bih_surfaces, localmins, localmaxs); + if(bih_triangles_count == 0) + return; + if(bih_triangles_count > (int) (sizeof(bih_triangles) / sizeof(*bih_triangles))) // hit too many, likely bad anyway + return; + if(bih_triangles_count > 0) + { + for (triangleindex = 0; triangleindex < bih_triangles_count; ++triangleindex) + { + surfaceindex = bih_surfaces[triangleindex]; + surface = surfaces + surfaceindex; + texture = surface->texture; + if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) + continue; + if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS) + continue; + R_DecalSystem_SplatTriangle(decalsystem, r, g, b, a, s1, t1, s2, t2, decalsequence, dynamic, planes, &projection, bih_triangles[triangleindex], surfaceindex); + } + } + else + { + for (surfacelistindex = 0;surfacelistindex < numsurfacelist;surfacelistindex++) + { + surfaceindex = surfacelist[surfacelistindex]; + surface = surfaces + surfaceindex; + // check cull box first because it rejects more than any other check + if (!dynamic && !BoxesOverlap(surface->mins, surface->maxs, localmins, localmaxs)) + continue; + // skip transparent surfaces + texture = surface->texture; + if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) + continue; + if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS) + continue; + numtriangles = surface->num_triangles; + for (triangleindex = 0; triangleindex < numtriangles; triangleindex++) + R_DecalSystem_SplatTriangle(decalsystem, r, g, b, a, s1, t1, s2, t2, decalsequence, dynamic, planes, &projection, triangleindex + surface->num_firsttriangle, surfaceindex); + } + } +} + +// do not call this outside of rendering code - use R_DecalSystem_SplatEntities instead +static void R_DecalSystem_ApplySplatEntities(const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize, int decalsequence) +{ + int renderentityindex; + float worldmins[3]; + float worldmaxs[3]; + entity_render_t *ent; + + if (!cl_decals_newsystem.integer) + return; + + worldmins[0] = worldorigin[0] - worldsize; + worldmins[1] = worldorigin[1] - worldsize; + worldmins[2] = worldorigin[2] - worldsize; + worldmaxs[0] = worldorigin[0] + worldsize; + worldmaxs[1] = worldorigin[1] + worldsize; + worldmaxs[2] = worldorigin[2] + worldsize; + + R_DecalSystem_SplatEntity(r_refdef.scene.worldentity, worldorigin, worldnormal, r, g, b, a, s1, t1, s2, t2, worldsize, decalsequence); + + for (renderentityindex = 0;renderentityindex < r_refdef.scene.numentities;renderentityindex++) + { + ent = r_refdef.scene.entities[renderentityindex]; + if (!BoxesOverlap(ent->mins, ent->maxs, worldmins, worldmaxs)) + continue; + + R_DecalSystem_SplatEntity(ent, worldorigin, worldnormal, r, g, b, a, s1, t1, s2, t2, worldsize, decalsequence); + } +} + +typedef struct r_decalsystem_splatqueue_s +{ + vec3_t worldorigin; + vec3_t worldnormal; + float color[4]; + float tcrange[4]; + float worldsize; + int decalsequence; +} +r_decalsystem_splatqueue_t; + +int r_decalsystem_numqueued = 0; +r_decalsystem_splatqueue_t r_decalsystem_queue[MAX_DECALSYSTEM_QUEUE]; + +void R_DecalSystem_SplatEntities(const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize) +{ + r_decalsystem_splatqueue_t *queue; + + if (!cl_decals_newsystem.integer || r_decalsystem_numqueued == MAX_DECALSYSTEM_QUEUE) + return; + + queue = &r_decalsystem_queue[r_decalsystem_numqueued++]; + VectorCopy(worldorigin, queue->worldorigin); + VectorCopy(worldnormal, queue->worldnormal); + Vector4Set(queue->color, r, g, b, a); + Vector4Set(queue->tcrange, s1, t1, s2, t2); + queue->worldsize = worldsize; + queue->decalsequence = cl.decalsequence++; +} + +static void R_DecalSystem_ApplySplatEntitiesQueue(void) +{ + int i; + r_decalsystem_splatqueue_t *queue; + + for (i = 0, queue = r_decalsystem_queue;i < r_decalsystem_numqueued;i++, queue++) + R_DecalSystem_ApplySplatEntities(queue->worldorigin, queue->worldnormal, queue->color[0], queue->color[1], queue->color[2], queue->color[3], queue->tcrange[0], queue->tcrange[1], queue->tcrange[2], queue->tcrange[3], queue->worldsize, queue->decalsequence); + r_decalsystem_numqueued = 0; +} + +extern cvar_t cl_decals_max; +static void R_DrawModelDecals_FadeEntity(entity_render_t *ent) +{ + int i; + decalsystem_t *decalsystem = &ent->decalsystem; + int numdecals; + int killsequence; + tridecal_t *decal; + float frametime; + float lifetime; + + if (!decalsystem->numdecals) + return; + + if (r_showsurfaces.integer) + return; + + if (ent->model != decalsystem->model || ent->alpha < 1 || (ent->flags & RENDER_ADDITIVE)) + { + R_DecalSystem_Reset(decalsystem); + return; + } + + killsequence = cl.decalsequence - max(1, cl_decals_max.integer); + lifetime = cl_decals_time.value + cl_decals_fadetime.value; + + if (decalsystem->lastupdatetime) + frametime = (r_refdef.scene.time - decalsystem->lastupdatetime); + else + frametime = 0; + decalsystem->lastupdatetime = r_refdef.scene.time; + decal = decalsystem->decals; + numdecals = decalsystem->numdecals; + + for (i = 0, decal = decalsystem->decals;i < numdecals;i++, decal++) + { + if (decal->color4f[0][3]) + { + decal->lived += frametime; + if (killsequence - decal->decalsequence > 0 || decal->lived >= lifetime) + { + memset(decal, 0, sizeof(*decal)); + if (decalsystem->freedecal > i) + decalsystem->freedecal = i; + } + } + } + decal = decalsystem->decals; + while (numdecals > 0 && !decal[numdecals-1].color4f[0][3]) + numdecals--; + + // collapse the array by shuffling the tail decals into the gaps + for (;;) + { + while (decalsystem->freedecal < numdecals && decal[decalsystem->freedecal].color4f[0][3]) + decalsystem->freedecal++; + if (decalsystem->freedecal == numdecals) + break; + decal[decalsystem->freedecal] = decal[--numdecals]; + } + + decalsystem->numdecals = numdecals; + + if (numdecals <= 0) + { + // if there are no decals left, reset decalsystem + R_DecalSystem_Reset(decalsystem); + } +} + +extern skinframe_t *decalskinframe; +static void R_DrawModelDecals_Entity(entity_render_t *ent) +{ + int i; + decalsystem_t *decalsystem = &ent->decalsystem; + int numdecals; + tridecal_t *decal; + float faderate; + float alpha; + float *v3f; + float *c4f; + float *t2f; + const int *e; + const unsigned char *surfacevisible = ent == r_refdef.scene.worldentity ? r_refdef.viewcache.world_surfacevisible : NULL; + int numtris = 0; + + numdecals = decalsystem->numdecals; + if (!numdecals) + return; + + if (r_showsurfaces.integer) + return; + + if (ent->model != decalsystem->model || ent->alpha < 1 || (ent->flags & RENDER_ADDITIVE)) + { + R_DecalSystem_Reset(decalsystem); + return; + } + + // if the model is static it doesn't matter what value we give for + // wantnormals and wanttangents, so this logic uses only rules applicable + // to a model, knowing that they are meaningless otherwise + if (ent == r_refdef.scene.worldentity) + RSurf_ActiveWorldEntity(); + else + RSurf_ActiveModelEntity(ent, false, false, false); + + decalsystem->lastupdatetime = r_refdef.scene.time; + decal = decalsystem->decals; + + faderate = 1.0f / max(0.001f, cl_decals_fadetime.value); + + // update vertex positions for animated models + v3f = decalsystem->vertex3f; + c4f = decalsystem->color4f; + t2f = decalsystem->texcoord2f; + for (i = 0, decal = decalsystem->decals;i < numdecals;i++, decal++) + { + if (!decal->color4f[0][3]) + continue; + + if (surfacevisible && !surfacevisible[decal->surfaceindex]) + continue; + + // skip backfaces + if (decal->triangleindex < 0 && DotProduct(r_refdef.view.origin, decal->plane) < decal->plane[3]) + continue; + + // update color values for fading decals + if (decal->lived >= cl_decals_time.value) + alpha = 1 - faderate * (decal->lived - cl_decals_time.value); + else + alpha = 1.0f; + + c4f[ 0] = decal->color4f[0][0] * alpha; + c4f[ 1] = decal->color4f[0][1] * alpha; + c4f[ 2] = decal->color4f[0][2] * alpha; + c4f[ 3] = 1; + c4f[ 4] = decal->color4f[1][0] * alpha; + c4f[ 5] = decal->color4f[1][1] * alpha; + c4f[ 6] = decal->color4f[1][2] * alpha; + c4f[ 7] = 1; + c4f[ 8] = decal->color4f[2][0] * alpha; + c4f[ 9] = decal->color4f[2][1] * alpha; + c4f[10] = decal->color4f[2][2] * alpha; + c4f[11] = 1; + + t2f[0] = decal->texcoord2f[0][0]; + t2f[1] = decal->texcoord2f[0][1]; + t2f[2] = decal->texcoord2f[1][0]; + t2f[3] = decal->texcoord2f[1][1]; + t2f[4] = decal->texcoord2f[2][0]; + t2f[5] = decal->texcoord2f[2][1]; + + // update vertex positions for animated models + if (decal->triangleindex >= 0 && decal->triangleindex < rsurface.modelnumtriangles) + { + e = rsurface.modelelement3i + 3*decal->triangleindex; + VectorCopy(rsurface.modelvertex3f + 3*e[0], v3f); + VectorCopy(rsurface.modelvertex3f + 3*e[1], v3f + 3); + VectorCopy(rsurface.modelvertex3f + 3*e[2], v3f + 6); + } + else + { + VectorCopy(decal->vertex3f[0], v3f); + VectorCopy(decal->vertex3f[1], v3f + 3); + VectorCopy(decal->vertex3f[2], v3f + 6); + } + + if (r_refdef.fogenabled) + { + alpha = RSurf_FogVertex(v3f); + VectorScale(c4f, alpha, c4f); + alpha = RSurf_FogVertex(v3f + 3); + VectorScale(c4f + 4, alpha, c4f + 4); + alpha = RSurf_FogVertex(v3f + 6); + VectorScale(c4f + 8, alpha, c4f + 8); + } + + v3f += 9; + c4f += 12; + t2f += 6; + numtris++; + } + + if (numtris > 0) + { + r_refdef.stats.drawndecals += numtris; + + // now render the decals all at once + // (this assumes they all use one particle font texture!) + RSurf_ActiveCustomEntity(&rsurface.matrix, &rsurface.inversematrix, rsurface.ent_flags, ent->shadertime, 1, 1, 1, 1, numdecals*3, decalsystem->vertex3f, decalsystem->texcoord2f, NULL, NULL, NULL, decalsystem->color4f, numtris, decalsystem->element3i, decalsystem->element3s, false, false); +// R_Mesh_ResetTextureState(); + R_Mesh_PrepareVertices_Generic_Arrays(numtris * 3, decalsystem->vertex3f, decalsystem->color4f, decalsystem->texcoord2f); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(rsurface.basepolygonfactor + r_polygonoffset_decals_factor.value, rsurface.basepolygonoffset + r_polygonoffset_decals_offset.value); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + R_SetupShader_Generic(decalskinframe->base, NULL, GL_MODULATE, 1, false); + R_Mesh_Draw(0, numtris * 3, 0, numtris, decalsystem->element3i, NULL, 0, decalsystem->element3s, NULL, 0); + } +} + +static void R_DrawModelDecals(void) +{ + int i, numdecals; + + // fade faster when there are too many decals + numdecals = r_refdef.scene.worldentity->decalsystem.numdecals; + for (i = 0;i < r_refdef.scene.numentities;i++) + numdecals += r_refdef.scene.entities[i]->decalsystem.numdecals; + + R_DrawModelDecals_FadeEntity(r_refdef.scene.worldentity); + for (i = 0;i < r_refdef.scene.numentities;i++) + if (r_refdef.scene.entities[i]->decalsystem.numdecals) + R_DrawModelDecals_FadeEntity(r_refdef.scene.entities[i]); + + R_DecalSystem_ApplySplatEntitiesQueue(); + + numdecals = r_refdef.scene.worldentity->decalsystem.numdecals; + for (i = 0;i < r_refdef.scene.numentities;i++) + numdecals += r_refdef.scene.entities[i]->decalsystem.numdecals; + + r_refdef.stats.totaldecals += numdecals; + + if (r_showsurfaces.integer) + return; + + R_DrawModelDecals_Entity(r_refdef.scene.worldentity); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + if (r_refdef.scene.entities[i]->decalsystem.numdecals) + R_DrawModelDecals_Entity(r_refdef.scene.entities[i]); + } +} + +extern cvar_t mod_collision_bih; +void R_DrawDebugModel(void) +{ + entity_render_t *ent = rsurface.entity; + int i, j, k, l, flagsmask; + const msurface_t *surface; + dp_model_t *model = ent->model; + vec3_t v; + + if (!sv.active && !cls.demoplayback && ent != r_refdef.scene.worldentity) + return; + + if (r_showoverdraw.value > 0) + { + float c = r_refdef.view.colorscale * r_showoverdraw.value * 0.125f; + flagsmask = MATERIALFLAG_SKY | MATERIALFLAG_WALL; + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + GL_DepthTest(false); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_BlendFunc(GL_ONE, GL_ONE); + for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) + { + if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) + continue; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, 1, &surface); + GL_CullFace((rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE) ? GL_NONE : r_refdef.view.cullface_back); + if (!rsurface.texture->currentlayers->depthmask) + GL_Color(c, 0, 0, 1.0f); + else if (ent == r_refdef.scene.worldentity) + GL_Color(c, c, c, 1.0f); + else + GL_Color(0, c, 0, 1.0f); + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); + RSurf_DrawBatch(); + } + } + rsurface.texture = NULL; + } + + flagsmask = MATERIALFLAG_SKY | MATERIALFLAG_WALL; + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + GL_DepthRange(0, 1); + GL_DepthTest(!r_showdisabledepthtest.integer); + GL_DepthMask(false); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (r_showcollisionbrushes.value > 0 && model->collision_bih.numleafs) + { + int triangleindex; + int bihleafindex; + qboolean cullbox = ent == r_refdef.scene.worldentity; + const q3mbrush_t *brush; + const bih_t *bih = &model->collision_bih; + const bih_leaf_t *bihleaf; + float vertex3f[3][3]; + GL_PolygonOffset(r_refdef.polygonfactor + r_showcollisionbrushes_polygonfactor.value, r_refdef.polygonoffset + r_showcollisionbrushes_polygonoffset.value); + cullbox = false; + for (bihleafindex = 0, bihleaf = bih->leafs;bihleafindex < bih->numleafs;bihleafindex++, bihleaf++) + { + if (cullbox && R_CullBox(bihleaf->mins, bihleaf->maxs)) + continue; + switch (bihleaf->type) + { + case BIH_BRUSH: + brush = model->brush.data_brushes + bihleaf->itemindex; + if (brush->colbrushf && brush->colbrushf->numtriangles) + { + GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); + R_Mesh_PrepareVertices_Generic_Arrays(brush->colbrushf->numpoints, brush->colbrushf->points->v, NULL, NULL); + R_Mesh_Draw(0, brush->colbrushf->numpoints, 0, brush->colbrushf->numtriangles, brush->colbrushf->elements, NULL, 0, NULL, NULL, 0); + } + break; + case BIH_COLLISIONTRIANGLE: + triangleindex = bihleaf->itemindex; + VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+0], vertex3f[0]); + VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+1], vertex3f[1]); + VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+2], vertex3f[2]); + GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); + R_Mesh_PrepareVertices_Generic_Arrays(3, vertex3f[0], NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + break; + case BIH_RENDERTRIANGLE: + triangleindex = bihleaf->itemindex; + VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+0], vertex3f[0]); + VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+1], vertex3f[1]); + VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+2], vertex3f[2]); + GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); + R_Mesh_PrepareVertices_Generic_Arrays(3, vertex3f[0], NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + break; + } + } + } + + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + + if (r_showtris.integer && qglPolygonMode) + { + if (r_showdisabledepthtest.integer) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + } + else + { + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); + } + qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);CHECKGLERROR + for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) + { + if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) + continue; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_NOGAPS, 1, &surface); + if (!rsurface.texture->currentlayers->depthmask) + GL_Color(r_refdef.view.colorscale, 0, 0, r_showtris.value); + else if (ent == r_refdef.scene.worldentity) + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, r_showtris.value); + else + GL_Color(0, r_refdef.view.colorscale, 0, r_showtris.value); + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); + RSurf_DrawBatch(); + } + } + qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);CHECKGLERROR + rsurface.texture = NULL; + } + + if (r_shownormals.value != 0 && qglBegin) + { + if (r_showdisabledepthtest.integer) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + } + else + { + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); + } + for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) + { + if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) + continue; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_NOGAPS, 1, &surface); + qglBegin(GL_LINES); + if (r_shownormals.value < 0 && rsurface.batchnormal3f) + { + for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) + { + VectorCopy(rsurface.batchvertex3f + l * 3, v); + GL_Color(0, 0, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + VectorMA(v, -r_shownormals.value, rsurface.batchnormal3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + } + } + if (r_shownormals.value > 0 && rsurface.batchsvector3f) + { + for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) + { + VectorCopy(rsurface.batchvertex3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, 0, 0, 1); + qglVertex3f(v[0], v[1], v[2]); + VectorMA(v, r_shownormals.value, rsurface.batchsvector3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + } + } + if (r_shownormals.value > 0 && rsurface.batchtvector3f) + { + for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) + { + VectorCopy(rsurface.batchvertex3f + l * 3, v); + GL_Color(0, r_refdef.view.colorscale, 0, 1); + qglVertex3f(v[0], v[1], v[2]); + VectorMA(v, r_shownormals.value, rsurface.batchtvector3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + } + } + if (r_shownormals.value > 0 && rsurface.batchnormal3f) + { + for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) + { + VectorCopy(rsurface.batchvertex3f + l * 3, v); + GL_Color(0, 0, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + VectorMA(v, r_shownormals.value, rsurface.batchnormal3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + } + } + qglEnd(); + CHECKGLERROR + } + } + rsurface.texture = NULL; + } +} + +extern void R_BuildLightMap(const entity_render_t *ent, msurface_t *surface); +int r_maxsurfacelist = 0; +const msurface_t **r_surfacelist = NULL; +void R_DrawWorldSurfaces(qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass) +{ + int i, j, endj, flagsmask; + dp_model_t *model = r_refdef.scene.worldmodel; + msurface_t *surfaces; + unsigned char *update; + int numsurfacelist = 0; + if (model == NULL) + return; + + if (r_maxsurfacelist < model->num_surfaces) + { + r_maxsurfacelist = model->num_surfaces; + if (r_surfacelist) + Mem_Free((msurface_t**)r_surfacelist); + r_surfacelist = (const msurface_t **) Mem_Alloc(r_main_mempool, r_maxsurfacelist * sizeof(*r_surfacelist)); + } + + RSurf_ActiveWorldEntity(); + + surfaces = model->data_surfaces; + update = model->brushq1.lightmapupdateflags; + + // update light styles on this submodel + if (!skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.lightmapintensity > 0) + { + model_brush_lightstyleinfo_t *style; + for (i = 0, style = model->brushq1.data_lightstyleinfo;i < model->brushq1.num_lightstyles;i++, style++) + { + if (style->value != r_refdef.scene.lightstylevalue[style->style]) + { + int *list = style->surfacelist; + style->value = r_refdef.scene.lightstylevalue[style->style]; + for (j = 0;j < style->numsurfaces;j++) + update[list[j]] = true; + } + } + } + + flagsmask = skysurfaces ? MATERIALFLAG_SKY : MATERIALFLAG_WALL; + + if (debug) + { + R_DrawDebugModel(); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + return; + } + + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + rsurface.texture = NULL; + rsurface.rtlight = NULL; + numsurfacelist = 0; + // add visible surfaces to draw list + for (i = 0;i < model->nummodelsurfaces;i++) + { + j = model->sortedmodelsurfaces[i]; + if (r_refdef.viewcache.world_surfacevisible[j]) + r_surfacelist[numsurfacelist++] = surfaces + j; + } + // update lightmaps if needed + if (model->brushq1.firstrender) + { + model->brushq1.firstrender = false; + for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) + if (update[j]) + R_BuildLightMap(r_refdef.scene.worldentity, surfaces + j); + } + else if (update) + { + for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) + if (r_refdef.viewcache.world_surfacevisible[j]) + if (update[j]) + R_BuildLightMap(r_refdef.scene.worldentity, surfaces + j); + } + // don't do anything if there were no surfaces + if (!numsurfacelist) + { + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + return; + } + R_QueueWorldSurfaceList(numsurfacelist, r_surfacelist, flagsmask, writedepth, depthonly, prepass); + + // add to stats if desired + if (r_speeds.integer && !skysurfaces && !depthonly) + { + r_refdef.stats.world_surfaces += numsurfacelist; + for (j = 0;j < numsurfacelist;j++) + r_refdef.stats.world_triangles += r_surfacelist[j]->num_triangles; + } + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_DrawModelSurfaces(entity_render_t *ent, qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass) +{ + int i, j, endj, flagsmask; + dp_model_t *model = ent->model; + msurface_t *surfaces; + unsigned char *update; + int numsurfacelist = 0; + if (model == NULL) + return; + + if (r_maxsurfacelist < model->num_surfaces) + { + r_maxsurfacelist = model->num_surfaces; + if (r_surfacelist) + Mem_Free((msurface_t **)r_surfacelist); + r_surfacelist = (const msurface_t **) Mem_Alloc(r_main_mempool, r_maxsurfacelist * sizeof(*r_surfacelist)); + } + + // if the model is static it doesn't matter what value we give for + // wantnormals and wanttangents, so this logic uses only rules applicable + // to a model, knowing that they are meaningless otherwise + if (ent == r_refdef.scene.worldentity) + RSurf_ActiveWorldEntity(); + else if (r_showsurfaces.integer && r_showsurfaces.integer != 3) + RSurf_ActiveModelEntity(ent, false, false, false); + else if (prepass) + RSurf_ActiveModelEntity(ent, true, true, true); + else if (depthonly) + { + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + RSurf_ActiveModelEntity(ent, model->wantnormals, model->wanttangents, false); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + RSurf_ActiveModelEntity(ent, model->wantnormals, false, false); + break; + } + } + else + { + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + RSurf_ActiveModelEntity(ent, true, true, false); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + RSurf_ActiveModelEntity(ent, true, false, false); + break; + } + } + + surfaces = model->data_surfaces; + update = model->brushq1.lightmapupdateflags; + + // update light styles + if (!skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.lightmapintensity > 0) + { + model_brush_lightstyleinfo_t *style; + for (i = 0, style = model->brushq1.data_lightstyleinfo;i < model->brushq1.num_lightstyles;i++, style++) + { + if (style->value != r_refdef.scene.lightstylevalue[style->style]) + { + int *list = style->surfacelist; + style->value = r_refdef.scene.lightstylevalue[style->style]; + for (j = 0;j < style->numsurfaces;j++) + update[list[j]] = true; + } + } + } + + flagsmask = skysurfaces ? MATERIALFLAG_SKY : MATERIALFLAG_WALL; + + if (debug) + { + R_DrawDebugModel(); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + return; + } + + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + rsurface.texture = NULL; + rsurface.rtlight = NULL; + numsurfacelist = 0; + // add visible surfaces to draw list + for (i = 0;i < model->nummodelsurfaces;i++) + r_surfacelist[numsurfacelist++] = surfaces + model->sortedmodelsurfaces[i]; + // don't do anything if there were no surfaces + if (!numsurfacelist) + { + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + return; + } + // update lightmaps if needed + if (update) + { + int updated = 0; + for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) + { + if (update[j]) + { + updated++; + R_BuildLightMap(ent, surfaces + j); + } + } + } + if (update) + for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) + if (update[j]) + R_BuildLightMap(ent, surfaces + j); + R_QueueModelSurfaceList(ent, numsurfacelist, r_surfacelist, flagsmask, writedepth, depthonly, prepass); + + // add to stats if desired + if (r_speeds.integer && !skysurfaces && !depthonly) + { + r_refdef.stats.entities_surfaces += numsurfacelist; + for (j = 0;j < numsurfacelist;j++) + r_refdef.stats.entities_triangles += r_surfacelist[j]->num_triangles; + } + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_DrawCustomSurface(skinframe_t *skinframe, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass) +{ + static texture_t texture; + static msurface_t surface; + const msurface_t *surfacelist = &surface; + + // fake enough texture and surface state to render this geometry + + texture.update_lastrenderframe = -1; // regenerate this texture + texture.basematerialflags = materialflags | MATERIALFLAG_CUSTOMSURFACE | MATERIALFLAG_WALL; + texture.currentskinframe = skinframe; + texture.currenttexmatrix = *texmatrix; // requires MATERIALFLAG_CUSTOMSURFACE + texture.offsetmapping = OFFSETMAPPING_OFF; + texture.offsetscale = 1; + texture.specularscalemod = 1; + texture.specularpowermod = 1; + + surface.texture = &texture; + surface.num_triangles = numtriangles; + surface.num_firsttriangle = firsttriangle; + surface.num_vertices = numvertices; + surface.num_firstvertex = firstvertex; + + // now render it + rsurface.texture = R_GetCurrentTexture(surface.texture); + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + R_DrawModelTextureSurfaceList(1, &surfacelist, writedepth, prepass); +} + +void R_DrawCustomSurface_Texture(texture_t *texture, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass) +{ + static msurface_t surface; + const msurface_t *surfacelist = &surface; + + // fake enough texture and surface state to render this geometry + surface.texture = texture; + surface.num_triangles = numtriangles; + surface.num_firsttriangle = firsttriangle; + surface.num_vertices = numvertices; + surface.num_firstvertex = firstvertex; + + // now render it + rsurface.texture = R_GetCurrentTexture(surface.texture); + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + R_DrawModelTextureSurfaceList(1, &surfacelist, writedepth, prepass); +} diff --git a/misc/source/darkplaces-src/gl_rsurf.c b/misc/source/darkplaces-src/gl_rsurf.c new file mode 100644 index 00000000..967edc6f --- /dev/null +++ b/misc/source/darkplaces-src/gl_rsurf.c @@ -0,0 +1,1623 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// r_surf.c: surface-related refresh code + +#include "quakedef.h" +#include "r_shadow.h" +#include "portals.h" +#include "csprogs.h" + +cvar_t r_ambient = {0, "r_ambient", "0", "brightens map, value is 0-128"}; +cvar_t r_lockpvs = {0, "r_lockpvs", "0", "disables pvs switching, allows you to walk around and inspect what is visible from a given location in the map (anything not visible from your current location will not be drawn)"}; +cvar_t r_lockvisibility = {0, "r_lockvisibility", "0", "disables visibility updates, allows you to walk around and inspect what is visible from a given viewpoint in the map (anything offscreen at the moment this is enabled will not be drawn)"}; +cvar_t r_useportalculling = {0, "r_useportalculling", "2", "improve framerate with r_novis 1 by using portal culling - still not as good as compiled visibility data in the map, but it helps (a value of 2 forces use of this even with vis data, which improves framerates in maps without too much complexity, but hurts in extremely complex maps, which is why 2 is not the default mode)"}; +cvar_t r_usesurfaceculling = {0, "r_usesurfaceculling", "1", "improve framerate by culling offscreen surfaces"}; +cvar_t r_q3bsp_renderskydepth = {0, "r_q3bsp_renderskydepth", "0", "draws sky depth masking in q3 maps (as in q1 maps), this means for example that sky polygons can hide other things"}; + +/* +=============== +R_BuildLightMap + +Combine and scale multiple lightmaps into the 8.8 format in blocklights +=============== +*/ +void R_BuildLightMap (const entity_render_t *ent, msurface_t *surface) +{ + int smax, tmax, i, size, size3, maps, l; + int *bl, scale; + unsigned char *lightmap, *out, *stain; + dp_model_t *model = ent->model; + int *intblocklights; + unsigned char *templight; + + smax = (surface->lightmapinfo->extents[0]>>4)+1; + tmax = (surface->lightmapinfo->extents[1]>>4)+1; + size = smax*tmax; + size3 = size*3; + + r_refdef.stats.lightmapupdatepixels += size; + r_refdef.stats.lightmapupdates++; + + if (cl.buildlightmapmemorysize < size*sizeof(int[3])) + { + cl.buildlightmapmemorysize = size*sizeof(int[3]); + if (cl.buildlightmapmemory) + Mem_Free(cl.buildlightmapmemory); + cl.buildlightmapmemory = (unsigned char *) Mem_Alloc(cls.levelmempool, cl.buildlightmapmemorysize); + } + + // these both point at the same buffer, templight is only used for final + // processing and can replace the intblocklights data as it goes + intblocklights = (int *)cl.buildlightmapmemory; + templight = (unsigned char *)cl.buildlightmapmemory; + + // update cached lighting info + model->brushq1.lightmapupdateflags[surface - model->data_surfaces] = false; + + lightmap = surface->lightmapinfo->samples; + +// set to full bright if no light data + bl = intblocklights; + if (!model->brushq1.lightdata) + { + for (i = 0;i < size3;i++) + bl[i] = 128*256; + } + else + { +// clear to no light + memset(bl, 0, size3*sizeof(*bl)); + +// add all the lightmaps + if (lightmap) + for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++, lightmap += size3) + for (scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]], i = 0;i < size3;i++) + bl[i] += lightmap[i] * scale; + } + + stain = surface->lightmapinfo->stainsamples; + bl = intblocklights; + out = templight; + // the >> 16 shift adjusts down 8 bits to account for the stainmap + // scaling, and remaps the 0-65536 (2x overbright) to 0-256, it will + // be doubled during rendering to achieve 2x overbright + // (0 = 0.0, 128 = 1.0, 256 = 2.0) + if (stain) + { + for (i = 0;i < size;i++, bl += 3, stain += 3, out += 4) + { + l = (bl[0] * stain[0]) >> 16;out[2] = min(l, 255); + l = (bl[1] * stain[1]) >> 16;out[1] = min(l, 255); + l = (bl[2] * stain[2]) >> 16;out[0] = min(l, 255); + out[3] = 255; + } + } + else + { + for (i = 0;i < size;i++, bl += 3, out += 4) + { + l = bl[0] >> 8;out[2] = min(l, 255); + l = bl[1] >> 8;out[1] = min(l, 255); + l = bl[2] >> 8;out[0] = min(l, 255); + out[3] = 255; + } + } + + R_UpdateTexture(surface->lightmaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1); + + // update the surface's deluxemap if it has one + if (surface->deluxemaptexture != r_texture_blanknormalmap) + { + vec3_t n; + unsigned char *normalmap = surface->lightmapinfo->nmapsamples; + lightmap = surface->lightmapinfo->samples; + // clear to no normalmap + bl = intblocklights; + memset(bl, 0, size3*sizeof(*bl)); + // add all the normalmaps + if (lightmap && normalmap) + { + for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++, lightmap += size3, normalmap += size3) + { + for (scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]], i = 0;i < size;i++) + { + // add the normalmap with weighting proportional to the style's lightmap intensity + l = (int)(VectorLength(lightmap + i*3) * scale); + bl[i*3+0] += ((int)normalmap[i*3+0] - 128) * l; + bl[i*3+1] += ((int)normalmap[i*3+1] - 128) * l; + bl[i*3+2] += ((int)normalmap[i*3+2] - 128) * l; + } + } + } + bl = intblocklights; + out = templight; + // we simply renormalize the weighted normals to get a valid deluxemap + for (i = 0;i < size;i++, bl += 3, out += 4) + { + VectorCopy(bl, n); + VectorNormalize(n); + l = (int)(n[0] * 128 + 128);out[2] = bound(0, l, 255); + l = (int)(n[1] * 128 + 128);out[1] = bound(0, l, 255); + l = (int)(n[2] * 128 + 128);out[0] = bound(0, l, 255); + out[3] = 255; + } + R_UpdateTexture(surface->deluxemaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1); + } +} + +void R_StainNode (mnode_t *node, dp_model_t *model, const vec3_t origin, float radius, const float fcolor[8]) +{ + float ndist, a, ratio, maxdist, maxdist2, maxdist3, invradius, sdtable[256], td, dist2; + msurface_t *surface, *endsurface; + int i, s, t, smax, tmax, smax3, impacts, impactt, stained; + unsigned char *bl; + vec3_t impact; + + maxdist = radius * radius; + invradius = 1.0f / radius; + +loc0: + if (!node->plane) + return; + ndist = PlaneDiff(origin, node->plane); + if (ndist > radius) + { + node = node->children[0]; + goto loc0; + } + if (ndist < -radius) + { + node = node->children[1]; + goto loc0; + } + + dist2 = ndist * ndist; + maxdist3 = maxdist - dist2; + + if (node->plane->type < 3) + { + VectorCopy(origin, impact); + impact[node->plane->type] -= ndist; + } + else + { + impact[0] = origin[0] - node->plane->normal[0] * ndist; + impact[1] = origin[1] - node->plane->normal[1] * ndist; + impact[2] = origin[2] - node->plane->normal[2] * ndist; + } + + for (surface = model->data_surfaces + node->firstsurface, endsurface = surface + node->numsurfaces;surface < endsurface;surface++) + { + if (surface->lightmapinfo->stainsamples) + { + smax = (surface->lightmapinfo->extents[0] >> 4) + 1; + tmax = (surface->lightmapinfo->extents[1] >> 4) + 1; + + impacts = (int)(DotProduct (impact, surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3] - surface->lightmapinfo->texturemins[0]); + impactt = (int)(DotProduct (impact, surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3] - surface->lightmapinfo->texturemins[1]); + + s = bound(0, impacts, smax * 16) - impacts; + t = bound(0, impactt, tmax * 16) - impactt; + i = (int)(s * s + t * t + dist2); + if ((i > maxdist) || (smax > (int)(sizeof(sdtable)/sizeof(sdtable[0])))) // smax overflow fix from Andreas Dehmel + continue; + + // reduce calculations + for (s = 0, i = impacts; s < smax; s++, i -= 16) + sdtable[s] = i * i + dist2; + + bl = surface->lightmapinfo->stainsamples; + smax3 = smax * 3; + stained = false; + + i = impactt; + for (t = 0;t < tmax;t++, i -= 16) + { + td = i * i; + // make sure some part of it is visible on this line + if (td < maxdist3) + { + maxdist2 = maxdist - td; + for (s = 0;s < smax;s++) + { + if (sdtable[s] < maxdist2) + { + ratio = lhrandom(0.0f, 1.0f); + a = (fcolor[3] + ratio * fcolor[7]) * (1.0f - sqrt(sdtable[s] + td) * invradius); + if (a >= (1.0f / 64.0f)) + { + if (a > 1) + a = 1; + bl[0] = (unsigned char) ((float) bl[0] + a * ((fcolor[0] + ratio * fcolor[4]) - (float) bl[0])); + bl[1] = (unsigned char) ((float) bl[1] + a * ((fcolor[1] + ratio * fcolor[5]) - (float) bl[1])); + bl[2] = (unsigned char) ((float) bl[2] + a * ((fcolor[2] + ratio * fcolor[6]) - (float) bl[2])); + stained = true; + } + } + bl += 3; + } + } + else // skip line + bl += smax3; + } + // force lightmap upload + if (stained) + model->brushq1.lightmapupdateflags[surface - model->data_surfaces] = true; + } + } + + if (node->children[0]->plane) + { + if (node->children[1]->plane) + { + R_StainNode(node->children[0], model, origin, radius, fcolor); + node = node->children[1]; + goto loc0; + } + else + { + node = node->children[0]; + goto loc0; + } + } + else if (node->children[1]->plane) + { + node = node->children[1]; + goto loc0; + } +} + +void R_Stain (const vec3_t origin, float radius, int cr1, int cg1, int cb1, int ca1, int cr2, int cg2, int cb2, int ca2) +{ + int n; + float fcolor[8]; + entity_render_t *ent; + dp_model_t *model; + vec3_t org; + if (r_refdef.scene.worldmodel == NULL || !r_refdef.scene.worldmodel->brush.data_nodes || !r_refdef.scene.worldmodel->brushq1.lightdata) + return; + fcolor[0] = cr1; + fcolor[1] = cg1; + fcolor[2] = cb1; + fcolor[3] = ca1 * (1.0f / 64.0f); + fcolor[4] = cr2 - cr1; + fcolor[5] = cg2 - cg1; + fcolor[6] = cb2 - cb1; + fcolor[7] = (ca2 - ca1) * (1.0f / 64.0f); + + R_StainNode(r_refdef.scene.worldmodel->brush.data_nodes + r_refdef.scene.worldmodel->brushq1.hulls[0].firstclipnode, r_refdef.scene.worldmodel, origin, radius, fcolor); + + // look for embedded bmodels + for (n = 0;n < cl.num_brushmodel_entities;n++) + { + ent = &cl.entities[cl.brushmodel_entities[n]].render; + model = ent->model; + if (model && model->name[0] == '*') + { + if (model->brush.data_nodes) + { + Matrix4x4_Transform(&ent->inversematrix, origin, org); + R_StainNode(model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode, model, org, radius, fcolor); + } + } + } +} + + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +static void R_DrawPortal_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + // due to the hacky nature of this function's parameters, this is never + // called with a batch, so numsurfaces is always 1, and the surfacelist + // contains only a leaf number for coloring purposes + const mportal_t *portal = (mportal_t *)ent; + qboolean isvis; + int i, numpoints; + float *v; + float vertex3f[POLYGONELEMENTS_MAXPOINTS*3]; + CHECKGLERROR + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + R_EntityMatrix(&identitymatrix); + + numpoints = min(portal->numpoints, POLYGONELEMENTS_MAXPOINTS); + +// R_Mesh_ResetTextureState(); + + isvis = (portal->here->clusterindex >= 0 && portal->past->clusterindex >= 0 && portal->here->clusterindex != portal->past->clusterindex); + + i = surfacelist[0] >> 1; + GL_Color(((i & 0x0007) >> 0) * (1.0f / 7.0f) * r_refdef.view.colorscale, + ((i & 0x0038) >> 3) * (1.0f / 7.0f) * r_refdef.view.colorscale, + ((i & 0x01C0) >> 6) * (1.0f / 7.0f) * r_refdef.view.colorscale, + isvis ? 0.125f : 0.03125f); + for (i = 0, v = vertex3f;i < numpoints;i++, v += 3) + VectorCopy(portal->points[i].position, v); + R_Mesh_PrepareVertices_Generic_Arrays(numpoints, vertex3f, NULL, NULL); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + R_Mesh_Draw(0, numpoints, 0, numpoints - 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +// LordHavoc: this is just a nice debugging tool, very slow +void R_DrawPortals(void) +{ + int i, leafnum; + mportal_t *portal; + float center[3], f; + dp_model_t *model = r_refdef.scene.worldmodel; + if (model == NULL) + return; + for (leafnum = 0;leafnum < r_refdef.scene.worldmodel->brush.num_leafs;leafnum++) + { + if (r_refdef.viewcache.world_leafvisible[leafnum]) + { + //for (portalnum = 0, portal = model->brush.data_portals;portalnum < model->brush.num_portals;portalnum++, portal++) + for (portal = r_refdef.scene.worldmodel->brush.data_leafs[leafnum].portals;portal;portal = portal->next) + { + if (portal->numpoints <= POLYGONELEMENTS_MAXPOINTS) + if (!R_CullBox(portal->mins, portal->maxs)) + { + VectorClear(center); + for (i = 0;i < portal->numpoints;i++) + VectorAdd(center, portal->points[i].position, center); + f = ixtable[portal->numpoints]; + VectorScale(center, f, center); + R_MeshQueue_AddTransparent(center, R_DrawPortal_Callback, (entity_render_t *)portal, leafnum, rsurface.rtlight); + } + } + } + } +} + +void R_View_WorldVisibility(qboolean forcenovis) +{ + int i, j, *mark; + mleaf_t *leaf; + mleaf_t *viewleaf; + dp_model_t *model = r_refdef.scene.worldmodel; + + if (!model) + return; + + if (r_refdef.view.usecustompvs) + { + // clear the visible surface and leaf flags arrays + memset(r_refdef.viewcache.world_surfacevisible, 0, model->num_surfaces); + memset(r_refdef.viewcache.world_leafvisible, 0, model->brush.num_leafs); + r_refdef.viewcache.world_novis = false; + + // simply cull each marked leaf to the frustum (view pyramid) + for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) + { + // if leaf is in current pvs and on the screen, mark its surfaces + if (CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex) && !R_CullBox(leaf->mins, leaf->maxs)) + { + r_refdef.stats.world_leafs++; + r_refdef.viewcache.world_leafvisible[j] = true; + if (leaf->numleafsurfaces) + for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) + r_refdef.viewcache.world_surfacevisible[*mark] = true; + } + } + return; + } + + // if possible find the leaf the view origin is in + viewleaf = model->brush.PointInLeaf ? model->brush.PointInLeaf(model, r_refdef.view.origin) : NULL; + // if possible fetch the visible cluster bits + if (!r_lockpvs.integer && model->brush.FatPVS) + model->brush.FatPVS(model, r_refdef.view.origin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); + + if (!r_lockvisibility.integer) + { + // clear the visible surface and leaf flags arrays + memset(r_refdef.viewcache.world_surfacevisible, 0, model->num_surfaces); + memset(r_refdef.viewcache.world_leafvisible, 0, model->brush.num_leafs); + + r_refdef.viewcache.world_novis = false; + + // if floating around in the void (no pvs data available, and no + // portals available), simply use all on-screen leafs. + if (!viewleaf || viewleaf->clusterindex < 0 || forcenovis || r_trippy.integer) + { + // no visibility method: (used when floating around in the void) + // simply cull each leaf to the frustum (view pyramid) + // similar to quake's RecursiveWorldNode but without cache misses + r_refdef.viewcache.world_novis = true; + for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) + { + if (leaf->clusterindex < 0) + continue; + // if leaf is in current pvs and on the screen, mark its surfaces + if (!R_CullBox(leaf->mins, leaf->maxs)) + { + r_refdef.stats.world_leafs++; + r_refdef.viewcache.world_leafvisible[j] = true; + if (leaf->numleafsurfaces) + for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) + r_refdef.viewcache.world_surfacevisible[*mark] = true; + } + } + } + // just check if each leaf in the PVS is on screen + // (unless portal culling is enabled) + else if (!model->brush.data_portals || r_useportalculling.integer < 1 || (r_useportalculling.integer < 2 && !r_novis.integer)) + { + // pvs method: + // simply check if each leaf is in the Potentially Visible Set, + // and cull to frustum (view pyramid) + // similar to quake's RecursiveWorldNode but without cache misses + for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) + { + if (leaf->clusterindex < 0) + continue; + // if leaf is in current pvs and on the screen, mark its surfaces + if (CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex) && !R_CullBox(leaf->mins, leaf->maxs)) + { + r_refdef.stats.world_leafs++; + r_refdef.viewcache.world_leafvisible[j] = true; + if (leaf->numleafsurfaces) + for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) + r_refdef.viewcache.world_surfacevisible[*mark] = true; + } + } + } + // if desired use a recursive portal flow, culling each portal to + // frustum and checking if the leaf the portal leads to is in the pvs + else + { + int leafstackpos; + mportal_t *p; + mleaf_t *leafstack[8192]; + // simple-frustum portal method: + // follows portals leading outward from viewleaf, does not venture + // offscreen or into leafs that are not visible, faster than + // Quake's RecursiveWorldNode and vastly better in unvised maps, + // often culls some surfaces that pvs alone would miss + // (such as a room in pvs that is hidden behind a wall, but the + // passage leading to the room is off-screen) + leafstack[0] = viewleaf; + leafstackpos = 1; + while (leafstackpos) + { + leaf = leafstack[--leafstackpos]; + if (r_refdef.viewcache.world_leafvisible[leaf - model->brush.data_leafs]) + continue; + if (leaf->clusterindex < 0) + continue; + r_refdef.stats.world_leafs++; + r_refdef.viewcache.world_leafvisible[leaf - model->brush.data_leafs] = true; + // mark any surfaces bounding this leaf + if (leaf->numleafsurfaces) + for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) + r_refdef.viewcache.world_surfacevisible[*mark] = true; + // follow portals into other leafs + // the checks are: + // if viewer is behind portal (portal faces outward into the scene) + // and the portal polygon's bounding box is on the screen + // and the leaf has not been visited yet + // and the leaf is visible in the pvs + // (the first two checks won't cause as many cache misses as the leaf checks) + for (p = leaf->portals;p;p = p->next) + { + r_refdef.stats.world_portals++; + if (DotProduct(r_refdef.view.origin, p->plane.normal) < (p->plane.dist + 1) + && !r_refdef.viewcache.world_leafvisible[p->past - model->brush.data_leafs] + && CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, p->past->clusterindex) + && !R_CullBox(p->mins, p->maxs) + && leafstackpos < (int)(sizeof(leafstack) / sizeof(leafstack[0]))) + leafstack[leafstackpos++] = p->past; + } + } + } + } + + if (r_usesurfaceculling.integer) + { + int k = model->firstmodelsurface; + int l = k + model->nummodelsurfaces; + unsigned char *visible = r_refdef.viewcache.world_surfacevisible; + msurface_t *surfaces = model->data_surfaces; + msurface_t *surface; + for (;k < l;k++) + { + if (visible[k]) + { + surface = surfaces + k; + if (R_CullBox(surface->mins, surface->maxs)) + visible[k] = false; + } + } +} +} + +void R_Q1BSP_DrawSky(entity_render_t *ent) +{ + if (ent->model == NULL) + return; + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(true, true, false, false, false); + else + R_DrawModelSurfaces(ent, true, true, false, false, false); +} + +extern void R_Water_AddWaterPlane(msurface_t *surface, int entno); +void R_Q1BSP_DrawAddWaterPlanes(entity_render_t *ent) +{ + int i, j, n, flagsmask; + dp_model_t *model = ent->model; + msurface_t *surfaces; + if (model == NULL) + return; + + if (ent == r_refdef.scene.worldentity) + RSurf_ActiveWorldEntity(); + else + RSurf_ActiveModelEntity(ent, false, false, false); + + surfaces = model->data_surfaces; + flagsmask = MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA; + + // add visible surfaces to draw list + if (ent == r_refdef.scene.worldentity) + { + for (i = 0;i < model->nummodelsurfaces;i++) + { + j = model->sortedmodelsurfaces[i]; + if (r_refdef.viewcache.world_surfacevisible[j]) + if (surfaces[j].texture->basematerialflags & flagsmask) + R_Water_AddWaterPlane(surfaces + j, 0); + } + } + else + { + if(ent->entitynumber >= MAX_EDICTS) // && CL_VM_TransformView(ent->entitynumber - MAX_EDICTS, NULL, NULL, NULL)) + n = ent->entitynumber; + else + n = 0; + for (i = 0;i < model->nummodelsurfaces;i++) + { + j = model->sortedmodelsurfaces[i]; + if (surfaces[j].texture->basematerialflags & flagsmask) + R_Water_AddWaterPlane(surfaces + j, n); + } + } + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_Q1BSP_Draw(entity_render_t *ent) +{ + dp_model_t *model = ent->model; + if (model == NULL) + return; + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(false, true, false, false, false); + else + R_DrawModelSurfaces(ent, false, true, false, false, false); +} + +void R_Q1BSP_DrawDepth(entity_render_t *ent) +{ + dp_model_t *model = ent->model; + if (model == NULL) + return; + GL_ColorMask(0,0,0,0); + GL_Color(1,1,1,1); + GL_DepthTest(true); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); +// R_Mesh_ResetTextureState(); + R_SetupShader_DepthOrShadow(false); + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(false, false, true, false, false); + else + R_DrawModelSurfaces(ent, false, false, true, false, false); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); +} + +void R_Q1BSP_DrawDebug(entity_render_t *ent) +{ + if (ent->model == NULL) + return; + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(false, false, false, true, false); + else + R_DrawModelSurfaces(ent, false, false, false, true, false); +} + +void R_Q1BSP_DrawPrepass(entity_render_t *ent) +{ + dp_model_t *model = ent->model; + if (model == NULL) + return; + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(false, true, false, false, true); + else + R_DrawModelSurfaces(ent, false, true, false, false, true); +} + +typedef struct r_q1bsp_getlightinfo_s +{ + dp_model_t *model; + vec3_t relativelightorigin; + float lightradius; + int *outleaflist; + unsigned char *outleafpvs; + int outnumleafs; + unsigned char *visitingleafpvs; + int *outsurfacelist; + unsigned char *outsurfacepvs; + unsigned char *tempsurfacepvs; + unsigned char *outshadowtrispvs; + unsigned char *outlighttrispvs; + int outnumsurfaces; + vec3_t outmins; + vec3_t outmaxs; + vec3_t lightmins; + vec3_t lightmaxs; + const unsigned char *pvs; + qboolean svbsp_active; + qboolean svbsp_insertoccluder; + int numfrustumplanes; + const mplane_t *frustumplanes; +} +r_q1bsp_getlightinfo_t; + +#define GETLIGHTINFO_MAXNODESTACK 4096 + +static void R_Q1BSP_RecursiveGetLightInfo_BSP(r_q1bsp_getlightinfo_t *info, qboolean skipsurfaces) +{ + // nodestack + mnode_t *nodestack[GETLIGHTINFO_MAXNODESTACK]; + int nodestackpos = 0; + // node processing + mplane_t *plane; + mnode_t *node; + int sides; + // leaf processing + mleaf_t *leaf; + const msurface_t *surface; + const msurface_t *surfaces = info->model->data_surfaces; + int numleafsurfaces; + int leafsurfaceindex; + int surfaceindex; + int triangleindex, t; + int currentmaterialflags; + qboolean castshadow; + const int *e; + const vec_t *v[3]; + float v2[3][3]; + qboolean insidebox; + qboolean frontsidecasting = r_shadow_frontsidecasting.integer != 0; + qboolean svbspactive = info->svbsp_active; + qboolean svbspinsertoccluder = info->svbsp_insertoccluder; + const int *leafsurfaceindices; + qboolean addedtris; + int i; + mportal_t *portal; + static float points[128][3]; + // push the root node onto our nodestack + nodestack[nodestackpos++] = info->model->brush.data_nodes; + // we'll be done when the nodestack is empty + while (nodestackpos) + { + // get a node from the stack to process + node = nodestack[--nodestackpos]; + // is it a node or a leaf? + plane = node->plane; + if (plane) + { + // node +#if 0 + if (!BoxesOverlap(info->lightmins, info->lightmaxs, node->mins, node->maxs)) + continue; +#endif +#if 0 + if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(node->mins, node->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) + continue; +#endif + // axial planes can be processed much more quickly + if (plane->type < 3) + { + // axial plane + if (info->lightmins[plane->type] > plane->dist) + nodestack[nodestackpos++] = node->children[0]; + else if (info->lightmaxs[plane->type] < plane->dist) + nodestack[nodestackpos++] = node->children[1]; + else + { + // recurse front side first because the svbsp building prefers it + if (info->relativelightorigin[plane->type] >= plane->dist) + { + if (nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->children[0]; + nodestack[nodestackpos++] = node->children[1]; + } + else + { + if (nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->children[1]; + nodestack[nodestackpos++] = node->children[0]; + } + } + } + else + { + // sloped plane + sides = BoxOnPlaneSide(info->lightmins, info->lightmaxs, plane); + switch (sides) + { + default: + continue; // ERROR: NAN bounding box! + case 1: + nodestack[nodestackpos++] = node->children[0]; + break; + case 2: + nodestack[nodestackpos++] = node->children[1]; + break; + case 3: + // recurse front side first because the svbsp building prefers it + if (PlaneDist(info->relativelightorigin, plane) >= 0) + { + if (nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->children[0]; + nodestack[nodestackpos++] = node->children[1]; + } + else + { + if (nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->children[1]; + nodestack[nodestackpos++] = node->children[0]; + } + break; + } + } + } + else + { + // leaf + leaf = (mleaf_t *)node; +#if 1 + if (r_shadow_frontsidecasting.integer && info->pvs != NULL && !CHECKPVSBIT(info->pvs, leaf->clusterindex)) + continue; +#endif +#if 1 + if (!BoxesOverlap(info->lightmins, info->lightmaxs, leaf->mins, leaf->maxs)) + continue; +#endif +#if 1 + if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(leaf->mins, leaf->maxs, info->numfrustumplanes, info->frustumplanes)) + continue; +#endif + + if (svbspactive) + { + // we can occlusion test the leaf by checking if all of its portals + // are occluded (unless the light is in this leaf - but that was + // already handled by the caller) + for (portal = leaf->portals;portal;portal = portal->next) + { + for (i = 0;i < portal->numpoints;i++) + VectorCopy(portal->points[i].position, points[i]); + if (SVBSP_AddPolygon(&r_svbsp, portal->numpoints, points[0], false, NULL, NULL, 0) & 2) + break; + } + if (leaf->portals && portal == NULL) + continue; // no portals of this leaf visible + } + + // add this leaf to the reduced light bounds + info->outmins[0] = min(info->outmins[0], leaf->mins[0]); + info->outmins[1] = min(info->outmins[1], leaf->mins[1]); + info->outmins[2] = min(info->outmins[2], leaf->mins[2]); + info->outmaxs[0] = max(info->outmaxs[0], leaf->maxs[0]); + info->outmaxs[1] = max(info->outmaxs[1], leaf->maxs[1]); + info->outmaxs[2] = max(info->outmaxs[2], leaf->maxs[2]); + + // mark this leaf as being visible to the light + if (info->outleafpvs) + { + int leafindex = leaf - info->model->brush.data_leafs; + if (!CHECKPVSBIT(info->outleafpvs, leafindex)) + { + SETPVSBIT(info->outleafpvs, leafindex); + info->outleaflist[info->outnumleafs++] = leafindex; + } + } + + // when using BIH, we skip the surfaces here + if (skipsurfaces) + continue; + + // iterate the surfaces linked by this leaf and check their triangles + leafsurfaceindices = leaf->firstleafsurface; + numleafsurfaces = leaf->numleafsurfaces; + if (svbspinsertoccluder) + { + for (leafsurfaceindex = 0;leafsurfaceindex < numleafsurfaces;leafsurfaceindex++) + { + surfaceindex = leafsurfaceindices[leafsurfaceindex]; + if (CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) + continue; + SETPVSBIT(info->outsurfacepvs, surfaceindex); + surface = surfaces + surfaceindex; + if (!BoxesOverlap(info->lightmins, info->lightmaxs, surface->mins, surface->maxs)) + continue; + currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; + castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); + if (!castshadow) + continue; + insidebox = BoxInsideBox(surface->mins, surface->maxs, info->lightmins, info->lightmaxs); + for (triangleindex = 0, t = surface->num_firstshadowmeshtriangle, e = info->model->brush.shadowmesh->element3i + t * 3;triangleindex < surface->num_triangles;triangleindex++, t++, e += 3) + { + v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; + v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; + v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; + VectorCopy(v[0], v2[0]); + VectorCopy(v[1], v2[1]); + VectorCopy(v[2], v2[2]); + if (insidebox || TriangleOverlapsBox(v2[0], v2[1], v2[2], info->lightmins, info->lightmaxs)) + SVBSP_AddPolygon(&r_svbsp, 3, v2[0], true, NULL, NULL, 0); + } + } + } + else + { + for (leafsurfaceindex = 0;leafsurfaceindex < numleafsurfaces;leafsurfaceindex++) + { + surfaceindex = leafsurfaceindices[leafsurfaceindex]; + if (CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) + continue; + SETPVSBIT(info->outsurfacepvs, surfaceindex); + surface = surfaces + surfaceindex; + if (!BoxesOverlap(info->lightmins, info->lightmaxs, surface->mins, surface->maxs)) + continue; + addedtris = false; + currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; + castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); + insidebox = BoxInsideBox(surface->mins, surface->maxs, info->lightmins, info->lightmaxs); + for (triangleindex = 0, t = surface->num_firstshadowmeshtriangle, e = info->model->brush.shadowmesh->element3i + t * 3;triangleindex < surface->num_triangles;triangleindex++, t++, e += 3) + { + v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; + v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; + v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; + VectorCopy(v[0], v2[0]); + VectorCopy(v[1], v2[1]); + VectorCopy(v[2], v2[2]); + if (!insidebox && !TriangleOverlapsBox(v2[0], v2[1], v2[2], info->lightmins, info->lightmaxs)) + continue; + if (svbspactive && !(SVBSP_AddPolygon(&r_svbsp, 3, v2[0], false, NULL, NULL, 0) & 2)) + continue; + // we don't omit triangles from lighting even if they are + // backfacing, because when using shadowmapping they are often + // not fully occluded on the horizon of an edge + SETPVSBIT(info->outlighttrispvs, t); + addedtris = true; + if (castshadow) + { + if (currentmaterialflags & MATERIALFLAG_NOCULLFACE) + { + // if the material is double sided we + // can't cull by direction + SETPVSBIT(info->outshadowtrispvs, t); + } + else if (frontsidecasting) + { + // front side casting occludes backfaces, + // so they are completely useless as both + // casters and lit polygons + if (PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) + SETPVSBIT(info->outshadowtrispvs, t); + } + else + { + // back side casting does not occlude + // anything so we can't cull lit polygons + if (!PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) + SETPVSBIT(info->outshadowtrispvs, t); + } + } + } + if (addedtris) + info->outsurfacelist[info->outnumsurfaces++] = surfaceindex; + } + } + } + } +} + +static void R_Q1BSP_RecursiveGetLightInfo_BIH(r_q1bsp_getlightinfo_t *info, const bih_t *bih) +{ + bih_leaf_t *leaf; + bih_node_t *node; + int nodenum; + int axis; + int surfaceindex; + int t; + int nodeleafindex; + int currentmaterialflags; + qboolean castshadow; + msurface_t *surface; + const int *e; + const vec_t *v[3]; + float v2[3][3]; + int nodestack[GETLIGHTINFO_MAXNODESTACK]; + int nodestackpos = 0; + // note: because the BSP leafs are not in the BIH tree, the _BSP function + // must be called to mark leafs visible for entity culling... + // we start at the root node + nodestack[nodestackpos++] = bih->rootnode; + // we'll be done when the stack is empty + while (nodestackpos) + { + // pop one off the stack to process + nodenum = nodestack[--nodestackpos]; + // node + node = bih->nodes + nodenum; + if (node->type == BIH_UNORDERED) + { + for (nodeleafindex = 0;nodeleafindex < BIH_MAXUNORDEREDCHILDREN && node->children[nodeleafindex] >= 0;nodeleafindex++) + { + leaf = bih->leafs + node->children[nodeleafindex]; + if (leaf->type != BIH_RENDERTRIANGLE) + continue; +#if 1 + if (!BoxesOverlap(info->lightmins, info->lightmaxs, leaf->mins, leaf->maxs)) + continue; +#endif +#if 1 + if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(leaf->mins, leaf->maxs, info->numfrustumplanes, info->frustumplanes)) + continue; +#endif + surfaceindex = leaf->surfaceindex; + surface = info->model->data_surfaces + surfaceindex; + currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; + castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); + t = leaf->itemindex + surface->num_firstshadowmeshtriangle - surface->num_firsttriangle; + e = info->model->brush.shadowmesh->element3i + t * 3; + v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; + v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; + v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; + VectorCopy(v[0], v2[0]); + VectorCopy(v[1], v2[1]); + VectorCopy(v[2], v2[2]); + if (info->svbsp_insertoccluder) + { + if (castshadow) + SVBSP_AddPolygon(&r_svbsp, 3, v2[0], true, NULL, NULL, 0); + continue; + } + if (info->svbsp_active && !(SVBSP_AddPolygon(&r_svbsp, 3, v2[0], false, NULL, NULL, 0) & 2)) + continue; + // we don't occlude triangles from lighting even + // if they are backfacing, because when using + // shadowmapping they are often not fully occluded + // on the horizon of an edge + SETPVSBIT(info->outlighttrispvs, t); + if (castshadow) + { + if (currentmaterialflags & MATERIALFLAG_NOCULLFACE) + { + // if the material is double sided we + // can't cull by direction + SETPVSBIT(info->outshadowtrispvs, t); + } + else if (r_shadow_frontsidecasting.integer) + { + // front side casting occludes backfaces, + // so they are completely useless as both + // casters and lit polygons + if (PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) + SETPVSBIT(info->outshadowtrispvs, t); + } + else + { + // back side casting does not occlude + // anything so we can't cull lit polygons + if (!PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) + SETPVSBIT(info->outshadowtrispvs, t); + } + } + if (!CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) + { + SETPVSBIT(info->outsurfacepvs, surfaceindex); + info->outsurfacelist[info->outnumsurfaces++] = surfaceindex; + } + } + } + else + { + axis = node->type - BIH_SPLITX; +#if 0 + if (!BoxesOverlap(info->lightmins, info->lightmaxs, node->mins, node->maxs)) + continue; +#endif +#if 0 + if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(node->mins, node->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) + continue; +#endif + if (info->lightmins[axis] <= node->backmax) + { + if (info->lightmaxs[axis] >= node->frontmin && nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->front; + nodestack[nodestackpos++] = node->back; + continue; + } + else if (info->lightmaxs[axis] >= node->frontmin) + { + nodestack[nodestackpos++] = node->front; + continue; + } + else + continue; // light falls between children, nothing here + } + } +} + +static void R_Q1BSP_CallRecursiveGetLightInfo(r_q1bsp_getlightinfo_t *info, qboolean use_svbsp) +{ + extern cvar_t r_shadow_usebihculling; + if (use_svbsp) + { + float origin[3]; + VectorCopy(info->relativelightorigin, origin); + r_svbsp.maxnodes = max(r_svbsp.maxnodes, 1<<12); + r_svbsp.nodes = (svbsp_node_t*) R_FrameData_Alloc(r_svbsp.maxnodes * sizeof(svbsp_node_t)); + info->svbsp_active = true; + info->svbsp_insertoccluder = true; + for (;;) + { + SVBSP_Init(&r_svbsp, origin, r_svbsp.maxnodes, r_svbsp.nodes); + R_Q1BSP_RecursiveGetLightInfo_BSP(info, false); + // if that failed, retry with more nodes + if (r_svbsp.ranoutofnodes) + { + // an upper limit is imposed + if (r_svbsp.maxnodes >= 2<<22) + break; + r_svbsp.maxnodes *= 2; + r_svbsp.nodes = (svbsp_node_t*) R_FrameData_Alloc(r_svbsp.maxnodes * sizeof(svbsp_node_t)); + //Mem_Free(r_svbsp.nodes); + //r_svbsp.nodes = (svbsp_node_t*) Mem_Alloc(tempmempool, r_svbsp.maxnodes * sizeof(svbsp_node_t)); + } + else + break; + } + // now clear the visibility arrays because we need to redo it + info->outnumleafs = 0; + info->outnumsurfaces = 0; + memset(info->outleafpvs, 0, (info->model->brush.num_leafs + 7) >> 3); + memset(info->outsurfacepvs, 0, (info->model->nummodelsurfaces + 7) >> 3); + if (info->model->brush.shadowmesh) + memset(info->outshadowtrispvs, 0, (info->model->brush.shadowmesh->numtriangles + 7) >> 3); + else + memset(info->outshadowtrispvs, 0, (info->model->surfmesh.num_triangles + 7) >> 3); + memset(info->outlighttrispvs, 0, (info->model->surfmesh.num_triangles + 7) >> 3); + } + else + info->svbsp_active = false; + + // we HAVE to mark the leaf the light is in as lit, because portals are + // irrelevant to a leaf that the light source is inside of + // (and they are all facing away, too) + { + mnode_t *node = info->model->brush.data_nodes; + mleaf_t *leaf; + while (node->plane) + node = node->children[(node->plane->type < 3 ? info->relativelightorigin[node->plane->type] : DotProduct(info->relativelightorigin,node->plane->normal)) < node->plane->dist]; + leaf = (mleaf_t *)node; + info->outmins[0] = min(info->outmins[0], leaf->mins[0]); + info->outmins[1] = min(info->outmins[1], leaf->mins[1]); + info->outmins[2] = min(info->outmins[2], leaf->mins[2]); + info->outmaxs[0] = max(info->outmaxs[0], leaf->maxs[0]); + info->outmaxs[1] = max(info->outmaxs[1], leaf->maxs[1]); + info->outmaxs[2] = max(info->outmaxs[2], leaf->maxs[2]); + if (info->outleafpvs) + { + int leafindex = leaf - info->model->brush.data_leafs; + if (!CHECKPVSBIT(info->outleafpvs, leafindex)) + { + SETPVSBIT(info->outleafpvs, leafindex); + info->outleaflist[info->outnumleafs++] = leafindex; + } + } + } + + info->svbsp_insertoccluder = false; + // use BIH culling on single leaf maps (generally this only happens if running a model as a map), otherwise use BSP culling to make use of vis data + if (r_shadow_usebihculling.integer > 0 && (r_shadow_usebihculling.integer == 2 || info->model->brush.num_leafs == 1) && info->model->render_bih.leafs != NULL) + { + R_Q1BSP_RecursiveGetLightInfo_BSP(info, true); + R_Q1BSP_RecursiveGetLightInfo_BIH(info, &info->model->render_bih); + } + else + R_Q1BSP_RecursiveGetLightInfo_BSP(info, false); + // we're using temporary framedata memory, so this pointer will be invalid soon, clear it + r_svbsp.nodes = NULL; + if (developer_extra.integer && use_svbsp) + { + Con_DPrintf("GetLightInfo: svbsp built with %i nodes, polygon stats:\n", r_svbsp.numnodes); + Con_DPrintf("occluders: %i accepted, %i rejected, %i fragments accepted, %i fragments rejected.\n", r_svbsp.stat_occluders_accepted, r_svbsp.stat_occluders_rejected, r_svbsp.stat_occluders_fragments_accepted, r_svbsp.stat_occluders_fragments_rejected); + Con_DPrintf("queries : %i accepted, %i rejected, %i fragments accepted, %i fragments rejected.\n", r_svbsp.stat_queries_accepted, r_svbsp.stat_queries_rejected, r_svbsp.stat_queries_fragments_accepted, r_svbsp.stat_queries_fragments_rejected); + } +} + +static msurface_t *r_q1bsp_getlightinfo_surfaces; + +int R_Q1BSP_GetLightInfo_comparefunc(const void *ap, const void *bp) +{ + int a = *(int*)ap; + int b = *(int*)bp; + const msurface_t *as = r_q1bsp_getlightinfo_surfaces + a; + const msurface_t *bs = r_q1bsp_getlightinfo_surfaces + b; + if (as->texture < bs->texture) + return -1; + if (as->texture > bs->texture) + return 1; + return a - b; +} + +extern cvar_t r_shadow_sortsurfaces; + +void R_Q1BSP_GetLightInfo(entity_render_t *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes) +{ + r_q1bsp_getlightinfo_t info; + VectorCopy(relativelightorigin, info.relativelightorigin); + info.lightradius = lightradius; + info.lightmins[0] = info.relativelightorigin[0] - info.lightradius; + info.lightmins[1] = info.relativelightorigin[1] - info.lightradius; + info.lightmins[2] = info.relativelightorigin[2] - info.lightradius; + info.lightmaxs[0] = info.relativelightorigin[0] + info.lightradius; + info.lightmaxs[1] = info.relativelightorigin[1] + info.lightradius; + info.lightmaxs[2] = info.relativelightorigin[2] + info.lightradius; + if (ent->model == NULL) + { + VectorCopy(info.lightmins, outmins); + VectorCopy(info.lightmaxs, outmaxs); + *outnumleafspointer = 0; + *outnumsurfacespointer = 0; + return; + } + info.model = ent->model; + info.outleaflist = outleaflist; + info.outleafpvs = outleafpvs; + info.outnumleafs = 0; + info.visitingleafpvs = visitingleafpvs; + info.outsurfacelist = outsurfacelist; + info.outsurfacepvs = outsurfacepvs; + info.outshadowtrispvs = outshadowtrispvs; + info.outlighttrispvs = outlighttrispvs; + info.outnumsurfaces = 0; + info.numfrustumplanes = numfrustumplanes; + info.frustumplanes = frustumplanes; + VectorCopy(info.relativelightorigin, info.outmins); + VectorCopy(info.relativelightorigin, info.outmaxs); + memset(visitingleafpvs, 0, (info.model->brush.num_leafs + 7) >> 3); + memset(outleafpvs, 0, (info.model->brush.num_leafs + 7) >> 3); + memset(outsurfacepvs, 0, (info.model->nummodelsurfaces + 7) >> 3); + if (info.model->brush.shadowmesh) + memset(outshadowtrispvs, 0, (info.model->brush.shadowmesh->numtriangles + 7) >> 3); + else + memset(outshadowtrispvs, 0, (info.model->surfmesh.num_triangles + 7) >> 3); + memset(outlighttrispvs, 0, (info.model->surfmesh.num_triangles + 7) >> 3); + if (info.model->brush.GetPVS && r_shadow_frontsidecasting.integer) + info.pvs = info.model->brush.GetPVS(info.model, info.relativelightorigin); + else + info.pvs = NULL; + RSurf_ActiveWorldEntity(); + + if (r_shadow_frontsidecasting.integer && r_shadow_compilingrtlight && r_shadow_realtime_world_compileportalculling.integer && info.model->brush.data_portals) + { + // use portal recursion for exact light volume culling, and exact surface checking + Portal_Visibility(info.model, info.relativelightorigin, info.outleaflist, info.outleafpvs, &info.outnumleafs, info.outsurfacelist, info.outsurfacepvs, &info.outnumsurfaces, NULL, 0, true, info.lightmins, info.lightmaxs, info.outmins, info.outmaxs, info.outshadowtrispvs, info.outlighttrispvs, info.visitingleafpvs); + } + else if (r_shadow_frontsidecasting.integer && r_shadow_realtime_dlight_portalculling.integer && info.model->brush.data_portals) + { + // use portal recursion for exact light volume culling, but not the expensive exact surface checking + Portal_Visibility(info.model, info.relativelightorigin, info.outleaflist, info.outleafpvs, &info.outnumleafs, info.outsurfacelist, info.outsurfacepvs, &info.outnumsurfaces, NULL, 0, r_shadow_realtime_dlight_portalculling.integer >= 2, info.lightmins, info.lightmaxs, info.outmins, info.outmaxs, info.outshadowtrispvs, info.outlighttrispvs, info.visitingleafpvs); + } + else + { + // recurse the bsp tree, checking leafs and surfaces for visibility + // optionally using svbsp for exact culling of compiled lights + // (or if the user enables dlight svbsp culling, which is mostly for + // debugging not actual use) + R_Q1BSP_CallRecursiveGetLightInfo(&info, (r_shadow_compilingrtlight ? r_shadow_realtime_world_compilesvbsp.integer : r_shadow_realtime_dlight_svbspculling.integer) != 0); + } + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + + // limit combined leaf box to light boundaries + outmins[0] = max(info.outmins[0] - 1, info.lightmins[0]); + outmins[1] = max(info.outmins[1] - 1, info.lightmins[1]); + outmins[2] = max(info.outmins[2] - 1, info.lightmins[2]); + outmaxs[0] = min(info.outmaxs[0] + 1, info.lightmaxs[0]); + outmaxs[1] = min(info.outmaxs[1] + 1, info.lightmaxs[1]); + outmaxs[2] = min(info.outmaxs[2] + 1, info.lightmaxs[2]); + + *outnumleafspointer = info.outnumleafs; + *outnumsurfacespointer = info.outnumsurfaces; + + // now sort surfaces by texture for faster rendering + r_q1bsp_getlightinfo_surfaces = info.model->data_surfaces; + if (r_shadow_sortsurfaces.integer) + qsort(info.outsurfacelist, info.outnumsurfaces, sizeof(*info.outsurfacelist), R_Q1BSP_GetLightInfo_comparefunc); +} + +void R_Q1BSP_CompileShadowVolume(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist) +{ + dp_model_t *model = ent->model; + msurface_t *surface; + int surfacelistindex; + float projectdistance = relativelightdirection ? lightradius : lightradius + model->radius*2 + r_shadow_projectdistance.value; + // if triangle neighbors are disabled, shadowvolumes are disabled + if (!model->brush.shadowmesh->neighbor3i) + return; + r_shadow_compilingrtlight->static_meshchain_shadow_zfail = Mod_ShadowMesh_Begin(r_main_mempool, 32768, 32768, NULL, NULL, NULL, false, false, true); + R_Shadow_PrepareShadowMark(model->brush.shadowmesh->numtriangles); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + surface = model->data_surfaces + surfacelist[surfacelistindex]; + if (surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW) + continue; + R_Shadow_MarkVolumeFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, relativelightorigin, relativelightdirection, r_shadow_compilingrtlight->cullmins, r_shadow_compilingrtlight->cullmaxs, surface->mins, surface->maxs); + } + R_Shadow_VolumeFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, model->brush.shadowmesh->neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); + r_shadow_compilingrtlight->static_meshchain_shadow_zfail = Mod_ShadowMesh_Finish(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zfail, false, false, true); +} + +extern cvar_t r_polygonoffset_submodel_factor; +extern cvar_t r_polygonoffset_submodel_offset; +void R_Q1BSP_DrawShadowVolume(entity_render_t *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const vec3_t lightmins, const vec3_t lightmaxs) +{ + dp_model_t *model = ent->model; + const msurface_t *surface; + int modelsurfacelistindex; + float projectdistance = relativelightdirection ? lightradius : lightradius + model->radius*2 + r_shadow_projectdistance.value; + // check the box in modelspace, it was already checked in worldspace + if (!BoxesOverlap(model->normalmins, model->normalmaxs, lightmins, lightmaxs)) + return; + R_FrameData_SetMark(); + if (ent->model->brush.submodel) + GL_PolygonOffset(r_refdef.shadowpolygonfactor + r_polygonoffset_submodel_factor.value, r_refdef.shadowpolygonoffset + r_polygonoffset_submodel_offset.value); + if (model->brush.shadowmesh) + { + // if triangle neighbors are disabled, shadowvolumes are disabled + if (!model->brush.shadowmesh->neighbor3i) + return; + R_Shadow_PrepareShadowMark(model->brush.shadowmesh->numtriangles); + for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) + { + surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; + if (R_GetCurrentTexture(surface->texture)->currentmaterialflags & MATERIALFLAG_NOSHADOW) + continue; + R_Shadow_MarkVolumeFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, relativelightorigin, relativelightdirection, lightmins, lightmaxs, surface->mins, surface->maxs); + } + R_Shadow_VolumeFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, model->brush.shadowmesh->neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); + } + else + { + // if triangle neighbors are disabled, shadowvolumes are disabled + if (!model->surfmesh.data_neighbor3i) + return; + projectdistance = lightradius + model->radius*2; + R_Shadow_PrepareShadowMark(model->surfmesh.num_triangles); + // identify lit faces within the bounding box + for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) + { + surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_NOSHADOW) + continue; + R_Shadow_MarkVolumeFromBox(surface->num_firsttriangle, surface->num_triangles, rsurface.modelvertex3f, rsurface.modelelement3i, relativelightorigin, relativelightdirection, lightmins, lightmaxs, surface->mins, surface->maxs); + } + R_Shadow_VolumeFromList(model->surfmesh.num_vertices, model->surfmesh.num_triangles, rsurface.modelvertex3f, model->surfmesh.data_element3i, model->surfmesh.data_neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); + } + if (ent->model->brush.submodel) + GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset); + R_FrameData_ReturnToMark(); +} + +void R_Q1BSP_CompileShadowMap(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist) +{ + dp_model_t *model = ent->model; + msurface_t *surface; + int surfacelistindex; + int sidetotals[6] = { 0, 0, 0, 0, 0, 0 }, sidemasks = 0; + int i; + r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap = Mod_ShadowMesh_Begin(r_main_mempool, 32768, 32768, NULL, NULL, NULL, false, false, true); + R_Shadow_PrepareShadowSides(model->brush.shadowmesh->numtriangles); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + surface = model->data_surfaces + surfacelist[surfacelistindex]; + sidemasks |= R_Shadow_ChooseSidesFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, &r_shadow_compilingrtlight->matrix_worldtolight, relativelightorigin, relativelightdirection, r_shadow_compilingrtlight->cullmins, r_shadow_compilingrtlight->cullmaxs, surface->mins, surface->maxs, surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW ? NULL : sidetotals); + } + R_Shadow_ShadowMapFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, numshadowsides, sidetotals, shadowsides, shadowsideslist); + r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap = Mod_ShadowMesh_Finish(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap, false, false, true); + r_shadow_compilingrtlight->static_shadowmap_receivers &= sidemasks; + for(i = 0;i<6;i++) + if(!sidetotals[i]) + r_shadow_compilingrtlight->static_shadowmap_casters &= ~(1 << i); +} + +#define RSURF_MAX_BATCHSURFACES 8192 + +static const msurface_t *batchsurfacelist[RSURF_MAX_BATCHSURFACES]; + +void R_Q1BSP_DrawShadowMap(int side, entity_render_t *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs) +{ + dp_model_t *model = ent->model; + const msurface_t *surface; + int modelsurfacelistindex, batchnumsurfaces; + // check the box in modelspace, it was already checked in worldspace + if (!BoxesOverlap(model->normalmins, model->normalmaxs, lightmins, lightmaxs)) + return; + R_FrameData_SetMark(); + // identify lit faces within the bounding box + for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) + { + surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; + if (surfacesides && !(surfacesides[modelsurfacelistindex] && (1 << side))) + continue; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_NOSHADOW) + continue; + if (!BoxesOverlap(lightmins, lightmaxs, surface->mins, surface->maxs)) + continue; + r_refdef.stats.lights_dynamicshadowtriangles += surface->num_triangles; + r_refdef.stats.lights_shadowtriangles += surface->num_triangles; + batchsurfacelist[0] = surface; + batchnumsurfaces = 1; + while(++modelsurfacelistindex < modelnumsurfaces && batchnumsurfaces < RSURF_MAX_BATCHSURFACES) + { + surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; + if (surfacesides && !(surfacesides[modelsurfacelistindex] & (1 << side))) + continue; + if (surface->texture != batchsurfacelist[0]->texture) + break; + if (!BoxesOverlap(lightmins, lightmaxs, surface->mins, surface->maxs)) + continue; + r_refdef.stats.lights_dynamicshadowtriangles += surface->num_triangles; + r_refdef.stats.lights_shadowtriangles += surface->num_triangles; + batchsurfacelist[batchnumsurfaces++] = surface; + } + --modelsurfacelistindex; + GL_CullFace(rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE ? GL_NONE : r_refdef.view.cullface_back); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX, batchnumsurfaces, batchsurfacelist); + if (rsurface.batchvertex3fbuffer) + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer); + else + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer); + RSurf_DrawBatch(); + } + R_FrameData_ReturnToMark(); +} + +#define BATCHSIZE 1024 + +static void R_Q1BSP_DrawLight_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i, j, endsurface; + texture_t *t; + const msurface_t *surface; + R_FrameData_SetMark(); + // note: in practice this never actually receives batches + R_Shadow_RenderMode_Begin(); + R_Shadow_RenderMode_ActiveLight(rtlight); + R_Shadow_RenderMode_Lighting(false, true, false); + R_Shadow_SetupEntityLight(ent); + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + surface = rsurface.modelsurfaces + surfacelist[i]; + t = surface->texture; + rsurface.texture = R_GetCurrentTexture(t); + endsurface = min(j + BATCHSIZE, numsurfaces); + for (j = i;j < endsurface;j++) + { + surface = rsurface.modelsurfaces + surfacelist[j]; + if (t != surface->texture) + break; + R_Shadow_RenderLighting(1, &surface); + } + } + R_Shadow_RenderMode_End(); + R_FrameData_ReturnToMark(); +} + +extern qboolean r_shadow_usingdeferredprepass; +void R_Q1BSP_DrawLight(entity_render_t *ent, int numsurfaces, const int *surfacelist, const unsigned char *lighttrispvs) +{ + dp_model_t *model = ent->model; + const msurface_t *surface; + int i, k, kend, l, endsurface, batchnumsurfaces, texturenumsurfaces; + const msurface_t **texturesurfacelist; + texture_t *tex; + CHECKGLERROR + R_FrameData_SetMark(); + // this is a double loop because non-visible surface skipping has to be + // fast, and even if this is not the world model (and hence no visibility + // checking) the input surface list and batch buffer are different formats + // so some processing is necessary. (luckily models have few surfaces) + for (i = 0;i < numsurfaces;) + { + batchnumsurfaces = 0; + endsurface = min(i + RSURF_MAX_BATCHSURFACES, numsurfaces); + if (ent == r_refdef.scene.worldentity) + { + for (;i < endsurface;i++) + if (r_refdef.viewcache.world_surfacevisible[surfacelist[i]]) + batchsurfacelist[batchnumsurfaces++] = model->data_surfaces + surfacelist[i]; + } + else + { + for (;i < endsurface;i++) + batchsurfacelist[batchnumsurfaces++] = model->data_surfaces + surfacelist[i]; + } + if (!batchnumsurfaces) + continue; + for (k = 0;k < batchnumsurfaces;k = kend) + { + surface = batchsurfacelist[k]; + tex = surface->texture; + rsurface.texture = R_GetCurrentTexture(tex); + // gather surfaces into a batch range + for (kend = k;kend < batchnumsurfaces && tex == batchsurfacelist[kend]->texture;kend++) + ; + // now figure out what to do with this particular range of surfaces + // VorteX: added MATERIALFLAG_NORTLIGHT + if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WALL + MATERIALFLAG_NORTLIGHT)) != MATERIALFLAG_WALL) + continue; + if (r_waterstate.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA))) + continue; + if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) + { + vec3_t tempcenter, center; + for (l = k;l < kend;l++) + { + surface = batchsurfacelist[l]; + tempcenter[0] = (surface->mins[0] + surface->maxs[0]) * 0.5f; + tempcenter[1] = (surface->mins[1] + surface->maxs[1]) * 0.5f; + tempcenter[2] = (surface->mins[2] + surface->maxs[2]) * 0.5f; + Matrix4x4_Transform(&rsurface.matrix, tempcenter, center); + R_MeshQueue_AddTransparent(rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST ? r_refdef.view.origin : center, R_Q1BSP_DrawLight_TransparentCallback, ent, surface - rsurface.modelsurfaces, rsurface.rtlight); + } + continue; + } + if (r_shadow_usingdeferredprepass) + continue; + texturenumsurfaces = kend - k; + texturesurfacelist = batchsurfacelist + k; + R_Shadow_RenderLighting(texturenumsurfaces, texturesurfacelist); + } + } + R_FrameData_ReturnToMark(); +} + +//Made by [515] +void R_ReplaceWorldTexture (void) +{ + dp_model_t *m; + texture_t *t; + int i; + const char *r, *newt; + skinframe_t *skinframe; + if (!r_refdef.scene.worldmodel) + { + Con_Printf("There is no worldmodel\n"); + return; + } + m = r_refdef.scene.worldmodel; + + if(Cmd_Argc() < 2) + { + Con_Print("r_replacemaptexture - replaces texture\n"); + Con_Print("r_replacemaptexture - switch back to default texture\n"); + return; + } + if(!cl.islocalgame || !cl.worldmodel) + { + Con_Print("This command works only in singleplayer\n"); + return; + } + r = Cmd_Argv(1); + newt = Cmd_Argv(2); + if(!newt[0]) + newt = r; + for(i=0,t=m->data_textures;inum_textures;i++,t++) + { + if(/*t->width && !strcasecmp(t->name, r)*/ matchpattern( t->name, r, true ) ) + { + if ((skinframe = R_SkinFrame_LoadExternal(newt, TEXF_MIPMAP | TEXF_ALPHA | TEXF_PICMIP, true))) + { +// t->skinframes[0] = skinframe; + t->currentskinframe = skinframe; + t->currentskinframe = skinframe; + Con_Printf("%s replaced with %s\n", r, newt); + } + else + { + Con_Printf("%s was not found\n", newt); + return; + } + } + } +} + +//Made by [515] +void R_ListWorldTextures (void) +{ + dp_model_t *m; + texture_t *t; + int i; + if (!r_refdef.scene.worldmodel) + { + Con_Printf("There is no worldmodel\n"); + return; + } + m = r_refdef.scene.worldmodel; + + Con_Print("Worldmodel textures :\n"); + for(i=0,t=m->data_textures;inum_textures;i++,t++) + if (t->numskinframes) + Con_Printf("%s\n", t->name); +} + +#if 0 +static void gl_surf_start(void) +{ +} + +static void gl_surf_shutdown(void) +{ +} + +static void gl_surf_newmap(void) +{ +} +#endif + +void GL_Surf_Init(void) +{ + + Cvar_RegisterVariable(&r_ambient); + Cvar_RegisterVariable(&r_lockpvs); + Cvar_RegisterVariable(&r_lockvisibility); + Cvar_RegisterVariable(&r_useportalculling); + Cvar_RegisterVariable(&r_usesurfaceculling); + Cvar_RegisterVariable(&r_q3bsp_renderskydepth); + + Cmd_AddCommand ("r_replacemaptexture", R_ReplaceWorldTexture, "override a map texture for testing purposes"); + Cmd_AddCommand ("r_listmaptextures", R_ListWorldTextures, "list all textures used by the current map"); + + //R_RegisterModule("GL_Surf", gl_surf_start, gl_surf_shutdown, gl_surf_newmap); +} + diff --git a/misc/source/darkplaces-src/gl_textures.c b/misc/source/darkplaces-src/gl_textures.c new file mode 100644 index 00000000..edefc5a6 --- /dev/null +++ b/misc/source/darkplaces-src/gl_textures.c @@ -0,0 +1,2615 @@ + +#include "quakedef.h" +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +#endif +#include "image.h" +#include "jpeg.h" +#include "image_png.h" +#include "intoverflow.h" +#include "dpsoftrast.h" + +cvar_t gl_max_size = {CVAR_SAVE, "gl_max_size", "2048", "maximum allowed texture size, can be used to reduce video memory usage, limited by hardware capabilities (typically 2048, 4096, or 8192)"}; +cvar_t gl_max_lightmapsize = {CVAR_SAVE, "gl_max_lightmapsize", "1024", "maximum allowed texture size for lightmap textures, use larger values to improve rendering speed, as long as there is enough video memory available (setting it too high for the hardware will cause very bad performance)"}; +cvar_t gl_picmip = {CVAR_SAVE, "gl_picmip", "0", "reduces resolution of textures by powers of 2, for example 1 will halve width/height, reducing texture memory usage by 75%"}; +cvar_t gl_picmip_world = {CVAR_SAVE, "gl_picmip_world", "0", "extra picmip level for world textures (may be negative, which will then reduce gl_picmip for these)"}; +cvar_t r_picmipworld = {CVAR_SAVE, "r_picmipworld", "1", "whether gl_picmip shall apply to world textures too (setting this to 0 is a shorthand for gl_picmip_world -9999999)"}; +cvar_t gl_picmip_sprites = {CVAR_SAVE, "gl_picmip_sprites", "0", "extra picmip level for sprite textures (may be negative, which will then reduce gl_picmip for these)"}; +cvar_t r_picmipsprites = {CVAR_SAVE, "r_picmipsprites", "1", "make gl_picmip affect sprites too (saves some graphics memory in sprite heavy games) (setting this to 0 is a shorthand for gl_picmip_sprites -9999999)"}; +cvar_t gl_picmip_other = {CVAR_SAVE, "gl_picmip_other", "0", "extra picmip level for other textures (may be negative, which will then reduce gl_picmip for these)"}; +cvar_t r_lerpimages = {CVAR_SAVE, "r_lerpimages", "1", "bilinear filters images when scaling them up to power of 2 size (mode 1), looks better than glquake (mode 0)"}; +cvar_t gl_texture_anisotropy = {CVAR_SAVE, "gl_texture_anisotropy", "1", "anisotropic filtering quality (if supported by hardware), 1 sample (no anisotropy) and 8 sample (8 tap anisotropy) are recommended values"}; +cvar_t gl_texturecompression = {CVAR_SAVE, "gl_texturecompression", "0", "whether to compress textures, a value of 0 disables compression (even if the individual cvars are 1), 1 enables fast (low quality) compression at startup, 2 enables slow (high quality) compression at startup"}; +cvar_t gl_texturecompression_color = {CVAR_SAVE, "gl_texturecompression_color", "1", "whether to compress colormap (diffuse) textures"}; +cvar_t gl_texturecompression_normal = {CVAR_SAVE, "gl_texturecompression_normal", "0", "whether to compress normalmap (normalmap) textures"}; +cvar_t gl_texturecompression_gloss = {CVAR_SAVE, "gl_texturecompression_gloss", "1", "whether to compress glossmap (specular) textures"}; +cvar_t gl_texturecompression_glow = {CVAR_SAVE, "gl_texturecompression_glow", "1", "whether to compress glowmap (luma) textures"}; +cvar_t gl_texturecompression_2d = {CVAR_SAVE, "gl_texturecompression_2d", "0", "whether to compress 2d (hud/menu) textures other than the font"}; +cvar_t gl_texturecompression_q3bsplightmaps = {CVAR_SAVE, "gl_texturecompression_q3bsplightmaps", "0", "whether to compress lightmaps in q3bsp format levels"}; +cvar_t gl_texturecompression_q3bspdeluxemaps = {CVAR_SAVE, "gl_texturecompression_q3bspdeluxemaps", "0", "whether to compress deluxemaps in q3bsp format levels (only levels compiled with q3map2 -deluxe have these)"}; +cvar_t gl_texturecompression_sky = {CVAR_SAVE, "gl_texturecompression_sky", "0", "whether to compress sky textures"}; +cvar_t gl_texturecompression_lightcubemaps = {CVAR_SAVE, "gl_texturecompression_lightcubemaps", "1", "whether to compress light cubemaps (spotlights and other light projection images)"}; +cvar_t gl_texturecompression_reflectmask = {CVAR_SAVE, "gl_texturecompression_reflectmask", "1", "whether to compress reflection cubemap masks (mask of which areas of the texture should reflect the generic shiny cubemap)"}; +cvar_t gl_texturecompression_sprites = {CVAR_SAVE, "gl_texturecompression_sprites", "1", "whether to compress sprites"}; +cvar_t gl_nopartialtextureupdates = {CVAR_SAVE, "gl_nopartialtextureupdates", "0", "use alternate path for dynamic lightmap updates that avoids a possibly slow code path in the driver"}; +cvar_t r_texture_dds_load_alphamode = {0, "r_texture_dds_load_alphamode", "1", "0: trust DDPF_ALPHAPIXELS flag, 1: texture format and brute force search if ambiguous, 2: texture format only"}; +cvar_t r_texture_dds_load_logfailure = {0, "r_texture_dds_load_logfailure", "0", "log missing DDS textures to ddstexturefailures.log"}; +cvar_t r_texture_dds_swdecode = {0, "r_texture_dds_swdecode", "0", "0: don't software decode DDS, 1: software decode DDS if unsupported, 2: always software decode DDS"}; + +qboolean gl_filter_force = false; +int gl_filter_min = GL_LINEAR_MIPMAP_LINEAR; +int gl_filter_mag = GL_LINEAR; +DPSOFTRAST_TEXTURE_FILTER dpsoftrast_filter_mipmap = DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE; +DPSOFTRAST_TEXTURE_FILTER dpsoftrast_filter_nomipmap = DPSOFTRAST_TEXTURE_FILTER_LINEAR; + +#ifdef SUPPORTD3D +int d3d_filter_flatmin = D3DTEXF_LINEAR; +int d3d_filter_flatmag = D3DTEXF_LINEAR; +int d3d_filter_flatmix = D3DTEXF_POINT; +int d3d_filter_mipmin = D3DTEXF_LINEAR; +int d3d_filter_mipmag = D3DTEXF_LINEAR; +int d3d_filter_mipmix = D3DTEXF_LINEAR; +int d3d_filter_nomip = false; +#endif + + +static mempool_t *texturemempool; +static memexpandablearray_t texturearray; + +// note: this must not conflict with TEXF_ flags in r_textures.h +// bitmask for mismatch checking +#define GLTEXF_IMPORTANTBITS (0) +// dynamic texture (treat texnum == 0 differently) +#define GLTEXF_DYNAMIC 0x00080000 + +typedef struct textypeinfo_s +{ + const char *name; + textype_t textype; + int inputbytesperpixel; + int internalbytesperpixel; + float glinternalbytesperpixel; + int glinternalformat; + int glformat; + int gltype; +} +textypeinfo_t; + +// framebuffer texture formats +static textypeinfo_t textype_shadowmap16 = {"shadowmap16", TEXTYPE_SHADOWMAP , 2, 2, 2.0f, GL_DEPTH_COMPONENT16_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_shadowmap24 = {"shadowmap24", TEXTYPE_SHADOWMAP , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; +static textypeinfo_t textype_colorbuffer = {"colorbuffer", TEXTYPE_COLORBUFFER , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_colorbuffer16f = {"colorbuffer16f", TEXTYPE_COLORBUFFER16F, 8, 8, 8.0f, GL_RGBA16F_ARB , GL_RGBA , GL_FLOAT }; +static textypeinfo_t textype_colorbuffer32f = {"colorbuffer32f", TEXTYPE_COLORBUFFER32F, 16, 16, 16.0f, GL_RGBA32F_ARB , GL_RGBA , GL_FLOAT }; + +// image formats: +static textypeinfo_t textype_alpha = {"alpha", TEXTYPE_ALPHA , 1, 4, 4.0f, GL_ALPHA , GL_ALPHA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_palette = {"palette", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGB , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_palette_alpha = {"palette_alpha", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba = {"rgba", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGB , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba_alpha = {"rgba_alpha", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba_compress = {"rgba_compress", TEXTYPE_RGBA , 4, 4, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba_alpha_compress = {"rgba_alpha_compress", TEXTYPE_RGBA , 4, 4, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra = {"bgra", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGB , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra_alpha = {"bgra_alpha", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra_compress = {"bgra_compress", TEXTYPE_BGRA , 4, 4, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra_alpha_compress = {"bgra_alpha_compress", TEXTYPE_BGRA , 4, 4, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_dxt1 = {"dxt1", TEXTYPE_DXT1 , 4, 0, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt1a = {"dxt1a", TEXTYPE_DXT1A , 4, 0, 0.5f, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt3 = {"dxt3", TEXTYPE_DXT3 , 4, 0, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt5 = {"dxt5", TEXTYPE_DXT5 , 4, 0, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , 0 , 0 }; +static textypeinfo_t textype_sRGB_palette = {"sRGB_palette", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_SRGB_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_palette_alpha = {"sRGB_palette_alpha", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_rgba = {"sRGB_rgba", TEXTYPE_RGBA , 4, 4, 4.0f, GL_SRGB_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_rgba_alpha = {"sRGB_rgba_alpha", TEXTYPE_RGBA , 4, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_rgba_compress = {"sRGB_rgba_compress", TEXTYPE_RGBA , 4, 4, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_rgba_alpha_compress = {"sRGB_rgba_alpha_compress", TEXTYPE_RGBA , 4, 4, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_bgra = {"sRGB_bgra", TEXTYPE_BGRA , 4, 4, 4.0f, GL_SRGB_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_bgra_alpha = {"sRGB_bgra_alpha", TEXTYPE_BGRA , 4, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_bgra_compress = {"sRGB_bgra_compress", TEXTYPE_BGRA , 4, 4, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_bgra_alpha_compress = {"sRGB_bgra_alpha_compress", TEXTYPE_BGRA , 4, 4, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_dxt1 = {"sRGB_dxt1", TEXTYPE_DXT1 , 4, 0, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , 0 , 0 }; +static textypeinfo_t textype_sRGB_dxt1a = {"sRGB_dxt1a", TEXTYPE_DXT1A , 4, 0, 0.5f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, 0 , 0 }; +static textypeinfo_t textype_sRGB_dxt3 = {"sRGB_dxt3", TEXTYPE_DXT3 , 4, 0, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, 0 , 0 }; +static textypeinfo_t textype_sRGB_dxt5 = {"sRGB_dxt5", TEXTYPE_DXT5 , 4, 0, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, 0 , 0 }; + +typedef enum gltexturetype_e +{ + GLTEXTURETYPE_2D, + GLTEXTURETYPE_3D, + GLTEXTURETYPE_CUBEMAP, + GLTEXTURETYPE_TOTAL +} +gltexturetype_t; + +static int gltexturetypeenums[GLTEXTURETYPE_TOTAL] = {GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP_ARB}; +static int gltexturetypedimensions[GLTEXTURETYPE_TOTAL] = {2, 3, 2}; +static int cubemapside[6] = +{ + GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, + GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB +}; + +typedef struct gltexture_s +{ + // this portion of the struct is exposed to the R_GetTexture macro for + // speed reasons, must be identical in rtexture_t! + int texnum; // GL texture slot number + qboolean dirty; // indicates that R_RealGetTexture should be called + int gltexturetypeenum; // used by R_Mesh_TexBind + // d3d stuff the backend needs + void *d3dtexture; +#ifdef SUPPORTD3D + qboolean d3disdepthsurface; // for depth/stencil surfaces + int d3dformat; + int d3dusage; + int d3dpool; + int d3daddressu; + int d3daddressv; + int d3daddressw; + int d3dmagfilter; + int d3dminfilter; + int d3dmipfilter; + int d3dmaxmiplevelfilter; + int d3dmipmaplodbias; + int d3dmaxmiplevel; +#endif + + // dynamic texture stuff [11/22/2007 Black] + updatecallback_t updatecallback; + void *updatacallback_data; + // --- [11/22/2007 Black] + + // stores backup copy of texture for deferred texture updates (gl_nopartialtextureupdates cvar) + unsigned char *bufferpixels; + qboolean buffermodified; + + // pointer to texturepool (check this to see if the texture is allocated) + struct gltexturepool_s *pool; + // pointer to next texture in texturepool chain + struct gltexture_s *chain; + // name of the texture (this might be removed someday), no duplicates + char identifier[MAX_QPATH + 32]; + // original data size in *inputtexels + int inputwidth, inputheight, inputdepth; + // copy of the original texture(s) supplied to the upload function, for + // delayed uploads (non-precached) + unsigned char *inputtexels; + // original data size in *inputtexels + int inputdatasize; + // flags supplied to the LoadTexture function + // (might be altered to remove TEXF_ALPHA), and GLTEXF_ private flags + int flags; + // picmip level + int miplevel; + // pointer to one of the textype_ structs + textypeinfo_t *textype; + // one of the GLTEXTURETYPE_ values + int texturetype; + // palette if the texture is TEXTYPE_PALETTE + const unsigned int *palette; + // actual stored texture size after gl_picmip and gl_max_size are applied + // (power of 2 if vid.support.arb_texture_non_power_of_two is not supported) + int tilewidth, tileheight, tiledepth; + // 1 or 6 depending on texturetype + int sides; + // how many mipmap levels in this texture + int miplevels; + // bytes per pixel + int bytesperpixel; + // GL_RGB or GL_RGBA or GL_DEPTH_COMPONENT + int glformat; + // 3 or 4 + int glinternalformat; + // GL_UNSIGNED_BYTE or GL_UNSIGNED_INT or GL_UNSIGNED_SHORT or GL_FLOAT + int gltype; +} +gltexture_t; + +#define TEXTUREPOOL_SENTINEL 0xC0DEDBAD + +typedef struct gltexturepool_s +{ + unsigned int sentinel; + struct gltexture_s *gltchain; + struct gltexturepool_s *next; +} +gltexturepool_t; + +static gltexturepool_t *gltexturepoolchain = NULL; + +static unsigned char *resizebuffer = NULL, *colorconvertbuffer; +static int resizebuffersize = 0; +static const unsigned char *texturebuffer; + +static textypeinfo_t *R_GetTexTypeInfo(textype_t textype, int flags) +{ + switch(textype) + { + case TEXTYPE_DXT1: return &textype_dxt1; + case TEXTYPE_DXT1A: return &textype_dxt1a; + case TEXTYPE_DXT3: return &textype_dxt3; + case TEXTYPE_DXT5: return &textype_dxt5; + case TEXTYPE_PALETTE: return (flags & TEXF_ALPHA) ? &textype_palette_alpha : &textype_palette; + case TEXTYPE_RGBA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_rgba_alpha_compress : &textype_rgba_compress) : ((flags & TEXF_ALPHA) ? &textype_rgba_alpha : &textype_rgba); + case TEXTYPE_BGRA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_bgra_alpha_compress : &textype_bgra_compress) : ((flags & TEXF_ALPHA) ? &textype_bgra_alpha : &textype_bgra); + case TEXTYPE_ALPHA: return &textype_alpha; + case TEXTYPE_SHADOWMAP: return (flags & TEXF_LOWPRECISION) ? &textype_shadowmap16 : &textype_shadowmap24; + case TEXTYPE_COLORBUFFER: return &textype_colorbuffer; + case TEXTYPE_COLORBUFFER16F: return &textype_colorbuffer16f; + case TEXTYPE_COLORBUFFER32F: return &textype_colorbuffer32f; + case TEXTYPE_SRGB_DXT1: return &textype_sRGB_dxt1; + case TEXTYPE_SRGB_DXT1A: return &textype_sRGB_dxt1a; + case TEXTYPE_SRGB_DXT3: return &textype_sRGB_dxt3; + case TEXTYPE_SRGB_DXT5: return &textype_sRGB_dxt5; + case TEXTYPE_SRGB_PALETTE: return (flags & TEXF_ALPHA) ? &textype_sRGB_palette_alpha : &textype_sRGB_palette; + case TEXTYPE_SRGB_RGBA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_sRGB_rgba_alpha_compress : &textype_sRGB_rgba_compress) : ((flags & TEXF_ALPHA) ? &textype_sRGB_rgba_alpha : &textype_sRGB_rgba); + case TEXTYPE_SRGB_BGRA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_sRGB_bgra_alpha_compress : &textype_sRGB_bgra_compress) : ((flags & TEXF_ALPHA) ? &textype_sRGB_bgra_alpha : &textype_sRGB_bgra); + default: + Host_Error("R_GetTexTypeInfo: unknown texture format"); + break; + } + return NULL; +} + +// dynamic texture code [11/22/2007 Black] +void R_MarkDirtyTexture(rtexture_t *rt) { + gltexture_t *glt = (gltexture_t*) rt; + if( !glt ) { + return; + } + + // dont do anything if the texture is already dirty (and make sure this *is* a dynamic texture after all!) + if (glt->flags & GLTEXF_DYNAMIC) + { + // mark it as dirty, so R_RealGetTexture gets called + glt->dirty = true; + } +} + +void R_MakeTextureDynamic(rtexture_t *rt, updatecallback_t updatecallback, void *data) { + gltexture_t *glt = (gltexture_t*) rt; + if( !glt ) { + return; + } + + glt->flags |= GLTEXF_DYNAMIC; + glt->updatecallback = updatecallback; + glt->updatacallback_data = data; +} + +static void R_UpdateDynamicTexture(gltexture_t *glt) { + glt->dirty = false; + if( glt->updatecallback ) { + glt->updatecallback( (rtexture_t*) glt, glt->updatacallback_data ); + } +} + +void R_PurgeTexture(rtexture_t *rt) +{ + if(rt && !(((gltexture_t*) rt)->flags & TEXF_PERSISTENT)) { + R_FreeTexture(rt); + } +} + +void R_FreeTexture(rtexture_t *rt) +{ + gltexture_t *glt, **gltpointer; + + glt = (gltexture_t *)rt; + if (glt == NULL) + Host_Error("R_FreeTexture: texture == NULL"); + + for (gltpointer = &glt->pool->gltchain;*gltpointer && *gltpointer != glt;gltpointer = &(*gltpointer)->chain); + if (*gltpointer == glt) + *gltpointer = glt->chain; + else + Host_Error("R_FreeTexture: texture \"%s\" not linked in pool", glt->identifier); + + R_Mesh_ClearBindingsForTexture(glt->texnum); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (glt->texnum) + { + CHECKGLERROR + qglDeleteTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (glt->d3disdepthsurface) + IDirect3DSurface9_Release((IDirect3DSurface9 *)glt->d3dtexture); + else if (glt->tiledepth > 1) + IDirect3DVolumeTexture9_Release((IDirect3DVolumeTexture9 *)glt->d3dtexture); + else if (glt->sides == 6) + IDirect3DCubeTexture9_Release((IDirect3DCubeTexture9 *)glt->d3dtexture); + else + IDirect3DTexture9_Release((IDirect3DTexture9 *)glt->d3dtexture); + glt->d3dtexture = NULL; +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (glt->texnum) + DPSOFTRAST_Texture_Free(glt->texnum); + break; + } + + if (glt->inputtexels) + Mem_Free(glt->inputtexels); + Mem_ExpandableArray_FreeRecord(&texturearray, glt); +} + +rtexturepool_t *R_AllocTexturePool(void) +{ + gltexturepool_t *pool; + if (texturemempool == NULL) + return NULL; + pool = (gltexturepool_t *)Mem_Alloc(texturemempool, sizeof(gltexturepool_t)); + if (pool == NULL) + return NULL; + pool->next = gltexturepoolchain; + gltexturepoolchain = pool; + pool->sentinel = TEXTUREPOOL_SENTINEL; + return (rtexturepool_t *)pool; +} + +void R_FreeTexturePool(rtexturepool_t **rtexturepool) +{ + gltexturepool_t *pool, **poolpointer; + if (rtexturepool == NULL) + return; + if (*rtexturepool == NULL) + return; + pool = (gltexturepool_t *)(*rtexturepool); + *rtexturepool = NULL; + if (pool->sentinel != TEXTUREPOOL_SENTINEL) + Host_Error("R_FreeTexturePool: pool already freed"); + for (poolpointer = &gltexturepoolchain;*poolpointer && *poolpointer != pool;poolpointer = &(*poolpointer)->next); + if (*poolpointer == pool) + *poolpointer = pool->next; + else + Host_Error("R_FreeTexturePool: pool not linked"); + while (pool->gltchain) + R_FreeTexture((rtexture_t *)pool->gltchain); + Mem_Free(pool); +} + + +typedef struct glmode_s +{ + const char *name; + int minification, magnification; + DPSOFTRAST_TEXTURE_FILTER dpsoftrastfilter_mipmap, dpsoftrastfilter_nomipmap; +} +glmode_t; + +static glmode_t modes[6] = +{ + {"GL_NEAREST", GL_NEAREST, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_LINEAR} +}; + +#ifdef SUPPORTD3D +typedef struct d3dmode_s +{ + const char *name; + int m1, m2; +} +d3dmode_t; + +static d3dmode_t d3dmodes[6] = +{ + {"GL_NEAREST", D3DTEXF_POINT, D3DTEXF_POINT}, + {"GL_LINEAR", D3DTEXF_LINEAR, D3DTEXF_POINT}, + {"GL_NEAREST_MIPMAP_NEAREST", D3DTEXF_POINT, D3DTEXF_POINT}, + {"GL_LINEAR_MIPMAP_NEAREST", D3DTEXF_LINEAR, D3DTEXF_POINT}, + {"GL_NEAREST_MIPMAP_LINEAR", D3DTEXF_POINT, D3DTEXF_LINEAR}, + {"GL_LINEAR_MIPMAP_LINEAR", D3DTEXF_LINEAR, D3DTEXF_LINEAR} +}; +#endif + +static void GL_TextureMode_f (void) +{ + int i; + GLint oldbindtexnum; + gltexture_t *glt; + gltexturepool_t *pool; + + if (Cmd_Argc() == 1) + { + Con_Printf("Texture mode is %sforced\n", gl_filter_force ? "" : "not "); + for (i = 0;i < 6;i++) + { + if (gl_filter_min == modes[i].minification) + { + Con_Printf("%s\n", modes[i].name); + return; + } + } + Con_Print("current filter is unknown???\n"); + return; + } + + for (i = 0;i < (int)(sizeof(modes)/sizeof(*modes));i++) + if (!strcasecmp (modes[i].name, Cmd_Argv(1) ) ) + break; + if (i == 6) + { + Con_Print("bad filter name\n"); + return; + } + + gl_filter_min = modes[i].minification; + gl_filter_mag = modes[i].magnification; + gl_filter_force = ((Cmd_Argc() > 2) && !strcasecmp(Cmd_Argv(2), "force")); + + dpsoftrast_filter_mipmap = modes[i].dpsoftrastfilter_mipmap; + dpsoftrast_filter_nomipmap = modes[i].dpsoftrastfilter_nomipmap; + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // change all the existing mipmap texture objects + // FIXME: force renderer(/client/something?) restart instead? + CHECKGLERROR + GL_ActiveTexture(0); + for (pool = gltexturepoolchain;pool;pool = pool->next) + { + for (glt = pool->gltchain;glt;glt = glt->chain) + { + // only update already uploaded images + if (glt->texnum && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) + { + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + if (glt->flags & TEXF_MIPMAP) + { + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MIN_FILTER, gl_filter_min);CHECKGLERROR + } + else + { + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MIN_FILTER, gl_filter_mag);CHECKGLERROR + } + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAG_FILTER, gl_filter_mag);CHECKGLERROR + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + } + } + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + d3d_filter_flatmin = d3dmodes[i].m1; + d3d_filter_flatmag = d3dmodes[i].m1; + d3d_filter_flatmix = D3DTEXF_POINT; + d3d_filter_mipmin = d3dmodes[i].m1; + d3d_filter_mipmag = d3dmodes[i].m1; + d3d_filter_mipmix = d3dmodes[i].m2; + d3d_filter_nomip = i < 2; + if (gl_texture_anisotropy.integer > 1 && i == 5) + d3d_filter_mipmin = d3d_filter_mipmag = D3DTEXF_ANISOTROPIC; + for (pool = gltexturepoolchain;pool;pool = pool->next) + { + for (glt = pool->gltchain;glt;glt = glt->chain) + { + // only update already uploaded images + if (glt->d3dtexture && !glt->d3disdepthsurface && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) + { + if (glt->flags & TEXF_MIPMAP) + { + glt->d3dminfilter = d3d_filter_mipmin; + glt->d3dmagfilter = d3d_filter_mipmag; + glt->d3dmipfilter = d3d_filter_mipmix; + glt->d3dmaxmiplevelfilter = 0; + } + else + { + glt->d3dminfilter = d3d_filter_flatmin; + glt->d3dmagfilter = d3d_filter_flatmag; + glt->d3dmipfilter = d3d_filter_flatmix; + glt->d3dmaxmiplevelfilter = 0; + } + } + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + // change all the existing texture objects + for (pool = gltexturepoolchain;pool;pool = pool->next) + for (glt = pool->gltchain;glt;glt = glt->chain) + if (glt->texnum && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) + DPSOFTRAST_Texture_Filter(glt->texnum, (glt->flags & TEXF_MIPMAP) ? dpsoftrast_filter_mipmap : dpsoftrast_filter_nomipmap); + break; + } +} + +static void GL_Texture_CalcImageSize(int texturetype, int flags, int miplevel, int inwidth, int inheight, int indepth, int *outwidth, int *outheight, int *outdepth, int *outmiplevels) +{ + int picmip = 0, maxsize = 0, width2 = 1, height2 = 1, depth2 = 1, miplevels = 1; + + switch (texturetype) + { + default: + case GLTEXTURETYPE_2D: + maxsize = vid.maxtexturesize_2d; + if (flags & TEXF_PICMIP) + { + maxsize = bound(1, gl_max_size.integer, maxsize); + picmip = miplevel; + } + break; + case GLTEXTURETYPE_3D: + maxsize = vid.maxtexturesize_3d; + break; + case GLTEXTURETYPE_CUBEMAP: + maxsize = vid.maxtexturesize_cubemap; + break; + } + + if (vid.support.arb_texture_non_power_of_two) + { + width2 = min(inwidth >> picmip, maxsize); + height2 = min(inheight >> picmip, maxsize); + depth2 = min(indepth >> picmip, maxsize); + } + else + { + for (width2 = 1;width2 < inwidth;width2 <<= 1); + for (width2 >>= picmip;width2 > maxsize;width2 >>= 1); + for (height2 = 1;height2 < inheight;height2 <<= 1); + for (height2 >>= picmip;height2 > maxsize;height2 >>= 1); + for (depth2 = 1;depth2 < indepth;depth2 <<= 1); + for (depth2 >>= picmip;depth2 > maxsize;depth2 >>= 1); + } + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#if 0 + // for some reason the REF rasterizer (and hence the PIX debugger) does not like small textures... + if (texturetype == GLTEXTURETYPE_2D) + { + width2 = max(width2, 2); + height2 = max(height2, 2); + } +#endif + break; + } + + miplevels = 1; + if (flags & TEXF_MIPMAP) + { + int extent = max(width2, max(height2, depth2)); + while(extent >>= 1) + miplevels++; + } + + if (outwidth) + *outwidth = max(1, width2); + if (outheight) + *outheight = max(1, height2); + if (outdepth) + *outdepth = max(1, depth2); + if (outmiplevels) + *outmiplevels = miplevels; +} + + +static int R_CalcTexelDataSize (gltexture_t *glt) +{ + int width2, height2, depth2, size; + + GL_Texture_CalcImageSize(glt->texturetype, glt->flags, glt->miplevel, glt->inputwidth, glt->inputheight, glt->inputdepth, &width2, &height2, &depth2, NULL); + + size = width2 * height2 * depth2; + + if (glt->flags & TEXF_MIPMAP) + { + while (width2 > 1 || height2 > 1 || depth2 > 1) + { + if (width2 > 1) + width2 >>= 1; + if (height2 > 1) + height2 >>= 1; + if (depth2 > 1) + depth2 >>= 1; + size += width2 * height2 * depth2; + } + } + + return (int)(size * glt->textype->glinternalbytesperpixel) * glt->sides; +} + +void R_TextureStats_Print(qboolean printeach, qboolean printpool, qboolean printtotal) +{ + int glsize; + int isloaded; + int pooltotal = 0, pooltotalt = 0, pooltotalp = 0, poolloaded = 0, poolloadedt = 0, poolloadedp = 0; + int sumtotal = 0, sumtotalt = 0, sumtotalp = 0, sumloaded = 0, sumloadedt = 0, sumloadedp = 0; + gltexture_t *glt; + gltexturepool_t *pool; + if (printeach) + Con_Print("glsize input loaded mip alpha name\n"); + for (pool = gltexturepoolchain;pool;pool = pool->next) + { + pooltotal = 0; + pooltotalt = 0; + pooltotalp = 0; + poolloaded = 0; + poolloadedt = 0; + poolloadedp = 0; + for (glt = pool->gltchain;glt;glt = glt->chain) + { + glsize = R_CalcTexelDataSize(glt); + isloaded = glt->texnum != 0; + pooltotal++; + pooltotalt += glsize; + pooltotalp += glt->inputdatasize; + if (isloaded) + { + poolloaded++; + poolloadedt += glsize; + poolloadedp += glt->inputdatasize; + } + if (printeach) + Con_Printf("%c%4i%c%c%4i%c %-24s %s %s %s %s\n", isloaded ? '[' : ' ', (glsize + 1023) / 1024, isloaded ? ']' : ' ', glt->inputtexels ? '[' : ' ', (glt->inputdatasize + 1023) / 1024, glt->inputtexels ? ']' : ' ', glt->textype->name, isloaded ? "loaded" : " ", (glt->flags & TEXF_MIPMAP) ? "mip" : " ", (glt->flags & TEXF_ALPHA) ? "alpha" : " ", glt->identifier); + } + if (printpool) + Con_Printf("texturepool %10p total: %i (%.3fMB, %.3fMB original), uploaded %i (%.3fMB, %.3fMB original), upload on demand %i (%.3fMB, %.3fMB original)\n", (void *)pool, pooltotal, pooltotalt / 1048576.0, pooltotalp / 1048576.0, poolloaded, poolloadedt / 1048576.0, poolloadedp / 1048576.0, pooltotal - poolloaded, (pooltotalt - poolloadedt) / 1048576.0, (pooltotalp - poolloadedp) / 1048576.0); + sumtotal += pooltotal; + sumtotalt += pooltotalt; + sumtotalp += pooltotalp; + sumloaded += poolloaded; + sumloadedt += poolloadedt; + sumloadedp += poolloadedp; + } + if (printtotal) + Con_Printf("textures total: %i (%.3fMB, %.3fMB original), uploaded %i (%.3fMB, %.3fMB original), upload on demand %i (%.3fMB, %.3fMB original)\n", sumtotal, sumtotalt / 1048576.0, sumtotalp / 1048576.0, sumloaded, sumloadedt / 1048576.0, sumloadedp / 1048576.0, sumtotal - sumloaded, (sumtotalt - sumloadedt) / 1048576.0, (sumtotalp - sumloadedp) / 1048576.0); +} + +static void R_TextureStats_f(void) +{ + R_TextureStats_Print(true, true, true); +} + +static void r_textures_start(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // LordHavoc: allow any alignment + CHECKGLERROR + qglPixelStorei(GL_UNPACK_ALIGNMENT, 1);CHECKGLERROR + qglPixelStorei(GL_PACK_ALIGNMENT, 1);CHECKGLERROR + break; + case RENDERPATH_D3D9: + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + + texturemempool = Mem_AllocPool("texture management", 0, NULL); + Mem_ExpandableArray_NewArray(&texturearray, texturemempool, sizeof(gltexture_t), 512); + + // Disable JPEG screenshots if the DLL isn't loaded + if (! JPEG_OpenLibrary ()) + Cvar_SetValueQuick (&scr_screenshot_jpeg, 0); + if (! PNG_OpenLibrary ()) + Cvar_SetValueQuick (&scr_screenshot_png, 0); +} + +static void r_textures_shutdown(void) +{ + rtexturepool_t *temp; + + JPEG_CloseLibrary (); + + while(gltexturepoolchain) + { + temp = (rtexturepool_t *) gltexturepoolchain; + R_FreeTexturePool(&temp); + } + + resizebuffersize = 0; + resizebuffer = NULL; + colorconvertbuffer = NULL; + texturebuffer = NULL; + Mem_ExpandableArray_FreeArray(&texturearray); + Mem_FreePool(&texturemempool); +} + +static void r_textures_newmap(void) +{ +} + +static void r_textures_devicelost(void) +{ + int i, endindex; + gltexture_t *glt; + endindex = Mem_ExpandableArray_IndexRange(&texturearray); + for (i = 0;i < endindex;i++) + { + glt = (gltexture_t *) Mem_ExpandableArray_RecordAtIndex(&texturearray, i); + if (!glt || !(glt->flags & TEXF_RENDERTARGET)) + continue; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (glt->d3disdepthsurface) + IDirect3DSurface9_Release((IDirect3DSurface9 *)glt->d3dtexture); + else if (glt->tiledepth > 1) + IDirect3DVolumeTexture9_Release((IDirect3DVolumeTexture9 *)glt->d3dtexture); + else if (glt->sides == 6) + IDirect3DCubeTexture9_Release((IDirect3DCubeTexture9 *)glt->d3dtexture); + else + IDirect3DTexture9_Release((IDirect3DTexture9 *)glt->d3dtexture); + glt->d3dtexture = NULL; +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + } +} + +static void r_textures_devicerestored(void) +{ + int i, endindex; + gltexture_t *glt; + endindex = Mem_ExpandableArray_IndexRange(&texturearray); + for (i = 0;i < endindex;i++) + { + glt = (gltexture_t *) Mem_ExpandableArray_RecordAtIndex(&texturearray, i); + if (!glt || !(glt->flags & TEXF_RENDERTARGET)) + continue; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + HRESULT d3dresult; + if (glt->d3disdepthsurface) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateDepthStencilSurface(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateDepthStencilSurface failed!"); + } + else if (glt->tiledepth > 1) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateVolumeTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->tiledepth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DVolumeTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateVolumeTexture failed!"); + } + else if (glt->sides == 6) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateCubeTexture(vid_d3d9dev, glt->tilewidth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DCubeTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateCubeTexture failed!"); + } + else + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateTexture failed!"); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + } +} + + +void R_Textures_Init (void) +{ + Cmd_AddCommand("gl_texturemode", &GL_TextureMode_f, "set texture filtering mode (GL_NEAREST, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, etc); an additional argument 'force' forces the texture mode even in cases where it may not be appropriate"); + Cmd_AddCommand("r_texturestats", R_TextureStats_f, "print information about all loaded textures and some statistics"); + Cvar_RegisterVariable (&gl_max_size); + Cvar_RegisterVariable (&gl_picmip); + Cvar_RegisterVariable (&gl_picmip_world); + Cvar_RegisterVariable (&r_picmipworld); + Cvar_RegisterVariable (&gl_picmip_sprites); + Cvar_RegisterVariable (&r_picmipsprites); + Cvar_RegisterVariable (&gl_picmip_other); + Cvar_RegisterVariable (&gl_max_lightmapsize); + Cvar_RegisterVariable (&r_lerpimages); + Cvar_RegisterVariable (&gl_texture_anisotropy); + Cvar_RegisterVariable (&gl_texturecompression); + Cvar_RegisterVariable (&gl_texturecompression_color); + Cvar_RegisterVariable (&gl_texturecompression_normal); + Cvar_RegisterVariable (&gl_texturecompression_gloss); + Cvar_RegisterVariable (&gl_texturecompression_glow); + Cvar_RegisterVariable (&gl_texturecompression_2d); + Cvar_RegisterVariable (&gl_texturecompression_q3bsplightmaps); + Cvar_RegisterVariable (&gl_texturecompression_q3bspdeluxemaps); + Cvar_RegisterVariable (&gl_texturecompression_sky); + Cvar_RegisterVariable (&gl_texturecompression_lightcubemaps); + Cvar_RegisterVariable (&gl_texturecompression_reflectmask); + Cvar_RegisterVariable (&gl_texturecompression_sprites); + Cvar_RegisterVariable (&gl_nopartialtextureupdates); + Cvar_RegisterVariable (&r_texture_dds_load_alphamode); + Cvar_RegisterVariable (&r_texture_dds_load_logfailure); + Cvar_RegisterVariable (&r_texture_dds_swdecode); + + R_RegisterModule("R_Textures", r_textures_start, r_textures_shutdown, r_textures_newmap, r_textures_devicelost, r_textures_devicerestored); +} + +void R_Textures_Frame (void) +{ + static int old_aniso = 0; + + // could do procedural texture animation here, if we keep track of which + // textures were accessed this frame... + + // free the resize buffers + resizebuffersize = 0; + if (resizebuffer) + { + Mem_Free(resizebuffer); + resizebuffer = NULL; + } + if (colorconvertbuffer) + { + Mem_Free(colorconvertbuffer); + colorconvertbuffer = NULL; + } + + if (old_aniso != gl_texture_anisotropy.integer) + { + gltexture_t *glt; + gltexturepool_t *pool; + GLint oldbindtexnum; + + old_aniso = bound(1, gl_texture_anisotropy.integer, (int)vid.max_anisotropy); + + Cvar_SetValueQuick(&gl_texture_anisotropy, old_aniso); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + GL_ActiveTexture(0); + for (pool = gltexturepoolchain;pool;pool = pool->next) + { + for (glt = pool->gltchain;glt;glt = glt->chain) + { + // only update already uploaded images + if (glt->texnum && (glt->flags & TEXF_MIPMAP) == TEXF_MIPMAP) + { + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAX_ANISOTROPY_EXT, old_aniso);CHECKGLERROR + + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + } + } + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } + } +} + +void R_MakeResizeBufferBigger(int size) +{ + if (resizebuffersize < size) + { + resizebuffersize = size; + if (resizebuffer) + Mem_Free(resizebuffer); + if (colorconvertbuffer) + Mem_Free(colorconvertbuffer); + resizebuffer = (unsigned char *)Mem_Alloc(texturemempool, resizebuffersize); + colorconvertbuffer = (unsigned char *)Mem_Alloc(texturemempool, resizebuffersize); + if (!resizebuffer || !colorconvertbuffer) + Host_Error("R_Upload: out of memory"); + } +} + +static void GL_SetupTextureParameters(int flags, textype_t textype, int texturetype) +{ + int textureenum = gltexturetypeenums[texturetype]; + int wrapmode = (flags & TEXF_CLAMP) ? GL_CLAMP_TO_EDGE : GL_REPEAT; + + CHECKGLERROR + + if (vid.support.ext_texture_filter_anisotropic && (flags & TEXF_MIPMAP)) + { + int aniso = bound(1, gl_texture_anisotropy.integer, (int)vid.max_anisotropy); + if (gl_texture_anisotropy.integer != aniso) + Cvar_SetValueQuick(&gl_texture_anisotropy, aniso); + qglTexParameteri(textureenum, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_WRAP_S, wrapmode);CHECKGLERROR + qglTexParameteri(textureenum, GL_TEXTURE_WRAP_T, wrapmode);CHECKGLERROR + if (gltexturetypedimensions[texturetype] >= 3) + { + qglTexParameteri(textureenum, GL_TEXTURE_WRAP_R, wrapmode);CHECKGLERROR + } + + CHECKGLERROR + if (!gl_filter_force && flags & TEXF_FORCENEAREST) + { + if (flags & TEXF_MIPMAP) + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);CHECKGLERROR + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_NEAREST);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, GL_NEAREST);CHECKGLERROR + } + else if (!gl_filter_force && flags & TEXF_FORCELINEAR) + { + if (flags & TEXF_MIPMAP) + { + if (gl_filter_min == GL_NEAREST_MIPMAP_LINEAR || gl_filter_min == GL_LINEAR_MIPMAP_LINEAR) + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);CHECKGLERROR + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);CHECKGLERROR + } + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, GL_LINEAR);CHECKGLERROR + } + else + { + if (flags & TEXF_MIPMAP) + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, gl_filter_min);CHECKGLERROR + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, gl_filter_mag);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, gl_filter_mag);CHECKGLERROR + } + + if (textype == TEXTYPE_SHADOWMAP) + { + if (vid.support.arb_shadow) + { + if (flags & TEXF_COMPARE) + { + qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);CHECKGLERROR + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);CHECKGLERROR + } + + CHECKGLERROR +} + +static void R_UploadPartialTexture(gltexture_t *glt, const unsigned char *data, int fragx, int fragy, int fragz, int fragwidth, int fragheight, int fragdepth) +{ + if (data == NULL) + Sys_Error("R_UploadPartialTexture \"%s\": partial update with NULL pixels", glt->identifier); + + if (glt->texturetype != GLTEXTURETYPE_2D) + Sys_Error("R_UploadPartialTexture \"%s\": partial update of type other than 2D", glt->identifier); + + if (glt->textype->textype == TEXTYPE_PALETTE) + Sys_Error("R_UploadPartialTexture \"%s\": partial update of paletted texture", glt->identifier); + + if (glt->flags & (TEXF_MIPMAP | TEXF_PICMIP)) + Sys_Error("R_UploadPartialTexture \"%s\": partial update not supported with MIPMAP or PICMIP flags", glt->identifier); + + if (glt->inputwidth != glt->tilewidth || glt->inputheight != glt->tileheight || glt->tiledepth != 1) + Sys_Error("R_UploadPartialTexture \"%s\": partial update not supported with stretched or special textures", glt->identifier); + + // update a portion of the image + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + { + int oldbindtexnum; + CHECKGLERROR + // we need to restore the texture binding after finishing the upload + GL_ActiveTexture(0); + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + qglTexSubImage2D(GL_TEXTURE_2D, 0, fragx, fragy, fragwidth, fragheight, glt->glformat, glt->gltype, data);CHECKGLERROR + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + RECT d3drect; + D3DLOCKED_RECT d3dlockedrect; + int y; + memset(&d3drect, 0, sizeof(d3drect)); + d3drect.left = fragx; + d3drect.top = fragy; + d3drect.right = fragx+fragwidth; + d3drect.bottom = fragy+fragheight; + if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, 0, &d3dlockedrect, &d3drect, 0) == D3D_OK && d3dlockedrect.pBits) + { + for (y = 0;y < fragheight;y++) + memcpy((unsigned char *)d3dlockedrect.pBits + d3dlockedrect.Pitch * y, data + fragwidth*glt->bytesperpixel * y, fragwidth*glt->bytesperpixel); + IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, 0); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Texture_UpdatePartial(glt->texnum, 0, data, fragx, fragy, fragwidth, fragheight); + break; + } +} + +static void R_UploadFullTexture(gltexture_t *glt, const unsigned char *data) +{ + int i, mip = 0, width, height, depth; + GLint oldbindtexnum = 0; + const unsigned char *prevbuffer; + prevbuffer = data; + + // error out if a stretch is needed on special texture types + if (glt->texturetype != GLTEXTURETYPE_2D && (glt->tilewidth != glt->inputwidth || glt->tileheight != glt->inputheight || glt->tiledepth != glt->inputdepth)) + Sys_Error("R_UploadFullTexture \"%s\": stretch uploads allowed only on 2D textures\n", glt->identifier); + + // when picmip or maxsize is applied, we scale up to a power of 2 multiple + // of the target size and then use the mipmap reduction function to get + // high quality supersampled results + for (width = glt->tilewidth;width < glt->inputwidth ;width <<= 1); + for (height = glt->tileheight;height < glt->inputheight;height <<= 1); + for (depth = glt->tiledepth;depth < glt->inputdepth ;depth <<= 1); + R_MakeResizeBufferBigger(width * height * depth * glt->sides * glt->bytesperpixel); + + if (prevbuffer == NULL) + { + width = glt->tilewidth; + height = glt->tileheight; + depth = glt->tiledepth; +// memset(resizebuffer, 0, width * height * depth * glt->sides * glt->bytesperpixel); +// prevbuffer = resizebuffer; + } + else + { + if (glt->textype->textype == TEXTYPE_PALETTE) + { + // promote paletted to BGRA, so we only have to worry about BGRA in the rest of this code + Image_Copy8bitBGRA(prevbuffer, colorconvertbuffer, glt->inputwidth * glt->inputheight * glt->inputdepth * glt->sides, glt->palette); + prevbuffer = colorconvertbuffer; + } + if (glt->flags & TEXF_RGBMULTIPLYBYALPHA) + { + // multiply RGB channels by A channel before uploading + int alpha; + for (i = 0;i < glt->inputwidth*glt->inputheight*glt->inputdepth*4;i += 4) + { + alpha = prevbuffer[i+3]; + colorconvertbuffer[i] = (prevbuffer[i] * alpha) >> 8; + colorconvertbuffer[i+1] = (prevbuffer[i+1] * alpha) >> 8; + colorconvertbuffer[i+2] = (prevbuffer[i+2] * alpha) >> 8; + colorconvertbuffer[i+3] = alpha; + } + prevbuffer = colorconvertbuffer; + } + // scale up to a power of 2 size (if appropriate) + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); + prevbuffer = resizebuffer; + } + // apply mipmap reduction algorithm to get down to picmip/max_size + while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); + prevbuffer = resizebuffer; + } + } + + // do the appropriate upload type... + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + + // we need to restore the texture binding after finishing the upload + GL_ActiveTexture(0); + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + + if (qglGetCompressedTexImageARB) + { + if (gl_texturecompression.integer >= 2) + qglHint(GL_TEXTURE_COMPRESSION_HINT_ARB, GL_NICEST); + else + qglHint(GL_TEXTURE_COMPRESSION_HINT_ARB, GL_FASTEST); + CHECKGLERROR + } + switch(glt->texturetype) + { + case GLTEXTURETYPE_2D: + qglTexImage2D(GL_TEXTURE_2D, mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + qglTexImage2D(GL_TEXTURE_2D, mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + } + } + break; + case GLTEXTURETYPE_3D: + qglTexImage3D(GL_TEXTURE_3D, mip++, glt->glinternalformat, width, height, depth, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + qglTexImage3D(GL_TEXTURE_3D, mip++, glt->glinternalformat, width, height, depth, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + } + } + break; + case GLTEXTURETYPE_CUBEMAP: + // convert and upload each side in turn, + // from a continuous block of input texels + texturebuffer = (unsigned char *)prevbuffer; + for (i = 0;i < 6;i++) + { + prevbuffer = texturebuffer; + texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); + prevbuffer = resizebuffer; + } + // picmip/max_size + while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); + prevbuffer = resizebuffer; + } + mip = 0; + qglTexImage2D(cubemapside[i], mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + qglTexImage2D(cubemapside[i], mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + } + } + } + break; + } + GL_SetupTextureParameters(glt->flags, glt->textype->textype, glt->texturetype); + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (!(glt->flags & TEXF_RENDERTARGET)) + { + D3DLOCKED_RECT d3dlockedrect; + D3DLOCKED_BOX d3dlockedbox; + switch(glt->texturetype) + { + case GLTEXTURETYPE_2D: + if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + if (prevbuffer) + memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); + else + memset(d3dlockedrect.pBits, 255, width*height*glt->bytesperpixel); + IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); + } + mip++; + if ((glt->flags & TEXF_MIPMAP) && prevbuffer) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); + IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); + } + mip++; + } + } + break; + case GLTEXTURETYPE_3D: + if (IDirect3DVolumeTexture9_LockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip, &d3dlockedbox, NULL, 0) == D3D_OK && d3dlockedbox.pBits) + { + // we are not honoring the RowPitch or SlicePitch, hopefully this works with all sizes + memcpy(d3dlockedbox.pBits, prevbuffer, width*height*depth*glt->bytesperpixel); + IDirect3DVolumeTexture9_UnlockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip); + } + mip++; + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + if (IDirect3DVolumeTexture9_LockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip, &d3dlockedbox, NULL, 0) == D3D_OK && d3dlockedbox.pBits) + { + // we are not honoring the RowPitch or SlicePitch, hopefully this works with all sizes + memcpy(d3dlockedbox.pBits, prevbuffer, width*height*depth*glt->bytesperpixel); + IDirect3DVolumeTexture9_UnlockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip); + } + mip++; + } + } + break; + case GLTEXTURETYPE_CUBEMAP: + // convert and upload each side in turn, + // from a continuous block of input texels + texturebuffer = (unsigned char *)prevbuffer; + for (i = 0;i < 6;i++) + { + prevbuffer = texturebuffer; + texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); + prevbuffer = resizebuffer; + } + // picmip/max_size + while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); + prevbuffer = resizebuffer; + } + mip = 0; + if (IDirect3DCubeTexture9_LockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); + IDirect3DCubeTexture9_UnlockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip); + } + mip++; + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + if (IDirect3DCubeTexture9_LockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); + IDirect3DCubeTexture9_UnlockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip); + } + mip++; + } + } + } + break; + } + } + glt->d3daddressw = 0; + if (glt->flags & TEXF_CLAMP) + { + glt->d3daddressu = D3DTADDRESS_CLAMP; + glt->d3daddressv = D3DTADDRESS_CLAMP; + if (glt->tiledepth > 1) + glt->d3daddressw = D3DTADDRESS_CLAMP; + } + else + { + glt->d3daddressu = D3DTADDRESS_WRAP; + glt->d3daddressv = D3DTADDRESS_WRAP; + if (glt->tiledepth > 1) + glt->d3daddressw = D3DTADDRESS_WRAP; + } + glt->d3dmipmaplodbias = 0; + glt->d3dmaxmiplevel = 0; + glt->d3dmaxmiplevelfilter = d3d_filter_nomip ? 0 : glt->d3dmaxmiplevel; + if (glt->flags & TEXF_FORCELINEAR) + { + glt->d3dminfilter = D3DTEXF_LINEAR; + glt->d3dmagfilter = D3DTEXF_LINEAR; + glt->d3dmipfilter = D3DTEXF_POINT; + } + else if (glt->flags & TEXF_FORCENEAREST) + { + glt->d3dminfilter = D3DTEXF_POINT; + glt->d3dmagfilter = D3DTEXF_POINT; + glt->d3dmipfilter = D3DTEXF_POINT; + } + else if (glt->flags & TEXF_MIPMAP) + { + glt->d3dminfilter = d3d_filter_mipmin; + glt->d3dmagfilter = d3d_filter_mipmag; + glt->d3dmipfilter = d3d_filter_mipmix; + } + else + { + glt->d3dminfilter = d3d_filter_flatmin; + glt->d3dmagfilter = d3d_filter_flatmag; + glt->d3dmipfilter = d3d_filter_flatmix; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + switch(glt->texturetype) + { + case GLTEXTURETYPE_2D: + DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); + break; + case GLTEXTURETYPE_3D: + DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); + break; + case GLTEXTURETYPE_CUBEMAP: + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + unsigned char *combinedbuffer = (unsigned char *)Mem_Alloc(tempmempool, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->sides*glt->bytesperpixel); + // convert and upload each side in turn, + // from a continuous block of input texels + // copy the results into combinedbuffer + texturebuffer = (unsigned char *)prevbuffer; + for (i = 0;i < 6;i++) + { + prevbuffer = texturebuffer; + texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); + prevbuffer = resizebuffer; + } + // picmip/max_size + while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); + prevbuffer = resizebuffer; + } + memcpy(combinedbuffer + i*glt->tilewidth*glt->tileheight*glt->tiledepth*glt->bytesperpixel, prevbuffer, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->bytesperpixel); + } + DPSOFTRAST_Texture_UpdateFull(glt->texnum, combinedbuffer); + Mem_Free(combinedbuffer); + } + else + DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); + break; + } + if (glt->flags & TEXF_FORCELINEAR) + DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_LINEAR); + else if (glt->flags & TEXF_FORCENEAREST) + DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_NEAREST); + else if (glt->flags & TEXF_MIPMAP) + DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_mipmap); + else + DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_nomipmap); + break; + } +} + +static rtexture_t *R_SetupTexture(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, int sides, int flags, int miplevel, textype_t textype, int texturetype, const unsigned char *data, const unsigned int *palette) +{ + int i, size; + gltexture_t *glt; + gltexturepool_t *pool = (gltexturepool_t *)rtexturepool; + textypeinfo_t *texinfo, *texinfo2; + unsigned char *temppixels = NULL; + qboolean swaprb; + + if (cls.state == ca_dedicated) + return NULL; + + // see if we need to swap red and blue (BGRA <-> RGBA conversion) + swaprb = false; + switch(textype) + { + case TEXTYPE_RGBA: if (vid.forcetextype == TEXTYPE_BGRA) {swaprb = true;textype = TEXTYPE_BGRA;} break; + case TEXTYPE_BGRA: if (vid.forcetextype == TEXTYPE_RGBA) {swaprb = true;textype = TEXTYPE_RGBA;} break; + case TEXTYPE_SRGB_RGBA: if (vid.forcetextype == TEXTYPE_BGRA) {swaprb = true;textype = TEXTYPE_SRGB_BGRA;} break; + case TEXTYPE_SRGB_BGRA: if (vid.forcetextype == TEXTYPE_RGBA) {swaprb = true;textype = TEXTYPE_SRGB_RGBA;} break; + default: break; + } + if (swaprb) + { + // swap bytes + static int rgbaswapindices[4] = {2, 1, 0, 3}; + size = width * height * depth * sides * 4; + temppixels = (unsigned char *)Mem_Alloc(tempmempool, size); + Image_CopyMux(temppixels, data, width, height*depth*sides, false, false, false, 4, 4, rgbaswapindices); + data = temppixels; + } + + // if sRGB texture formats are not supported, convert input to linear and upload as normal types + if (!vid.support.ext_texture_srgb) + { + qboolean convertsRGB = false; + switch(textype) + { + case TEXTYPE_SRGB_DXT1: textype = TEXTYPE_DXT1 ;convertsRGB = true;break; + case TEXTYPE_SRGB_DXT1A: textype = TEXTYPE_DXT1A ;convertsRGB = true;break; + case TEXTYPE_SRGB_DXT3: textype = TEXTYPE_DXT3 ;convertsRGB = true;break; + case TEXTYPE_SRGB_DXT5: textype = TEXTYPE_DXT5 ;convertsRGB = true;break; + case TEXTYPE_SRGB_PALETTE: textype = TEXTYPE_PALETTE;convertsRGB = true;break; + case TEXTYPE_SRGB_RGBA: textype = TEXTYPE_RGBA ;convertsRGB = true;break; + case TEXTYPE_SRGB_BGRA: textype = TEXTYPE_BGRA ;convertsRGB = true;break; + default: + break; + } + if (convertsRGB && data) + { + size = width * height * depth * sides * 4; + if (!temppixels) + { + temppixels = (unsigned char *)Mem_Alloc(tempmempool, size); + memcpy(temppixels, data, size); + } + Image_MakeLinearColorsFromsRGB(temppixels, temppixels, width*height*depth*sides); + } + } + + if (texturetype == GLTEXTURETYPE_CUBEMAP && !vid.support.arb_texture_cube_map) + { + Con_Printf ("R_LoadTexture: cubemap texture not supported by driver\n"); + return NULL; + } + if (texturetype == GLTEXTURETYPE_3D && !vid.support.ext_texture_3d) + { + Con_Printf ("R_LoadTexture: 3d texture not supported by driver\n"); + return NULL; + } + + texinfo = R_GetTexTypeInfo(textype, flags); + size = width * height * depth * sides * texinfo->inputbytesperpixel; + if (size < 1) + { + Con_Printf ("R_LoadTexture: bogus texture size (%dx%dx%dx%dbppx%dsides = %d bytes)\n", width, height, depth, texinfo->inputbytesperpixel * 8, sides, size); + return NULL; + } + + // clear the alpha flag if the texture has no transparent pixels + switch(textype) + { + case TEXTYPE_PALETTE: + case TEXTYPE_SRGB_PALETTE: + if (flags & TEXF_ALPHA) + { + flags &= ~TEXF_ALPHA; + if (data) + { + for (i = 0;i < size;i++) + { + if (((unsigned char *)&palette[data[i]])[3] < 255) + { + flags |= TEXF_ALPHA; + break; + } + } + } + } + break; + case TEXTYPE_RGBA: + case TEXTYPE_BGRA: + case TEXTYPE_SRGB_RGBA: + case TEXTYPE_SRGB_BGRA: + if (flags & TEXF_ALPHA) + { + flags &= ~TEXF_ALPHA; + if (data) + { + for (i = 3;i < size;i += 4) + { + if (data[i] < 255) + { + flags |= TEXF_ALPHA; + break; + } + } + } + } + break; + case TEXTYPE_SHADOWMAP: + break; + case TEXTYPE_DXT1: + case TEXTYPE_SRGB_DXT1: + break; + case TEXTYPE_DXT1A: + case TEXTYPE_SRGB_DXT1A: + case TEXTYPE_DXT3: + case TEXTYPE_SRGB_DXT3: + case TEXTYPE_DXT5: + case TEXTYPE_SRGB_DXT5: + flags |= TEXF_ALPHA; + break; + case TEXTYPE_ALPHA: + flags |= TEXF_ALPHA; + break; + case TEXTYPE_COLORBUFFER: + case TEXTYPE_COLORBUFFER16F: + case TEXTYPE_COLORBUFFER32F: + flags |= TEXF_ALPHA; + break; + default: + Sys_Error("R_LoadTexture: unknown texture type"); + } + + texinfo2 = R_GetTexTypeInfo(textype, flags); + if(size == width * height * depth * sides * texinfo->inputbytesperpixel) + texinfo = texinfo2; + else + Con_Printf ("R_LoadTexture: input size changed after alpha fallback\n"); + + glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); + if (identifier) + strlcpy (glt->identifier, identifier, sizeof(glt->identifier)); + glt->pool = pool; + glt->chain = pool->gltchain; + pool->gltchain = glt; + glt->inputwidth = width; + glt->inputheight = height; + glt->inputdepth = depth; + glt->flags = flags; + glt->miplevel = (miplevel < 0) ? R_PicmipForFlags(flags) : miplevel; // note: if miplevel is -1, we know the texture is in original size and we can picmip it normally + glt->textype = texinfo; + glt->texturetype = texturetype; + glt->inputdatasize = size; + glt->palette = palette; + glt->glinternalformat = texinfo->glinternalformat; + glt->glformat = texinfo->glformat; + glt->gltype = texinfo->gltype; + glt->bytesperpixel = texinfo->internalbytesperpixel; + glt->sides = glt->texturetype == GLTEXTURETYPE_CUBEMAP ? 6 : 1; + glt->texnum = 0; + glt->dirty = false; + glt->gltexturetypeenum = gltexturetypeenums[glt->texturetype]; + // init the dynamic texture attributes, too [11/22/2007 Black] + glt->updatecallback = NULL; + glt->updatacallback_data = NULL; + + GL_Texture_CalcImageSize(glt->texturetype, glt->flags, glt->miplevel, glt->inputwidth, glt->inputheight, glt->inputdepth, &glt->tilewidth, &glt->tileheight, &glt->tiledepth, &glt->miplevels); + + // upload the texture + // data may be NULL (blank texture for dynamic rendering) + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglGenTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DFORMAT d3dformat; + D3DPOOL d3dpool; + DWORD d3dusage; + HRESULT d3dresult; + d3dusage = 0; + d3dpool = D3DPOOL_MANAGED; + if (flags & TEXF_RENDERTARGET) + { + d3dusage |= D3DUSAGE_RENDERTARGET; + d3dpool = D3DPOOL_DEFAULT; + } + switch(textype) + { + case TEXTYPE_PALETTE: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; + case TEXTYPE_RGBA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8B8G8R8 : D3DFMT_X8B8G8R8;break; + case TEXTYPE_BGRA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; + case TEXTYPE_COLORBUFFER: d3dformat = D3DFMT_A8R8G8B8;break; + case TEXTYPE_COLORBUFFER16F: d3dformat = D3DFMT_A16B16G16R16F;break; + case TEXTYPE_COLORBUFFER32F: d3dformat = D3DFMT_A32B32G32R32F;break; + case TEXTYPE_SHADOWMAP: d3dformat = D3DFMT_D16;d3dusage = D3DUSAGE_DEPTHSTENCIL;break; // note: can not use D3DUSAGE_RENDERTARGET here + case TEXTYPE_ALPHA: d3dformat = D3DFMT_A8;break; + default: d3dformat = D3DFMT_A8R8G8B8;Sys_Error("R_LoadTexture: unsupported texture type %i when picking D3DFMT", (int)textype);break; + } + glt->d3dformat = d3dformat; + glt->d3dusage = d3dusage; + glt->d3dpool = d3dpool; + glt->d3disdepthsurface = textype == TEXTYPE_SHADOWMAP; + if (glt->d3disdepthsurface) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateDepthStencilSurface(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateDepthStencilSurface failed!"); + } + else if (glt->tiledepth > 1) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateVolumeTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->tiledepth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DVolumeTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateVolumeTexture failed!"); + } + else if (glt->sides == 6) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateCubeTexture(vid_d3d9dev, glt->tilewidth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DCubeTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateCubeTexture failed!"); + } + else + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateTexture failed!"); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + { + int tflags = 0; + switch(textype) + { + case TEXTYPE_PALETTE: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; + case TEXTYPE_RGBA: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA8;break; + case TEXTYPE_BGRA: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; + case TEXTYPE_COLORBUFFER: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; + case TEXTYPE_COLORBUFFER16F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA16F;break; + case TEXTYPE_COLORBUFFER32F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA32F;break; + case TEXTYPE_SHADOWMAP: tflags = DPSOFTRAST_TEXTURE_FORMAT_DEPTH;break; + case TEXTYPE_ALPHA: tflags = DPSOFTRAST_TEXTURE_FORMAT_ALPHA8;break; + default: Sys_Error("R_LoadTexture: unsupported texture type %i when picking DPSOFTRAST_TEXTURE_FLAGS", (int)textype); + } + if (glt->miplevels > 1) tflags |= DPSOFTRAST_TEXTURE_FLAG_MIPMAP; + if (flags & TEXF_ALPHA) tflags |= DPSOFTRAST_TEXTURE_FLAG_USEALPHA; + if (glt->sides == 6) tflags |= DPSOFTRAST_TEXTURE_FLAG_CUBEMAP; + if (glt->flags & TEXF_CLAMP) tflags |= DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE; + glt->texnum = DPSOFTRAST_Texture_New(tflags, glt->tilewidth, glt->tileheight, glt->tiledepth); + } + break; + } + + R_UploadFullTexture(glt, data); + if ((glt->flags & TEXF_ALLOWUPDATES) && gl_nopartialtextureupdates.integer) + glt->bufferpixels = (unsigned char *)Mem_Alloc(texturemempool, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->sides*glt->bytesperpixel); + + // free any temporary processing buffer we allocated... + if (temppixels) + Mem_Free(temppixels); + + // texture converting and uploading can take a while, so make sure we're sending keepalives + // FIXME: this causes rendering during R_Shadow_DrawLights +// CL_KeepaliveMessage(false); + + return (rtexture_t *)glt; +} + +rtexture_t *R_LoadTexture2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) +{ + return R_SetupTexture(rtexturepool, identifier, width, height, 1, 1, flags, miplevel, textype, GLTEXTURETYPE_2D, data, palette); +} + +rtexture_t *R_LoadTexture3D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) +{ + return R_SetupTexture(rtexturepool, identifier, width, height, depth, 1, flags, miplevel, textype, GLTEXTURETYPE_3D, data, palette); +} + +rtexture_t *R_LoadTextureCubeMap(rtexturepool_t *rtexturepool, const char *identifier, int width, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) +{ + return R_SetupTexture(rtexturepool, identifier, width, width, 1, 6, flags, miplevel, textype, GLTEXTURETYPE_CUBEMAP, data, palette); +} + +static int R_ShadowMapTextureFlags(int precision, qboolean filter) +{ + int flags = TEXF_RENDERTARGET | TEXF_CLAMP; + if (filter) + flags |= TEXF_FORCELINEAR | TEXF_COMPARE; + else + flags |= TEXF_FORCENEAREST; + if (precision <= 16) + flags |= TEXF_LOWPRECISION; + return flags; +} + +rtexture_t *R_LoadTextureShadowMap2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int precision, qboolean filter) +{ + return R_SetupTexture(rtexturepool, identifier, width, height, 1, 1, R_ShadowMapTextureFlags(precision, filter), -1, TEXTYPE_SHADOWMAP, GLTEXTURETYPE_2D, NULL, NULL); +} + +int R_SaveTextureDDSFile(rtexture_t *rt, const char *filename, qboolean skipuncompressed, qboolean hasalpha) +{ + gltexture_t *glt = (gltexture_t *)rt; + unsigned char *dds; + int oldbindtexnum; + int bytesperpixel = 0; + int bytesperblock = 0; + int dds_flags; + int dds_format_flags; + int dds_caps1; + int dds_caps2; + int ret; + int mip; + int mipmaps; + int mipinfo[16][4]; + int ddssize = 128; + GLint internalformat; + const char *ddsfourcc; + if (!rt) + return -1; // NULL pointer + if (!strcmp(gl_version, "2.0.5885 WinXP Release")) + return -2; // broken driver - crashes on reading internal format + if (!qglGetTexLevelParameteriv) + return -2; + GL_ActiveTexture(0); + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + qglGetTexLevelParameteriv(gltexturetypeenums[glt->texturetype], 0, GL_TEXTURE_INTERNAL_FORMAT, &internalformat); + switch(internalformat) + { + default: ddsfourcc = NULL;bytesperpixel = 4;break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: ddsfourcc = "DXT1";bytesperblock = 8;break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: ddsfourcc = "DXT3";bytesperblock = 16;break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: ddsfourcc = "DXT5";bytesperblock = 16;break; + } + // if premultiplied alpha, say so in the DDS file + if(glt->flags & TEXF_RGBMULTIPLYBYALPHA) + { + switch(internalformat) + { + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: ddsfourcc = "DXT2";break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: ddsfourcc = "DXT4";break; + } + } + if (!bytesperblock && skipuncompressed) + return -3; // skipped + memset(mipinfo, 0, sizeof(mipinfo)); + mipinfo[0][0] = glt->tilewidth; + mipinfo[0][1] = glt->tileheight; + mipmaps = 1; + if (glt->flags & TEXF_MIPMAP) + { + for (mip = 1;mip < 16;mip++) + { + mipinfo[mip][0] = mipinfo[mip-1][0] > 1 ? mipinfo[mip-1][0] >> 1 : 1; + mipinfo[mip][1] = mipinfo[mip-1][1] > 1 ? mipinfo[mip-1][1] >> 1 : 1; + if (mipinfo[mip][0] == 1 && mipinfo[mip][1] == 1) + { + mip++; + break; + } + } + mipmaps = mip; + } + for (mip = 0;mip < mipmaps;mip++) + { + mipinfo[mip][2] = bytesperblock ? ((mipinfo[mip][0]+3)/4)*((mipinfo[mip][1]+3)/4)*bytesperblock : mipinfo[mip][0]*mipinfo[mip][1]*bytesperpixel; + mipinfo[mip][3] = ddssize; + ddssize += mipinfo[mip][2]; + } + dds = (unsigned char *)Mem_Alloc(tempmempool, ddssize); + if (!dds) + return -4; + dds_caps1 = 0x1000; // DDSCAPS_TEXTURE + dds_caps2 = 0; + if (bytesperblock) + { + dds_flags = 0x81007; // DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_LINEARSIZE + dds_format_flags = 0x4; // DDPF_FOURCC + } + else + { + dds_flags = 0x100F; // DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH + dds_format_flags = 0x40; // DDPF_RGB + } + if (mipmaps) + { + dds_flags |= 0x20000; // DDSD_MIPMAPCOUNT + dds_caps1 |= 0x400008; // DDSCAPS_MIPMAP | DDSCAPS_COMPLEX + } + if(hasalpha) + dds_format_flags |= 0x1; // DDPF_ALPHAPIXELS + memcpy(dds, "DDS ", 4); + StoreLittleLong(dds+4, ddssize); + StoreLittleLong(dds+8, dds_flags); + StoreLittleLong(dds+12, mipinfo[0][1]); // height + StoreLittleLong(dds+16, mipinfo[0][0]); // width + StoreLittleLong(dds+24, 1); // depth + StoreLittleLong(dds+28, mipmaps); // mipmaps + StoreLittleLong(dds+76, 32); // format size + StoreLittleLong(dds+80, dds_format_flags); + StoreLittleLong(dds+108, dds_caps1); + StoreLittleLong(dds+112, dds_caps2); + if (bytesperblock) + { + StoreLittleLong(dds+20, mipinfo[0][2]); // linear size + memcpy(dds+84, ddsfourcc, 4); + for (mip = 0;mip < mipmaps;mip++) + { + qglGetCompressedTexImageARB(gltexturetypeenums[glt->texturetype], mip, dds + mipinfo[mip][3]);CHECKGLERROR + } + } + else + { + StoreLittleLong(dds+20, mipinfo[0][0]*bytesperpixel); // pitch + StoreLittleLong(dds+88, bytesperpixel*8); // bits per pixel + dds[94] = dds[97] = dds[100] = dds[107] = 255; // bgra byte order masks + for (mip = 0;mip < mipmaps;mip++) + { + qglGetTexImage(gltexturetypeenums[glt->texturetype], mip, GL_BGRA, GL_UNSIGNED_BYTE, dds + mipinfo[mip][3]);CHECKGLERROR + } + } + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + ret = FS_WriteFile(filename, dds, ddssize); + Mem_Free(dds); + return ret ? ddssize : -5; +} + +rtexture_t *R_LoadTextureDDSFile(rtexturepool_t *rtexturepool, const char *filename, int flags, qboolean *hasalphaflag, float *avgcolor, int miplevel) // DDS textures are opaque, so miplevel isn't a pointer but just seen as a hint +{ + int i, size, dds_format_flags, dds_miplevels, dds_width, dds_height; + //int dds_flags; + textype_t textype; + int bytesperblock, bytesperpixel; + int mipcomplete; + gltexture_t *glt; + gltexturepool_t *pool = (gltexturepool_t *)rtexturepool; + textypeinfo_t *texinfo; + int mip, mipwidth, mipheight, mipsize, mipsize_total; + unsigned int c; + GLint oldbindtexnum = 0; + const unsigned char *mippixels, *ddspixels, *mippixels_start; + unsigned char *dds; + fs_offset_t ddsfilesize; + unsigned int ddssize; + qboolean force_swdecode = (r_texture_dds_swdecode.integer > 1); + + if (cls.state == ca_dedicated) + return NULL; + + dds = FS_LoadFile(filename, tempmempool, true, &ddsfilesize); + ddssize = ddsfilesize; + + if (!dds) + { + if(r_texture_dds_load_logfailure.integer) + Log_Printf("ddstexturefailures.log", "%s\n", filename); + return NULL; // not found + } + + if (ddsfilesize <= 128 || memcmp(dds, "DDS ", 4) || ddssize < (unsigned int)BuffLittleLong(dds+4) || BuffLittleLong(dds+76) != 32) + { + Mem_Free(dds); + Con_Printf("^1%s: not a DDS image\n", filename); + return NULL; + } + + //dds_flags = BuffLittleLong(dds+8); + dds_format_flags = BuffLittleLong(dds+80); + dds_miplevels = (BuffLittleLong(dds+108) & 0x400000) ? BuffLittleLong(dds+28) : 1; + dds_width = BuffLittleLong(dds+16); + dds_height = BuffLittleLong(dds+12); + ddspixels = dds + 128; + + if(r_texture_dds_load_alphamode.integer == 0) + if(!(dds_format_flags & 0x1)) // DDPF_ALPHAPIXELS + flags &= ~TEXF_ALPHA; + + //flags &= ~TEXF_ALPHA; // disabled, as we DISABLE TEXF_ALPHA in the alpha detection, not enable it! + if ((dds_format_flags & 0x40) && BuffLittleLong(dds+88) == 32) + { + // very sloppy BGRA 32bit identification + textype = TEXTYPE_BGRA; + flags &= ~TEXF_COMPRESS; // don't let the textype be wrong + bytesperblock = 0; + bytesperpixel = 4; + size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(dds_width, dds_height), bytesperpixel); + if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) + { + Mem_Free(dds); + Con_Printf("^1%s: invalid BGRA DDS image\n", filename); + return NULL; + } + if((r_texture_dds_load_alphamode.integer == 1) && (flags & TEXF_ALPHA)) + { + // check alpha + for (i = 3;i < size;i += 4) + if (ddspixels[i] < 255) + break; + if (i >= size) + flags &= ~TEXF_ALPHA; + } + } + else if (!memcmp(dds+84, "DXT1", 4)) + { + // we need to find out if this is DXT1 (opaque) or DXT1A (transparent) + // LordHavoc: it is my belief that this does not infringe on the + // patent because it is not decoding pixels... + textype = TEXTYPE_DXT1; + bytesperblock = 8; + bytesperpixel = 0; + //size = ((dds_width+3)/4)*((dds_height+3)/4)*bytesperblock; + size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); + if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) + { + Mem_Free(dds); + Con_Printf("^1%s: invalid DXT1 DDS image\n", filename); + return NULL; + } + if(r_texture_dds_load_alphamode.integer && (flags & TEXF_ALPHA)) + { + if(r_texture_dds_load_alphamode.integer == 1) + { + // check alpha + for (i = 0;i < size;i += bytesperblock) + if (ddspixels[i+0] + ddspixels[i+1] * 256 <= ddspixels[i+2] + ddspixels[i+3] * 256) + { + // NOTE: this assumes sizeof(unsigned int) == 4 + unsigned int data = * (unsigned int *) &(ddspixels[i+4]); + // check if data, in base 4, contains a digit 3 (DXT1: transparent pixel) + if(data & (data<<1) & 0xAAAAAAAA)//rgh + break; + } + if (i < size) + textype = TEXTYPE_DXT1A; + else + flags &= ~TEXF_ALPHA; + } + else + { + flags &= ~TEXF_ALPHA; + } + } + } + else if (!memcmp(dds+84, "DXT3", 4) || !memcmp(dds+84, "DXT2", 4)) + { + if(!memcmp(dds+84, "DXT2", 4)) + { + if(!(flags & TEXF_RGBMULTIPLYBYALPHA)) + { + Con_Printf("^1%s: expecting DXT3 image without premultiplied alpha, got DXT2 image with premultiplied alpha\n", filename); + } + } + else + { + if(flags & TEXF_RGBMULTIPLYBYALPHA) + { + Con_Printf("^1%s: expecting DXT2 image without premultiplied alpha, got DXT3 image without premultiplied alpha\n", filename); + } + } + textype = TEXTYPE_DXT3; + bytesperblock = 16; + bytesperpixel = 0; + size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); + if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) + { + Mem_Free(dds); + Con_Printf("^1%s: invalid DXT3 DDS image\n", filename); + return NULL; + } + // we currently always assume alpha + } + else if (!memcmp(dds+84, "DXT5", 4) || !memcmp(dds+84, "DXT4", 4)) + { + if(!memcmp(dds+84, "DXT4", 4)) + { + if(!(flags & TEXF_RGBMULTIPLYBYALPHA)) + { + Con_Printf("^1%s: expecting DXT5 image without premultiplied alpha, got DXT4 image with premultiplied alpha\n", filename); + } + } + else + { + if(flags & TEXF_RGBMULTIPLYBYALPHA) + { + Con_Printf("^1%s: expecting DXT4 image without premultiplied alpha, got DXT5 image without premultiplied alpha\n", filename); + } + } + textype = TEXTYPE_DXT5; + bytesperblock = 16; + bytesperpixel = 0; + size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); + if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) + { + Mem_Free(dds); + Con_Printf("^1%s: invalid DXT5 DDS image\n", filename); + return NULL; + } + // we currently always assume alpha + } + else + { + Mem_Free(dds); + Con_Printf("^1%s: unrecognized/unsupported DDS format\n", filename); + return NULL; + } + + force_swdecode = false; + if(bytesperblock) + { + if(vid.support.arb_texture_compression && vid.support.ext_texture_compression_s3tc) + { + if(r_texture_dds_swdecode.integer > 1) + force_swdecode = true; + } + else + { + if(r_texture_dds_swdecode.integer < 1) + { + // unsupported + Mem_Free(dds); + return NULL; + } + force_swdecode = true; + } + } + + // return whether this texture is transparent + if (hasalphaflag) + *hasalphaflag = (flags & TEXF_ALPHA) != 0; + + // if we SW decode, choose 2 sizes bigger + if(force_swdecode) + { + // this is quarter res, so do not scale down more than we have to + miplevel -= 2; + + if(miplevel < 0) + Con_DPrintf("WARNING: fake software decoding of compressed texture %s degraded quality\n", filename); + } + + // this is where we apply gl_picmip + mippixels_start = ddspixels; + mipwidth = dds_width; + mipheight = dds_height; + while(miplevel >= 1 && dds_miplevels >= 1) + { + if (mipwidth <= 1 && mipheight <= 1) + break; + mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; + mippixels_start += mipsize; // just skip + --dds_miplevels; + --miplevel; + if (mipwidth > 1) + mipwidth >>= 1; + if (mipheight > 1) + mipheight >>= 1; + } + mipsize_total = ddssize - 128 - (mippixels_start - ddspixels); + mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; + + // from here on, we do not need the ddspixels and ddssize any more (apart from the statistics entry in glt) + + // fake decode S3TC if needed + if(force_swdecode) + { + int mipsize_new = mipsize_total / bytesperblock * 4; + unsigned char *mipnewpixels = (unsigned char *) Mem_Alloc(tempmempool, mipsize_new); + unsigned char *p = mipnewpixels; + for (i = bytesperblock == 16 ? 8 : 0;i < (int)mipsize_total;i += bytesperblock, p += 4) + { + c = mippixels_start[i] + 256*mippixels_start[i+1] + 65536*mippixels_start[i+2] + 16777216*mippixels_start[i+3]; + p[2] = (((c >> 11) & 0x1F) + ((c >> 27) & 0x1F)) * (0.5f / 31.0f * 255.0f); + p[1] = (((c >> 5) & 0x3F) + ((c >> 21) & 0x3F)) * (0.5f / 63.0f * 255.0f); + p[0] = (((c ) & 0x1F) + ((c >> 16) & 0x1F)) * (0.5f / 31.0f * 255.0f); + if(textype == TEXTYPE_DXT5) + p[3] = (0.5 * mippixels_start[i-8] + 0.5 * mippixels_start[i-7]); + else if(textype == TEXTYPE_DXT3) + p[3] = ( + (mippixels_start[i-8] & 0x0F) + + (mippixels_start[i-8] >> 4) + + (mippixels_start[i-7] & 0x0F) + + (mippixels_start[i-7] >> 4) + + (mippixels_start[i-6] & 0x0F) + + (mippixels_start[i-6] >> 4) + + (mippixels_start[i-5] & 0x0F) + + (mippixels_start[i-5] >> 4) + ) * (0.125f / 15.0f * 255.0f); + else + p[3] = 255; + } + + textype = TEXTYPE_BGRA; + bytesperblock = 0; + bytesperpixel = 4; + + // as each block becomes a pixel, we must use pixel count for this + mipwidth = (mipwidth + 3) / 4; + mipheight = (mipheight + 3) / 4; + mipsize = bytesperpixel * mipwidth * mipheight; + mippixels_start = mipnewpixels; + mipsize_total = mipsize_new; + } + + // start mip counting + mippixels = mippixels_start; + + // calculate average color if requested + if (avgcolor) + { + float f; + Vector4Clear(avgcolor); + if (bytesperblock) + { + for (i = bytesperblock == 16 ? 8 : 0;i < mipsize;i += bytesperblock) + { + c = mippixels[i] + 256*mippixels[i+1] + 65536*mippixels[i+2] + 16777216*mippixels[i+3]; + avgcolor[0] += ((c >> 11) & 0x1F) + ((c >> 27) & 0x1F); + avgcolor[1] += ((c >> 5) & 0x3F) + ((c >> 21) & 0x3F); + avgcolor[2] += ((c ) & 0x1F) + ((c >> 16) & 0x1F); + if(textype == TEXTYPE_DXT5) + avgcolor[3] += (0.5 * mippixels[i-8] + 0.5 * mippixels[i-7]); + else if(textype == TEXTYPE_DXT3) + avgcolor[3] += ( + (mippixels_start[i-8] & 0x0F) + + (mippixels_start[i-8] >> 4) + + (mippixels_start[i-7] & 0x0F) + + (mippixels_start[i-7] >> 4) + + (mippixels_start[i-6] & 0x0F) + + (mippixels_start[i-6] >> 4) + + (mippixels_start[i-5] & 0x0F) + + (mippixels_start[i-5] >> 4) + ) * (0.125f / 15.0f * 255.0f); + else + avgcolor[3] += 255; + } + f = (float)bytesperblock / size; + avgcolor[0] *= (0.5f / 31.0f) * f; + avgcolor[1] *= (0.5f / 63.0f) * f; + avgcolor[2] *= (0.5f / 31.0f) * f; + avgcolor[3] *= f; + } + else + { + for (i = 0;i < mipsize;i += 4) + { + avgcolor[0] += mippixels[i+2]; + avgcolor[1] += mippixels[i+1]; + avgcolor[2] += mippixels[i]; + avgcolor[3] += mippixels[i+3]; + } + f = (1.0f / 255.0f) * bytesperpixel / size; + avgcolor[0] *= f; + avgcolor[1] *= f; + avgcolor[2] *= f; + avgcolor[3] *= f; + } + } + + // when not requesting mipmaps, do not load them + if(!(flags & TEXF_MIPMAP)) + dds_miplevels = 0; + + if (dds_miplevels >= 1) + flags |= TEXF_MIPMAP; + else + flags &= ~TEXF_MIPMAP; + + texinfo = R_GetTexTypeInfo(textype, flags); + + glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); + strlcpy (glt->identifier, filename, sizeof(glt->identifier)); + glt->pool = pool; + glt->chain = pool->gltchain; + pool->gltchain = glt; + glt->inputwidth = mipwidth; + glt->inputheight = mipheight; + glt->inputdepth = 1; + glt->flags = flags; + glt->textype = texinfo; + glt->texturetype = GLTEXTURETYPE_2D; + glt->inputdatasize = ddssize; + glt->glinternalformat = texinfo->glinternalformat; + glt->glformat = texinfo->glformat; + glt->gltype = texinfo->gltype; + glt->bytesperpixel = texinfo->internalbytesperpixel; + glt->sides = 1; + glt->gltexturetypeenum = gltexturetypeenums[glt->texturetype]; + glt->tilewidth = mipwidth; + glt->tileheight = mipheight; + glt->tiledepth = 1; + glt->miplevels = dds_miplevels; + + // texture uploading can take a while, so make sure we're sending keepalives + CL_KeepaliveMessage(false); + + // create the texture object + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + GL_ActiveTexture(0); + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglGenTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DFORMAT d3dformat; + D3DPOOL d3dpool; + DWORD d3dusage; + switch(textype) + { + case TEXTYPE_BGRA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; + case TEXTYPE_DXT1: case TEXTYPE_DXT1A: d3dformat = D3DFMT_DXT1;break; + case TEXTYPE_DXT3: d3dformat = D3DFMT_DXT3;break; + case TEXTYPE_DXT5: d3dformat = D3DFMT_DXT5;break; + default: d3dformat = D3DFMT_A8R8G8B8;Host_Error("R_LoadTextureDDSFile: unsupported texture type %i when picking D3DFMT", (int)textype);break; + } + d3dusage = 0; + d3dpool = D3DPOOL_MANAGED; + IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, d3dusage, d3dformat, d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + glt->texnum = DPSOFTRAST_Texture_New(((glt->flags & TEXF_CLAMP) ? DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE : 0) | (dds_miplevels > 1 ? DPSOFTRAST_TEXTURE_FLAG_MIPMAP : 0), glt->tilewidth, glt->tileheight, glt->tiledepth); + break; + } + + // upload the texture + // we need to restore the texture binding after finishing the upload + mipcomplete = false; + + for (mip = 0;mip <= dds_miplevels;mip++) // <= to include the not-counted "largest" miplevel + { + mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; + if (mippixels + mipsize > mippixels_start + mipsize_total) + break; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (bytesperblock) + { + qglCompressedTexImage2DARB(GL_TEXTURE_2D, mip, glt->glinternalformat, mipwidth, mipheight, 0, mipsize, mippixels);CHECKGLERROR + } + else + { + qglTexImage2D(GL_TEXTURE_2D, mip, glt->glinternalformat, mipwidth, mipheight, 0, glt->glformat, glt->gltype, mippixels);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DLOCKED_RECT d3dlockedrect; + if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + memcpy(d3dlockedrect.pBits, mippixels, mipsize); + IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); + } + break; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (bytesperblock) + Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + else + DPSOFTRAST_Texture_UpdateFull(glt->texnum, mippixels); + // DPSOFTRAST calculates its own mipmaps + mip = dds_miplevels; + break; + } + mippixels += mipsize; + if (mipwidth <= 1 && mipheight <= 1) + { + mipcomplete = true; + break; + } + if (mipwidth > 1) + mipwidth >>= 1; + if (mipheight > 1) + mipheight >>= 1; + } + + // after upload we have to set some parameters... + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (dds_miplevels >= 1 && !mipcomplete) + { + // need to set GL_TEXTURE_MAX_LEVEL + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAX_LEVEL, dds_miplevels - 1);CHECKGLERROR + } + GL_SetupTextureParameters(glt->flags, glt->textype->textype, glt->texturetype); + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + glt->d3daddressw = 0; + if (glt->flags & TEXF_CLAMP) + { + glt->d3daddressu = D3DTADDRESS_CLAMP; + glt->d3daddressv = D3DTADDRESS_CLAMP; + if (glt->tiledepth > 1) + glt->d3daddressw = D3DTADDRESS_CLAMP; + } + else + { + glt->d3daddressu = D3DTADDRESS_WRAP; + glt->d3daddressv = D3DTADDRESS_WRAP; + if (glt->tiledepth > 1) + glt->d3daddressw = D3DTADDRESS_WRAP; + } + glt->d3dmipmaplodbias = 0; + glt->d3dmaxmiplevel = 0; + glt->d3dmaxmiplevelfilter = 0; + if (glt->flags & TEXF_MIPMAP) + { + glt->d3dminfilter = d3d_filter_mipmin; + glt->d3dmagfilter = d3d_filter_mipmag; + glt->d3dmipfilter = d3d_filter_mipmix; + } + else + { + glt->d3dminfilter = d3d_filter_flatmin; + glt->d3dmagfilter = d3d_filter_flatmag; + glt->d3dmipfilter = d3d_filter_flatmix; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (glt->flags & TEXF_FORCELINEAR) + DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_LINEAR); + else if (glt->flags & TEXF_FORCENEAREST) + DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_NEAREST); + else if (glt->flags & TEXF_MIPMAP) + DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_mipmap); + else + DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_nomipmap); + break; + } + + Mem_Free(dds); + if(force_swdecode) + Mem_Free((unsigned char *) mippixels_start); + return (rtexture_t *)glt; +} + +int R_TextureWidth(rtexture_t *rt) +{ + return rt ? ((gltexture_t *)rt)->inputwidth : 0; +} + +int R_TextureHeight(rtexture_t *rt) +{ + return rt ? ((gltexture_t *)rt)->inputheight : 0; +} + +void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth) +{ + gltexture_t *glt = (gltexture_t *)rt; + if (data == NULL) + Host_Error("R_UpdateTexture: no data supplied"); + if (glt == NULL) + Host_Error("R_UpdateTexture: no texture supplied"); + if (!glt->texnum && !glt->d3dtexture) + { + Con_DPrintf("R_UpdateTexture: texture %p \"%s\" in pool %p has not been uploaded yet\n", (void *)glt, glt->identifier, (void *)glt->pool); + return; + } + // update part of the texture + if (glt->bufferpixels) + { + int j; + int bpp = glt->bytesperpixel; + int inputskip = width*bpp; + int outputskip = glt->tilewidth*bpp; + const unsigned char *input = data; + unsigned char *output = glt->bufferpixels; + if (glt->inputdepth != 1 || glt->sides != 1) + Sys_Error("R_UpdateTexture on buffered texture that is not 2D\n"); + if (x < 0) + { + width += x; + input -= x*bpp; + x = 0; + } + if (y < 0) + { + height += y; + input -= y*inputskip; + y = 0; + } + if (width > glt->tilewidth - x) + width = glt->tilewidth - x; + if (height > glt->tileheight - y) + height = glt->tileheight - y; + if (width < 1 || height < 1) + return; + glt->dirty = true; + glt->buffermodified = true; + output += y*outputskip + x*bpp; + for (j = 0;j < height;j++, output += outputskip, input += inputskip) + memcpy(output, input, width*bpp); + } + else if (x || y || z || width != glt->inputwidth || height != glt->inputheight || depth != glt->inputdepth) + R_UploadPartialTexture(glt, data, x, y, z, width, height, depth); + else + R_UploadFullTexture(glt, data); +} + +int R_RealGetTexture(rtexture_t *rt) +{ + if (rt) + { + gltexture_t *glt; + glt = (gltexture_t *)rt; + if (glt->flags & GLTEXF_DYNAMIC) + R_UpdateDynamicTexture(glt); + if (glt->buffermodified && glt->bufferpixels) + { + glt->buffermodified = false; + R_UploadFullTexture(glt, glt->bufferpixels); + } + glt->dirty = false; + return glt->texnum; + } + else + return 0; +} + +void R_ClearTexture (rtexture_t *rt) +{ + gltexture_t *glt = (gltexture_t *)rt; + + R_UploadFullTexture(glt, NULL); +} + +int R_PicmipForFlags(int flags) +{ + int miplevel = 0; + if(flags & TEXF_PICMIP) + { + miplevel += gl_picmip.integer; + if (flags & TEXF_ISWORLD) + { + if (r_picmipworld.integer) + miplevel += gl_picmip_world.integer; + else + miplevel = 0; + } + else if (flags & TEXF_ISSPRITE) + { + if (r_picmipsprites.integer) + miplevel += gl_picmip_sprites.integer; + else + miplevel = 0; + } + else + miplevel += gl_picmip_other.integer; + } + return max(0, miplevel); +} diff --git a/misc/source/darkplaces-src/glquake.h b/misc/source/darkplaces-src/glquake.h new file mode 100644 index 00000000..8313cf67 --- /dev/null +++ b/misc/source/darkplaces-src/glquake.h @@ -0,0 +1,1017 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef GLQUAKE_H +#define GLQUAKE_H + +// disable data conversion warnings + +#ifdef _MSC_VER +#pragma warning(disable : 4310) // LordHavoc: MSVC++ 2008 x86: cast truncates constant value +#pragma warning(disable : 4245) // LordHavoc: MSVC++ 2008 x86: 'initializing' : conversion from 'int' to 'unsigned char', signed/unsigned mismatch +#pragma warning(disable : 4204) // LordHavoc: MSVC++ 2008 x86: nonstandard extension used : non-constant aggregate initializer +#pragma warning(disable : 4267) // LordHavoc: MSVC++ 2008 x64, conversion from 'size_t' to 'int', possible loss of data +//#pragma warning(disable : 4244) // LordHavoc: MSVC++ 4 x86, double/float +//#pragma warning(disable : 4305) // LordHavoc: MSVC++ 6 x86, double/float +//#pragma warning(disable : 4706) // LordHavoc: MSVC++ 2008 x86, assignment within conditional expression +//#pragma warning(disable : 4127) // LordHavoc: MSVC++ 2008 x86, conditional expression is constant +//#pragma warning(disable : 4100) // LordHavoc: MSVC++ 2008 x86, unreferenced formal parameter +//#pragma warning(disable : 4055) // LordHavoc: MSVC++ 2008 x86, 'type cast' from data pointer to function pointer +//#pragma warning(disable : 4054) // LordHavoc: MSVC++ 2008 x86, 'type cast' from function pointer to data pointer +#endif + + +//==================================================== + +// wgl uses APIENTRY +#ifndef APIENTRY +#define APIENTRY +#endif + +// for platforms (wgl) that do not use GLAPIENTRY +#ifndef GLAPIENTRY +#define GLAPIENTRY APIENTRY +#endif + +#ifndef GL_PROJECTION +#include + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +// 1-byte signed +typedef signed char GLbyte; +// 2-byte signed +typedef short GLshort; +// 4-byte signed +typedef int GLint; +// 1-byte unsigned +typedef unsigned char GLubyte; +// 2-byte unsigned +typedef unsigned short GLushort; +// 4-byte unsigned +typedef unsigned int GLuint; +// 4-byte signed +typedef int GLsizei; +// single precision float +typedef float GLfloat; +// single precision float in [0,1] +typedef float GLclampf; +// double precision float +typedef double GLdouble; +// double precision float in [0,1] +typedef double GLclampd; +// int whose size is the same as a pointer (?) +typedef ptrdiff_t GLintptrARB; +// int whose size is the same as a pointer (?) +typedef ptrdiff_t GLsizeiptrARB; + +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 + +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +#define GL_DEPTH_TEST 0x0B71 + +#define GL_CULL_FACE 0x0B44 + +#define GL_BLEND 0x0BE2 +#define GL_ALPHA_TEST 0x0BC0 + +#define GL_ZERO 0x0 +#define GL_ONE 0x1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#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_TEXTURE_ENV 0x2300 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#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_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 + +#define GL_ADD 0x0104 +#define GL_DECAL 0x2101 +#define GL_MODULATE 0x2100 + +#define GL_REPEAT 0x2901 +#define GL_CLAMP 0x2900 + +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +#define GL_FALSE 0x0 +#define GL_TRUE 0x1 + +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 + +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +//#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +//#define GL_EDGE_FLAG_ARRAY 0x8079 + +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C + +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +#define GL_NO_ERROR 0x0 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +#define GL_DITHER 0x0BD0 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 + +#define GL_MAX_TEXTURE_SIZE 0x0D33 + +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 + +#define GL_RED_SCALE 0x0D14 +#define GL_GREEN_SCALE 0x0D18 +#define GL_BLUE_SCALE 0x0D1A +#define GL_ALPHA_SCALE 0x0D1C + +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 + +#define GL_STENCIL_TEST 0x0B90 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 + +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +#define GL_POINT_SMOOTH 0x0B10 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_POLYGON_SMOOTH 0x0B41 + +#define GL_POLYGON_STIPPLE 0x0B42 + +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 + +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_VIEWPORT 0x0BA2 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_LUMINANCE 0x1909 +#define GL_INTENSITY 0x8049 + +#endif + +//GL_EXT_texture_filter_anisotropic +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +// GL_ARB_depth_texture +#ifndef GL_DEPTH_COMPONENT32_ARB +#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 + +// GL_ARB_shadow +#ifndef GL_TEXTURE_COMPARE_MODE_ARB +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +// GL_ARB_multitexture +extern void (GLAPIENTRY *qglMultiTexCoord1f) (GLenum, GLfloat); +extern void (GLAPIENTRY *qglMultiTexCoord2f) (GLenum, GLfloat, GLfloat); +extern void (GLAPIENTRY *qglMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); +extern void (GLAPIENTRY *qglMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (GLAPIENTRY *qglActiveTexture) (GLenum); +extern void (GLAPIENTRY *qglClientActiveTexture) (GLenum); +#ifndef GL_ACTIVE_TEXTURE_ARB +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#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 +#endif + +// GL_ARB_texture_env_combine +#ifndef GL_COMBINE_ARB +#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_MAX_ELEMENTS_VERTICES +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#endif +#ifndef GL_MAX_ELEMENTS_INDICES +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#endif + + +#ifndef GL_TEXTURE_3D +#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_TEXTURE_BINDING_3D 0x806A +extern void (GLAPIENTRY *qglTexImage3D)(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void (GLAPIENTRY *qglTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +extern void (GLAPIENTRY *qglCopyTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB +#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_DEPTH_COMPONENT16_ARB +#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_SCISSOR_TEST +#define GL_SCISSOR_TEST 0x0C11 +#define GL_SCISSOR_BOX 0x0C10 +#endif + +// GL_SGIS_texture_edge_clamp or GL_EXT_texture_edge_clamp +#ifndef GL_CLAMP_TO_EDGE +#define GL_CLAMP_TO_EDGE 0x812F +#endif + +//GL_ATI_separate_stencil +#ifndef GL_STENCIL_BACK_FUNC +#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 +#endif +extern void (GLAPIENTRY *qglStencilOpSeparate)(GLenum, GLenum, GLenum, GLenum); +extern void (GLAPIENTRY *qglStencilFuncSeparate)(GLenum, GLenum, GLint, GLuint); + +//GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +extern void (GLAPIENTRY *qglActiveStencilFaceEXT)(GLenum); + +//GL_EXT_blend_minmax +#ifndef GL_FUNC_ADD_EXT +#define GL_FUNC_ADD_EXT 0x8006 // also supplied by GL_EXT_blend_subtract +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 // also supplied by GL_EXT_blend_subtract +extern void (GLAPIENTRY *qglBlendEquationEXT)(GLenum); // also supplied by GL_EXT_blend_subtract +#endif + +//GL_EXT_blend_subtract +#ifndef GL_FUNC_SUBTRACT_EXT +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +extern void (GLAPIENTRY *qglBlendEquationEXT)(GLenum); // also supplied by GL_EXT_blend_subtract +#endif + +//GL_ARB_texture_non_power_of_two + +//GL_ARB_vertex_buffer_object +#ifndef GL_ARRAY_BUFFER_ARB +#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_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 +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#endif +extern void (GLAPIENTRY *qglBindBufferARB) (GLenum target, GLuint buffer); +extern void (GLAPIENTRY *qglDeleteBuffersARB) (GLsizei n, const GLuint *buffers); +extern void (GLAPIENTRY *qglGenBuffersARB) (GLsizei n, GLuint *buffers); +extern GLboolean (GLAPIENTRY *qglIsBufferARB) (GLuint buffer); +extern GLvoid* (GLAPIENTRY *qglMapBufferARB) (GLenum target, GLenum access); +extern GLboolean (GLAPIENTRY *qglUnmapBufferARB) (GLenum target); +extern void (GLAPIENTRY *qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +extern void (GLAPIENTRY *qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); + +//GL_EXT_framebuffer_object +#ifndef GL_FRAMEBUFFER_EXT +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#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_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#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 +#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_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_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#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_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#endif +extern GLboolean (GLAPIENTRY *qglIsRenderbufferEXT)(GLuint renderbuffer); +extern void (GLAPIENTRY *qglBindRenderbufferEXT)(GLenum target, GLuint renderbuffer); +extern void (GLAPIENTRY *qglDeleteRenderbuffersEXT)(GLsizei n, const GLuint *renderbuffers); +extern void (GLAPIENTRY *qglGenRenderbuffersEXT)(GLsizei n, GLuint *renderbuffers); +extern void (GLAPIENTRY *qglRenderbufferStorageEXT)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +extern void (GLAPIENTRY *qglGetRenderbufferParameterivEXT)(GLenum target, GLenum pname, GLint *params); +extern GLboolean (GLAPIENTRY *qglIsFramebufferEXT)(GLuint framebuffer); +extern void (GLAPIENTRY *qglBindFramebufferEXT)(GLenum target, GLuint framebuffer); +extern void (GLAPIENTRY *qglDeleteFramebuffersEXT)(GLsizei n, const GLuint *framebuffers); +extern void (GLAPIENTRY *qglGenFramebuffersEXT)(GLsizei n, GLuint *framebuffers); +extern GLenum (GLAPIENTRY *qglCheckFramebufferStatusEXT)(GLenum target); +//extern void (GLAPIENTRY *qglFramebufferTexture1DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +extern void (GLAPIENTRY *qglFramebufferTexture2DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +extern void (GLAPIENTRY *qglFramebufferTexture3DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +extern void (GLAPIENTRY *qglFramebufferRenderbufferEXT)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +extern void (GLAPIENTRY *qglGetFramebufferAttachmentParameterivEXT)(GLenum target, GLenum attachment, GLenum pname, GLint *params); +extern void (GLAPIENTRY *qglGenerateMipmapEXT)(GLenum target); + +// GL_ARB_draw_buffers +#ifndef GL_MAX_DRAW_BUFFERS_ARB +#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 +extern void (GLAPIENTRY *qglDrawBuffersARB)(GLsizei n, const GLenum *bufs); + +// GL_ARB_texture_float +#ifndef GL_RGBA32F_ARB +#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 + +// GL_EXT_texture_sRGB +#ifndef GL_SRGB_EXT +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif + +extern void (GLAPIENTRY *qglScissor)(GLint x, GLint y, GLsizei width, GLsizei height); + +extern void (GLAPIENTRY *qglClearColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + +extern void (GLAPIENTRY *qglClear)(GLbitfield mask); + +extern void (GLAPIENTRY *qglAlphaFunc)(GLenum func, GLclampf ref); +extern void (GLAPIENTRY *qglBlendFunc)(GLenum sfactor, GLenum dfactor); +extern void (GLAPIENTRY *qglCullFace)(GLenum mode); + +extern void (GLAPIENTRY *qglDrawBuffer)(GLenum mode); +extern void (GLAPIENTRY *qglReadBuffer)(GLenum mode); +extern void (GLAPIENTRY *qglEnable)(GLenum cap); +extern void (GLAPIENTRY *qglDisable)(GLenum cap); +extern GLboolean (GLAPIENTRY *qglIsEnabled)(GLenum cap); + +extern void (GLAPIENTRY *qglEnableClientState)(GLenum cap); +extern void (GLAPIENTRY *qglDisableClientState)(GLenum cap); + +extern void (GLAPIENTRY *qglGetBooleanv)(GLenum pname, GLboolean *params); +extern void (GLAPIENTRY *qglGetDoublev)(GLenum pname, GLdouble *params); +extern void (GLAPIENTRY *qglGetFloatv)(GLenum pname, GLfloat *params); +extern void (GLAPIENTRY *qglGetIntegerv)(GLenum pname, GLint *params); + +extern GLenum (GLAPIENTRY *qglGetError)(void); +extern const GLubyte* (GLAPIENTRY *qglGetString)(GLenum name); +extern void (GLAPIENTRY *qglFinish)(void); +extern void (GLAPIENTRY *qglFlush)(void); + +extern void (GLAPIENTRY *qglClearDepth)(GLclampd depth); +extern void (GLAPIENTRY *qglDepthFunc)(GLenum func); +extern void (GLAPIENTRY *qglDepthMask)(GLboolean flag); +extern void (GLAPIENTRY *qglDepthRange)(GLclampd near_val, GLclampd far_val); +extern void (GLAPIENTRY *qglColorMask)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); + +extern void (GLAPIENTRY *qglDrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +extern void (GLAPIENTRY *qglDrawElements)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern void (GLAPIENTRY *qglDrawArrays)(GLenum mode, GLint first, GLsizei count); +extern void (GLAPIENTRY *qglVertexPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +extern void (GLAPIENTRY *qglNormalPointer)(GLenum type, GLsizei stride, const GLvoid *ptr); +extern void (GLAPIENTRY *qglColorPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +extern void (GLAPIENTRY *qglTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +extern void (GLAPIENTRY *qglArrayElement)(GLint i); + +extern void (GLAPIENTRY *qglColor4ub)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void (GLAPIENTRY *qglColor4f)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void (GLAPIENTRY *qglTexCoord1f)(GLfloat s); +extern void (GLAPIENTRY *qglTexCoord2f)(GLfloat s, GLfloat t); +extern void (GLAPIENTRY *qglTexCoord3f)(GLfloat s, GLfloat t, GLfloat r); +extern void (GLAPIENTRY *qglTexCoord4f)(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void (GLAPIENTRY *qglVertex2f)(GLfloat x, GLfloat y); +extern void (GLAPIENTRY *qglVertex3f)(GLfloat x, GLfloat y, GLfloat z); +extern void (GLAPIENTRY *qglVertex4f)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void (GLAPIENTRY *qglBegin)(GLenum mode); +extern void (GLAPIENTRY *qglEnd)(void); + +extern void (GLAPIENTRY *qglMatrixMode)(GLenum mode); +//extern void (GLAPIENTRY *qglOrtho)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); +//extern void (GLAPIENTRY *qglFrustum)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); +extern void (GLAPIENTRY *qglViewport)(GLint x, GLint y, GLsizei width, GLsizei height); +//extern void (GLAPIENTRY *qglPushMatrix)(void); +//extern void (GLAPIENTRY *qglPopMatrix)(void); +extern void (GLAPIENTRY *qglLoadIdentity)(void); +//extern void (GLAPIENTRY *qglLoadMatrixd)(const GLdouble *m); +extern void (GLAPIENTRY *qglLoadMatrixf)(const GLfloat *m); +//extern void (GLAPIENTRY *qglMultMatrixd)(const GLdouble *m); +//extern void (GLAPIENTRY *qglMultMatrixf)(const GLfloat *m); +//extern void (GLAPIENTRY *qglRotated)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +//extern void (GLAPIENTRY *qglRotatef)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +//extern void (GLAPIENTRY *qglScaled)(GLdouble x, GLdouble y, GLdouble z); +//extern void (GLAPIENTRY *qglScalef)(GLfloat x, GLfloat y, GLfloat z); +//extern void (GLAPIENTRY *qglTranslated)(GLdouble x, GLdouble y, GLdouble z); +//extern void (GLAPIENTRY *qglTranslatef)(GLfloat x, GLfloat y, GLfloat z); + +extern void (GLAPIENTRY *qglReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); + +extern void (GLAPIENTRY *qglStencilFunc)(GLenum func, GLint ref, GLuint mask); +extern void (GLAPIENTRY *qglStencilMask)(GLuint mask); +extern void (GLAPIENTRY *qglStencilOp)(GLenum fail, GLenum zfail, GLenum zpass); +extern void (GLAPIENTRY *qglClearStencil)(GLint s); + +extern void (GLAPIENTRY *qglTexEnvf)(GLenum target, GLenum pname, GLfloat param); +extern void (GLAPIENTRY *qglTexEnvfv)(GLenum target, GLenum pname, const GLfloat *params); +extern void (GLAPIENTRY *qglTexEnvi)(GLenum target, GLenum pname, GLint param); +extern void (GLAPIENTRY *qglTexParameterf)(GLenum target, GLenum pname, GLfloat param); +extern void (GLAPIENTRY *qglTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); +extern void (GLAPIENTRY *qglTexParameteri)(GLenum target, GLenum pname, GLint param); +extern void (GLAPIENTRY *qglGetTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); +extern void (GLAPIENTRY *qglGetTexParameteriv)(GLenum target, GLenum pname, GLint *params); +extern void (GLAPIENTRY *qglGetTexLevelParameterfv)(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern void (GLAPIENTRY *qglGetTexLevelParameteriv)(GLenum target, GLint level, GLenum pname, GLint *params); +extern void (GLAPIENTRY *qglGetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern void (GLAPIENTRY *qglHint)(GLenum target, GLenum mode); + +extern void (GLAPIENTRY *qglGenTextures)(GLsizei n, GLuint *textures); +extern void (GLAPIENTRY *qglDeleteTextures)(GLsizei n, const GLuint *textures); +extern void (GLAPIENTRY *qglBindTexture)(GLenum target, GLuint texture); +//extern void (GLAPIENTRY *qglPrioritizeTextures)(GLsizei n, const GLuint *textures, const GLclampf *priorities); +//extern GLboolean (GLAPIENTRY *qglAreTexturesResident)(GLsizei n, const GLuint *textures, GLboolean *residences); +//extern GLboolean (GLAPIENTRY *qglIsTexture)(GLuint texture); +//extern void (GLAPIENTRY *qglPixelStoref)(GLenum pname, GLfloat param); +extern void (GLAPIENTRY *qglPixelStorei)(GLenum pname, GLint param); + +//extern void (GLAPIENTRY *qglTexImage1D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void (GLAPIENTRY *qglTexImage2D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +//extern void (GLAPIENTRY *qglTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern void (GLAPIENTRY *qglTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +//extern void (GLAPIENTRY *qglCopyTexImage1D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +extern void (GLAPIENTRY *qglCopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +//extern void (GLAPIENTRY *qglCopyTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void (GLAPIENTRY *qglCopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + +extern void (GLAPIENTRY *qglPolygonOffset)(GLfloat factor, GLfloat units); +extern void (GLAPIENTRY *qglPolygonMode)(GLenum face, GLenum mode); + +//extern void (GLAPIENTRY *qglClipPlane)(GLenum plane, const GLdouble *equation); +//extern void (GLAPIENTRY *qglGetClipPlane)(GLenum plane, GLdouble *equation); + +//[515]: added on 29.07.2005 +extern void (GLAPIENTRY *qglLineWidth)(GLfloat width); +extern void (GLAPIENTRY *qglPointSize)(GLfloat size); + +// GL 2.0 shader objects +#ifndef GL_PROGRAM_OBJECT +// 1-byte character string +typedef char GLchar; +#endif +extern void (GLAPIENTRY *qglDeleteShader)(GLuint obj); +extern void (GLAPIENTRY *qglDeleteProgram)(GLuint obj); +//extern GLuint (GLAPIENTRY *qglGetHandle)(GLenum pname); +extern void (GLAPIENTRY *qglDetachShader)(GLuint containerObj, GLuint attachedObj); +extern GLuint (GLAPIENTRY *qglCreateShader)(GLenum shaderType); +extern void (GLAPIENTRY *qglShaderSource)(GLuint shaderObj, GLsizei count, const GLchar **string, const GLint *length); +extern void (GLAPIENTRY *qglCompileShader)(GLuint shaderObj); +extern GLuint (GLAPIENTRY *qglCreateProgram)(void); +extern void (GLAPIENTRY *qglAttachShader)(GLuint containerObj, GLuint obj); +extern void (GLAPIENTRY *qglLinkProgram)(GLuint programObj); +extern void (GLAPIENTRY *qglUseProgram)(GLuint programObj); +extern void (GLAPIENTRY *qglValidateProgram)(GLuint programObj); +extern void (GLAPIENTRY *qglUniform1f)(GLint location, GLfloat v0); +extern void (GLAPIENTRY *qglUniform2f)(GLint location, GLfloat v0, GLfloat v1); +extern void (GLAPIENTRY *qglUniform3f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +extern void (GLAPIENTRY *qglUniform4f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +extern void (GLAPIENTRY *qglUniform1i)(GLint location, GLint v0); +extern void (GLAPIENTRY *qglUniform2i)(GLint location, GLint v0, GLint v1); +extern void (GLAPIENTRY *qglUniform3i)(GLint location, GLint v0, GLint v1, GLint v2); +extern void (GLAPIENTRY *qglUniform4i)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +extern void (GLAPIENTRY *qglUniform1fv)(GLint location, GLsizei count, const GLfloat *value); +extern void (GLAPIENTRY *qglUniform2fv)(GLint location, GLsizei count, const GLfloat *value); +extern void (GLAPIENTRY *qglUniform3fv)(GLint location, GLsizei count, const GLfloat *value); +extern void (GLAPIENTRY *qglUniform4fv)(GLint location, GLsizei count, const GLfloat *value); +extern void (GLAPIENTRY *qglUniform1iv)(GLint location, GLsizei count, const GLint *value); +extern void (GLAPIENTRY *qglUniform2iv)(GLint location, GLsizei count, const GLint *value); +extern void (GLAPIENTRY *qglUniform3iv)(GLint location, GLsizei count, const GLint *value); +extern void (GLAPIENTRY *qglUniform4iv)(GLint location, GLsizei count, const GLint *value); +extern void (GLAPIENTRY *qglUniformMatrix2fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +extern void (GLAPIENTRY *qglUniformMatrix3fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +extern void (GLAPIENTRY *qglUniformMatrix4fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +extern void (GLAPIENTRY *qglGetShaderiv)(GLuint obj, GLenum pname, GLint *params); +extern void (GLAPIENTRY *qglGetProgramiv)(GLuint obj, GLenum pname, GLint *params); +extern void (GLAPIENTRY *qglGetShaderInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); +extern void (GLAPIENTRY *qglGetProgramInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); +extern void (GLAPIENTRY *qglGetAttachedShaders)(GLuint containerObj, GLsizei maxCount, GLsizei *count, GLuint *obj); +extern GLint (GLAPIENTRY *qglGetUniformLocation)(GLuint programObj, const GLchar *name); +extern void (GLAPIENTRY *qglGetActiveUniform)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +extern void (GLAPIENTRY *qglGetUniformfv)(GLuint programObj, GLint location, GLfloat *params); +extern void (GLAPIENTRY *qglGetUniformiv)(GLuint programObj, GLint location, GLint *params); +extern void (GLAPIENTRY *qglGetShaderSource)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *source); +extern void (GLAPIENTRY *qglPolygonStipple)(const GLubyte *mask); +#ifndef GL_PROGRAM_OBJECT +#define GL_PROGRAM_OBJECT 0x8B40 +#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_SHADER_OBJECT 0x8B48 +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT 0x1406 +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT 0x1404 +#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_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#endif + +// GL 2.0 vertex shader +extern void (GLAPIENTRY *qglVertexAttrib1f)(GLuint index, GLfloat v0); +extern void (GLAPIENTRY *qglVertexAttrib1s)(GLuint index, GLshort v0); +extern void (GLAPIENTRY *qglVertexAttrib1d)(GLuint index, GLdouble v0); +extern void (GLAPIENTRY *qglVertexAttrib2f)(GLuint index, GLfloat v0, GLfloat v1); +extern void (GLAPIENTRY *qglVertexAttrib2s)(GLuint index, GLshort v0, GLshort v1); +extern void (GLAPIENTRY *qglVertexAttrib2d)(GLuint index, GLdouble v0, GLdouble v1); +extern void (GLAPIENTRY *qglVertexAttrib3f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); +extern void (GLAPIENTRY *qglVertexAttrib3s)(GLuint index, GLshort v0, GLshort v1, GLshort v2); +extern void (GLAPIENTRY *qglVertexAttrib3d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2); +extern void (GLAPIENTRY *qglVertexAttrib4f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +extern void (GLAPIENTRY *qglVertexAttrib4s)(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3); +extern void (GLAPIENTRY *qglVertexAttrib4d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +extern void (GLAPIENTRY *qglVertexAttrib4Nub)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +extern void (GLAPIENTRY *qglVertexAttrib1fv)(GLuint index, const GLfloat *v); +extern void (GLAPIENTRY *qglVertexAttrib1sv)(GLuint index, const GLshort *v); +extern void (GLAPIENTRY *qglVertexAttrib1dv)(GLuint index, const GLdouble *v); +extern void (GLAPIENTRY *qglVertexAttrib2fv)(GLuint index, const GLfloat *v); +extern void (GLAPIENTRY *qglVertexAttrib2sv)(GLuint index, const GLshort *v); +extern void (GLAPIENTRY *qglVertexAttrib2dv)(GLuint index, const GLdouble *v); +extern void (GLAPIENTRY *qglVertexAttrib3fv)(GLuint index, const GLfloat *v); +extern void (GLAPIENTRY *qglVertexAttrib3sv)(GLuint index, const GLshort *v); +extern void (GLAPIENTRY *qglVertexAttrib3dv)(GLuint index, const GLdouble *v); +extern void (GLAPIENTRY *qglVertexAttrib4fv)(GLuint index, const GLfloat *v); +extern void (GLAPIENTRY *qglVertexAttrib4sv)(GLuint index, const GLshort *v); +extern void (GLAPIENTRY *qglVertexAttrib4dv)(GLuint index, const GLdouble *v); +extern void (GLAPIENTRY *qglVertexAttrib4iv)(GLuint index, const GLint *v); +extern void (GLAPIENTRY *qglVertexAttrib4bv)(GLuint index, const GLbyte *v); +extern void (GLAPIENTRY *qglVertexAttrib4ubv)(GLuint index, const GLubyte *v); +extern void (GLAPIENTRY *qglVertexAttrib4usv)(GLuint index, const GLushort *v); +extern void (GLAPIENTRY *qglVertexAttrib4uiv)(GLuint index, const GLuint *v); +extern void (GLAPIENTRY *qglVertexAttrib4Nbv)(GLuint index, const GLbyte *v); +extern void (GLAPIENTRY *qglVertexAttrib4Nsv)(GLuint index, const GLshort *v); +extern void (GLAPIENTRY *qglVertexAttrib4Niv)(GLuint index, const GLint *v); +extern void (GLAPIENTRY *qglVertexAttrib4Nubv)(GLuint index, const GLubyte *v); +extern void (GLAPIENTRY *qglVertexAttrib4Nusv)(GLuint index, const GLushort *v); +extern void (GLAPIENTRY *qglVertexAttrib4Nuiv)(GLuint index, const GLuint *v); +extern void (GLAPIENTRY *qglVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +extern void (GLAPIENTRY *qglEnableVertexAttribArray)(GLuint index); +extern void (GLAPIENTRY *qglDisableVertexAttribArray)(GLuint index); +extern void (GLAPIENTRY *qglBindAttribLocation)(GLuint programObj, GLuint index, const GLchar *name); +extern void (GLAPIENTRY *qglBindFragDataLocation)(GLuint programObj, GLuint index, const GLchar *name); +extern void (GLAPIENTRY *qglGetActiveAttrib)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +extern GLint (GLAPIENTRY *qglGetAttribLocation)(GLuint programObj, const GLchar *name); +extern void (GLAPIENTRY *qglGetVertexAttribdv)(GLuint index, GLenum pname, GLdouble *params); +extern void (GLAPIENTRY *qglGetVertexAttribfv)(GLuint index, GLenum pname, GLfloat *params); +extern void (GLAPIENTRY *qglGetVertexAttribiv)(GLuint index, GLenum pname, GLint *params); +extern void (GLAPIENTRY *qglGetVertexAttribPointerv)(GLuint index, GLenum pname, GLvoid **pointer); +#ifndef GL_VERTEX_SHADER +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#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_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_FLOAT 0x1406 +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#endif + +// GL 2.0 fragment shader +#ifndef GL_FRAGMENT_SHADER +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#endif + +// GL 2.0 shading language 100 +#ifndef GL_SHADING_LANGUAGE_VERSION +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +// GL_ARB_texture_compression +extern void (GLAPIENTRY *qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +extern void (GLAPIENTRY *qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +//extern void (GLAPIENTRY *qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +extern void (GLAPIENTRY *qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +extern void (GLAPIENTRY *qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +//extern void (GLAPIENTRY *qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +extern void (GLAPIENTRY *qglGetCompressedTexImageARB)(GLenum target, GLint lod, void *img); +#ifndef GL_COMPRESSED_RGB_ARB +#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 + +// GL_EXT_texture_compression_s3tc +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT +#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 + +// GL_ARB_occlusion_query +extern void (GLAPIENTRY *qglGenQueriesARB)(GLsizei n, GLuint *ids); +extern void (GLAPIENTRY *qglDeleteQueriesARB)(GLsizei n, const GLuint *ids); +extern GLboolean (GLAPIENTRY *qglIsQueryARB)(GLuint qid); +extern void (GLAPIENTRY *qglBeginQueryARB)(GLenum target, GLuint qid); +extern void (GLAPIENTRY *qglEndQueryARB)(GLenum target); +extern void (GLAPIENTRY *qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params); +extern void (GLAPIENTRY *qglGetQueryObjectivARB)(GLuint qid, GLenum pname, GLint *params); +extern void (GLAPIENTRY *qglGetQueryObjectuivARB)(GLuint qid, GLenum pname, GLuint *params); +#ifndef GL_SAMPLES_PASSED_ARB +#define GL_SAMPLES_PASSED_ARB 0x8914 +#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 +#endif + +// GL_EXT_bgr +#define GL_BGR 0x80E0 + +// GL_EXT_bgra +#define GL_BGRA 0x80E1 + +//GL_AMD_texture_texture4 + +//GL_ARB_texture_gather + +//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 +extern void (GLAPIENTRY *qglSampleCoverageARB)(GLclampf value, GLboolean invert); + +extern void (GLAPIENTRY *qglPointSize)(GLfloat size); + +#define DEBUGGL + +#ifdef DEBUGGL +#define CHECKGLERROR {if (gl_paranoid.integer){if (gl_printcheckerror.integer) Con_Printf("CHECKGLERROR at %s:%d\n", __FILE__, __LINE__);errornumber = qglGetError ? qglGetError() : 0;if (errornumber) GL_PrintError(errornumber, __FILE__, __LINE__);}} +extern int errornumber; +void GL_PrintError(int errornumber, const char *filename, int linenumber); +#else +#define CHECKGLERROR +#endif + +#endif + diff --git a/misc/source/darkplaces-src/hmac.c b/misc/source/darkplaces-src/hmac.c new file mode 100644 index 00000000..af0d1196 --- /dev/null +++ b/misc/source/darkplaces-src/hmac.c @@ -0,0 +1,60 @@ +#include "quakedef.h" +#include "hmac.h" + +qboolean hmac( + hashfunc_t hfunc, int hlen, int hblock, + unsigned char *out, + const unsigned char *in, int n, + const unsigned char *key, int k +) +{ + static unsigned char hashbuf[32]; + static unsigned char k_xor_ipad[128]; + static unsigned char k_xor_opad[128]; + static unsigned char catbuf[65600]; // 65535 bytes max quake packet size + 64 for the hash + int i; + + if(sizeof(hashbuf) < (size_t) hlen) + return false; + if(sizeof(k_xor_ipad) < (size_t) hblock) + return false; + if(sizeof(k_xor_ipad) < (size_t) hlen) + return false; + if(sizeof(catbuf) < (size_t) hblock + (size_t) hlen) + return false; + if(sizeof(catbuf) < (size_t) hblock + (size_t) n) + return false; + + if(k > hblock) + { + // hash the key if it is too long + hfunc(k_xor_opad, key, k); + key = k_xor_opad; + k = hlen; + } + + if(k < hblock) + { + // zero pad the key if it is too short + if(key != k_xor_opad) + memcpy(k_xor_opad, key, k); + for(i = k; i < hblock; ++i) + k_xor_opad[i] = 0; + key = k_xor_opad; + k = hblock; + } + + for(i = 0; i < hblock; ++i) + { + k_xor_ipad[i] = key[i] ^ 0x36; + k_xor_opad[i] = key[i] ^ 0x5c; + } + + memcpy(catbuf, k_xor_ipad, hblock); + memcpy(catbuf + hblock, in, n); + hfunc(hashbuf, catbuf, hblock + n); + memcpy(catbuf, k_xor_opad, hblock); + memcpy(catbuf + hblock, hashbuf, hlen); + hfunc(out, catbuf, hblock + hlen); + return true; +} diff --git a/misc/source/darkplaces-src/hmac.h b/misc/source/darkplaces-src/hmac.h new file mode 100644 index 00000000..44939002 --- /dev/null +++ b/misc/source/darkplaces-src/hmac.h @@ -0,0 +1,15 @@ +#ifndef HMAC_H +#define HMAC_H + +typedef void (*hashfunc_t) (unsigned char *out, const unsigned char *in, int n); +qboolean hmac( + hashfunc_t hfunc, int hlen, int hblock, + unsigned char *out, + const unsigned char *in, int n, + const unsigned char *key, int k +); + +#define HMAC_MDFOUR_16BYTES(out, in, n, key, k) hmac(mdfour, 16, 64, out, in, n, key, k) +#define HMAC_SHA256_32BYTES(out, in, n, key, k) hmac(sha256, 32, 64, out, in, n, key, k) + +#endif diff --git a/misc/source/darkplaces-src/host.c b/misc/source/darkplaces-src/host.c new file mode 100644 index 00000000..e7a37d9b --- /dev/null +++ b/misc/source/darkplaces-src/host.c @@ -0,0 +1,1325 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// host.c -- coordinates spawning and killing of local servers + +#include "quakedef.h" + +#include +#include "libcurl.h" +#include "cdaudio.h" +#include "cl_gecko.h" +#include "cl_video.h" +#include "progsvm.h" +#include "csprogs.h" +#include "sv_demo.h" +#include "snd_main.h" +#include "thread.h" + +/* + +A server can always be started, even if the system started out as a client +to a remote system. + +A client can NOT be started if the system started as a dedicated server. + +Memory is cleared / released when a server or client begins, not when they end. + +*/ + +// how many frames have occurred +// (checked by Host_Error and Host_SaveConfig_f) +int host_framecount = 0; +// LordHavoc: set when quit is executed +qboolean host_shuttingdown = false; + +// the real time since application started, without any slowmo or clamping +double realtime; + +// current client +client_t *host_client; + +jmp_buf host_abortframe; +double host_starttime = 0; + +// pretend frames take this amount of time (in seconds), 0 = realtime +cvar_t host_framerate = {0, "host_framerate","0", "locks frame timing to this value in seconds, 0.05 is 20fps for example, note that this can easily run too fast, use cl_maxfps if you want to limit your framerate instead, or sys_ticrate to limit server speed"}; +// shows time used by certain subsystems +cvar_t host_speeds = {0, "host_speeds","0", "reports how much time is used in server/graphics/sound"}; +cvar_t host_maxwait = {0, "host_maxwait","1000", "maximum sleep time requested from the operating system in millisecond. Larger sleeps will be done using multiple host_maxwait length sleeps. Lowering this value will increase CPU load, but may help working around problems with accuracy of sleep times."}; +cvar_t cl_minfps = {CVAR_SAVE, "cl_minfps", "40", "minimum fps target - while the rendering performance is below this, it will drift toward lower quality"}; +cvar_t cl_minfps_fade = {CVAR_SAVE, "cl_minfps_fade", "0.2", "how fast the quality adapts to varying framerate"}; +cvar_t cl_minfps_qualitymax = {CVAR_SAVE, "cl_minfps_qualitymax", "1", "highest allowed drawdistance multiplier"}; +cvar_t cl_minfps_qualitymin = {CVAR_SAVE, "cl_minfps_qualitymin", "0.25", "lowest allowed drawdistance multiplier"}; +cvar_t cl_minfps_qualitypower = {CVAR_SAVE, "cl_minfps_qualitypower", "4", "raises quality value to a power of itself, higher values make quality drop more sharply in relation to framerate"}; +cvar_t cl_minfps_qualityscale = {CVAR_SAVE, "cl_minfps_qualityscale", "0.5", "multiplier for quality"}; +cvar_t cl_maxfps = {CVAR_SAVE, "cl_maxfps", "0", "maximum fps cap, 0 = unlimited, if game is running faster than this it will wait before running another frame (useful to make cpu time available to other programs)"}; +cvar_t cl_maxfps_alwayssleep = {0, "cl_maxfps_alwayssleep","1", "gives up some processing time to other applications each frame, value in milliseconds, disabled if cl_maxfps is 0"}; +cvar_t cl_maxidlefps = {CVAR_SAVE, "cl_maxidlefps", "20", "maximum fps cap when the game is not the active window (makes cpu time available to other programs"}; + +cvar_t developer = {CVAR_SAVE, "developer","0", "shows debugging messages and information (recommended for all developers and level designers); the value -1 also suppresses buffering and logging these messages"}; +cvar_t developer_extra = {0, "developer_extra", "0", "prints additional debugging messages, often very verbose!"}; +cvar_t developer_insane = {0, "developer_insane", "0", "prints huge streams of information about internal workings, entire contents of files being read/written, etc. Not recommended!"}; +cvar_t developer_loadfile = {0, "developer_loadfile","0", "prints name and size of every file loaded via the FS_LoadFile function (which is almost everything)"}; +cvar_t developer_loading = {0, "developer_loading","0", "prints information about files as they are loaded or unloaded successfully"}; +cvar_t developer_entityparsing = {0, "developer_entityparsing", "0", "prints detailed network entities information each time a packet is received"}; + +cvar_t timestamps = {CVAR_SAVE, "timestamps", "0", "prints timestamps on console messages"}; +cvar_t timeformat = {CVAR_SAVE, "timeformat", "[%Y-%m-%d %H:%M:%S] ", "time format to use on timestamped console messages"}; + +/* +================ +Host_AbortCurrentFrame + +aborts the current host frame and goes on with the next one +================ +*/ +void Host_AbortCurrentFrame(void) +{ + longjmp (host_abortframe, 1); +} + +/* +================ +Host_Error + +This shuts down both the client and server +================ +*/ +void Host_Error (const char *error, ...) +{ + static char hosterrorstring1[MAX_INPUTLINE]; + static char hosterrorstring2[MAX_INPUTLINE]; + static qboolean hosterror = false; + va_list argptr; + + // turn off rcon redirect if it was active when the crash occurred + // to prevent loops when it is a networking problem + Con_Rcon_Redirect_Abort(); + + va_start (argptr,error); + dpvsnprintf (hosterrorstring1,sizeof(hosterrorstring1),error,argptr); + va_end (argptr); + + Con_Printf("Host_Error: %s\n", hosterrorstring1); + + // LordHavoc: if crashing very early, or currently shutting down, do + // Sys_Error instead + if (host_framecount < 3 || host_shuttingdown) + Sys_Error ("Host_Error: %s", hosterrorstring1); + + if (hosterror) + Sys_Error ("Host_Error: recursively entered (original error was: %s new error is: %s)", hosterrorstring2, hosterrorstring1); + hosterror = true; + + strlcpy(hosterrorstring2, hosterrorstring1, sizeof(hosterrorstring2)); + + CL_Parse_DumpPacket(); + + CL_Parse_ErrorCleanUp(); + + //PR_Crash(); + + // print out where the crash happened, if it was caused by QC (and do a cleanup) + PRVM_Crash(); + + + Host_ShutdownServer (); + + if (cls.state == ca_dedicated) + Sys_Error ("Host_Error: %s",hosterrorstring2); // dedicated servers exit + + CL_Disconnect (); + cls.demonum = -1; + + hosterror = false; + + Host_AbortCurrentFrame(); +} + +void Host_ServerOptions (void) +{ + int i; + + // general default + svs.maxclients = 8; + +// COMMANDLINEOPTION: Server: -dedicated [playerlimit] starts a dedicated server (with a command console), default playerlimit is 8 +// COMMANDLINEOPTION: Server: -listen [playerlimit] starts a multiplayer server with graphical client, like singleplayer but other players can connect, default playerlimit is 8 + // if no client is in the executable or -dedicated is specified on + // commandline, start a dedicated server + i = COM_CheckParm ("-dedicated"); + if (i || !cl_available) + { + cls.state = ca_dedicated; + // check for -dedicated specifying how many players + if (i && i + 1 < com_argc && atoi (com_argv[i+1]) >= 1) + svs.maxclients = atoi (com_argv[i+1]); + if (COM_CheckParm ("-listen")) + Con_Printf ("Only one of -dedicated or -listen can be specified\n"); + // default sv_public on for dedicated servers (often hosted by serious administrators), off for listen servers (often hosted by clueless users) + Cvar_SetValue("sv_public", 1); + } + else if (cl_available) + { + // client exists and not dedicated, check if -listen is specified + cls.state = ca_disconnected; + i = COM_CheckParm ("-listen"); + if (i) + { + // default players unless specified + if (i + 1 < com_argc && atoi (com_argv[i+1]) >= 1) + svs.maxclients = atoi (com_argv[i+1]); + } + else + { + // default players in some games, singleplayer in most + if (gamemode != GAME_GOODVSBAD2 && gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC && gamemode != GAME_BATTLEMECH) + svs.maxclients = 1; + } + } + + svs.maxclients = svs.maxclients_next = bound(1, svs.maxclients, MAX_SCOREBOARD); + + svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients); + + if (svs.maxclients > 1 && !deathmatch.integer && !coop.integer) + Cvar_SetValueQuick(&deathmatch, 1); +} + +/* +======================= +Host_InitLocal +====================== +*/ +void Host_SaveConfig_f(void); +void Host_LoadConfig_f(void); +extern cvar_t sv_writepicture_quality; +extern cvar_t r_texture_jpeg_fastpicmip; +static void Host_InitLocal (void) +{ + Cmd_AddCommand("saveconfig", Host_SaveConfig_f, "save settings to config.cfg (or a specified filename) immediately (also automatic when quitting)"); + Cmd_AddCommand("loadconfig", Host_LoadConfig_f, "reset everything and reload configs"); + + Cvar_RegisterVariable (&host_framerate); + Cvar_RegisterVariable (&host_speeds); + Cvar_RegisterVariable (&host_maxwait); + Cvar_RegisterVariable (&cl_minfps); + Cvar_RegisterVariable (&cl_minfps_fade); + Cvar_RegisterVariable (&cl_minfps_qualitymax); + Cvar_RegisterVariable (&cl_minfps_qualitymin); + Cvar_RegisterVariable (&cl_minfps_qualitypower); + Cvar_RegisterVariable (&cl_minfps_qualityscale); + Cvar_RegisterVariable (&cl_maxfps); + Cvar_RegisterVariable (&cl_maxfps_alwayssleep); + Cvar_RegisterVariable (&cl_maxidlefps); + + Cvar_RegisterVariable (&developer); + Cvar_RegisterVariable (&developer_extra); + Cvar_RegisterVariable (&developer_insane); + Cvar_RegisterVariable (&developer_loadfile); + Cvar_RegisterVariable (&developer_loading); + Cvar_RegisterVariable (&developer_entityparsing); + + Cvar_RegisterVariable (×tamps); + Cvar_RegisterVariable (&timeformat); + + Cvar_RegisterVariable (&sv_writepicture_quality); + Cvar_RegisterVariable (&r_texture_jpeg_fastpicmip); +} + + +/* +=============== +Host_SaveConfig_f + +Writes key bindings and archived cvars to config.cfg +=============== +*/ +void Host_SaveConfig_to(const char *file) +{ + qfile_t *f; + +// dedicated servers initialize the host but don't parse and set the +// config.cfg cvars + // LordHavoc: don't save a config if it crashed in startup + if (host_framecount >= 3 && cls.state != ca_dedicated && !COM_CheckParm("-benchmark") && !COM_CheckParm("-capturedemo")) + { + f = FS_OpenRealFile(file, "wb", false); + if (!f) + { + Con_Printf("Couldn't write %s.\n", file); + return; + } + + Key_WriteBindings (f); + Cvar_WriteVariables (f); + + FS_Close (f); + } +} +void Host_SaveConfig(void) +{ + Host_SaveConfig_to(CONFIGFILENAME); +} +void Host_SaveConfig_f(void) +{ + const char *file = CONFIGFILENAME; + + if(Cmd_Argc() >= 2) { + file = Cmd_Argv(1); + Con_Printf("Saving to %s\n", file); + } + + Host_SaveConfig_to(file); +} + +void Host_AddConfigText(void) +{ + // set up the default startmap_sp and startmap_dm aliases (mods can + // override these) and then execute the quake.rc startup script + if (gamemode == GAME_NEHAHRA) + Cbuf_InsertText("alias startmap_sp \"map nehstart\"\nalias startmap_dm \"map nehstart\"\nexec " STARTCONFIGFILENAME "\n"); + else if (gamemode == GAME_TRANSFUSION) + Cbuf_InsertText("alias startmap_sp \"map e1m1\"\n""alias startmap_dm \"map bb1\"\nexec " STARTCONFIGFILENAME "\n"); + else if (gamemode == GAME_TEU) + Cbuf_InsertText("alias startmap_sp \"map start\"\nalias startmap_dm \"map start\"\nexec teu.rc\n"); + else + Cbuf_InsertText("alias startmap_sp \"map start\"\nalias startmap_dm \"map start\"\nexec " STARTCONFIGFILENAME "\n"); +} + +/* +=============== +Host_LoadConfig_f + +Resets key bindings and cvars to defaults and then reloads scripts +=============== +*/ +void Host_LoadConfig_f(void) +{ + // reset all cvars, commands and aliases to init values + Cmd_RestoreInitState(); + // prepend a menu restart command to execute after the config + Cbuf_InsertText("\nmenu_restart\n"); + // reset cvars to their defaults, and then exec startup scripts again + Host_AddConfigText(); +} + +/* +================= +SV_ClientPrint + +Sends text across to be displayed +FIXME: make this just a stuffed echo? +================= +*/ +void SV_ClientPrint(const char *msg) +{ + if (host_client->netconnection) + { + MSG_WriteByte(&host_client->netconnection->message, svc_print); + MSG_WriteString(&host_client->netconnection->message, msg); + } +} + +/* +================= +SV_ClientPrintf + +Sends text across to be displayed +FIXME: make this just a stuffed echo? +================= +*/ +void SV_ClientPrintf(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + SV_ClientPrint(msg); +} + +/* +================= +SV_BroadcastPrint + +Sends text to all active clients +================= +*/ +void SV_BroadcastPrint(const char *msg) +{ + int i; + client_t *client; + + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (client->active && client->netconnection) + { + MSG_WriteByte(&client->netconnection->message, svc_print); + MSG_WriteString(&client->netconnection->message, msg); + } + } + + if (sv_echobprint.integer && cls.state == ca_dedicated) + Con_Print(msg); +} + +/* +================= +SV_BroadcastPrintf + +Sends text to all active clients +================= +*/ +void SV_BroadcastPrintf(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + SV_BroadcastPrint(msg); +} + +/* +================= +Host_ClientCommands + +Send text over to the client to be executed +================= +*/ +void Host_ClientCommands(const char *fmt, ...) +{ + va_list argptr; + char string[MAX_INPUTLINE]; + + if (!host_client->netconnection) + return; + + va_start(argptr,fmt); + dpvsnprintf(string, sizeof(string), fmt, argptr); + va_end(argptr); + + MSG_WriteByte(&host_client->netconnection->message, svc_stufftext); + MSG_WriteString(&host_client->netconnection->message, string); +} + +/* +===================== +SV_DropClient + +Called when the player is getting totally kicked off the host +if (crash = true), don't bother sending signofs +===================== +*/ +void SV_DropClient(qboolean crash) +{ + int i; + Con_Printf("Client \"%s\" dropped\n", host_client->name); + + SV_StopDemoRecording(host_client); + + // make sure edict is not corrupt (from a level change for example) + host_client->edict = PRVM_EDICT_NUM(host_client - svs.clients + 1); + + if (host_client->netconnection) + { + // tell the client to be gone + if (!crash) + { + // LordHavoc: no opportunity for resending, so use unreliable 3 times + unsigned char bufdata[8]; + sizebuf_t buf; + memset(&buf, 0, sizeof(buf)); + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + MSG_WriteByte(&buf, svc_disconnect); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, false); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, false); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, false); + } + } + + // call qc ClientDisconnect function + // LordHavoc: don't call QC if server is dead (avoids recursive + // Host_Error in some mods when they run out of edicts) + if (host_client->clientconnectcalled && sv.active && host_client->edict) + { + // call the prog function for removing a client + // this will set the body to a dead frame, among other things + int saveSelf = PRVM_serverglobaledict(self); + host_client->clientconnectcalled = false; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram(PRVM_serverfunction(ClientDisconnect), "QC function ClientDisconnect is missing"); + PRVM_serverglobaledict(self) = saveSelf; + } + + if (host_client->netconnection) + { + // break the net connection + NetConn_Close(host_client->netconnection); + host_client->netconnection = NULL; + } + + // if a download is active, close it + if (host_client->download_file) + { + Con_DPrintf("Download of %s aborted when %s dropped\n", host_client->download_name, host_client->name); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + host_client->download_name[0] = 0; + host_client->download_expectedposition = 0; + host_client->download_started = false; + } + + // remove leaving player from scoreboard + host_client->name[0] = 0; + host_client->colors = 0; + host_client->frags = 0; + // send notification to all clients + // get number of client manually just to make sure we get it right... + i = host_client - svs.clients; + MSG_WriteByte (&sv.reliable_datagram, svc_updatename); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteString (&sv.reliable_datagram, host_client->name); + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteByte (&sv.reliable_datagram, host_client->colors); + MSG_WriteByte (&sv.reliable_datagram, svc_updatefrags); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteShort (&sv.reliable_datagram, host_client->frags); + + // free the client now + if (host_client->entitydatabase) + EntityFrame_FreeDatabase(host_client->entitydatabase); + if (host_client->entitydatabase4) + EntityFrame4_FreeDatabase(host_client->entitydatabase4); + if (host_client->entitydatabase5) + EntityFrame5_FreeDatabase(host_client->entitydatabase5); + + if (sv.active) + { + // clear a fields that matter to DP_SV_CLIENTNAME and DP_SV_CLIENTCOLORS, and also frags + PRVM_ED_ClearEdict(host_client->edict); + } + + // clear the client struct (this sets active to false) + memset(host_client, 0, sizeof(*host_client)); + + // update server listing on the master because player count changed + // (which the master uses for filtering empty/full servers) + NetConn_Heartbeat(1); + + if (sv.loadgame) + { + for (i = 0;i < svs.maxclients;i++) + if (svs.clients[i].active && !svs.clients[i].spawned) + break; + if (i == svs.maxclients) + { + Con_Printf("Loaded game, everyone rejoined - unpausing\n"); + sv.paused = sv.loadgame = false; // we're basically done with loading now + } + } +} + +/* +================== +Host_ShutdownServer + +This only happens at the end of a game, not between levels +================== +*/ +void Host_ShutdownServer(void) +{ + int i; + + Con_DPrintf("Host_ShutdownServer\n"); + + if (!sv.active) + return; + + NetConn_Heartbeat(2); + NetConn_Heartbeat(2); + +// make sure all the clients know we're disconnecting + SV_VM_Begin(); + World_End(&sv.world); + if(prog->loaded) + if(PRVM_serverfunction(SV_Shutdown)) + { + func_t s = PRVM_serverfunction(SV_Shutdown); + PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again + PRVM_ExecuteProgram(s,"SV_Shutdown() required"); + } + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if (host_client->active) + SV_DropClient(false); // server shutdown + SV_VM_End(); + + NetConn_CloseServerPorts(); + + sv.active = false; +// +// clear structures +// + memset(&sv, 0, sizeof(sv)); + memset(svs.clients, 0, svs.maxclients*sizeof(client_t)); +} + + +//============================================================================ + +/* +=================== +Host_GetConsoleCommands + +Add them exactly as if they had been typed at the console +=================== +*/ +void Host_GetConsoleCommands (void) +{ + char *cmd; + + while (1) + { + cmd = Sys_ConsoleInput (); + if (!cmd) + break; + Cbuf_AddText (cmd); + } +} + +/* +================== +Host_TimeReport + +Returns a time report string, for example for +================== +*/ +const char *Host_TimingReport(void) +{ + return va("%.1f%% CPU, %.2f%% lost, offset avg %.1fms, max %.1fms, sdev %.1fms", svs.perf_cpuload * 100, svs.perf_lost * 100, svs.perf_offset_avg * 1000, svs.perf_offset_max * 1000, svs.perf_offset_sdev * 1000); +} + +/* +================== +Host_Frame + +Runs all active servers +================== +*/ +static void Host_Init(void); +void Host_Main(void) +{ + double time1 = 0; + double time2 = 0; + double time3 = 0; + double cl_timer, sv_timer; + double clframetime, deltarealtime, oldrealtime; + double wait; + int pass1, pass2, pass3, i; + + Host_Init(); + + cl_timer = 0; + sv_timer = 0; + + realtime = host_starttime = Sys_DoubleTime(); + for (;;) + { + if (setjmp(host_abortframe)) + { + SCR_ClearLoadingScreen(false); + continue; // something bad happened, or the server disconnected + } + + oldrealtime = realtime; + realtime = Sys_DoubleTime(); + + deltarealtime = realtime - oldrealtime; + cl_timer += deltarealtime; + sv_timer += deltarealtime; + + svs.perf_acc_realtime += deltarealtime; + + // Look for clients who have spawned + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if(host_client->spawned) + if(host_client->netconnection) + break; + if(i == svs.maxclients) + { + // Nobody is looking? Then we won't do timing... + // Instead, reset it to zero + svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; + } + else if(svs.perf_acc_realtime > 5) + { + svs.perf_cpuload = 1 - svs.perf_acc_sleeptime / svs.perf_acc_realtime; + svs.perf_lost = svs.perf_acc_lost / svs.perf_acc_realtime; + if(svs.perf_acc_offset_samples > 0) + { + svs.perf_offset_max = svs.perf_acc_offset_max; + svs.perf_offset_avg = svs.perf_acc_offset / svs.perf_acc_offset_samples; + svs.perf_offset_sdev = sqrt(svs.perf_acc_offset_squared / svs.perf_acc_offset_samples - svs.perf_offset_avg * svs.perf_offset_avg); + } + if(svs.perf_lost > 0 && developer_extra.integer) + Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport()); + svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; + } + + if (slowmo.value < 0.00001 && slowmo.value != 0) + Cvar_SetValue("slowmo", 0); + if (host_framerate.value < 0.00001 && host_framerate.value != 0) + Cvar_SetValue("host_framerate", 0); + + // keep the random time dependent, but not when playing demos/benchmarking + if(!*sv_random_seed.string && !cls.demoplayback) + rand(); + + cl.islocalgame = NetConn_IsLocalGame(); + + // get new key events + Key_EventQueue_Unblock(); + SndSys_SendKeyEvents(); + Sys_SendKeyEvents(); + + NetConn_UpdateSockets(); + + Log_DestBuffer_Flush(); + + // receive packets on each main loop iteration, as the main loop may + // be undersleeping due to select() detecting a new packet + if (sv.active) + NetConn_ServerFrame(); + + Curl_Run(); + + // check for commands typed to the host + Host_GetConsoleCommands(); + + // when a server is running we only execute console commands on server frames + // (this mainly allows frikbot .way config files to work properly by staying in sync with the server qc) + // otherwise we execute them on client frames + if (sv.active ? sv_timer > 0 : cl_timer > 0) + { + // process console commands +// R_TimeReport("preconsole"); + CL_VM_PreventInformationLeaks(); + Cbuf_Execute(); +// R_TimeReport("console"); + } + + //Con_Printf("%6.0f %6.0f\n", cl_timer * 1000000.0, sv_timer * 1000000.0); + + // if the accumulators haven't become positive yet, wait a while + if (cls.state == ca_dedicated) + wait = sv_timer * -1000000.0; + else if (!sv.active) + wait = cl_timer * -1000000.0; + else + wait = max(cl_timer, sv_timer) * -1000000.0; + + if (!cls.timedemo && wait >= 1) + { + double time0; + + if(host_maxwait.value <= 0) + wait = min(wait, 1000000.0); + else + wait = min(wait, host_maxwait.value * 1000.0); + if(wait < 1) + wait = 1; // because we cast to int + + time0 = Sys_DoubleTime(); + if (sv_checkforpacketsduringsleep.integer && !sys_usenoclockbutbenchmark.integer) + NetConn_SleepMicroseconds((int)wait); + else + Sys_Sleep((int)wait); + svs.perf_acc_sleeptime += Sys_DoubleTime() - time0; +// R_TimeReport("sleep"); + continue; + } + + R_TimeReport("---"); + + //------------------- + // + // server operations + // + //------------------- + + // limit the frametime steps to no more than 100ms each + if (cl_timer > 0.1) + cl_timer = 0.1; + if (sv_timer > 0.1) + { + svs.perf_acc_lost += (sv_timer - 0.1); + sv_timer = 0.1; + } + + if (sv.active && sv_timer > 0) + { + // execute one or more server frames, with an upper limit on how much + // execution time to spend on server frames to avoid freezing the game if + // the server is overloaded, this execution time limit means the game will + // slow down if the server is taking too long. + int framecount, framelimit = 1; + double advancetime, aborttime = 0; + float offset; + + if (cls.state == ca_dedicated) + Collision_Cache_NewFrame(); + + // run the world state + // don't allow simulation to run too fast or too slow or logic glitches can occur + + // stop running server frames if the wall time reaches this value + if (sys_ticrate.value <= 0) + advancetime = sv_timer; + else if (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) + { + // synchronize to the client frametime, but no less than 10ms and no more than sys_ticrate + advancetime = bound(0.01, cl_timer, sys_ticrate.value); + framelimit = 10; + aborttime = realtime + 0.1; + } + else + { + advancetime = sys_ticrate.value; + // listen servers can run multiple server frames per client frame + if (cls.state == ca_connected) + { + framelimit = 10; + aborttime = realtime + 0.1; + } + } + if(slowmo.value > 0 && slowmo.value < 1) + advancetime = min(advancetime, 0.1 / slowmo.value); + else + advancetime = min(advancetime, 0.1); + + if(advancetime > 0) + { + offset = sv_timer + (Sys_DoubleTime() - realtime); + ++svs.perf_acc_offset_samples; + svs.perf_acc_offset += offset; + svs.perf_acc_offset_squared += offset * offset; + if(svs.perf_acc_offset_max < offset) + svs.perf_acc_offset_max = offset; + } + + // only advance time if not paused + // the game also pauses in singleplayer when menu or console is used + sv.frametime = advancetime * slowmo.value; + if (host_framerate.value) + sv.frametime = host_framerate.value; + if (sv.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) + sv.frametime = 0; + + // setup the VM frame + SV_VM_Begin(); + + for (framecount = 0;framecount < framelimit && sv_timer > 0;framecount++) + { + sv_timer -= advancetime; + + // move things around and think unless paused + if (sv.frametime) + SV_Physics(); + + // if this server frame took too long, break out of the loop + if (framelimit > 1 && Sys_DoubleTime() >= aborttime) + break; + } + R_TimeReport("serverphysics"); + + // send all messages to the clients + SV_SendClientMessages(); + + if (sv.paused == 1 && realtime > sv.pausedstart && sv.pausedstart > 0) { + prog->globals.generic[OFS_PARM0] = realtime - sv.pausedstart; + PRVM_ExecuteProgram(PRVM_serverfunction(SV_PausedTic), "QC function SV_PausedTic is missing"); + } + + // end the server VM frame + SV_VM_End(); + + // send an heartbeat if enough time has passed since the last one + NetConn_Heartbeat(0); + R_TimeReport("servernetwork"); + } + else + { + // don't let r_speeds display jump around + R_TimeReport("serverphysics"); + R_TimeReport("servernetwork"); + } + + //------------------- + // + // client operations + // + //------------------- + + if (cls.state != ca_dedicated && (cl_timer > 0 || cls.timedemo || ((vid_activewindow ? cl_maxfps : cl_maxidlefps).value < 1))) + { + R_TimeReport("---"); + Collision_Cache_NewFrame(); + R_TimeReport("collisioncache"); + // decide the simulation time + if (cls.capturevideo.active) + { + //*** + if (cls.capturevideo.realtime) + clframetime = cl.realframetime = max(cl_timer, 1.0 / cls.capturevideo.framerate); + else + { + clframetime = 1.0 / cls.capturevideo.framerate; + cl.realframetime = max(cl_timer, clframetime); + } + } + else if (vid_activewindow && cl_maxfps.value >= 1 && !cls.timedemo) + { + clframetime = cl.realframetime = max(cl_timer, 1.0 / cl_maxfps.value); + // when running slow, we need to sleep to keep input responsive + wait = bound(0, cl_maxfps_alwayssleep.value * 1000, 100000); + if (wait > 0) + Sys_Sleep((int)wait); + } + else if (!vid_activewindow && cl_maxidlefps.value >= 1 && !cls.timedemo) + clframetime = cl.realframetime = max(cl_timer, 1.0 / cl_maxidlefps.value); + else + clframetime = cl.realframetime = cl_timer; + + // apply slowmo scaling + clframetime *= cl.movevars_timescale; + // scale playback speed of demos by slowmo cvar + if (cls.demoplayback) + { + clframetime *= slowmo.value; + // if demo playback is paused, don't advance time at all + if (cls.demopaused) + clframetime = 0; + } + + // host_framerate overrides all else + if (host_framerate.value) + clframetime = host_framerate.value; + + if (cl.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) + clframetime = 0; + + if (cls.timedemo) + clframetime = cl.realframetime = cl_timer; + + // deduct the frame time from the accumulator + cl_timer -= cl.realframetime; + + cl.oldtime = cl.time; + cl.time += clframetime; + + // update video + if (host_speeds.integer) + time1 = Sys_DoubleTime(); + R_TimeReport("pre-input"); + + // Collect input into cmd + CL_Input(); + + R_TimeReport("input"); + + // check for new packets + NetConn_ClientFrame(); + + // read a new frame from a demo if needed + CL_ReadDemoMessage(); + R_TimeReport("clientnetwork"); + + // now that packets have been read, send input to server + CL_SendMove(); + R_TimeReport("sendmove"); + + // update client world (interpolate entities, create trails, etc) + CL_UpdateWorld(); + R_TimeReport("lerpworld"); + + CL_Video_Frame(); + CL_Gecko_Frame(); + + R_TimeReport("client"); + + CL_UpdateScreen(); + R_TimeReport("render"); + + if (host_speeds.integer) + time2 = Sys_DoubleTime(); + + // update audio + if(cl.csqc_usecsqclistener) + { + S_Update(&cl.csqc_listenermatrix); + cl.csqc_usecsqclistener = false; + } + else + S_Update(&r_refdef.view.matrix); + + CDAudio_Update(); + R_TimeReport("audio"); + + // reset gathering of mouse input + in_mouse_x = in_mouse_y = 0; + + if (host_speeds.integer) + { + pass1 = (int)((time1 - time3)*1000000); + time3 = Sys_DoubleTime(); + pass2 = (int)((time2 - time1)*1000000); + pass3 = (int)((time3 - time2)*1000000); + Con_Printf("%6ius total %6ius server %6ius gfx %6ius snd\n", + pass1+pass2+pass3, pass1, pass2, pass3); + } + } + +#if MEMPARANOIA + Mem_CheckSentinelsGlobal(); +#else + if (developer_memorydebug.integer) + Mem_CheckSentinelsGlobal(); +#endif + + // if there is some time remaining from this frame, reset the timers + if (cl_timer >= 0) + cl_timer = 0; + if (sv_timer >= 0) + { + svs.perf_acc_lost += sv_timer; + sv_timer = 0; + } + + host_framecount++; + } +} + +//============================================================================ + +qboolean vid_opened = false; +void Host_StartVideo(void) +{ + if (!vid_opened && cls.state != ca_dedicated) + { + vid_opened = true; + // make sure we open sockets before opening video because the Windows Firewall "unblock?" dialog can screw up the graphics context on some graphics drivers + NetConn_UpdateSockets(); + VID_Start(); + CDAudio_Startup(); + } +} + +char engineversion[128]; + +qboolean sys_nostdout = false; + +extern void u8_Init(void); +extern void Render_Init(void); +extern void Mathlib_Init(void); +extern void FS_Init_SelfPack(void); +extern void FS_Init(void); +extern void FS_Shutdown(void); +extern void PR_Cmd_Init(void); +extern void COM_Init_Commands(void); +extern void FS_Init_Commands(void); +extern qboolean host_stuffcmdsrun; + +/* +==================== +Host_Init +==================== +*/ +static void Host_Init (void) +{ + int i; + const char* os; + + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(false); + + // LordHavoc: quake never seeded the random number generator before... heh + if (COM_CheckParm("-benchmark")) + srand(0); // predictable random sequence for -benchmark + else + srand((unsigned int)time(NULL)); + + // FIXME: this is evil, but possibly temporary + // LordHavoc: doesn't seem very temporary... + // LordHavoc: made this a saved cvar +// COMMANDLINEOPTION: Console: -developer enables warnings and other notices (RECOMMENDED for mod developers) + if (COM_CheckParm("-developer")) + { + developer.value = developer.integer = 1; + developer.string = "1"; + } + + if (COM_CheckParm("-developer2")) + { + developer.value = developer.integer = 1; + developer.string = "1"; + developer_extra.value = developer_extra.integer = 1; + developer_extra.string = "1"; + developer_insane.value = developer_insane.integer = 1; + developer_insane.string = "1"; + developer_memory.value = developer_memory.integer = 1; + developer_memory.string = "1"; + developer_memorydebug.value = developer_memorydebug.integer = 1; + developer_memorydebug.string = "1"; + } + +// COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from + if (COM_CheckParm("-nostdout")) + sys_nostdout = 1; + + // used by everything + Memory_Init(); + + // initialize console command/cvar/alias/command execution systems + Cmd_Init(); + + // initialize memory subsystem cvars/commands + Memory_Init_Commands(); + + // initialize console and logging and its cvars/commands + Con_Init(); + + // initialize various cvars that could not be initialized earlier + u8_Init(); + Curl_Init_Commands(); + Cmd_Init_Commands(); + Sys_Init_Commands(); + COM_Init_Commands(); + FS_Init_Commands(); + + // initialize console window (only used by sys_win.c) + Sys_InitConsole(); + + // initialize the self-pack (must be before COM_InitGameType as it may add command line options) + FS_Init_SelfPack(); + + // detect gamemode from commandline options or executable name + COM_InitGameType(); + + // construct a version string for the corner of the console + os = DP_OS_NAME; + dpsnprintf (engineversion, sizeof (engineversion), "%s %s %s", gamename, os, buildstring); + Con_Printf("%s\n", engineversion); + + // initialize ixtable + Mathlib_Init(); + + // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name) + FS_Init(); + + // must be after FS_Init + Crypto_Init(); + Crypto_Init_Commands(); + + NetConn_Init(); + Curl_Init(); + //PR_Init(); + //PR_Cmd_Init(); + PRVM_Init(); + Mod_Init(); + World_Init(); + SV_Init(); + V_Init(); // some cvars needed by server player physics (cl_rollangle etc) + Host_InitCommands(); + Host_InitLocal(); + Host_ServerOptions(); + + if (cls.state == ca_dedicated) + Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server (or disconnect all clients if running a server)"); + else + { + Con_DPrintf("Initializing client\n"); + + R_Modules_Init(); + Palette_Init(); + MR_Init_Commands(); + Thread_Init(); + VID_Shared_Init(); + VID_Init(); + Render_Init(); + S_Init(); + CDAudio_Init(); + Key_Init(); + CL_Init(); + } + + // save off current state of aliases, commands and cvars for later restore if FS_GameDir_f is called + // NOTE: menu commands are freed by Cmd_RestoreInitState + Cmd_SaveInitState(); + + // FIXME: put this into some neat design, but the menu should be allowed to crash + // without crashing the whole game, so this should just be a short-time solution + + // here comes the not so critical stuff + if (setjmp(host_abortframe)) { + return; + } + + Host_AddConfigText(); + Cbuf_Execute(); + + // if stuffcmds wasn't run, then quake.rc is probably missing, use default + if (!host_stuffcmdsrun) + { + Cbuf_AddText("exec default.cfg\nexec " CONFIGFILENAME "\nexec autoexec.cfg\nstuffcmds\n"); + Cbuf_Execute(); + } + + // put up the loading image so the user doesn't stare at a black screen... + SCR_BeginLoadingPlaque(); + + if (cls.state != ca_dedicated) + { + MR_Init(); + } + + // check for special benchmark mode +// COMMANDLINEOPTION: Client: -benchmark runs a timedemo and quits, results of any timedemo can be found in gamedir/benchmark.log (for example id1/benchmark.log) + i = COM_CheckParm("-benchmark"); + if (i && i + 1 < com_argc) + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText(va("timedemo %s\n", com_argv[i + 1])); + Cbuf_Execute(); + } + + // check for special demo mode +// COMMANDLINEOPTION: Client: -demo runs a playdemo and quits + i = COM_CheckParm("-demo"); + if (i && i + 1 < com_argc) + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText(va("playdemo %s\n", com_argv[i + 1])); + Cbuf_Execute(); + } + +// COMMANDLINEOPTION: Client: -capturedemo captures a playdemo and quits + i = COM_CheckParm("-capturedemo"); + if (i && i + 1 < com_argc) + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText(va("playdemo %s\ncl_capturevideo 1\n", com_argv[i + 1])); + Cbuf_Execute(); + } + + if (cls.state == ca_dedicated || COM_CheckParm("-listen")) + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText("startmap_dm\n"); + Cbuf_Execute(); + } + + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText("togglemenu\n"); + Cbuf_Execute(); + } + + Con_DPrint("========Initialized=========\n"); + + //Host_StartVideo(); +} + + +/* +=============== +Host_Shutdown + +FIXME: this is a callback from Sys_Quit and Sys_Error. It would be better +to run quit through here before the final handoff to the sys code. +=============== +*/ +void Host_Shutdown(void) +{ + static qboolean isdown = false; + + if (isdown) + { + Con_Print("recursive shutdown\n"); + return; + } + if (setjmp(host_abortframe)) + { + Con_Print("aborted the quitting frame?!?\n"); + return; + } + isdown = true; + + // be quiet while shutting down + S_StopAllSounds(); + + // disconnect client from server if active + CL_Disconnect(); + + // shut down local server if active + Host_ShutdownServer (); + + // Shutdown menu + if(MR_Shutdown) + MR_Shutdown(); + + // AK shutdown PRVM + // AK hmm, no PRVM_Shutdown(); yet + + CL_Gecko_Shutdown(); + CL_Video_Shutdown(); + + Host_SaveConfig(); + + CDAudio_Shutdown (); + S_Terminate (); + Curl_Shutdown (); + NetConn_Shutdown (); + //PR_Shutdown (); + + if (cls.state != ca_dedicated) + { + R_Modules_Shutdown(); + VID_Shutdown(); + Thread_Shutdown(); + } + + Cmd_Shutdown(); + Key_Shutdown(); + CL_Shutdown(); + Sys_Shutdown(); + Log_Close(); + Crypto_Shutdown(); + FS_Shutdown(); + Con_Shutdown(); + Memory_Shutdown(); +} + diff --git a/misc/source/darkplaces-src/host_cmd.c b/misc/source/darkplaces-src/host_cmd.c new file mode 100644 index 00000000..b8ac161b --- /dev/null +++ b/misc/source/darkplaces-src/host_cmd.c @@ -0,0 +1,2975 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "sv_demo.h" +#include "image.h" + +#include "utf8lib.h" + +// for secure rcon authentication +#include "hmac.h" +#include "mdfour.h" +#include + +int current_skill; +cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"}; +cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"}; +cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"}; +cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."}; +cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; +cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"}; +cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"}; +cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"}; +cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"}; +cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"}; +cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"}; +cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"}; +qboolean allowcheats = false; + +extern qboolean host_shuttingdown; +extern cvar_t developer_entityparsing; + +/* +================== +Host_Quit_f +================== +*/ + +void Host_Quit_f (void) +{ + if(host_shuttingdown) + Con_Printf("shutting down already!\n"); + else + Sys_Quit (0); +} + +/* +================== +Host_Status_f +================== +*/ +void Host_Status_f (void) +{ + char qcstatus[256]; + client_t *client; + int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0; + void (*print) (const char *fmt, ...); + char ip[48]; // can contain a full length v6 address with [] and a port + int frags; + + if (cmd_source == src_command) + { + // if running a client, try to send over network so the client's status report parser will see the report + if (cls.state == ca_connected) + { + Cmd_ForwardToServer (); + return; + } + print = Con_Printf; + } + else + print = SV_ClientPrintf; + + if (!sv.active) + return; + + if(cmd_source == src_command) + SV_VM_Begin(); + + in = 0; + if (Cmd_Argc() == 2) + { + if (strcmp(Cmd_Argv(1), "1") == 0) + in = 1; + else if (strcmp(Cmd_Argv(1), "2") == 0) + in = 2; + } + + for (players = 0, i = 0;i < svs.maxclients;i++) + if (svs.clients[i].active) + players++; + print ("host: %s\n", Cvar_VariableString ("hostname")); + print ("version: %s build %s\n", gamename, buildstring); + print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol)); + print ("map: %s\n", sv.name); + print ("timing: %s\n", Host_TimingReport()); + print ("players: %i active (%i max)\n\n", players, svs.maxclients); + + if (in == 1) + print ("^2IP %%pl ping time frags no name\n"); + else if (in == 2) + print ("^5IP no name\n"); + + for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (!client->active) + continue; + + ++k; + + if (in == 0 || in == 1) + { + seconds = (int)(realtime - client->connecttime); + minutes = seconds / 60; + if (minutes) + { + seconds -= (minutes * 60); + hours = minutes / 60; + if (hours) + minutes -= (hours * 60); + } + else + hours = 0; + + packetloss = 0; + if (client->netconnection) + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) + packetloss++; + packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; + ping = bound(0, (int)floor(client->ping*1000+0.5), 9999); + } + + if(sv_status_privacy.integer && cmd_source != src_command) + strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48); + else + strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48); + + frags = client->frags; + + if(sv_status_show_qcstatus.integer) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1); + const char *str = PRVM_GetString(PRVM_serveredictstring(ed, clientstatus)); + if(str && *str) + { + char *p; + const char *q; + p = qcstatus; + for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q) + if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q)) + *p++ = *q; + *p = 0; + if(*qcstatus) + frags = atoi(qcstatus); + } + } + + if (in == 0) // default layout + { + if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99) + { + // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output + print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds); + print (" %s\n", ip); + } + else + { + // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway... + print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds); + print (" %s\n", ip); + } + } + else if (in == 1) // extended layout + { + print ("%s%-47s %2i %4i %2i:%02i:%02i %4i #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name); + } + else if (in == 2) // reduced layout + { + print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name); + } + } + + if(cmd_source == src_command) + SV_VM_End(); +} + + +/* +================== +Host_God_f + +Sets client to godmode +================== +*/ +void Host_God_f (void) +{ + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE; + if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) ) + SV_ClientPrint("godmode OFF\n"); + else + SV_ClientPrint("godmode ON\n"); +} + +void Host_Notarget_f (void) +{ + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET; + if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) ) + SV_ClientPrint("notarget OFF\n"); + else + SV_ClientPrint("notarget ON\n"); +} + +qboolean noclip_anglehack; + +void Host_Noclip_f (void) +{ + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP) + { + noclip_anglehack = true; + PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP; + SV_ClientPrint("noclip ON\n"); + } + else + { + noclip_anglehack = false; + PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK; + SV_ClientPrint("noclip OFF\n"); + } +} + +/* +================== +Host_Fly_f + +Sets client to flymode +================== +*/ +void Host_Fly_f (void) +{ + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY) + { + PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY; + SV_ClientPrint("flymode ON\n"); + } + else + { + PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK; + SV_ClientPrint("flymode OFF\n"); + } +} + + +/* +================== +Host_Ping_f + +================== +*/ +void Host_Pings_f (void); // called by Host_Ping_f +void Host_Ping_f (void) +{ + int i; + client_t *client; + void (*print) (const char *fmt, ...); + + if (cmd_source == src_command) + { + // if running a client, try to send over network so the client's ping report parser will see the report + if (cls.state == ca_connected) + { + Cmd_ForwardToServer (); + return; + } + print = Con_Printf; + } + else + print = SV_ClientPrintf; + + if (!sv.active) + return; + + print("Client ping times:\n"); + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (!client->active) + continue; + print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name); + } + + // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report) + // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console) + //Host_Pings_f(); +} + +/* +=============================================================================== + +SERVER TRANSITIONS + +=============================================================================== +*/ + +/* +====================== +Host_Map_f + +handle a +map +command from the console. Active clients are kicked off. +====================== +*/ +void Host_Map_f (void) +{ + char level[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Con_Print("map : start a new game (kicks off all players)\n"); + return; + } + + // GAME_DELUXEQUAKE - clear warpmark (used by QC) + if (gamemode == GAME_DELUXEQUAKE) + Cvar_Set("warpmark", ""); + + cls.demonum = -1; // stop demo loop in case this fails + + CL_Disconnect (); + Host_ShutdownServer(); + + if(svs.maxclients != svs.maxclients_next) + { + svs.maxclients = svs.maxclients_next; + if (svs.clients) + Mem_Free(svs.clients); + svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients); + } + + // remove menu + if (key_dest == key_menu || key_dest == key_menu_grabbed) + MR_ToggleMenu(0); + key_dest = key_game; + + svs.serverflags = 0; // haven't completed an episode yet + allowcheats = sv_cheats.integer != 0; + strlcpy(level, Cmd_Argv(1), sizeof(level)); + SV_SpawnServer(level); + if (sv.active && cls.state == ca_disconnected) + CL_EstablishConnection("local:1", -2); +} + +/* +================== +Host_Changelevel_f + +Goes to a new map, taking all clients along +================== +*/ +void Host_Changelevel_f (void) +{ + char level[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Con_Print("changelevel : continue game on a new level\n"); + return; + } + // HACKHACKHACK + if (!sv.active) { + Host_Map_f(); + return; + } + + // remove menu + if (key_dest == key_menu || key_dest == key_menu_grabbed) + MR_ToggleMenu(0); + key_dest = key_game; + + SV_VM_Begin(); + SV_SaveSpawnparms (); + SV_VM_End(); + allowcheats = sv_cheats.integer != 0; + strlcpy(level, Cmd_Argv(1), sizeof(level)); + SV_SpawnServer(level); + if (sv.active && cls.state == ca_disconnected) + CL_EstablishConnection("local:1", -2); +} + +/* +================== +Host_Restart_f + +Restarts the current server for a dead player +================== +*/ +void Host_Restart_f (void) +{ + char mapname[MAX_QPATH]; + + if (Cmd_Argc() != 1) + { + Con_Print("restart : restart current level\n"); + return; + } + if (!sv.active) + { + Con_Print("Only the server may restart\n"); + return; + } + + // remove menu + if (key_dest == key_menu || key_dest == key_menu_grabbed) + MR_ToggleMenu(0); + key_dest = key_game; + + allowcheats = sv_cheats.integer != 0; + strlcpy(mapname, sv.name, sizeof(mapname)); + SV_SpawnServer(mapname); + if (sv.active && cls.state == ca_disconnected) + CL_EstablishConnection("local:1", -2); +} + +/* +================== +Host_Reconnect_f + +This command causes the client to wait for the signon messages again. +This is sent just before a server changes levels +================== +*/ +void Host_Reconnect_f (void) +{ + char temp[128]; + // if not connected, reconnect to the most recent server + if (!cls.netcon) + { + // if we have connected to a server recently, the userinfo + // will still contain its IP address, so get the address... + InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp)); + if (temp[0]) + CL_EstablishConnection(temp, -1); + else + Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n"); + return; + } + // if connected, do something based on protocol + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + // quakeworld can just re-login + if (cls.qw_downloadmemory) // don't change when downloading + return; + + S_StopAllSounds(); + + if (cls.state == ca_connected && cls.signon < SIGNONS) + { + Con_Printf("reconnecting...\n"); + MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "new"); + } + } + else + { + // netquake uses reconnect on level changes (silly) + if (Cmd_Argc() != 1) + { + Con_Print("reconnect : wait for signon messages again\n"); + return; + } + if (!cls.signon) + { + Con_Print("reconnect: no signon, ignoring reconnect\n"); + return; + } + cls.signon = 0; // need new connection messages + } +} + +/* +===================== +Host_Connect_f + +User command to connect to server +===================== +*/ +void Host_Connect_f (void) +{ + if (Cmd_Argc() < 2) + { + Con_Print("connect [ ...]: connect to a multiplayer game\n"); + return; + } + // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command + if(rcon_secure.integer <= 0) + Cvar_SetQuick(&rcon_password, ""); + CL_EstablishConnection(Cmd_Argv(1), 2); +} + + +/* +=============================================================================== + +LOAD / SAVE GAME + +=============================================================================== +*/ + +#define SAVEGAME_VERSION 5 + +void Host_Savegame_to (const char *name) +{ + qfile_t *f; + int i, k, l, lightstyles = 64; + char comment[SAVEGAME_COMMENT_LENGTH+1]; + char line[MAX_INPUTLINE]; + qboolean isserver; + char *s; + + // first we have to figure out if this can be saved in 64 lightstyles + // (for Quake compatibility) + for (i=64 ; iedicts, message)), (int)PRVM_serverglobalfloat(killed_monsters), (int)PRVM_serverglobalfloat(total_monsters)); + else + dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", PRVM_NAME); + // convert space to _ to make stdio happy + // LordHavoc: convert control characters to _ as well + for (i=0 ; inum_edicts ; i++) + { + FS_Printf(f,"// edict %d\n", i); + //Con_Printf("edict %d...\n", i); + PRVM_ED_Write (f, PRVM_EDICT_NUM(i)); + } + +#if 1 + FS_Printf(f,"/*\n"); + FS_Printf(f,"// DarkPlaces extended savegame\n"); + // darkplaces extension - extra lightstyles, support for color lightstyles + for (i=0 ; istringbuffersarray); i++) + { + prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); + if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED)) + { + for(k = 0; k < stringbuffer->num_strings; k++) + { + if (!stringbuffer->strings[k]) + continue; + // Parse the string a bit to turn special characters + // (like newline, specifically) into escape codes + s = stringbuffer->strings[k]; + for (l = 0;l < (int)sizeof(line) - 2 && *s;) + { + if (*s == '\n') + { + line[l++] = '\\'; + line[l++] = 'n'; + } + else if (*s == '\r') + { + line[l++] = '\\'; + line[l++] = 'r'; + } + else if (*s == '\\') + { + line[l++] = '\\'; + line[l++] = '\\'; + } + else if (*s == '"') + { + line[l++] = '\\'; + line[l++] = '"'; + } + else + line[l++] = *s; + s++; + } + line[l] = '\0'; + FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line); + } + } + } + FS_Printf(f,"*/\n"); +#endif + + FS_Close (f); + Con_Print("done.\n"); +} + +/* +=============== +Host_Savegame_f +=============== +*/ +void Host_Savegame_f (void) +{ + char name[MAX_QPATH]; + qboolean deadflag = false; + + if (!sv.active) + { + Con_Print("Can't save - no server running.\n"); + return; + } + + SV_VM_Begin(); + deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag); + SV_VM_End(); + + if (cl.islocalgame) + { + // singleplayer checks + if (cl.intermission) + { + Con_Print("Can't save in intermission.\n"); + return; + } + + if (deadflag) + { + Con_Print("Can't savegame with a dead player\n"); + return; + } + } + else + Con_Print("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n"); + + if (Cmd_Argc() != 2) + { + Con_Print("save : save a game\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Print("Relative pathnames are not allowed.\n"); + return; + } + + strlcpy (name, Cmd_Argv(1), sizeof (name)); + FS_DefaultExtension (name, ".sav", sizeof (name)); + + SV_VM_Begin(); + Host_Savegame_to(name); + SV_VM_End(); +} + + +/* +=============== +Host_Loadgame_f +=============== +*/ + +void Host_Loadgame_f (void) +{ + char filename[MAX_QPATH]; + char mapname[MAX_QPATH]; + float time; + const char *start; + const char *end; + const char *t; + char *text; + prvm_edict_t *ent; + int i, k; + int entnum; + int version; + float spawn_parms[NUM_SPAWN_PARMS]; + prvm_stringbuffer_t *stringbuffer; + size_t alloclen; + + if (Cmd_Argc() != 2) + { + Con_Print("load : load a game\n"); + return; + } + + strlcpy (filename, Cmd_Argv(1), sizeof(filename)); + FS_DefaultExtension (filename, ".sav", sizeof (filename)); + + Con_Printf("Loading game from %s...\n", filename); + + // stop playing demos + if (cls.demoplayback) + CL_Disconnect (); + + // remove menu + if (key_dest == key_menu || key_dest == key_menu_grabbed) + MR_ToggleMenu(0); + key_dest = key_game; + + cls.demonum = -1; // stop demo loop in case this fails + + t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL); + if (!text) + { + Con_Print("ERROR: couldn't open.\n"); + return; + } + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading version\n"); + + // version + COM_ParseToken_Simple(&t, false, false); + version = atoi(com_token); + if (version != SAVEGAME_VERSION) + { + Mem_Free(text); + Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION); + return; + } + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading description\n"); + + // description + COM_ParseToken_Simple(&t, false, false); + + for (i = 0;i < NUM_SPAWN_PARMS;i++) + { + COM_ParseToken_Simple(&t, false, false); + spawn_parms[i] = atof(com_token); + } + // skill + COM_ParseToken_Simple(&t, false, false); +// this silliness is so we can load 1.06 save files, which have float skill values + current_skill = (int)(atof(com_token) + 0.5); + Cvar_SetValue ("skill", (float)current_skill); + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading mapname\n"); + + // mapname + COM_ParseToken_Simple(&t, false, false); + strlcpy (mapname, com_token, sizeof(mapname)); + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading time\n"); + + // time + COM_ParseToken_Simple(&t, false, false); + time = atof(com_token); + + allowcheats = sv_cheats.integer != 0; + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: spawning server\n"); + + SV_SpawnServer (mapname); + if (!sv.active) + { + Mem_Free(text); + Con_Print("Couldn't load map\n"); + return; + } + sv.paused = true; // pause until all clients connect + sv.loadgame = true; + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading light styles\n"); + +// load the light styles + + SV_VM_Begin(); + // -1 is the globals + entnum = -1; + + for (i = 0;i < MAX_LIGHTSTYLES;i++) + { + // light style + start = t; + COM_ParseToken_Simple(&t, false, false); + // if this is a 64 lightstyle savegame produced by Quake, stop now + // we have to check this because darkplaces may save more than 64 + if (com_token[0] == '{') + { + t = start; + break; + } + strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i])); + } + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: skipping until globals\n"); + + // now skip everything before the first opening brace + // (this is for forward compatibility, so that older versions (at + // least ones with this fix) can load savegames with extra data before the + // first brace, as might be produced by a later engine version) + for (;;) + { + start = t; + if (!COM_ParseToken_Simple(&t, false, false)) + break; + if (com_token[0] == '{') + { + t = start; + break; + } + } + + // unlink all entities + World_UnlinkAll(&sv.world); + +// load the edicts out of the savegame file + end = t; + for (;;) + { + start = t; + while (COM_ParseToken_Simple(&t, false, false)) + if (!strcmp(com_token, "}")) + break; + if (!COM_ParseToken_Simple(&start, false, false)) + { + // end of file + break; + } + if (strcmp(com_token,"{")) + { + Mem_Free(text); + Host_Error ("First token isn't a brace"); + } + + if (entnum == -1) + { + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading globals\n"); + + // parse the global vars + PRVM_ED_ParseGlobals (start); + + // restore the autocvar globals + Cvar_UpdateAllAutoCvars(); + } + else + { + // parse an edict + if (entnum >= MAX_EDICTS) + { + Mem_Free(text); + Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS); + } + while (entnum >= prog->max_edicts) + PRVM_MEM_IncreaseEdicts(); + ent = PRVM_EDICT_NUM(entnum); + memset(ent->fields.vp, 0, prog->entityfields * 4); + ent->priv.server->free = false; + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum); + + PRVM_ED_ParseEdict (start, ent); + + // link it into the bsp tree + if (!ent->priv.server->free) + SV_LinkEdict(ent); + } + + end = t; + entnum++; + } + + prog->num_edicts = entnum; + sv.time = time; + + for (i = 0;i < NUM_SPAWN_PARMS;i++) + svs.clients[0].spawn_parms[i] = spawn_parms[i]; + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: skipping until extended data\n"); + + // read extended data if present + // the extended data is stored inside a /* */ comment block, which the + // parser intentionally skips, so we have to check for it manually here + if(end) + { + while (*end == '\r' || *end == '\n') + end++; + if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n')) + { + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading extended data\n"); + + Con_Printf("Loading extended DarkPlaces savegame\n"); + t = end + 2; + memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles)); + memset(sv.model_precache[0], 0, sizeof(sv.model_precache)); + memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache)); + while (COM_ParseToken_Simple(&t, false, false)) + { + if (!strcmp(com_token, "sv.lightstyles")) + { + COM_ParseToken_Simple(&t, false, false); + i = atoi(com_token); + COM_ParseToken_Simple(&t, false, false); + if (i >= 0 && i < MAX_LIGHTSTYLES) + strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i])); + else + Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token); + } + else if (!strcmp(com_token, "sv.model_precache")) + { + COM_ParseToken_Simple(&t, false, false); + i = atoi(com_token); + COM_ParseToken_Simple(&t, false, false); + if (i >= 0 && i < MAX_MODELS) + { + strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i])); + sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL); + } + else + Con_Printf("unsupported model %i \"%s\"\n", i, com_token); + } + else if (!strcmp(com_token, "sv.sound_precache")) + { + COM_ParseToken_Simple(&t, false, false); + i = atoi(com_token); + COM_ParseToken_Simple(&t, false, false); + if (i >= 0 && i < MAX_SOUNDS) + strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i])); + else + Con_Printf("unsupported sound %i \"%s\"\n", i, com_token); + } + else if (!strcmp(com_token, "sv.bufstr")) + { + COM_ParseToken_Simple(&t, false, false); + i = atoi(com_token); + COM_ParseToken_Simple(&t, false, false); + k = atoi(com_token); + COM_ParseToken_Simple(&t, false, false); + stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); + // VorteX: nasty code, cleanup required + // create buffer at this index + if(!stringbuffer) + stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecordAtIndex(&prog->stringbuffersarray, i); + if (!stringbuffer) + Con_Printf("cant write string %i into buffer %i\n", k, i); + else + { + // code copied from VM_bufstr_set + // expand buffer + if (stringbuffer->max_strings <= i) + { + char **oldstrings = stringbuffer->strings; + stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128); + while (stringbuffer->max_strings <= i) + stringbuffer->max_strings *= 2; + stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0])); + if (stringbuffer->num_strings > 0) + memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0])); + if (oldstrings) + Mem_Free(oldstrings); + } + // allocate string + stringbuffer->num_strings = max(stringbuffer->num_strings, k + 1); + if(stringbuffer->strings[k]) + Mem_Free(stringbuffer->strings[k]); + stringbuffer->strings[k] = NULL; + alloclen = strlen(com_token) + 1; + stringbuffer->strings[k] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); + memcpy(stringbuffer->strings[k], com_token, alloclen); + } + } + // skip any trailing text or unrecognized commands + while (COM_ParseToken_Simple(&t, true, false) && strcmp(com_token, "\n")) + ; + } + } + } + Mem_Free(text); + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: finished\n"); + + SV_VM_End(); + + // make sure we're connected to loopback + if (sv.active && cls.state == ca_disconnected) + CL_EstablishConnection("local:1", -2); +} + +//============================================================================ + +/* +====================== +Host_Name_f +====================== +*/ +cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"}; +void Host_Name_f (void) +{ + int i, j; + qboolean valid_colors; + const char *newNameSource; + char newName[sizeof(host_client->name)]; + + if (Cmd_Argc () == 1) + { + Con_Printf("name: %s\n", cl_name.string); + return; + } + + if (Cmd_Argc () == 2) + newNameSource = Cmd_Argv(1); + else + newNameSource = Cmd_Args(); + + strlcpy(newName, newNameSource, sizeof(newName)); + + if (cmd_source == src_command) + { + Cvar_Set ("_cl_name", newName); + if (strlen(newNameSource) >= sizeof(newName)) // overflowed + { + Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1)); + Con_Printf("name: %s\n", cl_name.string); + } + return; + } + + if (realtime < host_client->nametime) + { + SV_ClientPrintf("You can't change name more than once every 5 seconds!\n"); + return; + } + + host_client->nametime = realtime + 5; + + // point the string back at updateclient->name to keep it safe + strlcpy (host_client->name, newName, sizeof (host_client->name)); + + for (i = 0, j = 0;host_client->name[i];i++) + if (host_client->name[i] != '\r' && host_client->name[i] != '\n') + host_client->name[j++] = host_client->name[i]; + host_client->name[j] = 0; + + if(host_client->name[0] == 1 || host_client->name[0] == 2) + // may interfere with chat area, and will needlessly beep; so let's add a ^7 + { + memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2); + host_client->name[sizeof(host_client->name) - 1] = 0; + host_client->name[0] = STRING_COLOR_TAG; + host_client->name[1] = '0' + STRING_COLOR_DEFAULT; + } + + u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors); + if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string + { + size_t l; + l = strlen(host_client->name); + if(l < sizeof(host_client->name) - 1) + { + // duplicate the color tag to escape it + host_client->name[i] = STRING_COLOR_TAG; + host_client->name[i+1] = 0; + //Con_DPrintf("abuse detected, adding another trailing color tag\n"); + } + else + { + // remove the last character to fix the color code + host_client->name[l-1] = 0; + //Con_DPrintf("abuse detected, removing a trailing color tag\n"); + } + } + + // find the last color tag offset and decide if we need to add a reset tag + for (i = 0, j = -1;host_client->name[i];i++) + { + if (host_client->name[i] == STRING_COLOR_TAG) + { + if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9') + { + j = i; + // if this happens to be a reset tag then we don't need one + if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT) + j = -1; + i++; + continue; + } + if (host_client->name[i+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(host_client->name[i+2]) && isxdigit(host_client->name[i+3]) && isxdigit(host_client->name[i+4])) + { + j = i; + i += 4; + continue; + } + if (host_client->name[i+1] == STRING_COLOR_TAG) + { + i++; + continue; + } + } + } + // does not end in the default color string, so add it + if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2) + memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1); + + PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(host_client->name); + if (strcmp(host_client->old_name, host_client->name)) + { + if (host_client->spawned) + SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name); + strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name)); + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatename); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteString (&sv.reliable_datagram, host_client->name); + SV_WriteNetnameIntoDemo(host_client); + } +} + +/* +====================== +Host_Playermodel_f +====================== +*/ +cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"}; +// the old cl_playermodel in cl_main has been renamed to __cl_playermodel +void Host_Playermodel_f (void) +{ + int i, j; + char newPath[sizeof(host_client->playermodel)]; + + if (Cmd_Argc () == 1) + { + Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string); + return; + } + + if (Cmd_Argc () == 2) + strlcpy (newPath, Cmd_Argv(1), sizeof (newPath)); + else + strlcpy (newPath, Cmd_Args(), sizeof (newPath)); + + for (i = 0, j = 0;newPath[i];i++) + if (newPath[i] != '\r' && newPath[i] != '\n') + newPath[j++] = newPath[i]; + newPath[j] = 0; + + if (cmd_source == src_command) + { + Cvar_Set ("_cl_playermodel", newPath); + return; + } + + /* + if (realtime < host_client->nametime) + { + SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n"); + return; + } + + host_client->nametime = realtime + 5; + */ + + // point the string back at updateclient->name to keep it safe + strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel)); + PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(host_client->playermodel); + if (strcmp(host_client->old_model, host_client->playermodel)) + { + strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model)); + /*// send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/ + } +} + +/* +====================== +Host_Playerskin_f +====================== +*/ +cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"}; +void Host_Playerskin_f (void) +{ + int i, j; + char newPath[sizeof(host_client->playerskin)]; + + if (Cmd_Argc () == 1) + { + Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string); + return; + } + + if (Cmd_Argc () == 2) + strlcpy (newPath, Cmd_Argv(1), sizeof (newPath)); + else + strlcpy (newPath, Cmd_Args(), sizeof (newPath)); + + for (i = 0, j = 0;newPath[i];i++) + if (newPath[i] != '\r' && newPath[i] != '\n') + newPath[j++] = newPath[i]; + newPath[j] = 0; + + if (cmd_source == src_command) + { + Cvar_Set ("_cl_playerskin", newPath); + return; + } + + /* + if (realtime < host_client->nametime) + { + SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n"); + return; + } + + host_client->nametime = realtime + 5; + */ + + // point the string back at updateclient->name to keep it safe + strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin)); + PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(host_client->playerskin); + if (strcmp(host_client->old_skin, host_client->playerskin)) + { + //if (host_client->spawned) + // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin); + strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin)); + /*// send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/ + } +} + +void Host_Version_f (void) +{ + Con_Printf("Version: %s build %s\n", gamename, buildstring); +} + +void Host_Say(qboolean teamonly) +{ + client_t *save; + int j, quoted; + const char *p1; + char *p2; + // LordHavoc: long say messages + char text[1024]; + qboolean fromServer = false; + + if (cmd_source == src_command) + { + if (cls.state == ca_dedicated) + { + fromServer = true; + teamonly = false; + } + else + { + Cmd_ForwardToServer (); + return; + } + } + + if (Cmd_Argc () < 2) + return; + + if (!teamplay.integer) + teamonly = false; + + p1 = Cmd_Args(); + quoted = false; + if (*p1 == '\"') + { + quoted = true; + p1++; + } + // note this uses the chat prefix \001 + if (!fromServer && !teamonly) + dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1); + else if (!fromServer && teamonly) + dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1); + else if(*(sv_adminnick.string)) + dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1); + else + dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1); + p2 = text + strlen(text); + while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted))) + { + if (p2[-1] == '\"' && quoted) + quoted = false; + p2[-1] = 0; + p2--; + } + strlcat(text, "\n", sizeof(text)); + + // note: save is not a valid edict if fromServer is true + save = host_client; + for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++) + if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team))) + SV_ClientPrint(text); + host_client = save; + + if (cls.state == ca_dedicated) + Con_Print(&text[1]); +} + + +void Host_Say_f(void) +{ + Host_Say(false); +} + + +void Host_Say_Team_f(void) +{ + Host_Say(true); +} + + +void Host_Tell_f(void) +{ + const char *playername_start = NULL; + size_t playername_length = 0; + int playernumber = 0; + client_t *save; + int j; + const char *p1, *p2; + char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64) + qboolean fromServer = false; + + if (cmd_source == src_command) + { + if (cls.state == ca_dedicated) + fromServer = true; + else + { + Cmd_ForwardToServer (); + return; + } + } + + if (Cmd_Argc () < 2) + return; + + // note this uses the chat prefix \001 + if (!fromServer) + dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name); + else if(*(sv_adminnick.string)) + dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string); + else + dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string); + + p1 = Cmd_Args(); + p2 = p1 + strlen(p1); + // remove the target name + while (p1 < p2 && *p1 == ' ') + p1++; + if(*p1 == '#') + { + ++p1; + while (p1 < p2 && *p1 == ' ') + p1++; + while (p1 < p2 && isdigit(*p1)) + { + playernumber = playernumber * 10 + (*p1 - '0'); + p1++; + } + --playernumber; + } + else if(*p1 == '"') + { + ++p1; + playername_start = p1; + while (p1 < p2 && *p1 != '"') + p1++; + playername_length = p1 - playername_start; + if(p1 < p2) + p1++; + } + else + { + playername_start = p1; + while (p1 < p2 && *p1 != ' ') + p1++; + playername_length = p1 - playername_start; + } + while (p1 < p2 && *p1 == ' ') + p1++; + if(playername_start) + { + // set playernumber to the right client + char namebuf[128]; + if(playername_length >= sizeof(namebuf)) + { + if (fromServer) + Con_Print("Host_Tell: too long player name/ID\n"); + else + SV_ClientPrint("Host_Tell: too long player name/ID\n"); + return; + } + memcpy(namebuf, playername_start, playername_length); + namebuf[playername_length] = 0; + for (playernumber = 0; playernumber < svs.maxclients; playernumber++) + { + if (!svs.clients[playernumber].active) + continue; + if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0) + break; + } + } + if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active)) + { + if (fromServer) + Con_Print("Host_Tell: invalid player name/ID\n"); + else + SV_ClientPrint("Host_Tell: invalid player name/ID\n"); + return; + } + // remove trailing newlines + while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r')) + p2--; + // remove quotes if present + if (*p1 == '"') + { + p1++; + if (p2[-1] == '"') + p2--; + else if (fromServer) + Con_Print("Host_Tell: missing end quote\n"); + else + SV_ClientPrint("Host_Tell: missing end quote\n"); + } + while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r')) + p2--; + if(p1 == p2) + return; // empty say + for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;) + text[j++] = *p1++; + text[j++] = '\n'; + text[j++] = 0; + + save = host_client; + host_client = svs.clients + playernumber; + SV_ClientPrint(text); + host_client = save; +} + + +/* +================== +Host_Color_f +================== +*/ +cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"}; +void Host_Color(int changetop, int changebottom) +{ + int top, bottom, playercolor; + + // get top and bottom either from the provided values or the current values + // (allows changing only top or bottom, or both at once) + top = changetop >= 0 ? changetop : (cl_color.integer >> 4); + bottom = changebottom >= 0 ? changebottom : cl_color.integer; + + top &= 15; + bottom &= 15; + // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out + //if (top > 13) + // top = 13; + //if (bottom > 13) + // bottom = 13; + + playercolor = top*16 + bottom; + + if (cmd_source == src_command) + { + Cvar_SetValueQuick(&cl_color, playercolor); + return; + } + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + return; + + if (host_client->edict && PRVM_clientfunction(SV_ChangeTeam)) + { + Con_DPrint("Calling SV_ChangeTeam\n"); + PRVM_serverglobalfloat(time) = sv.time; + prog->globals.generic[OFS_PARM0] = playercolor; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram(PRVM_clientfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing"); + } + else + { + if (host_client->edict) + { + PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor; + PRVM_serveredictfloat(host_client->edict, team) = bottom + 1; + } + host_client->colors = playercolor; + if (host_client->old_colors != host_client->colors) + { + host_client->old_colors = host_client->colors; + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteByte (&sv.reliable_datagram, host_client->colors); + } + } +} + +void Host_Color_f(void) +{ + int top, bottom; + + if (Cmd_Argc() == 1) + { + Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15); + Con_Print("color <0-15> [0-15]\n"); + return; + } + + if (Cmd_Argc() == 2) + top = bottom = atoi(Cmd_Argv(1)); + else + { + top = atoi(Cmd_Argv(1)); + bottom = atoi(Cmd_Argv(2)); + } + Host_Color(top, bottom); +} + +void Host_TopColor_f(void) +{ + if (Cmd_Argc() == 1) + { + Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15); + Con_Print("topcolor <0-15>\n"); + return; + } + + Host_Color(atoi(Cmd_Argv(1)), -1); +} + +void Host_BottomColor_f(void) +{ + if (Cmd_Argc() == 1) + { + Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15); + Con_Print("bottomcolor <0-15>\n"); + return; + } + + Host_Color(-1, atoi(Cmd_Argv(1))); +} + +cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"}; +void Host_Rate_f(void) +{ + int rate; + + if (Cmd_Argc() != 2) + { + Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer); + Con_Print("rate \n"); + return; + } + + rate = atoi(Cmd_Argv(1)); + + if (cmd_source == src_command) + { + Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate)); + return; + } + + host_client->rate = rate; +} + +/* +================== +Host_Kill_f +================== +*/ +void Host_Kill_f (void) +{ + if (PRVM_serveredictfloat(host_client->edict, health) <= 0) + { + SV_ClientPrint("Can't suicide -- already dead!\n"); + return; + } + + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram (PRVM_serverfunction(ClientKill), "QC function ClientKill is missing"); +} + + +/* +================== +Host_Pause_f +================== +*/ +void Host_Pause_f (void) +{ + if (!pausable.integer) + SV_ClientPrint("Pause not allowed.\n"); + else + { + sv.paused ^= 1; + SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un"); + // send notification to all clients + MSG_WriteByte(&sv.reliable_datagram, svc_setpause); + MSG_WriteByte(&sv.reliable_datagram, sv.paused); + } +} + +/* +====================== +Host_PModel_f +LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen. +LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility. +====================== +*/ +cvar_t cl_pmodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"}; +static void Host_PModel_f (void) +{ + int i; + + if (Cmd_Argc () == 1) + { + Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string); + return; + } + i = atoi(Cmd_Argv(1)); + + if (cmd_source == src_command) + { + if (cl_pmodel.integer == i) + return; + Cvar_SetValue ("_cl_pmodel", i); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + return; + } + + PRVM_serveredictfloat(host_client->edict, pmodel) = i; +} + +//=========================================================================== + + +/* +================== +Host_PreSpawn_f +================== +*/ +void Host_PreSpawn_f (void) +{ + if (host_client->spawned) + { + Con_Print("prespawn not valid -- already spawned\n"); + return; + } + + if (host_client->netconnection) + { + SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize); + MSG_WriteByte (&host_client->netconnection->message, svc_signonnum); + MSG_WriteByte (&host_client->netconnection->message, 2); + host_client->sendsignon = 0; // enable unlimited sends again + } + + // reset the name change timer because the client will send name soon + host_client->nametime = 0; +} + +/* +================== +Host_Spawn_f +================== +*/ +void Host_Spawn_f (void) +{ + int i; + client_t *client; + int stats[MAX_CL_STATS]; + + if (host_client->spawned) + { + Con_Print("Spawn not valid -- already spawned\n"); + return; + } + + // reset name change timer again because they might want to change name + // again in the first 5 seconds after connecting + host_client->nametime = 0; + + // LordHavoc: moved this above the QC calls at FrikaC's request + // LordHavoc: commented this out + //if (host_client->netconnection) + // SZ_Clear (&host_client->netconnection->message); + + // run the entrance script + if (sv.loadgame) + { + // loaded games are fully initialized already + if (PRVM_serverfunction(RestoreGame)) + { + Con_DPrint("Calling RestoreGame\n"); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram(PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing"); + } + } + else + { + //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), host_client->name); + + // copy spawn parms out of the client_t + for (i=0 ; i< NUM_SPAWN_PARMS ; i++) + (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i]; + + // call the spawn function + host_client->clientconnectcalled = true; + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram (PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing"); + + if (cls.state == ca_dedicated) + Con_Printf("%s connected\n", host_client->name); + + PRVM_ExecuteProgram (PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing"); + } + + if (!host_client->netconnection) + return; + + // send time of update + MSG_WriteByte (&host_client->netconnection->message, svc_time); + MSG_WriteFloat (&host_client->netconnection->message, sv.time); + + // send all current names, colors, and frag counts + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (!client->active) + continue; + MSG_WriteByte (&host_client->netconnection->message, svc_updatename); + MSG_WriteByte (&host_client->netconnection->message, i); + MSG_WriteString (&host_client->netconnection->message, client->name); + MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags); + MSG_WriteByte (&host_client->netconnection->message, i); + MSG_WriteShort (&host_client->netconnection->message, client->frags); + MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors); + MSG_WriteByte (&host_client->netconnection->message, i); + MSG_WriteByte (&host_client->netconnection->message, client->colors); + } + + // send all current light styles + for (i=0 ; inetconnection->message, svc_lightstyle); + MSG_WriteByte (&host_client->netconnection->message, (char)i); + MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]); + } + } + + // send some stats + MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS); + MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets)); + + MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS); + MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters)); + + MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS); + MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets)); + + MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS); + MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters)); + + // send a fixangle + // Never send a roll angle, because savegames can catch the server + // in a state where it is expecting the client to correct the angle + // and it won't happen if the game was just loaded, so you wind up + // with a permanent head tilt + if (sv.loadgame) + { + MSG_WriteByte (&host_client->netconnection->message, svc_setangle); + MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol); + MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol); + MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol); + } + else + { + MSG_WriteByte (&host_client->netconnection->message, svc_setangle); + MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol); + MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol); + MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol); + } + + SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats); + + MSG_WriteByte (&host_client->netconnection->message, svc_signonnum); + MSG_WriteByte (&host_client->netconnection->message, 3); +} + +/* +================== +Host_Begin_f +================== +*/ +void Host_Begin_f (void) +{ + host_client->spawned = true; + + // LordHavoc: note: this code also exists in SV_DropClient + if (sv.loadgame) + { + int i; + for (i = 0;i < svs.maxclients;i++) + if (svs.clients[i].active && !svs.clients[i].spawned) + break; + if (i == svs.maxclients) + { + Con_Printf("Loaded game, everyone rejoined - unpausing\n"); + sv.paused = sv.loadgame = false; // we're basically done with loading now + } + } +} + +//=========================================================================== + + +/* +================== +Host_Kick_f + +Kicks a user off of the server +================== +*/ +void Host_Kick_f (void) +{ + const char *who; + const char *message = NULL; + client_t *save; + int i; + qboolean byNumber = false; + + if (!sv.active) + return; + + SV_VM_Begin(); + save = host_client; + + if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0) + { + i = (int)(atof(Cmd_Argv(2)) - 1); + if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active) + return; + byNumber = true; + } + else + { + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + if (!host_client->active) + continue; + if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0) + break; + } + } + + if (i < svs.maxclients) + { + if (cmd_source == src_command) + { + if (cls.state == ca_dedicated) + who = "Console"; + else + who = cl_name.string; + } + else + who = save->name; + + // can't kick yourself! + if (host_client == save) + return; + + if (Cmd_Argc() > 2) + { + message = Cmd_Args(); + COM_ParseToken_Simple(&message, false, false); + if (byNumber) + { + message++; // skip the # + while (*message == ' ') // skip white space + message++; + message += strlen(Cmd_Argv(2)); // skip the number + } + while (*message && *message == ' ') + message++; + } + if (message) + SV_ClientPrintf("Kicked by %s: %s\n", who, message); + else + SV_ClientPrintf("Kicked by %s\n", who); + SV_DropClient (false); // kicked + } + + host_client = save; + SV_VM_End(); +} + +/* +=============================================================================== + +DEBUGGING TOOLS + +=============================================================================== +*/ + +/* +================== +Host_Give_f +================== +*/ +void Host_Give_f (void) +{ + const char *t; + int v; + + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + t = Cmd_Argv(1); + v = atoi (Cmd_Argv(2)); + + switch (t[0]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // MED 01/04/97 added hipnotic give stuff + if (gamemode == GAME_HIPNOTIC) + { + if (t[0] == '6') + { + if (t[1] == 'a') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN; + else + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER; + } + else if (t[0] == '9') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON; + else if (t[0] == '0') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR; + else if (t[0] >= '2') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2')); + } + else + { + if (t[0] >= '2') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2')); + } + break; + + case 's': + if (gamemode == GAME_ROGUE) + PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v; + + PRVM_serveredictfloat(host_client->edict, ammo_shells) = v; + break; + case 'n': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; + } + else + { + PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; + } + break; + case 'l': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; + } + break; + case 'r': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; + } + else + { + PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; + } + break; + case 'm': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; + } + break; + case 'h': + PRVM_serveredictfloat(host_client->edict, health) = v; + break; + case 'c': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; + } + else + { + PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; + } + break; + case 'p': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; + } + break; + } +} + +prvm_edict_t *FindViewthing (void) +{ + int i; + prvm_edict_t *e; + + for (i=0 ; inum_edicts ; i++) + { + e = PRVM_EDICT_NUM(i); + if (!strcmp (PRVM_GetString(PRVM_serveredictstring(e, classname)), "viewthing")) + return e; + } + Con_Print("No viewthing on map\n"); + return NULL; +} + +/* +================== +Host_Viewmodel_f +================== +*/ +void Host_Viewmodel_f (void) +{ + prvm_edict_t *e; + dp_model_t *m; + + if (!sv.active) + return; + + SV_VM_Begin(); + e = FindViewthing (); + SV_VM_End(); + if (!e) + return; + + m = Mod_ForName (Cmd_Argv(1), false, true, NULL); + if (!m || !m->loaded || !m->Draw) + { + Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1)); + return; + } + + PRVM_serveredictfloat(e, frame) = 0; + cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m; +} + +/* +================== +Host_Viewframe_f +================== +*/ +void Host_Viewframe_f (void) +{ + prvm_edict_t *e; + int f; + dp_model_t *m; + + if (!sv.active) + return; + + SV_VM_Begin(); + e = FindViewthing (); + SV_VM_End(); + if (!e) + return; + m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; + + f = atoi(Cmd_Argv(1)); + if (f >= m->numframes) + f = m->numframes-1; + + PRVM_serveredictfloat(e, frame) = f; +} + + +void PrintFrameName (dp_model_t *m, int frame) +{ + if (m->animscenes) + Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name); + else + Con_Printf("frame %i\n", frame); +} + +/* +================== +Host_Viewnext_f +================== +*/ +void Host_Viewnext_f (void) +{ + prvm_edict_t *e; + dp_model_t *m; + + if (!sv.active) + return; + + SV_VM_Begin(); + e = FindViewthing (); + SV_VM_End(); + if (!e) + return; + m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; + + PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1; + if (PRVM_serveredictfloat(e, frame) >= m->numframes) + PRVM_serveredictfloat(e, frame) = m->numframes - 1; + + PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame)); +} + +/* +================== +Host_Viewprev_f +================== +*/ +void Host_Viewprev_f (void) +{ + prvm_edict_t *e; + dp_model_t *m; + + if (!sv.active) + return; + + SV_VM_Begin(); + e = FindViewthing (); + SV_VM_End(); + if (!e) + return; + + m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; + + PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1; + if (PRVM_serveredictfloat(e, frame) < 0) + PRVM_serveredictfloat(e, frame) = 0; + + PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame)); +} + +/* +=============================================================================== + +DEMO LOOP CONTROL + +=============================================================================== +*/ + + +/* +================== +Host_Startdemos_f +================== +*/ +void Host_Startdemos_f (void) +{ + int i, c; + + if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo")) + return; + + c = Cmd_Argc() - 1; + if (c > MAX_DEMOS) + { + Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS); + c = MAX_DEMOS; + } + Con_DPrintf("%i demo(s) in loop\n", c); + + for (i=1 ; iflags & CVAR_PRIVATE)) + Cmd_ForwardStringToServer(va("sentcvar %s", cvarname)); + else + Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"", c->name, c->string)); + return; + } + if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand)) + return; + + old = host_client; + if (cls.state != ca_dedicated) + i = 1; + else + i = 0; + for(;i 0) + { + Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n"); + return; + } + + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); + + if (cls.netcon) + { + InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address)); + } + else + { + if (!rcon_address.string[0]) + { + Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); + return; + } + strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1); + } + LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer); + mysocket = NetConn_ChooseClientSocketForAddress(&to); + if (mysocket) + { + SZ_Clear(&net_message); + MSG_WriteLong (&net_message, 0); + MSG_WriteByte (&net_message, CCREQ_RCON); + SZ_Write(&net_message, (const unsigned char*)rcon_password.string, n); + MSG_WriteByte (&net_message, 0); // terminate the (possibly partial) string + MSG_WriteString (&net_message, Cmd_Args()); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, &to); + SZ_Clear (&net_message); + } +} + +//============================================================================= + +// QuakeWorld commands + +/* +===================== +Host_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void Host_Rcon_f (void) // credit: taken from QuakeWorld +{ + int i, n; + const char *e; + lhnetaddress_t to; + lhnetsocket_t *mysocket; + + if (!rcon_password.string || !rcon_password.string[0]) + { + Con_Printf ("You must set rcon_password before issuing an rcon command.\n"); + return; + } + + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); + + if (cls.netcon) + to = cls.netcon->peeraddress; + else + { + if (!rcon_address.string[0]) + { + Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); + return; + } + LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer); + } + mysocket = NetConn_ChooseClientSocketForAddress(&to); + if (mysocket && Cmd_Args()[0]) + { + // simply put together the rcon packet and send it + if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1) + { + if(cls.rcon_commands[cls.rcon_ringpos][0]) + { + char s[128]; + LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true); + Con_Printf("rcon to %s (for command %s) failed: too many buffered commands (possibly increase MAX_RCONS)\n", s, cls.rcon_commands[cls.rcon_ringpos]); + cls.rcon_commands[cls.rcon_ringpos][0] = 0; + --cls.rcon_trying; + } + for (i = 0;i < MAX_RCONS;i++) + if(cls.rcon_commands[i][0]) + if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i])) + break; + ++cls.rcon_trying; + if(i >= MAX_RCONS) + NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later + strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos])); + cls.rcon_addresses[cls.rcon_ringpos] = to; + cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value; + cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS; + } + else if(rcon_secure.integer > 0) + { + char buf[1500]; + char argbuf[1500]; + dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args()); + memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24); + if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n)) + { + buf[40] = ' '; + strlcpy(buf + 41, argbuf, sizeof(buf) - 41); + NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to); + } + } + else + { + NetConn_WriteString(mysocket, va("\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to); + } + } +} + +/* +==================== +Host_User_f + +user + +Dump userdata / masterdata for a user +==================== +*/ +void Host_User_f (void) // credit: taken from QuakeWorld +{ + int uid; + int i; + + if (Cmd_Argc() != 2) + { + Con_Printf ("Usage: user \n"); + return; + } + + uid = atoi(Cmd_Argv(1)); + + for (i = 0;i < cl.maxclients;i++) + { + if (!cl.scores[i].name[0]) + continue; + if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1))) + { + InfoString_Print(cl.scores[i].qw_userinfo); + return; + } + } + Con_Printf ("User not in server.\n"); +} + +/* +==================== +Host_Users_f + +Dump userids for all current players +==================== +*/ +void Host_Users_f (void) // credit: taken from QuakeWorld +{ + int i; + int c; + + c = 0; + Con_Printf ("userid frags name\n"); + Con_Printf ("------ ----- ----\n"); + for (i = 0;i < cl.maxclients;i++) + { + if (cl.scores[i].name[0]) + { + Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name); + c++; + } + } + + Con_Printf ("%i total users\n", c); +} + +/* +================== +Host_FullServerinfo_f + +Sent by server when serverinfo changes +================== +*/ +// TODO: shouldn't this be a cvar instead? +void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld +{ + char temp[512]; + if (Cmd_Argc() != 2) + { + Con_Printf ("usage: fullserverinfo \n"); + return; + } + + strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo)); + InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp)); + cl.qw_teamplay = atoi(temp); +} + +/* +================== +Host_FullInfo_f + +Allow clients to change userinfo +================== +Casey was here :) +*/ +void Host_FullInfo_f (void) // credit: taken from QuakeWorld +{ + char key[512]; + char value[512]; + char *o; + const char *s; + + if (Cmd_Argc() != 2) + { + Con_Printf ("fullinfo \n"); + return; + } + + s = Cmd_Argv(1); + if (*s == '\\') + s++; + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (!*s) + { + Con_Printf ("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + + CL_SetInfo(key, value, false, false, false, false); + } +} + +/* +================== +CL_SetInfo_f + +Allow clients to change userinfo +================== +*/ +void Host_SetInfo_f (void) // credit: taken from QuakeWorld +{ + if (Cmd_Argc() == 1) + { + InfoString_Print(cls.userinfo); + return; + } + if (Cmd_Argc() != 3) + { + Con_Printf ("usage: setinfo [ ]\n"); + return; + } + CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false); +} + +/* +==================== +Host_Packet_f + +packet + +Contents allows \n escape character +==================== +*/ +void Host_Packet_f (void) // credit: taken from QuakeWorld +{ + char send[2048]; + int i, l; + const char *in; + char *out; + lhnetaddress_t address; + lhnetsocket_t *mysocket; + + if (Cmd_Argc() != 3) + { + Con_Printf ("packet \n"); + return; + } + + if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer)) + { + Con_Printf ("Bad address\n"); + return; + } + + in = Cmd_Argv(2); + out = send+4; + send[0] = send[1] = send[2] = send[3] = -1; + + l = (int)strlen (in); + for (i=0 ; i= send + sizeof(send) - 1) + break; + if (in[i] == '\\' && in[i+1] == 'n') + { + *out++ = '\n'; + i++; + } + else if (in[i] == '\\' && in[i+1] == '0') + { + *out++ = '\0'; + i++; + } + else if (in[i] == '\\' && in[i+1] == 't') + { + *out++ = '\t'; + i++; + } + else if (in[i] == '\\' && in[i+1] == 'r') + { + *out++ = '\r'; + i++; + } + else if (in[i] == '\\' && in[i+1] == '"') + { + *out++ = '\"'; + i++; + } + else + *out++ = in[i]; + } + + mysocket = NetConn_ChooseClientSocketForAddress(&address); + if (!mysocket) + mysocket = NetConn_ChooseServerSocketForAddress(&address); + if (mysocket) + NetConn_Write(mysocket, send, out - send, &address); +} + +/* +==================== +Host_Pings_f + +Send back ping and packet loss update for all current players to this player +==================== +*/ +void Host_Pings_f (void) +{ + int i, j, ping, packetloss, movementloss; + char temp[128]; + + if (!host_client->netconnection) + return; + + if (sv.protocol != PROTOCOL_QUAKEWORLD) + { + MSG_WriteByte(&host_client->netconnection->message, svc_stufftext); + MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport"); + } + for (i = 0;i < svs.maxclients;i++) + { + packetloss = 0; + movementloss = 0; + if (svs.clients[i].netconnection) + { + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) + packetloss++; + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (svs.clients[i].movement_count[j] < 0) + movementloss++; + } + packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; + movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; + ping = (int)floor(svs.clients[i].ping*1000+0.5); + ping = bound(0, ping, 9999); + if (sv.protocol == PROTOCOL_QUAKEWORLD) + { + // send qw_svc_updateping and qw_svc_updatepl messages + MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping); + MSG_WriteShort(&host_client->netconnection->message, ping); + MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl); + MSG_WriteByte(&host_client->netconnection->message, packetloss); + } + else + { + // write the string into the packet as multiple unterminated strings to avoid needing a local buffer + if(movementloss) + dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss); + else + dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss); + MSG_WriteUnterminatedString(&host_client->netconnection->message, temp); + } + } + if (sv.protocol != PROTOCOL_QUAKEWORLD) + MSG_WriteString(&host_client->netconnection->message, "\n"); +} + +void Host_PingPLReport_f(void) +{ + char *errbyte; + int i; + int l = Cmd_Argc(); + if (l > cl.maxclients) + l = cl.maxclients; + for (i = 0;i < l;i++) + { + cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2)); + cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0); + if(errbyte && *errbyte == ',') + cl.scores[i].qw_movementloss = atoi(errbyte + 1); + else + cl.scores[i].qw_movementloss = 0; + } +} + +//============================================================================= + +/* +================== +Host_InitCommands +================== +*/ +void Host_InitCommands (void) +{ + dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp"); + + Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information"); + Cmd_AddCommand ("quit", Host_Quit_f, "quit the game"); + Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)"); + Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)"); + Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)"); + Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)"); + Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory"); + Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level"); + Cmd_AddCommand ("restart", Host_Restart_f, "restart current level"); + Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients"); + Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname"); + Cmd_AddCommand ("reconnect", Host_Reconnect_f, "reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server)"); + Cmd_AddCommand ("version", Host_Version_f, "print engine version"); + Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server"); + Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server"); + Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server"); + Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly"); + Cmd_AddCommand_WithClientCommand ("pause", NULL, Host_Pause_f, "pause the game (if the server allows pausing)"); + Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name"); + Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server"); + Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file"); + Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file"); + + Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)"); + Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command"); + Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos"); + + Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level"); + Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level"); + Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level"); + Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level"); + + Cvar_RegisterVariable (&cl_name); + Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name"); + Cvar_RegisterVariable (&cl_color); + Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors"); + Cvar_RegisterVariable (&cl_rate); + Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed"); + Cvar_RegisterVariable (&cl_pmodel); + Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice"); + + // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first) + Cvar_RegisterVariable (&cl_playermodel); + Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model"); + Cvar_RegisterVariable (&cl_playerskin); + Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number"); + + Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)"); + Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)"); + Cmd_AddCommand_WithClientCommand ("begin", NULL, Host_Begin_f, "signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)"); + Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once"); + + Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC"); + + Cvar_RegisterVariable (&rcon_password); + Cvar_RegisterVariable (&rcon_address); + Cvar_RegisterVariable (&rcon_secure); + Cvar_RegisterVariable (&rcon_secure_challengetimeout); + Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP"); + Cmd_AddCommand ("srcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP"); + Cmd_AddCommand ("pqrcon", Host_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)"); + Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard"); + Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard"); + Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string"); + Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo"); + Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo"); + Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string"); + Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color"); + Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color"); + + Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)"); + Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)"); + + Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)"); + Cvar_RegisterVariable (&r_fixtrans_auto); + + Cvar_RegisterVariable (&team); + Cvar_RegisterVariable (&skin); + Cvar_RegisterVariable (&noaim); + + Cvar_RegisterVariable(&sv_cheats); + Cvar_RegisterVariable(&sv_adminnick); + Cvar_RegisterVariable(&sv_status_privacy); + Cvar_RegisterVariable(&sv_status_show_qcstatus); +} + +void Host_NoOperation_f(void) +{ +} diff --git a/misc/source/darkplaces-src/image.c b/misc/source/darkplaces-src/image.c new file mode 100644 index 00000000..c556f393 --- /dev/null +++ b/misc/source/darkplaces-src/image.c @@ -0,0 +1,1570 @@ + +#include "quakedef.h" +#include "image.h" +#include "jpeg.h" +#include "image_png.h" +#include "r_shadow.h" + +int image_width; +int image_height; + +void Image_CopyAlphaFromBlueBGRA(unsigned char *outpixels, const unsigned char *inpixels, int w, int h) +{ + int i, n; + n = w * h; + for(i = 0; i < n; ++i) + outpixels[4*i+3] = inpixels[4*i]; // blue channel +} + +#if 1 +// written by LordHavoc in a readable way, optimized by Vic, further optimized by LordHavoc (the non-special index case), readable version preserved below this +void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices) +{ + int index, c, x, y; + const unsigned char *in, *line; + int row_inc = (inputflipy ? -inputwidth : inputwidth) * numinputcomponents, col_inc = (inputflipx ? -1 : 1) * numinputcomponents; + int row_ofs = (inputflipy ? (inputheight - 1) * inputwidth * numinputcomponents : 0), col_ofs = (inputflipx ? (inputwidth - 1) * numinputcomponents : 0); + + for (c = 0; c < numoutputcomponents; c++) + if (outputinputcomponentindices[c] & 0x80000000) + break; + if (c < numoutputcomponents) + { + // special indices used + if (inputflipdiagonal) + { + for (x = 0, line = inpixels + col_ofs; x < inputwidth; x++, line += col_inc) + for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numoutputcomponents) + for (c = 0; c < numoutputcomponents; c++) + outpixels[c] = ((index = outputinputcomponentindices[c]) & 0x80000000) ? index : in[index]; + } + else + { + for (y = 0, line = inpixels + row_ofs; y < inputheight; y++, line += row_inc) + for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numoutputcomponents) + for (c = 0; c < numoutputcomponents; c++) + outpixels[c] = ((index = outputinputcomponentindices[c]) & 0x80000000) ? index : in[index]; + } + } + else + { + // special indices not used + if (inputflipdiagonal) + { + for (x = 0, line = inpixels + col_ofs; x < inputwidth; x++, line += col_inc) + for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numoutputcomponents) + for (c = 0; c < numoutputcomponents; c++) + outpixels[c] = in[outputinputcomponentindices[c]]; + } + else + { + for (y = 0, line = inpixels + row_ofs; y < inputheight; y++, line += row_inc) + for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numoutputcomponents) + for (c = 0; c < numoutputcomponents; c++) + outpixels[c] = in[outputinputcomponentindices[c]]; + } + } +} +#else +// intentionally readable version +void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices) +{ + int index, c, x, y; + const unsigned char *in, *inrow, *incolumn; + if (inputflipdiagonal) + { + for (x = 0;x < inputwidth;x++) + { + for (y = 0;y < inputheight;y++) + { + in = inpixels + ((inputflipy ? inputheight - 1 - y : y) * inputwidth + (inputflipx ? inputwidth - 1 - x : x)) * numinputcomponents; + for (c = 0;c < numoutputcomponents;c++) + { + index = outputinputcomponentindices[c]; + if (index & 0x80000000) + *outpixels++ = index; + else + *outpixels++ = in[index]; + } + } + } + } + else + { + for (y = 0;y < inputheight;y++) + { + for (x = 0;x < inputwidth;x++) + { + in = inpixels + ((inputflipy ? inputheight - 1 - y : y) * inputwidth + (inputflipx ? inputwidth - 1 - x : x)) * numinputcomponents; + for (c = 0;c < numoutputcomponents;c++) + { + index = outputinputcomponentindices[c]; + if (index & 0x80000000) + *outpixels++ = index; + else + *outpixels++ = in[index]; + } + } + } + } +} +#endif + +void Image_GammaRemapRGB(const unsigned char *in, unsigned char *out, int pixels, const unsigned char *gammar, const unsigned char *gammag, const unsigned char *gammab) +{ + while (pixels--) + { + out[0] = gammar[in[0]]; + out[1] = gammag[in[1]]; + out[2] = gammab[in[2]]; + in += 3; + out += 3; + } +} + +// note: pal must be 32bit color +void Image_Copy8bitBGRA(const unsigned char *in, unsigned char *out, int pixels, const unsigned int *pal) +{ + int *iout = (int *)out; + while (pixels >= 8) + { + iout[0] = pal[in[0]]; + iout[1] = pal[in[1]]; + iout[2] = pal[in[2]]; + iout[3] = pal[in[3]]; + iout[4] = pal[in[4]]; + iout[5] = pal[in[5]]; + iout[6] = pal[in[6]]; + iout[7] = pal[in[7]]; + in += 8; + iout += 8; + pixels -= 8; + } + if (pixels & 4) + { + iout[0] = pal[in[0]]; + iout[1] = pal[in[1]]; + iout[2] = pal[in[2]]; + iout[3] = pal[in[3]]; + in += 4; + iout += 4; + } + if (pixels & 2) + { + iout[0] = pal[in[0]]; + iout[1] = pal[in[1]]; + in += 2; + iout += 2; + } + if (pixels & 1) + iout[0] = pal[in[0]]; +} + +/* +================================================================= + + PCX Loading + +================================================================= +*/ + +typedef struct pcx_s +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; +} pcx_t; + +/* +============ +LoadPCX +============ +*/ +unsigned char* LoadPCX_BGRA (const unsigned char *f, int filesize, int *miplevel) +{ + pcx_t pcx; + unsigned char *a, *b, *image_buffer, *pbuf; + const unsigned char *palette, *fin, *enddata; + int x, y, x2, dataByte; + + if (filesize < (int)sizeof(pcx) + 768) + { + Con_Print("Bad pcx file\n"); + return NULL; + } + + fin = f; + + memcpy(&pcx, fin, sizeof(pcx)); + fin += sizeof(pcx); + + // LordHavoc: big-endian support ported from QF newtree + pcx.xmax = LittleShort (pcx.xmax); + pcx.xmin = LittleShort (pcx.xmin); + pcx.ymax = LittleShort (pcx.ymax); + pcx.ymin = LittleShort (pcx.ymin); + pcx.hres = LittleShort (pcx.hres); + pcx.vres = LittleShort (pcx.vres); + pcx.bytes_per_line = LittleShort (pcx.bytes_per_line); + pcx.palette_type = LittleShort (pcx.palette_type); + + image_width = pcx.xmax + 1 - pcx.xmin; + image_height = pcx.ymax + 1 - pcx.ymin; + if (pcx.manufacturer != 0x0a || pcx.version != 5 || pcx.encoding != 1 || pcx.bits_per_pixel != 8 || image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) + { + Con_Print("Bad pcx file\n"); + return NULL; + } + + palette = f + filesize - 768; + + image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width*image_height*4); + if (!image_buffer) + { + Con_Printf("LoadPCX: not enough memory for %i by %i image\n", image_width, image_height); + return NULL; + } + pbuf = image_buffer + image_width*image_height*3; + enddata = palette; + + for (y = 0;y < image_height && fin < enddata;y++) + { + a = pbuf + y * image_width; + for (x = 0;x < image_width && fin < enddata;) + { + dataByte = *fin++; + if(dataByte >= 0xC0) + { + if (fin >= enddata) + break; + x2 = x + (dataByte & 0x3F); + dataByte = *fin++; + if (x2 > image_width) + x2 = image_width; // technically an error + while(x < x2) + a[x++] = dataByte; + } + else + a[x++] = dataByte; + } + while(x < image_width) + a[x++] = 0; + } + + a = image_buffer; + b = pbuf; + + for(x = 0;x < image_width*image_height;x++) + { + y = *b++ * 3; + *a++ = palette[y+2]; + *a++ = palette[y+1]; + *a++ = palette[y]; + *a++ = 255; + } + + return image_buffer; +} + +/* +============ +LoadPCX +============ +*/ +qboolean LoadPCX_QWSkin(const unsigned char *f, int filesize, unsigned char *pixels, int outwidth, int outheight) +{ + pcx_t pcx; + unsigned char *a; + const unsigned char *fin, *enddata; + int x, y, x2, dataByte, pcxwidth, pcxheight; + + if (filesize < (int)sizeof(pcx) + 768) + return false; + + image_width = outwidth; + image_height = outheight; + fin = f; + + memcpy(&pcx, fin, sizeof(pcx)); + fin += sizeof(pcx); + + // LordHavoc: big-endian support ported from QF newtree + pcx.xmax = LittleShort (pcx.xmax); + pcx.xmin = LittleShort (pcx.xmin); + pcx.ymax = LittleShort (pcx.ymax); + pcx.ymin = LittleShort (pcx.ymin); + pcx.hres = LittleShort (pcx.hres); + pcx.vres = LittleShort (pcx.vres); + pcx.bytes_per_line = LittleShort (pcx.bytes_per_line); + pcx.palette_type = LittleShort (pcx.palette_type); + + pcxwidth = pcx.xmax + 1 - pcx.xmin; + pcxheight = pcx.ymax + 1 - pcx.ymin; + if (pcx.manufacturer != 0x0a || pcx.version != 5 || pcx.encoding != 1 || pcx.bits_per_pixel != 8 || pcxwidth > 4096 || pcxheight > 4096 || pcxwidth <= 0 || pcxheight <= 0) + return false; + + enddata = f + filesize - 768; + + for (y = 0;y < outheight && fin < enddata;y++) + { + a = pixels + y * outwidth; + // pad the output with blank lines if needed + if (y >= pcxheight) + { + memset(a, 0, outwidth); + continue; + } + for (x = 0;x < pcxwidth;) + { + if (fin >= enddata) + return false; + dataByte = *fin++; + if(dataByte >= 0xC0) + { + x2 = x + (dataByte & 0x3F); + if (fin >= enddata) + return false; + if (x2 > pcxwidth) + return false; + dataByte = *fin++; + for (;x < x2;x++) + if (x < outwidth) + a[x] = dataByte; + } + else + { + if (x < outwidth) // truncate to destination width + a[x] = dataByte; + x++; + } + } + while(x < outwidth) + a[x++] = 0; + } + + return true; +} + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ + +typedef struct _TargaHeader +{ + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} +TargaHeader; + +void PrintTargaHeader(TargaHeader *t) +{ + Con_Printf("TargaHeader:\nuint8 id_length = %i;\nuint8 colormap_type = %i;\nuint8 image_type = %i;\nuint16 colormap_index = %i;\nuint16 colormap_length = %i;\nuint8 colormap_size = %i;\nuint16 x_origin = %i;\nuint16 y_origin = %i;\nuint16 width = %i;\nuint16 height = %i;\nuint8 pixel_size = %i;\nuint8 attributes = %i;\n", t->id_length, t->colormap_type, t->image_type, t->colormap_index, t->colormap_length, t->colormap_size, t->x_origin, t->y_origin, t->width, t->height, t->pixel_size, t->attributes); +} + +/* +============= +LoadTGA +============= +*/ +unsigned char *LoadTGA_BGRA (const unsigned char *f, int filesize, int *miplevel) +{ + int x, y, pix_inc, row_inci, runlen, alphabits; + unsigned char *image_buffer; + unsigned int *pixbufi; + const unsigned char *fin, *enddata; + TargaHeader targa_header; + unsigned int palettei[256]; + union + { + unsigned int i; + unsigned char b[4]; + } + bgra; + + if (filesize < 19) + return NULL; + + enddata = f + filesize; + + targa_header.id_length = f[0]; + targa_header.colormap_type = f[1]; + targa_header.image_type = f[2]; + + targa_header.colormap_index = f[3] + f[4] * 256; + targa_header.colormap_length = f[5] + f[6] * 256; + targa_header.colormap_size = f[7]; + targa_header.x_origin = f[8] + f[9] * 256; + targa_header.y_origin = f[10] + f[11] * 256; + targa_header.width = image_width = f[12] + f[13] * 256; + targa_header.height = image_height = f[14] + f[15] * 256; + targa_header.pixel_size = f[16]; + targa_header.attributes = f[17]; + + if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) + { + Con_Print("LoadTGA: invalid size\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + + // advance to end of header + fin = f + 18; + + // skip TARGA image comment (usually 0 bytes) + fin += targa_header.id_length; + + // read/skip the colormap if present (note: according to the TARGA spec it + // can be present even on truecolor or greyscale images, just not used by + // the image data) + if (targa_header.colormap_type) + { + if (targa_header.colormap_length > 256) + { + Con_Print("LoadTGA: only up to 256 colormap_length supported\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + if (targa_header.colormap_index) + { + Con_Print("LoadTGA: colormap_index not supported\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + if (targa_header.colormap_size == 24) + { + for (x = 0;x < targa_header.colormap_length;x++) + { + bgra.b[0] = *fin++; + bgra.b[1] = *fin++; + bgra.b[2] = *fin++; + bgra.b[3] = 255; + palettei[x] = bgra.i; + } + } + else if (targa_header.colormap_size == 32) + { + memcpy(palettei, fin, targa_header.colormap_length*4); + fin += targa_header.colormap_length * 4; + } + else + { + Con_Print("LoadTGA: Only 32 and 24 bit colormap_size supported\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + } + + // check our pixel_size restrictions according to image_type + switch (targa_header.image_type & ~8) + { + case 2: + if (targa_header.pixel_size != 24 && targa_header.pixel_size != 32) + { + Con_Print("LoadTGA: only 24bit and 32bit pixel sizes supported for type 2 and type 10 images\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + break; + case 3: + // set up a palette to make the loader easier + for (x = 0;x < 256;x++) + { + bgra.b[0] = bgra.b[1] = bgra.b[2] = x; + bgra.b[3] = 255; + palettei[x] = bgra.i; + } + // fall through to colormap case + case 1: + if (targa_header.pixel_size != 8) + { + Con_Print("LoadTGA: only 8bit pixel size for type 1, 3, 9, and 11 images supported\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + break; + default: + Con_Printf("LoadTGA: Only type 1, 2, 3, 9, 10, and 11 targa RGB images supported, image_type = %i\n", targa_header.image_type); + PrintTargaHeader(&targa_header); + return NULL; + } + + if (targa_header.attributes & 0x10) + { + Con_Print("LoadTGA: origin must be in top left or bottom left, top right and bottom right are not supported\n"); + return NULL; + } + + // number of attribute bits per pixel, we only support 0 or 8 + alphabits = targa_header.attributes & 0x0F; + if (alphabits != 8 && alphabits != 0) + { + Con_Print("LoadTGA: only 0 or 8 attribute (alpha) bits supported\n"); + return NULL; + } + + image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + if (!image_buffer) + { + Con_Printf("LoadTGA: not enough memory for %i by %i image\n", image_width, image_height); + return NULL; + } + + // If bit 5 of attributes isn't set, the image has been stored from bottom to top + if ((targa_header.attributes & 0x20) == 0) + { + pixbufi = (unsigned int*)image_buffer + (image_height - 1)*image_width; + row_inci = -image_width*2; + } + else + { + pixbufi = (unsigned int*)image_buffer; + row_inci = 0; + } + + x = 0; + y = 0; + pix_inc = 1; + if ((targa_header.image_type & ~8) == 2) + pix_inc = (targa_header.pixel_size + 7) / 8; + switch (targa_header.image_type) + { + case 1: // colormapped, uncompressed + case 3: // greyscale, uncompressed + if (fin + image_width * image_height * pix_inc > enddata) + break; + for (y = 0;y < image_height;y++, pixbufi += row_inci) + for (x = 0;x < image_width;x++) + *pixbufi++ = palettei[*fin++]; + break; + case 2: + // BGR or BGRA, uncompressed + if (fin + image_width * image_height * pix_inc > enddata) + break; + if (targa_header.pixel_size == 32 && alphabits) + { + for (y = 0;y < image_height;y++) + memcpy(pixbufi + y * (image_width + row_inci), fin + y * image_width * pix_inc, image_width*4); + } + else + { + for (y = 0;y < image_height;y++, pixbufi += row_inci) + { + for (x = 0;x < image_width;x++, fin += pix_inc) + { + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = 255; + *pixbufi++ = bgra.i; + } + } + } + break; + case 9: // colormapped, RLE + case 11: // greyscale, RLE + for (y = 0;y < image_height;y++, pixbufi += row_inci) + { + for (x = 0;x < image_width;) + { + if (fin >= enddata) + break; // error - truncated file + runlen = *fin++; + if (runlen & 0x80) + { + // RLE - all pixels the same color + runlen += 1 - 0x80; + if (fin + pix_inc > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + bgra.i = palettei[*fin++]; + for (;runlen--;x++) + *pixbufi++ = bgra.i; + } + else + { + // uncompressed - all pixels different color + runlen++; + if (fin + pix_inc * runlen > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + for (;runlen--;x++) + *pixbufi++ = palettei[*fin++]; + } + } + + if (x != image_width) + { + // pixbufi is useless now + Con_Printf("LoadTGA: corrupt file\n"); + break; + } + } + break; + case 10: + // BGR or BGRA, RLE + if (targa_header.pixel_size == 32 && alphabits) + { + for (y = 0;y < image_height;y++, pixbufi += row_inci) + { + for (x = 0;x < image_width;) + { + if (fin >= enddata) + break; // error - truncated file + runlen = *fin++; + if (runlen & 0x80) + { + // RLE - all pixels the same color + runlen += 1 - 0x80; + if (fin + pix_inc > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = fin[3]; + fin += pix_inc; + for (;runlen--;x++) + *pixbufi++ = bgra.i; + } + else + { + // uncompressed - all pixels different color + runlen++; + if (fin + pix_inc * runlen > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + for (;runlen--;x++) + { + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = fin[3]; + fin += pix_inc; + *pixbufi++ = bgra.i; + } + } + } + + if (x != image_width) + { + // pixbufi is useless now + Con_Printf("LoadTGA: corrupt file\n"); + break; + } + } + } + else + { + for (y = 0;y < image_height;y++, pixbufi += row_inci) + { + for (x = 0;x < image_width;) + { + if (fin >= enddata) + break; // error - truncated file + runlen = *fin++; + if (runlen & 0x80) + { + // RLE - all pixels the same color + runlen += 1 - 0x80; + if (fin + pix_inc > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = 255; + fin += pix_inc; + for (;runlen--;x++) + *pixbufi++ = bgra.i; + } + else + { + // uncompressed - all pixels different color + runlen++; + if (fin + pix_inc * runlen > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + for (;runlen--;x++) + { + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = 255; + fin += pix_inc; + *pixbufi++ = bgra.i; + } + } + } + + if (x != image_width) + { + // pixbufi is useless now + Con_Printf("LoadTGA: corrupt file\n"); + break; + } + } + } + break; + default: + // unknown image_type + break; + } + + return image_buffer; +} + +typedef struct q2wal_s +{ + char name[32]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} q2wal_t; + +unsigned char *LoadWAL_BGRA (const unsigned char *f, int filesize, int *miplevel) +{ + unsigned char *image_buffer; + const q2wal_t *inwal = (const q2wal_t *)f; + + if (filesize < (int) sizeof(q2wal_t)) + { + Con_Print("LoadWAL: invalid WAL file\n"); + return NULL; + } + + image_width = LittleLong(inwal->width); + image_height = LittleLong(inwal->height); + if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) + { + Con_Printf("LoadWAL: invalid size %ix%i\n", image_width, image_height); + return NULL; + } + + if (filesize < (int) sizeof(q2wal_t) + (int) LittleLong(inwal->offsets[0]) + image_width * image_height) + { + Con_Print("LoadWAL: invalid WAL file\n"); + return NULL; + } + + image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + if (!image_buffer) + { + Con_Printf("LoadWAL: not enough memory for %i by %i image\n", image_width, image_height); + return NULL; + } + Image_Copy8bitBGRA(f + LittleLong(inwal->offsets[0]), image_buffer, image_width * image_height, palette_bgra_complete); + return image_buffer; +} + + +void Image_StripImageExtension (const char *in, char *out, size_t size_out) +{ + const char *ext; + + if (size_out == 0) + return; + + ext = FS_FileExtension(in); + if (ext && (!strcmp(ext, "tga") || !strcmp(ext, "pcx") || !strcmp(ext, "lmp") || !strcmp(ext, "png") || !strcmp(ext, "jpg"))) + FS_StripExtension(in, out, size_out); + else + strlcpy(out, in, size_out); +} + +static unsigned char image_linearfromsrgb[256]; + +void Image_MakeLinearColorsFromsRGB(unsigned char *pout, const unsigned char *pin, int numpixels) +{ + int i; + // this math from http://www.opengl.org/registry/specs/EXT/texture_sRGB.txt + if (!image_linearfromsrgb[255]) + for (i = 0;i < 256;i++) + image_linearfromsrgb[i] = (unsigned char)(Image_LinearFloatFromsRGB(i) * 256.0f); + for (i = 0;i < numpixels;i++) + { + pout[i*4+0] = image_linearfromsrgb[pin[i*4+0]]; + pout[i*4+1] = image_linearfromsrgb[pin[i*4+1]]; + pout[i*4+2] = image_linearfromsrgb[pin[i*4+2]]; + pout[i*4+3] = pin[i*4+3]; + } +} + +typedef struct imageformat_s +{ + const char *formatstring; + unsigned char *(*loadfunc)(const unsigned char *f, int filesize, int *miplevel); +} +imageformat_t; + +// GAME_TENEBRAE only +imageformat_t imageformats_tenebrae[] = +{ + {"override/%s.tga", LoadTGA_BGRA}, + {"override/%s.png", PNG_LoadImage_BGRA}, + {"override/%s.jpg", JPEG_LoadImage_BGRA}, + {"override/%s.pcx", LoadPCX_BGRA}, + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {NULL, NULL} +}; + +imageformat_t imageformats_nopath[] = +{ + {"override/%s.tga", LoadTGA_BGRA}, + {"override/%s.png", PNG_LoadImage_BGRA}, + {"override/%s.jpg", JPEG_LoadImage_BGRA}, + {"textures/%s.tga", LoadTGA_BGRA}, + {"textures/%s.png", PNG_LoadImage_BGRA}, + {"textures/%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {NULL, NULL} +}; + +// GAME_DELUXEQUAKE only +// VorteX: the point why i use such messy texture paths is +// that GtkRadiant can't detect normal/gloss textures +// and exclude them from texture browser +// so i just use additional folder to store this textures +imageformat_t imageformats_dq[] = +{ + {"%s.tga", LoadTGA_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"texturemaps/%s.tga", LoadTGA_BGRA}, + {"texturemaps/%s.jpg", JPEG_LoadImage_BGRA}, + {NULL, NULL} +}; + +imageformat_t imageformats_textures[] = +{ + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {"%s.wal", LoadWAL_BGRA}, + {NULL, NULL} +}; + +imageformat_t imageformats_gfx[] = +{ + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {NULL, NULL} +}; + +imageformat_t imageformats_other[] = +{ + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {NULL, NULL} +}; + +int fixtransparentpixels(unsigned char *data, int w, int h); +unsigned char *loadimagepixelsbgra (const char *filename, qboolean complain, qboolean allowFixtrans, qboolean convertsRGB, int *miplevel) +{ + fs_offset_t filesize; + imageformat_t *firstformat, *format; + unsigned char *f, *data = NULL, *data2 = NULL; + char basename[MAX_QPATH], name[MAX_QPATH], name2[MAX_QPATH], *c; + //if (developer_memorydebug.integer) + // Mem_CheckSentinelsGlobal(); + if (developer_texturelogging.integer) + Log_Printf("textures.log", "%s\n", filename); + Image_StripImageExtension(filename, basename, sizeof(basename)); // strip filename extensions to allow replacement by other types + // replace *'s with #, so commandline utils don't get confused when dealing with the external files + for (c = basename;*c;c++) + if (*c == '*') + *c = '#'; + name[0] = 0; + if (strchr(basename, '/')) + { + int i; + for (i = 0;i < (int)sizeof(name)-1 && basename[i] != '/';i++) + name[i] = basename[i]; + name[i] = 0; + } + if (gamemode == GAME_TENEBRAE) + firstformat = imageformats_tenebrae; + else if (gamemode == GAME_DELUXEQUAKE) + firstformat = imageformats_dq; + else if (!strcasecmp(name, "textures")) + firstformat = imageformats_textures; + else if (!strcasecmp(name, "gfx")) + firstformat = imageformats_gfx; + else if (!strchr(basename, '/')) + firstformat = imageformats_nopath; + else + firstformat = imageformats_other; + // now try all the formats in the selected list + for (format = firstformat;format->formatstring;format++) + { + dpsnprintf (name, sizeof(name), format->formatstring, basename); + f = FS_LoadFile(name, tempmempool, true, &filesize); + if (f) + { + int mymiplevel = miplevel ? *miplevel : 0; + data = format->loadfunc(f, (int)filesize, &mymiplevel); + Mem_Free(f); + if (data) + { + if(format->loadfunc == JPEG_LoadImage_BGRA) // jpeg can't do alpha, so let's simulate it by loading another jpeg + { + dpsnprintf (name2, sizeof(name2), format->formatstring, va("%s_alpha", basename)); + f = FS_LoadFile(name2, tempmempool, true, &filesize); + if(f) + { + int mymiplevel2 = miplevel ? *miplevel : 0; + data2 = format->loadfunc(f, (int)filesize, &mymiplevel2); + if(mymiplevel != mymiplevel2) + Host_Error("loadimagepixelsbgra: miplevels differ"); + Mem_Free(f); + Image_CopyAlphaFromBlueBGRA(data, data2, image_width, image_height); + Mem_Free(data2); + } + } + if (developer_loading.integer) + Con_DPrintf("loaded image %s (%dx%d)\n", name, image_width, image_height); + if(miplevel) + *miplevel = mymiplevel; + //if (developer_memorydebug.integer) + // Mem_CheckSentinelsGlobal(); + if(allowFixtrans && r_fixtrans_auto.integer) + { + int n = fixtransparentpixels(data, image_width, image_height); + if(n) + { + Con_Printf("- had to fix %s (%d pixels changed)\n", name, n); + if(r_fixtrans_auto.integer >= 2) + { + char outfilename[MAX_QPATH], buf[MAX_QPATH]; + Image_StripImageExtension(name, buf, sizeof(buf)); + dpsnprintf(outfilename, sizeof(outfilename), "fixtrans/%s.tga", buf); + Image_WriteTGABGRA(outfilename, image_width, image_height, data); + Con_Printf("- %s written.\n", outfilename); + } + } + } + if (convertsRGB) + Image_MakeLinearColorsFromsRGB(data, data, image_width * image_height); + return data; + } + else + Con_DPrintf("Error loading image %s (file loaded but decode failed)\n", name); + } + } + if (complain) + { + Con_Printf("Couldn't load %s using ", filename); + for (format = firstformat;format->formatstring;format++) + { + dpsnprintf (name, sizeof(name), format->formatstring, basename); + Con_Printf(format == firstformat ? "\"%s\"" : (format[1].formatstring ? ", \"%s\"" : " or \"%s\".\n"), format->formatstring); + } + } + + // texture loading can take a while, so make sure we're sending keepalives + CL_KeepaliveMessage(false); + + //if (developer_memorydebug.integer) + // Mem_CheckSentinelsGlobal(); + return NULL; +} + +extern cvar_t gl_picmip; +rtexture_t *loadtextureimage (rtexturepool_t *pool, const char *filename, qboolean complain, int flags, qboolean allowFixtrans, qboolean sRGB) +{ + unsigned char *data; + rtexture_t *rt; + int miplevel = R_PicmipForFlags(flags); + if (!(data = loadimagepixelsbgra (filename, complain, allowFixtrans, false, &miplevel))) + return 0; + rt = R_LoadTexture2D(pool, filename, image_width, image_height, data, sRGB ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, flags, miplevel, NULL); + Mem_Free(data); + return rt; +} + +int fixtransparentpixels(unsigned char *data, int w, int h) +{ + int const FIXTRANS_NEEDED = 1; + int const FIXTRANS_HAS_L = 2; + int const FIXTRANS_HAS_R = 4; + int const FIXTRANS_HAS_U = 8; + int const FIXTRANS_HAS_D = 16; + int const FIXTRANS_FIXED = 32; + unsigned char *fixMask = (unsigned char *) Mem_Alloc(tempmempool, w * h); + int fixPixels = 0; + int changedPixels = 0; + int x, y; + +#define FIXTRANS_PIXEL (y*w+x) +#define FIXTRANS_PIXEL_U (((y+h-1)%h)*w+x) +#define FIXTRANS_PIXEL_D (((y+1)%h)*w+x) +#define FIXTRANS_PIXEL_L (y*w+((x+w-1)%w)) +#define FIXTRANS_PIXEL_R (y*w+((x+1)%w)) + + memset(fixMask, 0, w * h); + for(y = 0; y < h; ++y) + for(x = 0; x < w; ++x) + { + if(data[FIXTRANS_PIXEL * 4 + 3] == 0) + { + fixMask[FIXTRANS_PIXEL] |= FIXTRANS_NEEDED; + ++fixPixels; + } + else + { + fixMask[FIXTRANS_PIXEL_D] |= FIXTRANS_HAS_U; + fixMask[FIXTRANS_PIXEL_U] |= FIXTRANS_HAS_D; + fixMask[FIXTRANS_PIXEL_R] |= FIXTRANS_HAS_L; + fixMask[FIXTRANS_PIXEL_L] |= FIXTRANS_HAS_R; + } + } + if(fixPixels == w * h) + return 0; // sorry, can't do anything about this + while(fixPixels) + { + for(y = 0; y < h; ++y) + for(x = 0; x < w; ++x) + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_NEEDED) + { + unsigned int sumR = 0, sumG = 0, sumB = 0, sumA = 0, sumRA = 0, sumGA = 0, sumBA = 0, cnt = 0; + unsigned char r, g, b, a, r0, g0, b0; + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_U) + { + r = data[FIXTRANS_PIXEL_U * 4 + 2]; + g = data[FIXTRANS_PIXEL_U * 4 + 1]; + b = data[FIXTRANS_PIXEL_U * 4 + 0]; + a = data[FIXTRANS_PIXEL_U * 4 + 3]; + sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; + } + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_D) + { + r = data[FIXTRANS_PIXEL_D * 4 + 2]; + g = data[FIXTRANS_PIXEL_D * 4 + 1]; + b = data[FIXTRANS_PIXEL_D * 4 + 0]; + a = data[FIXTRANS_PIXEL_D * 4 + 3]; + sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; + } + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_L) + { + r = data[FIXTRANS_PIXEL_L * 4 + 2]; + g = data[FIXTRANS_PIXEL_L * 4 + 1]; + b = data[FIXTRANS_PIXEL_L * 4 + 0]; + a = data[FIXTRANS_PIXEL_L * 4 + 3]; + sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; + } + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_R) + { + r = data[FIXTRANS_PIXEL_R * 4 + 2]; + g = data[FIXTRANS_PIXEL_R * 4 + 1]; + b = data[FIXTRANS_PIXEL_R * 4 + 0]; + a = data[FIXTRANS_PIXEL_R * 4 + 3]; + sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; + } + if(!cnt) + continue; + r0 = data[FIXTRANS_PIXEL * 4 + 2]; + g0 = data[FIXTRANS_PIXEL * 4 + 1]; + b0 = data[FIXTRANS_PIXEL * 4 + 0]; + if(sumA) + { + // there is a surrounding non-alpha pixel + r = (sumRA + sumA / 2) / sumA; + g = (sumGA + sumA / 2) / sumA; + b = (sumBA + sumA / 2) / sumA; + } + else + { + // need to use a "regular" average + r = (sumR + cnt / 2) / cnt; + g = (sumG + cnt / 2) / cnt; + b = (sumB + cnt / 2) / cnt; + } + if(r != r0 || g != g0 || b != b0) + ++changedPixels; + data[FIXTRANS_PIXEL * 4 + 2] = r; + data[FIXTRANS_PIXEL * 4 + 1] = g; + data[FIXTRANS_PIXEL * 4 + 0] = b; + fixMask[FIXTRANS_PIXEL] |= FIXTRANS_FIXED; + } + for(y = 0; y < h; ++y) + for(x = 0; x < w; ++x) + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_FIXED) + { + fixMask[FIXTRANS_PIXEL] &= ~(FIXTRANS_NEEDED | FIXTRANS_FIXED); + fixMask[FIXTRANS_PIXEL_D] |= FIXTRANS_HAS_U; + fixMask[FIXTRANS_PIXEL_U] |= FIXTRANS_HAS_D; + fixMask[FIXTRANS_PIXEL_R] |= FIXTRANS_HAS_L; + fixMask[FIXTRANS_PIXEL_L] |= FIXTRANS_HAS_R; + --fixPixels; + } + } + return changedPixels; +} + +void Image_FixTransparentPixels_f(void) +{ + const char *filename, *filename_pattern; + fssearch_t *search; + int i, n; + char outfilename[MAX_QPATH], buf[MAX_QPATH]; + unsigned char *data; + if(Cmd_Argc() != 2) + { + Con_Printf("Usage: %s imagefile\n", Cmd_Argv(0)); + return; + } + filename_pattern = Cmd_Argv(1); + search = FS_Search(filename_pattern, true, true); + if(!search) + return; + for(i = 0; i < search->numfilenames; ++i) + { + filename = search->filenames[i]; + Con_Printf("Processing %s... ", filename); + Image_StripImageExtension(filename, buf, sizeof(buf)); + dpsnprintf(outfilename, sizeof(outfilename), "fixtrans/%s.tga", buf); + if(!(data = loadimagepixelsbgra(filename, true, false, false, NULL))) + return; + if((n = fixtransparentpixels(data, image_width, image_height))) + { + Image_WriteTGABGRA(outfilename, image_width, image_height, data); + Con_Printf("%s written (%d pixels changed).\n", outfilename, n); + } + else + Con_Printf("unchanged.\n"); + Mem_Free(data); + } + FS_FreeSearch(search); +} + +qboolean Image_WriteTGABGR_preflipped (const char *filename, int width, int height, const unsigned char *data) +{ + qboolean ret; + unsigned char buffer[18]; + const void *buffers[2]; + fs_offset_t sizes[2]; + + memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = (width >> 0) & 0xFF; + buffer[13] = (width >> 8) & 0xFF; + buffer[14] = (height >> 0) & 0xFF; + buffer[15] = (height >> 8) & 0xFF; + buffer[16] = 24; // pixel size + + buffers[0] = buffer; + sizes[0] = 18; + buffers[1] = data; + sizes[1] = width*height*3; + ret = FS_WriteFileInBlocks(filename, buffers, sizes, 2); + + return ret; +} + +qboolean Image_WriteTGABGRA (const char *filename, int width, int height, const unsigned char *data) +{ + int y; + unsigned char *buffer, *out; + const unsigned char *in, *end; + qboolean ret; + + buffer = (unsigned char *)Mem_Alloc(tempmempool, width*height*4 + 18); + + memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = (width >> 0) & 0xFF; + buffer[13] = (width >> 8) & 0xFF; + buffer[14] = (height >> 0) & 0xFF; + buffer[15] = (height >> 8) & 0xFF; + + for (y = 3;y < width*height*4;y += 4) + if (data[y] < 255) + break; + + if (y < width*height*4) + { + // save the alpha channel + buffer[16] = 32; // pixel size + buffer[17] = 8; // 8 bits of alpha + + // flip upside down + out = buffer + 18; + for (y = height - 1;y >= 0;y--) + { + memcpy(out, data + y * width * 4, width * 4); + out += width*4; + } + } + else + { + // save only the color channels + buffer[16] = 24; // pixel size + buffer[17] = 0; // 8 bits of alpha + + // truncate bgra to bgr and flip upside down + out = buffer + 18; + for (y = height - 1;y >= 0;y--) + { + in = data + y * width * 4; + end = in + width * 4; + for (;in < end;in += 4) + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + } + } + } + ret = FS_WriteFile (filename, buffer, out - buffer); + + Mem_Free(buffer); + + return ret; +} + +static void Image_Resample32LerpLine (const unsigned char *in, unsigned char *out, int inwidth, int outwidth) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; + fstep = (int) (inwidth*65536.0f/outwidth); + endx = (inwidth-1); + for (j = 0,f = 0;j < outwidth;j++, f += fstep) + { + xi = f >> 16; + if (xi != oldx) + { + in += (xi - oldx) * 4; + oldx = xi; + } + if (xi < endx) + { + lerp = f & 0xFFFF; + *out++ = (unsigned char) ((((in[4] - in[0]) * lerp) >> 16) + in[0]); + *out++ = (unsigned char) ((((in[5] - in[1]) * lerp) >> 16) + in[1]); + *out++ = (unsigned char) ((((in[6] - in[2]) * lerp) >> 16) + in[2]); + *out++ = (unsigned char) ((((in[7] - in[3]) * lerp) >> 16) + in[3]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + *out++ = in[3]; + } + } +} + +#define LERPBYTE(i) r = resamplerow1[i];out[i] = (unsigned char) ((((resamplerow2[i] - r) * lerp) >> 16) + r) +void Image_Resample32Lerp(const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight) +{ + int i, j, r, yi, oldy, f, fstep, lerp, endy = (inheight-1), inwidth4 = inwidth*4, outwidth4 = outwidth*4; + unsigned char *out; + const unsigned char *inrow; + unsigned char *resamplerow1; + unsigned char *resamplerow2; + out = (unsigned char *)outdata; + fstep = (int) (inheight*65536.0f/outheight); + + resamplerow1 = (unsigned char *)Mem_Alloc(tempmempool, outwidth*4*2); + resamplerow2 = resamplerow1 + outwidth*4; + + inrow = (const unsigned char *)indata; + oldy = 0; + Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); + Image_Resample32LerpLine (inrow + inwidth4, resamplerow2, inwidth, outwidth); + for (i = 0, f = 0;i < outheight;i++,f += fstep) + { + yi = f >> 16; + if (yi < endy) + { + lerp = f & 0xFFFF; + if (yi != oldy) + { + inrow = (unsigned char *)indata + inwidth4*yi; + if (yi == oldy+1) + memcpy(resamplerow1, resamplerow2, outwidth4); + else + Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); + Image_Resample32LerpLine (inrow + inwidth4, resamplerow2, inwidth, outwidth); + oldy = yi; + } + j = outwidth - 4; + while(j >= 0) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + LERPBYTE(12); + LERPBYTE(13); + LERPBYTE(14); + LERPBYTE(15); + out += 16; + resamplerow1 += 16; + resamplerow2 += 16; + j -= 4; + } + if (j & 2) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + out += 8; + resamplerow1 += 8; + resamplerow2 += 8; + } + if (j & 1) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + out += 4; + resamplerow1 += 4; + resamplerow2 += 4; + } + resamplerow1 -= outwidth4; + resamplerow2 -= outwidth4; + } + else + { + if (yi != oldy) + { + inrow = (unsigned char *)indata + inwidth4*yi; + if (yi == oldy+1) + memcpy(resamplerow1, resamplerow2, outwidth4); + else + Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); + oldy = yi; + } + memcpy(out, resamplerow1, outwidth4); + } + } + + Mem_Free(resamplerow1); + resamplerow1 = NULL; + resamplerow2 = NULL; +} + +void Image_Resample32Nolerp(const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight) +{ + int i, j; + unsigned frac, fracstep; + // relies on int being 4 bytes + int *inrow, *out; + out = (int *)outdata; + + fracstep = inwidth*0x10000/outwidth; + for (i = 0;i < outheight;i++) + { + inrow = (int *)indata + inwidth*(i*inheight/outheight); + frac = fracstep >> 1; + j = outwidth - 4; + while (j >= 0) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out[1] = inrow[frac >> 16];frac += fracstep; + out[2] = inrow[frac >> 16];frac += fracstep; + out[3] = inrow[frac >> 16];frac += fracstep; + out += 4; + j -= 4; + } + if (j & 2) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out[1] = inrow[frac >> 16];frac += fracstep; + out += 2; + } + if (j & 1) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out += 1; + } + } +} + +/* +================ +Image_Resample +================ +*/ +void Image_Resample32(const void *indata, int inwidth, int inheight, int indepth, void *outdata, int outwidth, int outheight, int outdepth, int quality) +{ + if (indepth != 1 || outdepth != 1) + { + Con_Printf ("Image_Resample: 3D resampling not supported\n"); + return; + } + if (quality) + Image_Resample32Lerp(indata, inwidth, inheight, outdata, outwidth, outheight); + else + Image_Resample32Nolerp(indata, inwidth, inheight, outdata, outwidth, outheight); +} + +// in can be the same as out +void Image_MipReduce32(const unsigned char *in, unsigned char *out, int *width, int *height, int *depth, int destwidth, int destheight, int destdepth) +{ + const unsigned char *inrow; + int x, y, nextrow; + if (*depth != 1 || destdepth != 1) + { + Con_Printf ("Image_Resample: 3D resampling not supported\n"); + if (*width > destwidth) + *width >>= 1; + if (*height > destheight) + *height >>= 1; + if (*depth > destdepth) + *depth >>= 1; + return; + } + // note: if given odd width/height this discards the last row/column of + // pixels, rather than doing a proper box-filter scale down + inrow = in; + nextrow = *width * 4; + if (*width > destwidth) + { + *width >>= 1; + if (*height > destheight) + { + // reduce both + *height >>= 1; + for (y = 0;y < *height;y++, inrow += nextrow * 2) + { + for (in = inrow, x = 0;x < *width;x++) + { + out[0] = (unsigned char) ((in[0] + in[4] + in[nextrow ] + in[nextrow+4]) >> 2); + out[1] = (unsigned char) ((in[1] + in[5] + in[nextrow+1] + in[nextrow+5]) >> 2); + out[2] = (unsigned char) ((in[2] + in[6] + in[nextrow+2] + in[nextrow+6]) >> 2); + out[3] = (unsigned char) ((in[3] + in[7] + in[nextrow+3] + in[nextrow+7]) >> 2); + out += 4; + in += 8; + } + } + } + else + { + // reduce width + for (y = 0;y < *height;y++, inrow += nextrow) + { + for (in = inrow, x = 0;x < *width;x++) + { + out[0] = (unsigned char) ((in[0] + in[4]) >> 1); + out[1] = (unsigned char) ((in[1] + in[5]) >> 1); + out[2] = (unsigned char) ((in[2] + in[6]) >> 1); + out[3] = (unsigned char) ((in[3] + in[7]) >> 1); + out += 4; + in += 8; + } + } + } + } + else + { + if (*height > destheight) + { + // reduce height + *height >>= 1; + for (y = 0;y < *height;y++, inrow += nextrow * 2) + { + for (in = inrow, x = 0;x < *width;x++) + { + out[0] = (unsigned char) ((in[0] + in[nextrow ]) >> 1); + out[1] = (unsigned char) ((in[1] + in[nextrow+1]) >> 1); + out[2] = (unsigned char) ((in[2] + in[nextrow+2]) >> 1); + out[3] = (unsigned char) ((in[3] + in[nextrow+3]) >> 1); + out += 4; + in += 4; + } + } + } + else + Con_Printf ("Image_MipReduce: desired size already achieved\n"); + } +} + +void Image_HeightmapToNormalmap_BGRA(const unsigned char *inpixels, unsigned char *outpixels, int width, int height, int clamp, float bumpscale) +{ + int x, y, x1, x2, y1, y2; + const unsigned char *b, *row[3]; + int p[5]; + unsigned char *out; + float ibumpscale, n[3]; + ibumpscale = (255.0f * 6.0f) / bumpscale; + out = outpixels; + for (y = 0, y1 = height-1;y < height;y1 = y, y++) + { + y2 = y + 1;if (y2 >= height) y2 = 0; + row[0] = inpixels + (y1 * width) * 4; + row[1] = inpixels + (y * width) * 4; + row[2] = inpixels + (y2 * width) * 4; + for (x = 0, x1 = width-1;x < width;x1 = x, x++) + { + x2 = x + 1;if (x2 >= width) x2 = 0; + // left, right + b = row[1] + x1 * 4;p[0] = (b[0] + b[1] + b[2]); + b = row[1] + x2 * 4;p[1] = (b[0] + b[1] + b[2]); + // above, below + b = row[0] + x * 4;p[2] = (b[0] + b[1] + b[2]); + b = row[2] + x * 4;p[3] = (b[0] + b[1] + b[2]); + // center + b = row[1] + x * 4;p[4] = (b[0] + b[1] + b[2]); + // calculate a normal from the slopes + n[0] = p[0] - p[1]; + n[1] = p[3] - p[2]; + n[2] = ibumpscale; + VectorNormalize(n); + // turn it into a dot3 rgb vector texture + out[2] = (int)(128.0f + n[0] * 127.0f); + out[1] = (int)(128.0f + n[1] * 127.0f); + out[0] = (int)(128.0f + n[2] * 127.0f); + out[3] = (p[4]) / 3; + out += 4; + } + } +} diff --git a/misc/source/darkplaces-src/image.h b/misc/source/darkplaces-src/image.h new file mode 100644 index 00000000..60fbcce6 --- /dev/null +++ b/misc/source/darkplaces-src/image.h @@ -0,0 +1,57 @@ + +#ifndef IMAGE_H +#define IMAGE_H + +extern int image_width, image_height; + + +// swizzle components (even converting number of components) and flip images +// (warning: input must be different than output due to non-linear read/write) +// (tip: component indices can contain values | 0x80000000 to tell it to +// store them directly into output, so 255 | 0x80000000 would write 255) +void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices); + +// applies gamma correction to RGB pixels, in can be the same as out +void Image_GammaRemapRGB(const unsigned char *in, unsigned char *out, int pixels, const unsigned char *gammar, const unsigned char *gammag, const unsigned char *gammab); + +// converts 8bit image data to BGRA, in can not be the same as out +void Image_Copy8bitBGRA(const unsigned char *in, unsigned char *out, int pixels, const unsigned int *pal); + +void Image_StripImageExtension (const char *in, char *out, size_t size_out); + +// called by conchars.tga loader in gl_draw.c, otherwise private +unsigned char *LoadTGA_BGRA (const unsigned char *f, int filesize, int *miplevel); + +// loads a texture, as pixel data +unsigned char *loadimagepixelsbgra (const char *filename, qboolean complain, qboolean allowFixtrans, qboolean convertsRGB, int *miplevel); + +// loads an 8bit pcx image into a 296x194x8bit buffer, with cropping as needed +qboolean LoadPCX_QWSkin(const unsigned char *f, int filesize, unsigned char *pixels, int outwidth, int outheight); + +// loads a texture, as a texture +rtexture_t *loadtextureimage (rtexturepool_t *pool, const char *filename, qboolean complain, int flags, qboolean allowFixtrans, qboolean sRGB); + +// writes an upside down BGR image into a TGA +qboolean Image_WriteTGABGR_preflipped (const char *filename, int width, int height, const unsigned char *data); + +// writes a BGRA image into a TGA file +qboolean Image_WriteTGABGRA (const char *filename, int width, int height, const unsigned char *data); + +// resizes the image (in can not be the same as out) +void Image_Resample32(const void *indata, int inwidth, int inheight, int indepth, void *outdata, int outwidth, int outheight, int outdepth, int quality); + +// scales the image down by a power of 2 (in can be the same as out) +void Image_MipReduce32(const unsigned char *in, unsigned char *out, int *width, int *height, int *depth, int destwidth, int destheight, int destdepth); + +void Image_HeightmapToNormalmap_BGRA(const unsigned char *inpixels, unsigned char *outpixels, int width, int height, int clamp, float bumpscale); + +// console command to fix the colors of transparent pixels (to prevent weird borders) +void Image_FixTransparentPixels_f(void); +extern cvar_t r_fixtrans_auto; + +#define Image_LinearFloatFromsRGB(c) (((c) < 11) ? (c) * 0.000302341331f : (float)pow(((c)*(1.0f/256.0f) + 0.055f)*(1.0f/1.0555f), 2.4f)) + +void Image_MakeLinearColorsFromsRGB(unsigned char *pout, const unsigned char *pin, int numpixels); + +#endif + diff --git a/misc/source/darkplaces-src/image_png.c b/misc/source/darkplaces-src/image_png.c new file mode 100644 index 00000000..4267331b --- /dev/null +++ b/misc/source/darkplaces-src/image_png.c @@ -0,0 +1,528 @@ +/* + Copyright (C) 2006 Serge "(515)" Ziryukin, Forest "LordHavoc" Hale + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +//[515]: png implemented into DP ONLY FOR TESTING 2d stuff with csqc +// so delete this bullshit :D +// +//LordHavoc: rewrote most of this. + +#include "quakedef.h" +#include "image.h" +#include "image_png.h" + +static void (*qpng_set_sig_bytes) (void*, int); +static int (*qpng_sig_cmp) (const unsigned char*, size_t, size_t); +static void* (*qpng_create_read_struct) (const char*, void*, void(*)(void *png, const char *message), void(*)(void *png, const char *message)); +static void* (*qpng_create_write_struct) (const char*, void*, void(*)(void *png, const char *message), void(*)(void *png, const char *message)); +static void* (*qpng_create_info_struct) (void*); +static void (*qpng_read_info) (void*, void*); +static void (*qpng_set_compression_level) (void*, int); +static void (*qpng_set_filter) (void*, int, int); +static void (*qpng_set_expand) (void*); +static void (*qpng_set_palette_to_rgb) (void*); +static void (*qpng_set_tRNS_to_alpha) (void*); +static void (*qpng_set_gray_to_rgb) (void*); +static void (*qpng_set_filler) (void*, unsigned int, int); +static void (*qpng_set_IHDR) (void*, void*, unsigned long, unsigned long, int, int, int, int, int); +static void (*qpng_set_packing) (void*); +static void (*qpng_set_bgr) (void*); +static int (*qpng_set_interlace_handling) (void*); +static void (*qpng_read_update_info) (void*, void*); +static void (*qpng_read_image) (void*, unsigned char**); +static void (*qpng_read_end) (void*, void*); +static void (*qpng_destroy_read_struct) (void**, void**, void**); +static void (*qpng_destroy_write_struct) (void**, void**); +static void (*qpng_set_read_fn) (void*, void*, void(*)(void *png, unsigned char *data, size_t length)); +static void (*qpng_set_write_fn) (void*, void*, void(*)(void *png, unsigned char *data, size_t length), void(*)(void *png)); +static unsigned int (*qpng_get_valid) (void*, void*, unsigned int); +static unsigned int (*qpng_get_rowbytes) (void*, void*); +static unsigned char (*qpng_get_channels) (void*, void*); +static unsigned char (*qpng_get_bit_depth) (void*, void*); +static unsigned int (*qpng_get_IHDR) (void*, void*, unsigned long*, unsigned long*, int *, int *, int *, int *, int *); +static unsigned int (*qpng_access_version_number) (void); // FIXME is this return type right? It is a png_uint_32 in libpng +static void (*qpng_write_info) (void*, void*); +static void (*qpng_write_row) (void*, unsigned char*); +static void (*qpng_write_end) (void*, void*); + +static dllfunction_t pngfuncs[] = +{ + {"png_set_sig_bytes", (void **) &qpng_set_sig_bytes}, + {"png_sig_cmp", (void **) &qpng_sig_cmp}, + {"png_create_read_struct", (void **) &qpng_create_read_struct}, + {"png_create_write_struct", (void **) &qpng_create_write_struct}, + {"png_create_info_struct", (void **) &qpng_create_info_struct}, + {"png_read_info", (void **) &qpng_read_info}, + {"png_set_compression_level", (void **) &qpng_set_compression_level}, + {"png_set_filter", (void **) &qpng_set_filter}, + {"png_set_expand", (void **) &qpng_set_expand}, + {"png_set_palette_to_rgb", (void **) &qpng_set_palette_to_rgb}, + {"png_set_tRNS_to_alpha", (void **) &qpng_set_tRNS_to_alpha}, + {"png_set_gray_to_rgb", (void **) &qpng_set_gray_to_rgb}, + {"png_set_filler", (void **) &qpng_set_filler}, + {"png_set_IHDR", (void **) &qpng_set_IHDR}, + {"png_set_packing", (void **) &qpng_set_packing}, + {"png_set_bgr", (void **) &qpng_set_bgr}, + {"png_set_interlace_handling", (void **) &qpng_set_interlace_handling}, + {"png_read_update_info", (void **) &qpng_read_update_info}, + {"png_read_image", (void **) &qpng_read_image}, + {"png_read_end", (void **) &qpng_read_end}, + {"png_destroy_read_struct", (void **) &qpng_destroy_read_struct}, + {"png_destroy_write_struct", (void **) &qpng_destroy_write_struct}, + {"png_set_read_fn", (void **) &qpng_set_read_fn}, + {"png_set_write_fn", (void **) &qpng_set_write_fn}, + {"png_get_valid", (void **) &qpng_get_valid}, + {"png_get_rowbytes", (void **) &qpng_get_rowbytes}, + {"png_get_channels", (void **) &qpng_get_channels}, + {"png_get_bit_depth", (void **) &qpng_get_bit_depth}, + {"png_get_IHDR", (void **) &qpng_get_IHDR}, + {"png_access_version_number", (void **) &qpng_access_version_number}, + {"png_write_info", (void **) &qpng_write_info}, + {"png_write_row", (void **) &qpng_write_row}, + {"png_write_end", (void **) &qpng_write_end}, + {NULL, NULL} +}; + +// Handle for PNG DLL +dllhandle_t png_dll = NULL; + + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +PNG_OpenLibrary + +Try to load the PNG DLL +==================== +*/ +qboolean PNG_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if WIN32 + "libpng14-14.dll", + "libpng14.dll", + "libpng12.dll", +#elif defined(MACOSX) + "libpng14.14.dylib", + "libpng12.0.dylib", +#else + "libpng14.so.14", // WTF libtool guidelines anyone? + "libpng12.so.0", + "libpng.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (png_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &png_dll, pngfuncs); +} + + +/* +==================== +PNG_CloseLibrary + +Unload the PNG DLL +==================== +*/ +void PNG_CloseLibrary (void) +{ + Sys_UnloadLibrary (&png_dll); +} + +/* +================================================================= + + PNG decompression + +================================================================= +*/ + +#define PNG_LIBPNG_VER_STRING_12 "1.2.4" +#define PNG_LIBPNG_VER_STRING_14 "1.4.0" + +#define PNG_COLOR_MASK_PALETTE 1 +#define PNG_COLOR_MASK_COLOR 2 +#define PNG_COLOR_MASK_ALPHA 4 + +#define PNG_COLOR_TYPE_GRAY 0 +#define PNG_COLOR_TYPE_PALETTE (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE) +#define PNG_COLOR_TYPE_RGB (PNG_COLOR_MASK_COLOR) +#define PNG_COLOR_TYPE_RGB_ALPHA (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA) +#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA) + +#define PNG_COLOR_TYPE_RGBA PNG_COLOR_TYPE_RGB_ALPHA +#define PNG_COLOR_TYPE_GA PNG_COLOR_TYPE_GRAY_ALPHA + +#define PNG_INFO_tRNS 0x0010 + +// this struct is only used for status information during loading +static struct +{ + const unsigned char *tmpBuf; + int tmpBuflength; + int tmpi; + //int FBgColor; + //int FTransparent; + unsigned int FRowBytes; + //double FGamma; + //double FScreenGamma; + unsigned char **FRowPtrs; + unsigned char *Data; + //char *Title; + //char *Author; + //char *Description; + int BitDepth; + int BytesPerPixel; + int ColorType; + unsigned long Height; // retarded libpng 1.2 pngconf.h uses long (64bit/32bit depending on arch) + unsigned long Width; // retarded libpng 1.2 pngconf.h uses long (64bit/32bit depending on arch) + int Interlace; + int Compression; + int Filter; + //double LastModified; + //int Transparent; + qfile_t *outfile; +} my_png; + +//LordHavoc: removed __cdecl prefix, added overrun protection, and rewrote this to be more efficient +void PNG_fReadData(void *png, unsigned char *data, size_t length) +{ + size_t l; + l = my_png.tmpBuflength - my_png.tmpi; + if (l < length) + { + Con_Printf("PNG_fReadData: overrun by %i bytes\n", (int)(length - l)); + // a read going past the end of the file, fill in the remaining bytes + // with 0 just to be consistent + memset(data + l, 0, length - l); + length = l; + } + memcpy(data, my_png.tmpBuf + my_png.tmpi, length); + my_png.tmpi += (int)length; + //Com_HexDumpToConsole(data, (int)length); +} + +void PNG_fWriteData(void *png, unsigned char *data, size_t length) +{ + FS_Write(my_png.outfile, data, length); +} + +void PNG_fFlushData(void *png) +{ +} + +void PNG_error_fn(void *png, const char *message) +{ + Con_Printf("PNG_LoadImage: error: %s\n", message); +} + +void PNG_warning_fn(void *png, const char *message) +{ + Con_Printf("PNG_LoadImage: warning: %s\n", message); +} + +extern int image_width; +extern int image_height; + +unsigned char *PNG_LoadImage_BGRA (const unsigned char *raw, int filesize, int *miplevel) +{ + unsigned int c; + unsigned int y; + void *png, *pnginfo; + unsigned char *imagedata = NULL; + unsigned char ioBuffer[8192]; + + // FIXME: register an error handler so that abort() won't be called on error + + // No DLL = no PNGs + if (!png_dll) + return NULL; + + if(qpng_sig_cmp(raw, 0, filesize)) + return NULL; + png = (void *)qpng_create_read_struct( + (qpng_access_version_number() / 100 == 102) ? PNG_LIBPNG_VER_STRING_12 : PNG_LIBPNG_VER_STRING_14, // nasty hack to support both libpng12 and libpng14 + 0, PNG_error_fn, PNG_warning_fn + ); + if(!png) + return NULL; + + // this must be memset before the setjmp error handler, because it relies + // on the fields in this struct for cleanup + memset(&my_png, 0, sizeof(my_png)); + + // NOTE: this relies on jmp_buf being the first thing in the png structure + // created by libpng! (this is correct for libpng 1.2.x) +#ifdef __cplusplus +#ifdef WIN64 + if (setjmp((_JBTYPE *)png)) +#elif defined(MACOSX) || defined(WIN32) + if (setjmp((int *)png)) +#else + if (setjmp((__jmp_buf_tag *)png)) +#endif +#else + if (setjmp(png)) +#endif + { + if (my_png.Data) + Mem_Free(my_png.Data); + my_png.Data = NULL; + if (my_png.FRowPtrs) + Mem_Free(my_png.FRowPtrs); + my_png.FRowPtrs = NULL; + qpng_destroy_read_struct(&png, &pnginfo, 0); + return NULL; + } + // + + pnginfo = qpng_create_info_struct(png); + if(!pnginfo) + { + qpng_destroy_read_struct(&png, &pnginfo, 0); + return NULL; + } + qpng_set_sig_bytes(png, 0); + + my_png.tmpBuf = raw; + my_png.tmpBuflength = filesize; + my_png.tmpi = 0; + //my_png.Data = NULL; + //my_png.FRowPtrs = NULL; + //my_png.Height = 0; + //my_png.Width = 0; + my_png.ColorType = PNG_COLOR_TYPE_RGB; + //my_png.Interlace = 0; + //my_png.Compression = 0; + //my_png.Filter = 0; + qpng_set_read_fn(png, ioBuffer, PNG_fReadData); + qpng_read_info(png, pnginfo); + qpng_get_IHDR(png, pnginfo, &my_png.Width, &my_png.Height,&my_png.BitDepth, &my_png.ColorType, &my_png.Interlace, &my_png.Compression, &my_png.Filter); + + // this check guards against pngconf.h with unsigned int *width/height parameters on big endian systems by detecting the strange values and shifting them down 32bits + // (if it's little endian the unwritten bytes are the most significant + // ones and we don't worry about that) + // + // this is only necessary because of retarded 64bit png_uint_32 types in libpng 1.2, which can (conceivably) vary by platform +#if LONG_MAX > 4000000000 + if (my_png.Width > LONG_MAX || my_png.Height > LONG_MAX) + { + my_png.Width >>= 32; + my_png.Height >>= 32; + } +#endif + + if (my_png.ColorType == PNG_COLOR_TYPE_PALETTE) + qpng_set_palette_to_rgb(png); + if (my_png.ColorType == PNG_COLOR_TYPE_GRAY || my_png.ColorType == PNG_COLOR_TYPE_GRAY_ALPHA) + qpng_set_gray_to_rgb(png); + if (qpng_get_valid(png, pnginfo, PNG_INFO_tRNS)) + qpng_set_tRNS_to_alpha(png); + if (my_png.BitDepth == 8 && !(my_png.ColorType & PNG_COLOR_MASK_ALPHA)) + qpng_set_filler(png, 255, 1); + if (( my_png.ColorType == PNG_COLOR_TYPE_GRAY) || (my_png.ColorType == PNG_COLOR_TYPE_GRAY_ALPHA )) + qpng_set_gray_to_rgb(png); + if (my_png.BitDepth < 8) + qpng_set_expand(png); + + qpng_read_update_info(png, pnginfo); + + my_png.FRowBytes = qpng_get_rowbytes(png, pnginfo); + my_png.BytesPerPixel = qpng_get_channels(png, pnginfo); + + my_png.FRowPtrs = (unsigned char **)Mem_Alloc(tempmempool, my_png.Height * sizeof(*my_png.FRowPtrs)); + if (my_png.FRowPtrs) + { + imagedata = (unsigned char *)Mem_Alloc(tempmempool, my_png.Height * my_png.FRowBytes); + if(imagedata) + { + my_png.Data = imagedata; + for(y = 0;y < my_png.Height;y++) + my_png.FRowPtrs[y] = my_png.Data + y * my_png.FRowBytes; + qpng_read_image(png, my_png.FRowPtrs); + } + else + { + Con_Printf("PNG_LoadImage : not enough memory\n"); + qpng_destroy_read_struct(&png, &pnginfo, 0); + Mem_Free(my_png.FRowPtrs); + return NULL; + } + Mem_Free(my_png.FRowPtrs); + my_png.FRowPtrs = NULL; + } + else + { + Con_Printf("PNG_LoadImage : not enough memory\n"); + qpng_destroy_read_struct(&png, &pnginfo, 0); + return NULL; + } + + qpng_read_end(png, pnginfo); + qpng_destroy_read_struct(&png, &pnginfo, 0); + + image_width = (int)my_png.Width; + image_height = (int)my_png.Height; + + if (my_png.BitDepth != 8) + { + Con_Printf ("PNG_LoadImage : bad color depth\n"); + Mem_Free(imagedata); + return NULL; + } + + // swizzle RGBA to BGRA + for (y = 0;y < (unsigned int)(image_width*image_height*4);y += 4) + { + c = imagedata[y+0]; + imagedata[y+0] = imagedata[y+2]; + imagedata[y+2] = c; + } + + return imagedata; +} + +/* +================================================================= + + PNG compression + +================================================================= +*/ + +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define PNG_INTERLACE_NONE 0 +#define PNG_INTERLACE_ADAM7 1 +#define PNG_FILTER_TYPE_BASE 0 +#define PNG_FILTER_TYPE_DEFAULT PNG_FILTER_TYPE_BASE +#define PNG_COMPRESSION_TYPE_BASE 0 +#define PNG_COMPRESSION_TYPE_DEFAULT PNG_COMPRESSION_TYPE_BASE +#define PNG_NO_FILTERS 0x00 +#define PNG_FILTER_NONE 0x08 +#define PNG_FILTER_SUB 0x10 +#define PNG_FILTER_UP 0x20 +#define PNG_FILTER_AVG 0x40 +#define PNG_FILTER_PAETH 0x80 +#define PNG_ALL_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | \ + PNG_FILTER_AVG | PNG_FILTER_PAETH) + +/* +==================== +PNG_SaveImage_preflipped + +Save a preflipped PNG image to a file +==================== +*/ +qboolean PNG_SaveImage_preflipped (const char *filename, int width, int height, qboolean has_alpha, unsigned char *data) +{ + unsigned int offset, linesize; + qfile_t* file = NULL; + void *png, *pnginfo; + unsigned char ioBuffer[8192]; + int passes, i, j; + + // No DLL = no JPEGs + if (!png_dll) + { + Con_Print("You need the libpng library to save PNG images\n"); + return false; + } + + png = (void *)qpng_create_write_struct( + (qpng_access_version_number() / 100 == 102) ? PNG_LIBPNG_VER_STRING_12 : PNG_LIBPNG_VER_STRING_14, // nasty hack to support both libpng12 and libpng14 + 0, PNG_error_fn, PNG_warning_fn + ); + if(!png) + return false; + pnginfo = (void *)qpng_create_info_struct(png); + if(!pnginfo) + { + qpng_destroy_write_struct(&png, NULL); + return false; + } + + // this must be memset before the setjmp error handler, because it relies + // on the fields in this struct for cleanup + memset(&my_png, 0, sizeof(my_png)); + + // NOTE: this relies on jmp_buf being the first thing in the png structure + // created by libpng! (this is correct for libpng 1.2.x) +#ifdef __cplusplus +#ifdef WIN64 + if (setjmp((_JBTYPE *)png)) +#elif defined(MACOSX) || defined(WIN32) + if (setjmp((int *)png)) +#else + if (setjmp((__jmp_buf_tag *)png)) +#endif +#else + if (setjmp(png)) +#endif + { + qpng_destroy_write_struct(&png, &pnginfo); + return false; + } + + // Open the file + file = FS_OpenRealFile(filename, "wb", true); + if (!file) + return false; + my_png.outfile = file; + qpng_set_write_fn(png, ioBuffer, PNG_fWriteData, PNG_fFlushData); + + //qpng_set_compression_level(png, Z_BEST_COMPRESSION); + qpng_set_compression_level(png, Z_BEST_SPEED); + qpng_set_IHDR(png, pnginfo, width, height, 8, has_alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_ADAM7, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + qpng_set_filter(png, 0, PNG_NO_FILTERS); + qpng_write_info(png, pnginfo); + qpng_set_packing(png); + qpng_set_bgr(png); + + passes = qpng_set_interlace_handling(png); + + linesize = width * (has_alpha ? 4 : 3); + offset = linesize * (height - 1); + for(i = 0; i < passes; ++i) + for(j = 0; j < height; ++j) + qpng_write_row(png, &data[offset - j * linesize]); + + qpng_write_end(png, NULL); + qpng_destroy_write_struct(&png, &pnginfo); + + FS_Close (file); + + return true; +} diff --git a/misc/source/darkplaces-src/image_png.h b/misc/source/darkplaces-src/image_png.h new file mode 100644 index 00000000..d290d98f --- /dev/null +++ b/misc/source/darkplaces-src/image_png.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2006 Serge "(515)" Ziryukin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef PNG_H +#define PNG_H + +qboolean PNG_OpenLibrary (void); +void PNG_CloseLibrary (void); +unsigned char* PNG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel); +qboolean PNG_SaveImage_preflipped (const char *filename, int width, int height, qboolean has_alpha, unsigned char *data); + +#endif + diff --git a/misc/source/darkplaces-src/input.h b/misc/source/darkplaces-src/input.h new file mode 100644 index 00000000..e157294f --- /dev/null +++ b/misc/source/darkplaces-src/input.h @@ -0,0 +1,53 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/// \file input.h -- external (non-keyboard) input devices + +#ifndef INPUT_H +#define INPUT_H + +extern cvar_t in_pitch_min; +extern cvar_t in_pitch_max; + +extern qboolean in_client_mouse; +extern float in_windowmouse_x, in_windowmouse_y; +extern float in_mouse_x, in_mouse_y; + +//enum input_dest_e {input_game,input_message,input_menu} input_dest; + +void IN_Move (void); +// add additional movement on top of the keyboard move cmd + +#define IN_BESTWEAPON_MAX 32 +typedef struct +{ + char name[32]; + int impulse; + int activeweaponcode; + int weaponbit; + int ammostat; + int ammomin; + /// \TODO add a parameter for the picture to be used by the sbar, and use it there +} +in_bestweapon_info_t; +extern in_bestweapon_info_t in_bestweapon_info[IN_BESTWEAPON_MAX]; +void IN_BestWeapon_ResetData(void); ///< call before each map so QC can start from a clean state + +#endif + diff --git a/misc/source/darkplaces-src/intoverflow.h b/misc/source/darkplaces-src/intoverflow.h new file mode 100644 index 00000000..df906168 --- /dev/null +++ b/misc/source/darkplaces-src/intoverflow.h @@ -0,0 +1,22 @@ +#ifndef INTOVERFLOW_H +#define INTOVERFLOW_H + +// simple safe library to handle integer overflows when doing buffer size calculations +// Usage: +// - calculate data size using INTOVERFLOW_??? macros +// - compare: calculated-size <= INTOVERFLOW_NORMALIZE(buffersize) +// Functionality: +// - all overflows (values > INTOVERFLOW_MAX) and errors are mapped to INTOVERFLOW_MAX +// - if any input of an operation is INTOVERFLOW_MAX, INTOVERFLOW_MAX will be returned +// - otherwise, regular arithmetics apply + +#define INTOVERFLOW_MAX 2147483647 + +#define INTOVERFLOW_ADD(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (a) < INTOVERFLOW_MAX - (b)) ? ((a) + (b)) : INTOVERFLOW_MAX) +#define INTOVERFLOW_SUB(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (b) <= (a)) ? ((a) - (b)) : INTOVERFLOW_MAX) +#define INTOVERFLOW_MUL(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (a) < INTOVERFLOW_MAX / (b)) ? ((a) * (b)) : INTOVERFLOW_MAX) +#define INTOVERFLOW_DIV(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (b) > 0) ? ((a) / (b)) : INTOVERFLOW_MAX) + +#define INTOVERFLOW_NORMALIZE(a) (((a) < INTOVERFLOW_MAX) ? (a) : (INTOVERFLOW_MAX - 1)) + +#endif diff --git a/misc/source/darkplaces-src/jpeg.c b/misc/source/darkplaces-src/jpeg.c new file mode 100644 index 00000000..8df1b484 --- /dev/null +++ b/misc/source/darkplaces-src/jpeg.c @@ -0,0 +1,1106 @@ +/* + Copyright (C) 2002 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#include "quakedef.h" +#include "image.h" +#include "jpeg.h" +#include "image_png.h" + +cvar_t sv_writepicture_quality = {CVAR_SAVE, "sv_writepicture_quality", "10", "WritePicture quality offset (higher means better quality, but slower)"}; +cvar_t r_texture_jpeg_fastpicmip = {CVAR_SAVE, "r_texture_jpeg_fastpicmip", "1", "perform gl_picmip during decompression for JPEG files (faster)"}; + +// jboolean is unsigned char instead of int on Win32 +#ifdef WIN32 +typedef unsigned char jboolean; +#else +typedef int jboolean; +#endif + +#ifdef LINK_TO_LIBJPEG +#include +#define qjpeg_create_compress jpeg_create_compress +#define qjpeg_create_decompress jpeg_create_decompress +#define qjpeg_destroy_compress jpeg_destroy_compress +#define qjpeg_destroy_decompress jpeg_destroy_decompress +#define qjpeg_finish_compress jpeg_finish_compress +#define qjpeg_finish_decompress jpeg_finish_decompress +#define qjpeg_resync_to_restart jpeg_resync_to_restart +#define qjpeg_read_header jpeg_read_header +#define qjpeg_read_scanlines jpeg_read_scanlines +#define qjpeg_set_defaults jpeg_set_defaults +#define qjpeg_set_quality jpeg_set_quality +#define qjpeg_start_compress jpeg_start_compress +#define qjpeg_start_decompress jpeg_start_decompress +#define qjpeg_std_error jpeg_std_error +#define qjpeg_write_scanlines jpeg_write_scanlines +#define qjpeg_simple_progression jpeg_simple_progression +#define jpeg_dll true +#else +/* +================================================================= + + Minimal set of definitions from the JPEG lib + + WARNING: for a matter of simplicity, several pointer types are + casted to "void*", and most enumerated values are not included + +================================================================= +*/ + +typedef void *j_common_ptr; +typedef struct jpeg_compress_struct *j_compress_ptr; +typedef struct jpeg_decompress_struct *j_decompress_ptr; + +#define JPEG_LIB_VERSION 62 // Version 6b + +typedef enum +{ + JCS_UNKNOWN, + JCS_GRAYSCALE, + JCS_RGB, + JCS_YCbCr, + JCS_CMYK, + JCS_YCCK +} J_COLOR_SPACE; +typedef enum {JPEG_DUMMY1} J_DCT_METHOD; +typedef enum {JPEG_DUMMY2} J_DITHER_MODE; +typedef unsigned int JDIMENSION; + +#define JPOOL_PERMANENT 0 // lasts until master record is destroyed +#define JPOOL_IMAGE 1 // lasts until done with image/datastream + +#define JPEG_EOI 0xD9 // EOI marker code + +#define JMSG_STR_PARM_MAX 80 + +#define DCTSIZE2 64 +#define NUM_QUANT_TBLS 4 +#define NUM_HUFF_TBLS 4 +#define NUM_ARITH_TBLS 16 +#define MAX_COMPS_IN_SCAN 4 +#define C_MAX_BLOCKS_IN_MCU 10 +#define D_MAX_BLOCKS_IN_MCU 10 + +struct jpeg_memory_mgr +{ + void* (*alloc_small) (j_common_ptr cinfo, int pool_id, size_t sizeofobject); + void (*_reserve_space_for_alloc_large) (void *dummy, ...); + void (*_reserve_space_for_alloc_sarray) (void *dummy, ...); + void (*_reserve_space_for_alloc_barray) (void *dummy, ...); + void (*_reserve_space_for_request_virt_sarray) (void *dummy, ...); + void (*_reserve_space_for_request_virt_barray) (void *dummy, ...); + void (*_reserve_space_for_realize_virt_arrays) (void *dummy, ...); + void (*_reserve_space_for_access_virt_sarray) (void *dummy, ...); + void (*_reserve_space_for_access_virt_barray) (void *dummy, ...); + void (*_reserve_space_for_free_pool) (void *dummy, ...); + void (*_reserve_space_for_self_destruct) (void *dummy, ...); + + long max_memory_to_use; + long max_alloc_chunk; +}; + +struct jpeg_error_mgr +{ + void (*error_exit) (j_common_ptr cinfo); + void (*emit_message) (j_common_ptr cinfo, int msg_level); + void (*output_message) (j_common_ptr cinfo); + void (*format_message) (j_common_ptr cinfo, char * buffer); + void (*reset_error_mgr) (j_common_ptr cinfo); + int msg_code; + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + int trace_level; + long num_warnings; + const char * const * jpeg_message_table; + int last_jpeg_message; + const char * const * addon_message_table; + int first_addon_message; + int last_addon_message; +}; + +struct jpeg_source_mgr +{ + const unsigned char *next_input_byte; + size_t bytes_in_buffer; + + void (*init_source) (j_decompress_ptr cinfo); + jboolean (*fill_input_buffer) (j_decompress_ptr cinfo); + void (*skip_input_data) (j_decompress_ptr cinfo, long num_bytes); + jboolean (*resync_to_restart) (j_decompress_ptr cinfo, int desired); + void (*term_source) (j_decompress_ptr cinfo); +}; + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + jboolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is currently used only for decompression. + */ + void *quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + +struct jpeg_decompress_struct +{ + struct jpeg_error_mgr *err; // USED + struct jpeg_memory_mgr *mem; // USED + + void *progress; + void *client_data; + jboolean is_decompressor; + int global_state; + + struct jpeg_source_mgr *src; // USED + JDIMENSION image_width; // USED + JDIMENSION image_height; // USED + + int num_components; + J_COLOR_SPACE jpeg_color_space; + J_COLOR_SPACE out_color_space; + unsigned int scale_num, scale_denom; + double output_gamma; + jboolean buffered_image; + jboolean raw_data_out; + J_DCT_METHOD dct_method; + jboolean do_fancy_upsampling; + jboolean do_block_smoothing; + jboolean quantize_colors; + J_DITHER_MODE dither_mode; + jboolean two_pass_quantize; + int desired_number_of_colors; + jboolean enable_1pass_quant; + jboolean enable_external_quant; + jboolean enable_2pass_quant; + JDIMENSION output_width; + + JDIMENSION output_height; // USED + + int out_color_components; + + int output_components; // USED + + int rec_outbuf_height; + int actual_number_of_colors; + void *colormap; + + JDIMENSION output_scanline; // USED + + int input_scan_number; + JDIMENSION input_iMCU_row; + int output_scan_number; + JDIMENSION output_iMCU_row; + int (*coef_bits)[DCTSIZE2]; + void *quant_tbl_ptrs[NUM_QUANT_TBLS]; + void *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + void *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + int data_precision; + jpeg_component_info *comp_info; + jboolean progressive_mode; + jboolean arith_code; + unsigned char arith_dc_L[NUM_ARITH_TBLS]; + unsigned char arith_dc_U[NUM_ARITH_TBLS]; + unsigned char arith_ac_K[NUM_ARITH_TBLS]; + unsigned int restart_interval; + jboolean saw_JFIF_marker; + unsigned char JFIF_major_version; + unsigned char JFIF_minor_version; + unsigned char density_unit; + unsigned short X_density; + unsigned short Y_density; + jboolean saw_Adobe_marker; + unsigned char Adobe_transform; + jboolean CCIR601_sampling; + void *marker_list; + int max_h_samp_factor; + int max_v_samp_factor; + int min_DCT_scaled_size; + JDIMENSION total_iMCU_rows; + void *sample_range_limit; + int comps_in_scan; + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + JDIMENSION MCUs_per_row; + JDIMENSION MCU_rows_in_scan; + int blocks_in_MCU; + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + int Ss, Se, Ah, Al; + int unread_marker; + void *master; + void *main; + void *coef; + void *post; + void *inputctl; + void *marker; + void *entropy; + void *idct; + void *upsample; + void *cconvert; + void *cquantize; +}; + + +struct jpeg_compress_struct +{ + struct jpeg_error_mgr *err; + struct jpeg_memory_mgr *mem; + void *progress; + void *client_data; + jboolean is_decompressor; + int global_state; + + void *dest; + JDIMENSION image_width; + JDIMENSION image_height; + int input_components; + J_COLOR_SPACE in_color_space; + double input_gamma; + int data_precision; + + int num_components; + J_COLOR_SPACE jpeg_color_space; + jpeg_component_info *comp_info; + void *quant_tbl_ptrs[NUM_QUANT_TBLS]; + void *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + void *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + unsigned char arith_dc_L[NUM_ARITH_TBLS]; + unsigned char arith_dc_U[NUM_ARITH_TBLS]; + unsigned char arith_ac_K[NUM_ARITH_TBLS]; + + int num_scans; + const void *scan_info; + jboolean raw_data_in; + jboolean arith_code; + jboolean optimize_coding; + jboolean CCIR601_sampling; + int smoothing_factor; + J_DCT_METHOD dct_method; + + unsigned int restart_interval; + int restart_in_rows; + + jboolean write_JFIF_header; + unsigned char JFIF_major_version; + unsigned char JFIF_minor_version; + unsigned char density_unit; + unsigned short X_density; + unsigned short Y_density; + jboolean write_Adobe_marker; + JDIMENSION next_scanline; + + jboolean progressive_mode; + int max_h_samp_factor; + int max_v_samp_factor; + JDIMENSION total_iMCU_rows; + int comps_in_scan; + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + JDIMENSION MCUs_per_row; + JDIMENSION MCU_rows_in_scan; + int blocks_in_MCU; + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + int Ss, Se, Ah, Al; + + void *master; + void *main; + void *prep; + void *coef; + void *marker; + void *cconvert; + void *downsample; + void *fdct; + void *entropy; + void *script_space; + int script_space_size; +}; + +struct jpeg_destination_mgr +{ + unsigned char* next_output_byte; + size_t free_in_buffer; + + void (*init_destination) (j_compress_ptr cinfo); + jboolean (*empty_output_buffer) (j_compress_ptr cinfo); + void (*term_destination) (j_compress_ptr cinfo); +}; + + +/* +================================================================= + + DarkPlaces definitions + +================================================================= +*/ + +// Functions exported from libjpeg +#define qjpeg_create_compress(cinfo) \ + qjpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof(struct jpeg_compress_struct)) +#define qjpeg_create_decompress(cinfo) \ + qjpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof(struct jpeg_decompress_struct)) + +static void (*qjpeg_CreateCompress) (j_compress_ptr cinfo, int version, size_t structsize); +static void (*qjpeg_CreateDecompress) (j_decompress_ptr cinfo, int version, size_t structsize); +static void (*qjpeg_destroy_compress) (j_compress_ptr cinfo); +static void (*qjpeg_destroy_decompress) (j_decompress_ptr cinfo); +static void (*qjpeg_finish_compress) (j_compress_ptr cinfo); +static jboolean (*qjpeg_finish_decompress) (j_decompress_ptr cinfo); +static jboolean (*qjpeg_resync_to_restart) (j_decompress_ptr cinfo, int desired); +static int (*qjpeg_read_header) (j_decompress_ptr cinfo, jboolean require_image); +static JDIMENSION (*qjpeg_read_scanlines) (j_decompress_ptr cinfo, unsigned char** scanlines, JDIMENSION max_lines); +static void (*qjpeg_set_defaults) (j_compress_ptr cinfo); +static void (*qjpeg_set_quality) (j_compress_ptr cinfo, int quality, jboolean force_baseline); +static jboolean (*qjpeg_start_compress) (j_compress_ptr cinfo, jboolean write_all_tables); +static jboolean (*qjpeg_start_decompress) (j_decompress_ptr cinfo); +static struct jpeg_error_mgr* (*qjpeg_std_error) (struct jpeg_error_mgr *err); +static JDIMENSION (*qjpeg_write_scanlines) (j_compress_ptr cinfo, unsigned char** scanlines, JDIMENSION num_lines); +static void (*qjpeg_simple_progression) (j_compress_ptr cinfo); + +static dllfunction_t jpegfuncs[] = +{ + {"jpeg_CreateCompress", (void **) &qjpeg_CreateCompress}, + {"jpeg_CreateDecompress", (void **) &qjpeg_CreateDecompress}, + {"jpeg_destroy_compress", (void **) &qjpeg_destroy_compress}, + {"jpeg_destroy_decompress", (void **) &qjpeg_destroy_decompress}, + {"jpeg_finish_compress", (void **) &qjpeg_finish_compress}, + {"jpeg_finish_decompress", (void **) &qjpeg_finish_decompress}, + {"jpeg_resync_to_restart", (void **) &qjpeg_resync_to_restart}, + {"jpeg_read_header", (void **) &qjpeg_read_header}, + {"jpeg_read_scanlines", (void **) &qjpeg_read_scanlines}, + {"jpeg_set_defaults", (void **) &qjpeg_set_defaults}, + {"jpeg_set_quality", (void **) &qjpeg_set_quality}, + {"jpeg_start_compress", (void **) &qjpeg_start_compress}, + {"jpeg_start_decompress", (void **) &qjpeg_start_decompress}, + {"jpeg_std_error", (void **) &qjpeg_std_error}, + {"jpeg_write_scanlines", (void **) &qjpeg_write_scanlines}, + {"jpeg_simple_progression", (void **) &qjpeg_simple_progression}, + {NULL, NULL} +}; + +// Handle for JPEG DLL +dllhandle_t jpeg_dll = NULL; +qboolean jpeg_tried_loading = 0; +#endif + +static unsigned char jpeg_eoi_marker [2] = {0xFF, JPEG_EOI}; +static jmp_buf error_in_jpeg; +static qboolean jpeg_toolarge; + +// Our own output manager for JPEG compression +typedef struct +{ + struct jpeg_destination_mgr pub; + + qfile_t* outfile; + unsigned char* buffer; + size_t bufsize; // used if outfile is NULL +} my_destination_mgr; +typedef my_destination_mgr* my_dest_ptr; + + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +JPEG_OpenLibrary + +Try to load the JPEG DLL +==================== +*/ +qboolean JPEG_OpenLibrary (void) +{ +#ifdef LINK_TO_LIBJPEG + return true; +#else + const char* dllnames [] = + { +#if defined(WIN32) + "libjpeg.dll", +#elif defined(MACOSX) + "libjpeg.62.dylib", +#else + "libjpeg.so.62", + "libjpeg.so", +#endif + NULL + }; + + // Already loaded? + if (jpeg_dll) + return true; + + if (jpeg_tried_loading) // only try once + return false; + + jpeg_tried_loading = true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &jpeg_dll, jpegfuncs); +#endif +} + + +/* +==================== +JPEG_CloseLibrary + +Unload the JPEG DLL +==================== +*/ +void JPEG_CloseLibrary (void) +{ +#ifndef LINK_TO_LIBJPEG + Sys_UnloadLibrary (&jpeg_dll); + jpeg_tried_loading = false; // allow retry +#endif +} + + +/* +================================================================= + + JPEG decompression + +================================================================= +*/ + +static void JPEG_Noop (j_decompress_ptr cinfo) {} + +static jboolean JPEG_FillInputBuffer (j_decompress_ptr cinfo) +{ + // Insert a fake EOI marker + cinfo->src->next_input_byte = jpeg_eoi_marker; + cinfo->src->bytes_in_buffer = 2; + + return TRUE; +} + +static void JPEG_SkipInputData (j_decompress_ptr cinfo, long num_bytes) +{ + if (cinfo->src->bytes_in_buffer <= (unsigned long)num_bytes) + { + cinfo->src->bytes_in_buffer = 0; + return; + } + + cinfo->src->next_input_byte += num_bytes; + cinfo->src->bytes_in_buffer -= num_bytes; +} + +static void JPEG_MemSrc (j_decompress_ptr cinfo, const unsigned char *buffer, size_t filesize) +{ + cinfo->src = (struct jpeg_source_mgr *)cinfo->mem->alloc_small ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (struct jpeg_source_mgr)); + + cinfo->src->next_input_byte = buffer; + cinfo->src->bytes_in_buffer = filesize; + + cinfo->src->init_source = JPEG_Noop; + cinfo->src->fill_input_buffer = JPEG_FillInputBuffer; + cinfo->src->skip_input_data = JPEG_SkipInputData; + cinfo->src->resync_to_restart = qjpeg_resync_to_restart; // use the default method + cinfo->src->term_source = JPEG_Noop; +} + +static void JPEG_ErrorExit (j_common_ptr cinfo) +{ + ((struct jpeg_decompress_struct*)cinfo)->err->output_message (cinfo); + longjmp(error_in_jpeg, 1); +} + + +/* +==================== +JPEG_LoadImage + +Load a JPEG image into a BGRA buffer +==================== +*/ +unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + unsigned char *image_buffer = NULL, *scanline = NULL; + unsigned int line; + int submip = 0; + + // No DLL = no JPEGs + if (!jpeg_dll) + return NULL; + + if(miplevel && r_texture_jpeg_fastpicmip.integer) + submip = bound(0, *miplevel, 3); + + cinfo.err = qjpeg_std_error (&jerr); + qjpeg_create_decompress (&cinfo); + if(setjmp(error_in_jpeg)) + goto error_caught; + cinfo.err = qjpeg_std_error (&jerr); + cinfo.err->error_exit = JPEG_ErrorExit; + JPEG_MemSrc (&cinfo, f, filesize); + qjpeg_read_header (&cinfo, TRUE); + cinfo.scale_num = 1; + cinfo.scale_denom = (1 << submip); + qjpeg_start_decompress (&cinfo); + + image_width = cinfo.output_width; + image_height = cinfo.output_height; + + if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) + { + Con_Printf("JPEG_LoadImage: invalid image size %ix%i\n", image_width, image_height); + return NULL; + } + + image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + scanline = (unsigned char *)Mem_Alloc(tempmempool, image_width * cinfo.output_components); + if (!image_buffer || !scanline) + { + if (image_buffer) + Mem_Free (image_buffer); + if (scanline) + Mem_Free (scanline); + + Con_Printf("JPEG_LoadImage: not enough memory for %i by %i image\n", image_width, image_height); + qjpeg_finish_decompress (&cinfo); + qjpeg_destroy_decompress (&cinfo); + return NULL; + } + + // Decompress the image, line by line + line = 0; + while (cinfo.output_scanline < cinfo.output_height) + { + unsigned char *buffer_ptr; + int ind; + + qjpeg_read_scanlines (&cinfo, &scanline, 1); + + // Convert the image to BGRA + switch (cinfo.output_components) + { + // RGB images + case 3: + buffer_ptr = &image_buffer[image_width * line * 4]; + for (ind = 0; ind < image_width * 3; ind += 3, buffer_ptr += 4) + { + buffer_ptr[2] = scanline[ind]; + buffer_ptr[1] = scanline[ind + 1]; + buffer_ptr[0] = scanline[ind + 2]; + buffer_ptr[3] = 255; + } + break; + + // Greyscale images (default to it, just in case) + case 1: + default: + buffer_ptr = &image_buffer[image_width * line * 4]; + for (ind = 0; ind < image_width; ind++, buffer_ptr += 4) + { + buffer_ptr[0] = scanline[ind]; + buffer_ptr[1] = scanline[ind]; + buffer_ptr[2] = scanline[ind]; + buffer_ptr[3] = 255; + } + } + + line++; + } + Mem_Free (scanline); scanline = NULL; + + qjpeg_finish_decompress (&cinfo); + qjpeg_destroy_decompress (&cinfo); + + if(miplevel) + *miplevel -= submip; + + return image_buffer; + +error_caught: + if(scanline) + Mem_Free (scanline); + if(image_buffer) + Mem_Free (image_buffer); + qjpeg_destroy_decompress (&cinfo); + return NULL; +} + + +/* +================================================================= + + JPEG compression + +================================================================= +*/ + +#define JPEG_OUTPUT_BUF_SIZE 4096 +static void JPEG_InitDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + dest->buffer = (unsigned char*)cinfo->mem->alloc_small ((j_common_ptr) cinfo, JPOOL_IMAGE, JPEG_OUTPUT_BUF_SIZE * sizeof(unsigned char)); + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; +} + +static jboolean JPEG_EmptyOutputBuffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + + if (FS_Write (dest->outfile, dest->buffer, JPEG_OUTPUT_BUF_SIZE) != (size_t) JPEG_OUTPUT_BUF_SIZE) + longjmp(error_in_jpeg, 1); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; + return true; +} + +static void JPEG_TermDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + size_t datacount = JPEG_OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + // Write any data remaining in the buffer + if (datacount > 0) + if (FS_Write (dest->outfile, dest->buffer, datacount) != (fs_offset_t)datacount) + longjmp(error_in_jpeg, 1); +} + +static void JPEG_FileDest (j_compress_ptr cinfo, qfile_t* outfile) +{ + my_dest_ptr dest; + + // First time for this JPEG object? + if (cinfo->dest == NULL) + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_destination_mgr)); + + dest = (my_dest_ptr)cinfo->dest; + dest->pub.init_destination = JPEG_InitDestination; + dest->pub.empty_output_buffer = JPEG_EmptyOutputBuffer; + dest->pub.term_destination = JPEG_TermDestination; + dest->outfile = outfile; +} + +static void JPEG_Mem_InitDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = dest->bufsize; +} + +static jboolean JPEG_Mem_EmptyOutputBuffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + jpeg_toolarge = true; + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = dest->bufsize; + return true; +} + +static void JPEG_Mem_TermDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + dest->bufsize = dest->pub.next_output_byte - dest->buffer; +} +static void JPEG_MemDest (j_compress_ptr cinfo, void* buf, size_t bufsize) +{ + my_dest_ptr dest; + + // First time for this JPEG object? + if (cinfo->dest == NULL) + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_destination_mgr)); + + dest = (my_dest_ptr)cinfo->dest; + dest->pub.init_destination = JPEG_Mem_InitDestination; + dest->pub.empty_output_buffer = JPEG_Mem_EmptyOutputBuffer; + dest->pub.term_destination = JPEG_Mem_TermDestination; + dest->outfile = NULL; + + dest->buffer = (unsigned char *) buf; + dest->bufsize = bufsize; +} + + +/* +==================== +JPEG_SaveImage_preflipped + +Save a preflipped JPEG image to a file +==================== +*/ +qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + unsigned char *scanline; + unsigned int offset, linesize; + qfile_t* file; + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + // Open the file + file = FS_OpenRealFile(filename, "wb", true); + if (!file) + return false; + + if(setjmp(error_in_jpeg)) + goto error_caught; + cinfo.err = qjpeg_std_error (&jerr); + cinfo.err->error_exit = JPEG_ErrorExit; + + qjpeg_create_compress (&cinfo); + JPEG_FileDest (&cinfo, file); + + // Set the parameters for compression + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.in_color_space = JCS_RGB; + cinfo.input_components = 3; + qjpeg_set_defaults (&cinfo); + qjpeg_set_quality (&cinfo, (int)(scr_screenshot_jpeg_quality.value * 100), TRUE); + qjpeg_simple_progression (&cinfo); + + // turn off subsampling (to make text look better) + cinfo.optimize_coding = 1; + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + + qjpeg_start_compress (&cinfo, true); + + // Compress each scanline + linesize = cinfo.image_width * 3; + offset = linesize * (cinfo.image_height - 1); + while (cinfo.next_scanline < cinfo.image_height) + { + scanline = &data[offset - cinfo.next_scanline * linesize]; + + qjpeg_write_scanlines (&cinfo, &scanline, 1); + } + + qjpeg_finish_compress (&cinfo); + qjpeg_destroy_compress (&cinfo); + + FS_Close (file); + return true; + +error_caught: + qjpeg_destroy_compress (&cinfo); + FS_Close (file); + return false; +} + +static size_t JPEG_try_SaveImage_to_Buffer (struct jpeg_compress_struct *cinfo, char *jpegbuf, size_t jpegsize, int quality, int width, int height, unsigned char *data) +{ + unsigned char *scanline; + unsigned int linesize; + + jpeg_toolarge = false; + JPEG_MemDest (cinfo, jpegbuf, jpegsize); + + // Set the parameters for compression + cinfo->image_width = width; + cinfo->image_height = height; + cinfo->in_color_space = JCS_RGB; + cinfo->input_components = 3; + qjpeg_set_defaults (cinfo); + qjpeg_set_quality (cinfo, quality, FALSE); + + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 1; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 1; + cinfo->optimize_coding = 1; + + qjpeg_start_compress (cinfo, true); + + // Compress each scanline + linesize = width * 3; + while (cinfo->next_scanline < cinfo->image_height) + { + scanline = &data[cinfo->next_scanline * linesize]; + + qjpeg_write_scanlines (cinfo, &scanline, 1); + } + + qjpeg_finish_compress (cinfo); + + if(jpeg_toolarge) + return 0; + + return ((my_dest_ptr) cinfo->dest)->bufsize; +} + +size_t JPEG_SaveImage_to_Buffer (char *jpegbuf, size_t jpegsize, int width, int height, unsigned char *data) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + + int quality; + int quality_guess; + size_t result; + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + if(setjmp(error_in_jpeg)) + goto error_caught; + cinfo.err = qjpeg_std_error (&jerr); + cinfo.err->error_exit = JPEG_ErrorExit; + + qjpeg_create_compress (&cinfo); + +#if 0 + // used to get the formula below + { + char buf[1048576]; + unsigned char *img; + int i; + + img = Mem_Alloc(tempmempool, width * height * 3); + for(i = 0; i < width * height * 3; ++i) + img[i] = rand() & 0xFF; + + for(i = 0; i <= 100; ++i) + { + Con_Printf("! %d %d %d %d\n", width, height, i, (int) JPEG_try_SaveImage_to_Buffer(&cinfo, buf, sizeof(buf), i, width, height, img)); + } + + Mem_Free(img); + } +#endif + + //quality_guess = (100 * jpegsize - 41000) / (width*height) + 2; // fits random data + quality_guess = (256 * jpegsize - 81920) / (width*height) - 8; // fits Nexuiz's/Xonotic's map pictures + + quality_guess = bound(0, quality_guess, 100); + quality = bound(0, quality_guess + sv_writepicture_quality.integer, 100); // assume it can do 10 failed attempts + + while(!(result = JPEG_try_SaveImage_to_Buffer(&cinfo, jpegbuf, jpegsize, quality, width, height, data))) + { + --quality; + if(quality < 0) + { + Con_Printf("couldn't write image at all, probably too big\n"); + return 0; + } + } + qjpeg_destroy_compress (&cinfo); + Con_DPrintf("JPEG_SaveImage_to_Buffer: guessed quality/size %d/%d, actually got %d/%d\n", quality_guess, (int)jpegsize, quality, (int)result); + + return result; + +error_caught: + qjpeg_destroy_compress (&cinfo); + return 0; +} + +typedef struct CompressedImageCacheItem +{ + char imagename[MAX_QPATH]; + size_t maxsize; + void *compressed; + size_t compressed_size; + struct CompressedImageCacheItem *next; +} +CompressedImageCacheItem; +#define COMPRESSEDIMAGECACHE_SIZE 4096 +static CompressedImageCacheItem *CompressedImageCache[COMPRESSEDIMAGECACHE_SIZE]; + +static void CompressedImageCache_Add(const char *imagename, size_t maxsize, void *compressed, size_t compressed_size) +{ + const char *hashkey = va("%s:%d", imagename, (int) maxsize); + int hashindex = CRC_Block((unsigned char *) hashkey, strlen(hashkey)) % COMPRESSEDIMAGECACHE_SIZE; + CompressedImageCacheItem *i; + + if(strlen(imagename) >= MAX_QPATH) + return; // can't add this + + i = (CompressedImageCacheItem*) Z_Malloc(sizeof(CompressedImageCacheItem)); + strlcpy(i->imagename, imagename, sizeof(i->imagename)); + i->maxsize = maxsize; + i->compressed = compressed; + i->compressed_size = compressed_size; + i->next = CompressedImageCache[hashindex]; + CompressedImageCache[hashindex] = i; +} + +static CompressedImageCacheItem *CompressedImageCache_Find(const char *imagename, size_t maxsize) +{ + const char *hashkey = va("%s:%d", imagename, (int) maxsize); + int hashindex = CRC_Block((unsigned char *) hashkey, strlen(hashkey)) % COMPRESSEDIMAGECACHE_SIZE; + CompressedImageCacheItem *i = CompressedImageCache[hashindex]; + + while(i) + { + if(i->maxsize == maxsize) + if(!strcmp(i->imagename, imagename)) + return i; + i = i->next; + } + return NULL; +} + +qboolean Image_Compress(const char *imagename, size_t maxsize, void **buf, size_t *size) +{ + unsigned char *imagedata, *newimagedata; + int maxPixelCount; + int components[3] = {2, 1, 0}; + CompressedImageCacheItem *i; + + JPEG_OpenLibrary (); // for now; LH had the idea of replacing this by a better format + PNG_OpenLibrary (); // for loading + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + i = CompressedImageCache_Find(imagename, maxsize); + if(i) + { + *size = i->compressed_size; + *buf = i->compressed; + } + + // load the image + imagedata = loadimagepixelsbgra(imagename, true, false, false, NULL); + if(!imagedata) + return false; + + // find an appropriate size for somewhat okay compression + if(maxsize <= 768) + maxPixelCount = 32 * 32; + else if(maxsize <= 1024) + maxPixelCount = 64 * 64; + else if(maxsize <= 4096) + maxPixelCount = 128 * 128; + else + maxPixelCount = 256 * 256; + + while(image_width * image_height > maxPixelCount) + { + int one = 1; + Image_MipReduce32(imagedata, imagedata, &image_width, &image_height, &one, image_width/2, image_height/2, 1); + } + + newimagedata = (unsigned char *) Mem_Alloc(tempmempool, image_width * image_height * 3); + + // convert the image from BGRA to RGB + Image_CopyMux(newimagedata, imagedata, image_width, image_height, false, false, false, 3, 4, components); + Mem_Free(imagedata); + + // try to compress it to JPEG + *buf = Z_Malloc(maxsize); + *size = JPEG_SaveImage_to_Buffer((char *) *buf, maxsize, image_width, image_height, newimagedata); + Mem_Free(newimagedata); + + if(!*size) + { + Z_Free(*buf); + *buf = NULL; + Con_Printf("could not compress image %s to %d bytes\n", imagename, (int)maxsize); + // return false; + // also cache failures! + } + + // store it in the cache + CompressedImageCache_Add(imagename, maxsize, *buf, *size); + return (*buf != NULL); +} diff --git a/misc/source/darkplaces-src/jpeg.h b/misc/source/darkplaces-src/jpeg.h new file mode 100644 index 00000000..ff17eb75 --- /dev/null +++ b/misc/source/darkplaces-src/jpeg.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2002 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef JPEG_H +#define JPEG_H + + +qboolean JPEG_OpenLibrary (void); +void JPEG_CloseLibrary (void); +unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel); +qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data); + +/*! \returns 0 if failed, or the size actually used. + */ +size_t JPEG_SaveImage_to_Buffer (char *jpegbuf, size_t jpegsize, int width, int height, unsigned char *data); +qboolean Image_Compress(const char *imagename, size_t maxsize, void **buf, size_t *size); + + +#endif diff --git a/misc/source/darkplaces-src/keys.c b/misc/source/darkplaces-src/keys.c new file mode 100644 index 00000000..ea6e2d59 --- /dev/null +++ b/misc/source/darkplaces-src/keys.c @@ -0,0 +1,1971 @@ +/* + Copyright (C) 1996-1997 Id Software, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + +#include "quakedef.h" +#include "cl_video.h" +#include "utf8lib.h" + +cvar_t con_closeontoggleconsole = {CVAR_SAVE, "con_closeontoggleconsole","1", "allows toggleconsole binds to close the console as well; when set to 2, this even works when not at the start of the line in console input; when set to 3, this works even if the toggleconsole key is the color tag"}; + +/* +key up events are sent even if in console mode +*/ + +char key_line[MAX_INPUTLINE]; +int key_linepos; +qboolean key_insert = true; // insert key toggle (for editing) +keydest_t key_dest; +int key_consoleactive; +char *keybindings[MAX_BINDMAPS][MAX_KEYS]; + +int history_line; +char history_savedline[MAX_INPUTLINE]; +char history_searchstring[MAX_INPUTLINE]; +qboolean history_matchfound = false; +conbuffer_t history; + +extern cvar_t con_textsize; + + +static void Key_History_Init(void) +{ + qfile_t *historyfile; + ConBuffer_Init(&history, HIST_TEXTSIZE, HIST_MAXLINES, zonemempool); + + historyfile = FS_OpenRealFile("darkplaces_history.txt", "rb", false); // rb to handle unix line endings on windows too + if(historyfile) + { + char buf[MAX_INPUTLINE]; + int bufpos; + int c; + + bufpos = 0; + for(;;) + { + c = FS_Getc(historyfile); + if(c < 0 || c == 0 || c == '\r' || c == '\n') + { + if(bufpos > 0) + { + buf[bufpos] = 0; + ConBuffer_AddLine(&history, buf, bufpos, 0); + bufpos = 0; + } + if(c < 0) + break; + } + else + { + if(bufpos < MAX_INPUTLINE - 1) + buf[bufpos++] = c; + } + } + + FS_Close(historyfile); + } + + history_line = -1; +} + +static void Key_History_Shutdown(void) +{ + // TODO write history to a file + + qfile_t *historyfile = FS_OpenRealFile("darkplaces_history.txt", "w", false); + if(historyfile) + { + int i; + for(i = 0; i < CONBUFFER_LINES_COUNT(&history); ++i) + FS_Printf(historyfile, "%s\n", ConBuffer_GetLine(&history, i)); + FS_Close(historyfile); + } + + ConBuffer_Shutdown(&history); +} + +static void Key_History_Push(void) +{ + if(key_line[1]) // empty? + if(strcmp(key_line, "]quit")) // putting these into the history just sucks + if(strncmp(key_line, "]quit ", 6)) // putting these into the history just sucks + ConBuffer_AddLine(&history, key_line + 1, strlen(key_line) - 1, 0); + Con_Printf("%s\n", key_line); // don't mark empty lines as history + history_line = -1; + if (history_matchfound) + history_matchfound = false; +} + +qboolean Key_History_Get_foundCommand(void) +{ + if (!history_matchfound) + return false; + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + history_matchfound = false; + return true; +} + +static void Key_History_Up(void) +{ + if(history_line == -1) // editing the "new" line + strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); + + if (Key_History_Get_foundCommand()) + return; + + if(history_line == -1) + { + history_line = CONBUFFER_LINES_COUNT(&history) - 1; + if(history_line != -1) + { + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + } + } + else if(history_line > 0) + { + --history_line; // this also does -1 -> 0, so it is good + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + } +} + +static void Key_History_Down(void) +{ + if(history_line == -1) // editing the "new" line + return; + + if (Key_History_Get_foundCommand()) + return; + + if(history_line < CONBUFFER_LINES_COUNT(&history) - 1) + { + ++history_line; + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + } + else + { + history_line = -1; + strlcpy(key_line + 1, history_savedline, sizeof(key_line) - 1); + } + + key_linepos = strlen(key_line); +} + +static void Key_History_First(void) +{ + if(history_line == -1) // editing the "new" line + strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); + + if (CONBUFFER_LINES_COUNT(&history) > 0) + { + history_line = 0; + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + } +} + +static void Key_History_Last(void) +{ + if(history_line == -1) // editing the "new" line + strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); + + if (CONBUFFER_LINES_COUNT(&history) > 0) + { + history_line = CONBUFFER_LINES_COUNT(&history) - 1; + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + } +} + +static void Key_History_Find_Backwards(void) +{ + int i; + const char *partial = key_line + 1; + size_t digits = strlen(va("%i", HIST_MAXLINES)); + + if (history_line == -1) // editing the "new" line + strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); + + if (strcmp(key_line + 1, history_searchstring)) // different string? Start a new search + { + strlcpy(history_searchstring, key_line + 1, sizeof(history_searchstring)); + i = CONBUFFER_LINES_COUNT(&history) - 1; + } + else if (history_line == -1) + i = CONBUFFER_LINES_COUNT(&history) - 1; + else + i = history_line - 1; + + if (!*partial) + partial = "*"; + else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? + partial = va("*%s*", partial); + + for ( ; i >= 0; i--) + if (matchpattern_with_separator(ConBuffer_GetLine(&history, i), partial, true, "", false)) + { + Con_Printf("^2%*i^7 %s\n", (int)digits, i+1, ConBuffer_GetLine(&history, i)); + history_line = i; + history_matchfound = true; + return; + } +} + +static void Key_History_Find_Forwards(void) +{ + int i; + const char *partial = key_line + 1; + size_t digits = strlen(va("%i", HIST_MAXLINES)); + + if (history_line == -1) // editing the "new" line + return; + + if (strcmp(key_line + 1, history_searchstring)) // different string? Start a new search + { + strlcpy(history_searchstring, key_line + 1, sizeof(history_searchstring)); + i = 0; + } + else i = history_line + 1; + + if (!*partial) + partial = "*"; + else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? + partial = va("*%s*", partial); + + for ( ; i < CONBUFFER_LINES_COUNT(&history); i++) + if (matchpattern_with_separator(ConBuffer_GetLine(&history, i), partial, true, "", false)) + { + Con_Printf("^2%*i^7 %s\n", (int)digits, i+1, ConBuffer_GetLine(&history, i)); + history_line = i; + history_matchfound = true; + return; + } +} + +static void Key_History_Find_All(void) +{ + const char *partial = key_line + 1; + int i, count = 0; + size_t digits = strlen(va("%i", HIST_MAXLINES)); + Con_Printf("History commands containing \"%s\":\n", key_line + 1); + + if (!*partial) + partial = "*"; + else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? + partial = va("*%s*", partial); + + for (i=0; i 1) + { + if (!strcmp(Cmd_Argv (1), "-c")) + { + ConBuffer_Clear(&history); + return; + } + i = strtol(Cmd_Argv (1), &errchar, 0); + if ((i < 0) || (i > CONBUFFER_LINES_COUNT(&history)) || (errchar && *errchar)) + i = 0; + else + i = CONBUFFER_LINES_COUNT(&history) - i; + } + + for ( ; i= MAX_INPUTLINE) + i= MAX_INPUTLINE - key_linepos - 1; + if (i > 0) + { + // terencehill: insert the clipboard text between the characters of the line + /* + char *temp = (char *) Z_Malloc(MAX_INPUTLINE); + cbd[i]=0; + temp[0]=0; + if ( key_linepos < (int)strlen(key_line) ) + strlcpy(temp, key_line + key_linepos, (int)strlen(key_line) - key_linepos +1); + key_line[key_linepos] = 0; + strlcat(key_line, cbd, sizeof(key_line)); + if (temp[0]) + strlcat(key_line, temp, sizeof(key_line)); + Z_Free(temp); + key_linepos += i; + */ + // blub: I'm changing this to use memmove() like the rest of the code does. + cbd[i] = 0; + memmove(key_line + key_linepos + i, key_line + key_linepos, sizeof(key_line) - key_linepos - i); + memcpy(key_line + key_linepos, cbd, i); + key_linepos += i; + } + Z_Free(cbd); + } + return; + } + + if (key == 'l' && keydown[K_CTRL]) + { + Cbuf_AddText ("clear\n"); + return; + } + + if (key == 'u' && keydown[K_CTRL]) // like vi/readline ^u: delete currently edited line + { + // clear line + key_line[0] = ']'; + key_line[1] = 0; + key_linepos = 1; + return; + } + + if (key == 'q' && keydown[K_CTRL]) // like zsh ^q: push line to history, don't execute, and clear + { + // clear line + Key_History_Push(); + key_line[0] = ']'; + key_line[1] = 0; + key_linepos = 1; + return; + } + + if (key == K_ENTER || key == K_KP_ENTER) + { + Cbuf_AddText (key_line+1); // skip the ] + Cbuf_AddText ("\n"); + Key_History_Push(); + key_line[0] = ']'; + key_line[1] = 0; // EvilTypeGuy: null terminate + key_linepos = 1; + // force an update, because the command may take some time + if (cls.state == ca_disconnected) + CL_UpdateScreen (); + return; + } + + if (key == K_TAB) + { + if(keydown[K_CTRL]) // append to the cvar its value + { + int cvar_len, cvar_str_len, chars_to_move; + char k; + char cvar[MAX_INPUTLINE]; + const char *cvar_str; + + // go to the start of the variable + while(--key_linepos) + { + k = key_line[key_linepos]; + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + } + key_linepos++; + + // save the variable name in cvar + for(cvar_len=0; (k = key_line[key_linepos + cvar_len]) != 0; cvar_len++) + { + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + cvar[cvar_len] = k; + } + if (cvar_len==0) + return; + cvar[cvar_len] = 0; + + // go to the end of the cvar + key_linepos += cvar_len; + + // save the content of the variable in cvar_str + cvar_str = Cvar_VariableString(cvar); + cvar_str_len = strlen(cvar_str); + if (cvar_str_len==0) + return; + + // insert space and cvar_str in key_line + chars_to_move = strlen(&key_line[key_linepos]); + if (key_linepos + 1 + cvar_str_len + chars_to_move < MAX_INPUTLINE) + { + if (chars_to_move) + memmove(&key_line[key_linepos + 1 + cvar_str_len], &key_line[key_linepos], chars_to_move); + key_line[key_linepos++] = ' '; + memcpy(&key_line[key_linepos], cvar_str, cvar_str_len); + key_linepos += cvar_str_len; + key_line[key_linepos + chars_to_move] = 0; + } + else + Con_Printf("Couldn't append cvar value, edit line too long.\n"); + return; + } + // Enhanced command completion + // by EvilTypeGuy eviltypeguy@qeradiant.com + // Thanks to Fett, Taniwha + Con_CompleteCommandLine(); + return; + } + + // Advanced Console Editing by Radix radix@planetquake.com + // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com + // Enhanced by [515] + // Enhanced by terencehill + + // move cursor to the previous character + if (key == K_LEFTARROW || key == K_KP_LEFTARROW) + { + if (key_linepos < 2) + return; + if(keydown[K_CTRL]) // move cursor to the previous word + { + int pos; + char k; + pos = key_linepos-1; + + if(pos) // skip all "; ' after the word + while(--pos) + { + k = key_line[pos]; + if (!(k == '\"' || k == ';' || k == ' ' || k == '\'')) + break; + } + + if(pos) + while(--pos) + { + k = key_line[pos]; + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + } + key_linepos = pos + 1; + } + else if(keydown[K_SHIFT]) // move cursor to the previous character ignoring colors + { + int pos; + size_t inchar = 0; + pos = u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte + while (pos) + if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && isdigit(key_line[pos])) + pos-=2; + else if(pos-4 > 0 && key_line[pos-4] == STRING_COLOR_TAG && key_line[pos-3] == STRING_COLOR_RGB_TAG_CHAR + && isxdigit(key_line[pos-2]) && isxdigit(key_line[pos-1]) && isxdigit(key_line[pos])) + pos-=5; + else + { + if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && key_line[pos] == STRING_COLOR_TAG) // consider ^^ as a character + pos--; + pos--; + break; + } + // we need to move to the beginning of the character when in a wide character: + u8_charidx(key_line, pos + 1, &inchar); + key_linepos = pos + 1 - inchar; + } + else + { + key_linepos = u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte + } + return; + } + + // delete char before cursor + if (key == K_BACKSPACE || (key == 'h' && keydown[K_CTRL])) + { + if (key_linepos > 1) + { + int newpos = u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte + strlcpy(key_line + newpos, key_line + key_linepos, sizeof(key_line) + 1 - key_linepos); + key_linepos = newpos; + } + return; + } + + // delete char on cursor + if (key == K_DEL || key == K_KP_DEL) + { + size_t linelen; + linelen = strlen(key_line); + if (key_linepos < (int)linelen) + memmove(key_line + key_linepos, key_line + key_linepos + u8_bytelen(key_line + key_linepos, 1), linelen - key_linepos); + return; + } + + + // move cursor to the next character + if (key == K_RIGHTARROW || key == K_KP_RIGHTARROW) + { + if (key_linepos >= (int)strlen(key_line)) + return; + if(keydown[K_CTRL]) // move cursor to the next word + { + int pos, len; + char k; + len = (int)strlen(key_line); + pos = key_linepos; + + while(++pos < len) + { + k = key_line[pos]; + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + } + + if (pos < len) // skip all "; ' after the word + while(++pos < len) + { + k = key_line[pos]; + if (!(k == '\"' || k == ';' || k == ' ' || k == '\'')) + break; + } + key_linepos = pos; + } + else if(keydown[K_SHIFT]) // move cursor to the next character ignoring colors + { + int pos, len; + len = (int)strlen(key_line); + pos = key_linepos; + + // go beyond all initial consecutive color tags, if any + if(pos < len) + while (key_line[pos] == STRING_COLOR_TAG) + { + if(isdigit(key_line[pos+1])) + pos+=2; + else if(key_line[pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos+2]) && isxdigit(key_line[pos+3]) && isxdigit(key_line[pos+4])) + pos+=5; + else + break; + } + + // skip the char + if (key_line[pos] == STRING_COLOR_TAG && key_line[pos+1] == STRING_COLOR_TAG) // consider ^^ as a character + pos++; + pos += u8_bytelen(key_line + pos, 1); + + // now go beyond all next consecutive color tags, if any + if(pos < len) + while (key_line[pos] == STRING_COLOR_TAG) + { + if(isdigit(key_line[pos+1])) + pos+=2; + else if(key_line[pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos+2]) && isxdigit(key_line[pos+3]) && isxdigit(key_line[pos+4])) + pos+=5; + else + break; + } + key_linepos = pos; + } + else + key_linepos += u8_bytelen(key_line + key_linepos, 1); + return; + } + + if (key == K_INS || key == K_KP_INS) // toggle insert mode + { + key_insert ^= 1; + return; + } + + // End Advanced Console Editing + + if (key == K_UPARROW || key == K_KP_UPARROW || (key == 'p' && keydown[K_CTRL])) + { + Key_History_Up(); + return; + } + + if (key == K_DOWNARROW || key == K_KP_DOWNARROW || (key == 'n' && keydown[K_CTRL])) + { + Key_History_Down(); + return; + } + // ~1.0795 = 82/76 using con_textsize 64 76 is height of the char, 6 is the distance between 2 lines + + if (keydown[K_CTRL]) + { + // prints all the matching commands + if (key == 'f') + { + Key_History_Find_All(); + return; + } + // Search forwards/backwards, pointing the history's index to the + // matching command but without fetching it to let one continue the search. + // To fetch it, it suffices to just press UP or DOWN. + if (key == 'r') + { + if (keydown[K_SHIFT]) + Key_History_Find_Forwards(); + else + Key_History_Find_Backwards(); + return; + } + // go to the last/first command of the history + if (key == ',') + { + Key_History_First(); + return; + } + if (key == '.') + { + Key_History_Last(); + return; + } + } + + if (key == K_PGUP || key == K_KP_PGUP) + { + if(keydown[K_CTRL]) + { + con_backscroll += ((vid_conheight.integer >> 2) / con_textsize.integer)-1; + } + else + con_backscroll += ((vid_conheight.integer >> 1) / con_textsize.integer)-3; + return; + } + + if (key == K_PGDN || key == K_KP_PGDN) + { + if(keydown[K_CTRL]) + { + con_backscroll -= ((vid_conheight.integer >> 2) / con_textsize.integer)-1; + } + else + con_backscroll -= ((vid_conheight.integer >> 1) / con_textsize.integer)-3; + return; + } + + if (key == K_MWHEELUP) + { + if(keydown[K_CTRL]) + con_backscroll += 1; + else if(keydown[K_SHIFT]) + con_backscroll += ((vid_conheight.integer >> 2) / con_textsize.integer)-1; + else + con_backscroll += 5; + return; + } + + if (key == K_MWHEELDOWN) + { + if(keydown[K_CTRL]) + con_backscroll -= 1; + else if(keydown[K_SHIFT]) + con_backscroll -= ((vid_conheight.integer >> 2) / con_textsize.integer)-1; + else + con_backscroll -= 5; + return; + } + + if (keydown[K_CTRL]) + { + // text zoom in + if (key == '+' || key == K_KP_PLUS) + { + if (con_textsize.integer < 128) + Cvar_SetValueQuick(&con_textsize, con_textsize.integer + 1); + return; + } + // text zoom out + if (key == '-' || key == K_KP_MINUS) + { + if (con_textsize.integer > 1) + Cvar_SetValueQuick(&con_textsize, con_textsize.integer - 1); + return; + } + // text zoom reset + if (key == '0' || key == K_KP_INS) + { + Cvar_SetValueQuick(&con_textsize, atoi(Cvar_VariableDefString("con_textsize"))); + return; + } + } + + if (key == K_HOME || key == K_KP_HOME) + { + if (keydown[K_CTRL]) + con_backscroll = CON_TEXTSIZE; + else + key_linepos = 1; + return; + } + + if (key == K_END || key == K_KP_END) + { + if (keydown[K_CTRL]) + con_backscroll = 0; + else + key_linepos = (int)strlen(key_line); + return; + } + + // non printable + if (unicode < 32) + return; + + if (key_linepos < MAX_INPUTLINE-1) + { + char buf[16]; + int len; + int blen; + blen = u8_fromchar(unicode, buf, sizeof(buf)); + if (!blen) + return; + len = (int)strlen(&key_line[key_linepos]); + // check insert mode, or always insert if at end of line + if (key_insert || len == 0) + { + // can't use strcpy to move string to right + len++; + //memmove(&key_line[key_linepos + u8_bytelen(key_line + key_linepos, 1)], &key_line[key_linepos], len); + memmove(&key_line[key_linepos + blen], &key_line[key_linepos], len); + } + memcpy(key_line + key_linepos, buf, blen); + key_linepos += blen; + //key_linepos += u8_fromchar(unicode, key_line + key_linepos, sizeof(key_line) - key_linepos - 1); + //key_line[key_linepos] = ascii; + //key_linepos++; + } +} + +//============================================================================ + +int chat_mode; +char chat_buffer[MAX_INPUTLINE]; +unsigned int chat_bufferlen = 0; + +extern int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos); + +static void +Key_Message (int key, int ascii) +{ + if (key == K_ENTER || ascii == 10 || ascii == 13) + { + if(chat_mode < 0) + Cmd_ExecuteString(chat_buffer, src_command); // not Cbuf_AddText to allow semiclons in args; however, this allows no variables then. Use aliases! + else + Cmd_ForwardStringToServer(va("%s %s", chat_mode ? "say_team" : "say ", chat_buffer)); + + key_dest = key_game; + chat_bufferlen = 0; + chat_buffer[0] = 0; + return; + } + + // TODO add support for arrow keys and simple editing + + if (key == K_ESCAPE) { + key_dest = key_game; + chat_bufferlen = 0; + chat_buffer[0] = 0; + return; + } + + if (key == K_BACKSPACE) { + if (chat_bufferlen) { + chat_bufferlen = u8_prevbyte(chat_buffer, chat_bufferlen); + chat_buffer[chat_bufferlen] = 0; + } + return; + } + + if(key == K_TAB) { + chat_bufferlen = Nicks_CompleteChatLine(chat_buffer, sizeof(chat_buffer), chat_bufferlen); + return; + } + + // ctrl+key generates an ascii value < 32 and shows a char from the charmap + if (ascii > 0 && ascii < 32 && utf8_enable.integer) + ascii = 0xE000 + ascii; + + if (chat_bufferlen == sizeof (chat_buffer) - 1) + return; // all full + + if (!ascii) + return; // non printable + + chat_bufferlen += u8_fromchar(ascii, chat_buffer+chat_bufferlen, sizeof(chat_buffer) - chat_bufferlen - 1); + + //chat_buffer[chat_bufferlen++] = ascii; + //chat_buffer[chat_bufferlen] = 0; +} + +//============================================================================ + + +/* +=================== +Returns a key number to be used to index keybindings[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. +=================== +*/ +int +Key_StringToKeynum (const char *str) +{ + const keyname_t *kn; + + if (!str || !str[0]) + return -1; + if (!str[1]) + return tolower(str[0]); + + for (kn = keynames; kn->name; kn++) { + if (!strcasecmp (str, kn->name)) + return kn->keynum; + } + return -1; +} + +/* +=================== +Returns a string (either a single ascii char, or a K_* name) for the +given keynum. +FIXME: handle quote special (general escape sequence?) +=================== +*/ +const char * +Key_KeynumToString (int keynum) +{ + const keyname_t *kn; + static char tinystr[2]; + + // -1 is an invalid code + if (keynum < 0) + return ""; + + // search overrides first, because some characters are special + for (kn = keynames; kn->name; kn++) + if (keynum == kn->keynum) + return kn->name; + + // if it is printable, output it as a single character + if (keynum > 32 && keynum < 256) + { + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + // if it is not overridden and not printable, we don't know what to do with it + return ""; +} + + +qboolean +Key_SetBinding (int keynum, int bindmap, const char *binding) +{ + char *newbinding; + size_t l; + + if (keynum == -1 || keynum >= MAX_KEYS) + return false; + if ((bindmap < 0) || (bindmap >= MAX_BINDMAPS)) + return false; + +// free old bindings + if (keybindings[bindmap][keynum]) { + Z_Free (keybindings[bindmap][keynum]); + keybindings[bindmap][keynum] = NULL; + } + if(!binding[0]) // make "" binds be removed --blub + return true; +// allocate memory for new binding + l = strlen (binding); + newbinding = (char *)Z_Malloc (l + 1); + memcpy (newbinding, binding, l + 1); + newbinding[l] = 0; + keybindings[bindmap][keynum] = newbinding; + return true; +} + +void Key_GetBindMap(int *fg, int *bg) +{ + if(fg) + *fg = key_bmap; + if(bg) + *bg = key_bmap2; +} + +qboolean Key_SetBindMap(int fg, int bg) +{ + if(fg >= MAX_BINDMAPS) + return false; + if(bg >= MAX_BINDMAPS) + return false; + if(fg >= 0) + key_bmap = fg; + if(bg >= 0) + key_bmap2 = bg; + return true; +} + +static void +Key_In_Unbind_f (void) +{ + int b, m; + char *errchar = NULL; + + if (Cmd_Argc () != 3) { + Con_Print("in_unbind : remove commands from a key\n"); + return; + } + + m = strtol(Cmd_Argv (1), &errchar, 0); + if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); + return; + } + + b = Key_StringToKeynum (Cmd_Argv (2)); + if (b == -1) { + Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (2)); + return; + } + + if(!Key_SetBinding (b, m, "")) + Con_Printf("Key_SetBinding failed for unknown reason\n"); +} + +static void +Key_In_Bind_f (void) +{ + int i, c, b, m; + char cmd[MAX_INPUTLINE]; + char *errchar = NULL; + + c = Cmd_Argc (); + + if (c != 3 && c != 4) { + Con_Print("in_bind [command] : attach a command to a key\n"); + return; + } + + m = strtol(Cmd_Argv (1), &errchar, 0); + if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); + return; + } + + b = Key_StringToKeynum (Cmd_Argv (2)); + if (b == -1 || b >= MAX_KEYS) { + Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (2)); + return; + } + + if (c == 3) { + if (keybindings[m][b]) + Con_Printf("\"%s\" = \"%s\"\n", Cmd_Argv (2), keybindings[m][b]); + else + Con_Printf("\"%s\" is not bound\n", Cmd_Argv (2)); + return; + } +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for (i = 3; i < c; i++) { + strlcat (cmd, Cmd_Argv (i), sizeof (cmd)); + if (i != (c - 1)) + strlcat (cmd, " ", sizeof (cmd)); + } + + if(!Key_SetBinding (b, m, cmd)) + Con_Printf("Key_SetBinding failed for unknown reason\n"); +} + +static void +Key_In_Bindmap_f (void) +{ + int m1, m2, c; + char *errchar = NULL; + + c = Cmd_Argc (); + + if (c != 3) { + Con_Print("in_bindmap : set current bindmap and fallback\n"); + return; + } + + m1 = strtol(Cmd_Argv (1), &errchar, 0); + if ((m1 < 0) || (m1 >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); + return; + } + + m2 = strtol(Cmd_Argv (2), &errchar, 0); + if ((m2 < 0) || (m2 >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(2)); + return; + } + + key_bmap = m1; + key_bmap2 = m2; +} + +static void +Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc () != 2) { + Con_Print("unbind : remove commands from a key\n"); + return; + } + + b = Key_StringToKeynum (Cmd_Argv (1)); + if (b == -1) { + Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (1)); + return; + } + + if(!Key_SetBinding (b, 0, "")) + Con_Printf("Key_SetBinding failed for unknown reason\n"); +} + +static void +Key_Unbindall_f (void) +{ + int i, j; + + for (j = 0; j < MAX_BINDMAPS; j++) + for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) + if (keybindings[j][i]) + Key_SetBinding (i, j, ""); +} + +static void +Key_PrintBindList(int j) +{ + char bindbuf[MAX_INPUTLINE]; + const char *p; + int i; + + for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) + { + p = keybindings[j][i]; + if (p) + { + Cmd_QuoteString(bindbuf, sizeof(bindbuf), p, "\"\\", false); + if (j == 0) + Con_Printf("^2%s ^7= \"%s\"\n", Key_KeynumToString (i), bindbuf); + else + Con_Printf("^3bindmap %d: ^2%s ^7= \"%s\"\n", j, Key_KeynumToString (i), bindbuf); + } + } +} + +static void +Key_In_BindList_f (void) +{ + int m; + char *errchar = NULL; + + if(Cmd_Argc() >= 2) + { + m = strtol(Cmd_Argv(1), &errchar, 0); + if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); + return; + } + Key_PrintBindList(m); + } + else + { + for (m = 0; m < MAX_BINDMAPS; m++) + Key_PrintBindList(m); + } +} + +static void +Key_BindList_f (void) +{ + Key_PrintBindList(0); +} + +static void +Key_Bind_f (void) +{ + int i, c, b; + char cmd[MAX_INPUTLINE]; + + c = Cmd_Argc (); + + if (c != 2 && c != 3) { + Con_Print("bind [command] : attach a command to a key\n"); + return; + } + b = Key_StringToKeynum (Cmd_Argv (1)); + if (b == -1 || b >= MAX_KEYS) { + Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (1)); + return; + } + + if (c == 2) { + if (keybindings[0][b]) + Con_Printf("\"%s\" = \"%s\"\n", Cmd_Argv (1), keybindings[0][b]); + else + Con_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++) { + strlcat (cmd, Cmd_Argv (i), sizeof (cmd)); + if (i != (c - 1)) + strlcat (cmd, " ", sizeof (cmd)); + } + + if(!Key_SetBinding (b, 0, cmd)) + Con_Printf("Key_SetBinding failed for unknown reason\n"); +} + +/* +============ +Writes lines containing "bind key value" +============ +*/ +void +Key_WriteBindings (qfile_t *f) +{ + int i, j; + char bindbuf[MAX_INPUTLINE]; + const char *p; + + for (j = 0; j < MAX_BINDMAPS; j++) + { + for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) + { + p = keybindings[j][i]; + if (p) + { + Cmd_QuoteString(bindbuf, sizeof(bindbuf), p, "\"\\", false); // don't need to escape $ because cvars are not expanded inside bind + if (j == 0) + FS_Printf(f, "bind %s \"%s\"\n", Key_KeynumToString (i), bindbuf); + else + FS_Printf(f, "in_bind %d %s \"%s\"\n", j, Key_KeynumToString (i), bindbuf); + } + } + } +} + + +void +Key_Init (void) +{ + Key_History_Init(); + key_line[0] = ']'; + key_line[1] = 0; + key_linepos = 1; + +// +// register our functions +// + Cmd_AddCommand ("in_bind", Key_In_Bind_f, "binds a command to the specified key in the selected bindmap"); + Cmd_AddCommand ("in_unbind", Key_In_Unbind_f, "removes command on the specified key in the selected bindmap"); + Cmd_AddCommand ("in_bindlist", Key_In_BindList_f, "bindlist: displays bound keys for all bindmaps, or the given bindmap"); + Cmd_AddCommand ("in_bindmap", Key_In_Bindmap_f, "selects active foreground and background (used only if a key is not bound in the foreground) bindmaps for typing"); + + Cmd_AddCommand ("bind", Key_Bind_f, "binds a command to the specified key in bindmap 0"); + Cmd_AddCommand ("unbind", Key_Unbind_f, "removes a command on the specified key in bindmap 0"); + Cmd_AddCommand ("bindlist", Key_BindList_f, "bindlist: displays bound keys for bindmap 0 bindmaps"); + Cmd_AddCommand ("unbindall", Key_Unbindall_f, "removes all commands from all keys in all bindmaps (leaving only shift-escape and escape)"); + + Cmd_AddCommand ("history", Key_History_f, "prints the history of executed commands (history X prints the last X entries, history -c clears the whole history)"); + + Cvar_RegisterVariable (&con_closeontoggleconsole); +} + +void +Key_Shutdown (void) +{ + Key_History_Shutdown(); +} + +const char *Key_GetBind (int key, int bindmap) +{ + const char *bind; + if (key < 0 || key >= MAX_KEYS) + return NULL; + if(bindmap >= MAX_BINDMAPS) + return NULL; + if(bindmap >= 0) + { + bind = keybindings[bindmap][key]; + } + else + { + bind = keybindings[key_bmap][key]; + if (!bind) + bind = keybindings[key_bmap2][key]; + } + return bind; +} + +void Key_FindKeysForCommand (const char *command, int *keys, int numkeys, int bindmap) +{ + int count; + int j; + const char *b; + + for (j = 0;j < numkeys;j++) + keys[j] = -1; + + if(bindmap >= MAX_BINDMAPS) + return; + + count = 0; + + for (j = 0; j < MAX_KEYS; ++j) + { + b = Key_GetBind(j, bindmap); + if (!b) + continue; + if (!strcmp (b, command) ) + { + keys[count++] = j; + if (count == numkeys) + break; + } + } +} + +qboolean CL_VM_InputEvent (qboolean down, int key, int ascii); + +/* +=================== +Called by the system between frames for both key up and key down events +Should NOT be called during an interrupt! +=================== +*/ +static char tbl_keyascii[MAX_KEYS]; +static keydest_t tbl_keydest[MAX_KEYS]; + +typedef struct eventqueueitem_s +{ + int key; + int ascii; + qboolean down; +} +eventqueueitem_t; +static int events_blocked = 0; +static eventqueueitem_t eventqueue[32]; +static unsigned eventqueue_idx = 0; + +static void Key_EventQueue_Add(int key, int ascii, qboolean down) +{ + if(eventqueue_idx < sizeof(eventqueue) / sizeof(*eventqueue)) + { + eventqueue[eventqueue_idx].key = key; + eventqueue[eventqueue_idx].ascii = ascii; + eventqueue[eventqueue_idx].down = down; + ++eventqueue_idx; + } +} + +void Key_EventQueue_Block(void) +{ + // block key events until call to Unblock + events_blocked = true; +} + +void Key_EventQueue_Unblock(void) +{ + // unblocks key events again + unsigned i; + events_blocked = false; + for(i = 0; i < eventqueue_idx; ++i) + Key_Event(eventqueue[i].key, eventqueue[i].ascii, eventqueue[i].down); + eventqueue_idx = 0; +} + +void +Key_Event (int key, int ascii, qboolean down) +{ + const char *bind; + qboolean q; + keydest_t keydest = key_dest; + + if (key < 0 || key >= MAX_KEYS) + return; + + if(events_blocked) + { + Key_EventQueue_Add(key, ascii, down); + return; + } + + if (ascii == 0x80 && utf8_enable.integer) // pressing AltGr-5 (or AltGr-e) and for some reason we get windows-1252 encoding? + ascii = 0x20AC; // we want the Euro currency sign + // TODO find out which vid_ drivers do it and fix it there + // but catching U+0080 here is no loss as that char is not useful anyway + + // get key binding + bind = keybindings[key_bmap][key]; + if (!bind) + bind = keybindings[key_bmap2][key]; + + if (developer_insane.integer) + Con_DPrintf("Key_Event(%i, '%c', %s) keydown %i bind \"%s\"\n", key, ascii ? ascii : '?', down ? "down" : "up", keydown[key], bind ? bind : ""); + + if(key_consoleactive) + keydest = key_console; + + if (down) + { + // increment key repeat count each time a down is received so that things + // which want to ignore key repeat can ignore it + keydown[key] = min(keydown[key] + 1, 2); + if(keydown[key] == 1) { + tbl_keyascii[key] = ascii; + tbl_keydest[key] = keydest; + } else { + ascii = tbl_keyascii[key]; + keydest = tbl_keydest[key]; + } + } + else + { + // clear repeat count now that the key is released + keydown[key] = 0; + keydest = tbl_keydest[key]; + ascii = tbl_keyascii[key]; + } + + if(keydest == key_void) + return; + + // key_consoleactive is a flag not a key_dest because the console is a + // high priority overlay ontop of the normal screen (designed as a safety + // feature so that developers and users can rescue themselves from a bad + // situation). + // + // this also means that toggling the console on/off does not lose the old + // key_dest state + + // specially handle escape (togglemenu) and shift-escape (toggleconsole) + // engine bindings, these are not handled as normal binds so that the user + // can recover from a completely empty bindmap + if (key == K_ESCAPE) + { + // ignore key repeats on escape + if (keydown[key] > 1) + return; + + // escape does these things: + // key_consoleactive - close console + // key_message - abort messagemode + // key_menu - go to parent menu (or key_game) + // key_game - open menu + + // in all modes shift-escape toggles console + if (keydown[K_SHIFT]) + { + if(down) + { + Con_ToggleConsole_f (); + tbl_keydest[key] = key_void; // esc release should go nowhere (especially not to key_menu or key_game) + } + return; + } + + switch (keydest) + { + case key_console: + if(down) + { + if(key_consoleactive & KEY_CONSOLEACTIVE_FORCED) + { + key_consoleactive &= ~KEY_CONSOLEACTIVE_USER; + MR_ToggleMenu(1); + } + else + Con_ToggleConsole_f(); + } + break; + + case key_message: + if (down) + Key_Message (key, ascii); // that'll close the message input + break; + + case key_menu: + case key_menu_grabbed: + MR_KeyEvent (key, ascii, down); + break; + + case key_game: + // csqc has priority over toggle menu if it wants to (e.g. handling escape for UI stuff in-game.. :sick:) + q = CL_VM_InputEvent(down, key, ascii); + if (!q && down) + MR_ToggleMenu(1); + break; + + default: + Con_Printf ("Key_Event: Bad key_dest\n"); + } + return; + } + + // send function keydowns to interpreter no matter what mode is (unless the menu has specifically grabbed the keyboard, for rebinding keys) + // VorteX: Omnicide does bind F* keys + if (keydest != key_menu_grabbed) + if (key >= K_F1 && key <= K_F12 && gamemode != GAME_BLOODOMNICIDE) + { + if (bind) + { + if(keydown[key] == 1 && down) + { + // button commands add keynum as a parm + if (bind[0] == '+') + Cbuf_AddText (va("%s %i\n", bind, key)); + else + { + Cbuf_AddText (bind); + Cbuf_AddText ("\n"); + } + } else if(bind[0] == '+' && !down && keydown[key] == 0) + Cbuf_AddText(va("-%s %i\n", bind + 1, key)); + } + return; + } + + // send input to console if it wants it + if (keydest == key_console) + { + if (!down) + return; + // con_closeontoggleconsole enables toggleconsole keys to close the + // console, as long as they are not the color prefix character + // (special exemption for german keyboard layouts) + if (con_closeontoggleconsole.integer && bind && !strncmp(bind, "toggleconsole", strlen("toggleconsole")) && (key_consoleactive & KEY_CONSOLEACTIVE_USER) && (con_closeontoggleconsole.integer >= ((ascii != STRING_COLOR_TAG) ? 2 : 3) || key_linepos == 1)) + { + Con_ToggleConsole_f (); + return; + } + Key_Console (key, ascii); + return; + } + + // handle toggleconsole in menu too + if (keydest == key_menu) + { + if (down && con_closeontoggleconsole.integer && bind && !strncmp(bind, "toggleconsole", strlen("toggleconsole")) && ascii != STRING_COLOR_TAG) + { + Con_ToggleConsole_f (); + tbl_keydest[key] = key_void; // key release should go nowhere (especially not to key_menu or key_game) + return; + } + } + + // ignore binds while a video is played, let the video system handle the key event + if (cl_videoplaying) + { + if (gamemode == GAME_BLOODOMNICIDE) // menu controls key events + MR_KeyEvent(key, ascii, down); + else + CL_Video_KeyEvent (key, ascii, keydown[key] != 0); + return; + } + + // anything else is a key press into the game, chat line, or menu + switch (keydest) + { + case key_message: + if (down) + Key_Message (key, ascii); + break; + case key_menu: + case key_menu_grabbed: + MR_KeyEvent (key, ascii, down); + break; + case key_game: + q = CL_VM_InputEvent(down, key, ascii); + // ignore key repeats on binds and only send the bind if the event hasnt been already processed by csqc + if (!q && bind) + { + if(keydown[key] == 1 && down) + { + // button commands add keynum as a parm + if (bind[0] == '+') + Cbuf_AddText (va("%s %i\n", bind, key)); + else + { + Cbuf_AddText (bind); + Cbuf_AddText ("\n"); + } + } else if(bind[0] == '+' && !down && keydown[key] == 0) + Cbuf_AddText(va("-%s %i\n", bind + 1, key)); + } + break; + default: + Con_Printf ("Key_Event: Bad key_dest\n"); + } +} + +/* +=================== +Key_ClearStates +=================== +*/ +void +Key_ClearStates (void) +{ + memset(keydown, 0, sizeof(keydown)); +} diff --git a/misc/source/darkplaces-src/keys.h b/misc/source/darkplaces-src/keys.h new file mode 100644 index 00000000..c2587aed --- /dev/null +++ b/misc/source/darkplaces-src/keys.h @@ -0,0 +1,391 @@ +/* + $RCSfile$ + + Copyright (C) 1996-1997 Id Software, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + + $Id$ +*/ + +#ifndef __KEYS_H +#define __KEYS_H + +#include "qtypes.h" + +// +// these are the key numbers that should be passed to Key_Event +// +typedef enum keynum_e +{ + K_TEXT = 1, // used only for unicode character input + K_TAB = 9, + K_ENTER = 13, + K_ESCAPE = 27, + K_SPACE = 32, + + // normal keys should be passed as lowercased ascii + + K_BACKSPACE = 127, + K_UPARROW, + K_DOWNARROW, + K_LEFTARROW, + K_RIGHTARROW, + + K_ALT, + K_CTRL, + K_SHIFT, + + 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_INS, + K_DEL, + K_PGDN, + K_PGUP, + K_HOME, + K_END, + + K_PAUSE, + + K_NUMLOCK, + K_CAPSLOCK, + K_SCROLLOCK, + + K_KP_0, + K_KP_INS = K_KP_0, + K_KP_1, + K_KP_END = K_KP_1, + K_KP_2, + K_KP_DOWNARROW = K_KP_2, + K_KP_3, + K_KP_PGDN = K_KP_3, + K_KP_4, + K_KP_LEFTARROW = K_KP_4, + K_KP_5, + K_KP_6, + K_KP_RIGHTARROW = K_KP_6, + K_KP_7, + K_KP_HOME = K_KP_7, + K_KP_8, + K_KP_UPARROW = K_KP_8, + K_KP_9, + K_KP_PGUP = K_KP_9, + K_KP_PERIOD, + K_KP_DEL = K_KP_PERIOD, + K_KP_DIVIDE, + K_KP_SLASH = K_KP_DIVIDE, + K_KP_MULTIPLY, + K_KP_MINUS, + K_KP_PLUS, + K_KP_ENTER, + K_KP_EQUALS, + + K_PRINTSCREEN, + + // mouse buttons generate virtual keys + + K_MOUSE1 = 512, + K_OTHERDEVICESBEGIN = K_MOUSE1, + K_MOUSE2, + K_MOUSE3, + K_MWHEELUP, + K_MWHEELDOWN, + K_MOUSE4, + K_MOUSE5, + K_MOUSE6, + K_MOUSE7, + K_MOUSE8, + K_MOUSE9, + K_MOUSE10, + K_MOUSE11, + K_MOUSE12, + K_MOUSE13, + K_MOUSE14, + K_MOUSE15, + K_MOUSE16, + +// +// joystick buttons +// + K_JOY1 = 768, + 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, + +// +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// + 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_AUX17, + K_AUX18, + K_AUX19, + K_AUX20, + K_AUX21, + K_AUX22, + K_AUX23, + K_AUX24, + K_AUX25, + K_AUX26, + K_AUX27, + K_AUX28, + K_AUX29, + K_AUX30, + K_AUX31, + K_AUX32, + + // Microsoft Xbox 360 Controller For Windows + K_X360_DPAD_UP, + K_X360_DPAD_DOWN, + K_X360_DPAD_LEFT, + K_X360_DPAD_RIGHT, + K_X360_START, + K_X360_BACK, + K_X360_LEFT_THUMB, + K_X360_RIGHT_THUMB, + K_X360_LEFT_SHOULDER, + K_X360_RIGHT_SHOULDER, + K_X360_A, + K_X360_B, + K_X360_X, + K_X360_Y, + K_X360_LEFT_TRIGGER, + K_X360_RIGHT_TRIGGER, + K_X360_LEFT_THUMB_UP, + K_X360_LEFT_THUMB_DOWN, + K_X360_LEFT_THUMB_LEFT, + K_X360_LEFT_THUMB_RIGHT, + K_X360_RIGHT_THUMB_UP, + K_X360_RIGHT_THUMB_DOWN, + K_X360_RIGHT_THUMB_LEFT, + K_X360_RIGHT_THUMB_RIGHT, + + // generic joystick emulation for menu + K_JOY_UP, + K_JOY_DOWN, + K_JOY_LEFT, + K_JOY_RIGHT, + + K_MIDINOTE0 = 896, // to this, the note number is added + K_MIDINOTE1, + K_MIDINOTE2, + K_MIDINOTE3, + K_MIDINOTE4, + K_MIDINOTE5, + K_MIDINOTE6, + K_MIDINOTE7, + K_MIDINOTE8, + K_MIDINOTE9, + K_MIDINOTE10, + K_MIDINOTE11, + K_MIDINOTE12, + K_MIDINOTE13, + K_MIDINOTE14, + K_MIDINOTE15, + K_MIDINOTE16, + K_MIDINOTE17, + K_MIDINOTE18, + K_MIDINOTE19, + K_MIDINOTE20, + K_MIDINOTE21, + K_MIDINOTE22, + K_MIDINOTE23, + K_MIDINOTE24, + K_MIDINOTE25, + K_MIDINOTE26, + K_MIDINOTE27, + K_MIDINOTE28, + K_MIDINOTE29, + K_MIDINOTE30, + K_MIDINOTE31, + K_MIDINOTE32, + K_MIDINOTE33, + K_MIDINOTE34, + K_MIDINOTE35, + K_MIDINOTE36, + K_MIDINOTE37, + K_MIDINOTE38, + K_MIDINOTE39, + K_MIDINOTE40, + K_MIDINOTE41, + K_MIDINOTE42, + K_MIDINOTE43, + K_MIDINOTE44, + K_MIDINOTE45, + K_MIDINOTE46, + K_MIDINOTE47, + K_MIDINOTE48, + K_MIDINOTE49, + K_MIDINOTE50, + K_MIDINOTE51, + K_MIDINOTE52, + K_MIDINOTE53, + K_MIDINOTE54, + K_MIDINOTE55, + K_MIDINOTE56, + K_MIDINOTE57, + K_MIDINOTE58, + K_MIDINOTE59, + K_MIDINOTE60, + K_MIDINOTE61, + K_MIDINOTE62, + K_MIDINOTE63, + K_MIDINOTE64, + K_MIDINOTE65, + K_MIDINOTE66, + K_MIDINOTE67, + K_MIDINOTE68, + K_MIDINOTE69, + K_MIDINOTE70, + K_MIDINOTE71, + K_MIDINOTE72, + K_MIDINOTE73, + K_MIDINOTE74, + K_MIDINOTE75, + K_MIDINOTE76, + K_MIDINOTE77, + K_MIDINOTE78, + K_MIDINOTE79, + K_MIDINOTE80, + K_MIDINOTE81, + K_MIDINOTE82, + K_MIDINOTE83, + K_MIDINOTE84, + K_MIDINOTE85, + K_MIDINOTE86, + K_MIDINOTE87, + K_MIDINOTE88, + K_MIDINOTE89, + K_MIDINOTE90, + K_MIDINOTE91, + K_MIDINOTE92, + K_MIDINOTE93, + K_MIDINOTE94, + K_MIDINOTE95, + K_MIDINOTE96, + K_MIDINOTE97, + K_MIDINOTE98, + K_MIDINOTE99, + K_MIDINOTE100, + K_MIDINOTE101, + K_MIDINOTE102, + K_MIDINOTE103, + K_MIDINOTE104, + K_MIDINOTE105, + K_MIDINOTE106, + K_MIDINOTE107, + K_MIDINOTE108, + K_MIDINOTE109, + K_MIDINOTE110, + K_MIDINOTE111, + K_MIDINOTE112, + K_MIDINOTE113, + K_MIDINOTE114, + K_MIDINOTE115, + K_MIDINOTE116, + K_MIDINOTE117, + K_MIDINOTE118, + K_MIDINOTE119, + K_MIDINOTE120, + K_MIDINOTE121, + K_MIDINOTE122, + K_MIDINOTE123, + K_MIDINOTE124, + K_MIDINOTE125, + K_MIDINOTE126, + K_MIDINOTE127, + + MAX_KEYS +} +keynum_t; + +typedef enum keydest_e { key_game, key_message, key_menu, key_menu_grabbed, key_console, key_void } keydest_t; + +extern char key_line[MAX_INPUTLINE]; +extern int key_linepos; +extern qboolean key_insert; // insert key toggle (for editing) +extern keydest_t key_dest; +// key_consoleactive bits +// user wants console (halfscreen) +#define KEY_CONSOLEACTIVE_USER 1 +// console forced because there's nothing else active (fullscreen) +#define KEY_CONSOLEACTIVE_FORCED 4 +extern int key_consoleactive; +extern char *keybindings[MAX_BINDMAPS][MAX_KEYS]; + +extern void Key_ClearEditLine(int edit_line); +extern int chat_mode; // 0 for say, 1 for say_team, -1 for command +extern char chat_buffer[MAX_INPUTLINE]; +extern unsigned int chat_bufferlen; + +void Key_WriteBindings(qfile_t *f); +void Key_Init(void); +void Key_Shutdown(void); +void Key_Init_Cvars(void); +void Key_Event(int key, int ascii, qboolean down); +void Key_ClearStates (void); +void Key_EventQueue_Block(void); +void Key_EventQueue_Unblock(void); + +qboolean Key_SetBinding (int keynum, int bindmap, const char *binding); +const char *Key_GetBind (int key, int bindmap); +void Key_FindKeysForCommand (const char *command, int *keys, int numkeys, int bindmap); +qboolean Key_SetBindMap(int fg, int bg); +void Key_GetBindMap(int *fg, int *bg); + +#endif // __KEYS_H + diff --git a/misc/source/darkplaces-src/keysym2ucs.c b/misc/source/darkplaces-src/keysym2ucs.c new file mode 100644 index 00000000..05ac8135 --- /dev/null +++ b/misc/source/darkplaces-src/keysym2ucs.c @@ -0,0 +1,848 @@ +/* $XFree86$ + * This module converts keysym values into the corresponding ISO 10646 + * (UCS, Unicode) values. + * + * The array keysymtab[] contains pairs of X11 keysym values for graphical + * characters and the corresponding Unicode value. The function + * keysym2ucs() maps a keysym onto a Unicode value using a binary search, + * therefore keysymtab[] must remain SORTED by keysym value. + * + * The keysym -> UTF-8 conversion will hopefully one day be provided + * by Xlib via XmbLookupString() and should ideally not have to be + * done in X applications. But we are not there yet. + * + * We allow to represent any UCS character in the range U-00000000 to + * U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff. + * This admittedly does not cover the entire 31-bit space of UCS, but + * it does cover all of the characters up to U-10FFFF, which can be + * represented by UTF-16, and more, and it is very unlikely that higher + * UCS codes will ever be assigned by ISO. So to get Unicode character + * U+ABCD you can directly use keysym 0x0100abcd. + * + * NOTE: The comments in the table below contain the actual character + * encoded in UTF-8, so for viewing and editing best use an editor in + * UTF-8 mode. + * + * Author: Markus G. Kuhn , + * University of Cambridge, April 2001 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This software is in the public domain. Share and enjoy! + * + * AUTOMATICALLY GENERATED FILE, DO NOT EDIT !!! (unicode/convmap.pl) + */ + +#include + +struct codepair { + unsigned short keysym; + unsigned short ucs; +} keysymtab[] = { + { 0x01a1, 0x0104 }, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ + { 0x01a2, 0x02d8 }, /* breve ˘ BREVE */ + { 0x01a3, 0x0141 }, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */ + { 0x01a5, 0x013d }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ + { 0x01a6, 0x015a }, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */ + { 0x01a9, 0x0160 }, /* Scaron Å  LATIN CAPITAL LETTER S WITH CARON */ + { 0x01aa, 0x015e }, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */ + { 0x01ab, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ + { 0x01ac, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ + { 0x01ae, 0x017d }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ + { 0x01af, 0x017b }, /* Zabovedot Å» LATIN CAPITAL LETTER Z WITH DOT ABOVE */ + { 0x01b1, 0x0105 }, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */ + { 0x01b2, 0x02db }, /* ogonek ˛ OGONEK */ + { 0x01b3, 0x0142 }, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */ + { 0x01b5, 0x013e }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ + { 0x01b6, 0x015b }, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */ + { 0x01b7, 0x02c7 }, /* caron ˇ CARON */ + { 0x01b9, 0x0161 }, /* scaron Å¡ LATIN SMALL LETTER S WITH CARON */ + { 0x01ba, 0x015f }, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */ + { 0x01bb, 0x0165 }, /* tcaron Å¥ LATIN SMALL LETTER T WITH CARON */ + { 0x01bc, 0x017a }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ + { 0x01bd, 0x02dd }, /* doubleacute ˝ DOUBLE ACUTE ACCENT */ + { 0x01be, 0x017e }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ + { 0x01bf, 0x017c }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ + { 0x01c0, 0x0154 }, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */ + { 0x01c3, 0x0102 }, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */ + { 0x01c5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ + { 0x01c6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ + { 0x01c8, 0x010c }, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */ + { 0x01ca, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ + { 0x01cc, 0x011a }, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */ + { 0x01cf, 0x010e }, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */ + { 0x01d0, 0x0110 }, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */ + { 0x01d1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ + { 0x01d2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ + { 0x01d5, 0x0150 }, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ + { 0x01d8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ + { 0x01d9, 0x016e }, /* Uring Å® LATIN CAPITAL LETTER U WITH RING ABOVE */ + { 0x01db, 0x0170 }, /* Udoubleacute Å° LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ + { 0x01de, 0x0162 }, /* Tcedilla Å¢ LATIN CAPITAL LETTER T WITH CEDILLA */ + { 0x01e0, 0x0155 }, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */ + { 0x01e3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ + { 0x01e5, 0x013a }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ + { 0x01e6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ + { 0x01e8, 0x010d }, /* ccaron č LATIN SMALL LETTER C WITH CARON */ + { 0x01ea, 0x0119 }, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */ + { 0x01ec, 0x011b }, /* ecaron ě LATIN SMALL LETTER E WITH CARON */ + { 0x01ef, 0x010f }, /* dcaron ď LATIN SMALL LETTER D WITH CARON */ + { 0x01f0, 0x0111 }, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */ + { 0x01f1, 0x0144 }, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */ + { 0x01f2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ + { 0x01f5, 0x0151 }, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */ + { 0x01f8, 0x0159 }, /* rcaron ř LATIN SMALL LETTER R WITH CARON */ + { 0x01f9, 0x016f }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ + { 0x01fb, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ + { 0x01fe, 0x0163 }, /* tcedilla Å£ LATIN SMALL LETTER T WITH CEDILLA */ + { 0x01ff, 0x02d9 }, /* abovedot ˙ DOT ABOVE */ + { 0x02a1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ + { 0x02a6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ + { 0x02a9, 0x0130 }, /* Iabovedot Ä° LATIN CAPITAL LETTER I WITH DOT ABOVE */ + { 0x02ab, 0x011e }, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */ + { 0x02ac, 0x0134 }, /* Jcircumflex Ä´ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ + { 0x02b1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ + { 0x02b6, 0x0125 }, /* hcircumflex Ä¥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ + { 0x02b9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */ + { 0x02bb, 0x011f }, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */ + { 0x02bc, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ + { 0x02c5, 0x010a }, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */ + { 0x02c6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ + { 0x02d5, 0x0120 }, /* Gabovedot Ä  LATIN CAPITAL LETTER G WITH DOT ABOVE */ + { 0x02d8, 0x011c }, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ + { 0x02dd, 0x016c }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ + { 0x02de, 0x015c }, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ + { 0x02e5, 0x010b }, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */ + { 0x02e6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ + { 0x02f5, 0x0121 }, /* gabovedot Ä¡ LATIN SMALL LETTER G WITH DOT ABOVE */ + { 0x02f8, 0x011d }, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */ + { 0x02fd, 0x016d }, /* ubreve Å­ LATIN SMALL LETTER U WITH BREVE */ + { 0x02fe, 0x015d }, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */ + { 0x03a2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */ + { 0x03a3, 0x0156 }, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */ + { 0x03a5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ + { 0x03a6, 0x013b }, /* Lcedilla Ä» LATIN CAPITAL LETTER L WITH CEDILLA */ + { 0x03aa, 0x0112 }, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */ + { 0x03ab, 0x0122 }, /* Gcedilla Ä¢ LATIN CAPITAL LETTER G WITH CEDILLA */ + { 0x03ac, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ + { 0x03b3, 0x0157 }, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */ + { 0x03b5, 0x0129 }, /* itilde Ä© LATIN SMALL LETTER I WITH TILDE */ + { 0x03b6, 0x013c }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ + { 0x03ba, 0x0113 }, /* emacron ē LATIN SMALL LETTER E WITH MACRON */ + { 0x03bb, 0x0123 }, /* gcedilla Ä£ LATIN SMALL LETTER G WITH CEDILLA */ + { 0x03bc, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ + { 0x03bd, 0x014a }, /* ENG Ŋ LATIN CAPITAL LETTER ENG */ + { 0x03bf, 0x014b }, /* eng ŋ LATIN SMALL LETTER ENG */ + { 0x03c0, 0x0100 }, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */ + { 0x03c7, 0x012e }, /* Iogonek Ä® LATIN CAPITAL LETTER I WITH OGONEK */ + { 0x03cc, 0x0116 }, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */ + { 0x03cf, 0x012a }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ + { 0x03d1, 0x0145 }, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */ + { 0x03d2, 0x014c }, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */ + { 0x03d3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ + { 0x03d9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ + { 0x03dd, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ + { 0x03de, 0x016a }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ + { 0x03e0, 0x0101 }, /* amacron ā LATIN SMALL LETTER A WITH MACRON */ + { 0x03e7, 0x012f }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ + { 0x03ec, 0x0117 }, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */ + { 0x03ef, 0x012b }, /* imacron Ä« LATIN SMALL LETTER I WITH MACRON */ + { 0x03f1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ + { 0x03f2, 0x014d }, /* omacron ō LATIN SMALL LETTER O WITH MACRON */ + { 0x03f3, 0x0137 }, /* kcedilla Ä· LATIN SMALL LETTER K WITH CEDILLA */ + { 0x03f9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ + { 0x03fd, 0x0169 }, /* utilde Å© LATIN SMALL LETTER U WITH TILDE */ + { 0x03fe, 0x016b }, /* umacron Å« LATIN SMALL LETTER U WITH MACRON */ + { 0x047e, 0x203e }, /* overline ‾ OVERLINE */ + { 0x04a1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ + { 0x04a2, 0x300c }, /* kana_openingbracket 「 LEFT CORNER BRACKET */ + { 0x04a3, 0x300d }, /* kana_closingbracket 」 RIGHT CORNER BRACKET */ + { 0x04a4, 0x3001 }, /* kana_comma 、 IDEOGRAPHIC COMMA */ + { 0x04a5, 0x30fb }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ + { 0x04a6, 0x30f2 }, /* kana_WO ヲ KATAKANA LETTER WO */ + { 0x04a7, 0x30a1 }, /* kana_a ァ KATAKANA LETTER SMALL A */ + { 0x04a8, 0x30a3 }, /* kana_i ィ KATAKANA LETTER SMALL I */ + { 0x04a9, 0x30a5 }, /* kana_u ゥ KATAKANA LETTER SMALL U */ + { 0x04aa, 0x30a7 }, /* kana_e ェ KATAKANA LETTER SMALL E */ + { 0x04ab, 0x30a9 }, /* kana_o ォ KATAKANA LETTER SMALL O */ + { 0x04ac, 0x30e3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */ + { 0x04ad, 0x30e5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */ + { 0x04ae, 0x30e7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */ + { 0x04af, 0x30c3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ + { 0x04b0, 0x30fc }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ + { 0x04b1, 0x30a2 }, /* kana_A ア KATAKANA LETTER A */ + { 0x04b2, 0x30a4 }, /* kana_I イ KATAKANA LETTER I */ + { 0x04b3, 0x30a6 }, /* kana_U ウ KATAKANA LETTER U */ + { 0x04b4, 0x30a8 }, /* kana_E エ KATAKANA LETTER E */ + { 0x04b5, 0x30aa }, /* kana_O オ KATAKANA LETTER O */ + { 0x04b6, 0x30ab }, /* kana_KA カ KATAKANA LETTER KA */ + { 0x04b7, 0x30ad }, /* kana_KI キ KATAKANA LETTER KI */ + { 0x04b8, 0x30af }, /* kana_KU ク KATAKANA LETTER KU */ + { 0x04b9, 0x30b1 }, /* kana_KE ケ KATAKANA LETTER KE */ + { 0x04ba, 0x30b3 }, /* kana_KO コ KATAKANA LETTER KO */ + { 0x04bb, 0x30b5 }, /* kana_SA サ KATAKANA LETTER SA */ + { 0x04bc, 0x30b7 }, /* kana_SHI シ KATAKANA LETTER SI */ + { 0x04bd, 0x30b9 }, /* kana_SU ス KATAKANA LETTER SU */ + { 0x04be, 0x30bb }, /* kana_SE セ KATAKANA LETTER SE */ + { 0x04bf, 0x30bd }, /* kana_SO ソ KATAKANA LETTER SO */ + { 0x04c0, 0x30bf }, /* kana_TA タ KATAKANA LETTER TA */ + { 0x04c1, 0x30c1 }, /* kana_CHI チ KATAKANA LETTER TI */ + { 0x04c2, 0x30c4 }, /* kana_TSU ツ KATAKANA LETTER TU */ + { 0x04c3, 0x30c6 }, /* kana_TE テ KATAKANA LETTER TE */ + { 0x04c4, 0x30c8 }, /* kana_TO ト KATAKANA LETTER TO */ + { 0x04c5, 0x30ca }, /* kana_NA ナ KATAKANA LETTER NA */ + { 0x04c6, 0x30cb }, /* kana_NI ニ KATAKANA LETTER NI */ + { 0x04c7, 0x30cc }, /* kana_NU ヌ KATAKANA LETTER NU */ + { 0x04c8, 0x30cd }, /* kana_NE ネ KATAKANA LETTER NE */ + { 0x04c9, 0x30ce }, /* kana_NO ノ KATAKANA LETTER NO */ + { 0x04ca, 0x30cf }, /* kana_HA ハ KATAKANA LETTER HA */ + { 0x04cb, 0x30d2 }, /* kana_HI ヒ KATAKANA LETTER HI */ + { 0x04cc, 0x30d5 }, /* kana_FU フ KATAKANA LETTER HU */ + { 0x04cd, 0x30d8 }, /* kana_HE ヘ KATAKANA LETTER HE */ + { 0x04ce, 0x30db }, /* kana_HO ホ KATAKANA LETTER HO */ + { 0x04cf, 0x30de }, /* kana_MA マ KATAKANA LETTER MA */ + { 0x04d0, 0x30df }, /* kana_MI ミ KATAKANA LETTER MI */ + { 0x04d1, 0x30e0 }, /* kana_MU ム KATAKANA LETTER MU */ + { 0x04d2, 0x30e1 }, /* kana_ME メ KATAKANA LETTER ME */ + { 0x04d3, 0x30e2 }, /* kana_MO モ KATAKANA LETTER MO */ + { 0x04d4, 0x30e4 }, /* kana_YA ヤ KATAKANA LETTER YA */ + { 0x04d5, 0x30e6 }, /* kana_YU ユ KATAKANA LETTER YU */ + { 0x04d6, 0x30e8 }, /* kana_YO ヨ KATAKANA LETTER YO */ + { 0x04d7, 0x30e9 }, /* kana_RA ラ KATAKANA LETTER RA */ + { 0x04d8, 0x30ea }, /* kana_RI リ KATAKANA LETTER RI */ + { 0x04d9, 0x30eb }, /* kana_RU ル KATAKANA LETTER RU */ + { 0x04da, 0x30ec }, /* kana_RE レ KATAKANA LETTER RE */ + { 0x04db, 0x30ed }, /* kana_RO ロ KATAKANA LETTER RO */ + { 0x04dc, 0x30ef }, /* kana_WA ワ KATAKANA LETTER WA */ + { 0x04dd, 0x30f3 }, /* kana_N ン KATAKANA LETTER N */ + { 0x04de, 0x309b }, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */ + { 0x04df, 0x309c }, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ + { 0x05ac, 0x060c }, /* Arabic_comma ، ARABIC COMMA */ + { 0x05bb, 0x061b }, /* Arabic_semicolon ؛ ARABIC SEMICOLON */ + { 0x05bf, 0x061f }, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */ + { 0x05c1, 0x0621 }, /* Arabic_hamza Ø¡ ARABIC LETTER HAMZA */ + { 0x05c2, 0x0622 }, /* Arabic_maddaonalef Ø¢ ARABIC LETTER ALEF WITH MADDA ABOVE */ + { 0x05c3, 0x0623 }, /* Arabic_hamzaonalef Ø£ ARABIC LETTER ALEF WITH HAMZA ABOVE */ + { 0x05c4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ + { 0x05c5, 0x0625 }, /* Arabic_hamzaunderalef Ø¥ ARABIC LETTER ALEF WITH HAMZA BELOW */ + { 0x05c6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ + { 0x05c7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */ + { 0x05c8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */ + { 0x05c9, 0x0629 }, /* Arabic_tehmarbuta Ø© ARABIC LETTER TEH MARBUTA */ + { 0x05ca, 0x062a }, /* Arabic_teh ت ARABIC LETTER TEH */ + { 0x05cb, 0x062b }, /* Arabic_theh Ø« ARABIC LETTER THEH */ + { 0x05cc, 0x062c }, /* Arabic_jeem ج ARABIC LETTER JEEM */ + { 0x05cd, 0x062d }, /* Arabic_hah Ø­ ARABIC LETTER HAH */ + { 0x05ce, 0x062e }, /* Arabic_khah Ø® ARABIC LETTER KHAH */ + { 0x05cf, 0x062f }, /* Arabic_dal د ARABIC LETTER DAL */ + { 0x05d0, 0x0630 }, /* Arabic_thal Ø° ARABIC LETTER THAL */ + { 0x05d1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */ + { 0x05d2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */ + { 0x05d3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */ + { 0x05d4, 0x0634 }, /* Arabic_sheen Ø´ ARABIC LETTER SHEEN */ + { 0x05d5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */ + { 0x05d6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */ + { 0x05d7, 0x0637 }, /* Arabic_tah Ø· ARABIC LETTER TAH */ + { 0x05d8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */ + { 0x05d9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */ + { 0x05da, 0x063a }, /* Arabic_ghain غ ARABIC LETTER GHAIN */ + { 0x05e0, 0x0640 }, /* Arabic_tatweel ـ ARABIC TATWEEL */ + { 0x05e1, 0x0641 }, /* Arabic_feh ف ARABIC LETTER FEH */ + { 0x05e2, 0x0642 }, /* Arabic_qaf ق ARABIC LETTER QAF */ + { 0x05e3, 0x0643 }, /* Arabic_kaf ك ARABIC LETTER KAF */ + { 0x05e4, 0x0644 }, /* Arabic_lam ل ARABIC LETTER LAM */ + { 0x05e5, 0x0645 }, /* Arabic_meem م ARABIC LETTER MEEM */ + { 0x05e6, 0x0646 }, /* Arabic_noon ن ARABIC LETTER NOON */ + { 0x05e7, 0x0647 }, /* Arabic_ha ه ARABIC LETTER HEH */ + { 0x05e8, 0x0648 }, /* Arabic_waw و ARABIC LETTER WAW */ + { 0x05e9, 0x0649 }, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */ + { 0x05ea, 0x064a }, /* Arabic_yeh ي ARABIC LETTER YEH */ + { 0x05eb, 0x064b }, /* Arabic_fathatan ً ARABIC FATHATAN */ + { 0x05ec, 0x064c }, /* Arabic_dammatan ٌ ARABIC DAMMATAN */ + { 0x05ed, 0x064d }, /* Arabic_kasratan ٍ ARABIC KASRATAN */ + { 0x05ee, 0x064e }, /* Arabic_fatha َ ARABIC FATHA */ + { 0x05ef, 0x064f }, /* Arabic_damma ُ ARABIC DAMMA */ + { 0x05f0, 0x0650 }, /* Arabic_kasra ِ ARABIC KASRA */ + { 0x05f1, 0x0651 }, /* Arabic_shadda ّ ARABIC SHADDA */ + { 0x05f2, 0x0652 }, /* Arabic_sukun ْ ARABIC SUKUN */ + { 0x06a1, 0x0452 }, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */ + { 0x06a2, 0x0453 }, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */ + { 0x06a3, 0x0451 }, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */ + { 0x06a4, 0x0454 }, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */ + { 0x06a5, 0x0455 }, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */ + { 0x06a6, 0x0456 }, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ + { 0x06a7, 0x0457 }, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */ + { 0x06a8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ + { 0x06a9, 0x0459 }, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */ + { 0x06aa, 0x045a }, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */ + { 0x06ab, 0x045b }, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */ + { 0x06ac, 0x045c }, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */ + { 0x06ae, 0x045e }, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */ + { 0x06af, 0x045f }, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */ + { 0x06b0, 0x2116 }, /* numerosign № NUMERO SIGN */ + { 0x06b1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ + { 0x06b2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ + { 0x06b3, 0x0401 }, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */ + { 0x06b4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ + { 0x06b5, 0x0405 }, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */ + { 0x06b6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ + { 0x06b7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ + { 0x06b8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ + { 0x06b9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ + { 0x06ba, 0x040a }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ + { 0x06bb, 0x040b }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ + { 0x06bc, 0x040c }, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */ + { 0x06be, 0x040e }, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */ + { 0x06bf, 0x040f }, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */ + { 0x06c0, 0x044e }, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */ + { 0x06c1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ + { 0x06c2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ + { 0x06c3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ + { 0x06c4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ + { 0x06c5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ + { 0x06c6, 0x0444 }, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */ + { 0x06c7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ + { 0x06c8, 0x0445 }, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */ + { 0x06c9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ + { 0x06ca, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ + { 0x06cb, 0x043a }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ + { 0x06cc, 0x043b }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ + { 0x06cd, 0x043c }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ + { 0x06ce, 0x043d }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ + { 0x06cf, 0x043e }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ + { 0x06d0, 0x043f }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ + { 0x06d1, 0x044f }, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */ + { 0x06d2, 0x0440 }, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */ + { 0x06d3, 0x0441 }, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */ + { 0x06d4, 0x0442 }, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */ + { 0x06d5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ + { 0x06d6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ + { 0x06d7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ + { 0x06d8, 0x044c }, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */ + { 0x06d9, 0x044b }, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */ + { 0x06da, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ + { 0x06db, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ + { 0x06dc, 0x044d }, /* Cyrillic_e э CYRILLIC SMALL LETTER E */ + { 0x06dd, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ + { 0x06de, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ + { 0x06df, 0x044a }, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */ + { 0x06e0, 0x042e }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ + { 0x06e1, 0x0410 }, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */ + { 0x06e2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ + { 0x06e3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ + { 0x06e4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ + { 0x06e5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ + { 0x06e6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ + { 0x06e7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ + { 0x06e8, 0x0425 }, /* Cyrillic_HA Ð¥ CYRILLIC CAPITAL LETTER HA */ + { 0x06e9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ + { 0x06ea, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ + { 0x06eb, 0x041a }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ + { 0x06ec, 0x041b }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ + { 0x06ed, 0x041c }, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */ + { 0x06ee, 0x041d }, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */ + { 0x06ef, 0x041e }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ + { 0x06f0, 0x041f }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ + { 0x06f1, 0x042f }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ + { 0x06f2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ + { 0x06f3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ + { 0x06f4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ + { 0x06f5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ + { 0x06f6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ + { 0x06f7, 0x0412 }, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */ + { 0x06f8, 0x042c }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ + { 0x06f9, 0x042b }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ + { 0x06fa, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ + { 0x06fb, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ + { 0x06fc, 0x042d }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ + { 0x06fd, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ + { 0x06fe, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ + { 0x06ff, 0x042a }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ + { 0x07a1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ + { 0x07a2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ + { 0x07a3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ + { 0x07a4, 0x038a }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ + { 0x07a5, 0x03aa }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ + { 0x07a7, 0x038c }, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */ + { 0x07a8, 0x038e }, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */ + { 0x07a9, 0x03ab }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ + { 0x07ab, 0x038f }, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */ + { 0x07ae, 0x0385 }, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */ + { 0x07af, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */ + { 0x07b1, 0x03ac }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ + { 0x07b2, 0x03ad }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ + { 0x07b3, 0x03ae }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ + { 0x07b4, 0x03af }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ + { 0x07b5, 0x03ca }, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ + { 0x07b6, 0x0390 }, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ + { 0x07b7, 0x03cc }, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */ + { 0x07b8, 0x03cd }, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */ + { 0x07b9, 0x03cb }, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ + { 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ + { 0x07bb, 0x03ce }, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */ + { 0x07c1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ + { 0x07c2, 0x0392 }, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */ + { 0x07c3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ + { 0x07c4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ + { 0x07c5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ + { 0x07c6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ + { 0x07c7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ + { 0x07c8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ + { 0x07c9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ + { 0x07ca, 0x039a }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ + { 0x07cb, 0x039b }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */ + { 0x07cc, 0x039c }, /* Greek_MU Μ GREEK CAPITAL LETTER MU */ + { 0x07cd, 0x039d }, /* Greek_NU Ν GREEK CAPITAL LETTER NU */ + { 0x07ce, 0x039e }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ + { 0x07cf, 0x039f }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ + { 0x07d0, 0x03a0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */ + { 0x07d1, 0x03a1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ + { 0x07d2, 0x03a3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ + { 0x07d4, 0x03a4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ + { 0x07d5, 0x03a5 }, /* Greek_UPSILON Î¥ GREEK CAPITAL LETTER UPSILON */ + { 0x07d6, 0x03a6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ + { 0x07d7, 0x03a7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ + { 0x07d8, 0x03a8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ + { 0x07d9, 0x03a9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ + { 0x07e1, 0x03b1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ + { 0x07e2, 0x03b2 }, /* Greek_beta β GREEK SMALL LETTER BETA */ + { 0x07e3, 0x03b3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ + { 0x07e4, 0x03b4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */ + { 0x07e5, 0x03b5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ + { 0x07e6, 0x03b6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ + { 0x07e7, 0x03b7 }, /* Greek_eta η GREEK SMALL LETTER ETA */ + { 0x07e8, 0x03b8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */ + { 0x07e9, 0x03b9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */ + { 0x07ea, 0x03ba }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ + { 0x07eb, 0x03bb }, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */ + { 0x07ec, 0x03bc }, /* Greek_mu μ GREEK SMALL LETTER MU */ + { 0x07ed, 0x03bd }, /* Greek_nu ν GREEK SMALL LETTER NU */ + { 0x07ee, 0x03be }, /* Greek_xi ξ GREEK SMALL LETTER XI */ + { 0x07ef, 0x03bf }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ + { 0x07f0, 0x03c0 }, /* Greek_pi π GREEK SMALL LETTER PI */ + { 0x07f1, 0x03c1 }, /* Greek_rho ρ GREEK SMALL LETTER RHO */ + { 0x07f2, 0x03c3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ + { 0x07f3, 0x03c2 }, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */ + { 0x07f4, 0x03c4 }, /* Greek_tau τ GREEK SMALL LETTER TAU */ + { 0x07f5, 0x03c5 }, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */ + { 0x07f6, 0x03c6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */ + { 0x07f7, 0x03c7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */ + { 0x07f8, 0x03c8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */ + { 0x07f9, 0x03c9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ + { 0x08a1, 0x23b7 }, /* leftradical ⎷ ??? */ + { 0x08a2, 0x250c }, /* topleftradical ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ + { 0x08a3, 0x2500 }, /* horizconnector ─ BOX DRAWINGS LIGHT HORIZONTAL */ + { 0x08a4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */ + { 0x08a5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ + { 0x08a6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ + { 0x08a7, 0x23a1 }, /* topleftsqbracket ⎡ ??? */ + { 0x08a8, 0x23a3 }, /* botleftsqbracket ⎣ ??? */ + { 0x08a9, 0x23a4 }, /* toprightsqbracket ⎤ ??? */ + { 0x08aa, 0x23a6 }, /* botrightsqbracket ⎦ ??? */ + { 0x08ab, 0x239b }, /* topleftparens ⎛ ??? */ + { 0x08ac, 0x239d }, /* botleftparens ⎝ ??? */ + { 0x08ad, 0x239e }, /* toprightparens ⎞ ??? */ + { 0x08ae, 0x23a0 }, /* botrightparens ⎠ ??? */ + { 0x08af, 0x23a8 }, /* leftmiddlecurlybrace ⎨ ??? */ + { 0x08b0, 0x23ac }, /* rightmiddlecurlybrace ⎬ ??? */ +/* 0x08b1 topleftsummation ? ??? */ +/* 0x08b2 botleftsummation ? ??? */ +/* 0x08b3 topvertsummationconnector ? ??? */ +/* 0x08b4 botvertsummationconnector ? ??? */ +/* 0x08b5 toprightsummation ? ??? */ +/* 0x08b6 botrightsummation ? ??? */ +/* 0x08b7 rightmiddlesummation ? ??? */ + { 0x08bc, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ + { 0x08bd, 0x2260 }, /* notequal ≠ NOT EQUAL TO */ + { 0x08be, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ + { 0x08bf, 0x222b }, /* integral ∫ INTEGRAL */ + { 0x08c0, 0x2234 }, /* therefore ∴ THEREFORE */ + { 0x08c1, 0x221d }, /* variation ∝ PROPORTIONAL TO */ + { 0x08c2, 0x221e }, /* infinity ∞ INFINITY */ + { 0x08c5, 0x2207 }, /* nabla ∇ NABLA */ + { 0x08c8, 0x223c }, /* approximate ∼ TILDE OPERATOR */ + { 0x08c9, 0x2243 }, /* similarequal ≃ ASYMPTOTICALLY EQUAL TO */ + { 0x08cd, 0x21d4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ + { 0x08ce, 0x21d2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ + { 0x08cf, 0x2261 }, /* identical ≡ IDENTICAL TO */ + { 0x08d6, 0x221a }, /* radical √ SQUARE ROOT */ + { 0x08da, 0x2282 }, /* includedin ⊂ SUBSET OF */ + { 0x08db, 0x2283 }, /* includes ⊃ SUPERSET OF */ + { 0x08dc, 0x2229 }, /* intersection ∩ INTERSECTION */ + { 0x08dd, 0x222a }, /* union ∪ UNION */ + { 0x08de, 0x2227 }, /* logicaland ∧ LOGICAL AND */ + { 0x08df, 0x2228 }, /* logicalor ∨ LOGICAL OR */ + { 0x08ef, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ + { 0x08f6, 0x0192 }, /* function ƒ LATIN SMALL LETTER F WITH HOOK */ + { 0x08fb, 0x2190 }, /* leftarrow ← LEFTWARDS ARROW */ + { 0x08fc, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */ + { 0x08fd, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */ + { 0x08fe, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */ +/* 0x09df blank ? ??? */ + { 0x09e0, 0x25c6 }, /* soliddiamond ◆ BLACK DIAMOND */ + { 0x09e1, 0x2592 }, /* checkerboard ▒ MEDIUM SHADE */ + { 0x09e2, 0x2409 }, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */ + { 0x09e3, 0x240c }, /* ff ␌ SYMBOL FOR FORM FEED */ + { 0x09e4, 0x240d }, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */ + { 0x09e5, 0x240a }, /* lf ␊ SYMBOL FOR LINE FEED */ + { 0x09e8, 0x2424 }, /* nl ␤ SYMBOL FOR NEWLINE */ + { 0x09e9, 0x240b }, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */ + { 0x09ea, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ + { 0x09eb, 0x2510 }, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */ + { 0x09ec, 0x250c }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ + { 0x09ed, 0x2514 }, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */ + { 0x09ee, 0x253c }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ + { 0x09ef, 0x23ba }, /* horizlinescan1 ⎺ HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ + { 0x09f0, 0x23bb }, /* horizlinescan3 ⎻ HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ + { 0x09f1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ + { 0x09f2, 0x23bc }, /* horizlinescan7 ⎼ HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ + { 0x09f3, 0x23bd }, /* horizlinescan9 ⎽ HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ + { 0x09f4, 0x251c }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ + { 0x09f5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ + { 0x09f6, 0x2534 }, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ + { 0x09f7, 0x252c }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ + { 0x09f8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ + { 0x0aa1, 0x2003 }, /* emspace   EM SPACE */ + { 0x0aa2, 0x2002 }, /* enspace   EN SPACE */ + { 0x0aa3, 0x2004 }, /* em3space   THREE-PER-EM SPACE */ + { 0x0aa4, 0x2005 }, /* em4space   FOUR-PER-EM SPACE */ + { 0x0aa5, 0x2007 }, /* digitspace   FIGURE SPACE */ + { 0x0aa6, 0x2008 }, /* punctspace   PUNCTUATION SPACE */ + { 0x0aa7, 0x2009 }, /* thinspace   THIN SPACE */ + { 0x0aa8, 0x200a }, /* hairspace   HAIR SPACE */ + { 0x0aa9, 0x2014 }, /* emdash — EM DASH */ + { 0x0aaa, 0x2013 }, /* endash – EN DASH */ +/* 0x0aac signifblank ? ??? */ + { 0x0aae, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */ + { 0x0aaf, 0x2025 }, /* doubbaselinedot ‥ TWO DOT LEADER */ + { 0x0ab0, 0x2153 }, /* onethird ⅓ VULGAR FRACTION ONE THIRD */ + { 0x0ab1, 0x2154 }, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */ + { 0x0ab2, 0x2155 }, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */ + { 0x0ab3, 0x2156 }, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */ + { 0x0ab4, 0x2157 }, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */ + { 0x0ab5, 0x2158 }, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */ + { 0x0ab6, 0x2159 }, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */ + { 0x0ab7, 0x215a }, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */ + { 0x0ab8, 0x2105 }, /* careof ℅ CARE OF */ + { 0x0abb, 0x2012 }, /* figdash ‒ FIGURE DASH */ + { 0x0abc, 0x2329 }, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */ +/* 0x0abd decimalpoint ? ??? */ + { 0x0abe, 0x232a }, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */ +/* 0x0abf marker ? ??? */ + { 0x0ac3, 0x215b }, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */ + { 0x0ac4, 0x215c }, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */ + { 0x0ac5, 0x215d }, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */ + { 0x0ac6, 0x215e }, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */ + { 0x0ac9, 0x2122 }, /* trademark ™ TRADE MARK SIGN */ + { 0x0aca, 0x2613 }, /* signaturemark ☓ SALTIRE */ +/* 0x0acb trademarkincircle ? ??? */ + { 0x0acc, 0x25c1 }, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */ + { 0x0acd, 0x25b7 }, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */ + { 0x0ace, 0x25cb }, /* emopencircle ○ WHITE CIRCLE */ + { 0x0acf, 0x25af }, /* emopenrectangle ▯ WHITE VERTICAL RECTANGLE */ + { 0x0ad0, 0x2018 }, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ + { 0x0ad1, 0x2019 }, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ + { 0x0ad2, 0x201c }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ + { 0x0ad3, 0x201d }, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */ + { 0x0ad4, 0x211e }, /* prescription ℞ PRESCRIPTION TAKE */ + { 0x0ad6, 0x2032 }, /* minutes ′ PRIME */ + { 0x0ad7, 0x2033 }, /* seconds ″ DOUBLE PRIME */ + { 0x0ad9, 0x271d }, /* latincross ✝ LATIN CROSS */ +/* 0x0ada hexagram ? ??? */ + { 0x0adb, 0x25ac }, /* filledrectbullet ▬ BLACK RECTANGLE */ + { 0x0adc, 0x25c0 }, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */ + { 0x0add, 0x25b6 }, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */ + { 0x0ade, 0x25cf }, /* emfilledcircle ● BLACK CIRCLE */ + { 0x0adf, 0x25ae }, /* emfilledrect ▮ BLACK VERTICAL RECTANGLE */ + { 0x0ae0, 0x25e6 }, /* enopencircbullet ◦ WHITE BULLET */ + { 0x0ae1, 0x25ab }, /* enopensquarebullet ▫ WHITE SMALL SQUARE */ + { 0x0ae2, 0x25ad }, /* openrectbullet ▭ WHITE RECTANGLE */ + { 0x0ae3, 0x25b3 }, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */ + { 0x0ae4, 0x25bd }, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */ + { 0x0ae5, 0x2606 }, /* openstar ☆ WHITE STAR */ + { 0x0ae6, 0x2022 }, /* enfilledcircbullet • BULLET */ + { 0x0ae7, 0x25aa }, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */ + { 0x0ae8, 0x25b2 }, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */ + { 0x0ae9, 0x25bc }, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */ + { 0x0aea, 0x261c }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ + { 0x0aeb, 0x261e }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ + { 0x0aec, 0x2663 }, /* club ♣ BLACK CLUB SUIT */ + { 0x0aed, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */ + { 0x0aee, 0x2665 }, /* heart ♥ BLACK HEART SUIT */ + { 0x0af0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */ + { 0x0af1, 0x2020 }, /* dagger † DAGGER */ + { 0x0af2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */ + { 0x0af3, 0x2713 }, /* checkmark ✓ CHECK MARK */ + { 0x0af4, 0x2717 }, /* ballotcross ✗ BALLOT X */ + { 0x0af5, 0x266f }, /* musicalsharp ♯ MUSIC SHARP SIGN */ + { 0x0af6, 0x266d }, /* musicalflat ♭ MUSIC FLAT SIGN */ + { 0x0af7, 0x2642 }, /* malesymbol ♂ MALE SIGN */ + { 0x0af8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */ + { 0x0af9, 0x260e }, /* telephone ☎ BLACK TELEPHONE */ + { 0x0afa, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */ + { 0x0afb, 0x2117 }, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */ + { 0x0afc, 0x2038 }, /* caret ‸ CARET */ + { 0x0afd, 0x201a }, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ + { 0x0afe, 0x201e }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ +/* 0x0aff cursor ? ??? */ + { 0x0ba3, 0x003c }, /* leftcaret < LESS-THAN SIGN */ + { 0x0ba6, 0x003e }, /* rightcaret > GREATER-THAN SIGN */ + { 0x0ba8, 0x2228 }, /* downcaret ∨ LOGICAL OR */ + { 0x0ba9, 0x2227 }, /* upcaret ∧ LOGICAL AND */ + { 0x0bc0, 0x00af }, /* overbar ¯ MACRON */ + { 0x0bc2, 0x22a5 }, /* downtack ⊥ UP TACK */ + { 0x0bc3, 0x2229 }, /* upshoe ∩ INTERSECTION */ + { 0x0bc4, 0x230a }, /* downstile ⌊ LEFT FLOOR */ + { 0x0bc6, 0x005f }, /* underbar _ LOW LINE */ + { 0x0bca, 0x2218 }, /* jot ∘ RING OPERATOR */ + { 0x0bcc, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */ + { 0x0bce, 0x22a4 }, /* uptack ⊤ DOWN TACK */ + { 0x0bcf, 0x25cb }, /* circle ○ WHITE CIRCLE */ + { 0x0bd3, 0x2308 }, /* upstile ⌈ LEFT CEILING */ + { 0x0bd6, 0x222a }, /* downshoe ∪ UNION */ + { 0x0bd8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */ + { 0x0bda, 0x2282 }, /* leftshoe ⊂ SUBSET OF */ + { 0x0bdc, 0x22a2 }, /* lefttack ⊢ RIGHT TACK */ + { 0x0bfc, 0x22a3 }, /* righttack ⊣ LEFT TACK */ + { 0x0cdf, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ + { 0x0ce0, 0x05d0 }, /* hebrew_aleph א HEBREW LETTER ALEF */ + { 0x0ce1, 0x05d1 }, /* hebrew_bet ב HEBREW LETTER BET */ + { 0x0ce2, 0x05d2 }, /* hebrew_gimel ג HEBREW LETTER GIMEL */ + { 0x0ce3, 0x05d3 }, /* hebrew_dalet ד HEBREW LETTER DALET */ + { 0x0ce4, 0x05d4 }, /* hebrew_he ה HEBREW LETTER HE */ + { 0x0ce5, 0x05d5 }, /* hebrew_waw ו HEBREW LETTER VAV */ + { 0x0ce6, 0x05d6 }, /* hebrew_zain ז HEBREW LETTER ZAYIN */ + { 0x0ce7, 0x05d7 }, /* hebrew_chet ח HEBREW LETTER HET */ + { 0x0ce8, 0x05d8 }, /* hebrew_tet ט HEBREW LETTER TET */ + { 0x0ce9, 0x05d9 }, /* hebrew_yod י HEBREW LETTER YOD */ + { 0x0cea, 0x05da }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ + { 0x0ceb, 0x05db }, /* hebrew_kaph כ HEBREW LETTER KAF */ + { 0x0cec, 0x05dc }, /* hebrew_lamed ל HEBREW LETTER LAMED */ + { 0x0ced, 0x05dd }, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */ + { 0x0cee, 0x05de }, /* hebrew_mem מ HEBREW LETTER MEM */ + { 0x0cef, 0x05df }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ + { 0x0cf0, 0x05e0 }, /* hebrew_nun ×  HEBREW LETTER NUN */ + { 0x0cf1, 0x05e1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */ + { 0x0cf2, 0x05e2 }, /* hebrew_ayin ×¢ HEBREW LETTER AYIN */ + { 0x0cf3, 0x05e3 }, /* hebrew_finalpe ×£ HEBREW LETTER FINAL PE */ + { 0x0cf4, 0x05e4 }, /* hebrew_pe פ HEBREW LETTER PE */ + { 0x0cf5, 0x05e5 }, /* hebrew_finalzade ×¥ HEBREW LETTER FINAL TSADI */ + { 0x0cf6, 0x05e6 }, /* hebrew_zade צ HEBREW LETTER TSADI */ + { 0x0cf7, 0x05e7 }, /* hebrew_qoph ק HEBREW LETTER QOF */ + { 0x0cf8, 0x05e8 }, /* hebrew_resh ר HEBREW LETTER RESH */ + { 0x0cf9, 0x05e9 }, /* hebrew_shin ש HEBREW LETTER SHIN */ + { 0x0cfa, 0x05ea }, /* hebrew_taw ת HEBREW LETTER TAV */ + { 0x0da1, 0x0e01 }, /* Thai_kokai ก THAI CHARACTER KO KAI */ + { 0x0da2, 0x0e02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ + { 0x0da3, 0x0e03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ + { 0x0da4, 0x0e04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ + { 0x0da5, 0x0e05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ + { 0x0da6, 0x0e06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ + { 0x0da7, 0x0e07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ + { 0x0da8, 0x0e08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ + { 0x0da9, 0x0e09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ + { 0x0daa, 0x0e0a }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ + { 0x0dab, 0x0e0b }, /* Thai_soso ซ THAI CHARACTER SO SO */ + { 0x0dac, 0x0e0c }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ + { 0x0dad, 0x0e0d }, /* Thai_yoying ญ THAI CHARACTER YO YING */ + { 0x0dae, 0x0e0e }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ + { 0x0daf, 0x0e0f }, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */ + { 0x0db0, 0x0e10 }, /* Thai_thothan ฐ THAI CHARACTER THO THAN */ + { 0x0db1, 0x0e11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ + { 0x0db2, 0x0e12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ + { 0x0db3, 0x0e13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */ + { 0x0db4, 0x0e14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */ + { 0x0db5, 0x0e15 }, /* Thai_totao ต THAI CHARACTER TO TAO */ + { 0x0db6, 0x0e16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ + { 0x0db7, 0x0e17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ + { 0x0db8, 0x0e18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */ + { 0x0db9, 0x0e19 }, /* Thai_nonu น THAI CHARACTER NO NU */ + { 0x0dba, 0x0e1a }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ + { 0x0dbb, 0x0e1b }, /* Thai_popla ป THAI CHARACTER PO PLA */ + { 0x0dbc, 0x0e1c }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ + { 0x0dbd, 0x0e1d }, /* Thai_fofa ฝ THAI CHARACTER FO FA */ + { 0x0dbe, 0x0e1e }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ + { 0x0dbf, 0x0e1f }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ + { 0x0dc0, 0x0e20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ + { 0x0dc1, 0x0e21 }, /* Thai_moma ม THAI CHARACTER MO MA */ + { 0x0dc2, 0x0e22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */ + { 0x0dc3, 0x0e23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */ + { 0x0dc4, 0x0e24 }, /* Thai_ru ฤ THAI CHARACTER RU */ + { 0x0dc5, 0x0e25 }, /* Thai_loling ล THAI CHARACTER LO LING */ + { 0x0dc6, 0x0e26 }, /* Thai_lu ฦ THAI CHARACTER LU */ + { 0x0dc7, 0x0e27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ + { 0x0dc8, 0x0e28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */ + { 0x0dc9, 0x0e29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ + { 0x0dca, 0x0e2a }, /* Thai_sosua ส THAI CHARACTER SO SUA */ + { 0x0dcb, 0x0e2b }, /* Thai_hohip ห THAI CHARACTER HO HIP */ + { 0x0dcc, 0x0e2c }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ + { 0x0dcd, 0x0e2d }, /* Thai_oang อ THAI CHARACTER O ANG */ + { 0x0dce, 0x0e2e }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ + { 0x0dcf, 0x0e2f }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ + { 0x0dd0, 0x0e30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */ + { 0x0dd1, 0x0e31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ + { 0x0dd2, 0x0e32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */ + { 0x0dd3, 0x0e33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */ + { 0x0dd4, 0x0e34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */ + { 0x0dd5, 0x0e35 }, /* Thai_saraii ี THAI CHARACTER SARA II */ + { 0x0dd6, 0x0e36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */ + { 0x0dd7, 0x0e37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ + { 0x0dd8, 0x0e38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */ + { 0x0dd9, 0x0e39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */ + { 0x0dda, 0x0e3a }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ +/* 0x0dde Thai_maihanakat_maitho ? ??? */ + { 0x0ddf, 0x0e3f }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ + { 0x0de0, 0x0e40 }, /* Thai_sarae เ THAI CHARACTER SARA E */ + { 0x0de1, 0x0e41 }, /* Thai_saraae แ THAI CHARACTER SARA AE */ + { 0x0de2, 0x0e42 }, /* Thai_sarao โ THAI CHARACTER SARA O */ + { 0x0de3, 0x0e43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ + { 0x0de4, 0x0e44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ + { 0x0de5, 0x0e45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ + { 0x0de6, 0x0e46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ + { 0x0de7, 0x0e47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ + { 0x0de8, 0x0e48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */ + { 0x0de9, 0x0e49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */ + { 0x0dea, 0x0e4a }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ + { 0x0deb, 0x0e4b }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ + { 0x0dec, 0x0e4c }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ + { 0x0ded, 0x0e4d }, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */ + { 0x0df0, 0x0e50 }, /* Thai_leksun ๐ THAI DIGIT ZERO */ + { 0x0df1, 0x0e51 }, /* Thai_leknung ๑ THAI DIGIT ONE */ + { 0x0df2, 0x0e52 }, /* Thai_leksong ๒ THAI DIGIT TWO */ + { 0x0df3, 0x0e53 }, /* Thai_leksam ๓ THAI DIGIT THREE */ + { 0x0df4, 0x0e54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */ + { 0x0df5, 0x0e55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */ + { 0x0df6, 0x0e56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */ + { 0x0df7, 0x0e57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ + { 0x0df8, 0x0e58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ + { 0x0df9, 0x0e59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */ + { 0x0ea1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ + { 0x0ea2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ + { 0x0ea3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ + { 0x0ea4, 0x3134 }, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */ + { 0x0ea5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ + { 0x0ea6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ + { 0x0ea7, 0x3137 }, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */ + { 0x0ea8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ + { 0x0ea9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ + { 0x0eaa, 0x313a }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ + { 0x0eab, 0x313b }, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */ + { 0x0eac, 0x313c }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ + { 0x0ead, 0x313d }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ + { 0x0eae, 0x313e }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ + { 0x0eaf, 0x313f }, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */ + { 0x0eb0, 0x3140 }, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */ + { 0x0eb1, 0x3141 }, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */ + { 0x0eb2, 0x3142 }, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */ + { 0x0eb3, 0x3143 }, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */ + { 0x0eb4, 0x3144 }, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */ + { 0x0eb5, 0x3145 }, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */ + { 0x0eb6, 0x3146 }, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */ + { 0x0eb7, 0x3147 }, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */ + { 0x0eb8, 0x3148 }, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */ + { 0x0eb9, 0x3149 }, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */ + { 0x0eba, 0x314a }, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */ + { 0x0ebb, 0x314b }, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */ + { 0x0ebc, 0x314c }, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */ + { 0x0ebd, 0x314d }, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */ + { 0x0ebe, 0x314e }, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */ + { 0x0ebf, 0x314f }, /* Hangul_A ㅏ HANGUL LETTER A */ + { 0x0ec0, 0x3150 }, /* Hangul_AE ㅐ HANGUL LETTER AE */ + { 0x0ec1, 0x3151 }, /* Hangul_YA ㅑ HANGUL LETTER YA */ + { 0x0ec2, 0x3152 }, /* Hangul_YAE ㅒ HANGUL LETTER YAE */ + { 0x0ec3, 0x3153 }, /* Hangul_EO ㅓ HANGUL LETTER EO */ + { 0x0ec4, 0x3154 }, /* Hangul_E ㅔ HANGUL LETTER E */ + { 0x0ec5, 0x3155 }, /* Hangul_YEO ㅕ HANGUL LETTER YEO */ + { 0x0ec6, 0x3156 }, /* Hangul_YE ㅖ HANGUL LETTER YE */ + { 0x0ec7, 0x3157 }, /* Hangul_O ㅗ HANGUL LETTER O */ + { 0x0ec8, 0x3158 }, /* Hangul_WA ㅘ HANGUL LETTER WA */ + { 0x0ec9, 0x3159 }, /* Hangul_WAE ㅙ HANGUL LETTER WAE */ + { 0x0eca, 0x315a }, /* Hangul_OE ㅚ HANGUL LETTER OE */ + { 0x0ecb, 0x315b }, /* Hangul_YO ㅛ HANGUL LETTER YO */ + { 0x0ecc, 0x315c }, /* Hangul_U ㅜ HANGUL LETTER U */ + { 0x0ecd, 0x315d }, /* Hangul_WEO ㅝ HANGUL LETTER WEO */ + { 0x0ece, 0x315e }, /* Hangul_WE ㅞ HANGUL LETTER WE */ + { 0x0ecf, 0x315f }, /* Hangul_WI ㅟ HANGUL LETTER WI */ + { 0x0ed0, 0x3160 }, /* Hangul_YU ㅠ HANGUL LETTER YU */ + { 0x0ed1, 0x3161 }, /* Hangul_EU ㅡ HANGUL LETTER EU */ + { 0x0ed2, 0x3162 }, /* Hangul_YI ㅢ HANGUL LETTER YI */ + { 0x0ed3, 0x3163 }, /* Hangul_I ㅣ HANGUL LETTER I */ + { 0x0ed4, 0x11a8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ + { 0x0ed5, 0x11a9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ + { 0x0ed6, 0x11aa }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ + { 0x0ed7, 0x11ab }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ + { 0x0ed8, 0x11ac }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ + { 0x0ed9, 0x11ad }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ + { 0x0eda, 0x11ae }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ + { 0x0edb, 0x11af }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ + { 0x0edc, 0x11b0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ + { 0x0edd, 0x11b1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ + { 0x0ede, 0x11b2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ + { 0x0edf, 0x11b3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ + { 0x0ee0, 0x11b4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ + { 0x0ee1, 0x11b5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ + { 0x0ee2, 0x11b6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ + { 0x0ee3, 0x11b7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ + { 0x0ee4, 0x11b8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ + { 0x0ee5, 0x11b9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ + { 0x0ee6, 0x11ba }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ + { 0x0ee7, 0x11bb }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ + { 0x0ee8, 0x11bc }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ + { 0x0ee9, 0x11bd }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ + { 0x0eea, 0x11be }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ + { 0x0eeb, 0x11bf }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ + { 0x0eec, 0x11c0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ + { 0x0eed, 0x11c1 }, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */ + { 0x0eee, 0x11c2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ + { 0x0eef, 0x316d }, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */ + { 0x0ef0, 0x3171 }, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */ + { 0x0ef1, 0x3178 }, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */ + { 0x0ef2, 0x317f }, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */ + { 0x0ef3, 0x3181 }, /* Hangul_KkogjiDalrinIeung ㆁ HANGUL LETTER YESIEUNG */ + { 0x0ef4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ + { 0x0ef5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ + { 0x0ef6, 0x318d }, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */ + { 0x0ef7, 0x318e }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ + { 0x0ef8, 0x11eb }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ + { 0x0ef9, 0x11f0 }, /* Hangul_J_KkogjiDalrinIeung ᇰ HANGUL JONGSEONG YESIEUNG */ + { 0x0efa, 0x11f9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ + { 0x0eff, 0x20a9 }, /* Korean_Won ₩ WON SIGN */ + { 0x13a4, 0x20ac }, /* Euro € EURO SIGN */ + { 0x13bc, 0x0152 }, /* OE Œ LATIN CAPITAL LIGATURE OE */ + { 0x13bd, 0x0153 }, /* oe œ LATIN SMALL LIGATURE OE */ + { 0x13be, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ + { 0x20ac, 0x20ac }, /* EuroSign € EURO SIGN */ +}; + +long keysym2ucs(KeySym keysym) +{ + int min = 0; + int max = sizeof(keysymtab) / sizeof(struct codepair) - 1; + int mid; + + /* first check for Latin-1 characters (1:1 mapping) */ + if ((keysym >= 0x0020 && keysym <= 0x007e) || + (keysym >= 0x00a0 && keysym <= 0x00ff)) + return keysym; + + /* also check for directly encoded 24-bit UCS characters */ + if ((keysym & 0xff000000) == 0x01000000) + return keysym & 0x00ffffff; + + /* binary search in table */ + while (max >= min) { + mid = (min + max) / 2; + if (keysymtab[mid].keysym < keysym) + min = mid + 1; + else if (keysymtab[mid].keysym > keysym) + max = mid - 1; + else { + /* found it */ + return keysymtab[mid].ucs; + } + } + + /* no matching Unicode value found */ + return -1; +} diff --git a/misc/source/darkplaces-src/lhfont.h b/misc/source/darkplaces-src/lhfont.h new file mode 100644 index 00000000..e6511809 --- /dev/null +++ b/misc/source/darkplaces-src/lhfont.h @@ -0,0 +1,106 @@ +0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x08,0x08,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x8B,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8B,0xFF,0x89,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x9D,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x8A,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x89,0xFF, +0x87,0x00,0x85,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x84,0xFF,0x88,0x00,0x8B,0xFF,0x88,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x9B,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x99,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF, +0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8A,0x00,0x82,0xFF,0x8D,0x00, +0x83,0xFF,0x8D,0x00,0x82,0xFF,0x96,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x94,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x95,0x00,0x84,0xFF,0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF, +0x87,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x99,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x9B,0x00,0x83,0xFF,0x85,0x00,0xFF,0x00,0xB6,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x93,0x00,0xFF,0x00,0xB7,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x92,0x00,0xFF,0x00,0xE0,0x00,0x86,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xFF,0x00,0xE1,0x00,0x85,0xFF, +0x81,0x00,0x83,0xFF,0x91,0x00,0xF3,0x00,0x85,0xFF,0xFF,0x00,0x85,0x00,0xF3,0x00,0x86,0xFF,0xFF,0x00,0x84,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF, +0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x86,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x81,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF, +0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x84,0xFF,0x90,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x81,0x00,0x84,0x00,0x84,0xFF,0x89,0x00,0x88,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x89,0xFF,0x84,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0xA4,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x88,0x00,0x8B,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x82,0x00,0x85,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0xA5,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF, +0x87,0x00,0x87,0xFF,0x83,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x94,0x00,0x86,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x95,0x00,0x85,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x97,0x00,0x85,0xFF,0xFF,0x00,0xE1,0x00,0x96,0x00,0x86,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8E,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x91,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF, +0x89,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8F,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0x8E,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00, +0x81,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x91,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x92,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF, +0x8A,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x93,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x84,0xFF,0x8C,0x00,0x87,0xFF,0x86,0x00,0x87,0xFF,0x94,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x95,0x00,0xFF,0x00,0xE4,0x00,0x83,0xFF,0x96,0x00,0xFF,0x00,0xE5,0x00,0x81,0xFF,0x97,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x8B,0xFF,0x83,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x82,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8A,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x81,0x00, +0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00, +0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, +0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x00,0x00,0x86,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x84,0x00,0x82,0xFF,0x83,0x00,0x82,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00, +0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x81,0xFF,0x83,0x00,0x81,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x86,0xFF,0x00,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x82,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF, +0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB3,0x00,0x83,0xFF,0xC7,0x00,0xFF,0x00,0xB3,0x00,0x84,0xFF,0xC6,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00, +0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x82,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x95,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x94,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00, +0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00, +0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x83,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x86,0xFF,0x82,0x00,0x82,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00, +0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x81,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF, +0x97,0x00,0x84,0xFF,0x88,0x00,0x8A,0xFF,0x82,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x89,0x00,0x89,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC5,0x00,0x83,0xFF,0xB5,0x00,0xFF,0x00,0xC5,0x00,0x84,0xFF,0xB4,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x95,0x00,0x83,0xFF, +0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x84,0x00,0x84,0xFF,0x84,0x00,0x83,0xFF,0x82,0x00,0x8C,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0xB0,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0xB0,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x85,0x00,0x84,0xFF, +0x82,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x97,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x86,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x96,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x94,0x00,0x84,0xFF,0x87,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x87,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x95,0x00,0x83,0xFF, +0x8F,0x00,0x83,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x95,0x00,0x84,0xFF,0x86,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x84,0xFF,0x86,0x00,0x88,0xFF,0x00,0x00,0x82,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBA,0x00,0x84,0xFF,0x85,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x8D,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x82,0x00,0x81,0xFF,0x88,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBB,0x00,0x84,0xFF, +0x84,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBC,0x00,0x84,0xFF,0x83,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBD,0x00,0x84,0xFF, +0x82,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0xDD,0x00,0x84,0xFF,0x81,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0xDF,0x00,0x83,0xFF,0x81,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB6,0x00, +0x87,0xFF,0x9A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0xE6,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB7,0x00,0x85,0xFF,0x9C,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xE7,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0xC3,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF, +0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x99,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x96,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF, +0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x94,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF, +0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x95,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF, +0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x98,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xB8,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB2,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xB1,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0x81,0x00,0xAB,0xFF, +0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF,0xA7,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00, +0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00, +0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x82,0x00,0xA9,0xFF,0x83,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF, +0xA7,0x00,0x83,0x00,0xA7,0xFF,0x84,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0xB1,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0xB2,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xFF,0x00,0xFF,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x8B,0x00, +0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8B,0xFF,0x89,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x9D,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x8A,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x84,0xFF,0x88,0x00,0x8B,0xFF,0x88,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x9B,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00, +0x89,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x99,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF, +0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8A,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x96,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x94,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x95,0x00,0x84,0xFF,0x87,0x00, +0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x99,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF, +0x8B,0x00,0x83,0xFF,0x9B,0x00,0x83,0xFF,0x85,0x00,0xFF,0x00,0xB6,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x93,0x00,0xFF,0x00,0xB7,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x92,0x00,0xFF,0x00,0xE0,0x00,0x86,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xFF,0x00,0xE1,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xF3,0x00,0x85,0xFF,0xFF,0x00,0x85,0x00,0xF3,0x00,0x86,0xFF,0xFF,0x00,0x84,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x86,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x84,0x00,0x89,0xFF,0x82,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x84,0xFF,0x90,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x84,0x00,0x84,0xFF,0x89,0x00,0x88,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x89,0xFF,0x84,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0xA4,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x85,0x00, +0x83,0xFF,0x88,0x00,0x8B,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x82,0x00,0x85,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0xA5,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x83,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x94,0x00,0x86,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF, +0x95,0x00,0x85,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x97,0x00,0x85,0xFF,0xFF,0x00,0xE1,0x00,0x96,0x00,0x86,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF, +0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8E,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x91,0x00, +0x84,0xFF,0x88,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00, +0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8F,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0x8E,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x81,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF, +0x86,0x00,0x85,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x91,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x92,0x00, +0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x93,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00, +0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x84,0xFF,0x8C,0x00,0x87,0xFF,0x86,0x00,0x87,0xFF,0x94,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF, +0x87,0x00,0x85,0xFF,0x95,0x00,0xFF,0x00,0xE4,0x00,0x83,0xFF,0x96,0x00,0xFF,0x00,0xE5,0x00,0x81,0xFF,0x97,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x82,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8A,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF, +0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x00,0x00,0x86,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00, +0x85,0xFF,0x84,0x00,0x82,0xFF,0x83,0x00,0x82,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x81,0xFF,0x83,0x00,0x81,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF, +0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x86,0xFF,0x00,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x82,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00, +0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF, +0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB3,0x00,0x83,0xFF,0xC7,0x00,0xFF,0x00,0xB3,0x00,0x84,0xFF,0xC6,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x82,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF, +0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x95,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, +0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x94,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x83,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x86,0xFF,0x82,0x00,0x82,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x87,0xFF, +0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x81,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x88,0x00,0x8A,0xFF,0x82,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x89,0x00,0x89,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC5,0x00,0x83,0xFF,0xB5,0x00,0xFF,0x00, +0xC5,0x00,0x84,0xFF,0xB4,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x84,0x00,0x84,0xFF,0x84,0x00,0x83,0xFF,0x82,0x00,0x8C,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF, +0x89,0x00,0x84,0xFF,0x8A,0x00,0xB0,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0xB0,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x85,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x97,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x87,0x00, +0x81,0xFF,0x81,0x00,0x81,0xFF,0x86,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x96,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x94,0x00,0x84,0xFF,0x87,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x87,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x95,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x95,0x00,0x84,0xFF,0x86,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x84,0xFF,0x86,0x00,0x88,0xFF,0x00,0x00,0x82,0xFF,0x88,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBA,0x00,0x84,0xFF,0x85,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x8D,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x82,0x00,0x81,0xFF,0x88,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBB,0x00,0x84,0xFF,0x84,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBC,0x00,0x84,0xFF,0x83,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBD,0x00,0x84,0xFF,0x82,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00, +0x84,0xFF,0xDD,0x00,0x84,0xFF,0x81,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0xDF,0x00,0x83,0xFF,0x81,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB6,0x00,0x87,0xFF,0x9A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0xE6,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB7,0x00,0x85,0xFF,0x9C,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xE7,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00, +0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0xC3,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x99,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x96,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x94,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF, +0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x95,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x98,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xB8,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00, +0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC2,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xC1,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00, +0x85,0xFF,0xA7,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF, +0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00, +0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x92,0x00,0xA9,0xFF,0x83,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF,0xA7,0x00,0x93,0x00,0xA7,0xFF,0x84,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0xC1,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0xC2,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xFF,0x00, +0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0x52,0x55,0x45,0x56,0x49,0x53,0x49,0x4F,0x4E,0x2D,0x58,0x46,0x49,0x4C,0x45,0x2E,0x00 diff --git a/misc/source/darkplaces-src/lhnet.c b/misc/source/darkplaces-src/lhnet.c new file mode 100644 index 00000000..7f9664c0 --- /dev/null +++ b/misc/source/darkplaces-src/lhnet.c @@ -0,0 +1,1389 @@ + +// Written by Forest Hale 2003-06-15 and placed into public domain. + +#ifdef WIN32 +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +# ifdef SUPPORTIPV6 +// Windows XP or higher is required for getaddrinfo, but the inclusion of wspiapi provides fallbacks for older versions +# define _WIN32_WINNT 0x0501 +# endif +# include +# include +# ifdef USE_WSPIAPI_H +# include +# endif +#endif + +#ifndef STANDALONETEST +#include "quakedef.h" +#endif + +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SUPPORTIPV6 +#include +#endif +#endif + +#ifdef __MORPHOS__ +#include +#endif + +// for Z_Malloc/Z_Free in quake +#ifndef STANDALONETEST +#include "zone.h" +#include "sys.h" +#include "netconn.h" +#else +#define Con_Print printf +#define Con_Printf printf +#define Z_Malloc malloc +#define Z_Free free +#endif + +#include "lhnet.h" + +#if defined(WIN32) +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ECONNREFUSED WSAECONNREFUSED + +#define SOCKETERRNO WSAGetLastError() + +#define IOC_VENDOR 0x18000000 +#define _WSAIOW(x,y) (IOC_IN|(x)|(y)) +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) + +#define SOCKLEN_T int +#elif defined(__MORPHOS__) +#define ioctlsocket IoctlSocket +#define closesocket CloseSocket +#define SOCKETERRNO Errno() + +#define SOCKLEN_T int +#else +#define ioctlsocket ioctl +#define closesocket close +#define SOCKETERRNO errno + +#define SOCKLEN_T socklen_t +#endif + +#ifdef MSG_DONTWAIT +#define LHNET_RECVFROM_FLAGS MSG_DONTWAIT +#define LHNET_SENDTO_FLAGS 0 +#else +#define LHNET_RECVFROM_FLAGS 0 +#define LHNET_SENDTO_FLAGS 0 +#endif + +typedef struct lhnetaddressnative_s +{ + lhnetaddresstype_t addresstype; + int port; + union + { + struct sockaddr sock; + struct sockaddr_in in; +#ifdef SUPPORTIPV6 + struct sockaddr_in6 in6; +#endif + } + addr; +} +lhnetaddressnative_t; + +// to make LHNETADDRESS_FromString resolve repeated hostnames faster, cache them +#define MAX_NAMECACHE 64 +static struct namecache_s +{ + lhnetaddressnative_t address; + double expirationtime; + char name[64]; +} +namecache[MAX_NAMECACHE]; +static int namecacheposition = 0; + +int LHNETADDRESS_FromPort(lhnetaddress_t *vaddress, lhnetaddresstype_t addresstype, int port) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + if (!address) + return 0; + switch(addresstype) + { + default: + break; + case LHNETADDRESSTYPE_LOOP: + // local:port (loopback) + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_LOOP; + address->port = port; + return 1; + case LHNETADDRESSTYPE_INET4: + // 0.0.0.0:port (INADDR_ANY, binds to all interfaces) + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = port; + address->addr.in.sin_family = AF_INET; + address->addr.in.sin_port = htons((unsigned short)port); + return 1; +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: + // [0:0:0:0:0:0:0:0]:port (IN6ADDR_ANY, binds to all interfaces) + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_INET6; + address->port = port; + address->addr.in6.sin6_family = AF_INET6; + address->addr.in6.sin6_port = htons((unsigned short)port); + return 1; +#endif + } + return 0; +} + +#ifdef SUPPORTIPV6 +int LHNETADDRESS_Resolve(lhnetaddressnative_t *address, const char *name, int port) +{ + char port_buff [16]; + struct addrinfo hints; + struct addrinfo* addrinf; + int err; + + dpsnprintf (port_buff, sizeof (port_buff), "%d", port); + port_buff[sizeof (port_buff) - 1] = '\0'; + + memset(&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + //hints.ai_flags = AI_PASSIVE; + + err = getaddrinfo(name, port_buff, &hints, &addrinf); + if (err != 0 || addrinf == NULL) + return 0; + if (addrinf->ai_addr->sa_family != AF_INET6 && addrinf->ai_addr->sa_family != AF_INET) + { + freeaddrinfo (addrinf); + return 0; + } + + // great it worked + if (addrinf->ai_addr->sa_family == AF_INET6) + { + address->addresstype = LHNETADDRESSTYPE_INET6; + memcpy(&address->addr.in6, addrinf->ai_addr, sizeof(address->addr.in6)); + } + else + { + address->addresstype = LHNETADDRESSTYPE_INET4; + memcpy(&address->addr.in, addrinf->ai_addr, sizeof(address->addr.in)); + } + address->port = port; + + freeaddrinfo (addrinf); + return 1; +} + +int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int defaultport) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + int i, port, d1, d2, d3, d4, resolved; + size_t namelen; + unsigned char *a; + char name[128]; +#ifdef STANDALONETEST + char string2[128]; +#endif + const char* addr_start; + const char* addr_end = NULL; + const char* port_name = NULL; + int addr_family = AF_UNSPEC; + + if (!address || !string || !*string) + return 0; + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_NONE; + port = 0; + + // If it's a bracketed IPv6 address + if (string[0] == '[') + { + const char* end_bracket = strchr(string, ']'); + + if (end_bracket == NULL) + return 0; + + if (end_bracket[1] == ':') + port_name = end_bracket + 2; + else if (end_bracket[1] != '\0') + return 0; + + addr_family = AF_INET6; + addr_start = &string[1]; + addr_end = end_bracket; + } + else + { + const char* first_colon; + + addr_start = string; + + // If it's a numeric non-bracket IPv6 address (-> no port), + // or it's a numeric IPv4 address, or a name, with a port + first_colon = strchr(string, ':'); + if (first_colon != NULL) + { + const char* last_colon = strrchr(first_colon + 1, ':'); + + // If it's an numeric IPv4 address, or a name, with a port + if (last_colon == NULL) + { + addr_end = first_colon; + port_name = first_colon + 1; + } + else + addr_family = AF_INET6; + } + } + + if (addr_end != NULL) + namelen = addr_end - addr_start; + else + namelen = strlen (addr_start); + + if (namelen >= sizeof(name)) + namelen = sizeof(name) - 1; + memcpy (name, addr_start, namelen); + name[namelen] = 0; + + if (port_name) + port = atoi(port_name); + + if (port == 0) + port = defaultport; + + // handle loopback + if (!strcmp(name, "local")) + { + address->addresstype = LHNETADDRESSTYPE_LOOP; + address->port = port; + return 1; + } + // try to parse as dotted decimal ipv4 address first + // note this supports partial ip addresses + d1 = d2 = d3 = d4 = 0; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + if (addr_family != AF_INET6 && + sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4) >= 1 && (unsigned int)d1 < 256 && (unsigned int)d2 < 256 && (unsigned int)d3 < 256 && (unsigned int)d4 < 256) + { + // parsed a valid ipv4 address + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = port; + address->addr.in.sin_family = AF_INET; + address->addr.in.sin_port = htons((unsigned short)port); + a = (unsigned char *)&address->addr.in.sin_addr; + a[0] = d1; + a[1] = d2; + a[2] = d3; + a[3] = d4; +#ifdef STANDALONETEST + LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); + printf("manual parsing of ipv4 dotted decimal address \"%s\" successful: %s\n", string, string2); +#endif + return 1; + } + for (i = 0;i < MAX_NAMECACHE;i++) + if (!strcmp(namecache[i].name, name)) + break; +#ifdef STANDALONETEST + if (i < MAX_NAMECACHE) +#else + if (i < MAX_NAMECACHE && realtime < namecache[i].expirationtime) +#endif + { + *address = namecache[i].address; + address->port = port; + if (address->addresstype == LHNETADDRESSTYPE_INET6) + { + address->addr.in6.sin6_port = htons((unsigned short)port); + return 1; + } + else if (address->addresstype == LHNETADDRESSTYPE_INET4) + { + address->addr.in.sin_port = htons((unsigned short)port); + return 1; + } + return 0; + } + + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; +#ifndef STANDALONETEST + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours +#endif + + // try resolving the address (handles dns and other ip formats) + resolved = LHNETADDRESS_Resolve(address, name, port); + if (resolved) + { +#ifdef STANDALONETEST + const char *protoname; + + switch (address->addresstype) + { + case LHNETADDRESSTYPE_INET6: + protoname = "ipv6"; + break; + case LHNETADDRESSTYPE_INET4: + protoname = "ipv4"; + break; + default: + protoname = "UNKNOWN"; + break; + } + LHNETADDRESS_ToString(vaddress, string2, sizeof(string2), 1); + Con_Printf("LHNETADDRESS_Resolve(\"%s\") returned %s address %s\n", string, protoname, string2); +#endif + namecache[namecacheposition].address = *address; + } + else + { +#ifdef STANDALONETEST + printf("name resolution failed on address \"%s\"\n", name); +#endif + namecache[namecacheposition].address.addresstype = LHNETADDRESSTYPE_NONE; + } + + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; + return resolved; +} +#else +int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int defaultport) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + int i, port, namelen, d1, d2, d3, d4; + struct hostent *hostentry; + unsigned char *a; + const char *colon; + char name[128]; +#ifdef STANDALONETEST + char string2[128]; +#endif + if (!address || !string || !*string) + return 0; + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_NONE; + port = 0; + colon = strrchr(string, ':'); + if (colon && (colon == strchr(string, ':') || (string[0] == '[' && colon - string > 0 && colon[-1] == ']'))) + // EITHER: colon is the ONLY colon OR: colon comes after [...] delimited IPv6 address + // fixes misparsing of IPv6 addresses without port + { + port = atoi(colon + 1); + } + else + colon = string + strlen(string); + if (port == 0) + port = defaultport; + namelen = colon - string; + if (namelen > 127) + namelen = 127; + if (string[0] == '[' && namelen > 0 && string[namelen-1] == ']') // ipv6 + { + string++; + namelen -= 2; + } + memcpy(name, string, namelen); + name[namelen] = 0; + // handle loopback + if (!strcmp(name, "local")) + { + address->addresstype = LHNETADDRESSTYPE_LOOP; + address->port = port; + return 1; + } + // try to parse as dotted decimal ipv4 address first + // note this supports partial ip addresses + d1 = d2 = d3 = d4 = 0; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + if (sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4) >= 1 && (unsigned int)d1 < 256 && (unsigned int)d2 < 256 && (unsigned int)d3 < 256 && (unsigned int)d4 < 256) + { + // parsed a valid ipv4 address + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = port; + address->addr.in.sin_family = AF_INET; + address->addr.in.sin_port = htons((unsigned short)port); + a = (unsigned char *)&address->addr.in.sin_addr; + a[0] = d1; + a[1] = d2; + a[2] = d3; + a[3] = d4; +#ifdef STANDALONETEST + LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); + printf("manual parsing of ipv4 dotted decimal address \"%s\" successful: %s\n", string, string2); +#endif + return 1; + } + for (i = 0;i < MAX_NAMECACHE;i++) + if (!strcmp(namecache[i].name, name)) + break; +#ifdef STANDALONETEST + if (i < MAX_NAMECACHE) +#else + if (i < MAX_NAMECACHE && realtime < namecache[i].expirationtime) +#endif + { + *address = namecache[i].address; + address->port = port; + if (address->addresstype == LHNETADDRESSTYPE_INET6) + { +#ifdef SUPPORTIPV6 + address->addr.in6.sin6_port = htons((unsigned short)port); + return 1; +#endif + } + else if (address->addresstype == LHNETADDRESSTYPE_INET4) + { + address->addr.in.sin_port = htons((unsigned short)port); + return 1; + } + return 0; + } + // try gethostbyname (handles dns and other ip formats) + hostentry = gethostbyname(name); + if (hostentry) + { + if (hostentry->h_addrtype == AF_INET6) + { +#ifdef SUPPORTIPV6 + // great it worked + address->addresstype = LHNETADDRESSTYPE_INET6; + address->port = port; + address->addr.in6.sin6_family = hostentry->h_addrtype; + address->addr.in6.sin6_port = htons((unsigned short)port); + memcpy(&address->addr.in6.sin6_addr, hostentry->h_addr_list[0], sizeof(address->addr.in6.sin6_addr)); + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; +#ifndef STANDALONETEST + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours +#endif + namecache[namecacheposition].address = *address; + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; +#ifdef STANDALONETEST + LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); + printf("gethostbyname(\"%s\") returned ipv6 address %s\n", string, string2); +#endif + return 1; +#endif + } + else if (hostentry->h_addrtype == AF_INET) + { + // great it worked + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = port; + address->addr.in.sin_family = hostentry->h_addrtype; + address->addr.in.sin_port = htons((unsigned short)port); + memcpy(&address->addr.in.sin_addr, hostentry->h_addr_list[0], sizeof(address->addr.in.sin_addr)); + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; +#ifndef STANDALONETEST + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours +#endif + namecache[namecacheposition].address = *address; + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; +#ifdef STANDALONETEST + LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); + printf("gethostbyname(\"%s\") returned ipv4 address %s\n", string, string2); +#endif + return 1; + } + } +#ifdef STANDALONETEST + printf("gethostbyname failed on address \"%s\"\n", name); +#endif + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; +#ifndef STANDALONETEST + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours +#endif + namecache[namecacheposition].address.addresstype = LHNETADDRESSTYPE_NONE; + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; + return 0; +} +#endif + +int LHNETADDRESS_ToString(const lhnetaddress_t *vaddress, char *string, int stringbuffersize, int includeport) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + const unsigned char *a; + *string = 0; + if (!address || !string || stringbuffersize < 1) + return 0; + switch(address->addresstype) + { + default: + break; + case LHNETADDRESSTYPE_LOOP: + if (includeport) + { + if (stringbuffersize >= 12) + { + dpsnprintf(string, stringbuffersize, "local:%d", address->port); + return 1; + } + } + else + { + if (stringbuffersize >= 6) + { + memcpy(string, "local", 6); + return 1; + } + } + break; + case LHNETADDRESSTYPE_INET4: + a = (const unsigned char *)(&address->addr.in.sin_addr); + if (includeport) + { + if (stringbuffersize >= 22) + { + dpsnprintf(string, stringbuffersize, "%d.%d.%d.%d:%d", a[0], a[1], a[2], a[3], address->port); + return 1; + } + } + else + { + if (stringbuffersize >= 16) + { + dpsnprintf(string, stringbuffersize, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]); + return 1; + } + } + break; +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: + a = (const unsigned char *)(&address->addr.in6.sin6_addr); + if (includeport) + { + if (stringbuffersize >= 88) + { + dpsnprintf(string, stringbuffersize, "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", a[0] * 256 + a[1], a[2] * 256 + a[3], a[4] * 256 + a[5], a[6] * 256 + a[7], a[8] * 256 + a[9], a[10] * 256 + a[11], a[12] * 256 + a[13], a[14] * 256 + a[15], address->port); + return 1; + } + } + else + { + if (stringbuffersize >= 80) + { + dpsnprintf(string, stringbuffersize, "%x:%x:%x:%x:%x:%x:%x:%x", a[0] * 256 + a[1], a[2] * 256 + a[3], a[4] * 256 + a[5], a[6] * 256 + a[7], a[8] * 256 + a[9], a[10] * 256 + a[11], a[12] * 256 + a[13], a[14] * 256 + a[15]); + return 1; + } + } + break; +#endif + } + return 0; +} + +int LHNETADDRESS_GetAddressType(const lhnetaddress_t *address) +{ + if (address) + return address->addresstype; + else + return LHNETADDRESSTYPE_NONE; +} + +const char *LHNETADDRESS_GetInterfaceName(const lhnetaddress_t *vaddress) +{ +#ifdef SUPPORTIPV6 + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + + if (address && address->addresstype == LHNETADDRESSTYPE_INET6) + { +#ifndef _WIN32 + + static char ifname [IF_NAMESIZE]; + + if (if_indextoname(address->addr.in6.sin6_scope_id, ifname) == ifname) + return ifname; + +#else + + // The Win32 API doesn't have if_indextoname() until Windows Vista, + // but luckily it just uses the interface ID as the interface name + + static char ifname [16]; + + if (dpsnprintf(ifname, sizeof(ifname), "%lu", address->addr.in6.sin6_scope_id) > 0) + return ifname; + +#endif + } +#endif + + return NULL; +} + +int LHNETADDRESS_GetPort(const lhnetaddress_t *address) +{ + if (!address) + return -1; + return address->port; +} + +int LHNETADDRESS_SetPort(lhnetaddress_t *vaddress, int port) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + if (!address) + return 0; + address->port = port; + switch(address->addresstype) + { + case LHNETADDRESSTYPE_LOOP: + return 1; + case LHNETADDRESSTYPE_INET4: + address->addr.in.sin_port = htons((unsigned short)port); + return 1; +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: + address->addr.in6.sin6_port = htons((unsigned short)port); + return 1; +#endif + default: + return 0; + } +} + +int LHNETADDRESS_Compare(const lhnetaddress_t *vaddress1, const lhnetaddress_t *vaddress2) +{ + lhnetaddressnative_t *address1 = (lhnetaddressnative_t *)vaddress1; + lhnetaddressnative_t *address2 = (lhnetaddressnative_t *)vaddress2; + if (!address1 || !address2) + return 1; + if (address1->addresstype != address2->addresstype) + return 1; + switch(address1->addresstype) + { + case LHNETADDRESSTYPE_LOOP: + if (address1->port != address2->port) + return -1; + return 0; + case LHNETADDRESSTYPE_INET4: + if (address1->addr.in.sin_family != address2->addr.in.sin_family) + return 1; + if (memcmp(&address1->addr.in.sin_addr, &address2->addr.in.sin_addr, sizeof(address1->addr.in.sin_addr))) + return 1; + if (address1->port != address2->port) + return -1; + return 0; +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: + if (address1->addr.in6.sin6_family != address2->addr.in6.sin6_family) + return 1; + if (memcmp(&address1->addr.in6.sin6_addr, &address2->addr.in6.sin6_addr, sizeof(address1->addr.in6.sin6_addr))) + return 1; + if (address1->port != address2->port) + return -1; + return 0; +#endif + default: + return 1; + } +} + +typedef struct lhnetpacket_s +{ + void *data; + int length; + int sourceport; + int destinationport; + time_t timeout; +#ifndef STANDALONETEST + double sentdoubletime; +#endif + struct lhnetpacket_s *next, *prev; +} +lhnetpacket_t; + +static int lhnet_active; +static lhnetsocket_t lhnet_socketlist; +static lhnetpacket_t lhnet_packetlist; +#ifdef WIN32 +static int lhnet_didWSAStartup = 0; +static WSADATA lhnet_winsockdata; +#endif + +void LHNET_Init(void) +{ + if (lhnet_active) + return; + lhnet_socketlist.next = lhnet_socketlist.prev = &lhnet_socketlist; + lhnet_packetlist.next = lhnet_packetlist.prev = &lhnet_packetlist; + lhnet_active = 1; +#ifdef WIN32 + lhnet_didWSAStartup = !WSAStartup(MAKEWORD(1, 1), &lhnet_winsockdata); + if (!lhnet_didWSAStartup) + Con_Print("LHNET_Init: WSAStartup failed, networking disabled\n"); +#endif +} + +void LHNET_Shutdown(void) +{ + lhnetpacket_t *p; + if (!lhnet_active) + return; + while (lhnet_socketlist.next != &lhnet_socketlist) + LHNET_CloseSocket(lhnet_socketlist.next); + while (lhnet_packetlist.next != &lhnet_packetlist) + { + p = lhnet_packetlist.next; + p->prev->next = p->next; + p->next->prev = p->prev; + Z_Free(p); + } +#ifdef WIN32 + if (lhnet_didWSAStartup) + { + lhnet_didWSAStartup = 0; + WSACleanup(); + } +#endif + lhnet_active = 0; +} + +static const char *LHNETPRIVATE_StrError(void) +{ +#ifdef WIN32 + int i = WSAGetLastError(); + switch (i) + { + case WSAEINTR: return "WSAEINTR"; + case WSAEBADF: return "WSAEBADF"; + case WSAEACCES: return "WSAEACCES"; + case WSAEFAULT: return "WSAEFAULT"; + case WSAEINVAL: return "WSAEINVAL"; + case WSAEMFILE: return "WSAEMFILE"; + case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: return "WSAEINPROGRESS"; + case WSAEALREADY: return "WSAEALREADY"; + case WSAENOTSOCK: return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: return "WSAENETDOWN"; + case WSAENETUNREACH: return "WSAENETUNREACH"; + case WSAENETRESET: return "WSAENETRESET"; + case WSAECONNABORTED: return "WSAECONNABORTED"; + case WSAECONNRESET: return "WSAECONNRESET"; + case WSAENOBUFS: return "WSAENOBUFS"; + case WSAEISCONN: return "WSAEISCONN"; + case WSAENOTCONN: return "WSAENOTCONN"; + case WSAESHUTDOWN: return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: return "WSAETIMEDOUT"; + case WSAECONNREFUSED: return "WSAECONNREFUSED"; + case WSAELOOP: return "WSAELOOP"; + case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; + case WSAEHOSTUNREACH: return "WSAEHOSTUNREACH"; + case WSAENOTEMPTY: return "WSAENOTEMPTY"; + case WSAEPROCLIM: return "WSAEPROCLIM"; + case WSAEUSERS: return "WSAEUSERS"; + case WSAEDQUOT: return "WSAEDQUOT"; + case WSAESTALE: return "WSAESTALE"; + case WSAEREMOTE: return "WSAEREMOTE"; + case WSAEDISCON: return "WSAEDISCON"; + case 0: return "no error"; + default: return "unknown WSAE error"; + } +#else + return strerror(errno); +#endif +} + +void LHNET_SleepUntilPacket_Microseconds(int microseconds) +{ +#ifdef FD_SET + fd_set fdreadset; + struct timeval tv; + int lastfd; + lhnetsocket_t *s; + FD_ZERO(&fdreadset); + lastfd = 0; + for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) + { + if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6) + { + if (lastfd < s->inetsocket) + lastfd = s->inetsocket; +#if defined(WIN32) && !defined(_MSC_VER) + FD_SET((int)s->inetsocket, &fdreadset); +#else + FD_SET((unsigned int)s->inetsocket, &fdreadset); +#endif + } + } + tv.tv_sec = microseconds / 1000000; + tv.tv_usec = microseconds % 1000000; + select(lastfd + 1, &fdreadset, NULL, NULL, &tv); +#else + Sys_Sleep(microseconds); +#endif +} + +lhnetsocket_t *LHNET_OpenSocket_Connectionless(lhnetaddress_t *address) +{ + lhnetsocket_t *lhnetsocket, *s; + if (!address) + return NULL; + lhnetsocket = (lhnetsocket_t *)Z_Malloc(sizeof(*lhnetsocket)); + if (lhnetsocket) + { + memset(lhnetsocket, 0, sizeof(*lhnetsocket)); + lhnetsocket->address = *address; + switch(lhnetsocket->address.addresstype) + { + case LHNETADDRESSTYPE_LOOP: + if (lhnetsocket->address.port == 0) + { + // allocate a port dynamically + // this search will always terminate because there is never + // an allocated socket with port 0, so if the number wraps it + // will find the port is unused, and then refuse to use port + // 0, causing an intentional failure condition + lhnetsocket->address.port = 1024; + for (;;) + { + for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) + if (s->address.addresstype == lhnetsocket->address.addresstype && s->address.port == lhnetsocket->address.port) + break; + if (s == &lhnet_socketlist) + break; + lhnetsocket->address.port++; + } + } + // check if the port is available + for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) + if (s->address.addresstype == lhnetsocket->address.addresstype && s->address.port == lhnetsocket->address.port) + break; + if (s == &lhnet_socketlist && lhnetsocket->address.port != 0) + { + lhnetsocket->next = &lhnet_socketlist; + lhnetsocket->prev = lhnetsocket->next->prev; + lhnetsocket->next->prev = lhnetsocket; + lhnetsocket->prev->next = lhnetsocket; + return lhnetsocket; + } + break; + case LHNETADDRESSTYPE_INET4: +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: +#endif +#ifdef WIN32 + if (lhnet_didWSAStartup) + { +#endif +#ifdef SUPPORTIPV6 + if ((lhnetsocket->inetsocket = socket(address->addresstype == LHNETADDRESSTYPE_INET6 ? PF_INET6 : PF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) +#else + if ((lhnetsocket->inetsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) +#endif + { +#ifdef WIN32 + u_long _false = 0; +#endif +#ifdef MSG_DONTWAIT + if (1) +#else +#ifdef WIN32 + u_long _true = 1; +#else + char _true = 1; +#endif + if (ioctlsocket(lhnetsocket->inetsocket, FIONBIO, &_true) != -1) +#endif + { +#ifdef IPV6_V6ONLY + // We need to set this flag to tell the OS that we only listen on IPv6. If we don't + // most OSes will create a dual-protocol socket that also listens on IPv4. In this case + // if an IPv4 socket is already bound to the port we want, our bind() call will fail. + int ipv6_only = 1; + if (address->addresstype != LHNETADDRESSTYPE_INET6 + || setsockopt (lhnetsocket->inetsocket, IPPROTO_IPV6, IPV6_V6ONLY, + (const char *)&ipv6_only, sizeof(ipv6_only)) == 0 +#ifdef WIN32 + // The Win32 API only supports IPV6_V6ONLY since Windows Vista, but fortunately + // the default value is what we want on Win32 anyway (IPV6_V6ONLY = true) + || SOCKETERRNO == WSAENOPROTOOPT +#endif + ) +#endif + { + lhnetaddressnative_t *localaddress = (lhnetaddressnative_t *)&lhnetsocket->address; + SOCKLEN_T namelen; + int bindresult; +#ifdef SUPPORTIPV6 + if (address->addresstype == LHNETADDRESSTYPE_INET6) + { + namelen = sizeof(localaddress->addr.in6); + bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); + if (bindresult != -1) + getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen); + } + else +#endif + { + namelen = sizeof(localaddress->addr.in); + bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); + if (bindresult != -1) + getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen); + } + if (bindresult != -1) + { + int i = 1; + // enable broadcast on this socket + setsockopt(lhnetsocket->inetsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)); + lhnetsocket->next = &lhnet_socketlist; + lhnetsocket->prev = lhnetsocket->next->prev; + lhnetsocket->next->prev = lhnetsocket; + lhnetsocket->prev->next = lhnetsocket; +#ifdef WIN32 + if (ioctlsocket(lhnetsocket->inetsocket, SIO_UDP_CONNRESET, &_false) == -1) + Con_DPrintf("LHNET_OpenSocket_Connectionless: ioctlsocket SIO_UDP_CONNRESET returned error: %s\n", LHNETPRIVATE_StrError()); +#endif + return lhnetsocket; + } + else + Con_Printf("LHNET_OpenSocket_Connectionless: bind returned error: %s\n", LHNETPRIVATE_StrError()); + } +#ifdef IPV6_V6ONLY + else + Con_Printf("LHNET_OpenSocket_Connectionless: setsockopt(IPV6_V6ONLY) returned error: %s\n", LHNETPRIVATE_StrError()); +#endif + } + else + Con_Printf("LHNET_OpenSocket_Connectionless: ioctlsocket returned error: %s\n", LHNETPRIVATE_StrError()); + closesocket(lhnetsocket->inetsocket); + } + else + Con_Printf("LHNET_OpenSocket_Connectionless: socket returned error: %s\n", LHNETPRIVATE_StrError()); +#ifdef WIN32 + } + else + Con_Print("LHNET_OpenSocket_Connectionless: can't open a socket (WSAStartup failed during LHNET_Init)\n"); +#endif + break; + default: + break; + } + Z_Free(lhnetsocket); + } + return NULL; +} + +void LHNET_CloseSocket(lhnetsocket_t *lhnetsocket) +{ + if (lhnetsocket) + { + // unlink from socket list + if (lhnetsocket->next == NULL) + return; // invalid! + lhnetsocket->next->prev = lhnetsocket->prev; + lhnetsocket->prev->next = lhnetsocket->next; + lhnetsocket->next = NULL; + lhnetsocket->prev = NULL; + + // no special close code for loopback, just inet + if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4 || lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) + { + closesocket(lhnetsocket->inetsocket); + } + Z_Free(lhnetsocket); + } +} + +lhnetaddress_t *LHNET_AddressFromSocket(lhnetsocket_t *sock) +{ + if (sock) + return &sock->address; + else + return NULL; +} + +int LHNET_Read(lhnetsocket_t *lhnetsocket, void *content, int maxcontentlength, lhnetaddress_t *vaddress) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + int value = 0; + if (!lhnetsocket || !address || !content || maxcontentlength < 1) + return -1; + if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_LOOP) + { + time_t currenttime; + lhnetpacket_t *p, *pnext; + // scan for any old packets to timeout while searching for a packet + // that is waiting to be delivered to this socket + currenttime = time(NULL); + for (p = lhnet_packetlist.next;p != &lhnet_packetlist;p = pnext) + { + pnext = p->next; + if (p->timeout < currenttime) + { + // unlink and free + p->next->prev = p->prev; + p->prev->next = p->next; + Z_Free(p); + continue; + } +#ifndef STANDALONETEST + if (cl_netlocalping.value && (realtime - cl_netlocalping.value * (1.0 / 2000.0)) < p->sentdoubletime) + continue; +#endif + if (value == 0 && p->destinationport == lhnetsocket->address.port) + { + if (p->length <= maxcontentlength) + { + lhnetaddressnative_t *localaddress = (lhnetaddressnative_t *)&lhnetsocket->address; + *address = *localaddress; + address->port = p->sourceport; + memcpy(content, p->data, p->length); + value = p->length; + } + else + value = -1; + // unlink and free + p->next->prev = p->prev; + p->prev->next = p->next; + Z_Free(p); + } + } + } + else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4) + { + SOCKLEN_T inetaddresslength; + address->addresstype = LHNETADDRESSTYPE_NONE; + inetaddresslength = sizeof(address->addr.in); + value = recvfrom(lhnetsocket->inetsocket, (char *)content, maxcontentlength, LHNET_RECVFROM_FLAGS, &address->addr.sock, &inetaddresslength); + if (value > 0) + { + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = ntohs(address->addr.in.sin_port); + return value; + } + else if (value < 0) + { + int e = SOCKETERRNO; + if (e == EWOULDBLOCK) + return 0; + switch (e) + { + case ECONNREFUSED: + Con_Print("Connection refused\n"); + return 0; + } + Con_DPrintf("LHNET_Read: recvfrom returned error: %s\n", LHNETPRIVATE_StrError()); + } + } +#ifdef SUPPORTIPV6 + else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) + { + SOCKLEN_T inetaddresslength; + address->addresstype = LHNETADDRESSTYPE_NONE; + inetaddresslength = sizeof(address->addr.in6); + value = recvfrom(lhnetsocket->inetsocket, (char *)content, maxcontentlength, LHNET_RECVFROM_FLAGS, &address->addr.sock, &inetaddresslength); + if (value > 0) + { + address->addresstype = LHNETADDRESSTYPE_INET6; + address->port = ntohs(address->addr.in6.sin6_port); + return value; + } + else if (value == -1) + { + int e = SOCKETERRNO; + if (e == EWOULDBLOCK) + return 0; + switch (e) + { + case ECONNREFUSED: + Con_Print("Connection refused\n"); + return 0; + } + Con_DPrintf("LHNET_Read: recvfrom returned error: %s\n", LHNETPRIVATE_StrError()); + } + } +#endif + return value; +} + +int LHNET_Write(lhnetsocket_t *lhnetsocket, const void *content, int contentlength, const lhnetaddress_t *vaddress) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + int value = -1; + if (!lhnetsocket || !address || !content || contentlength < 1) + return -1; + if (lhnetsocket->address.addresstype != address->addresstype) + return -1; + if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_LOOP) + { + lhnetpacket_t *p; + p = (lhnetpacket_t *)Z_Malloc(sizeof(*p) + contentlength); + p->data = (void *)(p + 1); + memcpy(p->data, content, contentlength); + p->length = contentlength; + p->sourceport = lhnetsocket->address.port; + p->destinationport = address->port; + p->timeout = time(NULL) + 10; + p->next = &lhnet_packetlist; + p->prev = p->next->prev; + p->next->prev = p; + p->prev->next = p; +#ifndef STANDALONETEST + p->sentdoubletime = realtime; +#endif + value = contentlength; + } + else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4) + { + value = sendto(lhnetsocket->inetsocket, (char *)content, contentlength, LHNET_SENDTO_FLAGS, (struct sockaddr *)&address->addr.in, sizeof(struct sockaddr_in)); + if (value == -1) + { + if (SOCKETERRNO == EWOULDBLOCK) + return 0; + Con_DPrintf("LHNET_Write: sendto returned error: %s\n", LHNETPRIVATE_StrError()); + } + } +#ifdef SUPPORTIPV6 + else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) + { + value = sendto(lhnetsocket->inetsocket, (char *)content, contentlength, 0, (struct sockaddr *)&address->addr.in6, sizeof(struct sockaddr_in6)); + if (value == -1) + { + if (SOCKETERRNO == EWOULDBLOCK) + return 0; + Con_DPrintf("LHNET_Write: sendto returned error: %s\n", LHNETPRIVATE_StrError()); + } + } +#endif + return value; +} + +#ifdef STANDALONETEST +int main(int argc, char **argv) +{ +#if 1 + char *buffer = "test", buffer2[1024]; + int blen = strlen(buffer); + int b2len = 1024; + lhnetsocket_t *sock1; + lhnetsocket_t *sock2; + lhnetaddress_t myaddy1; + lhnetaddress_t myaddy2; + lhnetaddress_t myaddy3; + lhnetaddress_t localhostaddy1; + lhnetaddress_t localhostaddy2; + int test1; + int test2; + + printf("calling LHNET_Init\n"); + LHNET_Init(); + + printf("calling LHNET_FromPort twice to create two local addresses\n"); + LHNETADDRESS_FromPort(&myaddy1, LHNETADDRESSTYPE_INET4, 4000); + LHNETADDRESS_FromPort(&myaddy2, LHNETADDRESSTYPE_INET4, 4001); + LHNETADDRESS_FromString(&localhostaddy1, "127.0.0.1", 4000); + LHNETADDRESS_FromString(&localhostaddy2, "127.0.0.1", 4001); + + printf("calling LHNET_OpenSocket_Connectionless twice to create two local sockets\n"); + sock1 = LHNET_OpenSocket_Connectionless(&myaddy1); + sock2 = LHNET_OpenSocket_Connectionless(&myaddy2); + + printf("calling LHNET_Write to send a packet from the first socket to the second socket\n"); + test1 = LHNET_Write(sock1, buffer, blen, &localhostaddy2); + printf("sleeping briefly\n"); +#ifdef WIN32 + Sleep (100); +#else + usleep (100000); +#endif + printf("calling LHNET_Read on the second socket to read the packet sent from the first socket\n"); + test2 = LHNET_Read(sock2, buffer2, b2len - 1, &myaddy3); + if (test2 > 0) + Con_Printf("socket to socket test succeeded\n"); + else + Con_Printf("socket to socket test failed\n"); + +#ifdef WIN32 + printf("press any key to exit\n"); + getchar(); +#endif + + printf("calling LHNET_Shutdown\n"); + LHNET_Shutdown(); + printf("exiting\n"); + return 0; +#else + lhnetsocket_t *sock[16], *sendsock; + int i; + int numsockets; + int count; + int length; + int port; + time_t oldtime; + time_t newtime; + char *sendmessage; + int sendmessagelength; + lhnetaddress_t destaddress; + lhnetaddress_t receiveaddress; + lhnetaddress_t sockaddress[16]; + char buffer[1536], addressstring[128], addressstring2[128]; + if ((argc == 2 || argc == 5) && (port = atoi(argv[1])) >= 1 && port < 65535) + { + printf("calling LHNET_Init()\n"); + LHNET_Init(); + + numsockets = 0; + LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_LOOP, port); + LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_INET4, port); + LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_INET6, port+1); + + sendsock = NULL; + sendmessage = NULL; + sendmessagelength = 0; + + for (i = 0;i < numsockets;i++) + { + LHNETADDRESS_ToString(&sockaddress[i], addressstring, sizeof(addressstring), 1); + printf("calling LHNET_OpenSocket_Connectionless(<%s>)\n", addressstring); + if ((sock[i] = LHNET_OpenSocket_Connectionless(&sockaddress[i]))) + { + LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); + printf("opened socket successfully (address \"%s\")\n", addressstring2); + } + else + { + printf("failed to open socket\n"); + if (i == 0) + { + LHNET_Shutdown(); + return -1; + } + } + } + count = 0; + if (argc == 5) + { + count = atoi(argv[2]); + if (LHNETADDRESS_FromString(&destaddress, argv[3], -1)) + { + sendmessage = argv[4]; + sendmessagelength = strlen(sendmessage); + sendsock = NULL; + for (i = 0;i < numsockets;i++) + if (sock[i] && LHNETADDRESS_GetAddressType(&destaddress) == LHNETADDRESS_GetAddressType(&sockaddress[i])) + sendsock = sock[i]; + if (sendsock == NULL) + { + printf("Could not find an open socket matching the addresstype (%i) of destination address, switching to listen only mode\n", LHNETADDRESS_GetAddressType(&destaddress)); + argc = 2; + } + } + else + { + printf("LHNETADDRESS_FromString did not like the address \"%s\", switching to listen only mode\n", argv[3]); + argc = 2; + } + } + printf("started, now listening for \"exit\" on the opened sockets\n"); + oldtime = time(NULL); + for(;;) + { +#ifdef WIN32 + Sleep(1); +#else + usleep(1); +#endif + for (i = 0;i < numsockets;i++) + { + if (sock[i]) + { + length = LHNET_Read(sock[i], buffer, sizeof(buffer), &receiveaddress); + if (length < 0) + printf("localsock read error: length < 0"); + else if (length > 0 && length < (int)sizeof(buffer)) + { + buffer[length] = 0; + LHNETADDRESS_ToString(&receiveaddress, addressstring, sizeof(addressstring), 1); + LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); + printf("received message \"%s\" from \"%s\" on socket \"%s\"\n", buffer, addressstring, addressstring2); + if (!strcmp(buffer, "exit")) + break; + } + } + } + if (i < numsockets) + break; + if (argc == 5 && count > 0) + { + newtime = time(NULL); + if (newtime != oldtime) + { + LHNETADDRESS_ToString(&destaddress, addressstring, sizeof(addressstring), 1); + LHNETADDRESS_ToString(LHNET_AddressFromSocket(sendsock), addressstring2, sizeof(addressstring2), 1); + printf("calling LHNET_Write(<%s>, \"%s\", %i, <%s>)\n", addressstring2, sendmessage, sendmessagelength, addressstring); + length = LHNET_Write(sendsock, sendmessage, sendmessagelength, &destaddress); + if (length == sendmessagelength) + printf("sent successfully\n"); + else + printf("LH_Write failed, returned %i (length of message was %i)\n", length, strlen(argv[4])); + oldtime = newtime; + count--; + if (count <= 0) + printf("Done sending, still listening for \"exit\"\n"); + } + } + } + for (i = 0;i < numsockets;i++) + { + if (sock[i]) + { + LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); + printf("calling LHNET_CloseSocket(<%s>)\n", addressstring2); + LHNET_CloseSocket(sock[i]); + } + } + printf("calling LHNET_Shutdown()\n"); + LHNET_Shutdown(); + return 0; + } + printf("Testing code for lhnet.c\nusage: lhnettest [ ]\n"); + return -1; +#endif +} +#endif + diff --git a/misc/source/darkplaces-src/lhnet.h b/misc/source/darkplaces-src/lhnet.h new file mode 100644 index 00000000..d46dbe99 --- /dev/null +++ b/misc/source/darkplaces-src/lhnet.h @@ -0,0 +1,51 @@ + +// Written by Forest Hale 2003-06-15 and placed into public domain. + +#ifndef LHNET_H +#define LHNET_H + +typedef enum lhnetaddresstype_e +{ + LHNETADDRESSTYPE_NONE, + LHNETADDRESSTYPE_LOOP, + LHNETADDRESSTYPE_INET4, + LHNETADDRESSTYPE_INET6 +} +lhnetaddresstype_t; + +typedef struct lhnetaddress_s +{ + lhnetaddresstype_t addresstype; + int port; // used by LHNETADDRESSTYPE_LOOP + unsigned char storage[256]; // sockaddr_in or sockaddr_in6 +} +lhnetaddress_t; + +int LHNETADDRESS_FromPort(lhnetaddress_t *address, lhnetaddresstype_t addresstype, int port); +int LHNETADDRESS_FromString(lhnetaddress_t *address, const char *string, int defaultport); +int LHNETADDRESS_ToString(const lhnetaddress_t *address, char *string, int stringbuffersize, int includeport); +int LHNETADDRESS_GetAddressType(const lhnetaddress_t *address); +const char *LHNETADDRESS_GetInterfaceName(const lhnetaddress_t *address); +int LHNETADDRESS_GetPort(const lhnetaddress_t *address); +int LHNETADDRESS_SetPort(lhnetaddress_t *address, int port); +int LHNETADDRESS_Compare(const lhnetaddress_t *address1, const lhnetaddress_t *address2); + +typedef struct lhnetsocket_s +{ + lhnetaddress_t address; + int inetsocket; + struct lhnetsocket_s *next, *prev; +} +lhnetsocket_t; + +void LHNET_Init(void); +void LHNET_Shutdown(void); +void LHNET_SleepUntilPacket_Microseconds(int microseconds); +lhnetsocket_t *LHNET_OpenSocket_Connectionless(lhnetaddress_t *address); +void LHNET_CloseSocket(lhnetsocket_t *lhnetsocket); +lhnetaddress_t *LHNET_AddressFromSocket(lhnetsocket_t *sock); +int LHNET_Read(lhnetsocket_t *lhnetsocket, void *content, int maxcontentlength, lhnetaddress_t *address); +int LHNET_Write(lhnetsocket_t *lhnetsocket, const void *content, int contentlength, const lhnetaddress_t *address); + +#endif + diff --git a/misc/source/darkplaces-src/libcurl.c b/misc/source/darkplaces-src/libcurl.c new file mode 100644 index 00000000..b472d9e9 --- /dev/null +++ b/misc/source/darkplaces-src/libcurl.c @@ -0,0 +1,1662 @@ +#include "quakedef.h" +#include "fs.h" +#include "libcurl.h" + +static cvar_t cl_curl_maxdownloads = {CVAR_SAVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"}; +static cvar_t cl_curl_maxspeed = {CVAR_SAVE, "cl_curl_maxspeed","300", "maximum download speed (KiB/s)"}; +static cvar_t sv_curl_defaulturl = {CVAR_SAVE, "sv_curl_defaulturl","", "default autodownload source URL"}; +static cvar_t sv_curl_serverpackages = {CVAR_SAVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"}; +static cvar_t sv_curl_maxspeed = {CVAR_SAVE, "sv_curl_maxspeed","0", "maximum download speed for clients downloading from sv_curl_defaulturl (KiB/s)"}; +static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"}; + +/* +================================================================= + + Minimal set of definitions from libcurl + + WARNING: for a matter of simplicity, several pointer types are + casted to "void*", and most enumerated values are not included + +================================================================= +*/ + +typedef struct CURL_s CURL; +typedef struct CURLM_s CURLM; +typedef struct curl_slist curl_slist; +typedef enum +{ + CURLE_OK = 0 +} +CURLcode; +typedef enum +{ + CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */ + CURLM_OK = 0 +} +CURLMcode; +#define CURL_GLOBAL_NOTHING 0 +#define CURL_GLOBAL_SSL 1 +#define CURL_GLOBAL_WIN32 2 +#define CURLOPTTYPE_LONG 0 +#define CURLOPTTYPE_OBJECTPOINT 10000 +#define CURLOPTTYPE_FUNCTIONPOINT 20000 +#define CURLOPTTYPE_OFF_T 30000 +#define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number +typedef enum +{ + CINIT(WRITEDATA, OBJECTPOINT, 1), + CINIT(URL, OBJECTPOINT, 2), + CINIT(ERRORBUFFER, OBJECTPOINT, 10), + CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11), + CINIT(POSTFIELDS, OBJECTPOINT, 15), + CINIT(REFERER, OBJECTPOINT, 16), + CINIT(USERAGENT, OBJECTPOINT, 18), + CINIT(LOW_SPEED_LIMIT, LONG , 19), + CINIT(LOW_SPEED_TIME, LONG, 20), + CINIT(RESUME_FROM, LONG, 21), + CINIT(HTTPHEADER, OBJECTPOINT, 23), + CINIT(POST, LONG, 47), /* HTTP POST method */ + CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */ + CINIT(POSTFIELDSIZE, LONG, 60), + CINIT(PRIVATE, OBJECTPOINT, 103), + CINIT(PROTOCOLS, LONG, 181), + CINIT(REDIR_PROTOCOLS, LONG, 182) +} +CURLoption; +#define CURLPROTO_HTTP (1<<0) +#define CURLPROTO_HTTPS (1<<1) +#define CURLPROTO_FTP (1<<2) +typedef enum +{ + CURLINFO_TEXT = 0, + CURLINFO_HEADER_IN, /* 1 */ + CURLINFO_HEADER_OUT, /* 2 */ + CURLINFO_DATA_IN, /* 3 */ + CURLINFO_DATA_OUT, /* 4 */ + CURLINFO_SSL_DATA_IN, /* 5 */ + CURLINFO_SSL_DATA_OUT, /* 6 */ + CURLINFO_END +} +curl_infotype; +#define CURLINFO_STRING 0x100000 +#define CURLINFO_LONG 0x200000 +#define CURLINFO_DOUBLE 0x300000 +#define CURLINFO_SLIST 0x400000 +#define CURLINFO_MASK 0x0fffff +#define CURLINFO_TYPEMASK 0xf00000 +typedef enum +{ + CURLINFO_NONE, /* first, never use this */ + CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1, + CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, + CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3, + CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4, + CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5, + CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6, + CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7, + CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8, + CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9, + CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10, + CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11, + CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12, + CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13, + CURLINFO_FILETIME = CURLINFO_LONG + 14, + CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, + CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16, + CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17, + CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, + CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19, + CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20, + CURLINFO_PRIVATE = CURLINFO_STRING + 21, + CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22, + CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23, + CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24, + CURLINFO_OS_ERRNO = CURLINFO_LONG + 25, + CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26, + CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27 +} +CURLINFO; + +typedef enum +{ + CURLMSG_NONE, /* first, not used */ + CURLMSG_DONE, /* This easy handle has completed. 'result' contains + the CURLcode of the transfer */ + CURLMSG_LAST +} +CURLMSG; +typedef struct +{ + CURLMSG msg; /* what this message means */ + CURL *easy_handle; /* the handle it concerns */ + union + { + void *whatever; /* message-specific data */ + CURLcode result; /* return code for transfer */ + } + data; +} +CURLMsg; + +static void (*qcurl_global_init) (long flags); +static void (*qcurl_global_cleanup) (void); + +static CURL * (*qcurl_easy_init) (void); +static void (*qcurl_easy_cleanup) (CURL *handle); +static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...); +static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...); +static const char * (*qcurl_easy_strerror) (CURLcode); + +static CURLM * (*qcurl_multi_init) (void); +static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles); +static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle); +static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle); +static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue); +static void (*qcurl_multi_cleanup) (CURLM *); +static const char * (*qcurl_multi_strerror) (CURLcode); +static curl_slist * (*qcurl_slist_append) (curl_slist *list, const char *string); +static void (*qcurl_slist_free_all) (curl_slist *list); + +static dllfunction_t curlfuncs[] = +{ + {"curl_global_init", (void **) &qcurl_global_init}, + {"curl_global_cleanup", (void **) &qcurl_global_cleanup}, + {"curl_easy_init", (void **) &qcurl_easy_init}, + {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup}, + {"curl_easy_setopt", (void **) &qcurl_easy_setopt}, + {"curl_easy_strerror", (void **) &qcurl_easy_strerror}, + {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo}, + {"curl_multi_init", (void **) &qcurl_multi_init}, + {"curl_multi_perform", (void **) &qcurl_multi_perform}, + {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle}, + {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle}, + {"curl_multi_info_read", (void **) &qcurl_multi_info_read}, + {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup}, + {"curl_multi_strerror", (void **) &qcurl_multi_strerror}, + {"curl_slist_append", (void **) &qcurl_slist_append}, + {"curl_slist_free_all", (void **) &qcurl_slist_free_all}, + {NULL, NULL} +}; + +// Handle for CURL DLL +static dllhandle_t curl_dll = NULL; +// will be checked at many places to find out if qcurl calls are allowed + +typedef struct downloadinfo_s +{ + char filename[MAX_OSPATH]; + char url[1024]; + char referer[256]; + qfile_t *stream; + fs_offset_t startpos; + CURL *curle; + qboolean started; + qboolean ispak; + unsigned long bytes_received; // for buffer + double bytes_received_curl; // for throttling + double bytes_sent_curl; // for throttling + struct downloadinfo_s *next, *prev; + qboolean forthismap; + double maxspeed; + curl_slist *slist; // http headers + + unsigned char *buffer; + size_t buffersize; + curl_callback_t callback; + void *callback_data; + + const unsigned char *postbuf; + size_t postbufsize; + const char *post_content_type; + const char *extraheaders; +} +downloadinfo; +static downloadinfo *downloads = NULL; +static int numdownloads = 0; + +static qboolean noclear = FALSE; + +static int numdownloads_fail = 0; +static int numdownloads_success = 0; +static int numdownloads_added = 0; +static char command_when_done[256] = ""; +static char command_when_error[256] = ""; + +/* +==================== +Curl_CommandWhenDone + +Sets the command which is to be executed when the last download completes AND +all downloads since last server connect ended with a successful status. +Setting the command to NULL clears it. +==================== +*/ +void Curl_CommandWhenDone(const char *cmd) +{ + if(!curl_dll) + return; + if(cmd) + strlcpy(command_when_done, cmd, sizeof(command_when_done)); + else + *command_when_done = 0; +} + +/* +FIXME +Do not use yet. Not complete. +Problem: what counts as an error? +*/ + +void Curl_CommandWhenError(const char *cmd) +{ + if(!curl_dll) + return; + if(cmd) + strlcpy(command_when_error, cmd, sizeof(command_when_error)); + else + *command_when_error = 0; +} + +/* +==================== +Curl_Clear_forthismap + +Clears the "will disconnect on failure" flags. +==================== +*/ +void Curl_Clear_forthismap(void) +{ + downloadinfo *di; + if(noclear) + return; + for(di = downloads; di; di = di->next) + di->forthismap = false; + Curl_CommandWhenError(NULL); + Curl_CommandWhenDone(NULL); + numdownloads_fail = 0; + numdownloads_success = 0; + numdownloads_added = 0; +} + +/* +==================== +Curl_Have_forthismap + +Returns true if a download needed for the current game is running. +==================== +*/ +qboolean Curl_Have_forthismap(void) +{ + return numdownloads_added != 0; +} + +void Curl_Register_predownload(void) +{ + Curl_CommandWhenDone("cl_begindownloads"); + Curl_CommandWhenError("cl_begindownloads"); +} + +/* +==================== +Curl_CheckCommandWhenDone + +Checks if a "done command" is to be executed. +All downloads finished, at least one success since connect, no single failure +-> execute the command. +*/ +static void Curl_CheckCommandWhenDone(void) +{ + if(!curl_dll) + return; + if(numdownloads_added && (numdownloads_success == numdownloads_added) && *command_when_done) + { + Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done); + Cbuf_AddText("\n"); + Cbuf_AddText(command_when_done); + Cbuf_AddText("\n"); + Curl_Clear_forthismap(); + } + else if(numdownloads_added && numdownloads_fail && *command_when_error) + { + Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error); + Cbuf_AddText("\n"); + Cbuf_AddText(command_when_error); + Cbuf_AddText("\n"); + Curl_Clear_forthismap(); + } +} + +/* +==================== +CURL_CloseLibrary + +Load the cURL DLL +==================== +*/ +static qboolean CURL_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libcurl-4.dll", + "libcurl-3.dll", +#elif defined(MACOSX) + "libcurl.4.dylib", // Mac OS X Notyetreleased + "libcurl.3.dylib", // Mac OS X Tiger + "libcurl.2.dylib", // Mac OS X Panther +#else + "libcurl.so.4", + "libcurl.so.3", + "libcurl.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (curl_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs); +} + + +/* +==================== +CURL_CloseLibrary + +Unload the cURL DLL +==================== +*/ +static void CURL_CloseLibrary (void) +{ + Sys_UnloadLibrary (&curl_dll); +} + + +static CURLM *curlm = NULL; +static double bytes_received = 0; // used for bandwidth throttling +static double bytes_sent = 0; // used for bandwidth throttling +static double curltime = 0; + +/* +==================== +CURL_fwrite + +fwrite-compatible function that writes the data to a file. libcurl can call +this. +==================== +*/ +static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi) +{ + fs_offset_t ret = -1; + size_t bytes = size * nmemb; + downloadinfo *di = (downloadinfo *) vdi; + + if(di->buffer) + { + if(di->bytes_received + bytes <= di->buffersize) + { + memcpy(di->buffer + di->bytes_received, data, bytes); + ret = bytes; + } + // otherwise: buffer overrun, ret stays -1 + } + + if(di->stream) + { + ret = FS_Write(di->stream, data, bytes); + } + + di->bytes_received += bytes; + + return ret; // why not ret / nmemb? +} + +typedef enum +{ + CURL_DOWNLOAD_SUCCESS = 0, + CURL_DOWNLOAD_FAILED, + CURL_DOWNLOAD_ABORTED, + CURL_DOWNLOAD_SERVERERROR +} +CurlStatus; + +static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) +{ + downloadinfo *di = (downloadinfo *) cbdata; + switch(status) + { + case CURLCBSTATUS_OK: + Con_DPrintf("Download of %s: OK\n", di->filename); + break; + case CURLCBSTATUS_FAILED: + Con_DPrintf("Download of %s: FAILED\n", di->filename); + break; + case CURLCBSTATUS_ABORTED: + Con_DPrintf("Download of %s: ABORTED\n", di->filename); + break; + case CURLCBSTATUS_SERVERERROR: + Con_DPrintf("Download of %s: (unknown server error)\n", di->filename); + break; + case CURLCBSTATUS_UNKNOWN: + Con_DPrintf("Download of %s: (unknown client error)\n", di->filename); + break; + default: + Con_DPrintf("Download of %s: %d\n", di->filename, status); + break; + } +} + +static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) +{ + curl_default_callback(status, length_received, buffer, cbdata); +} + +/* +==================== +Curl_EndDownload + +stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS, +CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error +code from libcurl, or 0, if another error has occurred. +==================== +*/ +static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, qboolean ispak, qboolean forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); +static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error) +{ + qboolean ok = false; + if(!curl_dll) + return; + switch(status) + { + case CURL_DOWNLOAD_SUCCESS: + ok = true; + di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data); + break; + case CURL_DOWNLOAD_FAILED: + di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data); + break; + case CURL_DOWNLOAD_ABORTED: + di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data); + break; + case CURL_DOWNLOAD_SERVERERROR: + // reopen to enforce it to have zero bytes again + if(di->stream) + { + FS_Close(di->stream); + di->stream = FS_OpenRealFile(di->filename, "wb", false); + } + + if(di->callback) + di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data); + break; + default: + if(di->callback) + di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data); + break; + } + + if(di->curle) + { + qcurl_multi_remove_handle(curlm, di->curle); + qcurl_easy_cleanup(di->curle); + if(di->slist) + qcurl_slist_free_all(di->slist); + } + + if(!di->callback && ok && !di->bytes_received) + { + Con_Printf("ERROR: empty file\n"); + ok = false; + } + + if(di->stream) + FS_Close(di->stream); + + if(ok && di->ispak) + { + ok = FS_AddPack(di->filename, NULL, true); + if(!ok) + { + // pack loading failed? + // this is critical + // better clear the file again... + di->stream = FS_OpenRealFile(di->filename, "wb", false); + FS_Close(di->stream); + + if(di->startpos && !di->callback) + { + // this was a resume? + // then try to redownload it without reporting the error + Curl_Begin(di->url, di->extraheaders, di->maxspeed, di->filename, di->ispak, di->forthismap, di->post_content_type, di->postbuf, di->postbufsize, NULL, 0, NULL, NULL); + di->forthismap = false; // don't count the error + } + } + } + + if(di->prev) + di->prev->next = di->next; + else + downloads = di->next; + if(di->next) + di->next->prev = di->prev; + + --numdownloads; + if(di->forthismap) + { + if(ok) + ++numdownloads_success; + else + ++numdownloads_fail; + } + Z_Free(di); +} + +/* +==================== +CleanURL + +Returns a "cleaned up" URL for display (to strip login data) +==================== +*/ +static const char *CleanURL(const char *url) +{ + static char urlbuf[1024]; + const char *p, *q, *r; + + // if URL is of form anything://foo-without-slash@rest, replace by anything://rest + p = strstr(url, "://"); + if(p) + { + q = strchr(p + 3, '@'); + if(q) + { + r = strchr(p + 3, '/'); + if(!r || q < r) + { + dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s%s", (int)(p - url + 3), url, q + 1); + return urlbuf; + } + } + } + + return url; +} + +/* +==================== +CheckPendingDownloads + +checks if there are free download slots to start new downloads in. +To not start too many downloads at once, only one download is added at a time, +up to a maximum number of cl_curl_maxdownloads are running. +==================== +*/ +static void CheckPendingDownloads(void) +{ + const char *h; + if(!curl_dll) + return; + if(numdownloads < cl_curl_maxdownloads.integer) + { + downloadinfo *di; + for(di = downloads; di; di = di->next) + { + if(!di->started) + { + if(!di->buffer) + { + Con_Printf("Downloading %s -> %s", CleanURL(di->url), di->filename); + + di->stream = FS_OpenRealFile(di->filename, "ab", false); + if(!di->stream) + { + Con_Printf("\nFAILED: Could not open output file %s\n", di->filename); + Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK); + return; + } + FS_Seek(di->stream, 0, SEEK_END); + di->startpos = FS_Tell(di->stream); + + if(di->startpos > 0) + Con_Printf(", resuming from position %ld", (long) di->startpos); + Con_Print("...\n"); + } + else + { + Con_DPrintf("Downloading %s -> memory\n", CleanURL(di->url)); + di->startpos = 0; + } + + di->curle = qcurl_easy_init(); + di->slist = NULL; + qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url); + qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, engineversion); + qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer); + qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos); + qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1); + qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite); + qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256); + qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45); + qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di); + qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di); + qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP); + if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK) + { + Con_Printf("^1WARNING:^7 for security reasons, please upgrade to libcurl 7.19.4 or above. In a later version of DarkPlaces, HTTP redirect support will be disabled for this libcurl version.\n"); + //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0); + } + if(di->post_content_type) + { + qcurl_easy_setopt(di->curle, CURLOPT_POST, 1); + qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDS, di->postbuf); + qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDSIZE, di->postbufsize); + di->slist = qcurl_slist_append(di->slist, va("Content-Type: %s", di->post_content_type)); + } + + // parse extra headers into slist + // \n separated list! + h = di->extraheaders; + while(h) + { + const char *hh = strchr(h, '\n'); + if(hh) + { + char *buf = (char *) Mem_Alloc(tempmempool, hh - h + 1); + memcpy(buf, h, hh - h); + buf[hh - h] = 0; + di->slist = qcurl_slist_append(di->slist, buf); + h = hh + 1; + } + else + { + di->slist = qcurl_slist_append(di->slist, h); + h = NULL; + } + } + + qcurl_easy_setopt(di->curle, CURLOPT_HTTPHEADER, di->slist); + + + qcurl_multi_add_handle(curlm, di->curle); + di->started = true; + ++numdownloads; + if(numdownloads >= cl_curl_maxdownloads.integer) + break; + } + } + } +} + +/* +==================== +Curl_Init + +this function MUST be called before using anything else in this file. +On Win32, this must be called AFTER WSAStartup has been done! +==================== +*/ +void Curl_Init(void) +{ + CURL_OpenLibrary(); + if(!curl_dll) + return; + qcurl_global_init(CURL_GLOBAL_NOTHING); + curlm = qcurl_multi_init(); +} + +/* +==================== +Curl_Shutdown + +Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET. +==================== +*/ +void Curl_ClearRequirements(void); +void Curl_Shutdown(void) +{ + if(!curl_dll) + return; + Curl_ClearRequirements(); + Curl_CancelAll(); + CURL_CloseLibrary(); + curl_dll = NULL; +} + +/* +==================== +Curl_Find + +Finds the internal information block for a download given by file name. +==================== +*/ +static downloadinfo *Curl_Find(const char *filename) +{ + downloadinfo *di; + if(!curl_dll) + return NULL; + for(di = downloads; di; di = di->next) + if(!strcasecmp(di->filename, filename)) + return di; + return NULL; +} + +void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata) +{ + downloadinfo *di; + if(!curl_dll) + return; + for(di = downloads; di; ) + { + if(di->callback == callback && di->callback_data == cbdata) + { + di->callback = curl_quiet_callback; // do NOT call the callback + Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK); + di = downloads; + } + else + di = di->next; + } +} + +/* +==================== +Curl_Begin + +Starts a download of a given URL to the file name portion of this URL (or name +if given) in the "dlcache/" folder. +==================== +*/ +static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, qboolean ispak, qboolean forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) +{ + if(!curl_dll) + { + return false; + } + else + { + char fn[MAX_OSPATH]; + char urlbuf[1024]; + const char *p, *q; + size_t length; + downloadinfo *di; + + // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server + p = strchr(URL, ':'); + if(p) + { + if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4)) + { + char addressstring[128]; + *addressstring = 0; + InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring)); + q = strchr(addressstring, ':'); + if(!q) + q = addressstring + strlen(addressstring); + if(*addressstring) + { + dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3); + URL = urlbuf; + } + } + } + + // Note: This extraction of the file name portion is NOT entirely correct. + // + // It does the following: + // + // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3 + // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3 + // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php + // + // However, I'd like to keep this "buggy" behavior so that PHP script + // authors can write download scripts without having to enable + // AcceptPathInfo on Apache. They just have to ensure that their script + // can be called with such a "fake" path name like + // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 + // + // By the way, such PHP scripts should either send the file or a + // "Location:" redirect; PHP code example: + // + // header("Location: http://www.example.com/"); + // + // By the way, this will set User-Agent to something like + // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to + // dp://serverhost:serverport/ so you can filter on this; an example + // httpd log file line might be: + // + // 141.2.16.3 - - [17/Mar/2006:22:32:43 +0100] "GET /maps/tznex07.pk3 HTTP/1.1" 200 1077455 "dp://141.2.16.7:26000/" "Nexuiz Linux 22:07:43 Mar 17 2006" + + if(!name) + name = CleanURL(URL); + + if(!buf) + { + p = strrchr(name, '/'); + p = p ? (p+1) : name; + q = strchr(p, '?'); + length = q ? (size_t)(q - p) : strlen(p); + dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p); + + name = fn; // make it point back + + // already downloading the file? + { + downloadinfo *di = Curl_Find(fn); + if(di) + { + Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(di->url)); + + // however, if it was not for this map yet... + if(forthismap && !di->forthismap) + { + di->forthismap = true; + // this "fakes" a download attempt so the client will wait for + // the download to finish and then reconnect + ++numdownloads_added; + } + + return false; + } + } + + if(ispak && FS_FileExists(fn)) + { + qboolean already_loaded; + if(FS_AddPack(fn, &already_loaded, true)) + { + Con_DPrintf("%s already exists, not downloading!\n", fn); + if(already_loaded) + Con_DPrintf("(pak was already loaded)\n"); + else + { + if(forthismap) + { + ++numdownloads_added; + ++numdownloads_success; + } + } + + return false; + } + else + { + qfile_t *f = FS_OpenRealFile(fn, "rb", false); + if(f) + { + char buf[4] = {0}; + FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp + + if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4)) + { + Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn); + FS_Close(f); + f = FS_OpenRealFile(fn, "wb", false); + if(f) + FS_Close(f); + } + else + { + // OK + FS_Close(f); + } + } + } + } + } + + // if we get here, we actually want to download... so first verify the + // URL scheme (so one can't read local files using file://) + if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8)) + { + Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL); + return false; + } + + if(forthismap) + ++numdownloads_added; + di = (downloadinfo *) Z_Malloc(sizeof(*di)); + strlcpy(di->filename, name, sizeof(di->filename)); + strlcpy(di->url, URL, sizeof(di->url)); + dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid"); + di->forthismap = forthismap; + di->stream = NULL; + di->startpos = 0; + di->curle = NULL; + di->started = false; + di->ispak = (ispak && !buf); + di->maxspeed = maxspeed; + di->bytes_received = 0; + di->bytes_received_curl = 0; + di->bytes_sent_curl = 0; + di->extraheaders = extraheaders; + di->next = downloads; + di->prev = NULL; + if(di->next) + di->next->prev = di; + + di->buffer = buf; + di->buffersize = bufsize; + if(callback == NULL) + { + di->callback = curl_default_callback; + di->callback_data = di; + } + else + { + di->callback = callback; + di->callback_data = cbdata; + } + + if(post_content_type) + { + di->post_content_type = post_content_type; + di->postbuf = postbuf; + di->postbufsize = postbufsize; + } + else + { + di->post_content_type = NULL; + di->postbuf = NULL; + di->postbufsize = 0; + } + + downloads = di; + return true; + } +} + +qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, qboolean ispak, qboolean forthismap) +{ + return Curl_Begin(URL, NULL, maxspeed, name, ispak, forthismap, NULL, NULL, 0, NULL, 0, NULL, NULL); +} +qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) +{ + return Curl_Begin(URL, NULL, maxspeed, NULL, false, false, NULL, NULL, 0, buf, bufsize, callback, cbdata); +} +qboolean Curl_Begin_ToMemory_POST(const char *URL, const char *extraheaders, double maxspeed, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) +{ + return Curl_Begin(URL, extraheaders, maxspeed, NULL, false, false, post_content_type, postbuf, postbufsize, buf, bufsize, callback, cbdata); +} + +/* +==================== +Curl_Run + +call this regularily as this will always download as much as possible without +blocking. +==================== +*/ +void Curl_Run(void) +{ + double maxspeed; + downloadinfo *di; + + noclear = FALSE; + + if(!cl_curl_enabled.integer) + return; + + if(!curl_dll) + return; + + Curl_CheckCommandWhenDone(); + + if(!downloads) + return; + + if(realtime < curltime) // throttle + return; + + { + int remaining; + CURLMcode mc; + + do + { + mc = qcurl_multi_perform(curlm, &remaining); + } + while(mc == CURLM_CALL_MULTI_PERFORM); + + for(di = downloads; di; di = di->next) + { + double b = 0; + qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_UPLOAD, &b); + bytes_sent += (b - di->bytes_sent_curl); + di->bytes_sent_curl = b; + qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_DOWNLOAD, &b); + bytes_sent += (b - di->bytes_received_curl); + di->bytes_received_curl = b; + } + + for(;;) + { + CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining); + if(!msg) + break; + if(msg->msg == CURLMSG_DONE) + { + CurlStatus failed = CURL_DOWNLOAD_SUCCESS; + CURLcode result; + qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di); + result = msg->data.result; + if(result) + { + failed = CURL_DOWNLOAD_FAILED; + } + else + { + long code; + qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code); + switch(code / 100) + { + case 4: // e.g. 404? + case 5: // e.g. 500? + failed = CURL_DOWNLOAD_SERVERERROR; + result = (CURLcode) code; + break; + } + } + + Curl_EndDownload(di, failed, result); + } + } + } + + CheckPendingDownloads(); + + // when will we curl the next time? + // we will wait a bit to ensure our download rate is kept. + // we now know that realtime >= curltime... so set up a new curltime + + // use the slowest allowing download to derive the maxspeed... this CAN + // be done better, but maybe later + maxspeed = cl_curl_maxspeed.value; + for(di = downloads; di; di = di->next) + if(di->maxspeed > 0) + if(di->maxspeed < maxspeed || maxspeed <= 0) + maxspeed = di->maxspeed; + + if(maxspeed > 0) + { + double bytes = bytes_sent + bytes_received; // maybe smoothen a bit? + curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0); + bytes_sent = 0; + bytes_received = 0; + } + else + curltime = realtime; +} + +/* +==================== +Curl_CancelAll + +Stops ALL downloads. +==================== +*/ +void Curl_CancelAll(void) +{ + if(!curl_dll) + return; + + while(downloads) + { + Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK); + // INVARIANT: downloads will point to the next download after that! + } +} + +/* +==================== +Curl_Running + +returns true iff there is a download running. +==================== +*/ +qboolean Curl_Running(void) +{ + if(!curl_dll) + return false; + + return downloads != NULL; +} + +/* +==================== +Curl_GetDownloadAmount + +returns a value from 0.0 to 1.0 which represents the downloaded amount of data +for the given download. +==================== +*/ +static double Curl_GetDownloadAmount(downloadinfo *di) +{ + if(!curl_dll) + return -2; + if(di->curle) + { + double length; + qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length); + if(length > 0) + return (di->startpos + di->bytes_received) / (di->startpos + length); + else + return 0; + } + else + return -1; +} + +/* +==================== +Curl_GetDownloadSpeed + +returns the speed of the given download in bytes per second +==================== +*/ +static double Curl_GetDownloadSpeed(downloadinfo *di) +{ + if(!curl_dll) + return -2; + if(di->curle) + { + double speed; + qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed); + return speed; + } + else + return -1; +} + +/* +==================== +Curl_Info_f + +prints the download list +==================== +*/ +// TODO rewrite using Curl_GetDownloadInfo? +static void Curl_Info_f(void) +{ + downloadinfo *di; + if(!curl_dll) + return; + if(Curl_Running()) + { + Con_Print("Currently running downloads:\n"); + for(di = downloads; di; di = di->next) + { + double speed, percent; + Con_Printf(" %s -> %s ", CleanURL(di->url), di->filename); + percent = 100.0 * Curl_GetDownloadAmount(di); + speed = Curl_GetDownloadSpeed(di); + if(percent >= 0) + Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0); + else + Con_Print("(queued)\n"); + } + } + else + { + Con_Print("No downloads running.\n"); + } +} + +/* +==================== +Curl_Curl_f + +implements the "curl" console command + +curl --info +curl --cancel +curl --cancel filename +curl url + +For internal use: + +curl [--pak] [--forthismap] [--for filename filename...] url + --pak: after downloading, load the package into the virtual file system + --for filename...: only download of at least one of the named files is missing + --forthismap: don't reconnect on failure + +curl --clear_autodownload + clears the download success/failure counters + +curl --finish_autodownload + if at least one download has been started, disconnect and drop to the menu + once the last download completes successfully, reconnect to the current server +==================== +*/ +void Curl_Curl_f(void) +{ + double maxspeed = 0; + int i; + int end; + qboolean pak = false; + qboolean forthismap = false; + const char *url; + const char *name = 0; + + if(!curl_dll) + { + Con_Print("libcurl DLL not found, this command is inactive.\n"); + return; + } + + if(!cl_curl_enabled.integer) + { + Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n"); + return; + } + + if(Cmd_Argc() < 2) + { + Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n"); + return; + } + + url = Cmd_Argv(Cmd_Argc() - 1); + end = Cmd_Argc(); + + for(i = 1; i != end; ++i) + { + const char *a = Cmd_Argv(i); + if(!strcmp(a, "--info")) + { + Curl_Info_f(); + return; + } + else if(!strcmp(a, "--cancel")) + { + if(i == end - 1) // last argument + Curl_CancelAll(); + else + { + downloadinfo *di = Curl_Find(url); + if(di) + Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK); + else + Con_Print("download not found\n"); + } + return; + } + else if(!strcmp(a, "--pak")) + { + pak = true; + } + else if(!strcmp(a, "--for")) // must be last option + { + for(i = i + 1; i != end - 1; ++i) + { + if(!FS_FileExists(Cmd_Argv(i))) + goto needthefile; // why can't I have a "double break"? + } + // if we get here, we have all the files... + return; + } + else if(!strcmp(a, "--forthismap")) + { + forthismap = true; + } + else if(!strcmp(a, "--as")) + { + if(i < end - 1) + { + ++i; + name = Cmd_Argv(i); + } + } + else if(!strcmp(a, "--clear_autodownload")) + { + // mark all running downloads as "not for this map", so if they + // fail, it does not matter + Curl_Clear_forthismap(); + return; + } + else if(!strcmp(a, "--finish_autodownload")) + { + if(numdownloads_added) + { + char donecommand[256]; + if(cls.netcon) + { + if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect + { + dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address); + Curl_CommandWhenDone(donecommand); + noclear = TRUE; + CL_Disconnect(); + noclear = FALSE; + Curl_CheckCommandWhenDone(); + } + else + Curl_Register_predownload(); + } + } + return; + } + else if(!strncmp(a, "--maxspeed=", 11)) + { + maxspeed = atof(a + 11); + } + else if(*a == '-') + { + Con_Printf("curl: invalid option %s\n", a); + // but we ignore the option + } + } + +needthefile: + Curl_Begin_ToFile(url, maxspeed, name, pak, forthismap); +} + +/* +static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata) +{ + Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer); + Z_Free(buffer); +} + +void Curl_CurlCat_f(void) +{ + unsigned char *buf; + const char *url = Cmd_Argv(1); + buf = Z_Malloc(16384); + Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL); +} +*/ + +/* +==================== +Curl_Init_Commands + +loads the commands and cvars this library uses +==================== +*/ +void Curl_Init_Commands(void) +{ + Cvar_RegisterVariable (&cl_curl_enabled); + Cvar_RegisterVariable (&cl_curl_maxdownloads); + Cvar_RegisterVariable (&cl_curl_maxspeed); + Cvar_RegisterVariable (&sv_curl_defaulturl); + Cvar_RegisterVariable (&sv_curl_serverpackages); + Cvar_RegisterVariable (&sv_curl_maxspeed); + Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path"); + //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)"); +} + +/* +==================== +Curl_GetDownloadInfo + +returns an array of Curl_downloadinfo_t structs for usage by GUIs. +The number of elements in the array is returned in int *nDownloads. +const char **additional_info may be set to a string of additional user +information, or to NULL if no such display shall occur. The returned +array must be freed later using Z_Free. +==================== +*/ +Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info) +{ + int i; + downloadinfo *di; + Curl_downloadinfo_t *downinfo; + static char addinfo[128]; + + if(!curl_dll) + { + *nDownloads = 0; + if(additional_info) + *additional_info = NULL; + return NULL; + } + + i = 0; + for(di = downloads; di; di = di->next) + ++i; + + downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i); + i = 0; + for(di = downloads; di; di = di->next) + { + // do not show infobars for background downloads + if(developer.integer <= 0) + if(di->buffer) + continue; + strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename)); + if(di->curle) + { + downinfo[i].progress = Curl_GetDownloadAmount(di); + downinfo[i].speed = Curl_GetDownloadSpeed(di); + downinfo[i].queued = false; + } + else + { + downinfo[i].queued = true; + } + ++i; + } + + if(additional_info) + { + // TODO: can I clear command_when_done as soon as the first download fails? + if(*command_when_done && !numdownloads_fail && numdownloads_added) + { + if(!strncmp(command_when_done, "connect ", 8)) + dpsnprintf(addinfo, sizeof(addinfo), "(will join %s when done)", command_when_done + 8); + else if(!strcmp(command_when_done, "cl_begindownloads")) + dpsnprintf(addinfo, sizeof(addinfo), "(will enter the game when done)"); + else + dpsnprintf(addinfo, sizeof(addinfo), "(will do '%s' when done)", command_when_done); + *additional_info = addinfo; + } + else + *additional_info = NULL; + } + + *nDownloads = i; + return downinfo; +} + + +/* +==================== +Curl_FindPackURL + +finds the URL where to find a given package. + +For this, it reads a file "curl_urls.txt" of the following format: + + data*.pk3 - + revdm*.pk3 http://revdm/downloads/are/here/ + * http://any/other/stuff/is/here/ + +The URLs should end in /. If not, downloads will still work, but the cached files +can't be just put into the data directory with the same download configuration +(you might want to do this if you want to tag downloaded files from your +server, but you should not). "-" means "don't download". + +If no single pattern matched, the cvar sv_curl_defaulturl is used as download +location instead. + +Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in +this file for obvious reasons. +==================== +*/ +static const char *Curl_FindPackURL(const char *filename) +{ + static char foundurl[1024]; + fs_offset_t filesize; + char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize); + if(buf && filesize) + { + // read lines of format "pattern url" + char *p = buf; + char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL; + qboolean eof = false; + + pattern = p; + while(!eof) + { + switch(*p) + { + case 0: + eof = true; + // fallthrough + case '\n': + case '\r': + if(pattern && url && patternend) + { + if(!urlend) + urlend = p; + *patternend = 0; + *urlend = 0; + if(matchpattern(filename, pattern, true)) + { + strlcpy(foundurl, url, sizeof(foundurl)); + Z_Free(buf); + return foundurl; + } + } + pattern = NULL; + patternend = NULL; + url = NULL; + urlend = NULL; + break; + case ' ': + case '\t': + if(pattern && !patternend) + patternend = p; + else if(url && !urlend) + urlend = p; + break; + default: + if(!pattern) + pattern = p; + else if(pattern && patternend && !url) + url = p; + break; + } + ++p; + } + } + if(buf) + Z_Free(buf); + return sv_curl_defaulturl.string; +} + +typedef struct requirement_s +{ + struct requirement_s *next; + char filename[MAX_OSPATH]; +} +requirement; +static requirement *requirements = NULL; + + +/* +==================== +Curl_RequireFile + +Adds the given file to the list of requirements. +==================== +*/ +void Curl_RequireFile(const char *filename) +{ + requirement *req = (requirement *) Z_Malloc(sizeof(*requirements)); + req->next = requirements; + strlcpy(req->filename, filename, sizeof(req->filename)); + requirements = req; +} + +/* +==================== +Curl_ClearRequirements + +Clears the list of required files for playing on the current map. +This should be called at every map change. +==================== +*/ +void Curl_ClearRequirements(void) +{ + while(requirements) + { + requirement *req = requirements; + requirements = requirements->next; + Z_Free(req); + } +} + +/* +==================== +Curl_SendRequirements + +Makes the current host_clients download all files he needs. +This is done by sending him the following console commands: + + curl --clear_autodownload + curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3 + curl --finish_autodownload +==================== +*/ +static qboolean Curl_SendRequirement(const char *filename, qboolean foundone, char *sendbuffer, size_t sendbuffer_len) +{ + const char *p; + const char *thispack = FS_WhichPack(filename); + const char *packurl; + + if(!thispack) + return false; + + p = strrchr(thispack, '/'); + if(p) + thispack = p + 1; + + packurl = Curl_FindPackURL(thispack); + + if(packurl && *packurl && strcmp(packurl, "-")) + { + if(!foundone) + strlcat(sendbuffer, "curl --clear_autodownload\n", sendbuffer_len); + + strlcat(sendbuffer, "curl --pak --forthismap --as ", sendbuffer_len); + strlcat(sendbuffer, thispack, sendbuffer_len); + if(sv_curl_maxspeed.value > 0) + dpsnprintf(sendbuffer + strlen(sendbuffer), sendbuffer_len - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value); + strlcat(sendbuffer, " --for ", sendbuffer_len); + strlcat(sendbuffer, filename, sendbuffer_len); + strlcat(sendbuffer, " ", sendbuffer_len); + strlcat(sendbuffer, packurl, sendbuffer_len); + strlcat(sendbuffer, thispack, sendbuffer_len); + strlcat(sendbuffer, "\n", sendbuffer_len); + + return true; + } + + return false; +} +void Curl_SendRequirements(void) +{ + // for each requirement, find the pack name + char sendbuffer[4096] = ""; + requirement *req; + qboolean foundone = false; + const char *p; + + for(req = requirements; req; req = req->next) + foundone = Curl_SendRequirement(req->filename, foundone, sendbuffer, sizeof(sendbuffer)) || foundone; + + p = sv_curl_serverpackages.string; + while(COM_ParseToken_Simple(&p, false, false)) + foundone = Curl_SendRequirement(com_token, foundone, sendbuffer, sizeof(sendbuffer)) || foundone; + + if(foundone) + strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer)); + + if(strlen(sendbuffer) + 1 < sizeof(sendbuffer)) + Host_ClientCommands("%s", sendbuffer); + else + Con_Printf("Could not initiate autodownload due to URL buffer overflow\n"); +} diff --git a/misc/source/darkplaces-src/libcurl.h b/misc/source/darkplaces-src/libcurl.h new file mode 100644 index 00000000..ed05551c --- /dev/null +++ b/misc/source/darkplaces-src/libcurl.h @@ -0,0 +1,47 @@ +enum +{ + CURLCBSTATUS_OK = 0, + CURLCBSTATUS_FAILED = -1, // failed for generic reason (e.g. buffer too small) + CURLCBSTATUS_ABORTED = -2, // aborted by curl --cancel + CURLCBSTATUS_SERVERERROR = -3, // only used if no HTTP status code is available + CURLCBSTATUS_UNKNOWN = -4 // should never happen +}; +typedef void (*curl_callback_t) (int status, size_t length_received, unsigned char *buffer, void *cbdata); +// code is one of the CURLCBSTATUS constants, or the HTTP error code (when > 0). + +void Curl_Run(void); +qboolean Curl_Running(void); +qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, qboolean ispak, qboolean forthismap); + +qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); +qboolean Curl_Begin_ToMemory_POST(const char *URL, const char *extraheaders, double maxspeed, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); + // NOTE: if these return false, the callback will NOT get called, so free your buffer then! +void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata); + // removes all downloads with the given callback and cbdata (this does NOT call the callbacks!) + +void Curl_Init(void); +void Curl_Init_Commands(void); +void Curl_Shutdown(void); +void Curl_CancelAll(void); +void Curl_Clear_forthismap(void); +qboolean Curl_Have_forthismap(void); +void Curl_Register_predownload(void); + +void Curl_ClearRequirements(void); +void Curl_RequireFile(const char *filename); +void Curl_SendRequirements(void); + +typedef struct Curl_downloadinfo_s +{ + char filename[MAX_QPATH]; + double progress; + double speed; + qboolean queued; +} +Curl_downloadinfo_t; +Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info); + // this may and should be Z_Free()ed + // the result is actually an array + // an additional info string may be returned in additional_info as a + // pointer to a static string (but the argument may be NULL if the caller + // does not care) diff --git a/misc/source/darkplaces-src/makefile b/misc/source/darkplaces-src/makefile new file mode 100644 index 00000000..84db7512 --- /dev/null +++ b/misc/source/darkplaces-src/makefile @@ -0,0 +1,328 @@ +##### DP_MAKE_TARGET autodetection and arch specific variables ##### + +ifndef DP_MAKE_TARGET + +# Win32 +ifdef WINDIR + DP_MAKE_TARGET=mingw +else + +# UNIXes +DP_ARCH:=$(shell uname) +ifneq ($(filter %BSD,$(DP_ARCH)),) + DP_MAKE_TARGET=bsd +else +ifeq ($(DP_ARCH), Darwin) + DP_MAKE_TARGET=macosx +else +ifeq ($(DP_ARCH), SunOS) + DP_MAKE_TARGET=sunos +else + DP_MAKE_TARGET=linux + +endif # ifeq ($(DP_ARCH), SunOS) +endif # ifeq ($(DP_ARCH), Darwin) +endif # ifneq ($(filter %BSD,$(DP_ARCH)),) +endif # ifdef windir +endif # ifndef DP_MAKE_TARGET + +# If we're not on compiling for Win32, we need additional information +ifneq ($(DP_MAKE_TARGET), mingw) + DP_ARCH:=$(shell uname) + DP_MACHINE:=$(shell uname -m) +endif + + +# Command used to delete files +ifdef windir + CMD_RM=del +else + CMD_RM=$(CMD_UNIXRM) +endif + +# 64bits AMD CPUs use another lib directory +ifeq ($(DP_MACHINE),x86_64) + UNIX_X11LIBPATH:=/usr/X11R6/lib64 +else + UNIX_X11LIBPATH:=/usr/X11R6/lib +endif + +# default targets +TARGETS_DEBUG=sv-debug cl-debug sdl-debug +TARGETS_PROFILE=sv-profile cl-profile sdl-profile +TARGETS_RELEASE=sv-release cl-release sdl-release +TARGETS_RELEASE_PROFILE=sv-release-profile cl-release-profile sdl-release-profile +TARGETS_NEXUIZ=sv-nexuiz cl-nexuiz sdl-nexuiz + +# Linux configuration +ifeq ($(DP_MAKE_TARGET), linux) + DEFAULT_SNDAPI=ALSA + OBJ_CD=$(OBJ_LINUXCD) + + OBJ_CL=$(OBJ_GLX) + OBJ_ICON= + OBJ_ICON_NEXUIZ= + + LDFLAGS_CL=$(LDFLAGS_LINUXCL) + LDFLAGS_SV=$(LDFLAGS_LINUXSV) + LDFLAGS_SDL=$(LDFLAGS_LINUXSDL) + + SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11) + SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11) + SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11) + + EXE_CL=$(EXE_UNIXCL) + EXE_SV=$(EXE_UNIXSV) + EXE_SDL=$(EXE_UNIXSDL) + EXE_CLNEXUIZ=$(EXE_UNIXCLNEXUIZ) + EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) + EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) + + # libjpeg dependency (set these to "" if you want to use dynamic loading instead) + CFLAGS_LIBJPEG=-DLINK_TO_LIBJPEG + LIB_JPEG=-ljpeg +endif + +# Mac OS X configuration +ifeq ($(DP_MAKE_TARGET), macosx) + DEFAULT_SNDAPI=COREAUDIO + OBJ_CD=$(OBJ_MACOSXCD) + + OBJ_CL=$(OBJ_AGL) + OBJ_ICON= + OBJ_ICON_NEXUIZ= + + LDFLAGS_CL=$(LDFLAGS_MACOSXCL) + LDFLAGS_SV=$(LDFLAGS_MACOSXSV) + LDFLAGS_SDL=$(LDFLAGS_MACOSXSDL) + + SDLCONFIG_CFLAGS=$(SDLCONFIG_MACOSXCFLAGS) + SDLCONFIG_LIBS=$(SDLCONFIG_MACOSXLIBS) + SDLCONFIG_STATICLIBS=$(SDLCONFIG_MACOSXSTATICLIBS) + + EXE_CL=$(EXE_MACOSXCL) + EXE_SV=$(EXE_UNIXSV) + EXE_SDL=$(EXE_UNIXSDL) + EXE_CLNEXUIZ=$(EXE_MACOSXCLNEXUIZ) + EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) + EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) + + ifeq ($(word 2, $(filter -arch, $(CC))), -arch) + CFLAGS_MAKEDEP= + endif + + # libjpeg dependency (set these to "" if you want to use dynamic loading instead) + # we don't currently link to libjpeg on Mac because the OS does not have an easy way to load libjpeg and we provide our own in the .app + CFLAGS_LIBJPEG= + LIB_JPEG= + + # on OS X, we don't build the CL by default because it uses deprecated + # and not-implemented-in-64bit Carbon + TARGETS_DEBUG=sv-debug sdl-debug + TARGETS_PROFILE=sv-profile sdl-profile + TARGETS_RELEASE=sv-release sdl-release + TARGETS_RELEASE_PROFILE=sv-release-profile sdl-release-profile + TARGETS_NEXUIZ=sv-nexuiz sdl-nexuiz +endif + +# SunOS configuration (Solaris) +ifeq ($(DP_MAKE_TARGET), sunos) + DEFAULT_SNDAPI=BSD + OBJ_CD=$(OBJ_SUNOSCD) + + OBJ_CL=$(OBJ_GLX) + OBJ_ICON= + OBJ_ICON_NEXUIZ= + + CFLAGS_EXTRA=$(CFLAGS_SUNOS) + + LDFLAGS_CL=$(LDFLAGS_SUNOSCL) + LDFLAGS_SV=$(LDFLAGS_SUNOSSV) + LDFLAGS_SDL=$(LDFLAGS_SUNOSSDL) + + SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11) + SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11) + SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11) + + EXE_CL=$(EXE_UNIXCL) + EXE_SV=$(EXE_UNIXSV) + EXE_SDL=$(EXE_UNIXSDL) + EXE_CLNEXUIZ=$(EXE_UNIXCLNEXUIZ) + EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) + EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) + + # libjpeg dependency (set these to "" if you want to use dynamic loading instead) + CFLAGS_LIBJPEG=-DLINK_TO_LIBJPEG + LIB_JPEG=-ljpeg +endif + +# BSD configuration +ifeq ($(DP_MAKE_TARGET), bsd) +ifeq ($(DP_ARCH),FreeBSD) + DEFAULT_SNDAPI=OSS +else + DEFAULT_SNDAPI=BSD +endif + OBJ_CD=$(OBJ_BSDCD) + + OBJ_CL=$(OBJ_GLX) + OBJ_ICON= + OBJ_ICON_NEXUIZ= + + LDFLAGS_CL=$(LDFLAGS_BSDCL) + LDFLAGS_SV=$(LDFLAGS_BSDSV) + LDFLAGS_SDL=$(LDFLAGS_BSDSDL) + + SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11) + SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11) + SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11) + + EXE_CL=$(EXE_UNIXCL) + EXE_SV=$(EXE_UNIXSV) + EXE_SDL=$(EXE_UNIXSDL) + EXE_CLNEXUIZ=$(EXE_UNIXCLNEXUIZ) + EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) + EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) + + # libjpeg dependency (set these to "" if you want to use dynamic loading instead) + CFLAGS_LIBJPEG=-DLINK_TO_LIBJPEG + LIB_JPEG=-ljpeg +endif + +# Win32 configuration +ifeq ($(WIN32RELEASE), 1) +# TARGET=i686-pc-mingw32 +# CC=$(TARGET)-g++ +# WINDRES=$(TARGET)-windres + CPUOPTIMIZATIONS=-march=i686 -fno-math-errno -ffinite-math-only -fno-rounding-math -fno-signaling-nans -fno-trapping-math +# CPUOPTIMIZATIONS+=-DUSE_WSPIAPI_H -DSUPPORTIPV6 + LDFLAGS_WINCOMMON=-Wl,--large-address-aware +else + LDFLAGS_WINCOMMON= +endif + +ifeq ($(WIN64RELEASE), 1) +# TARGET=x86_64-pc-mingw32 +# CC=$(TARGET)-g++ +# WINDRES=$(TARGET)-windres +endif + +ifeq ($(D3D), 1) + CFLAGS_D3D=-DSUPPORTD3D -DSUPPORTDIRECTX + CFLAGS_WARNINGS=-Wall + LDFLAGS_D3D=-ld3d9 +else + CFLAGS_D3D= + CFLAGS_WARNINGS=-Wall -Wold-style-definition -Wstrict-prototypes -Wsign-compare -Wdeclaration-after-statement + LDFLAGS_D3D= +endif + + +ifeq ($(DP_MAKE_TARGET), mingw) + DEFAULT_SNDAPI=WIN + OBJ_CD=$(OBJ_WINCD) + + OBJ_CL=$(OBJ_WGL) + OBJ_ICON=darkplaces.o + OBJ_ICON_NEXUIZ=nexuiz.o + + LDFLAGS_CL=$(LDFLAGS_WINCL) + LDFLAGS_SV=$(LDFLAGS_WINSV) + LDFLAGS_SDL=$(LDFLAGS_WINSDL) + + SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) + SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) + SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) + + EXE_CL=$(EXE_WINCL) + EXE_SV=$(EXE_WINSV) + EXE_SDL=$(EXE_WINSDL) + EXE_CLNEXUIZ=$(EXE_WINCLNEXUIZ) + EXE_SVNEXUIZ=$(EXE_WINSVNEXUIZ) + EXE_SDLNEXUIZ=$(EXE_WINSDLNEXUIZ) + + # libjpeg dependency (set these to "" if you want to use dynamic loading instead) + CFLAGS_LIBJPEG=-DLINK_TO_LIBJPEG + LIB_JPEG=-ljpeg +endif + +##### Sound configuration ##### + +ifndef DP_SOUND_API + DP_SOUND_API=$(DEFAULT_SNDAPI) +endif + +# NULL: no sound +ifeq ($(DP_SOUND_API), NULL) + OBJ_SOUND=$(OBJ_SND_NULL) + LIB_SOUND=$(LIB_SND_NULL) +endif + +# OSS: Open Sound System +ifeq ($(DP_SOUND_API), OSS) + OBJ_SOUND=$(OBJ_SND_OSS) + LIB_SOUND=$(LIB_SND_OSS) +endif + +# ALSA: Advanced Linux Sound Architecture +ifeq ($(DP_SOUND_API), ALSA) + OBJ_SOUND=$(OBJ_SND_ALSA) + LIB_SOUND=$(LIB_SND_ALSA) +endif + +# COREAUDIO: Core Audio +ifeq ($(DP_SOUND_API), COREAUDIO) + OBJ_SOUND=$(OBJ_SND_COREAUDIO) + LIB_SOUND=$(LIB_SND_COREAUDIO) +endif + +# BSD: BSD / Sun audio API +ifeq ($(DP_SOUND_API), BSD) + OBJ_SOUND=$(OBJ_SND_BSD) + LIB_SOUND=$(LIB_SND_BSD) +endif + +# WIN: DirectX and Win32 WAVE output +ifeq ($(DP_SOUND_API), WIN) + OBJ_SOUND=$(OBJ_SND_WIN) + LIB_SOUND=$(LIB_SND_WIN) +endif + +ifeq ($(DP_SOUND_API),3DRAS) + OBJ_SOUND=$(OBJ_SND_3DRAS) + LIB_SOUND=$(LIB_SND_3DRAS) +endif + +##### Extra CFLAGS ##### + +CFLAGS_MAKEDEP?=-MMD +ifdef DP_FS_BASEDIR + CFLAGS_FS=-DDP_FS_BASEDIR='\"$(DP_FS_BASEDIR)\"' +else + CFLAGS_FS= +endif + +CFLAGS_PRELOAD= +ifneq ($(DP_MAKE_TARGET), mingw) +ifdef DP_PRELOAD_DEPENDENCIES +# DP_PRELOAD_DEPENDENCIES: when set, link against the libraries needed using -l +# dynamically so they won't get loaded at runtime using dlopen + LDFLAGS_CL+=$(LDFLAGS_UNIXCL_PRELOAD) + LDFLAGS_SV+=$(LDFLAGS_UNIXSV_PRELOAD) + LDFLAGS_SDL+=$(LDFLAGS_UNIXSDL_PRELOAD) + CFLAGS_PRELOAD=$(CFLAGS_UNIX_PRELOAD) +endif +endif + +##### GNU Make specific definitions ##### + +DO_LD=$(CC) -o $@ $^ $(LDFLAGS) + + +##### Definitions shared by all makefiles ##### +include makefile.inc + + +##### Dependency files ##### + +-include *.d diff --git a/misc/source/darkplaces-src/makefile.inc b/misc/source/darkplaces-src/makefile.inc new file mode 100644 index 00000000..dd55442a --- /dev/null +++ b/misc/source/darkplaces-src/makefile.inc @@ -0,0 +1,590 @@ +# Invalid call detection +CHECKLEVEL1 = @if [ "$(LEVEL)" != 1 ]; then $(MAKE) help; false; fi +CHECKLEVEL2 = @if [ "$(LEVEL)" != 2 ]; then $(MAKE) help; false; fi + +# Choose the compiler you want to use +CC?=gcc + +# athlon optimizations +#CPUOPTIMIZATIONS?=-march=athlon +# athlon xp optimizations +#CPUOPTIMIZATIONS?=-march=athlon-xp +# athlon 64 optimizations +#CPUOPTIMIZATIONS?=-march=athlon64 -m32 +# Pentium 3 optimizations +#CPUOPTIMIZATIONS?=-march=pentium3 +# Pentium 4 optimizations +#CPUOPTIMIZATIONS?=-march=pentium4 +# 686 (Pentium Pro/II) optimizations +#CPUOPTIMIZATIONS?=-march=i686 +# No specific CPU (386 compatible) +#CPUOPTIMIZATIONS?= +# Experimental +#CPUOPTIMIZATIONS?=-fno-math-errno -ffinite-math-only -fno-rounding-math -fno-signaling-nans -fassociative-math -freciprocal-math -fno-signed-zeros -fno-trapping-math +# Normal +CPUOPTIMIZATIONS?=-fno-math-errno -ffinite-math-only -fno-rounding-math -fno-signaling-nans -fno-trapping-math +# NOTE: *never* *ever* use the -ffast-math or -funsafe-math-optimizations flag + +# Additional stuff for libode +LIB_ODE=`[ -n "$(DP_ODE_STATIC_LIBDIR)" ] && "$(DP_ODE_STATIC_LIBDIR)/../bin/ode-config" --libs` `[ -n "$(DP_ODE_STATIC_LIBDIR)" ] && echo -lstdc++` +CFLAGS_ODE=`[ -n "$(DP_ODE_STATIC_LIBDIR)" ] && "$(DP_ODE_STATIC_LIBDIR)/../bin/ode-config" --cflags || { [ -n "$(DP_ODE_DYNAMIC)" ] && echo \ -DODE_DYNAMIC; }` `[ -n "$(DP_ODE_STATIC_LIBDIR)" ] && echo -DODE_STATIC` + + +SDL_CONFIG?=sdl-config +SDLCONFIG_UNIXCFLAGS?=`$(SDL_CONFIG) --cflags` +SDLCONFIG_UNIXCFLAGS_X11?= +SDLCONFIG_UNIXLIBS?=`$(SDL_CONFIG) --libs` +SDLCONFIG_UNIXLIBS_X11?=-lX11 +SDLCONFIG_UNIXSTATICLIBS?=`$(SDL_CONFIG) --static-libs` +SDLCONFIG_UNIXSTATICLIBS_X11?=-lX11 +SDLCONFIG_MACOSXCFLAGS=-I/Library/Frameworks/SDL.framework/Headers -I$(HOME)/Library/Frameworks/SDL.framework/Headers +SDLCONFIG_MACOSXLIBS=-F$(HOME)/Library/Frameworks/ -framework SDL -framework Cocoa $(SDLCONFIG_MACOSXCFLAGS) +SDLCONFIG_MACOSXSTATICLIBS=-F$(HOME)/Library/Frameworks/ -framework SDL -framework Cocoa $(SDLCONFIG_MACOSXCFLAGS) +STRIP?=strip + + +###### Sound and audio CD ##### + +OBJ_SND_COMMON=snd_main.o snd_mem.o snd_mix.o snd_ogg.o snd_wav.o snd_modplug.o + +# statically loading d0_blind_id +LIB_CRYPTO=`[ -n "$(DP_CRYPTO_STATIC_LIBDIR)" ] && echo \ $(DP_CRYPTO_STATIC_LIBDIR)/libd0_blind_id.a\ $(DP_CRYPTO_STATIC_LIBDIR)/libgmp.a` +CFLAGS_CRYPTO=`[ -n "$(DP_CRYPTO_STATIC_LIBDIR)" ] && echo \ -I$(DP_CRYPTO_STATIC_LIBDIR)/../include\ -DCRYPTO_STATIC` +LIB_CRYPTO_RIJNDAEL=`[ -n "$(DP_CRYPTO_RIJNDAEL_STATIC_LIBDIR)" ] && echo \ $(DP_CRYPTO_RIJNDAEL_STATIC_LIBDIR)/libd0_rijndael.a` +CFLAGS_CRYPTO_RIJNDAEL=`[ -n "$(DP_CRYPTO_RIJNDAEL_STATIC_LIBDIR)" ] && echo \ -I$(DP_CRYPTO_RIJNDAEL_STATIC_LIBDIR)/../include\ -DCRYPTO_RIJNDAEL_STATIC` + +# Additional stuff for libmodplug +LIB_SND_MODPLUG=`[ -n "$(DP_MODPLUG_STATIC_LIBDIR)" ] && echo \ $(DP_MODPLUG_STATIC_LIBDIR)/libmodplug.a\ -lstdc++` +CFLAGS_SND_MODPLUG=`[ -n "$(DP_MODPLUG_STATIC_LIBDIR)" ] && echo \ -I$(DP_MODPLUG_STATIC_LIBDIR)/../include\ -DSND_MODPLUG_STATIC` + +# No sound +OBJ_SND_NULL=snd_null.o +LIB_SND_NULL= + +# Open Sound System (Linux, FreeBSD and Solaris) +OBJ_SND_OSS=$(OBJ_SND_COMMON) snd_oss.o +LIB_SND_OSS=$(LIB_SND_MODPLUG) + +# Advanced Linux Sound Architecture (Linux) +OBJ_SND_ALSA=$(OBJ_SND_COMMON) snd_alsa.o +LIB_SND_ALSA=-lasound $(LIB_SND_MODPLUG) + +# Core Audio (Mac OS X) +OBJ_SND_COREAUDIO=$(OBJ_SND_COMMON) snd_coreaudio.o +LIB_SND_COREAUDIO=-framework CoreAudio $(LIB_SND_MODPLUG) + +# BSD / Sun audio API (NetBSD and OpenBSD) +OBJ_SND_BSD=$(OBJ_SND_COMMON) snd_bsd.o +LIB_SND_BSD=$(LIB_SND_MODPLUG) + +# DirectX and Win32 WAVE output (Win32) +OBJ_SND_WIN=$(OBJ_SND_COMMON) snd_win.o +LIB_SND_WIN=$(LIB_SND_MODPLUG) + +# Qantourisc's 3D Realtime Acoustic Lib (3D RAS) +OBJ_SND_3DRAS=snd_3dras.o +LIB_SND_3DRAS= + +# CD objects +OBJ_NOCD=cd_null.o + + +###### Common objects and flags ##### + +# Common objects +OBJ_COMMON= \ + bih.o \ + cap_avi.o \ + cap_ogg.o \ + cd_shared.o \ + crypto.o \ + cl_collision.o \ + cl_demo.o \ + cl_dyntexture.o \ + cl_gecko.o \ + cl_input.o \ + cl_main.o \ + cl_parse.o \ + cl_particles.o \ + cl_screen.o \ + cl_video.o \ + clvm_cmds.o \ + cmd.o \ + collision.o \ + common.o \ + console.o \ + csprogs.o \ + curves.o \ + cvar.o \ + dpsoftrast.o \ + dpvsimpledecode.o \ + filematch.o \ + fractalnoise.o \ + fs.o \ + ft2.o \ + utf8lib.o \ + gl_backend.o \ + gl_draw.o \ + gl_rmain.o \ + gl_rsurf.o \ + gl_textures.o \ + hmac.o \ + host.o \ + host_cmd.o \ + image.o \ + image_png.o \ + jpeg.o \ + keys.o \ + lhnet.o \ + libcurl.o \ + mathlib.o \ + matrixlib.o \ + mdfour.o \ + menu.o \ + meshqueue.o \ + mod_skeletal_animatevertices_sse.o \ + mod_skeletal_animatevertices_generic.o \ + model_alias.o \ + model_brush.o \ + model_shared.o \ + model_sprite.o \ + mvm_cmds.o \ + netconn.o \ + palette.o \ + polygon.o \ + portals.o \ + protocol.o \ + prvm_cmds.o \ + prvm_edict.o \ + prvm_exec.o \ + r_explosion.o \ + r_lerpanim.o \ + r_lightning.o \ + r_modules.o \ + r_shadow.o \ + r_sky.o \ + r_sprites.o \ + sbar.o \ + sv_demo.o \ + sv_main.o \ + sv_move.o \ + sv_phys.o \ + sv_user.o \ + svbsp.o \ + svvm_cmds.o \ + sys_shared.o \ + vid_shared.o \ + view.o \ + wad.o \ + world.o \ + zone.o + +# note that builddate.c is very intentionally not compiled to a .o before +# being linked, because it should be recompiled every time an executable is +# built to give the executable a proper date string +OBJ_SV= builddate.c sys_linux.o vid_null.o thread_null.o $(OBJ_SND_NULL) $(OBJ_NOCD) $(OBJ_COMMON) +OBJ_SDL= builddate.c sys_sdl.o vid_sdl.o thread_sdl.o $(OBJ_SND_COMMON) snd_sdl.o cd_sdl.o $(OBJ_COMMON) + + +# Compilation +CFLAGS_COMMON=$(CFLAGS_MAKEDEP) $(CFLAGS_PRELOAD) $(CFLAGS_FS) $(CFLAGS_WARNINGS) $(CFLAGS_LIBJPEG) $(CFLAGS_D3D) -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES +CFLAGS_DEBUG=-ggdb +CFLAGS_PROFILE=-g -pg -ggdb -fprofile-arcs +CFLAGS_RELEASE= +CFLAGS_RELEASE_PROFILE=-fbranch-probabilities +CFLAGS_SDL=$(SDLCONFIG_CFLAGS) + +CFLAGS_SSE=-msse +CFLAGS_SSE2=-msse2 + +OPTIM_DEBUG=$(CPUOPTIMIZATIONS) +#OPTIM_RELEASE=-O2 -fno-strict-aliasing -ffast-math -funroll-loops $(CPUOPTIMIZATIONS) +#OPTIM_RELEASE=-O2 -fno-strict-aliasing -fno-math-errno -fno-trapping-math -ffinite-math-only -fno-signaling-nans -fcx-limited-range -funroll-loops $(CPUOPTIMIZATIONS) +#OPTIM_RELEASE=-O2 -fno-strict-aliasing -funroll-loops $(CPUOPTIMIZATIONS) +#OPTIM_RELEASE=-O2 -fno-strict-aliasing $(CPUOPTIMIZATIONS) +OPTIM_RELEASE=-O3 -fno-strict-aliasing $(CPUOPTIMIZATIONS) +# NOTE: *never* *ever* use the -ffast-math or -funsafe-math-optimizations flag + +DO_CC=$(CC) $(CFLAGS) -c $< -o $@ + + +# Link +LDFLAGS_DEBUG=-g -ggdb $(OPTIM_DEBUG) -DSVNREVISION=`{ test -d .svn && svnversion; } || { test -d .git && git describe --always; } || echo -` -DBUILDTYPE=debug +LDFLAGS_PROFILE=-g -pg -fprofile-arcs $(OPTIM_RELEASE) -DSVNREVISION=`{ test -d .svn && svnversion; } || { test -d .git && git describe --always; } || echo -` -DBUILDTYPE=profile +LDFLAGS_RELEASE=$(OPTIM_RELEASE) -DSVNREVISION=`{ test -d .svn && svnversion; } || { test -d .git && git describe --always; } || echo -` -DBUILDTYPE=release + + +##### UNIX specific variables ##### + +OBJ_GLX= builddate.c sys_linux.o vid_glx.o thread_pthread.o keysym2ucs.o $(OBJ_SOUND) $(OBJ_CD) $(OBJ_COMMON) + +LDFLAGS_UNIXCOMMON=-lm $(LIB_ODE) $(LIB_JPEG) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) +LDFLAGS_UNIXCL=-L$(UNIX_X11LIBPATH) -lX11 -lXpm -lXext -lXxf86dga -lXxf86vm -pthread $(LIB_SOUND) +LDFLAGS_UNIXCL_PRELOAD=-lz -ljpeg -lpng -logg -ltheora -lvorbis -lvorbisenc -lvorbisfile -lcurl -lmodplug +LDFLAGS_UNIXSV_PRELOAD=-lz -ljpeg -lpng -lcurl +LDFLAGS_UNIXSDL_PRELOAD=-lz -ljpeg -lpng -logg -ltheora -lvorbis -lvorbisenc -lvorbisfile -lcurl -lmodplug +CFLAGS_UNIX_PRELOAD=-DPREFER_PRELOAD + +LDFLAGS_UNIXSDL=$(SDLCONFIG_LIBS) $(LIB_SND_MODPLUG) +EXE_UNIXCL=darkplaces-glx +EXE_UNIXSV=darkplaces-dedicated +EXE_UNIXSDL=darkplaces-sdl +EXE_UNIXCLNEXUIZ=nexuiz-glx +EXE_UNIXSVNEXUIZ=nexuiz-dedicated +EXE_UNIXSDLNEXUIZ=nexuiz-sdl + +CMD_UNIXRM=rm -rf + + +##### Linux specific variables ##### + +# If you want CD sound in Linux +OBJ_LINUXCD=cd_linux.o +# If you want no CD audio +#OBJ_LINUXCD=$(OBJ_NOCD) + +# Link +LDFLAGS_LINUXCL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl $(LDFLAGS_UNIXCL) +LDFLAGS_LINUXSV=$(LDFLAGS_UNIXCOMMON) -lrt -ldl +LDFLAGS_LINUXSDL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl $(LDFLAGS_UNIXSDL) + + +##### Mac OS X specific variables ##### + +# No CD support available +OBJ_MACOSXCD=$(OBJ_NOCD) + +# Link +LDFLAGS_MACOSXCL=$(LDFLAGS_UNIXCOMMON) -ldl -framework IOKit -framework Carbon $(LIB_SOUND) +LDFLAGS_MACOSXSV=$(LDFLAGS_UNIXCOMMON) -ldl +LDFLAGS_MACOSXSDL=$(LDFLAGS_UNIXCOMMON) -ldl -framework IOKit $(SDLCONFIG_STATICLIBS) SDLMain.m + +OBJ_AGL= builddate.c sys_linux.o vid_agl.o thread_null.o $(OBJ_SOUND) $(OBJ_CD) $(OBJ_COMMON) + +EXE_MACOSXCL=darkplaces-agl +EXE_MACOSXCLNEXUIZ=nexuiz-agl + + +##### SunOS specific variables ##### + +# No CD support available +OBJ_SUNOSCD=$(OBJ_NOCD) + +CFLAGS_SUNOS=-I/usr/lib/oss/include -DBSD_COMP -DSUNOS + +# Link +LDFLAGS_SUNOSCL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -lsocket -lnsl -R$(UNIX_X11LIBPATH) -L$(UNIX_X11LIBPATH) -lX11 -lXpm -lXext -lXxf86vm $(LIB_SOUND) +LDFLAGS_SUNOSSV=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -lsocket -lnsl +LDFLAGS_SUNOSSDL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -lsocket -lnsl $(LDFLAGS_UNIXSDL) + + +##### BSD specific variables ##### + +#if you want CD sound in BSD +OBJ_BSDCD=cd_bsd.o +#if you want no CD audio +#OBJ_BSDCD=$(OBJ_NOCD) + +# Link +LDFLAGS_BSDCL=$(LDFLAGS_UNIXCOMMON) -lutil $(LDFLAGS_UNIXCL) +LDFLAGS_BSDSV=$(LDFLAGS_UNIXCOMMON) +LDFLAGS_BSDSDL=$(LDFLAGS_UNIXCOMMON) $(LDFLAGS_UNIXSDL) + + +##### Win32 specific variables ##### + +WINDRES ?= windres +#if you want CD sound in Win32 +OBJ_WINCD=cd_win.o +#if you want no CD audio +#OBJ_WINCD=$(OBJ_NOCD) + +OBJ_WGL= builddate.c sys_win.o vid_wgl.o thread_null.o $(OBJ_SND_WIN) $(OBJ_WINCD) $(OBJ_COMMON) + +# Link +# see LDFLAGS_WINCOMMON in makefile +LDFLAGS_WINCL=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) -mwindows -lwinmm -luser32 -lgdi32 -ldxguid -ldinput -lcomctl32 -lws2_32 $(LDFLAGS_D3D) $(LIB_JPEG) +LDFLAGS_WINSV=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) -mconsole -lwinmm -lws2_32 $(LIB_JPEG) +LDFLAGS_WINSDL=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) $(SDLCONFIG_LIBS) $(LIB_SND_MODPLUG) -lwinmm -lws2_32 $(LIB_JPEG) +EXE_WINCL=darkplaces.exe +EXE_WINSV=darkplaces-dedicated.exe +EXE_WINSDL=darkplaces-sdl.exe +EXE_WINCLNEXUIZ=nexuiz.exe +EXE_WINSVNEXUIZ=nexuiz-dedicated.exe +EXE_WINSDLNEXUIZ=nexuiz-sdl.exe + + +##### Commands ##### + +.PHONY : clean clean-profile help \ + debug profile release \ + cl-debug cl-profile cl-release \ + sv-debug sv-profile sv-release \ + sdl-debug sdl-profile sdl-release + +help: + @echo + @echo "===== Choose one =====" + @echo "* $(MAKE) clean : delete all files produced by the build except" + @echo " profiling information" + @echo "* $(MAKE) clean-profile : delete all files produced by the build, including" + @echo " profiling informaiton" + @echo "* $(MAKE) help : this help" + @echo "* $(MAKE) debug : make client and server binaries (debug versions)" + @echo "* $(MAKE) profile : make client and server binaries (profile versions)" + @echo "* $(MAKE) release : make client and server binaries (release versions)" + @echo "* $(MAKE) release-profile : make client and server binaries (release versions)" + @echo " with profileing optomizations) The profiled" + @echo " version of the program must have been" + @echo " previously compiled" + @echo "* $(MAKE) nexuiz : make client and server binaries with nexuiz icon" + @echo " (release versions)" + @echo "* $(MAKE) cl-debug : make client (debug version)" + @echo "* $(MAKE) cl-profile : make client (profile version)" + @echo "* $(MAKE) cl-release : make client (release version)" + @echo "* $(MAKE) cl-release-profile : make client (release version)" + @echo "* $(MAKE) cl-nexuiz : make client with nexuiz icon (release version)" + @echo "* $(MAKE) sv-debug : make dedicated server (debug version)" + @echo "* $(MAKE) sv-profile : make dedicated server (profile version)" + @echo "* $(MAKE) sv-release : make dedicated server (release version)" + @echo "* $(MAKE) sv-release-profile : make dedicated server (release version)" + @echo "* $(MAKE) sv-nexuiz : make dedicated server with nexuiz icon" + @echo " (release version)" + @echo "* $(MAKE) sdl-debug : make SDL client (debug version)" + @echo "* $(MAKE) sdl-profile : make SDL client (profile version)" + @echo "* $(MAKE) sdl-profile-profile : make SDL client (profile version)" + @echo "* $(MAKE) sdl-release : make SDL client (release version)" + @echo "* $(MAKE) sdl-nexuiz : make SDL client with nexuiz icon (release version)" + @echo + +debug : + $(MAKE) $(TARGETS_DEBUG) + +profile : + $(MAKE) $(TARGETS_PROFILE) + +release : + $(MAKE) $(TARGETS_RELEASE) + +release-profile : + $(MAKE) $(TARGETS_RELEASE_PROFILE) + +nexuiz : + $(MAKE) $(TARGETS_NEXUIZ) + +cl-debug : + $(MAKE) bin-debug \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_CL)" LDFLAGS_COMMON="$(LDFLAGS_CL)" LEVEL=1 + +cl-profile : + $(MAKE) bin-profile \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_CL)" LDFLAGS_COMMON="$(LDFLAGS_CL)" LEVEL=1 + +cl-release : + $(MAKE) bin-release \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_CL)" LDFLAGS_COMMON="$(LDFLAGS_CL)" LEVEL=1 + +cl-release-profile : + $(MAKE) bin-release-profile \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_CL)" LDFLAGS_COMMON="$(LDFLAGS_CL)" LEVEL=1 + +cl-nexuiz : + $(MAKE) bin-release \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_CLNEXUIZ)" LDFLAGS_COMMON="$(LDFLAGS_CL)" LEVEL=1 + +sv-debug : + $(MAKE) bin-debug \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SV)" LDFLAGS_COMMON="$(LDFLAGS_SV)" LEVEL=1 + +sv-profile : + $(MAKE) bin-profile \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SV)" LDFLAGS_COMMON="$(LDFLAGS_SV)" LEVEL=1 + +sv-release : + $(MAKE) bin-release \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SV)" LDFLAGS_COMMON="$(LDFLAGS_SV)" LEVEL=1 + +sv-release-profile : + $(MAKE) bin-release-profile \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SV)" LDFLAGS_COMMON="$(LDFLAGS_SV)" LEVEL=1 + +sv-nexuiz : + $(MAKE) bin-release \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SVNEXUIZ)" LDFLAGS_COMMON="$(LDFLAGS_SV)" LEVEL=1 + +sdl-debug : + $(MAKE) bin-debug \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SDL)" LDFLAGS_COMMON="$(LDFLAGS_SDL)" LEVEL=1 + +sdl-profile : + $(MAKE) bin-profile \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SDL)" LDFLAGS_COMMON="$(LDFLAGS_SDL)" LEVEL=1 + +sdl-release : + $(MAKE) bin-release \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SDL)" LDFLAGS_COMMON="$(LDFLAGS_SDL)" LEVEL=1 + +sdl-release-profile : + $(MAKE) bin-release-profile \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SDL)" LDFLAGS_COMMON="$(LDFLAGS_SDL)" LEVEL=1 + +sdl-nexuiz : + $(MAKE) bin-release \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + EXE="$(EXE_SDLNEXUIZ)" LDFLAGS_COMMON="$(LDFLAGS_SDL)" LEVEL=1 + +bin-debug : + $(CHECKLEVEL1) + @echo + @echo "========== $(EXE) (debug) ==========" + $(MAKE) $(EXE) \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + CFLAGS="$(CFLAGS_COMMON) $(CFLAGS_EXTRA) $(CFLAGS_DEBUG) $(OPTIM_DEBUG)"\ + LDFLAGS="$(LDFLAGS_DEBUG) $(LDFLAGS_COMMON)" LEVEL=2 + +bin-profile : + $(CHECKLEVEL1) + @echo + @echo "========== $(EXE) (profile) ==========" + $(MAKE) $(EXE) \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + CFLAGS="$(CFLAGS_COMMON) $(CFLAGS_EXTRA) $(CFLAGS_PROFILE) $(OPTIM_RELEASE)"\ + LDFLAGS="$(LDFLAGS_PROFILE) $(LDFLAGS_COMMON)" LEVEL=2 + +bin-release : + $(CHECKLEVEL1) + @echo + @echo "========== $(EXE) (release) ==========" + $(MAKE) $(EXE) \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + CFLAGS="$(CFLAGS_COMMON) $(CFLAGS_EXTRA) $(CFLAGS_RELEASE) $(OPTIM_RELEASE)"\ + LDFLAGS="$(LDFLAGS_RELEASE) $(LDFLAGS_COMMON)" LEVEL=2 + $(STRIP) $(EXE) + +bin-release-profile : + $(CHECKLEVEL1) + @echo + @echo "========== $(EXE) (release) ==========" + $(MAKE) $(EXE) \ + DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ + CFLAGS="$(CFLAGS_COMMON) $(CFLAGS_EXTRA) $(CFLAGS_RELEASE_PROFILE) $(OPTIM_RELEASE)"\ + LDFLAGS="$(LDFLAGS_RELEASE) $(LDFLAGS_COMMON)" LEVEL=2 + $(STRIP) $(EXE) + + +snd_modplug.o: snd_modplug.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_SND_MODPLUG) + +world.o: world.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_ODE) + +vid_glx.o: vid_glx.c + $(CHECKLEVEL2) + $(DO_CC) -I/usr/X11R6/include + +keysym2ucs.o: keysym2ucs.c + $(CHECKLEVEL2) + $(DO_CC) -I/usr/X11R6/include + +vid_sdl.o: vid_sdl.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_SDL) + +sys_sdl.o: sys_sdl.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_SDL) + +snd_sdl.o: snd_sdl.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_SDL) + +thread_sdl.o: thread_sdl.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_SDL) + +snd_3dras.o: snd_3dras.c + $(CHECKLEVEL2) + $(DO_CC) + +cd_sdl.o: cd_sdl.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_SDL) + +crypto.o: crypto.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_CRYPTO) $(CFLAGS_CRYPTO_RIJNDAEL) + +mod_skeletal_animatevertices_sse.o: mod_skeletal_animatevertices_sse.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_SSE) + +dpsoftrast.o: dpsoftrast.c + $(CHECKLEVEL2) + $(DO_CC) $(CFLAGS_SSE2) + +darkplaces.o: %.o : %.rc + $(CHECKLEVEL2) + $(WINDRES) -o $@ $< + +nexuiz.o: %.o : %.rc + $(CHECKLEVEL2) + $(WINDRES) -o $@ $< + +.c.o: + $(CHECKLEVEL2) + $(DO_CC) + +$(EXE_CL): $(OBJ_CL) $(OBJ_ICON) + $(CHECKLEVEL2) + $(DO_LD) + +$(EXE_SV): $(OBJ_SV) $(OBJ_ICON) + $(CHECKLEVEL2) + $(DO_LD) + +$(EXE_SDL): $(OBJ_SDL) $(OBJ_ICON) + $(CHECKLEVEL2) + $(DO_LD) + +$(EXE_CLNEXUIZ): $(OBJ_CL) $(OBJ_ICON_NEXUIZ) + $(CHECKLEVEL2) + $(DO_LD) + +$(EXE_SVNEXUIZ): $(OBJ_SV) $(OBJ_ICON_NEXUIZ) + $(CHECKLEVEL2) + $(DO_LD) + +$(EXE_SDLNEXUIZ): $(OBJ_SDL) $(OBJ_ICON_NEXUIZ) + $(CHECKLEVEL2) + $(DO_LD) + +clean: + -$(CMD_RM) $(EXE_CL) + -$(CMD_RM) $(EXE_SV) + -$(CMD_RM) $(EXE_SDL) + -$(CMD_RM) $(EXE_CLNEXUIZ) + -$(CMD_RM) $(EXE_SVNEXUIZ) + -$(CMD_RM) $(EXE_SDLNEXUIZ) + -$(CMD_RM) *.o + -$(CMD_RM) *.d + +clean-profile: clean + -$(CMD_RM) *.gcda + -$(CMD_RM) *.gcno + diff --git a/misc/source/darkplaces-src/mathlib.c b/misc/source/darkplaces-src/mathlib.c new file mode 100644 index 00000000..dae0de50 --- /dev/null +++ b/misc/source/darkplaces-src/mathlib.c @@ -0,0 +1,767 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// mathlib.c -- math primitives + +#include "quakedef.h" + +#include + +vec3_t vec3_origin = {0,0,0}; +float ixtable[4096]; + +/*-----------------------------------------------------------------*/ + +float m_bytenormals[NUMVERTEXNORMALS][3] = +{ +{-0.525731, 0.000000, 0.850651}, {-0.442863, 0.238856, 0.864188}, +{-0.295242, 0.000000, 0.955423}, {-0.309017, 0.500000, 0.809017}, +{-0.162460, 0.262866, 0.951056}, {0.000000, 0.000000, 1.000000}, +{0.000000, 0.850651, 0.525731}, {-0.147621, 0.716567, 0.681718}, +{0.147621, 0.716567, 0.681718}, {0.000000, 0.525731, 0.850651}, +{0.309017, 0.500000, 0.809017}, {0.525731, 0.000000, 0.850651}, +{0.295242, 0.000000, 0.955423}, {0.442863, 0.238856, 0.864188}, +{0.162460, 0.262866, 0.951056}, {-0.681718, 0.147621, 0.716567}, +{-0.809017, 0.309017, 0.500000}, {-0.587785, 0.425325, 0.688191}, +{-0.850651, 0.525731, 0.000000}, {-0.864188, 0.442863, 0.238856}, +{-0.716567, 0.681718, 0.147621}, {-0.688191, 0.587785, 0.425325}, +{-0.500000, 0.809017, 0.309017}, {-0.238856, 0.864188, 0.442863}, +{-0.425325, 0.688191, 0.587785}, {-0.716567, 0.681718, -0.147621}, +{-0.500000, 0.809017, -0.309017}, {-0.525731, 0.850651, 0.000000}, +{0.000000, 0.850651, -0.525731}, {-0.238856, 0.864188, -0.442863}, +{0.000000, 0.955423, -0.295242}, {-0.262866, 0.951056, -0.162460}, +{0.000000, 1.000000, 0.000000}, {0.000000, 0.955423, 0.295242}, +{-0.262866, 0.951056, 0.162460}, {0.238856, 0.864188, 0.442863}, +{0.262866, 0.951056, 0.162460}, {0.500000, 0.809017, 0.309017}, +{0.238856, 0.864188, -0.442863}, {0.262866, 0.951056, -0.162460}, +{0.500000, 0.809017, -0.309017}, {0.850651, 0.525731, 0.000000}, +{0.716567, 0.681718, 0.147621}, {0.716567, 0.681718, -0.147621}, +{0.525731, 0.850651, 0.000000}, {0.425325, 0.688191, 0.587785}, +{0.864188, 0.442863, 0.238856}, {0.688191, 0.587785, 0.425325}, +{0.809017, 0.309017, 0.500000}, {0.681718, 0.147621, 0.716567}, +{0.587785, 0.425325, 0.688191}, {0.955423, 0.295242, 0.000000}, +{1.000000, 0.000000, 0.000000}, {0.951056, 0.162460, 0.262866}, +{0.850651, -0.525731, 0.000000}, {0.955423, -0.295242, 0.000000}, +{0.864188, -0.442863, 0.238856}, {0.951056, -0.162460, 0.262866}, +{0.809017, -0.309017, 0.500000}, {0.681718, -0.147621, 0.716567}, +{0.850651, 0.000000, 0.525731}, {0.864188, 0.442863, -0.238856}, +{0.809017, 0.309017, -0.500000}, {0.951056, 0.162460, -0.262866}, +{0.525731, 0.000000, -0.850651}, {0.681718, 0.147621, -0.716567}, +{0.681718, -0.147621, -0.716567}, {0.850651, 0.000000, -0.525731}, +{0.809017, -0.309017, -0.500000}, {0.864188, -0.442863, -0.238856}, +{0.951056, -0.162460, -0.262866}, {0.147621, 0.716567, -0.681718}, +{0.309017, 0.500000, -0.809017}, {0.425325, 0.688191, -0.587785}, +{0.442863, 0.238856, -0.864188}, {0.587785, 0.425325, -0.688191}, +{0.688191, 0.587785, -0.425325}, {-0.147621, 0.716567, -0.681718}, +{-0.309017, 0.500000, -0.809017}, {0.000000, 0.525731, -0.850651}, +{-0.525731, 0.000000, -0.850651}, {-0.442863, 0.238856, -0.864188}, +{-0.295242, 0.000000, -0.955423}, {-0.162460, 0.262866, -0.951056}, +{0.000000, 0.000000, -1.000000}, {0.295242, 0.000000, -0.955423}, +{0.162460, 0.262866, -0.951056}, {-0.442863, -0.238856, -0.864188}, +{-0.309017, -0.500000, -0.809017}, {-0.162460, -0.262866, -0.951056}, +{0.000000, -0.850651, -0.525731}, {-0.147621, -0.716567, -0.681718}, +{0.147621, -0.716567, -0.681718}, {0.000000, -0.525731, -0.850651}, +{0.309017, -0.500000, -0.809017}, {0.442863, -0.238856, -0.864188}, +{0.162460, -0.262866, -0.951056}, {0.238856, -0.864188, -0.442863}, +{0.500000, -0.809017, -0.309017}, {0.425325, -0.688191, -0.587785}, +{0.716567, -0.681718, -0.147621}, {0.688191, -0.587785, -0.425325}, +{0.587785, -0.425325, -0.688191}, {0.000000, -0.955423, -0.295242}, +{0.000000, -1.000000, 0.000000}, {0.262866, -0.951056, -0.162460}, +{0.000000, -0.850651, 0.525731}, {0.000000, -0.955423, 0.295242}, +{0.238856, -0.864188, 0.442863}, {0.262866, -0.951056, 0.162460}, +{0.500000, -0.809017, 0.309017}, {0.716567, -0.681718, 0.147621}, +{0.525731, -0.850651, 0.000000}, {-0.238856, -0.864188, -0.442863}, +{-0.500000, -0.809017, -0.309017}, {-0.262866, -0.951056, -0.162460}, +{-0.850651, -0.525731, 0.000000}, {-0.716567, -0.681718, -0.147621}, +{-0.716567, -0.681718, 0.147621}, {-0.525731, -0.850651, 0.000000}, +{-0.500000, -0.809017, 0.309017}, {-0.238856, -0.864188, 0.442863}, +{-0.262866, -0.951056, 0.162460}, {-0.864188, -0.442863, 0.238856}, +{-0.809017, -0.309017, 0.500000}, {-0.688191, -0.587785, 0.425325}, +{-0.681718, -0.147621, 0.716567}, {-0.442863, -0.238856, 0.864188}, +{-0.587785, -0.425325, 0.688191}, {-0.309017, -0.500000, 0.809017}, +{-0.147621, -0.716567, 0.681718}, {-0.425325, -0.688191, 0.587785}, +{-0.162460, -0.262866, 0.951056}, {0.442863, -0.238856, 0.864188}, +{0.162460, -0.262866, 0.951056}, {0.309017, -0.500000, 0.809017}, +{0.147621, -0.716567, 0.681718}, {0.000000, -0.525731, 0.850651}, +{0.425325, -0.688191, 0.587785}, {0.587785, -0.425325, 0.688191}, +{0.688191, -0.587785, 0.425325}, {-0.955423, 0.295242, 0.000000}, +{-0.951056, 0.162460, 0.262866}, {-1.000000, 0.000000, 0.000000}, +{-0.850651, 0.000000, 0.525731}, {-0.955423, -0.295242, 0.000000}, +{-0.951056, -0.162460, 0.262866}, {-0.864188, 0.442863, -0.238856}, +{-0.951056, 0.162460, -0.262866}, {-0.809017, 0.309017, -0.500000}, +{-0.864188, -0.442863, -0.238856}, {-0.951056, -0.162460, -0.262866}, +{-0.809017, -0.309017, -0.500000}, {-0.681718, 0.147621, -0.716567}, +{-0.681718, -0.147621, -0.716567}, {-0.850651, 0.000000, -0.525731}, +{-0.688191, 0.587785, -0.425325}, {-0.587785, 0.425325, -0.688191}, +{-0.425325, 0.688191, -0.587785}, {-0.425325, -0.688191, -0.587785}, +{-0.587785, -0.425325, -0.688191}, {-0.688191, -0.587785, -0.425325}, +}; + +#if 0 +unsigned char NormalToByte(const vec3_t n) +{ + int i, best; + float bestdistance, distance; + + best = 0; + bestdistance = DotProduct (n, m_bytenormals[0]); + for (i = 1;i < NUMVERTEXNORMALS;i++) + { + distance = DotProduct (n, m_bytenormals[i]); + if (distance > bestdistance) + { + bestdistance = distance; + best = i; + } + } + return best; +} + +// note: uses byte partly to force unsigned for the validity check +void ByteToNormal(unsigned char num, vec3_t n) +{ + if (num < NUMVERTEXNORMALS) + VectorCopy(m_bytenormals[num], n); + else + VectorClear(n); // FIXME: complain? +} + +// assumes "src" is normalized +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + // LordHavoc: optimized to death and beyond + int pos; + float minelem; + + if (src[0]) + { + dst[0] = 0; + if (src[1]) + { + dst[1] = 0; + if (src[2]) + { + dst[2] = 0; + pos = 0; + minelem = fabs(src[0]); + if (fabs(src[1]) < minelem) + { + pos = 1; + minelem = fabs(src[1]); + } + if (fabs(src[2]) < minelem) + pos = 2; + + dst[pos] = 1; + dst[0] -= src[pos] * src[0]; + dst[1] -= src[pos] * src[1]; + dst[2] -= src[pos] * src[2]; + + // normalize the result + VectorNormalize(dst); + } + else + dst[2] = 1; + } + else + { + dst[1] = 1; + dst[2] = 0; + } + } + else + { + dst[0] = 1; + dst[1] = 0; + dst[2] = 0; + } +} +#endif + + +// LordHavoc: like AngleVectors, but taking a forward vector instead of angles, useful! +void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up) +{ + float d; + + right[0] = forward[2]; + right[1] = -forward[0]; + right[2] = forward[1]; + + d = DotProduct(forward, right); + VectorMA(right, -d, forward, right); + VectorNormalize(right); + CrossProduct(right, forward, up); +} + +void VectorVectorsDouble(const double *forward, double *right, double *up) +{ + double d; + + right[0] = forward[2]; + right[1] = -forward[0]; + right[2] = forward[1]; + + d = DotProduct(forward, right); + VectorMA(right, -d, forward, right); + VectorNormalize(right); + CrossProduct(right, forward, up); +} + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float t0, t1; + float angle, c, s; + vec3_t vr, vu, vf; + + angle = DEG2RAD(degrees); + c = cos(angle); + s = sin(angle); + VectorCopy(dir, vf); + VectorVectors(vf, vr, vu); + + t0 = vr[0] * c + vu[0] * -s; + t1 = vr[0] * s + vu[0] * c; + dst[0] = (t0 * vr[0] + t1 * vu[0] + vf[0] * vf[0]) * point[0] + + (t0 * vr[1] + t1 * vu[1] + vf[0] * vf[1]) * point[1] + + (t0 * vr[2] + t1 * vu[2] + vf[0] * vf[2]) * point[2]; + + t0 = vr[1] * c + vu[1] * -s; + t1 = vr[1] * s + vu[1] * c; + dst[1] = (t0 * vr[0] + t1 * vu[0] + vf[1] * vf[0]) * point[0] + + (t0 * vr[1] + t1 * vu[1] + vf[1] * vf[1]) * point[1] + + (t0 * vr[2] + t1 * vu[2] + vf[1] * vf[2]) * point[2]; + + t0 = vr[2] * c + vu[2] * -s; + t1 = vr[2] * s + vu[2] * c; + dst[2] = (t0 * vr[0] + t1 * vu[0] + vf[2] * vf[0]) * point[0] + + (t0 * vr[1] + t1 * vu[1] + vf[2] * vf[1]) * point[1] + + (t0 * vr[2] + t1 * vu[2] + vf[2] * vf[2]) * point[2]; +} + +/*-----------------------------------------------------------------*/ + +// returns the smallest integer greater than or equal to "value", or 0 if "value" is too big +unsigned int CeilPowerOf2(unsigned int value) +{ + unsigned int ceilvalue; + + if (value > (1U << (sizeof(int) * 8 - 1))) + return 0; + + ceilvalue = 1; + while (ceilvalue < value) + ceilvalue <<= 1; + + return ceilvalue; +} + + +/*-----------------------------------------------------------------*/ + + +void PlaneClassify(mplane_t *p) +{ + // for optimized plane comparisons + if (p->normal[0] == 1) + p->type = 0; + else if (p->normal[1] == 1) + p->type = 1; + else if (p->normal[2] == 1) + p->type = 2; + else + p->type = 3; + // for BoxOnPlaneSide + p->signbits = 0; + if (p->normal[0] < 0) // 1 + p->signbits |= 1; + if (p->normal[1] < 0) // 2 + p->signbits |= 2; + if (p->normal[2] < 0) // 4 + p->signbits |= 4; +} + +int BoxOnPlaneSide(const vec3_t emins, const vec3_t emaxs, const mplane_t *p) +{ + if (p->type < 3) + return ((emaxs[p->type] >= p->dist) | ((emins[p->type] < p->dist) << 1)); + switch(p->signbits) + { + default: + case 0: return (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) < p->dist) << 1)); + case 1: return (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) < p->dist) << 1)); + case 2: return (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) < p->dist) << 1)); + case 3: return (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) < p->dist) << 1)); + case 4: return (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); + case 5: return (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); + case 6: return (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); + case 7: return (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); + } +} + +#if 0 +int BoxOnPlaneSide_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, const vec_t dist) +{ + switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) + { + default: + case 0: return (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2]) < dist) << 1)); + case 1: return (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2]) < dist) << 1)); + case 2: return (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) < dist) << 1)); + case 3: return (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) < dist) << 1)); + case 4: return (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) < dist) << 1)); + case 5: return (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) < dist) << 1)); + case 6: return (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) < dist) << 1)); + case 7: return (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) < dist) << 1)); + } +} +#endif + +void BoxPlaneCorners(const vec3_t emins, const vec3_t emaxs, const mplane_t *p, vec3_t outnear, vec3_t outfar) +{ + if (p->type < 3) + { + outnear[0] = outnear[1] = outnear[2] = outfar[0] = outfar[1] = outfar[2] = 0; + outnear[p->type] = emins[p->type]; + outfar[p->type] = emaxs[p->type]; + return; + } + switch(p->signbits) + { + default: + case 0: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emins[2];break; + case 1: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emins[2];break; + case 2: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; + case 3: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; + case 4: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; + case 5: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; + case 6: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; + case 7: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; + } +} + +void BoxPlaneCorners_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec3_t outnear, vec3_t outfar) +{ + switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) + { + default: + case 0: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emins[2];break; + case 1: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emins[2];break; + case 2: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; + case 3: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; + case 4: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; + case 5: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; + case 6: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; + case 7: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; + } +} + +void BoxPlaneCornerDistances(const vec3_t emins, const vec3_t emaxs, const mplane_t *p, vec_t *outneardist, vec_t *outfardist) +{ + if (p->type < 3) + { + *outneardist = emins[p->type] - p->dist; + *outfardist = emaxs[p->type] - p->dist; + return; + } + switch(p->signbits) + { + default: + case 0: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;break; + case 1: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;break; + case 2: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;break; + case 3: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;break; + case 4: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;break; + case 5: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;break; + case 6: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;break; + case 7: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;break; + } +} + +void BoxPlaneCornerDistances_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec_t *outneardist, vec_t *outfardist) +{ + switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) + { + default: + case 0: *outneardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2];break; + case 1: *outneardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2];break; + case 2: *outneardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2];break; + case 3: *outneardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2];break; + case 4: *outneardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2];*outfardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2];break; + case 5: *outneardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2];break; + case 6: *outneardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2];*outfardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];break; + case 7: *outneardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];break; + } +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + double angle, sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right || up) + { + if (angles[ROLL]) + { + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + if (right) + { + right[0] = -1*(sr*sp*cy+cr*-sy); + right[1] = -1*(sr*sp*sy+cr*cy); + right[2] = -1*(sr*cp); + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } + } + else + { + if (right) + { + right[0] = sy; + right[1] = -cy; + right[2] = 0; + } + if (up) + { + up[0] = (sp*cy); + up[1] = (sp*sy); + up[2] = cp; + } + } + } +} + +void AngleVectorsFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up) +{ + double angle, sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (left || up) + { + if (angles[ROLL]) + { + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + if (left) + { + left[0] = sr*sp*cy+cr*-sy; + left[1] = sr*sp*sy+cr*cy; + left[2] = sr*cp; + } + if (up) + { + up[0] = cr*sp*cy+-sr*-sy; + up[1] = cr*sp*sy+-sr*cy; + up[2] = cr*cp; + } + } + else + { + if (left) + { + left[0] = -sy; + left[1] = cy; + left[2] = 0; + } + if (up) + { + up[0] = sp*cy; + up[1] = sp*sy; + up[2] = cp; + } + } + } +} + +// LordHavoc: calculates pitch/yaw/roll angles from forward and up vectors +void AnglesFromVectors (vec3_t angles, const vec3_t forward, const vec3_t up, qboolean flippitch) +{ + if (forward[0] == 0 && forward[1] == 0) + { + if(forward[2] > 0) + { + angles[PITCH] = -M_PI * 0.5; + angles[YAW] = up ? atan2(-up[1], -up[0]) : 0; + } + else + { + angles[PITCH] = M_PI * 0.5; + angles[YAW] = up ? atan2(up[1], up[0]) : 0; + } + angles[ROLL] = 0; + } + else + { + angles[YAW] = atan2(forward[1], forward[0]); + angles[PITCH] = -atan2(forward[2], sqrt(forward[0]*forward[0] + forward[1]*forward[1])); + if (up) + { + vec_t cp = cos(angles[PITCH]), sp = sin(angles[PITCH]); + vec_t cy = cos(angles[YAW]), sy = sin(angles[YAW]); + vec3_t tleft, tup; + tleft[0] = -sy; + tleft[1] = cy; + tleft[2] = 0; + tup[0] = sp*cy; + tup[1] = sp*sy; + tup[2] = cp; + angles[ROLL] = -atan2(DotProduct(up, tleft), DotProduct(up, tup)); + } + else + angles[ROLL] = 0; + } + + // now convert radians to degrees, and make all values positive + VectorScale(angles, 180.0 / M_PI, angles); + if (flippitch) + angles[PITCH] *= -1; + if (angles[PITCH] < 0) angles[PITCH] += 360; + if (angles[YAW] < 0) angles[YAW] += 360; + if (angles[ROLL] < 0) angles[ROLL] += 360; + +#if 0 +{ + // debugging code + vec3_t tforward, tleft, tup, nforward, nup; + VectorCopy(forward, nforward); + VectorNormalize(nforward); + if (up) + { + VectorCopy(up, nup); + VectorNormalize(nup); + AngleVectors(angles, tforward, tleft, tup); + if (VectorDistance(tforward, nforward) > 0.01 || VectorDistance(tup, nup) > 0.01) + { + Con_Printf("vectoangles('%f %f %f', '%f %f %f') = %f %f %f\n", nforward[0], nforward[1], nforward[2], nup[0], nup[1], nup[2], angles[0], angles[1], angles[2]); + Con_Printf("^3But that is '%f %f %f', '%f %f %f'\n", tforward[0], tforward[1], tforward[2], tup[0], tup[1], tup[2]); + } + } + else + { + AngleVectors(angles, tforward, tleft, tup); + if (VectorDistance(tforward, nforward) > 0.01) + { + Con_Printf("vectoangles('%f %f %f') = %f %f %f\n", nforward[0], nforward[1], nforward[2], angles[0], angles[1], angles[2]); + Con_Printf("^3But that is '%f %f %f'\n", tforward[0], tforward[1], tforward[2]); + } + } +} +#endif +} + +#if 0 +void AngleMatrix (const vec3_t angles, const vec3_t translate, vec_t matrix[][4]) +{ + double angle, sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + matrix[0][0] = cp*cy; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[0][2] = cr*sp*cy+-sr*-sy; + matrix[0][3] = translate[0]; + matrix[1][0] = cp*sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = cr*sp*sy+-sr*cy; + matrix[1][3] = translate[1]; + matrix[2][0] = -sp; + matrix[2][1] = sr*cp; + matrix[2][2] = cr*cp; + matrix[2][3] = translate[2]; +} +#endif + + +// LordHavoc: renamed this to Length, and made the normal one a #define +float VectorNormalizeLength (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (const float in1[3*3], const float in2[3*3], float out[3*3]) +{ + out[0*3+0] = in1[0*3+0] * in2[0*3+0] + in1[0*3+1] * in2[1*3+0] + in1[0*3+2] * in2[2*3+0]; + out[0*3+1] = in1[0*3+0] * in2[0*3+1] + in1[0*3+1] * in2[1*3+1] + in1[0*3+2] * in2[2*3+1]; + out[0*3+2] = in1[0*3+0] * in2[0*3+2] + in1[0*3+1] * in2[1*3+2] + in1[0*3+2] * in2[2*3+2]; + out[1*3+0] = in1[1*3+0] * in2[0*3+0] + in1[1*3+1] * in2[1*3+0] + in1[1*3+2] * in2[2*3+0]; + out[1*3+1] = in1[1*3+0] * in2[0*3+1] + in1[1*3+1] * in2[1*3+1] + in1[1*3+2] * in2[2*3+1]; + out[1*3+2] = in1[1*3+0] * in2[0*3+2] + in1[1*3+1] * in2[1*3+2] + in1[1*3+2] * in2[2*3+2]; + out[2*3+0] = in1[2*3+0] * in2[0*3+0] + in1[2*3+1] * in2[1*3+0] + in1[2*3+2] * in2[2*3+0]; + out[2*3+1] = in1[2*3+0] * in2[0*3+1] + in1[2*3+1] * in2[1*3+1] + in1[2*3+2] * in2[2*3+1]; + out[2*3+2] = in1[2*3+0] * in2[0*3+2] + in1[2*3+1] * in2[1*3+2] + in1[2*3+2] * in2[2*3+2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (const float in1[3*4], const float in2[3*4], float out[3*4]) +{ + out[0*4+0] = in1[0*4+0] * in2[0*4+0] + in1[0*4+1] * in2[1*4+0] + in1[0*4+2] * in2[2*4+0]; + out[0*4+1] = in1[0*4+0] * in2[0*4+1] + in1[0*4+1] * in2[1*4+1] + in1[0*4+2] * in2[2*4+1]; + out[0*4+2] = in1[0*4+0] * in2[0*4+2] + in1[0*4+1] * in2[1*4+2] + in1[0*4+2] * in2[2*4+2]; + out[0*4+3] = in1[0*4+0] * in2[0*4+3] + in1[0*4+1] * in2[1*4+3] + in1[0*4+2] * in2[2*4+3] + in1[0*4+3]; + out[1*4+0] = in1[1*4+0] * in2[0*4+0] + in1[1*4+1] * in2[1*4+0] + in1[1*4+2] * in2[2*4+0]; + out[1*4+1] = in1[1*4+0] * in2[0*4+1] + in1[1*4+1] * in2[1*4+1] + in1[1*4+2] * in2[2*4+1]; + out[1*4+2] = in1[1*4+0] * in2[0*4+2] + in1[1*4+1] * in2[1*4+2] + in1[1*4+2] * in2[2*4+2]; + out[1*4+3] = in1[1*4+0] * in2[0*4+3] + in1[1*4+1] * in2[1*4+3] + in1[1*4+2] * in2[2*4+3] + in1[1*4+3]; + out[2*4+0] = in1[2*4+0] * in2[0*4+0] + in1[2*4+1] * in2[1*4+0] + in1[2*4+2] * in2[2*4+0]; + out[2*4+1] = in1[2*4+0] * in2[0*4+1] + in1[2*4+1] * in2[1*4+1] + in1[2*4+2] * in2[2*4+1]; + out[2*4+2] = in1[2*4+0] * in2[0*4+2] + in1[2*4+1] * in2[1*4+2] + in1[2*4+2] * in2[2*4+2]; + out[2*4+3] = in1[2*4+0] * in2[0*4+3] + in1[2*4+1] * in2[1*4+3] + in1[2*4+2] * in2[2*4+3] + in1[2*4+3]; +} + +float RadiusFromBounds (const vec3_t mins, const vec3_t maxs) +{ + vec3_t m1, m2; + VectorMultiply(mins, mins, m1); + VectorMultiply(maxs, maxs, m2); + return sqrt(max(m1[0], m2[0]) + max(m1[1], m2[1]) + max(m1[2], m2[2])); +} + +float RadiusFromBoundsAndOrigin (const vec3_t mins, const vec3_t maxs, const vec3_t origin) +{ + vec3_t m1, m2; + VectorSubtract(mins, origin, m1);VectorMultiply(m1, m1, m1); + VectorSubtract(maxs, origin, m2);VectorMultiply(m2, m2, m2); + return sqrt(max(m1[0], m2[0]) + max(m1[1], m2[1]) + max(m1[2], m2[2])); +} + +void Mathlib_Init(void) +{ + int a; + + // LordHavoc: setup 1.0f / N table for quick recipricols of integers + ixtable[0] = 0; + for (a = 1;a < 4096;a++) + ixtable[a] = 1.0f / a; +} + +#include "matrixlib.h" + +void Matrix4x4_Print(const matrix4x4_t *in) +{ + Con_Printf("%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n" + , in->m[0][0], in->m[0][1], in->m[0][2], in->m[0][3] + , in->m[1][0], in->m[1][1], in->m[1][2], in->m[1][3] + , in->m[2][0], in->m[2][1], in->m[2][2], in->m[2][3] + , in->m[3][0], in->m[3][1], in->m[3][2], in->m[3][3]); +} + +int Math_atov(const char *s, vec3_t out) +{ + int i; + VectorClear(out); + if (*s == '\'') + s++; + for (i = 0;i < 3;i++) + { + while (*s == ' ' || *s == '\t') + s++; + out[i] = atof (s); + if (out[i] == 0 && *s != '-' && *s != '+' && (*s < '0' || *s > '9')) + break; // not a number + while (*s && *s != ' ' && *s !='\t' && *s != '\'') + s++; + if (*s == '\'') + break; + } + return i; +} + +void BoxFromPoints(vec3_t mins, vec3_t maxs, int numpoints, vec_t *point3f) +{ + int i; + VectorCopy(point3f, mins); + VectorCopy(point3f, maxs); + for (i = 1, point3f += 3;i < numpoints;i++, point3f += 3) + { + mins[0] = min(mins[0], point3f[0]);maxs[0] = max(maxs[0], point3f[0]); + mins[1] = min(mins[1], point3f[1]);maxs[1] = max(maxs[1], point3f[1]); + mins[2] = min(mins[2], point3f[2]);maxs[2] = max(maxs[2], point3f[2]); + } +} + +// LordHavoc: this has to be done right or you get severe precision breakdown +int LoopingFrameNumberFromDouble(double t, int loopframes) +{ + if (loopframes) + return (int)(t - floor(t/loopframes)*loopframes); + else + return (int)t; +} + diff --git a/misc/source/darkplaces-src/mathlib.h b/misc/source/darkplaces-src/mathlib.h new file mode 100644 index 00000000..e1c7f647 --- /dev/null +++ b/misc/source/darkplaces-src/mathlib.h @@ -0,0 +1,297 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// mathlib.h + +#ifndef MATHLIB_H +#define MATHLIB_H + +#include "qtypes.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef vec_t vec5_t[5]; +typedef vec_t vec6_t[6]; +typedef vec_t vec7_t[7]; +typedef vec_t vec8_t[8]; +struct mplane_s; +extern vec3_t vec3_origin; + +#define nanmask (255<<23) +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +#define bound(min,num,max) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) + +#ifndef min +#define min(A,B) ((A) < (B) ? (A) : (B)) +#define max(A,B) ((A) > (B) ? (A) : (B)) +#endif + +/// LordHavoc: this function never returns exactly MIN or exactly MAX, because +/// of a QuakeC bug in id1 where the line +/// self.nextthink = self.nexthink + random() * 0.5; +/// can result in 0 (self.nextthink is 0 at this point in the code to begin +/// with), causing "stone monsters" that never spawned properly, also MAX is +/// avoided because some people use random() as an index into arrays or for +/// loop conditions, where hitting exactly MAX may be a fatal error +#define lhrandom(MIN,MAX) (((double)(rand() + 0.5) / ((double)RAND_MAX + 1)) * ((MAX)-(MIN)) + (MIN)) + +#define invpow(base,number) (log(number) / log(base)) + +/// returns log base 2 of "n" +/// \WARNING: "n" MUST be a power of 2! +#define log2i(n) ((((n) & 0xAAAAAAAA) != 0 ? 1 : 0) | (((n) & 0xCCCCCCCC) != 0 ? 2 : 0) | (((n) & 0xF0F0F0F0) != 0 ? 4 : 0) | (((n) & 0xFF00FF00) != 0 ? 8 : 0) | (((n) & 0xFFFF0000) != 0 ? 16 : 0)) + +/// \TODO: what is this function supposed to do? +#define bit2i(n) log2i((n) << 1) + +/// boolean XOR (why doesn't C have the ^^ operator for this purpose?) +#define boolxor(a,b) (!(a) != !(b)) + +/// returns the smallest integer greater than or equal to "value", or 0 if "value" is too big +unsigned int CeilPowerOf2(unsigned int value); + +#define DEG2RAD(a) ((a) * ((float) M_PI / 180.0f)) +#define RAD2DEG(a) ((a) * (180.0f / (float) M_PI)) +#define ANGLEMOD(a) ((a) - 360.0 * floor((a) / 360.0)) + +#define DotProduct2(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]) +#define Vector2Clear(a) ((a)[0]=(a)[1]=0) +#define Vector2Compare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])) +#define Vector2Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1]) +#define Vector2Negate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1])) +#define Vector2Set(a,b,c,d,e) ((a)[0]=(b),(a)[1]=(c)) +#define Vector2Scale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale)) +#define Vector2Normalize2(v,dest) {float ilength = (float) sqrt(DotProduct2((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;} + +#define DotProduct4(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]+(a)[3]*(b)[3]) +#define Vector4Clear(a) ((a)[0]=(a)[1]=(a)[2]=(a)[3]=0) +#define Vector4Compare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])&&((a)[2]==(b)[2])&&((a)[3]==(b)[3])) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define Vector4Negate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1]),(b)[2]=-((a)[2]),(b)[3]=-((a)[3])) +#define Vector4Set(a,b,c,d,e) ((a)[0]=(b),(a)[1]=(c),(a)[2]=(d),(a)[3]=(e)) +#define Vector4Normalize2(v,dest) {float ilength = (float) sqrt(DotProduct4((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;dest[2] = (v)[2] * ilength;dest[3] = (v)[3] * ilength;} +#define Vector4Subtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2],(c)[3]=(a)[3]-(b)[3]) +#define Vector4Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3]) +#define Vector4Scale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale),(out)[3] = (in)[3] * (scale)) +#define Vector4Multiply(a,b,c) ((c)[0]=(a)[0]*(b)[0],(c)[1]=(a)[1]*(b)[1],(c)[2]=(a)[2]*(b)[2],(c)[3]=(a)[3]*(b)[3]) +#define Vector4MA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2],(c)[3] = (a)[3] + (scale) * (b)[3]) +#define Vector4Lerp(v1,lerp,v2,c) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2]), (c)[3] = (v1)[3] + (lerp) * ((v2)[3] - (v1)[3])) + +#define VectorNegate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1]),(b)[2]=-((a)[2])) +#define VectorSet(a,b,c,d) ((a)[0]=(b),(a)[1]=(c),(a)[2]=(d)) +#define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0) +#define DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) +#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorMultiply(a,b,c) ((c)[0]=(a)[0]*(b)[0],(c)[1]=(a)[1]*(b)[1],(c)[2]=(a)[2]*(b)[2]) +#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) +#define VectorNormalize(v) {float ilength = (float) sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0f / ilength;(v)[0] *= ilength;(v)[1] *= ilength;(v)[2] *= ilength;} +#define VectorNormalize2(v,dest) {float ilength = (float) sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;dest[2] = (v)[2] * ilength;} +#define VectorNormalizeDouble(v) {double ilength = sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0 / ilength;(v)[0] *= ilength;(v)[1] *= ilength;(v)[2] *= ilength;} +#define VectorDistance2(a, b) (((a)[0] - (b)[0]) * ((a)[0] - (b)[0]) + ((a)[1] - (b)[1]) * ((a)[1] - (b)[1]) + ((a)[2] - (b)[2]) * ((a)[2] - (b)[2])) +#define VectorDistance(a, b) (sqrt(VectorDistance2(a,b))) +#define VectorLength(a) (sqrt((double)DotProduct(a, a))) +#define VectorLength2(a) (DotProduct(a, a)) +#define VectorScale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale)) +#define VectorScaleCast(in, scale, outtype, out) ((out)[0] = (outtype) ((in)[0] * (scale)),(out)[1] = (outtype) ((in)[1] * (scale)),(out)[2] = (outtype) ((in)[2] * (scale))) +#define VectorCompare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])&&((a)[2]==(b)[2])) +#define VectorMA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2]) +#define VectorM(scale1, b1, c) ((c)[0] = (scale1) * (b1)[0],(c)[1] = (scale1) * (b1)[1],(c)[2] = (scale1) * (b1)[2]) +#define VectorMAM(scale1, b1, scale2, b2, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2]) +#define VectorMAMAM(scale1, b1, scale2, b2, scale3, b3, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2]) +#define VectorMAMAMAM(scale1, b1, scale2, b2, scale3, b3, scale4, b4, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0] + (scale4) * (b4)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1] + (scale4) * (b4)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2] + (scale4) * (b4)[2]) +#define VectorRandom(v) do{(v)[0] = lhrandom(-1, 1);(v)[1] = lhrandom(-1, 1);(v)[2] = lhrandom(-1, 1);}while(DotProduct(v, v) > 1) +#define VectorLerp(v1,lerp,v2,c) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2])) +#define VectorReflect(a,r,b,c) do{double d;d = DotProduct((a), (b)) * -(1.0 + (r));VectorMA((a), (d), (b), (c));}while(0) +#define BoxesOverlap(a,b,c,d) ((a)[0] <= (d)[0] && (b)[0] >= (c)[0] && (a)[1] <= (d)[1] && (b)[1] >= (c)[1] && (a)[2] <= (d)[2] && (b)[2] >= (c)[2]) +#define BoxInsideBox(a,b,c,d) ((a)[0] >= (c)[0] && (b)[0] <= (d)[0] && (a)[1] >= (c)[1] && (b)[1] <= (d)[1] && (a)[2] >= (c)[2] && (b)[2] <= (d)[2]) +#define TriangleOverlapsBox(a,b,c,d,e) (min((a)[0], min((b)[0], (c)[0])) < (e)[0] && max((a)[0], max((b)[0], (c)[0])) > (d)[0] && min((a)[1], min((b)[1], (c)[1])) < (e)[1] && max((a)[1], max((b)[1], (c)[1])) > (d)[1] && min((a)[2], min((b)[2], (c)[2])) < (e)[2] && max((a)[2], max((b)[2], (c)[2])) > (d)[2]) + +#define TriangleNormal(a,b,c,n) ( \ + (n)[0] = ((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1]), \ + (n)[1] = ((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2]), \ + (n)[2] = ((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0]) \ + ) + +/*! Fast PointInfrontOfTriangle. + * subtracts v1 from v0 and v2, combined into a crossproduct, combined with a + * dotproduct of the light location relative to the first point of the + * triangle (any point works, since any triangle is obviously flat), and + * finally a comparison to determine if the light is infront of the triangle + * (the goal of this statement) we do not need to normalize the surface + * normal because both sides of the comparison use it, therefore they are + * both multiplied the same amount... furthermore a subtract can be done on + * the point to eliminate one dotproduct + * this is ((p - a) * cross(a-b,c-b)) + */ +#define PointInfrontOfTriangle(p,a,b,c) \ +( ((p)[0] - (a)[0]) * (((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1])) \ ++ ((p)[1] - (a)[1]) * (((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2])) \ ++ ((p)[2] - (a)[2]) * (((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0])) > 0) + +#if 0 +// readable version, kept only for explanatory reasons +int PointInfrontOfTriangle(const float *p, const float *a, const float *b, const float *c) +{ + float dir0[3], dir1[3], normal[3]; + + // calculate two mostly perpendicular edge directions + VectorSubtract(a, b, dir0); + VectorSubtract(c, b, dir1); + + // we have two edge directions, we can calculate a third vector from + // them, which is the direction of the surface normal (its magnitude + // is not 1 however) + CrossProduct(dir0, dir1, normal); + + // compare distance of light along normal, with distance of any point + // of the triangle along the same normal (the triangle is planar, + // I.E. flat, so all points give the same answer) + return DotProduct(p, normal) > DotProduct(a, normal); +} +#endif + +#define lhcheeserand() (seed = (seed * 987211u) ^ (seed >> 13u) ^ 914867) +#define lhcheeserandom(MIN,MAX) ((double)(lhcheeserand() + 0.5) / ((double)4096.0*1024.0*1024.0) * ((MAX)-(MIN)) + (MIN)) +#define VectorCheeseRandom(v) do{(v)[0] = lhcheeserandom(-1, 1);(v)[1] = lhcheeserandom(-1, 1);(v)[2] = lhcheeserandom(-1, 1);}while(DotProduct(v, v) > 1) + +/* +// LordHavoc: quaternion math, untested, don't know if these are correct, +// need to add conversion to/from matrices +// LordHavoc: later note: the matrix faq is useful: http://skal.planet-d.net/demo/matrixfaq.htm +// LordHavoc: these are probably very wrong and I'm not sure I care, not used by anything + +// returns length of quaternion +#define qlen(a) ((float) sqrt((a)[0]*(a)[0]+(a)[1]*(a)[1]+(a)[2]*(a)[2]+(a)[3]*(a)[3])) +// returns squared length of quaternion +#define qlen2(a) ((a)[0]*(a)[0]+(a)[1]*(a)[1]+(a)[2]*(a)[2]+(a)[3]*(a)[3]) +// makes a quaternion from x, y, z, and a rotation angle (in degrees) +#define QuatMake(x,y,z,r,c)\ +{\ +if (r == 0)\ +{\ +(c)[0]=(float) ((x) * (1.0f / 0.0f));\ +(c)[1]=(float) ((y) * (1.0f / 0.0f));\ +(c)[2]=(float) ((z) * (1.0f / 0.0f));\ +(c)[3]=(float) 1.0f;\ +}\ +else\ +{\ +float r2 = (r) * 0.5 * (M_PI / 180);\ +float r2is = 1.0f / sin(r2);\ +(c)[0]=(float) ((x)/r2is);\ +(c)[1]=(float) ((y)/r2is);\ +(c)[2]=(float) ((z)/r2is);\ +(c)[3]=(float) (cos(r2));\ +}\ +} +// makes a quaternion from a vector and a rotation angle (in degrees) +#define QuatFromVec(a,r,c) QuatMake((a)[0],(a)[1],(a)[2],(r)) +// copies a quaternion +#define QuatCopy(a,c) {(c)[0]=(a)[0];(c)[1]=(a)[1];(c)[2]=(a)[2];(c)[3]=(a)[3];} +#define QuatSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];(c)[3]=(a)[3]-(b)[3];} +#define QuatAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];(c)[3]=(a)[3]+(b)[3];} +#define QuatScale(a,b,c) {(c)[0]=(a)[0]*b;(c)[1]=(a)[1]*b;(c)[2]=(a)[2]*b;(c)[3]=(a)[3]*b;} +// FIXME: this is wrong, do some more research on quaternions +//#define QuatMultiply(a,b,c) {(c)[0]=(a)[0]*(b)[0];(c)[1]=(a)[1]*(b)[1];(c)[2]=(a)[2]*(b)[2];(c)[3]=(a)[3]*(b)[3];} +// FIXME: this is wrong, do some more research on quaternions +//#define QuatMultiplyAdd(a,b,d,c) {(c)[0]=(a)[0]*(b)[0]+d[0];(c)[1]=(a)[1]*(b)[1]+d[1];(c)[2]=(a)[2]*(b)[2]+d[2];(c)[3]=(a)[3]*(b)[3]+d[3];} +#define qdist(a,b) ((float) sqrt(((b)[0]-(a)[0])*((b)[0]-(a)[0])+((b)[1]-(a)[1])*((b)[1]-(a)[1])+((b)[2]-(a)[2])*((b)[2]-(a)[2])+((b)[3]-(a)[3])*((b)[3]-(a)[3]))) +#define qdist2(a,b) (((b)[0]-(a)[0])*((b)[0]-(a)[0])+((b)[1]-(a)[1])*((b)[1]-(a)[1])+((b)[2]-(a)[2])*((b)[2]-(a)[2])+((b)[3]-(a)[3])*((b)[3]-(a)[3])) +*/ + +#define VectorCopy4(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];(b)[3]=(a)[3];} + +vec_t Length (vec3_t v); + +/// returns vector length +float VectorNormalizeLength (vec3_t v); + +/// returns vector length +float VectorNormalizeLength2 (vec3_t v, vec3_t dest); + +#define NUMVERTEXNORMALS 162 +extern float m_bytenormals[NUMVERTEXNORMALS][3]; + +unsigned char NormalToByte(const vec3_t n); +void ByteToNormal(unsigned char num, vec3_t n); + +void R_ConcatRotations (const float in1[3*3], const float in2[3*3], float out[3*3]); +void R_ConcatTransforms (const float in1[3*4], const float in2[3*4], float out[3*4]); + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +/// LordHavoc: proper matrix version of AngleVectors +void AngleVectorsFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up); +/// LordHavoc: builds a [3][4] matrix +void AngleMatrix (const vec3_t angles, const vec3_t translate, vec_t matrix[][4]); +/// LordHavoc: calculates pitch/yaw/roll angles from forward and up vectors +void AnglesFromVectors (vec3_t angles, const vec3_t forward, const vec3_t up, qboolean flippitch); + +/// LordHavoc: like AngleVectors, but taking a forward vector instead of angles, useful! +void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up); +void VectorVectorsDouble(const double *forward, double *right, double *up); + +void PlaneClassify(struct mplane_s *p); +int BoxOnPlaneSide(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p); +int BoxOnPlaneSide_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, const vec_t dist); +void BoxPlaneCorners(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p, vec3_t outnear, vec3_t outfar); +void BoxPlaneCorners_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec3_t outnear, vec3_t outfar); +void BoxPlaneCornerDistances(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p, vec_t *outnear, vec_t *outfar); +void BoxPlaneCornerDistances_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec_t *outnear, vec_t *outfar); + +#define PlaneDist(point,plane) ((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) +#define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) + +/// LordHavoc: minimal plane structure +typedef struct tinyplane_s +{ + float normal[3], dist; +} +tinyplane_t; + +typedef struct tinydoubleplane_s +{ + double normal[3], dist; +} +tinydoubleplane_t; + +void RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees); + +float RadiusFromBounds (const vec3_t mins, const vec3_t maxs); +float RadiusFromBoundsAndOrigin (const vec3_t mins, const vec3_t maxs, const vec3_t origin); + +struct matrix4x4_s; +/// print a matrix to the console +void Matrix4x4_Print(const struct matrix4x4_s *in); +int Math_atov(const char *s, vec3_t out); + +void BoxFromPoints(vec3_t mins, vec3_t maxs, int numpoints, vec_t *point3f); + +int LoopingFrameNumberFromDouble(double t, int loopframes); + +#endif + diff --git a/misc/source/darkplaces-src/matrixlib.c b/misc/source/darkplaces-src/matrixlib.c new file mode 100644 index 00000000..f84e922d --- /dev/null +++ b/misc/source/darkplaces-src/matrixlib.c @@ -0,0 +1,1824 @@ +#include "quakedef.h" + +#include +#include "matrixlib.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4244) // LordHavoc: MSVC++ 4 x86, double/float +#pragma warning(disable : 4305) // LordHavoc: MSVC++ 6 x86, double/float +#endif + +const matrix4x4_t identitymatrix = +{ + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1} + } +}; + +void Matrix4x4_Copy (matrix4x4_t *out, const matrix4x4_t *in) +{ + *out = *in; +} + +void Matrix4x4_CopyRotateOnly (matrix4x4_t *out, const matrix4x4_t *in) +{ + out->m[0][0] = in->m[0][0]; + out->m[0][1] = in->m[0][1]; + out->m[0][2] = in->m[0][2]; + out->m[0][3] = 0.0f; + out->m[1][0] = in->m[1][0]; + out->m[1][1] = in->m[1][1]; + out->m[1][2] = in->m[1][2]; + out->m[1][3] = 0.0f; + out->m[2][0] = in->m[2][0]; + out->m[2][1] = in->m[2][1]; + out->m[2][2] = in->m[2][2]; + out->m[2][3] = 0.0f; + out->m[3][0] = 0.0f; + out->m[3][1] = 0.0f; + out->m[3][2] = 0.0f; + out->m[3][3] = 1.0f; +} + +void Matrix4x4_CopyTranslateOnly (matrix4x4_t *out, const matrix4x4_t *in) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = 1.0f; + out->m[1][0] = 0.0f; + out->m[2][0] = 0.0f; + out->m[3][0] = in->m[0][3]; + out->m[0][1] = 0.0f; + out->m[1][1] = 1.0f; + out->m[2][1] = 0.0f; + out->m[3][1] = in->m[1][3]; + out->m[0][2] = 0.0f; + out->m[1][2] = 0.0f; + out->m[2][2] = 1.0f; + out->m[3][2] = in->m[2][3]; + out->m[0][3] = 0.0f; + out->m[1][3] = 0.0f; + out->m[2][3] = 0.0f; + out->m[3][3] = 1.0f; +#else + out->m[0][0] = 1.0f; + out->m[0][1] = 0.0f; + out->m[0][2] = 0.0f; + out->m[0][3] = in->m[0][3]; + out->m[1][0] = 0.0f; + out->m[1][1] = 1.0f; + out->m[1][2] = 0.0f; + out->m[1][3] = in->m[1][3]; + out->m[2][0] = 0.0f; + out->m[2][1] = 0.0f; + out->m[2][2] = 1.0f; + out->m[2][3] = in->m[2][3]; + out->m[3][0] = 0.0f; + out->m[3][1] = 0.0f; + out->m[3][2] = 0.0f; + out->m[3][3] = 1.0f; +#endif +} + +void Matrix4x4_Concat (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in1->m[0][0] * in2->m[0][0] + in1->m[1][0] * in2->m[0][1] + in1->m[2][0] * in2->m[0][2] + in1->m[3][0] * in2->m[0][3]; + out->m[1][0] = in1->m[0][0] * in2->m[1][0] + in1->m[1][0] * in2->m[1][1] + in1->m[2][0] * in2->m[1][2] + in1->m[3][0] * in2->m[1][3]; + out->m[2][0] = in1->m[0][0] * in2->m[2][0] + in1->m[1][0] * in2->m[2][1] + in1->m[2][0] * in2->m[2][2] + in1->m[3][0] * in2->m[2][3]; + out->m[3][0] = in1->m[0][0] * in2->m[3][0] + in1->m[1][0] * in2->m[3][1] + in1->m[2][0] * in2->m[3][2] + in1->m[3][0] * in2->m[3][3]; + out->m[0][1] = in1->m[0][1] * in2->m[0][0] + in1->m[1][1] * in2->m[0][1] + in1->m[2][1] * in2->m[0][2] + in1->m[3][1] * in2->m[0][3]; + out->m[1][1] = in1->m[0][1] * in2->m[1][0] + in1->m[1][1] * in2->m[1][1] + in1->m[2][1] * in2->m[1][2] + in1->m[3][1] * in2->m[1][3]; + out->m[2][1] = in1->m[0][1] * in2->m[2][0] + in1->m[1][1] * in2->m[2][1] + in1->m[2][1] * in2->m[2][2] + in1->m[3][1] * in2->m[2][3]; + out->m[3][1] = in1->m[0][1] * in2->m[3][0] + in1->m[1][1] * in2->m[3][1] + in1->m[2][1] * in2->m[3][2] + in1->m[3][1] * in2->m[3][3]; + out->m[0][2] = in1->m[0][2] * in2->m[0][0] + in1->m[1][2] * in2->m[0][1] + in1->m[2][2] * in2->m[0][2] + in1->m[3][2] * in2->m[0][3]; + out->m[1][2] = in1->m[0][2] * in2->m[1][0] + in1->m[1][2] * in2->m[1][1] + in1->m[2][2] * in2->m[1][2] + in1->m[3][2] * in2->m[1][3]; + out->m[2][2] = in1->m[0][2] * in2->m[2][0] + in1->m[1][2] * in2->m[2][1] + in1->m[2][2] * in2->m[2][2] + in1->m[3][2] * in2->m[2][3]; + out->m[3][2] = in1->m[0][2] * in2->m[3][0] + in1->m[1][2] * in2->m[3][1] + in1->m[2][2] * in2->m[3][2] + in1->m[3][2] * in2->m[3][3]; + out->m[0][3] = in1->m[0][3] * in2->m[0][0] + in1->m[1][3] * in2->m[0][1] + in1->m[2][3] * in2->m[0][2] + in1->m[3][3] * in2->m[0][3]; + out->m[1][3] = in1->m[0][3] * in2->m[1][0] + in1->m[1][3] * in2->m[1][1] + in1->m[2][3] * in2->m[1][2] + in1->m[3][3] * in2->m[1][3]; + out->m[2][3] = in1->m[0][3] * in2->m[2][0] + in1->m[1][3] * in2->m[2][1] + in1->m[2][3] * in2->m[2][2] + in1->m[3][3] * in2->m[2][3]; + out->m[3][3] = in1->m[0][3] * in2->m[3][0] + in1->m[1][3] * in2->m[3][1] + in1->m[2][3] * in2->m[3][2] + in1->m[3][3] * in2->m[3][3]; +#else + out->m[0][0] = in1->m[0][0] * in2->m[0][0] + in1->m[0][1] * in2->m[1][0] + in1->m[0][2] * in2->m[2][0] + in1->m[0][3] * in2->m[3][0]; + out->m[0][1] = in1->m[0][0] * in2->m[0][1] + in1->m[0][1] * in2->m[1][1] + in1->m[0][2] * in2->m[2][1] + in1->m[0][3] * in2->m[3][1]; + out->m[0][2] = in1->m[0][0] * in2->m[0][2] + in1->m[0][1] * in2->m[1][2] + in1->m[0][2] * in2->m[2][2] + in1->m[0][3] * in2->m[3][2]; + out->m[0][3] = in1->m[0][0] * in2->m[0][3] + in1->m[0][1] * in2->m[1][3] + in1->m[0][2] * in2->m[2][3] + in1->m[0][3] * in2->m[3][3]; + out->m[1][0] = in1->m[1][0] * in2->m[0][0] + in1->m[1][1] * in2->m[1][0] + in1->m[1][2] * in2->m[2][0] + in1->m[1][3] * in2->m[3][0]; + out->m[1][1] = in1->m[1][0] * in2->m[0][1] + in1->m[1][1] * in2->m[1][1] + in1->m[1][2] * in2->m[2][1] + in1->m[1][3] * in2->m[3][1]; + out->m[1][2] = in1->m[1][0] * in2->m[0][2] + in1->m[1][1] * in2->m[1][2] + in1->m[1][2] * in2->m[2][2] + in1->m[1][3] * in2->m[3][2]; + out->m[1][3] = in1->m[1][0] * in2->m[0][3] + in1->m[1][1] * in2->m[1][3] + in1->m[1][2] * in2->m[2][3] + in1->m[1][3] * in2->m[3][3]; + out->m[2][0] = in1->m[2][0] * in2->m[0][0] + in1->m[2][1] * in2->m[1][0] + in1->m[2][2] * in2->m[2][0] + in1->m[2][3] * in2->m[3][0]; + out->m[2][1] = in1->m[2][0] * in2->m[0][1] + in1->m[2][1] * in2->m[1][1] + in1->m[2][2] * in2->m[2][1] + in1->m[2][3] * in2->m[3][1]; + out->m[2][2] = in1->m[2][0] * in2->m[0][2] + in1->m[2][1] * in2->m[1][2] + in1->m[2][2] * in2->m[2][2] + in1->m[2][3] * in2->m[3][2]; + out->m[2][3] = in1->m[2][0] * in2->m[0][3] + in1->m[2][1] * in2->m[1][3] + in1->m[2][2] * in2->m[2][3] + in1->m[2][3] * in2->m[3][3]; + out->m[3][0] = in1->m[3][0] * in2->m[0][0] + in1->m[3][1] * in2->m[1][0] + in1->m[3][2] * in2->m[2][0] + in1->m[3][3] * in2->m[3][0]; + out->m[3][1] = in1->m[3][0] * in2->m[0][1] + in1->m[3][1] * in2->m[1][1] + in1->m[3][2] * in2->m[2][1] + in1->m[3][3] * in2->m[3][1]; + out->m[3][2] = in1->m[3][0] * in2->m[0][2] + in1->m[3][1] * in2->m[1][2] + in1->m[3][2] * in2->m[2][2] + in1->m[3][3] * in2->m[3][2]; + out->m[3][3] = in1->m[3][0] * in2->m[0][3] + in1->m[3][1] * in2->m[1][3] + in1->m[3][2] * in2->m[2][3] + in1->m[3][3] * in2->m[3][3]; +#endif +} + +void Matrix4x4_Transpose (matrix4x4_t *out, const matrix4x4_t *in1) +{ + out->m[0][0] = in1->m[0][0]; + out->m[0][1] = in1->m[1][0]; + out->m[0][2] = in1->m[2][0]; + out->m[0][3] = in1->m[3][0]; + out->m[1][0] = in1->m[0][1]; + out->m[1][1] = in1->m[1][1]; + out->m[1][2] = in1->m[2][1]; + out->m[1][3] = in1->m[3][1]; + out->m[2][0] = in1->m[0][2]; + out->m[2][1] = in1->m[1][2]; + out->m[2][2] = in1->m[2][2]; + out->m[2][3] = in1->m[3][2]; + out->m[3][0] = in1->m[0][3]; + out->m[3][1] = in1->m[1][3]; + out->m[3][2] = in1->m[2][3]; + out->m[3][3] = in1->m[3][3]; +} + +#if 1 +// Adapted from code contributed to Mesa by David Moore (Mesa 7.6 under SGI Free License B - which is MIT/X11-type) +// added helper for common subexpression elimination by eihrul, and other optimizations by div0 +int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) +{ + float det; + + // note: orientation does not matter, as transpose(invert(transpose(m))) == invert(m), proof: + // transpose(invert(transpose(m))) * m + // = transpose(invert(transpose(m))) * transpose(transpose(m)) + // = transpose(transpose(m) * invert(transpose(m))) + // = transpose(identity) + // = identity + + // this seems to help gcc's common subexpression elimination, and also makes the code look nicer + float m00 = in1->m[0][0], m01 = in1->m[0][1], m02 = in1->m[0][2], m03 = in1->m[0][3], + m10 = in1->m[1][0], m11 = in1->m[1][1], m12 = in1->m[1][2], m13 = in1->m[1][3], + m20 = in1->m[2][0], m21 = in1->m[2][1], m22 = in1->m[2][2], m23 = in1->m[2][3], + m30 = in1->m[3][0], m31 = in1->m[3][1], m32 = in1->m[3][2], m33 = in1->m[3][3]; + + // calculate the adjoint + out->m[0][0] = (m11*(m22*m33 - m23*m32) - m21*(m12*m33 - m13*m32) + m31*(m12*m23 - m13*m22)); + out->m[0][1] = -(m01*(m22*m33 - m23*m32) - m21*(m02*m33 - m03*m32) + m31*(m02*m23 - m03*m22)); + out->m[0][2] = (m01*(m12*m33 - m13*m32) - m11*(m02*m33 - m03*m32) + m31*(m02*m13 - m03*m12)); + out->m[0][3] = -(m01*(m12*m23 - m13*m22) - m11*(m02*m23 - m03*m22) + m21*(m02*m13 - m03*m12)); + out->m[1][0] = -(m10*(m22*m33 - m23*m32) - m20*(m12*m33 - m13*m32) + m30*(m12*m23 - m13*m22)); + out->m[1][1] = (m00*(m22*m33 - m23*m32) - m20*(m02*m33 - m03*m32) + m30*(m02*m23 - m03*m22)); + out->m[1][2] = -(m00*(m12*m33 - m13*m32) - m10*(m02*m33 - m03*m32) + m30*(m02*m13 - m03*m12)); + out->m[1][3] = (m00*(m12*m23 - m13*m22) - m10*(m02*m23 - m03*m22) + m20*(m02*m13 - m03*m12)); + out->m[2][0] = (m10*(m21*m33 - m23*m31) - m20*(m11*m33 - m13*m31) + m30*(m11*m23 - m13*m21)); + out->m[2][1] = -(m00*(m21*m33 - m23*m31) - m20*(m01*m33 - m03*m31) + m30*(m01*m23 - m03*m21)); + out->m[2][2] = (m00*(m11*m33 - m13*m31) - m10*(m01*m33 - m03*m31) + m30*(m01*m13 - m03*m11)); + out->m[2][3] = -(m00*(m11*m23 - m13*m21) - m10*(m01*m23 - m03*m21) + m20*(m01*m13 - m03*m11)); + out->m[3][0] = -(m10*(m21*m32 - m22*m31) - m20*(m11*m32 - m12*m31) + m30*(m11*m22 - m12*m21)); + out->m[3][1] = (m00*(m21*m32 - m22*m31) - m20*(m01*m32 - m02*m31) + m30*(m01*m22 - m02*m21)); + out->m[3][2] = -(m00*(m11*m32 - m12*m31) - m10*(m01*m32 - m02*m31) + m30*(m01*m12 - m02*m11)); + out->m[3][3] = (m00*(m11*m22 - m12*m21) - m10*(m01*m22 - m02*m21) + m20*(m01*m12 - m02*m11)); + + // calculate the determinant (as inverse == 1/det * adjoint, adjoint * m == identity * det, so this calculates the det) + det = m00*out->m[0][0] + m10*out->m[0][1] + m20*out->m[0][2] + m30*out->m[0][3]; + if (det == 0.0f) + return 0; + + // multiplications are faster than divisions, usually + det = 1.0f / det; + + // manually unrolled loop to multiply all matrix elements by 1/det + out->m[0][0] *= det; out->m[0][1] *= det; out->m[0][2] *= det; out->m[0][3] *= det; + out->m[1][0] *= det; out->m[1][1] *= det; out->m[1][2] *= det; out->m[1][3] *= det; + out->m[2][0] *= det; out->m[2][1] *= det; out->m[2][2] *= det; out->m[2][3] *= det; + out->m[3][0] *= det; out->m[3][1] *= det; out->m[3][2] *= det; out->m[3][3] *= det; + + return 1; +} +#elif 1 +// Adapted from code contributed to Mesa by David Moore (Mesa 7.6 under SGI Free License B - which is MIT/X11-type) +int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) +{ + matrix4x4_t temp; + double det; + int i, j; + +#ifdef MATRIX4x4_OPENGLORIENTATION + temp.m[0][0] = in1->m[1][1]*in1->m[2][2]*in1->m[3][3] - in1->m[1][1]*in1->m[2][3]*in1->m[3][2] - in1->m[2][1]*in1->m[1][2]*in1->m[3][3] + in1->m[2][1]*in1->m[1][3]*in1->m[3][2] + in1->m[3][1]*in1->m[1][2]*in1->m[2][3] - in1->m[3][1]*in1->m[1][3]*in1->m[2][2]; + temp.m[1][0] = -in1->m[1][0]*in1->m[2][2]*in1->m[3][3] + in1->m[1][0]*in1->m[2][3]*in1->m[3][2] + in1->m[2][0]*in1->m[1][2]*in1->m[3][3] - in1->m[2][0]*in1->m[1][3]*in1->m[3][2] - in1->m[3][0]*in1->m[1][2]*in1->m[2][3] + in1->m[3][0]*in1->m[1][3]*in1->m[2][2]; + temp.m[2][0] = in1->m[1][0]*in1->m[2][1]*in1->m[3][3] - in1->m[1][0]*in1->m[2][3]*in1->m[3][1] - in1->m[2][0]*in1->m[1][1]*in1->m[3][3] + in1->m[2][0]*in1->m[1][3]*in1->m[3][1] + in1->m[3][0]*in1->m[1][1]*in1->m[2][3] - in1->m[3][0]*in1->m[1][3]*in1->m[2][1]; + temp.m[3][0] = -in1->m[1][0]*in1->m[2][1]*in1->m[3][2] + in1->m[1][0]*in1->m[2][2]*in1->m[3][1] + in1->m[2][0]*in1->m[1][1]*in1->m[3][2] - in1->m[2][0]*in1->m[1][2]*in1->m[3][1] - in1->m[3][0]*in1->m[1][1]*in1->m[2][2] + in1->m[3][0]*in1->m[1][2]*in1->m[2][1]; + temp.m[0][1] = -in1->m[0][1]*in1->m[2][2]*in1->m[3][3] + in1->m[0][1]*in1->m[2][3]*in1->m[3][2] + in1->m[2][1]*in1->m[0][2]*in1->m[3][3] - in1->m[2][1]*in1->m[0][3]*in1->m[3][2] - in1->m[3][1]*in1->m[0][2]*in1->m[2][3] + in1->m[3][1]*in1->m[0][3]*in1->m[2][2]; + temp.m[1][1] = in1->m[0][0]*in1->m[2][2]*in1->m[3][3] - in1->m[0][0]*in1->m[2][3]*in1->m[3][2] - in1->m[2][0]*in1->m[0][2]*in1->m[3][3] + in1->m[2][0]*in1->m[0][3]*in1->m[3][2] + in1->m[3][0]*in1->m[0][2]*in1->m[2][3] - in1->m[3][0]*in1->m[0][3]*in1->m[2][2]; + temp.m[2][1] = -in1->m[0][0]*in1->m[2][1]*in1->m[3][3] + in1->m[0][0]*in1->m[2][3]*in1->m[3][1] + in1->m[2][0]*in1->m[0][1]*in1->m[3][3] - in1->m[2][0]*in1->m[0][3]*in1->m[3][1] - in1->m[3][0]*in1->m[0][1]*in1->m[2][3] + in1->m[3][0]*in1->m[0][3]*in1->m[2][1]; + temp.m[3][1] = in1->m[0][0]*in1->m[2][1]*in1->m[3][2] - in1->m[0][0]*in1->m[2][2]*in1->m[3][1] - in1->m[2][0]*in1->m[0][1]*in1->m[3][2] + in1->m[2][0]*in1->m[0][2]*in1->m[3][1] + in1->m[3][0]*in1->m[0][1]*in1->m[2][2] - in1->m[3][0]*in1->m[0][2]*in1->m[2][1]; + temp.m[0][2] = in1->m[0][1]*in1->m[1][2]*in1->m[3][3] - in1->m[0][1]*in1->m[1][3]*in1->m[3][2] - in1->m[1][1]*in1->m[0][2]*in1->m[3][3] + in1->m[1][1]*in1->m[0][3]*in1->m[3][2] + in1->m[3][1]*in1->m[0][2]*in1->m[1][3] - in1->m[3][1]*in1->m[0][3]*in1->m[1][2]; + temp.m[1][2] = -in1->m[0][0]*in1->m[1][2]*in1->m[3][3] + in1->m[0][0]*in1->m[1][3]*in1->m[3][2] + in1->m[1][0]*in1->m[0][2]*in1->m[3][3] - in1->m[1][0]*in1->m[0][3]*in1->m[3][2] - in1->m[3][0]*in1->m[0][2]*in1->m[1][3] + in1->m[3][0]*in1->m[0][3]*in1->m[1][2]; + temp.m[2][2] = in1->m[0][0]*in1->m[1][1]*in1->m[3][3] - in1->m[0][0]*in1->m[1][3]*in1->m[3][1] - in1->m[1][0]*in1->m[0][1]*in1->m[3][3] + in1->m[1][0]*in1->m[0][3]*in1->m[3][1] + in1->m[3][0]*in1->m[0][1]*in1->m[1][3] - in1->m[3][0]*in1->m[0][3]*in1->m[1][1]; + temp.m[3][2] = -in1->m[0][0]*in1->m[1][1]*in1->m[3][2] + in1->m[0][0]*in1->m[1][2]*in1->m[3][1] + in1->m[1][0]*in1->m[0][1]*in1->m[3][2] - in1->m[1][0]*in1->m[0][2]*in1->m[3][1] - in1->m[3][0]*in1->m[0][1]*in1->m[1][2] + in1->m[3][0]*in1->m[0][2]*in1->m[1][1]; + temp.m[0][3] = -in1->m[0][1]*in1->m[1][2]*in1->m[2][3] + in1->m[0][1]*in1->m[1][3]*in1->m[2][2] + in1->m[1][1]*in1->m[0][2]*in1->m[2][3] - in1->m[1][1]*in1->m[0][3]*in1->m[2][2] - in1->m[2][1]*in1->m[0][2]*in1->m[1][3] + in1->m[2][1]*in1->m[0][3]*in1->m[1][2]; + temp.m[1][3] = in1->m[0][0]*in1->m[1][2]*in1->m[2][3] - in1->m[0][0]*in1->m[1][3]*in1->m[2][2] - in1->m[1][0]*in1->m[0][2]*in1->m[2][3] + in1->m[1][0]*in1->m[0][3]*in1->m[2][2] + in1->m[2][0]*in1->m[0][2]*in1->m[1][3] - in1->m[2][0]*in1->m[0][3]*in1->m[1][2]; + temp.m[2][3] = -in1->m[0][0]*in1->m[1][1]*in1->m[2][3] + in1->m[0][0]*in1->m[1][3]*in1->m[2][1] + in1->m[1][0]*in1->m[0][1]*in1->m[2][3] - in1->m[1][0]*in1->m[0][3]*in1->m[2][1] - in1->m[2][0]*in1->m[0][1]*in1->m[1][3] + in1->m[2][0]*in1->m[0][3]*in1->m[1][1]; + temp.m[3][3] = in1->m[0][0]*in1->m[1][1]*in1->m[2][2] - in1->m[0][0]*in1->m[1][2]*in1->m[2][1] - in1->m[1][0]*in1->m[0][1]*in1->m[2][2] + in1->m[1][0]*in1->m[0][2]*in1->m[2][1] + in1->m[2][0]*in1->m[0][1]*in1->m[1][2] - in1->m[2][0]*in1->m[0][2]*in1->m[1][1]; +#else + temp.m[0][0] = in1->m[1][1]*in1->m[2][2]*in1->m[3][3] - in1->m[1][1]*in1->m[3][2]*in1->m[2][3] - in1->m[1][2]*in1->m[2][1]*in1->m[3][3] + in1->m[1][2]*in1->m[3][1]*in1->m[2][3] + in1->m[1][3]*in1->m[2][1]*in1->m[3][2] - in1->m[1][3]*in1->m[3][1]*in1->m[2][2]; + temp.m[0][1] = -in1->m[0][1]*in1->m[2][2]*in1->m[3][3] + in1->m[0][1]*in1->m[3][2]*in1->m[2][3] + in1->m[0][2]*in1->m[2][1]*in1->m[3][3] - in1->m[0][2]*in1->m[3][1]*in1->m[2][3] - in1->m[0][3]*in1->m[2][1]*in1->m[3][2] + in1->m[0][3]*in1->m[3][1]*in1->m[2][2]; + temp.m[0][2] = in1->m[0][1]*in1->m[1][2]*in1->m[3][3] - in1->m[0][1]*in1->m[3][2]*in1->m[1][3] - in1->m[0][2]*in1->m[1][1]*in1->m[3][3] + in1->m[0][2]*in1->m[3][1]*in1->m[1][3] + in1->m[0][3]*in1->m[1][1]*in1->m[3][2] - in1->m[0][3]*in1->m[3][1]*in1->m[1][2]; + temp.m[0][3] = -in1->m[0][1]*in1->m[1][2]*in1->m[2][3] + in1->m[0][1]*in1->m[2][2]*in1->m[1][3] + in1->m[0][2]*in1->m[1][1]*in1->m[2][3] - in1->m[0][2]*in1->m[2][1]*in1->m[1][3] - in1->m[0][3]*in1->m[1][1]*in1->m[2][2] + in1->m[0][3]*in1->m[2][1]*in1->m[1][2]; + temp.m[1][0] = -in1->m[1][0]*in1->m[2][2]*in1->m[3][3] + in1->m[1][0]*in1->m[3][2]*in1->m[2][3] + in1->m[1][2]*in1->m[2][0]*in1->m[3][3] - in1->m[1][2]*in1->m[3][0]*in1->m[2][3] - in1->m[1][3]*in1->m[2][0]*in1->m[3][2] + in1->m[1][3]*in1->m[3][0]*in1->m[2][2]; + temp.m[1][1] = in1->m[0][0]*in1->m[2][2]*in1->m[3][3] - in1->m[0][0]*in1->m[3][2]*in1->m[2][3] - in1->m[0][2]*in1->m[2][0]*in1->m[3][3] + in1->m[0][2]*in1->m[3][0]*in1->m[2][3] + in1->m[0][3]*in1->m[2][0]*in1->m[3][2] - in1->m[0][3]*in1->m[3][0]*in1->m[2][2]; + temp.m[1][2] = -in1->m[0][0]*in1->m[1][2]*in1->m[3][3] + in1->m[0][0]*in1->m[3][2]*in1->m[1][3] + in1->m[0][2]*in1->m[1][0]*in1->m[3][3] - in1->m[0][2]*in1->m[3][0]*in1->m[1][3] - in1->m[0][3]*in1->m[1][0]*in1->m[3][2] + in1->m[0][3]*in1->m[3][0]*in1->m[1][2]; + temp.m[1][3] = in1->m[0][0]*in1->m[1][2]*in1->m[2][3] - in1->m[0][0]*in1->m[2][2]*in1->m[1][3] - in1->m[0][2]*in1->m[1][0]*in1->m[2][3] + in1->m[0][2]*in1->m[2][0]*in1->m[1][3] + in1->m[0][3]*in1->m[1][0]*in1->m[2][2] - in1->m[0][3]*in1->m[2][0]*in1->m[1][2]; + temp.m[2][0] = in1->m[1][0]*in1->m[2][1]*in1->m[3][3] - in1->m[1][0]*in1->m[3][1]*in1->m[2][3] - in1->m[1][1]*in1->m[2][0]*in1->m[3][3] + in1->m[1][1]*in1->m[3][0]*in1->m[2][3] + in1->m[1][3]*in1->m[2][0]*in1->m[3][1] - in1->m[1][3]*in1->m[3][0]*in1->m[2][1]; + temp.m[2][1] = -in1->m[0][0]*in1->m[2][1]*in1->m[3][3] + in1->m[0][0]*in1->m[3][1]*in1->m[2][3] + in1->m[0][1]*in1->m[2][0]*in1->m[3][3] - in1->m[0][1]*in1->m[3][0]*in1->m[2][3] - in1->m[0][3]*in1->m[2][0]*in1->m[3][1] + in1->m[0][3]*in1->m[3][0]*in1->m[2][1]; + temp.m[2][2] = in1->m[0][0]*in1->m[1][1]*in1->m[3][3] - in1->m[0][0]*in1->m[3][1]*in1->m[1][3] - in1->m[0][1]*in1->m[1][0]*in1->m[3][3] + in1->m[0][1]*in1->m[3][0]*in1->m[1][3] + in1->m[0][3]*in1->m[1][0]*in1->m[3][1] - in1->m[0][3]*in1->m[3][0]*in1->m[1][1]; + temp.m[2][3] = -in1->m[0][0]*in1->m[1][1]*in1->m[2][3] + in1->m[0][0]*in1->m[2][1]*in1->m[1][3] + in1->m[0][1]*in1->m[1][0]*in1->m[2][3] - in1->m[0][1]*in1->m[2][0]*in1->m[1][3] - in1->m[0][3]*in1->m[1][0]*in1->m[2][1] + in1->m[0][3]*in1->m[2][0]*in1->m[1][1]; + temp.m[3][0] = -in1->m[1][0]*in1->m[2][1]*in1->m[3][2] + in1->m[1][0]*in1->m[3][1]*in1->m[2][2] + in1->m[1][1]*in1->m[2][0]*in1->m[3][2] - in1->m[1][1]*in1->m[3][0]*in1->m[2][2] - in1->m[1][2]*in1->m[2][0]*in1->m[3][1] + in1->m[1][2]*in1->m[3][0]*in1->m[2][1]; + temp.m[3][1] = in1->m[0][0]*in1->m[2][1]*in1->m[3][2] - in1->m[0][0]*in1->m[3][1]*in1->m[2][2] - in1->m[0][1]*in1->m[2][0]*in1->m[3][2] + in1->m[0][1]*in1->m[3][0]*in1->m[2][2] + in1->m[0][2]*in1->m[2][0]*in1->m[3][1] - in1->m[0][2]*in1->m[3][0]*in1->m[2][1]; + temp.m[3][2] = -in1->m[0][0]*in1->m[1][1]*in1->m[3][2] + in1->m[0][0]*in1->m[3][1]*in1->m[1][2] + in1->m[0][1]*in1->m[1][0]*in1->m[3][2] - in1->m[0][1]*in1->m[3][0]*in1->m[1][2] - in1->m[0][2]*in1->m[1][0]*in1->m[3][1] + in1->m[0][2]*in1->m[3][0]*in1->m[1][1]; + temp.m[3][3] = in1->m[0][0]*in1->m[1][1]*in1->m[2][2] - in1->m[0][0]*in1->m[2][1]*in1->m[1][2] - in1->m[0][1]*in1->m[1][0]*in1->m[2][2] + in1->m[0][1]*in1->m[2][0]*in1->m[1][2] + in1->m[0][2]*in1->m[1][0]*in1->m[2][1] - in1->m[0][2]*in1->m[2][0]*in1->m[1][1]; +#endif + + det = in1->m[0][0]*temp.m[0][0] + in1->m[1][0]*temp.m[0][1] + in1->m[2][0]*temp.m[0][2] + in1->m[3][0]*temp.m[0][3]; + if (det == 0.0f) + return 0; + + det = 1.0f / det; + + for (i = 0;i < 4;i++) + for (j = 0;j < 4;j++) + out->m[i][j] = temp.m[i][j] * det; + + return 1; +} +#else +int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) +{ + double *temp; + double *r[4]; + double rtemp[4][8]; + double m[4]; + double s; + + r[0] = rtemp[0]; + r[1] = rtemp[1]; + r[2] = rtemp[2]; + r[3] = rtemp[3]; + +#ifdef MATRIX4x4_OPENGLORIENTATION + r[0][0] = in1->m[0][0]; r[0][1] = in1->m[1][0]; r[0][2] = in1->m[2][0]; r[0][3] = in1->m[3][0]; + r[0][4] = 1.0; r[0][5] = r[0][6] = r[0][7] = 0.0; + + r[1][0] = in1->m[0][1]; r[1][1] = in1->m[1][1]; r[1][2] = in1->m[2][1]; r[1][3] = in1->m[3][1]; + r[1][5] = 1.0; r[1][4] = r[1][6] = r[1][7] = 0.0; + + r[2][0] = in1->m[0][2]; r[2][1] = in1->m[1][2]; r[2][2] = in1->m[2][2]; r[2][3] = in1->m[3][2]; + r[2][6] = 1.0; r[2][4] = r[2][5] = r[2][7] = 0.0; + + r[3][0] = in1->m[0][3]; r[3][1] = in1->m[1][3]; r[3][2] = in1->m[2][3]; r[3][3] = in1->m[3][3]; + r[3][7] = 1.0; r[3][4] = r[3][5] = r[3][6] = 0.0; +#else + r[0][0] = in1->m[0][0]; r[0][1] = in1->m[0][1]; r[0][2] = in1->m[0][2]; r[0][3] = in1->m[0][3]; + r[0][4] = 1.0; r[0][5] = r[0][6] = r[0][7] = 0.0; + + r[1][0] = in1->m[1][0]; r[1][1] = in1->m[1][1]; r[1][2] = in1->m[1][2]; r[1][3] = in1->m[1][3]; + r[1][5] = 1.0; r[1][4] = r[1][6] = r[1][7] = 0.0; + + r[2][0] = in1->m[2][0]; r[2][1] = in1->m[2][1]; r[2][2] = in1->m[2][2]; r[2][3] = in1->m[2][3]; + r[2][6] = 1.0; r[2][4] = r[2][5] = r[2][7] = 0.0; + + r[3][0] = in1->m[3][0]; r[3][1] = in1->m[3][1]; r[3][2] = in1->m[3][2]; r[3][3] = in1->m[3][3]; + r[3][7] = 1.0; r[3][4] = r[3][5] = r[3][6] = 0.0; +#endif + + if (fabs (r[3][0]) > fabs (r[2][0])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } + if (fabs (r[2][0]) > fabs (r[1][0])) { temp = r[2]; r[2] = r[1]; r[1] = temp; } + if (fabs (r[1][0]) > fabs (r[0][0])) { temp = r[1]; r[1] = r[0]; r[0] = temp; } + + if (r[0][0]) + { + m[1] = r[1][0] / r[0][0]; + m[2] = r[2][0] / r[0][0]; + m[3] = r[3][0] / r[0][0]; + + s = r[0][1]; r[1][1] -= m[1] * s; r[2][1] -= m[2] * s; r[3][1] -= m[3] * s; + s = r[0][2]; r[1][2] -= m[1] * s; r[2][2] -= m[2] * s; r[3][2] -= m[3] * s; + s = r[0][3]; r[1][3] -= m[1] * s; r[2][3] -= m[2] * s; r[3][3] -= m[3] * s; + + s = r[0][4]; if (s) { r[1][4] -= m[1] * s; r[2][4] -= m[2] * s; r[3][4] -= m[3] * s; } + s = r[0][5]; if (s) { r[1][5] -= m[1] * s; r[2][5] -= m[2] * s; r[3][5] -= m[3] * s; } + s = r[0][6]; if (s) { r[1][6] -= m[1] * s; r[2][6] -= m[2] * s; r[3][6] -= m[3] * s; } + s = r[0][7]; if (s) { r[1][7] -= m[1] * s; r[2][7] -= m[2] * s; r[3][7] -= m[3] * s; } + + if (fabs (r[3][1]) > fabs (r[2][1])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } + if (fabs (r[2][1]) > fabs (r[1][1])) { temp = r[2]; r[2] = r[1]; r[1] = temp; } + + if (r[1][1]) + { + m[2] = r[2][1] / r[1][1]; + m[3] = r[3][1] / r[1][1]; + r[2][2] -= m[2] * r[1][2]; + r[3][2] -= m[3] * r[1][2]; + r[2][3] -= m[2] * r[1][3]; + r[3][3] -= m[3] * r[1][3]; + + s = r[1][4]; if (s) { r[2][4] -= m[2] * s; r[3][4] -= m[3] * s; } + s = r[1][5]; if (s) { r[2][5] -= m[2] * s; r[3][5] -= m[3] * s; } + s = r[1][6]; if (s) { r[2][6] -= m[2] * s; r[3][6] -= m[3] * s; } + s = r[1][7]; if (s) { r[2][7] -= m[2] * s; r[3][7] -= m[3] * s; } + + if (fabs (r[3][2]) > fabs (r[2][2])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } + + if (r[2][2]) + { + m[3] = r[3][2] / r[2][2]; + r[3][3] -= m[3] * r[2][3]; + r[3][4] -= m[3] * r[2][4]; + r[3][5] -= m[3] * r[2][5]; + r[3][6] -= m[3] * r[2][6]; + r[3][7] -= m[3] * r[2][7]; + + if (r[3][3]) + { + s = 1.0 / r[3][3]; + r[3][4] *= s; + r[3][5] *= s; + r[3][6] *= s; + r[3][7] *= s; + + m[2] = r[2][3]; + s = 1.0 / r[2][2]; + r[2][4] = s * (r[2][4] - r[3][4] * m[2]); + r[2][5] = s * (r[2][5] - r[3][5] * m[2]); + r[2][6] = s * (r[2][6] - r[3][6] * m[2]); + r[2][7] = s * (r[2][7] - r[3][7] * m[2]); + + m[1] = r[1][3]; + r[1][4] -= r[3][4] * m[1], r[1][5] -= r[3][5] * m[1]; + r[1][6] -= r[3][6] * m[1], r[1][7] -= r[3][7] * m[1]; + + m[0] = r[0][3]; + r[0][4] -= r[3][4] * m[0], r[0][5] -= r[3][5] * m[0]; + r[0][6] -= r[3][6] * m[0], r[0][7] -= r[3][7] * m[0]; + + m[1] = r[1][2]; + s = 1.0 / r[1][1]; + r[1][4] = s * (r[1][4] - r[2][4] * m[1]), r[1][5] = s * (r[1][5] - r[2][5] * m[1]); + r[1][6] = s * (r[1][6] - r[2][6] * m[1]), r[1][7] = s * (r[1][7] - r[2][7] * m[1]); + + m[0] = r[0][2]; + r[0][4] -= r[2][4] * m[0], r[0][5] -= r[2][5] * m[0]; + r[0][6] -= r[2][6] * m[0], r[0][7] -= r[2][7] * m[0]; + + m[0] = r[0][1]; + s = 1.0 / r[0][0]; + r[0][4] = s * (r[0][4] - r[1][4] * m[0]), r[0][5] = s * (r[0][5] - r[1][5] * m[0]); + r[0][6] = s * (r[0][6] - r[1][6] * m[0]), r[0][7] = s * (r[0][7] - r[1][7] * m[0]); + +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = r[0][4]; + out->m[0][1] = r[1][4]; + out->m[0][2] = r[2][4]; + out->m[0][3] = r[3][4]; + out->m[1][0] = r[0][5]; + out->m[1][1] = r[1][5]; + out->m[1][2] = r[2][5]; + out->m[1][3] = r[3][5]; + out->m[2][0] = r[0][6]; + out->m[2][1] = r[1][6]; + out->m[2][2] = r[2][6]; + out->m[2][3] = r[3][6]; + out->m[3][0] = r[0][7]; + out->m[3][1] = r[1][7]; + out->m[3][2] = r[2][7]; + out->m[3][3] = r[3][7]; +#else + out->m[0][0] = r[0][4]; + out->m[0][1] = r[0][5]; + out->m[0][2] = r[0][6]; + out->m[0][3] = r[0][7]; + out->m[1][0] = r[1][4]; + out->m[1][1] = r[1][5]; + out->m[1][2] = r[1][6]; + out->m[1][3] = r[1][7]; + out->m[2][0] = r[2][4]; + out->m[2][1] = r[2][5]; + out->m[2][2] = r[2][6]; + out->m[2][3] = r[2][7]; + out->m[3][0] = r[3][4]; + out->m[3][1] = r[3][5]; + out->m[3][2] = r[3][6]; + out->m[3][3] = r[3][7]; +#endif + + return 1; + } + } + } + } + + return 0; +} +#endif + +void Matrix4x4_Invert_Simple (matrix4x4_t *out, const matrix4x4_t *in1) +{ + // we only support uniform scaling, so assume the first row is enough + // (note the lack of sqrt here, because we're trying to undo the scaling, + // this means multiplying by the inverse scale twice - squaring it, which + // makes the sqrt a waste of time) +#if 1 + double scale = 1.0 / (in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2]); +#else + double scale = 3.0 / sqrt + (in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2] + + in1->m[1][0] * in1->m[1][0] + in1->m[1][1] * in1->m[1][1] + in1->m[1][2] * in1->m[1][2] + + in1->m[2][0] * in1->m[2][0] + in1->m[2][1] * in1->m[2][1] + in1->m[2][2] * in1->m[2][2]); + scale *= scale; +#endif + + // invert the rotation by transposing and multiplying by the squared + // recipricol of the input matrix scale as described above + out->m[0][0] = in1->m[0][0] * scale; + out->m[0][1] = in1->m[1][0] * scale; + out->m[0][2] = in1->m[2][0] * scale; + out->m[1][0] = in1->m[0][1] * scale; + out->m[1][1] = in1->m[1][1] * scale; + out->m[1][2] = in1->m[2][1] * scale; + out->m[2][0] = in1->m[0][2] * scale; + out->m[2][1] = in1->m[1][2] * scale; + out->m[2][2] = in1->m[2][2] * scale; + +#ifdef MATRIX4x4_OPENGLORIENTATION + // invert the translate + out->m[3][0] = -(in1->m[3][0] * out->m[0][0] + in1->m[3][1] * out->m[1][0] + in1->m[3][2] * out->m[2][0]); + out->m[3][1] = -(in1->m[3][0] * out->m[0][1] + in1->m[3][1] * out->m[1][1] + in1->m[3][2] * out->m[2][1]); + out->m[3][2] = -(in1->m[3][0] * out->m[0][2] + in1->m[3][1] * out->m[1][2] + in1->m[3][2] * out->m[2][2]); + + // don't know if there's anything worth doing here + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + // invert the translate + out->m[0][3] = -(in1->m[0][3] * out->m[0][0] + in1->m[1][3] * out->m[0][1] + in1->m[2][3] * out->m[0][2]); + out->m[1][3] = -(in1->m[0][3] * out->m[1][0] + in1->m[1][3] * out->m[1][1] + in1->m[2][3] * out->m[1][2]); + out->m[2][3] = -(in1->m[0][3] * out->m[2][0] + in1->m[1][3] * out->m[2][1] + in1->m[2][3] * out->m[2][2]); + + // don't know if there's anything worth doing here + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif +} + +void Matrix4x4_Interpolate (matrix4x4_t *out, matrix4x4_t *in1, matrix4x4_t *in2, double frac) +{ + int i, j; + for (i = 0;i < 4;i++) + for (j = 0;j < 4;j++) + out->m[i][j] = in1->m[i][j] + frac * (in2->m[i][j] - in1->m[i][j]); +} + +void Matrix4x4_Clear (matrix4x4_t *out) +{ + int i, j; + for (i = 0;i < 4;i++) + for (j = 0;j < 4;j++) + out->m[i][j] = 0; +} + +void Matrix4x4_Accumulate (matrix4x4_t *out, matrix4x4_t *in, double weight) +{ + int i, j; + for (i = 0;i < 4;i++) + for (j = 0;j < 4;j++) + out->m[i][j] += in->m[i][j] * weight; +} + +void Matrix4x4_Normalize (matrix4x4_t *out, matrix4x4_t *in1) +{ + // scale rotation matrix vectors to a length of 1 + // note: this is only designed to undo uniform scaling + double scale = 1.0 / sqrt(in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2]); + *out = *in1; + Matrix4x4_Scale(out, scale, 1); +} + +void Matrix4x4_Normalize3 (matrix4x4_t *out, matrix4x4_t *in1) +{ + int i; + double scale; + // scale each rotation matrix vector to a length of 1 + // intended for use after Matrix4x4_Interpolate or Matrix4x4_Accumulate + *out = *in1; + for (i = 0;i < 3;i++) + { +#ifdef MATRIX4x4_OPENGLORIENTATION + scale = sqrt(in1->m[i][0] * in1->m[i][0] + in1->m[i][1] * in1->m[i][1] + in1->m[i][2] * in1->m[i][2]); + if (scale) + scale = 1.0 / scale; + out->m[i][0] *= scale; + out->m[i][1] *= scale; + out->m[i][2] *= scale; +#else + scale = sqrt(in1->m[0][i] * in1->m[0][i] + in1->m[1][i] * in1->m[1][i] + in1->m[2][i] * in1->m[2][i]); + if (scale) + scale = 1.0 / scale; + out->m[0][i] *= scale; + out->m[1][i] *= scale; + out->m[2][i] *= scale; +#endif + } +} + +void Matrix4x4_Reflect (matrix4x4_t *out, double normalx, double normaly, double normalz, double dist, double axisscale) +{ + int i; + double d; + double p[4], p2[4]; + p[0] = normalx; + p[1] = normaly; + p[2] = normalz; + p[3] = -dist; + p2[0] = p[0] * axisscale; + p2[1] = p[1] * axisscale; + p2[2] = p[2] * axisscale; + p2[3] = 0; + for (i = 0;i < 4;i++) + { +#ifdef MATRIX4x4_OPENGLORIENTATION + d = out->m[i][0] * p[0] + out->m[i][1] * p[1] + out->m[i][2] * p[2] + out->m[i][3] * p[3]; + out->m[i][0] += p2[0] * d; + out->m[i][1] += p2[1] * d; + out->m[i][2] += p2[2] * d; +#else + d = out->m[0][i] * p[0] + out->m[1][i] * p[1] + out->m[2][i] * p[2] + out->m[3][i] * p[3]; + out->m[0][i] += p2[0] * d; + out->m[1][i] += p2[1] * d; + out->m[2][i] += p2[2] * d; +#endif + } +} + +void Matrix4x4_CreateIdentity (matrix4x4_t *out) +{ + out->m[0][0]=1.0f; + out->m[0][1]=0.0f; + out->m[0][2]=0.0f; + out->m[0][3]=0.0f; + out->m[1][0]=0.0f; + out->m[1][1]=1.0f; + out->m[1][2]=0.0f; + out->m[1][3]=0.0f; + out->m[2][0]=0.0f; + out->m[2][1]=0.0f; + out->m[2][2]=1.0f; + out->m[2][3]=0.0f; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +} + +void Matrix4x4_CreateTranslate (matrix4x4_t *out, double x, double y, double z) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0]=1.0f; + out->m[1][0]=0.0f; + out->m[2][0]=0.0f; + out->m[3][0]=x; + out->m[0][1]=0.0f; + out->m[1][1]=1.0f; + out->m[2][1]=0.0f; + out->m[3][1]=y; + out->m[0][2]=0.0f; + out->m[1][2]=0.0f; + out->m[2][2]=1.0f; + out->m[3][2]=z; + out->m[0][3]=0.0f; + out->m[1][3]=0.0f; + out->m[2][3]=0.0f; + out->m[3][3]=1.0f; +#else + out->m[0][0]=1.0f; + out->m[0][1]=0.0f; + out->m[0][2]=0.0f; + out->m[0][3]=x; + out->m[1][0]=0.0f; + out->m[1][1]=1.0f; + out->m[1][2]=0.0f; + out->m[1][3]=y; + out->m[2][0]=0.0f; + out->m[2][1]=0.0f; + out->m[2][2]=1.0f; + out->m[2][3]=z; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +#endif +} + +void Matrix4x4_CreateRotate (matrix4x4_t *out, double angle, double x, double y, double z) +{ + double len, c, s; + + len = x*x+y*y+z*z; + if (len != 0.0f) + len = 1.0f / sqrt(len); + x *= len; + y *= len; + z *= len; + + angle *= (-M_PI / 180.0); + c = cos(angle); + s = sin(angle); + +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0]=x * x + c * (1 - x * x); + out->m[1][0]=x * y * (1 - c) + z * s; + out->m[2][0]=z * x * (1 - c) - y * s; + out->m[3][0]=0.0f; + out->m[0][1]=x * y * (1 - c) - z * s; + out->m[1][1]=y * y + c * (1 - y * y); + out->m[2][1]=y * z * (1 - c) + x * s; + out->m[3][1]=0.0f; + out->m[0][2]=z * x * (1 - c) + y * s; + out->m[1][2]=y * z * (1 - c) - x * s; + out->m[2][2]=z * z + c * (1 - z * z); + out->m[3][2]=0.0f; + out->m[0][3]=0.0f; + out->m[1][3]=0.0f; + out->m[2][3]=0.0f; + out->m[3][3]=1.0f; +#else + out->m[0][0]=x * x + c * (1 - x * x); + out->m[0][1]=x * y * (1 - c) + z * s; + out->m[0][2]=z * x * (1 - c) - y * s; + out->m[0][3]=0.0f; + out->m[1][0]=x * y * (1 - c) - z * s; + out->m[1][1]=y * y + c * (1 - y * y); + out->m[1][2]=y * z * (1 - c) + x * s; + out->m[1][3]=0.0f; + out->m[2][0]=z * x * (1 - c) + y * s; + out->m[2][1]=y * z * (1 - c) - x * s; + out->m[2][2]=z * z + c * (1 - z * z); + out->m[2][3]=0.0f; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +#endif +} + +void Matrix4x4_CreateScale (matrix4x4_t *out, double x) +{ + out->m[0][0]=x; + out->m[0][1]=0.0f; + out->m[0][2]=0.0f; + out->m[0][3]=0.0f; + out->m[1][0]=0.0f; + out->m[1][1]=x; + out->m[1][2]=0.0f; + out->m[1][3]=0.0f; + out->m[2][0]=0.0f; + out->m[2][1]=0.0f; + out->m[2][2]=x; + out->m[2][3]=0.0f; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +} + +void Matrix4x4_CreateScale3 (matrix4x4_t *out, double x, double y, double z) +{ + out->m[0][0]=x; + out->m[0][1]=0.0f; + out->m[0][2]=0.0f; + out->m[0][3]=0.0f; + out->m[1][0]=0.0f; + out->m[1][1]=y; + out->m[1][2]=0.0f; + out->m[1][3]=0.0f; + out->m[2][0]=0.0f; + out->m[2][1]=0.0f; + out->m[2][2]=z; + out->m[2][3]=0.0f; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +} + +void Matrix4x4_CreateFromQuakeEntity(matrix4x4_t *out, double x, double y, double z, double pitch, double yaw, double roll, double scale) +{ + double angle, sr, sp, sy, cr, cp, cy; + + if (roll) + { + angle = yaw * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = pitch * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = roll * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = (cp*cy) * scale; + out->m[1][0] = (sr*sp*cy+cr*-sy) * scale; + out->m[2][0] = (cr*sp*cy+-sr*-sy) * scale; + out->m[3][0] = x; + out->m[0][1] = (cp*sy) * scale; + out->m[1][1] = (sr*sp*sy+cr*cy) * scale; + out->m[2][1] = (cr*sp*sy+-sr*cy) * scale; + out->m[3][1] = y; + out->m[0][2] = (-sp) * scale; + out->m[1][2] = (sr*cp) * scale; + out->m[2][2] = (cr*cp) * scale; + out->m[3][2] = z; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = (cp*cy) * scale; + out->m[0][1] = (sr*sp*cy+cr*-sy) * scale; + out->m[0][2] = (cr*sp*cy+-sr*-sy) * scale; + out->m[0][3] = x; + out->m[1][0] = (cp*sy) * scale; + out->m[1][1] = (sr*sp*sy+cr*cy) * scale; + out->m[1][2] = (cr*sp*sy+-sr*cy) * scale; + out->m[1][3] = y; + out->m[2][0] = (-sp) * scale; + out->m[2][1] = (sr*cp) * scale; + out->m[2][2] = (cr*cp) * scale; + out->m[2][3] = z; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif + } + else if (pitch) + { + angle = yaw * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = pitch * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = (cp*cy) * scale; + out->m[1][0] = (-sy) * scale; + out->m[2][0] = (sp*cy) * scale; + out->m[3][0] = x; + out->m[0][1] = (cp*sy) * scale; + out->m[1][1] = (cy) * scale; + out->m[2][1] = (sp*sy) * scale; + out->m[3][1] = y; + out->m[0][2] = (-sp) * scale; + out->m[1][2] = 0; + out->m[2][2] = (cp) * scale; + out->m[3][2] = z; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = (cp*cy) * scale; + out->m[0][1] = (-sy) * scale; + out->m[0][2] = (sp*cy) * scale; + out->m[0][3] = x; + out->m[1][0] = (cp*sy) * scale; + out->m[1][1] = (cy) * scale; + out->m[1][2] = (sp*sy) * scale; + out->m[1][3] = y; + out->m[2][0] = (-sp) * scale; + out->m[2][1] = 0; + out->m[2][2] = (cp) * scale; + out->m[2][3] = z; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif + } + else if (yaw) + { + angle = yaw * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = (cy) * scale; + out->m[1][0] = (-sy) * scale; + out->m[2][0] = 0; + out->m[3][0] = x; + out->m[0][1] = (sy) * scale; + out->m[1][1] = (cy) * scale; + out->m[2][1] = 0; + out->m[3][1] = y; + out->m[0][2] = 0; + out->m[1][2] = 0; + out->m[2][2] = scale; + out->m[3][2] = z; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = (cy) * scale; + out->m[0][1] = (-sy) * scale; + out->m[0][2] = 0; + out->m[0][3] = x; + out->m[1][0] = (sy) * scale; + out->m[1][1] = (cy) * scale; + out->m[1][2] = 0; + out->m[1][3] = y; + out->m[2][0] = 0; + out->m[2][1] = 0; + out->m[2][2] = scale; + out->m[2][3] = z; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif + } + else + { +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = scale; + out->m[1][0] = 0; + out->m[2][0] = 0; + out->m[3][0] = x; + out->m[0][1] = 0; + out->m[1][1] = scale; + out->m[2][1] = 0; + out->m[3][1] = y; + out->m[0][2] = 0; + out->m[1][2] = 0; + out->m[2][2] = scale; + out->m[3][2] = z; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = scale; + out->m[0][1] = 0; + out->m[0][2] = 0; + out->m[0][3] = x; + out->m[1][0] = 0; + out->m[1][1] = scale; + out->m[1][2] = 0; + out->m[1][3] = y; + out->m[2][0] = 0; + out->m[2][1] = 0; + out->m[2][2] = scale; + out->m[2][3] = z; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif + } +} + +void Matrix4x4_ToVectors(const matrix4x4_t *in, float vx[3], float vy[3], float vz[3], float t[3]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + vx[0] = in->m[0][0]; + vx[1] = in->m[0][1]; + vx[2] = in->m[0][2]; + vy[0] = in->m[1][0]; + vy[1] = in->m[1][1]; + vy[2] = in->m[1][2]; + vz[0] = in->m[2][0]; + vz[1] = in->m[2][1]; + vz[2] = in->m[2][2]; + t [0] = in->m[3][0]; + t [1] = in->m[3][1]; + t [2] = in->m[3][2]; +#else + vx[0] = in->m[0][0]; + vx[1] = in->m[1][0]; + vx[2] = in->m[2][0]; + vy[0] = in->m[0][1]; + vy[1] = in->m[1][1]; + vy[2] = in->m[2][1]; + vz[0] = in->m[0][2]; + vz[1] = in->m[1][2]; + vz[2] = in->m[2][2]; + t [0] = in->m[0][3]; + t [1] = in->m[1][3]; + t [2] = in->m[2][3]; +#endif +} + +void Matrix4x4_FromVectors(matrix4x4_t *out, const float vx[3], const float vy[3], const float vz[3], const float t[3]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = vx[0]; + out->m[1][0] = vy[0]; + out->m[2][0] = vz[0]; + out->m[3][0] = t[0]; + out->m[0][1] = vx[1]; + out->m[1][1] = vy[1]; + out->m[2][1] = vz[1]; + out->m[3][1] = t[1]; + out->m[0][2] = vx[2]; + out->m[1][2] = vy[2]; + out->m[2][2] = vz[2]; + out->m[3][2] = t[2]; + out->m[0][3] = 0.0f; + out->m[1][3] = 0.0f; + out->m[2][3] = 0.0f; + out->m[3][3] = 1.0f; +#else + out->m[0][0] = vx[0]; + out->m[0][1] = vy[0]; + out->m[0][2] = vz[0]; + out->m[0][3] = t[0]; + out->m[1][0] = vx[1]; + out->m[1][1] = vy[1]; + out->m[1][2] = vz[1]; + out->m[1][3] = t[1]; + out->m[2][0] = vx[2]; + out->m[2][1] = vy[2]; + out->m[2][2] = vz[2]; + out->m[2][3] = t[2]; + out->m[3][0] = 0.0f; + out->m[3][1] = 0.0f; + out->m[3][2] = 0.0f; + out->m[3][3] = 1.0f; +#endif +} + +void Matrix4x4_ToArrayDoubleGL(const matrix4x4_t *in, double out[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; + out[12] = in->m[3][0]; + out[13] = in->m[3][1]; + out[14] = in->m[3][2]; + out[15] = in->m[3][3]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; + out[12] = in->m[0][3]; + out[13] = in->m[1][3]; + out[14] = in->m[2][3]; + out[15] = in->m[3][3]; +#endif +} + +void Matrix4x4_FromArrayDoubleGL (matrix4x4_t *out, const double in[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = in[12]; + out->m[3][1] = in[13]; + out->m[3][2] = in[14]; + out->m[3][3] = in[15]; +#else + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = in[12]; + out->m[1][3] = in[13]; + out->m[2][3] = in[14]; + out->m[3][3] = in[15]; +#endif +} + +void Matrix4x4_ToArrayDoubleD3D(const matrix4x4_t *in, double out[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; + out[12] = in->m[0][3]; + out[13] = in->m[1][3]; + out[14] = in->m[2][3]; + out[15] = in->m[3][3]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; + out[12] = in->m[3][0]; + out[13] = in->m[3][1]; + out[14] = in->m[3][2]; + out[15] = in->m[3][3]; +#endif +} + +void Matrix4x4_FromArrayDoubleD3D (matrix4x4_t *out, const double in[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = in[12]; + out->m[1][3] = in[13]; + out->m[2][3] = in[14]; + out->m[3][3] = in[15]; +#else + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = in[12]; + out->m[3][1] = in[13]; + out->m[3][2] = in[14]; + out->m[3][3] = in[15]; +#endif +} + +void Matrix4x4_ToArrayFloatGL(const matrix4x4_t *in, float out[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; + out[12] = in->m[3][0]; + out[13] = in->m[3][1]; + out[14] = in->m[3][2]; + out[15] = in->m[3][3]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; + out[12] = in->m[0][3]; + out[13] = in->m[1][3]; + out[14] = in->m[2][3]; + out[15] = in->m[3][3]; +#endif +} + +void Matrix4x4_FromArrayFloatGL (matrix4x4_t *out, const float in[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = in[12]; + out->m[3][1] = in[13]; + out->m[3][2] = in[14]; + out->m[3][3] = in[15]; +#else + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = in[12]; + out->m[1][3] = in[13]; + out->m[2][3] = in[14]; + out->m[3][3] = in[15]; +#endif +} + +void Matrix4x4_ToArrayFloatD3D(const matrix4x4_t *in, float out[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; + out[12] = in->m[0][3]; + out[13] = in->m[1][3]; + out[14] = in->m[2][3]; + out[15] = in->m[3][3]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; + out[12] = in->m[3][0]; + out[13] = in->m[3][1]; + out[14] = in->m[3][2]; + out[15] = in->m[3][3]; +#endif +} + +void Matrix4x4_FromArrayFloatD3D (matrix4x4_t *out, const float in[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = in[12]; + out->m[1][3] = in[13]; + out->m[2][3] = in[14]; + out->m[3][3] = in[15]; +#else + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = in[12]; + out->m[3][1] = in[13]; + out->m[3][2] = in[14]; + out->m[3][3] = in[15]; +#endif +} + +void Matrix4x4_ToArray12FloatGL(const matrix4x4_t *in, float out[12]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[1][0]; + out[ 4] = in->m[1][1]; + out[ 5] = in->m[1][2]; + out[ 6] = in->m[2][0]; + out[ 7] = in->m[2][1]; + out[ 8] = in->m[2][2]; + out[ 9] = in->m[3][0]; + out[10] = in->m[3][1]; + out[11] = in->m[3][2]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[0][1]; + out[ 4] = in->m[1][1]; + out[ 5] = in->m[2][1]; + out[ 6] = in->m[0][2]; + out[ 7] = in->m[1][2]; + out[ 8] = in->m[2][2]; + out[ 9] = in->m[0][3]; + out[10] = in->m[1][3]; + out[11] = in->m[2][3]; +#endif +} + +void Matrix4x4_FromArray12FloatGL(matrix4x4_t *out, const float in[12]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = 0; + out->m[1][0] = in[3]; + out->m[1][1] = in[4]; + out->m[1][2] = in[5]; + out->m[1][3] = 0; + out->m[2][0] = in[6]; + out->m[2][1] = in[7]; + out->m[2][2] = in[8]; + out->m[2][3] = 0; + out->m[3][0] = in[9]; + out->m[3][1] = in[10]; + out->m[3][2] = in[11]; + out->m[3][3] = 1; +#else + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = 0; + out->m[0][1] = in[3]; + out->m[1][1] = in[4]; + out->m[2][1] = in[5]; + out->m[3][1] = 0; + out->m[0][2] = in[6]; + out->m[1][2] = in[7]; + out->m[2][2] = in[8]; + out->m[3][2] = 0; + out->m[0][3] = in[9]; + out->m[1][3] = in[10]; + out->m[2][3] = in[11]; + out->m[3][3] = 1; +#endif +} + +void Matrix4x4_ToArray12FloatD3D(const matrix4x4_t *in, float out[12]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; +#endif +} + +void Matrix4x4_FromArray12FloatD3D(matrix4x4_t *out, const float in[12]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif +} + +void Matrix4x4_FromOriginQuat(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z, double w) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + m->m[0][0]=1-2*(y*y+z*z);m->m[1][0]= 2*(x*y-z*w);m->m[2][0]= 2*(x*z+y*w);m->m[3][0]=ox; + m->m[0][1]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[2][1]= 2*(y*z-x*w);m->m[3][1]=oy; + m->m[0][2]= 2*(x*z-y*w);m->m[1][2]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[3][2]=oz; + m->m[0][3]= 0 ;m->m[1][3]= 0 ;m->m[2][3]= 0 ;m->m[3][3]=1; +#else + m->m[0][0]=1-2*(y*y+z*z);m->m[0][1]= 2*(x*y-z*w);m->m[0][2]= 2*(x*z+y*w);m->m[0][3]=ox; + m->m[1][0]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[1][2]= 2*(y*z-x*w);m->m[1][3]=oy; + m->m[2][0]= 2*(x*z-y*w);m->m[2][1]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[2][3]=oz; + m->m[3][0]= 0 ;m->m[3][1]= 0 ;m->m[3][2]= 0 ;m->m[3][3]=1; +#endif +} + +// see http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm +void Matrix4x4_ToOrigin3Quat4Float(const matrix4x4_t *m, float *origin, float *quat) +{ +#if 0 + float s; + quat[3] = sqrt(1.0f + m->m[0][0] + m->m[1][1] + m->m[2][2]) * 0.5f; + s = 0.25f / quat[3]; +#ifdef MATRIX4x4_OPENGLORIENTATION + origin[0] = m->m[3][0]; + origin[1] = m->m[3][1]; + origin[2] = m->m[3][2]; + quat[0] = (m->m[1][2] - m->m[2][1]) * s; + quat[1] = (m->m[2][0] - m->m[0][2]) * s; + quat[2] = (m->m[0][1] - m->m[1][0]) * s; +#else + origin[0] = m->m[0][3]; + origin[1] = m->m[1][3]; + origin[2] = m->m[2][3]; + quat[0] = (m->m[2][1] - m->m[1][2]) * s; + quat[1] = (m->m[0][2] - m->m[2][0]) * s; + quat[2] = (m->m[1][0] - m->m[0][1]) * s; +#endif + +#else + +#ifdef MATRIX4x4_OPENGLORIENTATION + float trace = m->m[0][0] + m->m[1][1] + m->m[2][2]; + origin[0] = m->m[3][0]; + origin[1] = m->m[3][1]; + origin[2] = m->m[3][2]; + if(trace > 0) + { + float r = sqrt(1.0f + trace), inv = 0.5f / r; + quat[0] = (m->m[1][2] - m->m[2][1]) * inv; + quat[1] = (m->m[2][0] - m->m[0][2]) * inv; + quat[2] = (m->m[0][1] - m->m[1][0]) * inv; + quat[3] = 0.5f * r; + } + else if(m->m[0][0] > m->m[1][1] && m->m[0][0] > m->m[2][2]) + { + float r = sqrt(1.0f + m->m[0][0] - m->m[1][1] - m->m[2][2]), inv = 0.5f / r; + quat[0] = 0.5f * r; + quat[1] = (m->m[0][1] + m->m[1][0]) * inv; + quat[2] = (m->m[2][0] + m->m[0][2]) * inv; + quat[3] = (m->m[1][2] - m->m[2][1]) * inv; + } + else if(m->m[1][1] > m->m[2][2]) + { + float r = sqrt(1.0f + m->m[1][1] - m->m[0][0] - m->m[2][2]), inv = 0.5f / r; + quat[0] = (m->m[0][1] + m->m[1][0]) * inv; + quat[1] = 0.5f * r; + quat[2] = (m->m[1][2] + m->m[2][1]) * inv; + quat[3] = (m->m[2][0] - m->m[0][2]) * inv; + } + else + { + float r = sqrt(1.0f + m->m[2][2] - m->m[0][0] - m->m[1][1]), inv = 0.5f / r; + quat[0] = (m->m[2][0] + m->m[0][2]) * inv; + quat[1] = (m->m[1][2] + m->m[2][1]) * inv; + quat[2] = 0.5f * r; + quat[3] = (m->m[0][1] - m->m[1][0]) * inv; + } +#else + float trace = m->m[0][0] + m->m[1][1] + m->m[2][2]; + origin[0] = m->m[0][3]; + origin[1] = m->m[1][3]; + origin[2] = m->m[2][3]; + if(trace > 0) + { + float r = sqrt(1.0f + trace), inv = 0.5f / r; + quat[0] = (m->m[2][1] - m->m[1][2]) * inv; + quat[1] = (m->m[0][2] - m->m[2][0]) * inv; + quat[2] = (m->m[1][0] - m->m[0][1]) * inv; + quat[3] = 0.5f * r; + } + else if(m->m[0][0] > m->m[1][1] && m->m[0][0] > m->m[2][2]) + { + float r = sqrt(1.0f + m->m[0][0] - m->m[1][1] - m->m[2][2]), inv = 0.5f / r; + quat[0] = 0.5f * r; + quat[1] = (m->m[1][0] + m->m[0][1]) * inv; + quat[2] = (m->m[0][2] + m->m[2][0]) * inv; + quat[3] = (m->m[2][1] - m->m[1][2]) * inv; + } + else if(m->m[1][1] > m->m[2][2]) + { + float r = sqrt(1.0f + m->m[1][1] - m->m[0][0] - m->m[2][2]), inv = 0.5f / r; + quat[0] = (m->m[1][0] + m->m[0][1]) * inv; + quat[1] = 0.5f * r; + quat[2] = (m->m[2][1] + m->m[1][2]) * inv; + quat[3] = (m->m[0][2] - m->m[2][0]) * inv; + } + else + { + float r = sqrt(1.0f + m->m[2][2] - m->m[0][0] - m->m[1][1]), inv = 0.5f / r; + quat[0] = (m->m[0][2] + m->m[2][0]) * inv; + quat[1] = (m->m[2][1] + m->m[1][2]) * inv; + quat[2] = 0.5f * r; + quat[3] = (m->m[1][0] - m->m[0][1]) * inv; + } +#endif + +#endif +} + +// LordHavoc: I got this code from: +//http://www.doom3world.org/phpbb2/viewtopic.php?t=2884 +void Matrix4x4_FromDoom3Joint(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z) +{ + double w = 1.0f - (x*x+y*y+z*z); + w = w > 0.0f ? -sqrt(w) : 0.0f; +#ifdef MATRIX4x4_OPENGLORIENTATION + m->m[0][0]=1-2*(y*y+z*z);m->m[1][0]= 2*(x*y-z*w);m->m[2][0]= 2*(x*z+y*w);m->m[3][0]=ox; + m->m[0][1]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[2][1]= 2*(y*z-x*w);m->m[3][1]=oy; + m->m[0][2]= 2*(x*z-y*w);m->m[1][2]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[3][2]=oz; + m->m[0][3]= 0 ;m->m[1][3]= 0 ;m->m[2][3]= 0 ;m->m[3][3]=1; +#else + m->m[0][0]=1-2*(y*y+z*z);m->m[0][1]= 2*(x*y-z*w);m->m[0][2]= 2*(x*z+y*w);m->m[0][3]=ox; + m->m[1][0]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[1][2]= 2*(y*z-x*w);m->m[1][3]=oy; + m->m[2][0]= 2*(x*z-y*w);m->m[2][1]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[2][3]=oz; + m->m[3][0]= 0 ;m->m[3][1]= 0 ;m->m[3][2]= 0 ;m->m[3][3]=1; +#endif +} + +void Matrix4x4_FromBonePose6s(matrix4x4_t *m, float originscale, const short *pose6s) +{ + float origin[3]; + float quat[4]; + origin[0] = pose6s[0] * originscale; + origin[1] = pose6s[1] * originscale; + origin[2] = pose6s[2] * originscale; + quat[0] = pose6s[3] * (1.0f / 32767.0f); + quat[1] = pose6s[4] * (1.0f / 32767.0f); + quat[2] = pose6s[5] * (1.0f / 32767.0f); + quat[3] = 1.0f - (quat[0]*quat[0]+quat[1]*quat[1]+quat[2]*quat[2]); + quat[3] = quat[3] > 0.0f ? -sqrt(quat[3]) : 0.0f; + Matrix4x4_FromOriginQuat(m, origin[0], origin[1], origin[2], quat[0], quat[1], quat[2], quat[3]); +} + +void Matrix4x4_ToBonePose6s(const matrix4x4_t *m, float origininvscale, short *pose6s) +{ + float origin[3]; + float quat[4]; + float quatscale; + Matrix4x4_ToOrigin3Quat4Float(m, origin, quat); + // normalize quaternion so that it is unit length + quatscale = quat[0]*quat[0]+quat[1]*quat[1]+quat[2]*quat[2]+quat[3]*quat[3]; + if (quatscale) + quatscale = (quat[3] >= 0 ? -32767.0f : 32767.0f) / sqrt(quatscale); + // use a negative scale on the quat because the above function produces a + // positive quat[3] and canonical quaternions have negative quat[3] + pose6s[0] = origin[0] * origininvscale; + pose6s[1] = origin[1] * origininvscale; + pose6s[2] = origin[2] * origininvscale; + pose6s[3] = quat[0] * quatscale; + pose6s[4] = quat[1] * quatscale; + pose6s[5] = quat[2] * quatscale; +} + +void Matrix4x4_Blend (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2, double blend) +{ + double iblend = 1 - blend; + out->m[0][0] = in1->m[0][0] * iblend + in2->m[0][0] * blend; + out->m[0][1] = in1->m[0][1] * iblend + in2->m[0][1] * blend; + out->m[0][2] = in1->m[0][2] * iblend + in2->m[0][2] * blend; + out->m[0][3] = in1->m[0][3] * iblend + in2->m[0][3] * blend; + out->m[1][0] = in1->m[1][0] * iblend + in2->m[1][0] * blend; + out->m[1][1] = in1->m[1][1] * iblend + in2->m[1][1] * blend; + out->m[1][2] = in1->m[1][2] * iblend + in2->m[1][2] * blend; + out->m[1][3] = in1->m[1][3] * iblend + in2->m[1][3] * blend; + out->m[2][0] = in1->m[2][0] * iblend + in2->m[2][0] * blend; + out->m[2][1] = in1->m[2][1] * iblend + in2->m[2][1] * blend; + out->m[2][2] = in1->m[2][2] * iblend + in2->m[2][2] * blend; + out->m[2][3] = in1->m[2][3] * iblend + in2->m[2][3] * blend; + out->m[3][0] = in1->m[3][0] * iblend + in2->m[3][0] * blend; + out->m[3][1] = in1->m[3][1] * iblend + in2->m[3][1] * blend; + out->m[3][2] = in1->m[3][2] * iblend + in2->m[3][2] * blend; + out->m[3][3] = in1->m[3][3] * iblend + in2->m[3][3] * blend; +} + + +void Matrix4x4_Transform (const matrix4x4_t *in, const float v[3], float out[3]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0] + in->m[3][0]; + out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1] + in->m[3][1]; + out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2] + in->m[3][2]; +#else + out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2] + in->m[0][3]; + out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2] + in->m[1][3]; + out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2] + in->m[2][3]; +#endif +} + +void Matrix4x4_Transform4 (const matrix4x4_t *in, const float v[4], float out[4]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0] + v[3] * in->m[3][0]; + out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1] + v[3] * in->m[3][1]; + out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2] + v[3] * in->m[3][2]; + out[3] = v[0] * in->m[0][3] + v[1] * in->m[1][3] + v[2] * in->m[2][3] + v[3] * in->m[3][3]; +#else + out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2] + v[3] * in->m[0][3]; + out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2] + v[3] * in->m[1][3]; + out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2] + v[3] * in->m[2][3]; + out[3] = v[0] * in->m[3][0] + v[1] * in->m[3][1] + v[2] * in->m[3][2] + v[3] * in->m[3][3]; +#endif +} + +void Matrix4x4_Transform3x3 (const matrix4x4_t *in, const float v[3], float out[3]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0]; + out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1]; + out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2]; +#else + out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2]; + out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2]; + out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2]; +#endif +} + +// transforms a positive distance plane (A*x+B*y+C*z-D=0) through a rotation or translation matrix +void Matrix4x4_TransformPositivePlane(const matrix4x4_t *in, float x, float y, float z, float d, float *o) +{ + float scale = sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); + float iscale = 1.0f / scale; +#ifdef MATRIX4x4_OPENGLORIENTATION + o[0] = (x * in->m[0][0] + y * in->m[1][0] + z * in->m[2][0]) * iscale; + o[1] = (x * in->m[0][1] + y * in->m[1][1] + z * in->m[2][1]) * iscale; + o[2] = (x * in->m[0][2] + y * in->m[1][2] + z * in->m[2][2]) * iscale; + o[3] = d * scale + (o[0] * in->m[3][0] + o[1] * in->m[3][1] + o[2] * in->m[3][2]); +#else + o[0] = (x * in->m[0][0] + y * in->m[0][1] + z * in->m[0][2]) * iscale; + o[1] = (x * in->m[1][0] + y * in->m[1][1] + z * in->m[1][2]) * iscale; + o[2] = (x * in->m[2][0] + y * in->m[2][1] + z * in->m[2][2]) * iscale; + o[3] = d * scale + (o[0] * in->m[0][3] + o[1] * in->m[1][3] + o[2] * in->m[2][3]); +#endif +} + +// transforms a standard plane (A*x+B*y+C*z+D=0) through a rotation or translation matrix +void Matrix4x4_TransformStandardPlane(const matrix4x4_t *in, float x, float y, float z, float d, float *o) +{ + float scale = sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); + float iscale = 1.0f / scale; +#ifdef MATRIX4x4_OPENGLORIENTATION + o[0] = (x * in->m[0][0] + y * in->m[1][0] + z * in->m[2][0]) * iscale; + o[1] = (x * in->m[0][1] + y * in->m[1][1] + z * in->m[2][1]) * iscale; + o[2] = (x * in->m[0][2] + y * in->m[1][2] + z * in->m[2][2]) * iscale; + o[3] = d * scale - (o[0] * in->m[3][0] + o[1] * in->m[3][1] + o[2] * in->m[3][2]); +#else + o[0] = (x * in->m[0][0] + y * in->m[0][1] + z * in->m[0][2]) * iscale; + o[1] = (x * in->m[1][0] + y * in->m[1][1] + z * in->m[1][2]) * iscale; + o[2] = (x * in->m[2][0] + y * in->m[2][1] + z * in->m[2][2]) * iscale; + o[3] = d * scale - (o[0] * in->m[0][3] + o[1] * in->m[1][3] + o[2] * in->m[2][3]); +#endif +} + +/* +void Matrix4x4_SimpleUntransform (const matrix4x4_t *in, const float v[3], float out[3]) +{ + double t[3]; +#ifdef MATRIX4x4_OPENGLORIENTATION + t[0] = v[0] - in->m[3][0]; + t[1] = v[1] - in->m[3][1]; + t[2] = v[2] - in->m[3][2]; + out[0] = t[0] * in->m[0][0] + t[1] * in->m[0][1] + t[2] * in->m[0][2]; + out[1] = t[0] * in->m[1][0] + t[1] * in->m[1][1] + t[2] * in->m[1][2]; + out[2] = t[0] * in->m[2][0] + t[1] * in->m[2][1] + t[2] * in->m[2][2]; +#else + t[0] = v[0] - in->m[0][3]; + t[1] = v[1] - in->m[1][3]; + t[2] = v[2] - in->m[2][3]; + out[0] = t[0] * in->m[0][0] + t[1] * in->m[1][0] + t[2] * in->m[2][0]; + out[1] = t[0] * in->m[0][1] + t[1] * in->m[1][1] + t[2] * in->m[2][1]; + out[2] = t[0] * in->m[0][2] + t[1] * in->m[1][2] + t[2] * in->m[2][2]; +#endif +} +*/ + +// FIXME: optimize +void Matrix4x4_ConcatTranslate (matrix4x4_t *out, double x, double y, double z) +{ + matrix4x4_t base, temp; + base = *out; + Matrix4x4_CreateTranslate(&temp, x, y, z); + Matrix4x4_Concat(out, &base, &temp); +} + +// FIXME: optimize +void Matrix4x4_ConcatRotate (matrix4x4_t *out, double angle, double x, double y, double z) +{ + matrix4x4_t base, temp; + base = *out; + Matrix4x4_CreateRotate(&temp, angle, x, y, z); + Matrix4x4_Concat(out, &base, &temp); +} + +// FIXME: optimize +void Matrix4x4_ConcatScale (matrix4x4_t *out, double x) +{ + matrix4x4_t base, temp; + base = *out; + Matrix4x4_CreateScale(&temp, x); + Matrix4x4_Concat(out, &base, &temp); +} + +// FIXME: optimize +void Matrix4x4_ConcatScale3 (matrix4x4_t *out, double x, double y, double z) +{ + matrix4x4_t base, temp; + base = *out; + Matrix4x4_CreateScale3(&temp, x, y, z); + Matrix4x4_Concat(out, &base, &temp); +} + +void Matrix4x4_OriginFromMatrix (const matrix4x4_t *in, float *out) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[0] = in->m[3][0]; + out[1] = in->m[3][1]; + out[2] = in->m[3][2]; +#else + out[0] = in->m[0][3]; + out[1] = in->m[1][3]; + out[2] = in->m[2][3]; +#endif +} + +double Matrix4x4_ScaleFromMatrix (const matrix4x4_t *in) +{ + // we only support uniform scaling, so assume the first row is enough + return sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); +} + +void Matrix4x4_SetOrigin (matrix4x4_t *out, double x, double y, double z) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[3][0] = x; + out->m[3][1] = y; + out->m[3][2] = z; +#else + out->m[0][3] = x; + out->m[1][3] = y; + out->m[2][3] = z; +#endif +} + +void Matrix4x4_AdjustOrigin (matrix4x4_t *out, double x, double y, double z) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[3][0] += x; + out->m[3][1] += y; + out->m[3][2] += z; +#else + out->m[0][3] += x; + out->m[1][3] += y; + out->m[2][3] += z; +#endif +} + +void Matrix4x4_Scale (matrix4x4_t *out, double rotatescale, double originscale) +{ + out->m[0][0] *= rotatescale; + out->m[0][1] *= rotatescale; + out->m[0][2] *= rotatescale; + out->m[1][0] *= rotatescale; + out->m[1][1] *= rotatescale; + out->m[1][2] *= rotatescale; + out->m[2][0] *= rotatescale; + out->m[2][1] *= rotatescale; + out->m[2][2] *= rotatescale; +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[3][0] *= originscale; + out->m[3][1] *= originscale; + out->m[3][2] *= originscale; +#else + out->m[0][3] *= originscale; + out->m[1][3] *= originscale; + out->m[2][3] *= originscale; +#endif +} + +void Matrix4x4_Abs (matrix4x4_t *out) +{ + out->m[0][0] = fabs(out->m[0][0]); + out->m[0][1] = fabs(out->m[0][1]); + out->m[0][2] = fabs(out->m[0][2]); + out->m[1][0] = fabs(out->m[1][0]); + out->m[1][1] = fabs(out->m[1][1]); + out->m[1][2] = fabs(out->m[1][2]); + out->m[2][0] = fabs(out->m[2][0]); + out->m[2][1] = fabs(out->m[2][1]); + out->m[2][2] = fabs(out->m[2][2]); +} + diff --git a/misc/source/darkplaces-src/matrixlib.h b/misc/source/darkplaces-src/matrixlib.h new file mode 100644 index 00000000..ae83c1d6 --- /dev/null +++ b/misc/source/darkplaces-src/matrixlib.h @@ -0,0 +1,172 @@ + +#ifndef MATRIXLIB_H +#define MATRIXLIB_H + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +//#define MATRIX4x4_OPENGLORIENTATION + +typedef struct matrix4x4_s +{ + float m[4][4]; +} +matrix4x4_t; + +extern const matrix4x4_t identitymatrix; + +// functions for manipulating 4x4 matrices + +// copy a matrix4x4 +void Matrix4x4_Copy (matrix4x4_t *out, const matrix4x4_t *in); +// copy only the rotation portion of a matrix4x4 +void Matrix4x4_CopyRotateOnly (matrix4x4_t *out, const matrix4x4_t *in); +// copy only the translate portion of a matrix4x4 +void Matrix4x4_CopyTranslateOnly (matrix4x4_t *out, const matrix4x4_t *in); +// multiply two matrix4x4 together, combining their transformations +// (warning: order matters - Concat(a, b, c) != Concat(a, c, b)) +void Matrix4x4_Concat (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2); +// swaps the rows and columns of the matrix +// (is this useful for anything?) +void Matrix4x4_Transpose (matrix4x4_t *out, const matrix4x4_t *in1); +// creates a matrix that does the opposite of the matrix provided +// this is a full matrix inverter, it should be able to invert any matrix that +// is possible to invert +// (non-uniform scaling, rotation, shearing, and translation, possibly others) +// warning: this function is SLOW +int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1); +// creates a matrix that does the opposite of the matrix provided +// only supports translate, rotate, scale (not scale3) matrices +void Matrix4x4_Invert_Simple (matrix4x4_t *out, const matrix4x4_t *in1); +// blends between two matrices, used primarily for animation interpolation +// (note: it is recommended to follow this with Matrix4x4_Normalize, a method +// known as nlerp rotation, often better for animation purposes than slerp) +void Matrix4x4_Interpolate (matrix4x4_t *out, matrix4x4_t *in1, matrix4x4_t *in2, double frac); +// zeros all matrix components, used with Matrix4x4_Accumulate +void Matrix4x4_Clear (matrix4x4_t *out); +// adds a weighted contribution from the supplied matrix, used to blend 3 or +// more matrices with weighting, it is recommended that Matrix4x4_Normalize be +// called afterward (a method known as nlerp rotation, often better for +// animation purposes than slerp) +void Matrix4x4_Accumulate (matrix4x4_t *out, matrix4x4_t *in, double weight); +// creates a matrix that does the same rotation and translation as the matrix +// provided, but no uniform scaling, does not support scale3 matrices +void Matrix4x4_Normalize (matrix4x4_t *out, matrix4x4_t *in1); +// creates a matrix with vectors normalized individually (use after +// Matrix4x4_Accumulate) +void Matrix4x4_Normalize3 (matrix4x4_t *out, matrix4x4_t *in1); +// modifies a matrix to have all vectors and origin reflected across the plane +// to the opposite side (at least if axisscale is -2) +void Matrix4x4_Reflect (matrix4x4_t *out, double normalx, double normaly, double normalz, double dist, double axisscale); + +// creates an identity matrix +// (a matrix which does nothing) +void Matrix4x4_CreateIdentity (matrix4x4_t *out); +// creates a translate matrix +// (moves vectors) +void Matrix4x4_CreateTranslate (matrix4x4_t *out, double x, double y, double z); +// creates a rotate matrix +// (rotates vectors) +void Matrix4x4_CreateRotate (matrix4x4_t *out, double angle, double x, double y, double z); +// creates a scaling matrix +// (expands or contracts vectors) +// (warning: do not apply this kind of matrix to direction vectors) +void Matrix4x4_CreateScale (matrix4x4_t *out, double x); +// creates a squishing matrix +// (expands or contracts vectors differently in different axis) +// (warning: this is not reversed by Invert_Simple) +// (warning: do not apply this kind of matrix to direction vectors) +void Matrix4x4_CreateScale3 (matrix4x4_t *out, double x, double y, double z); +// creates a matrix for a quake entity +void Matrix4x4_CreateFromQuakeEntity(matrix4x4_t *out, double x, double y, double z, double pitch, double yaw, double roll, double scale); + +// converts a matrix4x4 to a set of 3D vectors for the 3 axial directions, and the translate +void Matrix4x4_ToVectors(const matrix4x4_t *in, float vx[3], float vy[3], float vz[3], float t[3]); +// creates a matrix4x4 from a set of 3D vectors for axial directions, and translate +void Matrix4x4_FromVectors(matrix4x4_t *out, const float vx[3], const float vy[3], const float vz[3], const float t[3]); + +// converts a matrix4x4 to a double[16] array in the OpenGL orientation +void Matrix4x4_ToArrayDoubleGL(const matrix4x4_t *in, double out[16]); +// creates a matrix4x4 from a double[16] array in the OpenGL orientation +void Matrix4x4_FromArrayDoubleGL(matrix4x4_t *out, const double in[16]); +// converts a matrix4x4 to a double[16] array in the Direct3D orientation +void Matrix4x4_ToArrayDoubleD3D(const matrix4x4_t *in, double out[16]); +// creates a matrix4x4 from a double[16] array in the Direct3D orientation +void Matrix4x4_FromArrayDoubleD3D(matrix4x4_t *out, const double in[16]); + +// converts a matrix4x4 to a float[16] array in the OpenGL orientation +void Matrix4x4_ToArrayFloatGL(const matrix4x4_t *in, float out[16]); +// creates a matrix4x4 from a float[16] array in the OpenGL orientation +void Matrix4x4_FromArrayFloatGL(matrix4x4_t *out, const float in[16]); +// converts a matrix4x4 to a float[16] array in the Direct3D orientation +void Matrix4x4_ToArrayFloatD3D(const matrix4x4_t *in, float out[16]); +// creates a matrix4x4 from a float[16] array in the Direct3D orientation +void Matrix4x4_FromArrayFloatD3D(matrix4x4_t *out, const float in[16]); + +// converts a matrix4x4 to a float[12] array in the OpenGL orientation +void Matrix4x4_ToArray12FloatGL(const matrix4x4_t *in, float out[12]); +// creates a matrix4x4 from a float[12] array in the OpenGL orientation +void Matrix4x4_FromArray12FloatGL(matrix4x4_t *out, const float in[12]); +// converts a matrix4x4 to a float[12] array in the Direct3D orientation +void Matrix4x4_ToArray12FloatD3D(const matrix4x4_t *in, float out[12]); +// creates a matrix4x4 from a float[12] array in the Direct3D orientation +void Matrix4x4_FromArray12FloatD3D(matrix4x4_t *out, const float in[12]); + +// creates a matrix4x4 from an origin and quaternion (used mostly with skeletal model formats such as PSK) +void Matrix4x4_FromOriginQuat(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z, double w); +// creates an origin and quaternion from a matrix4x4_t, quat[3] is always positive +void Matrix4x4_ToOrigin3Quat4Float(const matrix4x4_t *m, float *origin, float *quat); +// creates a matrix4x4 from an origin and canonical unit-length quaternion (used mostly with skeletal model formats such as MD5) +void Matrix4x4_FromDoom3Joint(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z); + +// creates a matrix4x4_t from an origin and canonical unit-length quaternion in short[6] normalized format +void Matrix4x4_FromBonePose6s(matrix4x4_t *m, float originscale, const short *pose6s); +// creates a short[6] representation from normalized matrix4x4_t +void Matrix4x4_ToBonePose6s(const matrix4x4_t *m, float origininvscale, short *pose6s); + +// blends two matrices together, at a given percentage (blend controls percentage of in2) +void Matrix4x4_Blend (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2, double blend); + +// transforms a 3D vector through a matrix4x4 +void Matrix4x4_Transform (const matrix4x4_t *in, const float v[3], float out[3]); +// transforms a 4D vector through a matrix4x4 +// (warning: if you don't know why you would need this, you don't need it) +// (warning: the 4th component of the vector should be 1.0) +void Matrix4x4_Transform4 (const matrix4x4_t *in, const float v[4], float out[4]); +// reverse transforms a 3D vector through a matrix4x4, at least for *simple* +// cases (rotation and translation *ONLY*), this attempts to undo the results +// of Transform +//void Matrix4x4_SimpleUntransform (const matrix4x4_t *in, const float v[3], float out[3]); +// transforms a direction vector through the rotation part of a matrix +void Matrix4x4_Transform3x3 (const matrix4x4_t *in, const float v[3], float out[3]); +// transforms a positive distance plane (A*x+B*y+C*z-D=0) through a rotation or translation matrix +void Matrix4x4_TransformPositivePlane (const matrix4x4_t *in, float x, float y, float z, float d, float *o); +// transforms a standard plane (A*x+B*y+C*z+D=0) through a rotation or translation matrix +void Matrix4x4_TransformStandardPlane (const matrix4x4_t *in, float x, float y, float z, float d, float *o); + +// ease of use functions +// immediately applies a Translate to the matrix +void Matrix4x4_ConcatTranslate (matrix4x4_t *out, double x, double y, double z); +// immediately applies a Rotate to the matrix +void Matrix4x4_ConcatRotate (matrix4x4_t *out, double angle, double x, double y, double z); +// immediately applies a Scale to the matrix +void Matrix4x4_ConcatScale (matrix4x4_t *out, double x); +// immediately applies a Scale3 to the matrix +void Matrix4x4_ConcatScale3 (matrix4x4_t *out, double x, double y, double z); + +// extracts origin vector (translate) from matrix +void Matrix4x4_OriginFromMatrix (const matrix4x4_t *in, float *out); +// extracts scaling factor from matrix (only works for uniform scaling) +double Matrix4x4_ScaleFromMatrix (const matrix4x4_t *in); + +// replaces origin vector (translate) in matrix +void Matrix4x4_SetOrigin (matrix4x4_t *out, double x, double y, double z); +// moves origin vector (translate) in matrix by a simple translate +void Matrix4x4_AdjustOrigin (matrix4x4_t *out, double x, double y, double z); +// scales vectors of a matrix in place and allows you to scale origin as well +void Matrix4x4_Scale (matrix4x4_t *out, double rotatescale, double originscale); +// ensures each element of the 3x3 rotation matrix is facing in the + direction +void Matrix4x4_Abs (matrix4x4_t *out); + +#endif diff --git a/misc/source/darkplaces-src/mdfour.c b/misc/source/darkplaces-src/mdfour.c new file mode 100644 index 00000000..e057a94b --- /dev/null +++ b/misc/source/darkplaces-src/mdfour.c @@ -0,0 +1,229 @@ +/* + mdfour.c + + An implementation of MD4 designed for use in the samba SMB + authentication protocol + + Copyright (C) 1997-1998 Andrew Tridgell + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + + $Id$ +*/ + +#include "quakedef.h" + +#include /* XoXus: needed for memset call */ +#include "mdfour.h" + +/* NOTE: This code makes no attempt to be fast! + + It assumes that a int is at least 32 bits long +*/ + +static struct mdfour *m; + +#define F(X,Y,Z) (((X)&(Y)) | ((~(X))&(Z))) +#define G(X,Y,Z) (((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z))) +#define H(X,Y,Z) ((X)^(Y)^(Z)) +#ifdef LARGE_INT32 +#define lshift(x,s) ((((x)<<(s))&0xFFFFFFFF) | (((x)>>(32-(s)))&0xFFFFFFFF)) +#else +#define lshift(x,s) (((x)<<(s)) | ((x)>>(32-(s)))) +#endif + +#define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s) +#define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + 0x5A827999,s) +#define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + 0x6ED9EBA1,s) + +/* this applies md4 to 64 byte chunks */ +static void mdfour64(uint32 *M) +{ + int j; + uint32 AA, BB, CC, DD; + uint32 X[16]; + uint32 A,B,C,D; + + for (j=0;j<16;j++) + X[j] = M[j]; + + A = m->A; B = m->B; C = m->C; D = m->D; + AA = A; BB = B; CC = C; DD = D; + + ROUND1(A,B,C,D, 0, 3); ROUND1(D,A,B,C, 1, 7); + ROUND1(C,D,A,B, 2, 11); ROUND1(B,C,D,A, 3, 19); + ROUND1(A,B,C,D, 4, 3); ROUND1(D,A,B,C, 5, 7); + ROUND1(C,D,A,B, 6, 11); ROUND1(B,C,D,A, 7, 19); + ROUND1(A,B,C,D, 8, 3); ROUND1(D,A,B,C, 9, 7); + ROUND1(C,D,A,B, 10, 11); ROUND1(B,C,D,A, 11, 19); + ROUND1(A,B,C,D, 12, 3); ROUND1(D,A,B,C, 13, 7); + ROUND1(C,D,A,B, 14, 11); ROUND1(B,C,D,A, 15, 19); + + ROUND2(A,B,C,D, 0, 3); ROUND2(D,A,B,C, 4, 5); + ROUND2(C,D,A,B, 8, 9); ROUND2(B,C,D,A, 12, 13); + ROUND2(A,B,C,D, 1, 3); ROUND2(D,A,B,C, 5, 5); + ROUND2(C,D,A,B, 9, 9); ROUND2(B,C,D,A, 13, 13); + ROUND2(A,B,C,D, 2, 3); ROUND2(D,A,B,C, 6, 5); + ROUND2(C,D,A,B, 10, 9); ROUND2(B,C,D,A, 14, 13); + ROUND2(A,B,C,D, 3, 3); ROUND2(D,A,B,C, 7, 5); + ROUND2(C,D,A,B, 11, 9); ROUND2(B,C,D,A, 15, 13); + + ROUND3(A,B,C,D, 0, 3); ROUND3(D,A,B,C, 8, 9); + ROUND3(C,D,A,B, 4, 11); ROUND3(B,C,D,A, 12, 15); + ROUND3(A,B,C,D, 2, 3); ROUND3(D,A,B,C, 10, 9); + ROUND3(C,D,A,B, 6, 11); ROUND3(B,C,D,A, 14, 15); + ROUND3(A,B,C,D, 1, 3); ROUND3(D,A,B,C, 9, 9); + ROUND3(C,D,A,B, 5, 11); ROUND3(B,C,D,A, 13, 15); + ROUND3(A,B,C,D, 3, 3); ROUND3(D,A,B,C, 11, 9); + ROUND3(C,D,A,B, 7, 11); ROUND3(B,C,D,A, 15, 15); + + A += AA; B += BB; C += CC; D += DD; + +#ifdef LARGE_INT32 + A &= 0xFFFFFFFF; B &= 0xFFFFFFFF; + C &= 0xFFFFFFFF; D &= 0xFFFFFFFF; +#endif + + for (j=0;j<16;j++) + X[j] = 0; + + m->A = A; m->B = B; m->C = C; m->D = D; +} + +static void copy64(uint32 *M, const unsigned char *in) +{ + int i; + + for (i=0;i<16;i++) + M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) | + (in[i*4+1]<<8) | (in[i*4+0]<<0); +} + +static void copy4(unsigned char *out,uint32 x) +{ + out[0] = x&0xFF; + out[1] = (x>>8)&0xFF; + out[2] = (x>>16)&0xFF; + out[3] = (x>>24)&0xFF; +} + +void mdfour_begin(struct mdfour *md) +{ + md->A = 0x67452301; + md->B = 0xefcdab89; + md->C = 0x98badcfe; + md->D = 0x10325476; + md->totalN = 0; +} + + +static void mdfour_tail(const unsigned char *in, int n) +{ + unsigned char buf[128]; + uint32 M[16]; + uint32 b; + + m->totalN += n; + + b = m->totalN * 8; + + memset(buf, 0, 128); + if (n) memcpy(buf, in, n); + buf[n] = 0x80; + + if (n <= 55) { + copy4(buf+56, b); + copy64(M, buf); + mdfour64(M); + } else { + copy4(buf+120, b); + copy64(M, buf); + mdfour64(M); + copy64(M, buf+64); + mdfour64(M); + } +} + +void mdfour_update(struct mdfour *md, const unsigned char *in, int n) +{ + uint32 M[16]; + +// start of edit by Forest 'LordHavoc' Hale +// commented out to prevent crashing when length is 0 +// if (n == 0) mdfour_tail(in, n); +// end of edit by Forest 'LordHavoc' Hale + + m = md; + + while (n >= 64) { + copy64(M, in); + mdfour64(M); + in += 64; + n -= 64; + m->totalN += 64; + } + + mdfour_tail(in, n); +} + + +void mdfour_result(struct mdfour *md, unsigned char *out) +{ + m = md; + + copy4(out, m->A); + copy4(out+4, m->B); + copy4(out+8, m->C); + copy4(out+12, m->D); +} + + +void mdfour(unsigned char *out, const unsigned char *in, int n) +{ + struct mdfour md; + mdfour_begin(&md); + mdfour_update(&md, in, n); + mdfour_result(&md, out); +} + +/////////////////////////////////////////////////////////////// +// MD4-based checksum utility functions +// +// Copyright (C) 2000 Jeff Teunissen +// +// Author: Jeff Teunissen +// Date: 01 Jan 2000 + +unsigned Com_BlockChecksum (void *buffer, int length) +{ + int digest[4]; + unsigned val; + + mdfour ( (unsigned char *) digest, (unsigned char *) buffer, length ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} + +void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf) +{ + mdfour ( outbuf, (unsigned char *) buffer, len ); +} + diff --git a/misc/source/darkplaces-src/mdfour.h b/misc/source/darkplaces-src/mdfour.h new file mode 100644 index 00000000..3ef654c8 --- /dev/null +++ b/misc/source/darkplaces-src/mdfour.h @@ -0,0 +1,54 @@ +/* + mdfour.h + + an implementation of MD4 designed for use in the SMB authentication + protocol + + Copyright (C) Andrew Tridgell 1997-1998 + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + +#ifndef _MDFOUR_H +#define _MDFOUR_H + +#ifndef int32 +#define int32 int +#endif + +#if SIZEOF_INT > 4 +#define LARGE_INT32 +#endif + +#ifndef uint32 +#define uint32 unsigned int32 +#endif + +struct mdfour { + uint32 A, B, C, D; + uint32 totalN; +}; + +void mdfour_begin(struct mdfour *md); // old: MD4Init +void mdfour_update(struct mdfour *md, const unsigned char *in, int n); //old: MD4Update +void mdfour_result(struct mdfour *md, unsigned char *out); // old: MD4Final +void mdfour(unsigned char *out, const unsigned char *in, int n); + +#endif // _MDFOUR_H + diff --git a/misc/source/darkplaces-src/menu.c b/misc/source/darkplaces-src/menu.c new file mode 100644 index 00000000..c09abda4 --- /dev/null +++ b/misc/source/darkplaces-src/menu.c @@ -0,0 +1,5526 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "quakedef.h" +#include "cdaudio.h" +#include "image.h" +#include "progsvm.h" + +#include "mprogdefs.h" + +#define TYPE_DEMO 1 +#define TYPE_GAME 2 +#define TYPE_BOTH 3 + +static cvar_t forceqmenu = { 0, "forceqmenu", "0", "enables the quake menu instead of the quakec menu.dat (if present)" }; + +static int NehGameType; + +enum m_state_e m_state; +char m_return_reason[128]; + +void M_Menu_Main_f (void); + void M_Menu_SinglePlayer_f (void); + void M_Menu_Transfusion_Episode_f (void); + void M_Menu_Transfusion_Skill_f (void); + void M_Menu_Load_f (void); + void M_Menu_Save_f (void); + void M_Menu_MultiPlayer_f (void); + void M_Menu_Setup_f (void); + void M_Menu_Options_f (void); + void M_Menu_Options_Effects_f (void); + void M_Menu_Options_Graphics_f (void); + void M_Menu_Options_ColorControl_f (void); + void M_Menu_Keys_f (void); + void M_Menu_Reset_f (void); + void M_Menu_Video_f (void); + void M_Menu_Help_f (void); + void M_Menu_Credits_f (void); + void M_Menu_Quit_f (void); +void M_Menu_LanConfig_f (void); +void M_Menu_GameOptions_f (void); +void M_Menu_ServerList_f (void); +void M_Menu_ModList_f (void); + +static void M_Main_Draw (void); + static void M_SinglePlayer_Draw (void); + static void M_Transfusion_Episode_Draw (void); + static void M_Transfusion_Skill_Draw (void); + static void M_Load_Draw (void); + static void M_Save_Draw (void); + static void M_MultiPlayer_Draw (void); + static void M_Setup_Draw (void); + static void M_Options_Draw (void); + static void M_Options_Effects_Draw (void); + static void M_Options_Graphics_Draw (void); + static void M_Options_ColorControl_Draw (void); + static void M_Keys_Draw (void); + static void M_Reset_Draw (void); + static void M_Video_Draw (void); + static void M_Help_Draw (void); + static void M_Credits_Draw (void); + static void M_Quit_Draw (void); +static void M_LanConfig_Draw (void); +static void M_GameOptions_Draw (void); +static void M_ServerList_Draw (void); +static void M_ModList_Draw (void); + + +static void M_Main_Key (int key, int ascii); + static void M_SinglePlayer_Key (int key, int ascii); + static void M_Transfusion_Episode_Key (int key, int ascii); + static void M_Transfusion_Skill_Key (int key, int ascii); + static void M_Load_Key (int key, int ascii); + static void M_Save_Key (int key, int ascii); + static void M_MultiPlayer_Key (int key, int ascii); + static void M_Setup_Key (int key, int ascii); + static void M_Options_Key (int key, int ascii); + static void M_Options_Effects_Key (int key, int ascii); + static void M_Options_Graphics_Key (int key, int ascii); + static void M_Options_ColorControl_Key (int key, int ascii); + static void M_Keys_Key (int key, int ascii); + static void M_Reset_Key (int key, int ascii); + static void M_Video_Key (int key, int ascii); + static void M_Help_Key (int key, int ascii); + static void M_Credits_Key (int key, int ascii); + static void M_Quit_Key (int key, int ascii); +static void M_LanConfig_Key (int key, int ascii); +static void M_GameOptions_Key (int key, int ascii); +static void M_ServerList_Key (int key, int ascii); +static void M_ModList_Key (int key, int ascii); + +static qboolean m_entersound; ///< play after drawing a frame, so caching won't disrupt the sound + +void M_Update_Return_Reason(const char *s) +{ + strlcpy(m_return_reason, s, sizeof(m_return_reason)); + if (s) + Con_DPrintf("%s\n", s); +} + +#define StartingGame (m_multiplayer_cursor == 1) +#define JoiningGame (m_multiplayer_cursor == 0) + +// Nehahra +#define NumberOfNehahraDemos 34 +typedef struct nehahrademonames_s +{ + const char *name; + const char *desc; +} nehahrademonames_t; + +static nehahrademonames_t NehahraDemos[NumberOfNehahraDemos] = +{ + {"intro", "Prologue"}, + {"genf", "The Beginning"}, + {"genlab", "A Doomed Project"}, + {"nehcre", "The New Recruits"}, + {"maxneh", "Breakthrough"}, + {"maxchar", "Renewal and Duty"}, + {"crisis", "Worlds Collide"}, + {"postcris", "Darkening Skies"}, + {"hearing", "The Hearing"}, + {"getjack", "On a Mexican Radio"}, + {"prelude", "Honor and Justice"}, + {"abase", "A Message Sent"}, + {"effect", "The Other Side"}, + {"uhoh", "Missing in Action"}, + {"prepare", "The Response"}, + {"vision", "Farsighted Eyes"}, + {"maxturns", "Enter the Immortal"}, + {"backlot", "Separate Ways"}, + {"maxside", "The Ancient Runes"}, + {"counter", "The New Initiative"}, + {"warprep", "Ghosts to the World"}, + {"counter1", "A Fate Worse Than Death"}, + {"counter2", "Friendly Fire"}, + {"counter3", "Minor Setback"}, + {"madmax", "Scores to Settle"}, + {"quake", "One Man"}, + {"cthmm", "Shattered Masks"}, + {"shades", "Deal with the Dead"}, + {"gophil", "An Unlikely Hero"}, + {"cstrike", "War in Hell"}, + {"shubset", "The Conspiracy"}, + {"shubdie", "Even Death May Die"}, + {"newranks", "An Empty Throne"}, + {"seal", "The Seal is Broken"} +}; + +static float menu_x, menu_y, menu_width, menu_height; + +static void M_Background(int width, int height) +{ + menu_width = bound(1.0f, (float)width, vid_conwidth.value); + menu_height = bound(1.0f, (float)height, vid_conheight.value); + menu_x = (vid_conwidth.integer - menu_width) * 0.5; + menu_y = (vid_conheight.integer - menu_height) * 0.5; + //DrawQ_Fill(menu_x, menu_y, menu_width, menu_height, 0, 0, 0, 0.5, 0); + DrawQ_Fill(0, 0, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, 0.5, 0); +} + +/* +================ +M_DrawCharacter + +Draws one solid graphics character +================ +*/ +static void M_DrawCharacter (float cx, float cy, int num) +{ + char temp[2]; + temp[0] = num; + temp[1] = 0; + DrawQ_String(menu_x + cx, menu_y + cy, temp, 1, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); +} + +static void M_PrintColored(float cx, float cy, const char *str) +{ + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_MENU); +} + +static void M_Print(float cx, float cy, const char *str) +{ + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); +} + +static void M_PrintRed(float cx, float cy, const char *str) +{ + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 0, 0, 1, 0, NULL, true, FONT_MENU); +} + +static void M_ItemPrint(float cx, float cy, const char *str, int unghosted) +{ + if (unghosted) + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); + else + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 0.4, 0.4, 0.4, 1, 0, NULL, true, FONT_MENU); +} + +static void M_DrawPic(float cx, float cy, const char *picname) +{ + DrawQ_Pic(menu_x + cx, menu_y + cy, Draw_CachePic (picname), 0, 0, 1, 1, 1, 1, 0); +} + +static void M_DrawTextBox(float x, float y, float width, float height) +{ + int n; + float cx, cy; + + // draw left side + cx = x; + cy = y; + M_DrawPic (cx, cy, "gfx/box_tl"); + for (n = 0; n < height; n++) + { + cy += 8; + M_DrawPic (cx, cy, "gfx/box_ml"); + } + M_DrawPic (cx, cy+8, "gfx/box_bl"); + + // draw middle + cx += 8; + while (width > 0) + { + cy = y; + M_DrawPic (cx, cy, "gfx/box_tm"); + for (n = 0; n < height; n++) + { + cy += 8; + if (n >= 1) + M_DrawPic (cx, cy, "gfx/box_mm2"); + else + M_DrawPic (cx, cy, "gfx/box_mm"); + } + M_DrawPic (cx, cy+8, "gfx/box_bm"); + width -= 2; + cx += 16; + } + + // draw right side + cy = y; + M_DrawPic (cx, cy, "gfx/box_tr"); + for (n = 0; n < height; n++) + { + cy += 8; + M_DrawPic (cx, cy, "gfx/box_mr"); + } + M_DrawPic (cx, cy+8, "gfx/box_br"); +} + +//============================================================================= + +//int m_save_demonum; + +/* +================ +M_ToggleMenu +================ +*/ +void M_ToggleMenu(int mode) +{ + m_entersound = true; + + if ((key_dest != key_menu && key_dest != key_menu_grabbed) || m_state != m_main) + { + if(mode == 0) + return; // the menu is off, and we want it off + M_Menu_Main_f (); + } + else + { + if(mode == 1) + return; // the menu is on, and we want it on + key_dest = key_game; + m_state = m_none; + } +} + + +static int demo_cursor; +static void M_Demo_Draw (void) +{ + int i; + + M_Background(320, 200); + + for (i = 0;i < NumberOfNehahraDemos;i++) + M_Print(16, 16 + 8*i, NehahraDemos[i].desc); + + // line cursor + M_DrawCharacter (8, 16 + demo_cursor*8, 12+((int)(realtime*4)&1)); +} + + +void M_Menu_Demos_f (void) +{ + key_dest = key_menu; + m_state = m_demo; + m_entersound = true; +} + + +static void M_Demo_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_ENTER: + S_LocalSound ("sound/misc/menu2.wav"); + m_state = m_none; + key_dest = key_game; + Cbuf_AddText (va ("playdemo %s\n", NehahraDemos[demo_cursor].name)); + return; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("sound/misc/menu1.wav"); + demo_cursor--; + if (demo_cursor < 0) + demo_cursor = NumberOfNehahraDemos-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("sound/misc/menu1.wav"); + demo_cursor++; + if (demo_cursor >= NumberOfNehahraDemos) + demo_cursor = 0; + break; + } +} + +//============================================================================= +/* MAIN MENU */ + +static int m_main_cursor; +static qboolean m_missingdata = false; + +static int MAIN_ITEMS = 4; // Nehahra: Menu Disable + + +void M_Menu_Main_f (void) +{ + const char *s; + s = "gfx/mainmenu"; + + if (gamemode == GAME_NEHAHRA) + { + if (FS_FileExists("maps/neh1m4.bsp")) + { + if (FS_FileExists("hearing.dem")) + { + Con_DPrint("Main menu: Nehahra movie and game detected.\n"); + NehGameType = TYPE_BOTH; + } + else + { + Con_DPrint("Nehahra game detected.\n"); + NehGameType = TYPE_GAME; + } + } + else + { + if (FS_FileExists("hearing.dem")) + { + Con_DPrint("Nehahra movie detected.\n"); + NehGameType = TYPE_DEMO; + } + else + { + Con_DPrint("Nehahra not found.\n"); + NehGameType = TYPE_GAME; // could just complain, but... + } + } + if (NehGameType == TYPE_DEMO) + MAIN_ITEMS = 4; + else if (NehGameType == TYPE_GAME) + MAIN_ITEMS = 5; + else + MAIN_ITEMS = 6; + } + else if (gamemode == GAME_TRANSFUSION) + { + s = "gfx/menu/mainmenu1"; + if (sv.active && !cl.intermission && cl.islocalgame) + MAIN_ITEMS = 8; + else + MAIN_ITEMS = 7; + } + else + MAIN_ITEMS = 5; + + // check if the game data is missing and use a different main menu if so + m_missingdata = !forceqmenu.integer && Draw_CachePic (s)->tex == r_texture_notexture; + if (m_missingdata) + MAIN_ITEMS = 2; + + /* + if (key_dest != key_menu) + { + m_save_demonum = cls.demonum; + cls.demonum = -1; + } + */ + key_dest = key_menu; + m_state = m_main; + m_entersound = true; +} + + +static void M_Main_Draw (void) +{ + int f; + cachepic_t *p; + + if (m_missingdata) + { + float y; + const char *s; + M_Background(640, 480); //fall back is always to 640x480, this makes it most readable at that. + y = 480/3-16; + s = "You have reached this menu due to missing or unlocatable content/data";M_PrintRed ((640-strlen(s)*8)*0.5, (480/3)-16, s);y+=8; + y+=8; + s = "You may consider adding";M_Print ((640-strlen(s)*8)*0.5, y, s);y+=8; + s = "-basedir /path/to/game";M_Print ((640-strlen(s)*8)*0.5, y, s);y+=8; + s = "to your launch commandline";M_Print ((640-strlen(s)*8)*0.5, y, s);y+=8; + M_Print (640/2 - 48, 480/2, "Open Console"); //The console usually better shows errors (failures) + M_Print (640/2 - 48, 480/2 + 8, "Quit"); + M_DrawCharacter(640/2 - 56, 480/2 + (8 * m_main_cursor), 12+((int)(realtime*4)&1)); + return; + } + + if (gamemode == GAME_TRANSFUSION) { + int y1, y2, y3; + M_Background(640, 480); + p = Draw_CachePic ("gfx/menu/tb-transfusion"); + M_DrawPic (640/2 - p->width/2, 40, "gfx/menu/tb-transfusion"); + y2 = 120; + // 8 rather than MAIN_ITEMS to skip a number and not miss the last option + for (y1 = 1; y1 <= 8; y1++) + { + if (MAIN_ITEMS == 7 && y1 == 4) + y1++; + M_DrawPic (0, y2, va("gfx/menu/mainmenu%i", y1)); + y2 += 40; + } + if (MAIN_ITEMS == 7 && m_main_cursor > 2) + y3 = m_main_cursor + 2; + else + y3 = m_main_cursor + 1; + M_DrawPic (0, 120 + m_main_cursor * 40, va("gfx/menu/mainmenu%iselected", y3)); + return; + } + + M_Background(320, 200); + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/ttl_main"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_main"); +// Nehahra + if (gamemode == GAME_NEHAHRA) + { + if (NehGameType == TYPE_BOTH) + M_DrawPic (72, 32, "gfx/mainmenu"); + else if (NehGameType == TYPE_GAME) + M_DrawPic (72, 32, "gfx/gamemenu"); + else + M_DrawPic (72, 32, "gfx/demomenu"); + } + else + M_DrawPic (72, 32, "gfx/mainmenu"); + + f = (int)(realtime * 10)%6; + + M_DrawPic (54, 32 + m_main_cursor * 20, va("gfx/menudot%i", f+1)); +} + + +static void M_Main_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + key_dest = key_game; + m_state = m_none; + //cls.demonum = m_save_demonum; + //if (cls.demonum != -1 && !cls.demoplayback && cls.state != ca_connected) + // CL_NextDemo (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (++m_main_cursor >= MAIN_ITEMS) + m_main_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (--m_main_cursor < 0) + m_main_cursor = MAIN_ITEMS - 1; + break; + + case K_ENTER: + m_entersound = true; + + if (m_missingdata) + { + switch (m_main_cursor) + { + case 0: + if (cls.state == ca_connected) + { + m_state = m_none; + key_dest = key_game; + } + Con_ToggleConsole_f (); + break; + case 1: + M_Menu_Quit_f (); + break; + } + } + else if (gamemode == GAME_NEHAHRA) + { + switch (NehGameType) + { + case TYPE_BOTH: + switch (m_main_cursor) + { + case 0: + M_Menu_SinglePlayer_f (); + break; + + case 1: + M_Menu_Demos_f (); + break; + + case 2: + M_Menu_MultiPlayer_f (); + break; + + case 3: + M_Menu_Options_f (); + break; + + case 4: + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("playdemo endcred\n"); + break; + + case 5: + M_Menu_Quit_f (); + break; + } + break; + case TYPE_GAME: + switch (m_main_cursor) + { + case 0: + M_Menu_SinglePlayer_f (); + break; + + case 1: + M_Menu_MultiPlayer_f (); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("playdemo endcred\n"); + break; + + case 4: + M_Menu_Quit_f (); + break; + } + break; + case TYPE_DEMO: + switch (m_main_cursor) + { + case 0: + M_Menu_Demos_f (); + break; + + case 1: + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("playdemo endcred\n"); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Quit_f (); + break; + } + break; + } + } + else if (gamemode == GAME_TRANSFUSION) { + if (MAIN_ITEMS == 7) + { + switch (m_main_cursor) + { + case 0: + M_Menu_Transfusion_Episode_f (); + break; + + case 1: + M_Menu_MultiPlayer_f (); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Load_f (); + break; + + case 4: + M_Menu_Help_f (); + break; + + case 5: + M_Menu_Credits_f (); + break; + + case 6: + M_Menu_Quit_f (); + break; + } + } + else + { + switch (m_main_cursor) + { + case 0: + M_Menu_Transfusion_Episode_f (); + break; + + case 1: + M_Menu_MultiPlayer_f (); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Save_f (); + break; + + case 4: + M_Menu_Load_f (); + break; + + case 5: + M_Menu_Help_f (); + break; + + case 6: + M_Menu_Credits_f (); + break; + + case 7: + M_Menu_Quit_f (); + break; + } + } + } + else + { + switch (m_main_cursor) + { + case 0: + M_Menu_SinglePlayer_f (); + break; + + case 1: + M_Menu_MultiPlayer_f (); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Help_f (); + break; + + case 4: + M_Menu_Quit_f (); + break; + } + } + } +} + +//============================================================================= +/* SINGLE PLAYER MENU */ + +static int m_singleplayer_cursor; +#define SINGLEPLAYER_ITEMS 3 + + +void M_Menu_SinglePlayer_f (void) +{ + key_dest = key_menu; + m_state = m_singleplayer; + m_entersound = true; +} + + +static void M_SinglePlayer_Draw (void) +{ + cachepic_t *p; + + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/ttl_sgl"); + + // Some mods don't have a single player mode + if (gamemode == GAME_GOODVSBAD2 || gamemode == GAME_BATTLEMECH) + { + M_DrawPic ((320 - p->width) / 2, 4, "gfx/ttl_sgl"); + + M_DrawTextBox (60, 8 * 8, 23, 4); + if (gamemode == GAME_GOODVSBAD2) + M_Print(95, 10 * 8, "Good Vs Bad 2 is for"); + else // if (gamemode == GAME_BATTLEMECH) + M_Print(95, 10 * 8, "Battlemech is for"); + M_Print(83, 11 * 8, "multiplayer play only"); + } + else + { + int f; + + M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_sgl"); + M_DrawPic (72, 32, "gfx/sp_menu"); + + f = (int)(realtime * 10)%6; + + M_DrawPic (54, 32 + m_singleplayer_cursor * 20, va("gfx/menudot%i", f+1)); + } +} + + +static void M_SinglePlayer_Key (int key, int ascii) +{ + if (gamemode == GAME_GOODVSBAD2 || gamemode == GAME_BATTLEMECH) + { + if (key == K_ESCAPE || key == K_ENTER) + m_state = m_main; + return; + } + + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (++m_singleplayer_cursor >= SINGLEPLAYER_ITEMS) + m_singleplayer_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (--m_singleplayer_cursor < 0) + m_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1; + break; + + case K_ENTER: + m_entersound = true; + + switch (m_singleplayer_cursor) + { + case 0: + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("maxplayers 1\n"); + Cbuf_AddText ("deathmatch 0\n"); + Cbuf_AddText ("coop 0\n"); + if (gamemode == GAME_TRANSFUSION) + { + key_dest = key_menu; + M_Menu_Transfusion_Episode_f (); + break; + } + Cbuf_AddText ("startmap_sp\n"); + break; + + case 1: + M_Menu_Load_f (); + break; + + case 2: + M_Menu_Save_f (); + break; + } + } +} + +//============================================================================= +/* LOAD/SAVE MENU */ + +static int load_cursor; ///< 0 < load_cursor < MAX_SAVEGAMES + +static char m_filenames[MAX_SAVEGAMES][SAVEGAME_COMMENT_LENGTH+1]; +static int loadable[MAX_SAVEGAMES]; + +static void M_ScanSaves (void) +{ + int i, j; + size_t len; + char name[MAX_OSPATH]; + char buf[SAVEGAME_COMMENT_LENGTH + 256]; + const char *t; + qfile_t *f; +// int version; + + for (i=0 ; iwidth)/2, 4, "gfx/p_load" ); + + for (i=0 ; i< MAX_SAVEGAMES; i++) + M_Print(16, 32 + 8*i, m_filenames[i]); + +// line cursor + M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1)); +} + + +static void M_Save_Draw (void) +{ + int i; + cachepic_t *p; + + M_Background(320, 200); + + p = Draw_CachePic ("gfx/p_save"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/p_save"); + + for (i=0 ; i= MAX_SAVEGAMES) + load_cursor = 0; + break; + } +} + + +static void M_Save_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + if (gamemode == GAME_TRANSFUSION) + M_Menu_Main_f (); + else + M_Menu_SinglePlayer_f (); + break; + + case K_ENTER: + m_state = m_none; + key_dest = key_game; + Cbuf_AddText (va("save s%i\n", load_cursor)); + return; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("sound/misc/menu1.wav"); + load_cursor--; + if (load_cursor < 0) + load_cursor = MAX_SAVEGAMES-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("sound/misc/menu1.wav"); + load_cursor++; + if (load_cursor >= MAX_SAVEGAMES) + load_cursor = 0; + break; + } +} + +//============================================================================= +/* Transfusion Single Player Episode Menu */ + +static int m_episode_cursor; +#define EPISODE_ITEMS 6 + +void M_Menu_Transfusion_Episode_f (void) +{ + m_entersound = true; + m_state = m_transfusion_episode; + key_dest = key_menu; +} + +static void M_Transfusion_Episode_Draw (void) +{ + int y; + cachepic_t *p; + M_Background(640, 480); + + p = Draw_CachePic ("gfx/menu/tb-episodes"); + M_DrawPic (640/2 - p->width/2, 40, "gfx/menu/tb-episodes"); + for (y = 0; y < EPISODE_ITEMS; y++){ + M_DrawPic (0, 160 + y * 40, va("gfx/menu/episode%i", y+1)); + } + + M_DrawPic (0, 120 + (m_episode_cursor + 1) * 40, va("gfx/menu/episode%iselected", m_episode_cursor + 1)); +} + +static void M_Transfusion_Episode_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + m_episode_cursor++; + if (m_episode_cursor >= EPISODE_ITEMS) + m_episode_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + m_episode_cursor--; + if (m_episode_cursor < 0) + m_episode_cursor = EPISODE_ITEMS - 1; + break; + + case K_ENTER: + Cbuf_AddText ("deathmatch 0\n"); + m_entersound = true; + M_Menu_Transfusion_Skill_f (); + } +} + +//============================================================================= +/* Transfusion Single Player Skill Menu */ + +static int m_skill_cursor = 2; +#define SKILL_ITEMS 5 + +void M_Menu_Transfusion_Skill_f (void) +{ + m_entersound = true; + m_state = m_transfusion_skill; + key_dest = key_menu; +} + +static void M_Transfusion_Skill_Draw (void) +{ + int y; + cachepic_t *p; + M_Background(640, 480); + + p = Draw_CachePic ("gfx/menu/tb-difficulty"); + M_DrawPic(640/2 - p->width/2, 40, "gfx/menu/tb-difficulty"); + + for (y = 0; y < SKILL_ITEMS; y++) + { + M_DrawPic (0, 180 + y * 40, va("gfx/menu/difficulty%i", y+1)); + } + M_DrawPic (0, 140 + (m_skill_cursor + 1) *40, va("gfx/menu/difficulty%iselected", m_skill_cursor + 1)); +} + +static void M_Transfusion_Skill_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Transfusion_Episode_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + m_skill_cursor++; + if (m_skill_cursor >= SKILL_ITEMS) + m_skill_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + m_skill_cursor--; + if (m_skill_cursor < 0) + m_skill_cursor = SKILL_ITEMS - 1; + break; + + case K_ENTER: + m_entersound = true; + switch (m_skill_cursor) + { + case 0: + Cbuf_AddText ("skill 1\n"); + break; + case 1: + Cbuf_AddText ("skill 2\n"); + break; + case 2: + Cbuf_AddText ("skill 3\n"); + break; + case 3: + Cbuf_AddText ("skill 4\n"); + break; + case 4: + Cbuf_AddText ("skill 5\n"); + break; + } + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("maxplayers 1\n"); + Cbuf_AddText ("deathmatch 0\n"); + Cbuf_AddText ("coop 0\n"); + switch (m_episode_cursor) + { + case 0: + Cbuf_AddText ("map e1m1\n"); + break; + case 1: + Cbuf_AddText ("map e2m1\n"); + break; + case 2: + Cbuf_AddText ("map e3m1\n"); + break; + case 3: + Cbuf_AddText ("map e4m1\n"); + break; + case 4: + Cbuf_AddText ("map e6m1\n"); + break; + case 5: + Cbuf_AddText ("map cp01\n"); + break; + } + } +} +//============================================================================= +/* MULTIPLAYER MENU */ + +static int m_multiplayer_cursor; +#define MULTIPLAYER_ITEMS 3 + + +void M_Menu_MultiPlayer_f (void) +{ + key_dest = key_menu; + m_state = m_multiplayer; + m_entersound = true; +} + + +static void M_MultiPlayer_Draw (void) +{ + int f; + cachepic_t *p; + + if (gamemode == GAME_TRANSFUSION) + { + M_Background(640, 480); + p = Draw_CachePic ("gfx/menu/tb-online"); + M_DrawPic (640/2 - p->width/2, 140, "gfx/menu/tb-online"); + for (f = 1; f <= MULTIPLAYER_ITEMS; f++) + M_DrawPic (0, 180 + f*40, va("gfx/menu/online%i", f)); + M_DrawPic (0, 220 + m_multiplayer_cursor * 40, va("gfx/menu/online%iselected", m_multiplayer_cursor + 1)); + return; + } + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_multi"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); + M_DrawPic (72, 32, "gfx/mp_menu"); + + f = (int)(realtime * 10)%6; + + M_DrawPic (54, 32 + m_multiplayer_cursor * 20, va("gfx/menudot%i", f+1)); +} + + +static void M_MultiPlayer_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (++m_multiplayer_cursor >= MULTIPLAYER_ITEMS) + m_multiplayer_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (--m_multiplayer_cursor < 0) + m_multiplayer_cursor = MULTIPLAYER_ITEMS - 1; + break; + + case K_ENTER: + m_entersound = true; + switch (m_multiplayer_cursor) + { + case 0: + case 1: + M_Menu_LanConfig_f (); + break; + + case 2: + M_Menu_Setup_f (); + break; + } + } +} + +//============================================================================= +/* SETUP MENU */ + +static int setup_cursor = 4; +static int setup_cursor_table[] = {40, 64, 88, 124, 140}; + +static char setup_myname[MAX_SCOREBOARDNAME]; +static int setup_oldtop; +static int setup_oldbottom; +static int setup_top; +static int setup_bottom; +static int setup_rate; +static int setup_oldrate; + +#define NUM_SETUP_CMDS 5 + +void M_Menu_Setup_f (void) +{ + key_dest = key_menu; + m_state = m_setup; + m_entersound = true; + strlcpy(setup_myname, cl_name.string, sizeof(setup_myname)); + setup_top = setup_oldtop = cl_color.integer >> 4; + setup_bottom = setup_oldbottom = cl_color.integer & 15; + setup_rate = cl_rate.integer; +} + +static int menuplyr_width, menuplyr_height, menuplyr_top, menuplyr_bottom, menuplyr_load; +static unsigned char *menuplyr_pixels; +static unsigned int *menuplyr_translated; + +typedef struct ratetable_s +{ + int rate; + const char *name; +} +ratetable_t; + +#define RATES ((int)(sizeof(setup_ratetable)/sizeof(setup_ratetable[0]))) +static ratetable_t setup_ratetable[] = +{ + {1000, "28.8 bad"}, + {1500, "28.8 mediocre"}, + {2000, "28.8 good"}, + {2500, "33.6 mediocre"}, + {3000, "33.6 good"}, + {3500, "56k bad"}, + {4000, "56k mediocre"}, + {4500, "56k adequate"}, + {5000, "56k good"}, + {7000, "64k ISDN"}, + {15000, "128k ISDN"}, + {25000, "broadband"} +}; + +static int setup_rateindex(int rate) +{ + int i; + for (i = 0;i < RATES;i++) + if (setup_ratetable[i].rate > setup_rate) + break; + return bound(1, i, RATES) - 1; +} + +static void M_Setup_Draw (void) +{ + int i, j; + cachepic_t *p; + + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_multi"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); + + M_Print(64, 40, "Your name"); + M_DrawTextBox (160, 32, 16, 1); + M_PrintColored(168, 40, setup_myname); + + if (gamemode != GAME_GOODVSBAD2) + { + M_Print(64, 64, "Shirt color"); + M_Print(64, 88, "Pants color"); + } + + M_Print(64, 124-8, "Network speed limit"); + M_Print(168, 124, va("%i (%s)", setup_rate, setup_ratetable[setup_rateindex(setup_rate)].name)); + + M_DrawTextBox (64, 140-8, 14, 1); + M_Print(72, 140, "Accept Changes"); + + // LordHavoc: rewrote this code greatly + if (menuplyr_load) + { + unsigned char *f; + fs_offset_t filesize; + menuplyr_load = false; + menuplyr_top = -1; + menuplyr_bottom = -1; + f = FS_LoadFile("gfx/menuplyr.lmp", tempmempool, true, &filesize); + if (f && filesize >= 9) + { + int width, height; + width = f[0] + f[1] * 256 + f[2] * 65536 + f[3] * 16777216; + height = f[4] + f[5] * 256 + f[6] * 65536 + f[7] * 16777216; + if (filesize >= 8 + width * height) + { + menuplyr_width = width; + menuplyr_height = height; + menuplyr_pixels = (unsigned char *)Mem_Alloc(cls.permanentmempool, width * height); + menuplyr_translated = (unsigned int *)Mem_Alloc(cls.permanentmempool, width * height * 4); + memcpy(menuplyr_pixels, f + 8, width * height); + } + } + if (f) + Mem_Free(f); + } + + if (menuplyr_pixels) + { + if (menuplyr_top != setup_top || menuplyr_bottom != setup_bottom) + { + menuplyr_top = setup_top; + menuplyr_bottom = setup_bottom; + + for (i = 0;i < menuplyr_width * menuplyr_height;i++) + { + j = menuplyr_pixels[i]; + if (j >= TOP_RANGE && j < TOP_RANGE + 16) + { + if (menuplyr_top < 8 || menuplyr_top == 14) + j = menuplyr_top * 16 + (j - TOP_RANGE); + else + j = menuplyr_top * 16 + 15-(j - TOP_RANGE); + } + else if (j >= BOTTOM_RANGE && j < BOTTOM_RANGE + 16) + { + if (menuplyr_bottom < 8 || menuplyr_bottom == 14) + j = menuplyr_bottom * 16 + (j - BOTTOM_RANGE); + else + j = menuplyr_bottom * 16 + 15-(j - BOTTOM_RANGE); + } + menuplyr_translated[i] = palette_bgra_transparent[j]; + } + Draw_NewPic("gfx/menuplyr", menuplyr_width, menuplyr_height, true, (unsigned char *)menuplyr_translated); + } + M_DrawPic(160, 48, "gfx/bigbox"); + M_DrawPic(172, 56, "gfx/menuplyr"); + } + + if (setup_cursor == 0) + M_DrawCharacter (168 + 8*strlen(setup_myname), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1)); + else + M_DrawCharacter (56, setup_cursor_table [setup_cursor], 12+((int)(realtime*4)&1)); +} + + +static void M_Setup_Key (int k, int ascii) +{ + int l; + + switch (k) + { + case K_ESCAPE: + M_Menu_MultiPlayer_f (); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + setup_cursor--; + if (setup_cursor < 0) + setup_cursor = NUM_SETUP_CMDS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + setup_cursor++; + if (setup_cursor >= NUM_SETUP_CMDS) + setup_cursor = 0; + break; + + case K_LEFTARROW: + if (setup_cursor < 1) + return; + S_LocalSound ("sound/misc/menu3.wav"); + if (setup_cursor == 1) + setup_top = setup_top - 1; + if (setup_cursor == 2) + setup_bottom = setup_bottom - 1; + if (setup_cursor == 3) + { + l = setup_rateindex(setup_rate) - 1; + if (l < 0) + l = RATES - 1; + setup_rate = setup_ratetable[l].rate; + } + break; + case K_RIGHTARROW: + if (setup_cursor < 1) + return; +forward: + S_LocalSound ("sound/misc/menu3.wav"); + if (setup_cursor == 1) + setup_top = setup_top + 1; + if (setup_cursor == 2) + setup_bottom = setup_bottom + 1; + if (setup_cursor == 3) + { + l = setup_rateindex(setup_rate) + 1; + if (l >= RATES) + l = 0; + setup_rate = setup_ratetable[l].rate; + } + break; + + case K_ENTER: + if (setup_cursor == 0) + return; + + if (setup_cursor == 1 || setup_cursor == 2 || setup_cursor == 3) + goto forward; + + // setup_cursor == 4 (Accept changes) + if (strcmp(cl_name.string, setup_myname) != 0) + Cbuf_AddText ( va ("name \"%s\"\n", setup_myname) ); + if (setup_top != setup_oldtop || setup_bottom != setup_oldbottom) + Cbuf_AddText( va ("color %i %i\n", setup_top, setup_bottom) ); + if (setup_rate != setup_oldrate) + Cbuf_AddText(va("rate %i\n", setup_rate)); + + m_entersound = true; + M_Menu_MultiPlayer_f (); + break; + + case K_BACKSPACE: + if (setup_cursor == 0) + { + if (strlen(setup_myname)) + setup_myname[strlen(setup_myname)-1] = 0; + } + break; + + default: + if (ascii < 32) + break; + if (setup_cursor == 0) + { + l = (int)strlen(setup_myname); + if (l < 15) + { + setup_myname[l+1] = 0; + setup_myname[l] = ascii; + } + } + } + + if (setup_top > 15) + setup_top = 0; + if (setup_top < 0) + setup_top = 15; + if (setup_bottom > 15) + setup_bottom = 0; + if (setup_bottom < 0) + setup_bottom = 15; +} + +//============================================================================= +/* OPTIONS MENU */ + +#define SLIDER_RANGE 10 + +static void M_DrawSlider (int x, int y, float num, float rangemin, float rangemax) +{ + char text[16]; + int i; + float range; + range = bound(0, (num - rangemin) / (rangemax - rangemin), 1); + M_DrawCharacter (x-8, y, 128); + for (i = 0;i < SLIDER_RANGE;i++) + M_DrawCharacter (x + i*8, y, 129); + M_DrawCharacter (x+i*8, y, 130); + M_DrawCharacter (x + (SLIDER_RANGE-1)*8 * range, y, 131); + if (fabs((int)num - num) < 0.01) + dpsnprintf(text, sizeof(text), "%i", (int)num); + else + dpsnprintf(text, sizeof(text), "%.3f", num); + M_Print(x + (SLIDER_RANGE+2) * 8, y, text); +} + +static void M_DrawCheckbox (int x, int y, int on) +{ + if (on) + M_Print(x, y, "on"); + else + M_Print(x, y, "off"); +} + + +//#define OPTIONS_ITEMS 25 aule was here +#define OPTIONS_ITEMS 27 + + +static int options_cursor; + +void M_Menu_Options_f (void) +{ + key_dest = key_menu; + m_state = m_options; + m_entersound = true; +} + +extern cvar_t slowmo; +extern dllhandle_t jpeg_dll; +extern cvar_t gl_texture_anisotropy; +extern cvar_t r_textshadow; +extern cvar_t r_hdr_scenebrightness; + +static void M_Menu_Options_AdjustSliders (int dir) +{ + int optnum; + double f; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 0; + if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) Cvar_SetValueQuick(&crosshair, bound(0, crosshair.integer + dir, 7)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&sensitivity, bound(1, sensitivity.value + dir * 0.5, 50)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&m_pitch, -m_pitch.value); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&scr_fov, bound(1, scr_fov.integer + dir * 1, 170)); + else if (options_cursor == optnum++) + { + if (cl_forwardspeed.value > 200) + { + Cvar_SetValueQuick (&cl_forwardspeed, 200); + Cvar_SetValueQuick (&cl_backspeed, 200); + } + else + { + Cvar_SetValueQuick (&cl_forwardspeed, 400); + Cvar_SetValueQuick (&cl_backspeed, 400); + } + } + else if (options_cursor == optnum++) Cvar_SetValueQuick(&showfps, !showfps.integer); + else if (options_cursor == optnum++) {f = !(showdate.integer && showtime.integer);Cvar_SetValueQuick(&showdate, f);Cvar_SetValueQuick(&showtime, f);} + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) Cvar_SetValueQuick(&r_hdr_scenebrightness, bound(1, r_hdr_scenebrightness.value + dir * 0.0625, 4)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&v_contrast, bound(1, v_contrast.value + dir * 0.0625, 4)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&v_gamma, bound(0.5, v_gamma.value + dir * 0.0625, 3)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&volume, bound(0, volume.value + dir * 0.0625, 1)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&bgmvolume, bound(0, bgmvolume.value + dir * 0.0625, 1)); +} + +static int optnum; +static int opty; +static int optcursor; + +static void M_Options_PrintCommand(const char *s, int enabled) +{ + if (opty >= 32) + { + if (optnum == optcursor) + DrawQ_Fill(menu_x + 48, menu_y + opty, 320, 8, optnum == optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_ItemPrint(0 + 48, opty, s, enabled); + } + opty += 8; + optnum++; +} + +static void M_Options_PrintCheckbox(const char *s, int enabled, int yes) +{ + if (opty >= 32) + { + if (optnum == optcursor) + DrawQ_Fill(menu_x + 48, menu_y + opty, 320, 8, optnum == optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_ItemPrint(0 + 48, opty, s, enabled); + M_DrawCheckbox(0 + 48 + (int)strlen(s) * 8 + 8, opty, yes); + } + opty += 8; + optnum++; +} + +static void M_Options_PrintSlider(const char *s, int enabled, float value, float minvalue, float maxvalue) +{ + if (opty >= 32) + { + if (optnum == optcursor) + DrawQ_Fill(menu_x + 48, menu_y + opty, 320, 8, optnum == optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_ItemPrint(0 + 48, opty, s, enabled); + M_DrawSlider(0 + 48 + (int)strlen(s) * 8 + 8, opty, value, minvalue, maxvalue); + } + opty += 8; + optnum++; +} + +static void M_Options_Draw (void) +{ + int visible; + cachepic_t *p; + + M_Background(320, bound(200, 32 + OPTIONS_ITEMS * 8, vid_conheight.integer)); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optnum = 0; + optcursor = options_cursor; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, OPTIONS_ITEMS - visible)) * 8; + + M_Options_PrintCommand( " Customize controls", true); + M_Options_PrintCommand( " Go to console", true); + M_Options_PrintCommand( " Reset to defaults", true); + M_Options_PrintCommand( " Change Video Mode", true); + M_Options_PrintSlider( " Crosshair", true, crosshair.value, 0, 7); + M_Options_PrintSlider( " Mouse Speed", true, sensitivity.value, 1, 50); + M_Options_PrintCheckbox(" Invert Mouse", true, m_pitch.value < 0); + M_Options_PrintSlider( " Field of View", true, scr_fov.integer, 1, 170); + M_Options_PrintCheckbox(" Always Run", true, cl_forwardspeed.value > 200); + M_Options_PrintCheckbox(" Show Framerate", true, showfps.integer); + M_Options_PrintCheckbox(" Show Date and Time", true, showdate.integer && showtime.integer); + M_Options_PrintCommand( " Custom Brightness", true); + M_Options_PrintSlider( " Game Brightness", true, r_hdr_scenebrightness.value, 1, 4); + M_Options_PrintSlider( " Brightness", true, v_contrast.value, 1, 2); + M_Options_PrintSlider( " Gamma", true, v_gamma.value, 0.5, 3); + M_Options_PrintSlider( " Sound Volume", snd_initialized.integer, volume.value, 0, 1); + M_Options_PrintSlider( " Music Volume", cdaudioinitialized.integer, bgmvolume.value, 0, 1); + M_Options_PrintCommand( " Customize Effects", true); + M_Options_PrintCommand( " Effects: Quake", true); + M_Options_PrintCommand( " Effects: Normal", true); + M_Options_PrintCommand( " Effects: High", true); + M_Options_PrintCommand( " Customize Lighting", true); + M_Options_PrintCommand( " Lighting: Flares", true); + M_Options_PrintCommand( " Lighting: Normal", true); + M_Options_PrintCommand( " Lighting: High", true); + M_Options_PrintCommand( " Lighting: Full", true); + M_Options_PrintCommand( " Browse Mods", true); +} + + +static void M_Options_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_ENTER: + m_entersound = true; + switch (options_cursor) + { + case 0: + M_Menu_Keys_f (); + break; + case 1: + m_state = m_none; + key_dest = key_game; + Con_ToggleConsole_f (); + break; + case 2: + M_Menu_Reset_f (); + break; + case 3: + M_Menu_Video_f (); + break; + case 11: + M_Menu_Options_ColorControl_f (); + break; + case 17: // Customize Effects + M_Menu_Options_Effects_f (); + break; + case 18: // Effects: Quake + Cbuf_AddText("cl_particles 1;cl_particles_quake 1;cl_particles_quality 1;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 0;cl_stainmaps_clearonload 1;cl_decals 0;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 0;cl_beams_polygons 0;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); + break; + case 19: // Effects: Normal + Cbuf_AddText("cl_particles 1;cl_particles_quake 0;cl_particles_quality 1;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 0;cl_stainmaps_clearonload 1;cl_decals 1;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 1;cl_beams_polygons 1;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); + break; + case 20: // Effects: High + Cbuf_AddText("cl_particles 1;cl_particles_quake 0;cl_particles_quality 2;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 1;cl_stainmaps_clearonload 1;cl_decals 1;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 1;cl_beams_polygons 1;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); + break; + case 21: + M_Menu_Options_Graphics_f (); + break; + case 22: // Lighting: Flares + Cbuf_AddText("r_coronas 1;gl_flashblend 1;r_shadow_gloss 0;r_shadow_realtime_dlight 0;r_shadow_realtime_dlight_shadows 0;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 0;r_hdr 0"); + break; + case 23: // Lighting: Normal + Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 0;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 0;r_hdr 0"); + break; + case 24: // Lighting: High + Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 1;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 1;r_hdr 0"); + break; + case 25: // Lighting: Full + Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 1;r_shadow_realtime_world 1;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 1;r_hdr 0"); + break; + case 26: + M_Menu_ModList_f (); + break; + default: + M_Menu_Options_AdjustSliders (1); + break; + } + return; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_cursor--; + if (options_cursor < 0) + options_cursor = OPTIONS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_cursor++; + if (options_cursor >= OPTIONS_ITEMS) + options_cursor = 0; + break; + + case K_LEFTARROW: + M_Menu_Options_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + M_Menu_Options_AdjustSliders (1); + break; + } +} + +#define OPTIONS_EFFECTS_ITEMS 35 + +static int options_effects_cursor; + +void M_Menu_Options_Effects_f (void) +{ + key_dest = key_menu; + m_state = m_options_effects; + m_entersound = true; +} + + +extern cvar_t cl_stainmaps; +extern cvar_t cl_stainmaps_clearonload; +extern cvar_t r_explosionclip; +extern cvar_t r_coronas; +extern cvar_t gl_flashblend; +extern cvar_t cl_beams_polygons; +extern cvar_t cl_beams_quakepositionhack; +extern cvar_t cl_beams_instantaimhack; +extern cvar_t cl_beams_lightatend; +extern cvar_t r_lightningbeam_thickness; +extern cvar_t r_lightningbeam_scroll; +extern cvar_t r_lightningbeam_repeatdistance; +extern cvar_t r_lightningbeam_color_red; +extern cvar_t r_lightningbeam_color_green; +extern cvar_t r_lightningbeam_color_blue; +extern cvar_t r_lightningbeam_qmbtexture; + +static void M_Menu_Options_Effects_AdjustSliders (int dir) +{ + int optnum; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 0; + if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles, !cl_particles.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_quake, !cl_particles_quake.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_quality, bound(1, cl_particles_quality.value + dir * 0.5, 4)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_explosions_shell, !cl_particles_explosions_shell.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_explosionclip, !r_explosionclip.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_stainmaps, !cl_stainmaps.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_stainmaps_clearonload, !cl_stainmaps_clearonload.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_decals, !cl_decals.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_bulletimpacts, !cl_particles_bulletimpacts.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_smoke, !cl_particles_smoke.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_sparks, !cl_particles_sparks.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_bubbles, !cl_particles_bubbles.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood, !cl_particles_blood.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood_alpha, bound(0.2, cl_particles_blood_alpha.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood_bloodhack, !cl_particles_blood_bloodhack.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_polygons, !cl_beams_polygons.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_instantaimhack, !cl_beams_instantaimhack.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_quakepositionhack, !cl_beams_quakepositionhack.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_lightatend, !cl_beams_lightatend.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_thickness, bound(1, r_lightningbeam_thickness.integer + dir, 10)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_scroll, bound(0, r_lightningbeam_scroll.integer + dir, 10)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_repeatdistance, bound(64, r_lightningbeam_repeatdistance.integer + dir * 64, 1024)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_red, bound(0, r_lightningbeam_color_red.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_green, bound(0, r_lightningbeam_color_green.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_blue, bound(0, r_lightningbeam_color_blue.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_qmbtexture, !r_lightningbeam_qmbtexture.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerpmodels, !r_lerpmodels.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerpsprites, !r_lerpsprites.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerplightstyles, !r_lerplightstyles.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&gl_polyblend, bound(0, gl_polyblend.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_skyscroll1, bound(-8, r_skyscroll1.value + dir * 0.1, 8)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_skyscroll2, bound(-8, r_skyscroll2.value + dir * 0.1, 8)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_waterwarp, bound(0, r_waterwarp.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_wateralpha, bound(0, r_wateralpha.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_waterscroll, bound(0, r_waterscroll.value + dir * 0.5, 10)); +} + +static void M_Options_Effects_Draw (void) +{ + int visible; + cachepic_t *p; + + M_Background(320, bound(200, 32 + OPTIONS_EFFECTS_ITEMS * 8, vid_conheight.integer)); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optcursor = options_effects_cursor; + optnum = 0; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, OPTIONS_EFFECTS_ITEMS - visible)) * 8; + + M_Options_PrintCheckbox(" Particles", true, cl_particles.integer); + M_Options_PrintCheckbox(" Quake-style Particles", true, cl_particles_quake.integer); + M_Options_PrintSlider( " Particles Quality", true, cl_particles_quality.value, 1, 4); + M_Options_PrintCheckbox(" Explosion Shell", true, cl_particles_explosions_shell.integer); + M_Options_PrintCheckbox(" Explosion Shell Clip", true, r_explosionclip.integer); + M_Options_PrintCheckbox(" Stainmaps", true, cl_stainmaps.integer); + M_Options_PrintCheckbox("Onload Clear Stainmaps", true, cl_stainmaps_clearonload.integer); + M_Options_PrintCheckbox(" Decals", true, cl_decals.integer); + M_Options_PrintCheckbox(" Bullet Impacts", true, cl_particles_bulletimpacts.integer); + M_Options_PrintCheckbox(" Smoke", true, cl_particles_smoke.integer); + M_Options_PrintCheckbox(" Sparks", true, cl_particles_sparks.integer); + M_Options_PrintCheckbox(" Bubbles", true, cl_particles_bubbles.integer); + M_Options_PrintCheckbox(" Blood", true, cl_particles_blood.integer); + M_Options_PrintSlider( " Blood Opacity", true, cl_particles_blood_alpha.value, 0.2, 1); + M_Options_PrintCheckbox("Force New Blood Effect", true, cl_particles_blood_bloodhack.integer); + M_Options_PrintCheckbox(" Polygon Lightning", true, cl_beams_polygons.integer); + M_Options_PrintCheckbox("Smooth Sweep Lightning", true, cl_beams_instantaimhack.integer); + M_Options_PrintCheckbox(" Waist-level Lightning", true, cl_beams_quakepositionhack.integer); + M_Options_PrintCheckbox(" Lightning End Light", true, cl_beams_lightatend.integer); + M_Options_PrintSlider( " Lightning Thickness", cl_beams_polygons.integer, r_lightningbeam_thickness.integer, 1, 10); + M_Options_PrintSlider( " Lightning Scroll", cl_beams_polygons.integer, r_lightningbeam_scroll.integer, 0, 10); + M_Options_PrintSlider( " Lightning Repeat Dist", cl_beams_polygons.integer, r_lightningbeam_repeatdistance.integer, 64, 1024); + M_Options_PrintSlider( " Lightning Color Red", cl_beams_polygons.integer, r_lightningbeam_color_red.value, 0, 1); + M_Options_PrintSlider( " Lightning Color Green", cl_beams_polygons.integer, r_lightningbeam_color_green.value, 0, 1); + M_Options_PrintSlider( " Lightning Color Blue", cl_beams_polygons.integer, r_lightningbeam_color_blue.value, 0, 1); + M_Options_PrintCheckbox(" Lightning QMB Texture", cl_beams_polygons.integer, r_lightningbeam_qmbtexture.integer); + M_Options_PrintCheckbox(" Model Interpolation", true, r_lerpmodels.integer); + M_Options_PrintCheckbox(" Sprite Interpolation", true, r_lerpsprites.integer); + M_Options_PrintCheckbox(" Flicker Interpolation", true, r_lerplightstyles.integer); + M_Options_PrintSlider( " View Blend", true, gl_polyblend.value, 0, 1); + M_Options_PrintSlider( "Upper Sky Scroll Speed", true, r_skyscroll1.value, -8, 8); + M_Options_PrintSlider( "Lower Sky Scroll Speed", true, r_skyscroll2.value, -8, 8); + M_Options_PrintSlider( " Underwater View Warp", true, r_waterwarp.value, 0, 1); + M_Options_PrintSlider( " Water Alpha (opacity)", true, r_wateralpha.value, 0, 1); + M_Options_PrintSlider( " Water Movement", true, r_waterscroll.value, 0, 10); +} + + +static void M_Options_Effects_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Options_f (); + break; + + case K_ENTER: + M_Menu_Options_Effects_AdjustSliders (1); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_effects_cursor--; + if (options_effects_cursor < 0) + options_effects_cursor = OPTIONS_EFFECTS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_effects_cursor++; + if (options_effects_cursor >= OPTIONS_EFFECTS_ITEMS) + options_effects_cursor = 0; + break; + + case K_LEFTARROW: + M_Menu_Options_Effects_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + M_Menu_Options_Effects_AdjustSliders (1); + break; + } +} + + +#define OPTIONS_GRAPHICS_ITEMS 20 + +static int options_graphics_cursor; + +void M_Menu_Options_Graphics_f (void) +{ + key_dest = key_menu; + m_state = m_options_graphics; + m_entersound = true; +} + +extern cvar_t r_shadow_gloss; +extern cvar_t r_shadow_realtime_dlight; +extern cvar_t r_shadow_realtime_dlight_shadows; +extern cvar_t r_shadow_realtime_world; +extern cvar_t r_shadow_realtime_world_lightmaps; +extern cvar_t r_shadow_realtime_world_shadows; +extern cvar_t r_bloom; +extern cvar_t r_bloom_colorscale; +extern cvar_t r_bloom_colorsubtract; +extern cvar_t r_bloom_colorexponent; +extern cvar_t r_bloom_blur; +extern cvar_t r_bloom_brighten; +extern cvar_t r_bloom_resolution; +extern cvar_t r_hdr; +extern cvar_t r_hdr_scenebrightness; +extern cvar_t r_hdr_glowintensity; +extern cvar_t r_hdr_range; +extern cvar_t gl_picmip; + +static void M_Menu_Options_Graphics_AdjustSliders (int dir) +{ + int optnum; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 0; + + if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_coronas, bound(0, r_coronas.value + dir * 0.125, 4)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&gl_flashblend, !gl_flashblend.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_gloss, bound(0, r_shadow_gloss.integer + dir, 2)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_dlight, !r_shadow_realtime_dlight.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_dlight_shadows, !r_shadow_realtime_dlight_shadows.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world, !r_shadow_realtime_world.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world_lightmaps, bound(0, r_shadow_realtime_world_lightmaps.value + dir * 0.1, 1)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world_shadows, !r_shadow_realtime_world_shadows.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_scenebrightness, bound(0.25, r_hdr_scenebrightness.value + dir * 0.125, 4)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom, !r_bloom.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr, !r_hdr.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_range, bound(1, r_hdr_range.value + dir * 0.25, 16)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_glowintensity, bound(0, r_hdr_glowintensity.value + dir * 0.25, 4)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorscale, bound(0.0625, r_bloom_colorscale.value + dir * 0.0625, 1)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorsubtract, bound(0, r_bloom_colorsubtract.value + dir * 0.0625, 1-0.0625)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorexponent, bound(1, r_bloom_colorexponent.value * (dir > 0 ? 2.0 : 0.5), 8)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_brighten, bound(1, r_bloom_brighten.value + dir * 0.0625, 4)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_blur, bound(1, r_bloom_blur.value + dir * 1, 16)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_resolution, bound(64, r_bloom_resolution.value + dir * 64, 2048)); + else if (options_graphics_cursor == optnum++) Cbuf_AddText ("r_restart\n"); +} + + +static void M_Options_Graphics_Draw (void) +{ + int visible; + cachepic_t *p; + + M_Background(320, bound(200, 32 + OPTIONS_GRAPHICS_ITEMS * 8, vid_conheight.integer)); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optcursor = options_graphics_cursor; + optnum = 0; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, OPTIONS_GRAPHICS_ITEMS - visible)) * 8; + + M_Options_PrintSlider( " Corona Intensity", true, r_coronas.value, 0, 4); + M_Options_PrintCheckbox(" Use Only Coronas", true, gl_flashblend.integer); + M_Options_PrintSlider( " Gloss Mode", true, r_shadow_gloss.integer, 0, 2); + M_Options_PrintCheckbox(" RT DLights", !gl_flashblend.integer, r_shadow_realtime_dlight.integer); + M_Options_PrintCheckbox(" RT DLight Shadows", !gl_flashblend.integer, r_shadow_realtime_dlight_shadows.integer); + M_Options_PrintCheckbox(" RT World", true, r_shadow_realtime_world.integer); + M_Options_PrintSlider( " RT World Lightmaps", true, r_shadow_realtime_world_lightmaps.value, 0, 1); + M_Options_PrintCheckbox(" RT World Shadow", true, r_shadow_realtime_world_shadows.integer); + M_Options_PrintSlider( " Scene Brightness", true, r_hdr_scenebrightness.value, 0.25, 4); + M_Options_PrintCheckbox(" Bloom Effect", !r_hdr.integer, r_bloom.integer); + M_Options_PrintCheckbox(" HDR Bloom Effect", true, r_hdr.integer); + M_Options_PrintSlider( " HDR Dynamic Range", r_hdr.integer, r_hdr_range.value, 1, 16); + M_Options_PrintSlider( " HDR Glow Intensity", r_hdr.integer, r_hdr_glowintensity.value, 0, 4); + M_Options_PrintSlider( " Bloom Color Scale", r_hdr.integer || r_bloom.integer, r_bloom_colorscale.value, 0.0625, 1); + M_Options_PrintSlider( " Bloom Color Subtract", r_hdr.integer || r_bloom.integer, r_bloom_colorsubtract.value, 0, 1-0.0625); + M_Options_PrintSlider( " Bloom Color Exponent", r_hdr.integer || r_bloom.integer, r_bloom_colorexponent.value, 1, 8); + M_Options_PrintSlider( " Bloom Intensity", r_hdr.integer || r_bloom.integer, r_bloom_brighten.value, 1, 4); + M_Options_PrintSlider( " Bloom Blur", r_hdr.integer || r_bloom.integer, r_bloom_blur.value, 1, 16); + M_Options_PrintSlider( " Bloom Resolution", r_hdr.integer || r_bloom.integer, r_bloom_resolution.value, 64, 2048); + M_Options_PrintCommand( " Restart Renderer", true); +} + + +static void M_Options_Graphics_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Options_f (); + break; + + case K_ENTER: + M_Menu_Options_Graphics_AdjustSliders (1); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_graphics_cursor--; + if (options_graphics_cursor < 0) + options_graphics_cursor = OPTIONS_GRAPHICS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_graphics_cursor++; + if (options_graphics_cursor >= OPTIONS_GRAPHICS_ITEMS) + options_graphics_cursor = 0; + break; + + case K_LEFTARROW: + M_Menu_Options_Graphics_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + M_Menu_Options_Graphics_AdjustSliders (1); + break; + } +} + + +#define OPTIONS_COLORCONTROL_ITEMS 18 + +static int options_colorcontrol_cursor; + +// intensity value to match up to 50% dither to 'correct' quake +static cvar_t menu_options_colorcontrol_correctionvalue = {0, "menu_options_colorcontrol_correctionvalue", "0.5", "intensity value that matches up to white/black dither pattern, should be 0.5 for linear color"}; + +void M_Menu_Options_ColorControl_f (void) +{ + key_dest = key_menu; + m_state = m_options_colorcontrol; + m_entersound = true; +} + + +static void M_Menu_Options_ColorControl_AdjustSliders (int dir) +{ + int optnum; + float f; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 1; + if (options_colorcontrol_cursor == optnum++) + Cvar_SetValueQuick (&v_hwgamma, !v_hwgamma.integer); + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 0); + Cvar_SetValueQuick (&v_gamma, bound(1, v_gamma.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 0); + Cvar_SetValueQuick (&v_contrast, bound(1, v_contrast.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 0); + Cvar_SetValueQuick (&v_brightness, bound(0, v_brightness.value + dir * 0.05, 0.8)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, !v_color_enable.integer); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_black_r, bound(0, v_color_black_r.value + dir * 0.0125, 0.8)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_black_g, bound(0, v_color_black_g.value + dir * 0.0125, 0.8)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_black_b, bound(0, v_color_black_b.value + dir * 0.0125, 0.8)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + f = bound(0, (v_color_black_r.value + v_color_black_g.value + v_color_black_b.value) / 3 + dir * 0.0125, 0.8); + Cvar_SetValueQuick (&v_color_black_r, f); + Cvar_SetValueQuick (&v_color_black_g, f); + Cvar_SetValueQuick (&v_color_black_b, f); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_grey_r, bound(0, v_color_grey_r.value + dir * 0.0125, 0.95)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_grey_g, bound(0, v_color_grey_g.value + dir * 0.0125, 0.95)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_grey_b, bound(0, v_color_grey_b.value + dir * 0.0125, 0.95)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + f = bound(0, (v_color_grey_r.value + v_color_grey_g.value + v_color_grey_b.value) / 3 + dir * 0.0125, 0.95); + Cvar_SetValueQuick (&v_color_grey_r, f); + Cvar_SetValueQuick (&v_color_grey_g, f); + Cvar_SetValueQuick (&v_color_grey_b, f); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_white_r, bound(1, v_color_white_r.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_white_g, bound(1, v_color_white_g.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_white_b, bound(1, v_color_white_b.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + f = bound(1, (v_color_white_r.value + v_color_white_g.value + v_color_white_b.value) / 3 + dir * 0.125, 5); + Cvar_SetValueQuick (&v_color_white_r, f); + Cvar_SetValueQuick (&v_color_white_g, f); + Cvar_SetValueQuick (&v_color_white_b, f); + } +} + +static void M_Options_ColorControl_Draw (void) +{ + int visible; + float x, c, s, t, u, v; + cachepic_t *p, *dither; + + dither = Draw_CachePic_Flags ("gfx/colorcontrol/ditherpattern", CACHEPICFLAG_NOCLAMP); + + M_Background(320, 256); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optcursor = options_colorcontrol_cursor; + optnum = 0; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, OPTIONS_COLORCONTROL_ITEMS - visible)) * 8; + + M_Options_PrintCommand( " Reset to defaults", true); + M_Options_PrintCheckbox("Hardware Gamma Control", vid_hardwaregammasupported.integer, v_hwgamma.integer); + M_Options_PrintSlider( " Gamma", !v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_gamma.value, 1, 5); + M_Options_PrintSlider( " Contrast", !v_color_enable.integer, v_contrast.value, 1, 5); + M_Options_PrintSlider( " Brightness", !v_color_enable.integer, v_brightness.value, 0, 0.8); + M_Options_PrintCheckbox(" Color Level Controls", true, v_color_enable.integer); + M_Options_PrintSlider( " Black: Red ", v_color_enable.integer, v_color_black_r.value, 0, 0.8); + M_Options_PrintSlider( " Black: Green", v_color_enable.integer, v_color_black_g.value, 0, 0.8); + M_Options_PrintSlider( " Black: Blue ", v_color_enable.integer, v_color_black_b.value, 0, 0.8); + M_Options_PrintSlider( " Black: Grey ", v_color_enable.integer, (v_color_black_r.value + v_color_black_g.value + v_color_black_b.value) / 3, 0, 0.8); + M_Options_PrintSlider( " Grey: Red ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_r.value, 0, 0.95); + M_Options_PrintSlider( " Grey: Green", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_g.value, 0, 0.95); + M_Options_PrintSlider( " Grey: Blue ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_b.value, 0, 0.95); + M_Options_PrintSlider( " Grey: Grey ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, (v_color_grey_r.value + v_color_grey_g.value + v_color_grey_b.value) / 3, 0, 0.95); + M_Options_PrintSlider( " White: Red ", v_color_enable.integer, v_color_white_r.value, 1, 5); + M_Options_PrintSlider( " White: Green", v_color_enable.integer, v_color_white_g.value, 1, 5); + M_Options_PrintSlider( " White: Blue ", v_color_enable.integer, v_color_white_b.value, 1, 5); + M_Options_PrintSlider( " White: Grey ", v_color_enable.integer, (v_color_white_r.value + v_color_white_g.value + v_color_white_b.value) / 3, 1, 5); + + opty += 4; + DrawQ_Fill(menu_x, menu_y + opty, 320, 4 + 64 + 8 + 64 + 4, 0, 0, 0, 1, 0);opty += 4; + s = (float) 312 / 2 * vid.width / vid_conwidth.integer; + t = (float) 4 / 2 * vid.height / vid_conheight.integer; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, dither, 312, 4, 0,0, 1,0,0,1, s,0, 1,0,0,1, 0,t, 1,0,0,1, s,t, 1,0,0,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 1,0,0,1, 0,1, 0,0,0,1, 1,1, 1,0,0,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, dither, 312, 4, 0,0, 0,1,0,1, s,0, 0,1,0,1, 0,t, 0,1,0,1, s,t, 0,1,0,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 0,1,0,1, 0,1, 0,0,0,1, 1,1, 0,1,0,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, dither, 312, 4, 0,0, 0,0,1,1, s,0, 0,0,1,1, 0,t, 0,0,1,1, s,t, 0,0,1,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 0,0,1,1, 0,1, 0,0,0,1, 1,1, 0,0,1,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, dither, 312, 4, 0,0, 1,1,1,1, s,0, 1,1,1,1, 0,t, 1,1,1,1, s,t, 1,1,1,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 1,1,1,1, 0,1, 0,0,0,1, 1,1, 1,1,1,1, 0);opty += 4; + + c = menu_options_colorcontrol_correctionvalue.value; // intensity value that should be matched up to a 50% dither to 'correct' quake + s = (float) 48 / 2 * vid.width / vid_conwidth.integer; + t = (float) 48 / 2 * vid.height / vid_conheight.integer; + u = s * 0.5; + v = t * 0.5; + opty += 8; + x = 4; + DrawQ_Fill(menu_x + x, menu_y + opty, 64, 48, c, 0, 0, 1, 0); + DrawQ_SuperPic(menu_x + x + 16, menu_y + opty + 16, dither, 16, 16, 0,0, 1,0,0,1, s,0, 1,0,0,1, 0,t, 1,0,0,1, s,t, 1,0,0,1, 0); + DrawQ_SuperPic(menu_x + x + 32, menu_y + opty + 16, dither, 16, 16, 0,0, 1,0,0,1, u,0, 1,0,0,1, 0,v, 1,0,0,1, u,v, 1,0,0,1, 0); + x += 80; + DrawQ_Fill(menu_x + x, menu_y + opty, 64, 48, 0, c, 0, 1, 0); + DrawQ_SuperPic(menu_x + x + 16, menu_y + opty + 16, dither, 16, 16, 0,0, 0,1,0,1, s,0, 0,1,0,1, 0,t, 0,1,0,1, s,t, 0,1,0,1, 0); + DrawQ_SuperPic(menu_x + x + 32, menu_y + opty + 16, dither, 16, 16, 0,0, 0,1,0,1, u,0, 0,1,0,1, 0,v, 0,1,0,1, u,v, 0,1,0,1, 0); + x += 80; + DrawQ_Fill(menu_x + x, menu_y + opty, 64, 48, 0, 0, c, 1, 0); + DrawQ_SuperPic(menu_x + x + 16, menu_y + opty + 16, dither, 16, 16, 0,0, 0,0,1,1, s,0, 0,0,1,1, 0,t, 0,0,1,1, s,t, 0,0,1,1, 0); + DrawQ_SuperPic(menu_x + x + 32, menu_y + opty + 16, dither, 16, 16, 0,0, 0,0,1,1, u,0, 0,0,1,1, 0,v, 0,0,1,1, u,v, 0,0,1,1, 0); + x += 80; + DrawQ_Fill(menu_x + x, menu_y + opty, 64, 48, c, c, c, 1, 0); + DrawQ_SuperPic(menu_x + x + 16, menu_y + opty + 16, dither, 16, 16, 0,0, 1,1,1,1, s,0, 1,1,1,1, 0,t, 1,1,1,1, s,t, 1,1,1,1, 0); + DrawQ_SuperPic(menu_x + x + 32, menu_y + opty + 16, dither, 16, 16, 0,0, 1,1,1,1, u,0, 1,1,1,1, 0,v, 1,1,1,1, u,v, 1,1,1,1, 0); +} + + +static void M_Options_ColorControl_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Options_f (); + break; + + case K_ENTER: + m_entersound = true; + switch (options_colorcontrol_cursor) + { + case 0: + Cvar_SetValueQuick(&v_hwgamma, 1); + Cvar_SetValueQuick(&v_gamma, 1); + Cvar_SetValueQuick(&v_contrast, 1); + Cvar_SetValueQuick(&v_brightness, 0); + Cvar_SetValueQuick(&v_color_enable, 0); + Cvar_SetValueQuick(&v_color_black_r, 0); + Cvar_SetValueQuick(&v_color_black_g, 0); + Cvar_SetValueQuick(&v_color_black_b, 0); + Cvar_SetValueQuick(&v_color_grey_r, 0); + Cvar_SetValueQuick(&v_color_grey_g, 0); + Cvar_SetValueQuick(&v_color_grey_b, 0); + Cvar_SetValueQuick(&v_color_white_r, 1); + Cvar_SetValueQuick(&v_color_white_g, 1); + Cvar_SetValueQuick(&v_color_white_b, 1); + break; + default: + M_Menu_Options_ColorControl_AdjustSliders (1); + break; + } + return; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_colorcontrol_cursor--; + if (options_colorcontrol_cursor < 0) + options_colorcontrol_cursor = OPTIONS_COLORCONTROL_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_colorcontrol_cursor++; + if (options_colorcontrol_cursor >= OPTIONS_COLORCONTROL_ITEMS) + options_colorcontrol_cursor = 0; + break; + + case K_LEFTARROW: + M_Menu_Options_ColorControl_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + M_Menu_Options_ColorControl_AdjustSliders (1); + break; + } +} + + +//============================================================================= +/* KEYS MENU */ + +static const char *quakebindnames[][2] = +{ +{"+attack", "attack"}, +{"impulse 10", "next weapon"}, +{"impulse 12", "previous weapon"}, +{"+jump", "jump / swim up"}, +{"+forward", "walk forward"}, +{"+back", "backpedal"}, +{"+left", "turn left"}, +{"+right", "turn right"}, +{"+speed", "run"}, +{"+moveleft", "step left"}, +{"+moveright", "step right"}, +{"+strafe", "sidestep"}, +{"+lookup", "look up"}, +{"+lookdown", "look down"}, +{"centerview", "center view"}, +{"+mlook", "mouse look"}, +{"+klook", "keyboard look"}, +{"+moveup", "swim up"}, +{"+movedown", "swim down"} +}; + +static const char *transfusionbindnames[][2] = +{ +{"", "Movement"}, // Movement commands +{"+forward", "walk forward"}, +{"+back", "backpedal"}, +{"+left", "turn left"}, +{"+right", "turn right"}, +{"+moveleft", "step left"}, +{"+moveright", "step right"}, +{"+jump", "jump / swim up"}, +{"+movedown", "swim down"}, +{"", "Combat"}, // Combat commands +{"impulse 1", "Pitch Fork"}, +{"impulse 2", "Flare Gun"}, +{"impulse 3", "Shotgun"}, +{"impulse 4", "Machine Gun"}, +{"impulse 5", "Incinerator"}, +{"impulse 6", "Bombs (TNT)"}, +{"impulse 35", "Proximity Bomb"}, +{"impulse 36", "Remote Detonator"}, +{"impulse 7", "Aerosol Can"}, +{"impulse 8", "Tesla Cannon"}, +{"impulse 9", "Life Leech"}, +{"impulse 10", "Voodoo Doll"}, +{"impulse 21", "next weapon"}, +{"impulse 22", "previous weapon"}, +{"+attack", "attack"}, +{"+button3", "altfire"}, +{"", "Inventory"}, // Inventory commands +{"impulse 40", "Dr.'s Bag"}, +{"impulse 41", "Crystal Ball"}, +{"impulse 42", "Beast Vision"}, +{"impulse 43", "Jump Boots"}, +{"impulse 23", "next item"}, +{"impulse 24", "previous item"}, +{"impulse 25", "use item"}, +{"", "Misc"}, // Misc commands +{"+button4", "use"}, +{"impulse 50", "add bot (red)"}, +{"impulse 51", "add bot (blue)"}, +{"impulse 52", "kick a bot"}, +{"impulse 26", "next armor type"}, +{"impulse 27", "identify player"}, +{"impulse 55", "voting menu"}, +{"impulse 56", "observer mode"}, +{"", "Taunts"}, // Taunts +{"impulse 70", "taunt 0"}, +{"impulse 71", "taunt 1"}, +{"impulse 72", "taunt 2"}, +{"impulse 73", "taunt 3"}, +{"impulse 74", "taunt 4"}, +{"impulse 75", "taunt 5"}, +{"impulse 76", "taunt 6"}, +{"impulse 77", "taunt 7"}, +{"impulse 78", "taunt 8"}, +{"impulse 79", "taunt 9"} +}; + +static const char *goodvsbad2bindnames[][2] = +{ +{"impulse 69", "Power 1"}, +{"impulse 70", "Power 2"}, +{"impulse 71", "Power 3"}, +{"+jump", "jump / swim up"}, +{"+forward", "walk forward"}, +{"+back", "backpedal"}, +{"+left", "turn left"}, +{"+right", "turn right"}, +{"+speed", "run"}, +{"+moveleft", "step left"}, +{"+moveright", "step right"}, +{"+strafe", "sidestep"}, +{"+lookup", "look up"}, +{"+lookdown", "look down"}, +{"centerview", "center view"}, +{"+mlook", "mouse look"}, +{"kill", "kill yourself"}, +{"+moveup", "swim up"}, +{"+movedown", "swim down"} +}; + +static int numcommands; +static const char *(*bindnames)[2]; + +/* +typedef struct binditem_s +{ + char *command, *description; + struct binditem_s *next; +} +binditem_t; + +typedef struct bindcategory_s +{ + char *name; + binditem_t *binds; + struct bindcategory_s *next; +} +bindcategory_t; + +static bindcategory_t *bindcategories = NULL; + +static void M_ClearBinds (void) +{ + for (c = bindcategories;c;c = cnext) + { + cnext = c->next; + for (b = c->binds;b;b = bnext) + { + bnext = b->next; + Z_Free(b); + } + Z_Free(c); + } + bindcategories = NULL; +} + +static void M_AddBindToCategory(bindcategory_t *c, char *command, char *description) +{ + for (b = &c->binds;*b;*b = &(*b)->next); + *b = Z_Alloc(sizeof(binditem_t) + strlen(command) + 1 + strlen(description) + 1); + *b->command = (char *)((*b) + 1); + *b->description = *b->command + strlen(command) + 1; + strlcpy(*b->command, command, strlen(command) + 1); + strlcpy(*b->description, description, strlen(description) + 1); +} + +static void M_AddBind (char *category, char *command, char *description) +{ + for (c = &bindcategories;*c;c = &(*c)->next) + { + if (!strcmp(category, (*c)->name)) + { + M_AddBindToCategory(*c, command, description); + return; + } + } + *c = Z_Alloc(sizeof(bindcategory_t)); + M_AddBindToCategory(*c, command, description); +} + +static void M_DefaultBinds (void) +{ + M_ClearBinds(); + M_AddBind("movement", "+jump", "jump / swim up"); + M_AddBind("movement", "+forward", "walk forward"); + M_AddBind("movement", "+back", "backpedal"); + M_AddBind("movement", "+left", "turn left"); + M_AddBind("movement", "+right", "turn right"); + M_AddBind("movement", "+speed", "run"); + M_AddBind("movement", "+moveleft", "step left"); + M_AddBind("movement", "+moveright", "step right"); + M_AddBind("movement", "+strafe", "sidestep"); + M_AddBind("movement", "+lookup", "look up"); + M_AddBind("movement", "+lookdown", "look down"); + M_AddBind("movement", "centerview", "center view"); + M_AddBind("movement", "+mlook", "mouse look"); + M_AddBind("movement", "+klook", "keyboard look"); + M_AddBind("movement", "+moveup", "swim up"); + M_AddBind("movement", "+movedown", "swim down"); + M_AddBind("weapons", "+attack", "attack"); + M_AddBind("weapons", "impulse 10", "next weapon"); + M_AddBind("weapons", "impulse 12", "previous weapon"); + M_AddBind("weapons", "impulse 1", "select weapon 1 (axe)"); + M_AddBind("weapons", "impulse 2", "select weapon 2 (shotgun)"); + M_AddBind("weapons", "impulse 3", "select weapon 3 (super )"); + M_AddBind("weapons", "impulse 4", "select weapon 4 (nailgun)"); + M_AddBind("weapons", "impulse 5", "select weapon 5 (super nailgun)"); + M_AddBind("weapons", "impulse 6", "select weapon 6 (grenade launcher)"); + M_AddBind("weapons", "impulse 7", "select weapon 7 (rocket launcher)"); + M_AddBind("weapons", "impulse 8", "select weapon 8 (lightning gun)"); +} +*/ + + +static int keys_cursor; +static int bind_grab; + +void M_Menu_Keys_f (void) +{ + key_dest = key_menu_grabbed; + m_state = m_keys; + m_entersound = true; + + if (gamemode == GAME_TRANSFUSION) + { + numcommands = sizeof(transfusionbindnames) / sizeof(transfusionbindnames[0]); + bindnames = transfusionbindnames; + } + else if (gamemode == GAME_GOODVSBAD2) + { + numcommands = sizeof(goodvsbad2bindnames) / sizeof(goodvsbad2bindnames[0]); + bindnames = goodvsbad2bindnames; + } + else + { + numcommands = sizeof(quakebindnames) / sizeof(quakebindnames[0]); + bindnames = quakebindnames; + } + + // Make sure "keys_cursor" doesn't start on a section in the binding list + keys_cursor = 0; + while (bindnames[keys_cursor][0][0] == '\0') + { + keys_cursor++; + + // Only sections? There may be a problem somewhere... + if (keys_cursor >= numcommands) + Sys_Error ("M_Init: The key binding list only contains sections"); + } +} + +#define NUMKEYS 5 + +static void M_UnbindCommand (const char *command) +{ + int j; + const char *b; + + for (j = 0; j < (int)sizeof (keybindings[0]) / (int)sizeof (keybindings[0][0]); j++) + { + b = keybindings[0][j]; + if (!b) + continue; + if (!strcmp (b, command)) + Key_SetBinding (j, 0, ""); + } +} + + +static void M_Keys_Draw (void) +{ + int i, j; + int keys[NUMKEYS]; + int y; + cachepic_t *p; + char keystring[MAX_INPUTLINE]; + + M_Background(320, 48 + 8 * numcommands); + + p = Draw_CachePic ("gfx/ttl_cstm"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_cstm"); + + if (bind_grab) + M_Print(12, 32, "Press a key or button for this action"); + else + M_Print(18, 32, "Enter to change, backspace to clear"); + +// search for known bindings + for (i=0 ; i 0) + strlcat(keystring, " or ", sizeof(keystring)); + strlcat(keystring, Key_KeynumToString (keys[j]), sizeof(keystring)); + } + } + } + M_Print(150, y, keystring); + } + + if (bind_grab) + M_DrawCharacter (140, 48 + keys_cursor*8, '='); + else + M_DrawCharacter (140, 48 + keys_cursor*8, 12+((int)(realtime*4)&1)); +} + + +static void M_Keys_Key (int k, int ascii) +{ + char cmd[80]; + int keys[NUMKEYS]; + + if (bind_grab) + { // defining a key + S_LocalSound ("sound/misc/menu1.wav"); + if (k == K_ESCAPE) + { + bind_grab = false; + } + else //if (k != '`') + { + dpsnprintf (cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString (k), bindnames[keys_cursor][0]); + Cbuf_InsertText (cmd); + } + + bind_grab = false; + return; + } + + switch (k) + { + case K_ESCAPE: + M_Menu_Options_f (); + break; + + case K_LEFTARROW: + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + do + { + keys_cursor--; + if (keys_cursor < 0) + keys_cursor = numcommands-1; + } + while (bindnames[keys_cursor][0][0] == '\0'); // skip sections + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("sound/misc/menu1.wav"); + do + { + keys_cursor++; + if (keys_cursor >= numcommands) + keys_cursor = 0; + } + while (bindnames[keys_cursor][0][0] == '\0'); // skip sections + break; + + case K_ENTER: // go into bind mode + Key_FindKeysForCommand (bindnames[keys_cursor][0], keys, NUMKEYS, 0); + S_LocalSound ("sound/misc/menu2.wav"); + if (keys[NUMKEYS - 1] != -1) + M_UnbindCommand (bindnames[keys_cursor][0]); + bind_grab = true; + break; + + case K_BACKSPACE: // delete bindings + case K_DEL: // delete bindings + S_LocalSound ("sound/misc/menu2.wav"); + M_UnbindCommand (bindnames[keys_cursor][0]); + break; + } +} + +void M_Menu_Reset_f (void) +{ + key_dest = key_menu; + m_state = m_reset; + m_entersound = true; +} + + +static void M_Reset_Key (int key, int ascii) +{ + switch (key) + { + case 'Y': + case 'y': + Cbuf_AddText ("cvar_resettodefaults_all;exec default.cfg\n"); + // no break here since we also exit the menu + + case K_ESCAPE: + case 'n': + case 'N': + m_state = m_options; + m_entersound = true; + break; + + default: + break; + } +} + +static void M_Reset_Draw (void) +{ + int lines = 2, linelength = 20; + M_Background(linelength * 8 + 16, lines * 8 + 16); + M_DrawTextBox(0, 0, linelength, lines); + M_Print(8 + 4 * (linelength - 19), 8, "Really wanna reset?"); + M_Print(8 + 4 * (linelength - 11), 16, "Press y / n"); +} + +//============================================================================= +/* VIDEO MENU */ + +video_resolution_t video_resolutions_hardcoded[] = +{ +{"Standard 4x3" , 320, 240, 320, 240, 1 }, +{"Standard 4x3" , 400, 300, 400, 300, 1 }, +{"Standard 4x3" , 512, 384, 512, 384, 1 }, +{"Standard 4x3" , 640, 480, 640, 480, 1 }, +{"Standard 4x3" , 800, 600, 640, 480, 1 }, +{"Standard 4x3" , 1024, 768, 640, 480, 1 }, +{"Standard 4x3" , 1152, 864, 640, 480, 1 }, +{"Standard 4x3" , 1280, 960, 640, 480, 1 }, +{"Standard 4x3" , 1400,1050, 640, 480, 1 }, +{"Standard 4x3" , 1600,1200, 640, 480, 1 }, +{"Standard 4x3" , 1792,1344, 640, 480, 1 }, +{"Standard 4x3" , 1856,1392, 640, 480, 1 }, +{"Standard 4x3" , 1920,1440, 640, 480, 1 }, +{"Standard 4x3" , 2048,1536, 640, 480, 1 }, +{"Short Pixel (CRT) 5x4" , 320, 256, 320, 256, 0.9375}, +{"Short Pixel (CRT) 5x4" , 640, 512, 640, 512, 0.9375}, +{"Short Pixel (CRT) 5x4" , 1280,1024, 640, 512, 0.9375}, +{"Tall Pixel (CRT) 8x5" , 320, 200, 320, 200, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 640, 400, 640, 400, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 840, 525, 640, 400, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 960, 600, 640, 400, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 1680,1050, 640, 400, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 1920,1200, 640, 400, 1.2 }, +{"Square Pixel (LCD) 5x4" , 320, 256, 320, 256, 1 }, +{"Square Pixel (LCD) 5x4" , 640, 512, 640, 512, 1 }, +{"Square Pixel (LCD) 5x4" , 1280,1024, 640, 512, 1 }, +{"WideScreen 5x3" , 640, 384, 640, 384, 1 }, +{"WideScreen 5x3" , 1280, 768, 640, 384, 1 }, +{"WideScreen 8x5" , 320, 200, 320, 200, 1 }, +{"WideScreen 8x5" , 640, 400, 640, 400, 1 }, +{"WideScreen 8x5" , 720, 450, 720, 450, 1 }, +{"WideScreen 8x5" , 840, 525, 640, 400, 1 }, +{"WideScreen 8x5" , 960, 600, 640, 400, 1 }, +{"WideScreen 8x5" , 1280, 800, 640, 400, 1 }, +{"WideScreen 8x5" , 1440, 900, 720, 450, 1 }, +{"WideScreen 8x5" , 1680,1050, 640, 400, 1 }, +{"WideScreen 8x5" , 1920,1200, 640, 400, 1 }, +{"WideScreen 8x5" , 2560,1600, 640, 400, 1 }, +{"WideScreen 8x5" , 3840,2400, 640, 400, 1 }, +{"WideScreen 14x9" , 840, 540, 640, 400, 1 }, +{"WideScreen 14x9" , 1680,1080, 640, 400, 1 }, +{"WideScreen 16x9" , 640, 360, 640, 360, 1 }, +{"WideScreen 16x9" , 683, 384, 683, 384, 1 }, +{"WideScreen 16x9" , 960, 540, 640, 360, 1 }, +{"WideScreen 16x9" , 1280, 720, 640, 360, 1 }, +{"WideScreen 16x9" , 1360, 768, 680, 384, 1 }, +{"WideScreen 16x9" , 1366, 768, 683, 384, 1 }, +{"WideScreen 16x9" , 1920,1080, 640, 360, 1 }, +{"WideScreen 16x9" , 2560,1440, 640, 360, 1 }, +{"WideScreen 16x9" , 3840,2160, 640, 360, 1 }, +{"NTSC 3x2" , 360, 240, 360, 240, 1.125 }, +{"NTSC 3x2" , 720, 480, 720, 480, 1.125 }, +{"PAL 14x11" , 360, 283, 360, 283, 0.9545}, +{"PAL 14x11" , 720, 566, 720, 566, 0.9545}, +{"NES 8x7" , 256, 224, 256, 224, 1.1667}, +{"SNES 8x7" , 512, 448, 512, 448, 1.1667}, +{NULL, 0, 0, 0, 0, 0} +}; +// this is the number of the default mode (640x480) in the list above +int video_resolutions_hardcoded_count = sizeof(video_resolutions_hardcoded) / sizeof(*video_resolutions_hardcoded) - 1; + +#define VIDEO_ITEMS 11 +static int video_cursor = 0; +static int video_cursor_table[VIDEO_ITEMS] = {68, 88, 96, 104, 112, 120, 128, 136, 144, 152, 168}; +static int menu_video_resolution; + +video_resolution_t *video_resolutions; +int video_resolutions_count; + +static video_resolution_t *menu_video_resolutions; +static int menu_video_resolutions_count; +static qboolean menu_video_resolutions_forfullscreen; + +static void M_Menu_Video_FindResolution(int w, int h, float a) +{ + int i; + + if(menu_video_resolutions_forfullscreen) + { + menu_video_resolutions = video_resolutions; + menu_video_resolutions_count = video_resolutions_count; + } + else + { + menu_video_resolutions = video_resolutions_hardcoded; + menu_video_resolutions_count = video_resolutions_hardcoded_count; + } + + // Look for the closest match to the current resolution + menu_video_resolution = 0; + for (i = 1;i < menu_video_resolutions_count;i++) + { + // if the new mode would be a worse match in width, skip it + if (abs(menu_video_resolutions[i].width - w) > abs(menu_video_resolutions[menu_video_resolution].width - w)) + continue; + // if it is equal in width, check height + if (menu_video_resolutions[i].width == w && menu_video_resolutions[menu_video_resolution].width == w) + { + // if the new mode would be a worse match in height, skip it + if (abs(menu_video_resolutions[i].height - h) > abs(menu_video_resolutions[menu_video_resolution].height - h)) + continue; + // if it is equal in width and height, check pixel aspect + if (menu_video_resolutions[i].height == h && menu_video_resolutions[menu_video_resolution].height == h) + { + // if the new mode would be a worse match in pixel aspect, skip it + if (abs(menu_video_resolutions[i].pixelheight - a) > abs(menu_video_resolutions[menu_video_resolution].pixelheight - a)) + continue; + // if it is equal in everything, skip it (prefer earlier modes) + if (menu_video_resolutions[i].pixelheight == a && menu_video_resolutions[menu_video_resolution].pixelheight == a) + continue; + // better match for width, height, and pixel aspect + menu_video_resolution = i; + } + else // better match for width and height + menu_video_resolution = i; + } + else // better match for width + menu_video_resolution = i; + } +} + +void M_Menu_Video_f (void) +{ + key_dest = key_menu; + m_state = m_video; + m_entersound = true; + + M_Menu_Video_FindResolution(vid.width, vid.height, vid_pixelheight.value); +} + + +static void M_Video_Draw (void) +{ + int t; + cachepic_t *p; + + if(!!vid_fullscreen.integer != menu_video_resolutions_forfullscreen) + { + video_resolution_t *res = &menu_video_resolutions[menu_video_resolution]; + menu_video_resolutions_forfullscreen = !!vid_fullscreen.integer; + M_Menu_Video_FindResolution(res->width, res->height, res->pixelheight); + } + + M_Background(320, 200); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/vidmodes"); + M_DrawPic((320-p->width)/2, 4, "gfx/vidmodes"); + + t = 0; + + // Current and Proposed Resolution + M_Print(16, video_cursor_table[t] - 12, " Current Resolution"); + if (vid_supportrefreshrate && vid.userefreshrate && vid.fullscreen) + M_Print(220, video_cursor_table[t] - 12, va("%dx%d %.2fhz", vid.width, vid.height, vid.refreshrate)); + else + M_Print(220, video_cursor_table[t] - 12, va("%dx%d", vid.width, vid.height)); + M_Print(16, video_cursor_table[t], " New Resolution"); + M_Print(220, video_cursor_table[t], va("%dx%d", menu_video_resolutions[menu_video_resolution].width, menu_video_resolutions[menu_video_resolution].height)); + M_Print(96, video_cursor_table[t] + 8, va("Type: %s", menu_video_resolutions[menu_video_resolution].type)); + t++; + + // Bits per pixel + M_Print(16, video_cursor_table[t], " Bits per pixel"); + M_Print(220, video_cursor_table[t], (vid_bitsperpixel.integer == 32) ? "32" : "16"); + t++; + + // Bits per pixel + M_Print(16, video_cursor_table[t], " Antialiasing"); + M_DrawSlider(220, video_cursor_table[t], vid_samples.value, 1, 32); + t++; + + // Refresh Rate + M_ItemPrint(16, video_cursor_table[t], " Use Refresh Rate", vid_supportrefreshrate); + M_DrawCheckbox(220, video_cursor_table[t], vid_userefreshrate.integer); + t++; + + // Refresh Rate + M_ItemPrint(16, video_cursor_table[t], " Refresh Rate", vid_supportrefreshrate && vid_userefreshrate.integer); + M_DrawSlider(220, video_cursor_table[t], vid_refreshrate.value, 50, 150); + t++; + + // Fullscreen + M_Print(16, video_cursor_table[t], " Fullscreen"); + M_DrawCheckbox(220, video_cursor_table[t], vid_fullscreen.integer); + t++; + + // Vertical Sync + M_ItemPrint(16, video_cursor_table[t], " Vertical Sync", true); + M_DrawCheckbox(220, video_cursor_table[t], vid_vsync.integer); + t++; + + M_ItemPrint(16, video_cursor_table[t], " Anisotropic Filter", vid.support.ext_texture_filter_anisotropic); + M_DrawSlider(220, video_cursor_table[t], gl_texture_anisotropy.integer, 1, vid.max_anisotropy); + t++; + + M_ItemPrint(16, video_cursor_table[t], " Texture Quality", true); + M_DrawSlider(220, video_cursor_table[t], gl_picmip.value, 3, 0); + t++; + + M_ItemPrint(16, video_cursor_table[t], " Texture Compression", vid.support.arb_texture_compression); + M_DrawCheckbox(220, video_cursor_table[t], gl_texturecompression.integer); + t++; + + // "Apply" button + M_Print(220, video_cursor_table[t], "Apply"); + t++; + + // Cursor + M_DrawCharacter(200, video_cursor_table[video_cursor], 12+((int)(realtime*4)&1)); +} + + +static void M_Menu_Video_AdjustSliders (int dir) +{ + int t; + + S_LocalSound ("sound/misc/menu3.wav"); + + t = 0; + if (video_cursor == t++) + { + // Resolution + int r; + for(r = 0;r < menu_video_resolutions_count;r++) + { + menu_video_resolution += dir; + if (menu_video_resolution >= menu_video_resolutions_count) + menu_video_resolution = 0; + if (menu_video_resolution < 0) + menu_video_resolution = menu_video_resolutions_count - 1; + if (menu_video_resolutions[menu_video_resolution].width >= vid_minwidth.integer && menu_video_resolutions[menu_video_resolution].height >= vid_minheight.integer) + break; + } + } + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_bitsperpixel, (vid_bitsperpixel.integer == 32) ? 16 : 32); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_samples, bound(1, vid_samples.value * (dir > 0 ? 2 : 0.5), 32)); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_userefreshrate, !vid_userefreshrate.integer); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_refreshrate, bound(50, vid_refreshrate.value + dir, 150)); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_fullscreen, !vid_fullscreen.integer); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_vsync, !vid_vsync.integer); + else if (video_cursor == t++) + Cvar_SetValueQuick (&gl_texture_anisotropy, bound(1, gl_texture_anisotropy.value * (dir < 0 ? 0.5 : 2.0), vid.max_anisotropy)); + else if (video_cursor == t++) + Cvar_SetValueQuick (&gl_picmip, bound(0, gl_picmip.value - dir, 3)); + else if (video_cursor == t++) + Cvar_SetValueQuick (&gl_texturecompression, !gl_texturecompression.integer); +} + + +static void M_Video_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + // vid_shared.c has a copy of the current video config. We restore it + Cvar_SetValueQuick(&vid_fullscreen, vid.fullscreen); + Cvar_SetValueQuick(&vid_bitsperpixel, vid.bitsperpixel); + Cvar_SetValueQuick(&vid_samples, vid.samples); + if (vid_supportrefreshrate) + Cvar_SetValueQuick(&vid_refreshrate, vid.refreshrate); + Cvar_SetValueQuick(&vid_userefreshrate, vid.userefreshrate); + + S_LocalSound ("sound/misc/menu1.wav"); + M_Menu_Options_f (); + break; + + case K_ENTER: + m_entersound = true; + switch (video_cursor) + { + case (VIDEO_ITEMS - 1): + Cvar_SetValueQuick (&vid_width, menu_video_resolutions[menu_video_resolution].width); + Cvar_SetValueQuick (&vid_height, menu_video_resolutions[menu_video_resolution].height); + Cvar_SetValueQuick (&vid_conwidth, menu_video_resolutions[menu_video_resolution].conwidth); + Cvar_SetValueQuick (&vid_conheight, menu_video_resolutions[menu_video_resolution].conheight); + Cvar_SetValueQuick (&vid_pixelheight, menu_video_resolutions[menu_video_resolution].pixelheight); + Cbuf_AddText ("vid_restart\n"); + M_Menu_Options_f (); + break; + default: + M_Menu_Video_AdjustSliders (1); + } + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + video_cursor--; + if (video_cursor < 0) + video_cursor = VIDEO_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + video_cursor++; + if (video_cursor >= VIDEO_ITEMS) + video_cursor = 0; + break; + + case K_LEFTARROW: + M_Menu_Video_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + M_Menu_Video_AdjustSliders (1); + break; + } +} + +//============================================================================= +/* HELP MENU */ + +static int help_page; +#define NUM_HELP_PAGES 6 + + +void M_Menu_Help_f (void) +{ + key_dest = key_menu; + m_state = m_help; + m_entersound = true; + help_page = 0; +} + + + +static void M_Help_Draw (void) +{ + M_Background(320, 200); + M_DrawPic (0, 0, va("gfx/help%i", help_page)); +} + + +static void M_Help_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_UPARROW: + case K_RIGHTARROW: + m_entersound = true; + if (++help_page >= NUM_HELP_PAGES) + help_page = 0; + break; + + case K_DOWNARROW: + case K_LEFTARROW: + m_entersound = true; + if (--help_page < 0) + help_page = NUM_HELP_PAGES-1; + break; + } + +} + +//============================================================================= +/* CEDITS MENU */ + +void M_Menu_Credits_f (void) +{ + key_dest = key_menu; + m_state = m_credits; + m_entersound = true; +} + + + +static void M_Credits_Draw (void) +{ + M_Background(640, 480); + M_DrawPic (0, 0, "gfx/creditsmiddle"); + M_Print (640/2 - 14/2*8, 236, "Coming soon..."); + M_DrawPic (0, 0, "gfx/creditstop"); + M_DrawPic (0, 433, "gfx/creditsbottom"); +} + + +static void M_Credits_Key (int key, int ascii) +{ + M_Menu_Main_f (); +} + +//============================================================================= +/* QUIT MENU */ + +static const char *m_quit_message[9]; +static int m_quit_prevstate; +static qboolean wasInMenus; + + +static int M_QuitMessage(const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6, const char *line7, const char *line8) +{ + m_quit_message[0] = line1; + m_quit_message[1] = line2; + m_quit_message[2] = line3; + m_quit_message[3] = line4; + m_quit_message[4] = line5; + m_quit_message[5] = line6; + m_quit_message[6] = line7; + m_quit_message[7] = line8; + m_quit_message[8] = NULL; + return 1; +} + +static int M_ChooseQuitMessage(int request) +{ + if (m_missingdata) + { + // frag related quit messages are pointless for a fallback menu, so use something generic + if (request-- == 0) return M_QuitMessage("Are you sure you want to quit?","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + return 0; + } + switch (gamemode) + { + case GAME_NORMAL: + case GAME_HIPNOTIC: + case GAME_ROGUE: + case GAME_NEHAHRA: + case GAME_DEFEATINDETAIL2: + if (request-- == 0) return M_QuitMessage("Are you gonna quit","this game just like","everything else?",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Milord, methinks that","thou art a lowly","quitter. Is this true?",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Do I need to bust your","face open for trying","to quit?",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Man, I oughta smack you","for trying to quit!","Press Y to get","smacked out.",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Press Y to quit like a","big loser in life.","Press N to stay proud","and successful!",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("If you press Y to","quit, I will summon","Satan all over your","hard drive!",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Um, Asmodeus dislikes","his children trying to","quit. Press Y to return","to your Tinkertoys.",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("If you quit now, I'll","throw a blanket-party","for you next time!",NULL,NULL,NULL,NULL,NULL); + break; + case GAME_GOODVSBAD2: + if (request-- == 0) return M_QuitMessage("Press Yes To Quit","...","Yes",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Do you really want to","Quit?","Play Good vs bad 3!",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("All your quit are","belong to long duck","dong",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Press Y to quit","","But are you too legit?",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("This game was made by","e@chip-web.com","It is by far the best","game ever made.",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Even I really dont","know of a game better","Press Y to quit","like rougue chedder",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("After you stop playing","tell the guys who made","counterstrike to just","kill themselves now",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Press Y to exit to DOS","","SSH login as user Y","to exit to Linux",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Press Y like you","were waanderers","from Ys'",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("This game was made in","Nippon like the SS","announcer's saying ipon",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("you","want to quit?",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Please stop playing","this stupid game",NULL,NULL,NULL,NULL,NULL,NULL); + break; + case GAME_BATTLEMECH: + if (request-- == 0) return M_QuitMessage("? WHY ?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Leave now and your mech is scrap!","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Accept Defeat?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Wait! There are more mechs to destroy!","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Where's your bloodlust?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Your mech here is way more impressive","than your car out there...","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Quitting won't reduce your debt","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + break; + case GAME_OPENQUARTZ: + if (request-- == 0) return M_QuitMessage("There is nothing like free beer!","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("GNU is not Unix!","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("You prefer free beer over free speech?","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Is OpenQuartz Propaganda?","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + break; + default: + if (request-- == 0) return M_QuitMessage("Tired of fragging already?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Quit now and forfeit your bodycount?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Are you sure you want to quit?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Off to do something constructive?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); + break; + } + return 0; +} + +void M_Menu_Quit_f (void) +{ + int n; + if (m_state == m_quit) + return; + wasInMenus = (key_dest == key_menu || key_dest == key_menu_grabbed); + key_dest = key_menu; + m_quit_prevstate = m_state; + m_state = m_quit; + m_entersound = true; + // count how many there are + for (n = 1;M_ChooseQuitMessage(n);n++); + // choose one + M_ChooseQuitMessage(rand() % n); +} + + +static void M_Quit_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + case 'n': + case 'N': + if (wasInMenus) + { + m_state = (enum m_state_e)m_quit_prevstate; + m_entersound = true; + } + else + { + key_dest = key_game; + m_state = m_none; + } + break; + + case 'Y': + case 'y': + Host_Quit_f (); + break; + + default: + break; + } +} + +static void M_Quit_Draw (void) +{ + int i, l, linelength, firstline, lastline, lines; + for (i = 0, linelength = 0, firstline = 9999, lastline = -1;m_quit_message[i];i++) + { + if ((l = (int)strlen(m_quit_message[i]))) + { + if (firstline > i) + firstline = i; + if (lastline < i) + lastline = i; + if (linelength < l) + linelength = l; + } + } + lines = (lastline - firstline) + 1; + M_Background(linelength * 8 + 16, lines * 8 + 16); + if (!m_missingdata) //since this is a fallback menu for missing data, it is very hard to read with the box + M_DrawTextBox(0, 0, linelength, lines); //this is less obtrusive than hacking up the M_DrawTextBox function + for (i = 0, l = firstline;i < lines;i++, l++) + M_Print(8 + 4 * (linelength - strlen(m_quit_message[l])), 8 + 8 * i, m_quit_message[l]); +} + +//============================================================================= +/* LAN CONFIG MENU */ + +static int lanConfig_cursor = -1; +static int lanConfig_cursor_table [] = {56, 76, 84, 120}; +#define NUM_LANCONFIG_CMDS 4 + +static int lanConfig_port; +static char lanConfig_portname[6]; +static char lanConfig_joinname[40]; + +void M_Menu_LanConfig_f (void) +{ + key_dest = key_menu; + m_state = m_lanconfig; + m_entersound = true; + if (lanConfig_cursor == -1) + { + if (JoiningGame) + lanConfig_cursor = 1; + } + if (StartingGame) + lanConfig_cursor = 1; + lanConfig_port = 26000; + dpsnprintf(lanConfig_portname, sizeof(lanConfig_portname), "%u", (unsigned int) lanConfig_port); + + M_Update_Return_Reason(""); +} + + +static void M_LanConfig_Draw (void) +{ + cachepic_t *p; + int basex; + const char *startJoin; + const char *protocol; + + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_multi"); + basex = (320-p->width)/2; + M_DrawPic (basex, 4, "gfx/p_multi"); + + if (StartingGame) + startJoin = "New Game"; + else + startJoin = "Join Game"; + protocol = "TCP/IP"; + M_Print(basex, 32, va ("%s - %s", startJoin, protocol)); + basex += 8; + + M_Print(basex, lanConfig_cursor_table[0], "Port"); + M_DrawTextBox (basex+8*8, lanConfig_cursor_table[0]-8, sizeof(lanConfig_portname), 1); + M_Print(basex+9*8, lanConfig_cursor_table[0], lanConfig_portname); + + if (JoiningGame) + { + M_Print(basex, lanConfig_cursor_table[1], "Search for DarkPlaces games..."); + M_Print(basex, lanConfig_cursor_table[2], "Search for QuakeWorld games..."); + M_Print(basex, lanConfig_cursor_table[3]-16, "Join game at:"); + M_DrawTextBox (basex+8, lanConfig_cursor_table[3]-8, sizeof(lanConfig_joinname), 1); + M_Print(basex+16, lanConfig_cursor_table[3], lanConfig_joinname); + } + else + { + M_DrawTextBox (basex, lanConfig_cursor_table[1]-8, 2, 1); + M_Print(basex+8, lanConfig_cursor_table[1], "OK"); + } + + M_DrawCharacter (basex-8, lanConfig_cursor_table [lanConfig_cursor], 12+((int)(realtime*4)&1)); + + if (lanConfig_cursor == 0) + M_DrawCharacter (basex+9*8 + 8*strlen(lanConfig_portname), lanConfig_cursor_table [lanConfig_cursor], 10+((int)(realtime*4)&1)); + + if (lanConfig_cursor == 3) + M_DrawCharacter (basex+16 + 8*strlen(lanConfig_joinname), lanConfig_cursor_table [lanConfig_cursor], 10+((int)(realtime*4)&1)); + + if (*m_return_reason) + M_Print(basex, 168, m_return_reason); +} + + +static void M_LanConfig_Key (int key, int ascii) +{ + int l; + + switch (key) + { + case K_ESCAPE: + M_Menu_MultiPlayer_f (); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + lanConfig_cursor--; + if (lanConfig_cursor < 0) + lanConfig_cursor = NUM_LANCONFIG_CMDS-1; + // when in start game menu, skip the unused search qw servers item + if (StartingGame && lanConfig_cursor == 2) + lanConfig_cursor = 1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + lanConfig_cursor++; + if (lanConfig_cursor >= NUM_LANCONFIG_CMDS) + lanConfig_cursor = 0; + // when in start game menu, skip the unused search qw servers item + if (StartingGame && lanConfig_cursor == 1) + lanConfig_cursor = 2; + break; + + case K_ENTER: + if (lanConfig_cursor == 0) + break; + + m_entersound = true; + + Cbuf_AddText ("stopdemo\n"); + + Cvar_SetValue("port", lanConfig_port); + + if (lanConfig_cursor == 1 || lanConfig_cursor == 2) + { + if (StartingGame) + { + M_Menu_GameOptions_f (); + break; + } + M_Menu_ServerList_f(); + break; + } + + if (lanConfig_cursor == 3) + Cbuf_AddText ( va ("connect \"%s\"\n", lanConfig_joinname) ); + break; + + case K_BACKSPACE: + if (lanConfig_cursor == 0) + { + if (strlen(lanConfig_portname)) + lanConfig_portname[strlen(lanConfig_portname)-1] = 0; + } + + if (lanConfig_cursor == 3) + { + if (strlen(lanConfig_joinname)) + lanConfig_joinname[strlen(lanConfig_joinname)-1] = 0; + } + break; + + default: + if (ascii < 32) + break; + + if (lanConfig_cursor == 3) + { + l = (int)strlen(lanConfig_joinname); + if (l < (int)sizeof(lanConfig_joinname) - 1) + { + lanConfig_joinname[l+1] = 0; + lanConfig_joinname[l] = ascii; + } + } + + if (ascii < '0' || ascii > '9') + break; + if (lanConfig_cursor == 0) + { + l = (int)strlen(lanConfig_portname); + if (l < (int)sizeof(lanConfig_portname) - 1) + { + lanConfig_portname[l+1] = 0; + lanConfig_portname[l] = ascii; + } + } + } + + if (StartingGame && lanConfig_cursor == 3) + { + if (key == K_UPARROW) + lanConfig_cursor = 1; + else + lanConfig_cursor = 0; + } + + l = atoi(lanConfig_portname); + if (l <= 65535) + lanConfig_port = l; + dpsnprintf(lanConfig_portname, sizeof(lanConfig_portname), "%u", (unsigned int) lanConfig_port); +} + +//============================================================================= +/* GAME OPTIONS MENU */ + +typedef struct level_s +{ + const char *name; + const char *description; +} level_t; + +typedef struct episode_s +{ + const char *description; + int firstLevel; + int levels; +} episode_t; + +typedef struct gamelevels_s +{ + const char *gamename; + level_t *levels; + episode_t *episodes; + int numepisodes; +} +gamelevels_t; + +static level_t quakelevels[] = +{ + {"start", "Entrance"}, // 0 + + {"e1m1", "Slipgate Complex"}, // 1 + {"e1m2", "Castle of the Damned"}, + {"e1m3", "The Necropolis"}, + {"e1m4", "The Grisly Grotto"}, + {"e1m5", "Gloom Keep"}, + {"e1m6", "The Door To Chthon"}, + {"e1m7", "The House of Chthon"}, + {"e1m8", "Ziggurat Vertigo"}, + + {"e2m1", "The Installation"}, // 9 + {"e2m2", "Ogre Citadel"}, + {"e2m3", "Crypt of Decay"}, + {"e2m4", "The Ebon Fortress"}, + {"e2m5", "The Wizard's Manse"}, + {"e2m6", "The Dismal Oubliette"}, + {"e2m7", "Underearth"}, + + {"e3m1", "Termination Central"}, // 16 + {"e3m2", "The Vaults of Zin"}, + {"e3m3", "The Tomb of Terror"}, + {"e3m4", "Satan's Dark Delight"}, + {"e3m5", "Wind Tunnels"}, + {"e3m6", "Chambers of Torment"}, + {"e3m7", "The Haunted Halls"}, + + {"e4m1", "The Sewage System"}, // 23 + {"e4m2", "The Tower of Despair"}, + {"e4m3", "The Elder God Shrine"}, + {"e4m4", "The Palace of Hate"}, + {"e4m5", "Hell's Atrium"}, + {"e4m6", "The Pain Maze"}, + {"e4m7", "Azure Agony"}, + {"e4m8", "The Nameless City"}, + + {"end", "Shub-Niggurath's Pit"}, // 31 + + {"dm1", "Place of Two Deaths"}, // 32 + {"dm2", "Claustrophobopolis"}, + {"dm3", "The Abandoned Base"}, + {"dm4", "The Bad Place"}, + {"dm5", "The Cistern"}, + {"dm6", "The Dark Zone"} +}; + +static episode_t quakeepisodes[] = +{ + {"Welcome to Quake", 0, 1}, + {"Doomed Dimension", 1, 8}, + {"Realm of Black Magic", 9, 7}, + {"Netherworld", 16, 7}, + {"The Elder World", 23, 8}, + {"Final Level", 31, 1}, + {"Deathmatch Arena", 32, 6} +}; + + //MED 01/06/97 added hipnotic levels +static level_t hipnoticlevels[] = +{ + {"start", "Command HQ"}, // 0 + + {"hip1m1", "The Pumping Station"}, // 1 + {"hip1m2", "Storage Facility"}, + {"hip1m3", "The Lost Mine"}, + {"hip1m4", "Research Facility"}, + {"hip1m5", "Military Complex"}, + + {"hip2m1", "Ancient Realms"}, // 6 + {"hip2m2", "The Black Cathedral"}, + {"hip2m3", "The Catacombs"}, + {"hip2m4", "The Crypt"}, + {"hip2m5", "Mortum's Keep"}, + {"hip2m6", "The Gremlin's Domain"}, + + {"hip3m1", "Tur Torment"}, // 12 + {"hip3m2", "Pandemonium"}, + {"hip3m3", "Limbo"}, + {"hip3m4", "The Gauntlet"}, + + {"hipend", "Armagon's Lair"}, // 16 + + {"hipdm1", "The Edge of Oblivion"} // 17 +}; + +//MED 01/06/97 added hipnotic episodes +static episode_t hipnoticepisodes[] = +{ + {"Scourge of Armagon", 0, 1}, + {"Fortress of the Dead", 1, 5}, + {"Dominion of Darkness", 6, 6}, + {"The Rift", 12, 4}, + {"Final Level", 16, 1}, + {"Deathmatch Arena", 17, 1} +}; + +//PGM 01/07/97 added rogue levels +//PGM 03/02/97 added dmatch level +static level_t roguelevels[] = +{ + {"start", "Split Decision"}, + {"r1m1", "Deviant's Domain"}, + {"r1m2", "Dread Portal"}, + {"r1m3", "Judgement Call"}, + {"r1m4", "Cave of Death"}, + {"r1m5", "Towers of Wrath"}, + {"r1m6", "Temple of Pain"}, + {"r1m7", "Tomb of the Overlord"}, + {"r2m1", "Tempus Fugit"}, + {"r2m2", "Elemental Fury I"}, + {"r2m3", "Elemental Fury II"}, + {"r2m4", "Curse of Osiris"}, + {"r2m5", "Wizard's Keep"}, + {"r2m6", "Blood Sacrifice"}, + {"r2m7", "Last Bastion"}, + {"r2m8", "Source of Evil"}, + {"ctf1", "Division of Change"} +}; + +//PGM 01/07/97 added rogue episodes +//PGM 03/02/97 added dmatch episode +static episode_t rogueepisodes[] = +{ + {"Introduction", 0, 1}, + {"Hell's Fortress", 1, 7}, + {"Corridors of Time", 8, 8}, + {"Deathmatch Arena", 16, 1} +}; + +static level_t nehahralevels[] = +{ + {"nehstart", "Welcome to Nehahra"}, + {"neh1m1", "Forge City1: Slipgates"}, + {"neh1m2", "Forge City2: Boiler"}, + {"neh1m3", "Forge City3: Escape"}, + {"neh1m4", "Grind Core"}, + {"neh1m5", "Industrial Silence"}, + {"neh1m6", "Locked-Up Anger"}, + {"neh1m7", "Wanderer of the Wastes"}, + {"neh1m8", "Artemis System Net"}, + {"neh1m9", "To the Death"}, + {"neh2m1", "The Gates of Ghoro"}, + {"neh2m2", "Sacred Trinity"}, + {"neh2m3", "Realm of the Ancients"}, + {"neh2m4", "Temple of the Ancients"}, + {"neh2m5", "Dreams Made Flesh"}, + {"neh2m6", "Your Last Cup of Sorrow"}, + {"nehsec", "Ogre's Bane"}, + {"nehahra", "Nehahra's Den"}, + {"nehend", "Quintessence"} +}; + +static episode_t nehahraepisodes[] = +{ + {"Welcome to Nehahra", 0, 1}, + {"The Fall of Forge", 1, 9}, + {"The Outlands", 10, 7}, + {"Dimension of the Lost", 17, 2} +}; + +// Map list for Transfusion +static level_t transfusionlevels[] = +{ + {"e1m1", "Cradle to Grave"}, + {"e1m2", "Wrong Side of the Tracks"}, + {"e1m3", "Phantom Express"}, + {"e1m4", "Dark Carnival"}, + {"e1m5", "Hallowed Grounds"}, + {"e1m6", "The Great Temple"}, + {"e1m7", "Altar of Stone"}, + {"e1m8", "House of Horrors"}, + + {"e2m1", "Shipwrecked"}, + {"e2m2", "The Lumber Mill"}, + {"e2m3", "Rest for the Wicked"}, + {"e2m4", "The Overlooked Hotel"}, + {"e2m5", "The Haunting"}, + {"e2m6", "The Cold Rush"}, + {"e2m7", "Bowels of the Earth"}, + {"e2m8", "The Lair of Shial"}, + {"e2m9", "Thin Ice"}, + + {"e3m1", "Ghost Town"}, + {"e3m2", "The Siege"}, + {"e3m3", "Raw Sewage"}, + {"e3m4", "The Sick Ward"}, + {"e3m5", "Spare Parts"}, + {"e3m6", "Monster Bait"}, + {"e3m7", "The Pit of Cerberus"}, + {"e3m8", "Catacombs"}, + + {"e4m1", "Butchery Loves Company"}, + {"e4m2", "Breeding Grounds"}, + {"e4m3", "Charnel House"}, + {"e4m4", "Crystal Lake"}, + {"e4m5", "Fire and Brimstone"}, + {"e4m6", "The Ganglion Depths"}, + {"e4m7", "In the Flesh"}, + {"e4m8", "The Hall of the Epiphany"}, + {"e4m9", "Mall of the Dead"}, + + {"bb1", "The Stronghold"}, + {"bb2", "Winter Wonderland"}, + {"bb3", "Bodies"}, + {"bb4", "The Tower"}, + {"bb5", "Click!"}, + {"bb6", "Twin Fortress"}, + {"bb7", "Midgard"}, + {"bb8", "Fun With Heads"}, + {"dm1", "Monolith Building 11"}, + {"dm2", "Power!"}, + {"dm3", "Area 15"}, + + {"e6m1", "Welcome to Your Life"}, + {"e6m2", "They Are Here"}, + {"e6m3", "Public Storage"}, + {"e6m4", "Aqueducts"}, + {"e6m5", "The Ruined Temple"}, + {"e6m6", "Forbidden Rituals"}, + {"e6m7", "The Dungeon"}, + {"e6m8", "Beauty and the Beast"}, + {"e6m9", "Forgotten Catacombs"}, + + {"cp01", "Boat Docks"}, + {"cp02", "Old Opera House"}, + {"cp03", "Gothic Library"}, + {"cp04", "Lost Monastery"}, + {"cp05", "Steamboat"}, + {"cp06", "Graveyard"}, + {"cp07", "Mountain Pass"}, + {"cp08", "Abysmal Mine"}, + {"cp09", "Castle"}, + {"cps1", "Boggy Creek"}, + + {"cpbb01", "Crypt of Despair"}, + {"cpbb02", "Pits of Blood"}, + {"cpbb03", "Unholy Cathedral"}, + {"cpbb04", "Deadly Inspirations"}, + + {"b2a15", "Area 15 (B2)"}, + {"b2bodies", "BB_Bodies (B2)"}, + {"b2cabana", "BB_Cabana"}, + {"b2power", "BB_Power"}, + {"barena", "Blood Arena"}, + {"bkeep", "Blood Keep"}, + {"bstar", "Brown Star"}, + {"crypt", "The Crypt"}, + + {"bb3_2k1", "Bodies Infusion"}, + {"captasao", "Captasao"}, + {"curandero", "Curandero"}, + {"dcamp", "DeathCamp"}, + {"highnoon", "HighNoon"}, + {"qbb1", "The Confluence"}, + {"qbb2", "KathartiK"}, + {"qbb3", "Caleb's Woodland Retreat"}, + {"zoo", "Zoo"}, + + {"dranzbb6", "Black Coffee"}, + {"fragm", "Frag'M"}, + {"maim", "Maim"}, + {"qe1m7", "The House of Chthon"}, + {"qdm1", "Place of Two Deaths"}, + {"qdm4", "The Bad Place"}, + {"qdm5", "The Cistern"}, + {"qmorbias", "DM-Morbias"}, + {"simple", "Dead Simple"} +}; + +static episode_t transfusionepisodes[] = +{ + {"The Way of All Flesh", 0, 8}, + {"Even Death May Die", 8, 9}, + {"Farewell to Arms", 17, 8}, + {"Dead Reckoning", 25, 9}, + {"BloodBath", 34, 11}, + {"Post Mortem", 45, 9}, + {"Cryptic Passage", 54, 10}, + {"Cryptic BloodBath", 64, 4}, + {"Blood 2", 68, 8}, + {"Transfusion", 76, 9}, + {"Conversions", 85, 9} +}; + +static level_t goodvsbad2levels[] = +{ + {"rts", "Many Paths"}, // 0 + {"chess", "Chess, Scott Hess"}, // 1 + {"dot", "Big Wall"}, + {"city2", "The Big City"}, + {"bwall", "0 G like Psychic TV"}, + {"snow", "Wireframed"}, + {"telep", "Infinite Falling"}, + {"faces", "Facing Bases"}, + {"island", "Adventure Islands"}, +}; + +static episode_t goodvsbad2episodes[] = +{ + {"Levels? Bevels!", 0, 8}, +}; + +static level_t battlemechlevels[] = +{ + {"start", "Parking Level"}, + {"dm1", "Hot Dump"}, // 1 + {"dm2", "The Pits"}, + {"dm3", "Dimber Died"}, + {"dm4", "Fire in the Hole"}, + {"dm5", "Clubhouses"}, + {"dm6", "Army go Underground"}, +}; + +static episode_t battlemechepisodes[] = +{ + {"Time for Battle", 0, 7}, +}; + +static level_t openquartzlevels[] = +{ + {"start", "Welcome to Openquartz"}, + + {"void1", "The center of nowhere"}, // 1 + {"void2", "The place with no name"}, + {"void3", "The lost supply base"}, + {"void4", "Past the outer limits"}, + {"void5", "Into the nonexistance"}, + {"void6", "Void walk"}, + + {"vtest", "Warp Central"}, + {"box", "The deathmatch box"}, + {"bunkers", "Void command"}, + {"house", "House of chaos"}, + {"office", "Overnight office kill"}, + {"am1", "The nameless chambers"}, +}; + +static episode_t openquartzepisodes[] = +{ + {"Single Player", 0, 1}, + {"Void Deathmatch", 1, 6}, + {"Contrib", 7, 6}, +}; + +static level_t defeatindetail2levels[] = +{ + {"atac3", "River Crossing"}, + {"atac4", "Canyon Chaos"}, + {"atac7", "Desert Stormer"}, +}; + +static episode_t defeatindetail2episodes[] = +{ + {"ATAC Campaign", 0, 3}, +}; + +static level_t prydonlevels[] = +{ + {"curig2", "Capel Curig"}, // 0 + + {"tdastart", "Gateway"}, // 1 +}; + +static episode_t prydonepisodes[] = +{ + {"Prydon Gate", 0, 1}, + {"The Dark Age", 1, 1} +}; + +static gamelevels_t sharewarequakegame = {"Shareware Quake", quakelevels, quakeepisodes, 2}; +static gamelevels_t registeredquakegame = {"Quake", quakelevels, quakeepisodes, 7}; +static gamelevels_t hipnoticgame = {"Scourge of Armagon", hipnoticlevels, hipnoticepisodes, 6}; +static gamelevels_t roguegame = {"Dissolution of Eternity", roguelevels, rogueepisodes, 4}; +static gamelevels_t nehahragame = {"Nehahra", nehahralevels, nehahraepisodes, 4}; +static gamelevels_t transfusiongame = {"Transfusion", transfusionlevels, transfusionepisodes, 11}; +static gamelevels_t goodvsbad2game = {"Good Vs. Bad 2", goodvsbad2levels, goodvsbad2episodes, 1}; +static gamelevels_t battlemechgame = {"Battlemech", battlemechlevels, battlemechepisodes, 1}; +static gamelevels_t openquartzgame = {"OpenQuartz", openquartzlevels, openquartzepisodes, 3}; +static gamelevels_t defeatindetail2game = {"Defeat In Detail 2", defeatindetail2levels, defeatindetail2episodes, 1}; +static gamelevels_t prydongame = {"Prydon Gate", prydonlevels, prydonepisodes, 2}; + +typedef struct gameinfo_s +{ + gamemode_t gameid; + gamelevels_t *notregistered; + gamelevels_t *registered; +} +gameinfo_t; + +static gameinfo_t gamelist[] = +{ + {GAME_NORMAL, &sharewarequakegame, ®isteredquakegame}, + {GAME_HIPNOTIC, &hipnoticgame, &hipnoticgame}, + {GAME_ROGUE, &roguegame, &roguegame}, + {GAME_NEHAHRA, &nehahragame, &nehahragame}, + {GAME_TRANSFUSION, &transfusiongame, &transfusiongame}, + {GAME_GOODVSBAD2, &goodvsbad2game, &goodvsbad2game}, + {GAME_BATTLEMECH, &battlemechgame, &battlemechgame}, + {GAME_OPENQUARTZ, &openquartzgame, &openquartzgame}, + {GAME_DEFEATINDETAIL2, &defeatindetail2game, &defeatindetail2game}, + {GAME_PRYDON, &prydongame, &prydongame}, +}; + +static gamelevels_t *gameoptions_levels = NULL; + +static int startepisode; +static int startlevel; +static int maxplayers; +static qboolean m_serverInfoMessage = false; +static double m_serverInfoMessageTime; + +void M_Menu_GameOptions_f (void) +{ + int i; + key_dest = key_menu; + m_state = m_gameoptions; + m_entersound = true; + if (maxplayers == 0) + maxplayers = svs.maxclients; + if (maxplayers < 2) + maxplayers = min(8, MAX_SCOREBOARD); + // pick game level list based on gamemode (use GAME_NORMAL if no matches) + gameoptions_levels = registered.integer ? gamelist[0].registered : gamelist[0].notregistered; + for (i = 0;i < (int)(sizeof(gamelist)/sizeof(gamelist[0]));i++) + if (gamelist[i].gameid == gamemode) + gameoptions_levels = registered.integer ? gamelist[i].registered : gamelist[i].notregistered; +} + + +static int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 104, 112, 140, 160, 168}; +#define NUM_GAMEOPTIONS 12 +static int gameoptions_cursor; + +void M_GameOptions_Draw (void) +{ + cachepic_t *p; + int x; + + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_multi"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); + + M_DrawTextBox (152, 32, 10, 1); + M_Print(160, 40, "begin game"); + + M_Print(0, 56, " Max players"); + M_Print(160, 56, va("%i", maxplayers) ); + + if (gamemode != GAME_GOODVSBAD2) + { + M_Print(0, 64, " Game Type"); + if (gamemode == GAME_TRANSFUSION) + { + if (!coop.integer && !deathmatch.integer) + Cvar_SetValue("deathmatch", 1); + if (deathmatch.integer == 0) + M_Print(160, 64, "Cooperative"); + else if (deathmatch.integer == 2) + M_Print(160, 64, "Capture the Flag"); + else + M_Print(160, 64, "Blood Bath"); + } + else if (gamemode == GAME_BATTLEMECH) + { + if (!deathmatch.integer) + Cvar_SetValue("deathmatch", 1); + if (deathmatch.integer == 2) + M_Print(160, 64, "Rambo Match"); + else + M_Print(160, 64, "Deathmatch"); + } + else + { + if (!coop.integer && !deathmatch.integer) + Cvar_SetValue("deathmatch", 1); + if (coop.integer) + M_Print(160, 64, "Cooperative"); + else + M_Print(160, 64, "Deathmatch"); + } + + M_Print(0, 72, " Teamplay"); + if (gamemode == GAME_ROGUE) + { + const char *msg; + + switch((int)teamplay.integer) + { + case 1: msg = "No Friendly Fire"; break; + case 2: msg = "Friendly Fire"; break; + case 3: msg = "Tag"; break; + case 4: msg = "Capture the Flag"; break; + case 5: msg = "One Flag CTF"; break; + case 6: msg = "Three Team CTF"; break; + default: msg = "Off"; break; + } + M_Print(160, 72, msg); + } + else + { + const char *msg; + + switch (teamplay.integer) + { + case 0: msg = "Off"; break; + case 2: msg = "Friendly Fire"; break; + default: msg = "No Friendly Fire"; break; + } + M_Print(160, 72, msg); + } + M_Print(0, 80, " Skill"); + if (gamemode == GAME_TRANSFUSION) + { + if (skill.integer == 1) + M_Print(160, 80, "Still Kicking"); + else if (skill.integer == 2) + M_Print(160, 80, "Pink On The Inside"); + else if (skill.integer == 3) + M_Print(160, 80, "Lightly Broiled"); + else if (skill.integer == 4) + M_Print(160, 80, "Well Done"); + else + M_Print(160, 80, "Extra Crispy"); + } + else + { + if (skill.integer == 0) + M_Print(160, 80, "Easy difficulty"); + else if (skill.integer == 1) + M_Print(160, 80, "Normal difficulty"); + else if (skill.integer == 2) + M_Print(160, 80, "Hard difficulty"); + else + M_Print(160, 80, "Nightmare difficulty"); + } + M_Print(0, 88, " Frag Limit"); + if (fraglimit.integer == 0) + M_Print(160, 88, "none"); + else + M_Print(160, 88, va("%i frags", fraglimit.integer)); + + M_Print(0, 96, " Time Limit"); + if (timelimit.integer == 0) + M_Print(160, 96, "none"); + else + M_Print(160, 96, va("%i minutes", timelimit.integer)); + } + + M_Print(0, 104, " Public server"); + M_Print(160, 104, (sv_public.integer == 0) ? "no" : "yes"); + + M_Print(0, 112, " Server maxrate"); + M_Print(160, 112, va("%i", sv_maxrate.integer)); + + M_Print(0, 128, " Server name"); + M_DrawTextBox (0, 132, 38, 1); + M_Print(8, 140, hostname.string); + + if (gamemode != GAME_GOODVSBAD2) + { + M_Print(0, 160, " Episode"); + M_Print(160, 160, gameoptions_levels->episodes[startepisode].description); + } + + M_Print(0, 168, " Level"); + M_Print(160, 168, gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].description); + M_Print(160, 176, gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].name); + +// line cursor + if (gameoptions_cursor == 9) + M_DrawCharacter (8 + 8 * strlen(hostname.string), gameoptions_cursor_table[gameoptions_cursor], 10+((int)(realtime*4)&1)); + else + M_DrawCharacter (144, gameoptions_cursor_table[gameoptions_cursor], 12+((int)(realtime*4)&1)); + + if (m_serverInfoMessage) + { + if ((realtime - m_serverInfoMessageTime) < 5.0) + { + x = (320-26*8)/2; + M_DrawTextBox (x, 138, 24, 4); + x += 8; + M_Print(x, 146, " More than 255 players??"); + M_Print(x, 154, " First, question your "); + M_Print(x, 162, " sanity, then email "); + M_Print(x, 170, " lordhavoc@ghdigital.com"); + } + else + m_serverInfoMessage = false; + } +} + + +static void M_NetStart_Change (int dir) +{ + int count; + + switch (gameoptions_cursor) + { + case 1: + maxplayers += dir; + if (maxplayers > MAX_SCOREBOARD) + { + maxplayers = MAX_SCOREBOARD; + m_serverInfoMessage = true; + m_serverInfoMessageTime = realtime; + } + if (maxplayers < 2) + maxplayers = 2; + break; + + case 2: + if (gamemode == GAME_GOODVSBAD2) + break; + if (gamemode == GAME_TRANSFUSION) + { + switch (deathmatch.integer) + { + // From Cooperative to BloodBath + case 0: + Cvar_SetValueQuick (&coop, 0); + Cvar_SetValueQuick (&deathmatch, 1); + break; + + // From BloodBath to CTF + case 1: + Cvar_SetValueQuick (&coop, 0); + Cvar_SetValueQuick (&deathmatch, 2); + break; + + // From CTF to Cooperative + //case 2: + default: + Cvar_SetValueQuick (&coop, 1); + Cvar_SetValueQuick (&deathmatch, 0); + } + } + else if (gamemode == GAME_BATTLEMECH) + { + if (deathmatch.integer == 2) // changing from Rambo to Deathmatch + Cvar_SetValueQuick (&deathmatch, 0); + else // changing from Deathmatch to Rambo + Cvar_SetValueQuick (&deathmatch, 2); + } + else + { + if (deathmatch.integer) // changing from deathmatch to coop + { + Cvar_SetValueQuick (&coop, 1); + Cvar_SetValueQuick (&deathmatch, 0); + } + else // changing from coop to deathmatch + { + Cvar_SetValueQuick (&coop, 0); + Cvar_SetValueQuick (&deathmatch, 1); + } + } + break; + + case 3: + if (gamemode == GAME_GOODVSBAD2) + break; + if (gamemode == GAME_ROGUE) + count = 6; + else + count = 2; + + Cvar_SetValueQuick (&teamplay, teamplay.integer + dir); + if (teamplay.integer > count) + Cvar_SetValueQuick (&teamplay, 0); + else if (teamplay.integer < 0) + Cvar_SetValueQuick (&teamplay, count); + break; + + case 4: + if (gamemode == GAME_GOODVSBAD2) + break; + Cvar_SetValueQuick (&skill, skill.integer + dir); + if (gamemode == GAME_TRANSFUSION) + { + if (skill.integer > 5) + Cvar_SetValueQuick (&skill, 1); + if (skill.integer < 1) + Cvar_SetValueQuick (&skill, 5); + } + else + { + if (skill.integer > 3) + Cvar_SetValueQuick (&skill, 0); + if (skill.integer < 0) + Cvar_SetValueQuick (&skill, 3); + } + break; + + case 5: + if (gamemode == GAME_GOODVSBAD2) + break; + Cvar_SetValueQuick (&fraglimit, fraglimit.integer + dir*10); + if (fraglimit.integer > 100) + Cvar_SetValueQuick (&fraglimit, 0); + if (fraglimit.integer < 0) + Cvar_SetValueQuick (&fraglimit, 100); + break; + + case 6: + if (gamemode == GAME_GOODVSBAD2) + break; + Cvar_SetValueQuick (&timelimit, timelimit.value + dir*5); + if (timelimit.value > 60) + Cvar_SetValueQuick (&timelimit, 0); + if (timelimit.value < 0) + Cvar_SetValueQuick (&timelimit, 60); + break; + + case 7: + Cvar_SetValueQuick (&sv_public, !sv_public.integer); + break; + + case 8: + Cvar_SetValueQuick (&sv_maxrate, sv_maxrate.integer + dir*500); + if (sv_maxrate.integer < NET_MINRATE) + Cvar_SetValueQuick (&sv_maxrate, NET_MINRATE); + break; + + case 9: + break; + + case 10: + if (gamemode == GAME_GOODVSBAD2) + break; + startepisode += dir; + + if (startepisode < 0) + startepisode = gameoptions_levels->numepisodes - 1; + + if (startepisode >= gameoptions_levels->numepisodes) + startepisode = 0; + + startlevel = 0; + break; + + case 11: + startlevel += dir; + + if (startlevel < 0) + startlevel = gameoptions_levels->episodes[startepisode].levels - 1; + + if (startlevel >= gameoptions_levels->episodes[startepisode].levels) + startlevel = 0; + break; + } +} + +static void M_GameOptions_Key (int key, int ascii) +{ + int l; + char hostnamebuf[128]; + + switch (key) + { + case K_ESCAPE: + M_Menu_MultiPlayer_f (); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + gameoptions_cursor--; + if (gameoptions_cursor < 0) + gameoptions_cursor = NUM_GAMEOPTIONS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + gameoptions_cursor++; + if (gameoptions_cursor >= NUM_GAMEOPTIONS) + gameoptions_cursor = 0; + break; + + case K_LEFTARROW: + if (gameoptions_cursor == 0) + break; + S_LocalSound ("sound/misc/menu3.wav"); + M_NetStart_Change (-1); + break; + + case K_RIGHTARROW: + if (gameoptions_cursor == 0) + break; + S_LocalSound ("sound/misc/menu3.wav"); + M_NetStart_Change (1); + break; + + case K_ENTER: + S_LocalSound ("sound/misc/menu2.wav"); + if (gameoptions_cursor == 0) + { + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ( va ("maxplayers %u\n", maxplayers) ); + + Cbuf_AddText ( va ("map %s\n", gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].name) ); + return; + } + + M_NetStart_Change (1); + break; + + case K_BACKSPACE: + if (gameoptions_cursor == 9) + { + l = (int)strlen(hostname.string); + if (l) + { + l = min(l - 1, 37); + memcpy(hostnamebuf, hostname.string, l); + hostnamebuf[l] = 0; + Cvar_Set("hostname", hostnamebuf); + } + } + break; + + default: + if (ascii < 32) + break; + if (gameoptions_cursor == 9) + { + l = (int)strlen(hostname.string); + if (l < 37) + { + memcpy(hostnamebuf, hostname.string, l); + hostnamebuf[l] = ascii; + hostnamebuf[l+1] = 0; + Cvar_Set("hostname", hostnamebuf); + } + } + } +} + +//============================================================================= +/* SLIST MENU */ + +static int slist_cursor; + +void M_Menu_ServerList_f (void) +{ + key_dest = key_menu; + m_state = m_slist; + m_entersound = true; + slist_cursor = 0; + M_Update_Return_Reason(""); + if (lanConfig_cursor == 2) + Net_SlistQW_f(); + else + Net_Slist_f(); +} + + +static void M_ServerList_Draw (void) +{ + int n, y, visible, start, end, numplayers, maxplayers; + cachepic_t *p; + const char *s; + + // use as much vertical space as available + if (gamemode == GAME_TRANSFUSION) + M_Background(640, vid_conheight.integer - 80); + else + M_Background(640, vid_conheight.integer); + // scroll the list as the cursor moves + ServerList_GetPlayerStatistics(&numplayers, &maxplayers); + s = va("%i/%i masters %i/%i servers %i/%i players", masterreplycount, masterquerycount, serverreplycount, serverquerycount, numplayers, maxplayers); + M_PrintRed((640 - strlen(s) * 8) / 2, 32, s); + if (*m_return_reason) + M_Print(16, menu_height - 8, m_return_reason); + y = 48; + visible = (int)((menu_height - 16 - y) / 8 / 2); + start = bound(0, slist_cursor - (visible >> 1), serverlist_viewcount - visible); + end = min(start + visible, serverlist_viewcount); + + p = Draw_CachePic ("gfx/p_multi"); + M_DrawPic((640 - p->width) / 2, 4, "gfx/p_multi"); + if (end > start) + { + for (n = start;n < end;n++) + { + serverlist_entry_t *entry = ServerList_GetViewEntry(n); + DrawQ_Fill(menu_x, menu_y + y, 640, 16, n == slist_cursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_PrintColored(0, y, entry->line1);y += 8; + M_PrintColored(0, y, entry->line2);y += 8; + } + } + else if (realtime - masterquerytime > 10) + { + if (masterquerycount) + M_Print(0, y, "No servers found"); + else + M_Print(0, y, "No master servers found (network problem?)"); + } + else + { + if (serverquerycount) + M_Print(0, y, "Querying servers"); + else + M_Print(0, y, "Querying master servers"); + } +} + + +static void M_ServerList_Key(int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_LanConfig_f(); + break; + + case K_SPACE: + if (lanConfig_cursor == 2) + Net_SlistQW_f(); + else + Net_Slist_f(); + break; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("sound/misc/menu1.wav"); + slist_cursor--; + if (slist_cursor < 0) + slist_cursor = serverlist_viewcount - 1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("sound/misc/menu1.wav"); + slist_cursor++; + if (slist_cursor >= serverlist_viewcount) + slist_cursor = 0; + break; + + case K_ENTER: + S_LocalSound ("sound/misc/menu2.wav"); + if (serverlist_viewcount) + Cbuf_AddText(va("connect \"%s\"\n", ServerList_GetViewEntry(slist_cursor)->info.cname)); + break; + + default: + break; + } + +} + +//============================================================================= +/* MODLIST MENU */ +// same limit of mod dirs as in fs.c +#define MODLIST_MAXDIRS 16 +static int modlist_enabled [MODLIST_MAXDIRS]; //array of indexs to modlist +static int modlist_numenabled; //number of enabled (or in process to be..) mods + +typedef struct modlist_entry_s +{ + qboolean loaded; // used to determine whether this entry is loaded and running + int enabled; // index to array of modlist_enabled + + // name of the modification, this is (will...be) displayed on the menu entry + char name[128]; + // directory where we will find it + char dir[MAX_QPATH]; +} modlist_entry_t; + +static int modlist_cursor; +//static int modlist_viewcount; + +static int modlist_count = 0; +static modlist_entry_t modlist[MODLIST_TOTALSIZE]; + +void ModList_RebuildList(void) +{ + int i,j; + stringlist_t list; + + stringlistinit(&list); + listdirectory(&list, fs_basedir, ""); + stringlistsort(&list, true); + modlist_count = 0; + modlist_numenabled = fs_numgamedirs; + for (i = 0;i < list.numstrings;i++) + { + if (modlist_count >= MODLIST_TOTALSIZE) break; + // check all dirs to see if they "appear" to be mods + // reject any dirs that are part of the base game + if (gamedirname1 && !strcasecmp(gamedirname1, list.strings[i])) continue; + //if (gamedirname2 && !strcasecmp(gamedirname2, list.strings[i])) continue; + if (FS_CheckNastyPath (list.strings[i], true)) continue; + if (!FS_CheckGameDir(list.strings[i])) continue; + + strlcpy (modlist[modlist_count].dir, list.strings[i], sizeof(modlist[modlist_count].dir)); + //check currently loaded mods + modlist[modlist_count].loaded = false; + if (fs_numgamedirs) + for (j = 0; j < fs_numgamedirs; j++) + if (!strcasecmp(fs_gamedirs[j], modlist[modlist_count].dir)) + { + modlist[modlist_count].loaded = true; + modlist[modlist_count].enabled = j; + modlist_enabled[j] = modlist_count; + break; + } + modlist_count ++; + } + stringlistfreecontents(&list); +} + +void ModList_Enable (void) +{ + int i; + int numgamedirs; + char gamedirs[MODLIST_MAXDIRS][MAX_QPATH]; + + // copy our mod list into an array for FS_ChangeGameDirs + numgamedirs = modlist_numenabled; + for (i = 0; i < modlist_numenabled; i++) + strlcpy (gamedirs[i], modlist[modlist_enabled[i]].dir,sizeof (gamedirs[i])); + + // this code snippet is from FS_ChangeGameDirs + if (fs_numgamedirs == numgamedirs) + { + for (i = 0;i < numgamedirs;i++) + if (strcasecmp(fs_gamedirs[i], gamedirs[i])) + break; + if (i == numgamedirs) + return; // already using this set of gamedirs, do nothing + } + + // this part is basically the same as the FS_GameDir_f function + if ((cls.state == ca_connected && !cls.demoplayback) || sv.active) + { + // actually, changing during game would work fine, but would be stupid + Con_Printf("Can not change gamedir while client is connected or server is running!\n"); + return; + } + + FS_ChangeGameDirs (modlist_numenabled, gamedirs, true, true); +} + +void M_Menu_ModList_f (void) +{ + key_dest = key_menu; + m_state = m_modlist; + m_entersound = true; + modlist_cursor = 0; + M_Update_Return_Reason(""); + ModList_RebuildList(); +} + +static void M_Menu_ModList_AdjustSliders (int dir) +{ + int i; + S_LocalSound ("sound/misc/menu3.wav"); + + // stop adding mods, we reach the limit + if (!modlist[modlist_cursor].loaded && (modlist_numenabled == MODLIST_MAXDIRS)) return; + modlist[modlist_cursor].loaded = !modlist[modlist_cursor].loaded; + if (modlist[modlist_cursor].loaded) + { + modlist[modlist_cursor].enabled = modlist_numenabled; + //push the value on the enabled list + modlist_enabled[modlist_numenabled++] = modlist_cursor; + } + else + { + //eliminate the value from the enabled list + for (i = modlist[modlist_cursor].enabled; i < modlist_numenabled; i++) + { + modlist_enabled[i] = modlist_enabled[i+1]; + modlist[modlist_enabled[i]].enabled--; + } + modlist_numenabled--; + } +} + +static void M_ModList_Draw (void) +{ + int n, y, visible, start, end; + cachepic_t *p; + const char *s_available = "Available Mods"; + const char *s_enabled = "Enabled Mods"; + + // use as much vertical space as available + if (gamemode == GAME_TRANSFUSION) + M_Background(640, vid_conheight.integer - 80); + else + M_Background(640, vid_conheight.integer); + + M_PrintRed(48 + 32, 32, s_available); + M_PrintRed(432, 32, s_enabled); + // Draw a list box with all enabled mods + DrawQ_Pic(menu_x + 432, menu_y + 48, NULL, 172, 8 * modlist_numenabled, 0, 0, 0, 0.5, 0); + for (y = 0; y < modlist_numenabled; y++) + M_PrintRed(432, 48 + y * 8, modlist[modlist_enabled[y]].dir); + + if (*m_return_reason) + M_Print(16, menu_height - 8, m_return_reason); + // scroll the list as the cursor moves + y = 48; + visible = (int)((menu_height - 16 - y) / 8 / 2); + start = bound(0, modlist_cursor - (visible >> 1), modlist_count - visible); + end = min(start + visible, modlist_count); + + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((640 - p->width) / 2, 4, "gfx/p_option"); + if (end > start) + { + for (n = start;n < end;n++) + { + DrawQ_Pic(menu_x + 40, menu_y + y, NULL, 360, 8, n == modlist_cursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_ItemPrint(80, y, modlist[n].dir, true); + M_DrawCheckbox(48, y, modlist[n].loaded); + y +=8; + } + } + else + { + M_Print(80, y, "No Mods found"); + } +} + +static void M_ModList_Key(int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + ModList_Enable (); + M_Menu_Options_f(); + break; + + case K_SPACE: + S_LocalSound ("sound/misc/menu2.wav"); + ModList_RebuildList(); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + modlist_cursor--; + if (modlist_cursor < 0) + modlist_cursor = modlist_count - 1; + break; + + case K_LEFTARROW: + M_Menu_ModList_AdjustSliders (-1); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + modlist_cursor++; + if (modlist_cursor >= modlist_count) + modlist_cursor = 0; + break; + + case K_RIGHTARROW: + M_Menu_ModList_AdjustSliders (1); + break; + + case K_ENTER: + S_LocalSound ("sound/misc/menu2.wav"); + ModList_Enable (); + break; + + default: + break; + } + +} + +//============================================================================= +/* Menu Subsystem */ + +static void M_KeyEvent(int key, int ascii, qboolean downevent); +static void M_Draw(void); +void M_ToggleMenu(int mode); +static void M_Shutdown(void); + +void M_Init (void) +{ + menuplyr_load = true; + menuplyr_pixels = NULL; + + Cmd_AddCommand ("menu_main", M_Menu_Main_f, "open the main menu"); + Cmd_AddCommand ("menu_singleplayer", M_Menu_SinglePlayer_f, "open the singleplayer menu"); + Cmd_AddCommand ("menu_load", M_Menu_Load_f, "open the loadgame menu"); + Cmd_AddCommand ("menu_save", M_Menu_Save_f, "open the savegame menu"); + Cmd_AddCommand ("menu_multiplayer", M_Menu_MultiPlayer_f, "open the multiplayer menu"); + Cmd_AddCommand ("menu_setup", M_Menu_Setup_f, "open the player setup menu"); + Cmd_AddCommand ("menu_options", M_Menu_Options_f, "open the options menu"); + Cmd_AddCommand ("menu_options_effects", M_Menu_Options_Effects_f, "open the effects options menu"); + Cmd_AddCommand ("menu_options_graphics", M_Menu_Options_Graphics_f, "open the graphics options menu"); + Cmd_AddCommand ("menu_options_colorcontrol", M_Menu_Options_ColorControl_f, "open the color control menu"); + Cmd_AddCommand ("menu_keys", M_Menu_Keys_f, "open the key binding menu"); + Cmd_AddCommand ("menu_video", M_Menu_Video_f, "open the video options menu"); + Cmd_AddCommand ("menu_reset", M_Menu_Reset_f, "open the reset to defaults menu"); + Cmd_AddCommand ("menu_mods", M_Menu_ModList_f, "open the mods browser menu"); + Cmd_AddCommand ("help", M_Menu_Help_f, "open the help menu"); + Cmd_AddCommand ("menu_quit", M_Menu_Quit_f, "open the quit menu"); + Cmd_AddCommand ("menu_transfusion_episode", M_Menu_Transfusion_Episode_f, "open the transfusion episode select menu"); + Cmd_AddCommand ("menu_transfusion_skill", M_Menu_Transfusion_Skill_f, "open the transfusion skill select menu"); + Cmd_AddCommand ("menu_credits", M_Menu_Credits_f, "open the credits menu"); +} + +void M_Draw (void) +{ + if (key_dest != key_menu && key_dest != key_menu_grabbed) + m_state = m_none; + + if (m_state == m_none) + return; + + switch (m_state) + { + case m_none: + break; + + case m_main: + M_Main_Draw (); + break; + + case m_demo: + M_Demo_Draw (); + break; + + case m_singleplayer: + M_SinglePlayer_Draw (); + break; + + case m_transfusion_episode: + M_Transfusion_Episode_Draw (); + break; + + case m_transfusion_skill: + M_Transfusion_Skill_Draw (); + break; + + case m_load: + M_Load_Draw (); + break; + + case m_save: + M_Save_Draw (); + break; + + case m_multiplayer: + M_MultiPlayer_Draw (); + break; + + case m_setup: + M_Setup_Draw (); + break; + + case m_options: + M_Options_Draw (); + break; + + case m_options_effects: + M_Options_Effects_Draw (); + break; + + case m_options_graphics: + M_Options_Graphics_Draw (); + break; + + case m_options_colorcontrol: + M_Options_ColorControl_Draw (); + break; + + case m_keys: + M_Keys_Draw (); + break; + + case m_reset: + M_Reset_Draw (); + break; + + case m_video: + M_Video_Draw (); + break; + + case m_help: + M_Help_Draw (); + break; + + case m_credits: + M_Credits_Draw (); + break; + + case m_quit: + M_Quit_Draw (); + break; + + case m_lanconfig: + M_LanConfig_Draw (); + break; + + case m_gameoptions: + M_GameOptions_Draw (); + break; + + case m_slist: + M_ServerList_Draw (); + break; + + case m_modlist: + M_ModList_Draw (); + break; + } + + if (gamemode == GAME_TRANSFUSION && !m_missingdata) { + if (m_state != m_credits) { + cachepic_t *p, *drop1, *drop2, *drop3; + int g, scale_x, scale_y, scale_y_repeat, top_offset; + float scale_y_rate; + scale_y_repeat = vid_conheight.integer * 2; + g = (int)(realtime * 64)%96; + scale_y_rate = (float)(g+1) / 96; + top_offset = (g+12)/12; + p = Draw_CachePic (va("gfx/menu/blooddrip%i", top_offset)); + drop1 = Draw_CachePic ("gfx/menu/blooddrop1"); + drop2 = Draw_CachePic ("gfx/menu/blooddrop2"); + drop3 = Draw_CachePic ("gfx/menu/blooddrop3"); + for (scale_x = 0; scale_x <= vid_conwidth.integer; scale_x += p->width) { + for (scale_y = -scale_y_repeat; scale_y <= vid_conheight.integer; scale_y += scale_y_repeat) { + DrawQ_Pic (scale_x + 21, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 116, scale_y_repeat + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 180, scale_y_repeat * .275 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 242, scale_y_repeat * .75 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 304, scale_y_repeat * .25 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 362, scale_y_repeat * .46125 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 402, scale_y_repeat * .1725 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 438, scale_y_repeat * .9 + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 484, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 557, scale_y_repeat * .9425 + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 606, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop2, 0, 0, 1, 1, 1, 1, 0); + } + DrawQ_Pic (scale_x, -1, Draw_CachePic (va("gfx/menu/blooddrip%i", top_offset)), 0, 0, 1, 1, 1, 1, 0); + } + } + } + + if (m_entersound) + { + S_LocalSound ("sound/misc/menu2.wav"); + m_entersound = false; + } + + S_ExtraUpdate (); +} + + +void M_KeyEvent (int key, int ascii, qboolean downevent) +{ + if (!downevent) + return; + switch (m_state) + { + case m_none: + return; + + case m_main: + M_Main_Key (key, ascii); + return; + + case m_demo: + M_Demo_Key (key, ascii); + return; + + case m_singleplayer: + M_SinglePlayer_Key (key, ascii); + return; + + case m_transfusion_episode: + M_Transfusion_Episode_Key (key, ascii); + return; + + case m_transfusion_skill: + M_Transfusion_Skill_Key (key, ascii); + return; + + case m_load: + M_Load_Key (key, ascii); + return; + + case m_save: + M_Save_Key (key, ascii); + return; + + case m_multiplayer: + M_MultiPlayer_Key (key, ascii); + return; + + case m_setup: + M_Setup_Key (key, ascii); + return; + + case m_options: + M_Options_Key (key, ascii); + return; + + case m_options_effects: + M_Options_Effects_Key (key, ascii); + return; + + case m_options_graphics: + M_Options_Graphics_Key (key, ascii); + return; + + case m_options_colorcontrol: + M_Options_ColorControl_Key (key, ascii); + return; + + case m_keys: + M_Keys_Key (key, ascii); + return; + + case m_reset: + M_Reset_Key (key, ascii); + return; + + case m_video: + M_Video_Key (key, ascii); + return; + + case m_help: + M_Help_Key (key, ascii); + return; + + case m_credits: + M_Credits_Key (key, ascii); + return; + + case m_quit: + M_Quit_Key (key, ascii); + return; + + case m_lanconfig: + M_LanConfig_Key (key, ascii); + return; + + case m_gameoptions: + M_GameOptions_Key (key, ascii); + return; + + case m_slist: + M_ServerList_Key (key, ascii); + return; + + case m_modlist: + M_ModList_Key (key, ascii); + return; + } + +} + +void M_NewMap(void) +{ +} + +void M_Shutdown(void) +{ + // reset key_dest + key_dest = key_game; +} + +//============================================================================ +// Menu prog handling + +static const char *m_required_func[] = { +"m_init", +"m_keydown", +"m_draw", +"m_toggle", +"m_shutdown", +}; + +static int m_numrequiredfunc = sizeof(m_required_func) / sizeof(char*); + +static prvm_required_field_t m_required_fields[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_menufieldvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_menufieldstring(x) {ev_string, #x}, +#define PRVM_DECLARE_menufieldedict(x) {ev_entity, #x}, +#define PRVM_DECLARE_menufieldfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +static int m_numrequiredfields = sizeof(m_required_fields) / sizeof(m_required_fields[0]); + +static prvm_required_field_t m_required_globals[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_menuglobalvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_menuglobalstring(x) {ev_string, #x}, +#define PRVM_DECLARE_menuglobaledict(x) {ev_entity, #x}, +#define PRVM_DECLARE_menuglobalfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +static int m_numrequiredglobals = sizeof(m_required_globals) / sizeof(m_required_globals[0]); + +void MR_SetRouting (qboolean forceold); + +void MP_Error(const char *format, ...) DP_FUNC_PRINTF(1); +void MP_Error(const char *format, ...) +{ + static qboolean processingError = false; + char errorstring[MAX_INPUTLINE]; + va_list argptr; + + va_start (argptr, format); + dpvsnprintf (errorstring, sizeof(errorstring), format, argptr); + va_end (argptr); + Con_Printf( "Menu_Error: %s\n", errorstring ); + + if( !processingError ) { + processingError = true; + PRVM_Crash(); + processingError = false; + } else { + Con_Printf( "Menu_Error: Recursive call to MP_Error (from PRVM_Crash)!\n" ); + } + + // fall back to the normal menu + + // say it + Con_Print("Falling back to normal menu\n"); + + key_dest = key_game; + + // init the normal menu now -> this will also correct the menu router pointers + MR_SetRouting (TRUE); + + // reset the active scene, too (to be on the safe side ;)) + R_SelectScene( RST_CLIENT ); + + Host_AbortCurrentFrame(); +} + +void MP_KeyEvent (int key, int ascii, qboolean downevent) +{ + PRVM_Begin; + PRVM_SetProg(PRVM_MENUPROG); + + // pass key + prog->globals.generic[OFS_PARM0] = (float) key; + prog->globals.generic[OFS_PARM1] = (float) ascii; + if (downevent) + PRVM_ExecuteProgram(PRVM_menufunction(m_keydown),"m_keydown(float key, float ascii) required"); + else if (PRVM_menufunction(m_keyup)) + PRVM_ExecuteProgram(PRVM_menufunction(m_keyup),"m_keyup(float key, float ascii) required"); + + PRVM_End; +} + +void MP_Draw (void) +{ + // declarations that are needed right now + + float oldquality; + + R_SelectScene( RST_MENU ); + + // reset the temp entities each frame + r_refdef.scene.numtempentities = 0; + + // menu scenes do not use reduced rendering quality + oldquality = r_refdef.view.quality; + r_refdef.view.quality = 1; + // TODO: this needs to be exposed to R_SetView (or something similar) ASAP [2/5/2008 Andreas] + r_refdef.scene.time = realtime; + + PRVM_Begin; + PRVM_SetProg(PRVM_MENUPROG); + + // FIXME: this really shouldnt error out lest we have a very broken refdef state...? + // or does it kill the server too? + PRVM_ExecuteProgram(PRVM_menufunction(m_draw),"m_draw() required"); + + PRVM_End; + + // TODO: imo this should be moved into scene, too [1/27/2008 Andreas] + r_refdef.view.quality = oldquality; + + R_SelectScene( RST_CLIENT ); +} + +void MP_ToggleMenu(int mode) +{ + PRVM_Begin; + PRVM_SetProg(PRVM_MENUPROG); + + prog->globals.generic[OFS_PARM0] = (float) mode; + PRVM_ExecuteProgram(PRVM_menufunction(m_toggle),"m_toggle() required"); + + PRVM_End; +} + +void MP_NewMap(void) +{ + PRVM_Begin; + PRVM_SetProg(PRVM_MENUPROG); + if (PRVM_menufunction(m_newmap)) + PRVM_ExecuteProgram(PRVM_menufunction(m_newmap),"m_newmap() required"); + PRVM_End; +} + +void MP_Shutdown (void) +{ + PRVM_Begin; + PRVM_SetProg(PRVM_MENUPROG); + + PRVM_ExecuteProgram(PRVM_menufunction(m_shutdown),"m_shutdown() required"); + + // reset key_dest + key_dest = key_game; + + // AK not using this cause Im not sure whether this is useful at all instead : + PRVM_ResetProg(); + + PRVM_End; +} + +void MP_Init (void) +{ + PRVM_Begin; + PRVM_InitProg(PRVM_MENUPROG); + + prog->edictprivate_size = 0; // no private struct used + prog->name = M_NAME; + prog->num_edicts = 1; + prog->limit_edicts = M_MAX_EDICTS; + prog->extensionstring = vm_m_extensions; + prog->builtins = vm_m_builtins; + prog->numbuiltins = vm_m_numbuiltins; + prog->init_cmd = VM_M_Cmd_Init; + prog->reset_cmd = VM_M_Cmd_Reset; + prog->error_cmd = MP_Error; + prog->ExecuteProgram = MVM_ExecuteProgram; + + // allocate the mempools + prog->progs_mempool = Mem_AllocPool(M_PROG_FILENAME, 0, NULL); + + PRVM_LoadProgs(M_PROG_FILENAME, m_numrequiredfunc, m_required_func, m_numrequiredfields, m_required_fields, m_numrequiredglobals, m_required_globals); + + // note: OP_STATE is not supported by menu qc, we don't even try to detect + // it here + + in_client_mouse = true; + + // call the prog init + PRVM_ExecuteProgram(PRVM_menufunction(m_init),"m_init() required"); + + PRVM_End; +} + +//============================================================================ +// Menu router + +void (*MR_KeyEvent) (int key, int ascii, qboolean downevent); +void (*MR_Draw) (void); +void (*MR_ToggleMenu) (int mode); +void (*MR_Shutdown) (void); +void (*MR_NewMap) (void); + +void MR_SetRouting(qboolean forceold) +{ + // if the menu prog isnt available or forceqmenu ist set, use the old menu + if(!FS_FileExists(M_PROG_FILENAME) || forceqmenu.integer || forceold) + { + // set menu router function pointers + MR_KeyEvent = M_KeyEvent; + MR_Draw = M_Draw; + MR_ToggleMenu = M_ToggleMenu; + MR_Shutdown = M_Shutdown; + MR_NewMap = M_NewMap; + M_Init(); + } + else + { + // set menu router function pointers + MR_KeyEvent = MP_KeyEvent; + MR_Draw = MP_Draw; + MR_ToggleMenu = MP_ToggleMenu; + MR_Shutdown = MP_Shutdown; + MR_NewMap = MP_NewMap; + MP_Init(); + } +} + +void MR_Restart(void) +{ + if(MR_Shutdown) + MR_Shutdown (); + MR_SetRouting (FALSE); +} + +void Call_MR_ToggleMenu_f(void) +{ + int m; + m = ((Cmd_Argc() < 2) ? -1 : atoi(Cmd_Argv(1))); + Host_StartVideo(); + if(MR_ToggleMenu) + MR_ToggleMenu(m); +} + +void MR_Init_Commands(void) +{ + // set router console commands + Cvar_RegisterVariable (&forceqmenu); + Cvar_RegisterVariable (&menu_options_colorcontrol_correctionvalue); + Cmd_AddCommand ("menu_restart",MR_Restart, "restart menu system (reloads menu.dat)"); + Cmd_AddCommand ("togglemenu", Call_MR_ToggleMenu_f, "opens or closes menu"); +} + +void MR_Init(void) +{ + vid_mode_t res[1024]; + size_t res_count, i; + + res_count = VID_ListModes(res, sizeof(res) / sizeof(*res)); + res_count = VID_SortModes(res, res_count, false, false, true); + if(res_count) + { + video_resolutions_count = res_count; + video_resolutions = (video_resolution_t *) Mem_Alloc(cls.permanentmempool, sizeof(*video_resolutions) * (video_resolutions_count + 1)); + memset(&video_resolutions[video_resolutions_count], 0, sizeof(video_resolutions[video_resolutions_count])); + for(i = 0; i < res_count; ++i) + { + int n, d, t; + video_resolutions[i].type = "Detected mode"; // FIXME make this more dynamic + video_resolutions[i].width = res[i].width; + video_resolutions[i].height = res[i].height; + video_resolutions[i].pixelheight = res[i].pixelheight_num / (double) res[i].pixelheight_denom; + n = res[i].pixelheight_denom * video_resolutions[i].width; + d = res[i].pixelheight_num * video_resolutions[i].height; + while(d) + { + t = n; + n = d; + d = t % d; + } + d = (res[i].pixelheight_num * video_resolutions[i].height) / n; + n = (res[i].pixelheight_denom * video_resolutions[i].width) / n; + switch(n * 0x10000 | d) + { + case 0x00040003: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 480; + video_resolutions[i].type = "Standard 4x3"; + break; + case 0x00050004: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 512; + if(res[i].pixelheight_denom == res[i].pixelheight_num) + video_resolutions[i].type = "Square Pixel (LCD) 5x4"; + else + video_resolutions[i].type = "Short Pixel (CRT) 5x4"; + break; + case 0x00080005: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 400; + if(res[i].pixelheight_denom == res[i].pixelheight_num) + video_resolutions[i].type = "Widescreen 8x5"; + else + video_resolutions[i].type = "Tall Pixel (CRT) 8x5"; + + break; + case 0x00050003: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 384; + video_resolutions[i].type = "Widescreen 5x3"; + break; + case 0x000D0009: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 400; + video_resolutions[i].type = "Widescreen 14x9"; + break; + case 0x00100009: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 480; + video_resolutions[i].type = "Widescreen 16x9"; + break; + case 0x00030002: + video_resolutions[i].conwidth = 720; + video_resolutions[i].conheight = 480; + video_resolutions[i].type = "NTSC 3x2"; + break; + case 0x000D000B: + video_resolutions[i].conwidth = 720; + video_resolutions[i].conheight = 566; + video_resolutions[i].type = "PAL 14x11"; + break; + case 0x00080007: + if(video_resolutions[i].width >= 512) + { + video_resolutions[i].conwidth = 512; + video_resolutions[i].conheight = 448; + video_resolutions[i].type = "SNES 8x7"; + } + else + { + video_resolutions[i].conwidth = 256; + video_resolutions[i].conheight = 224; + video_resolutions[i].type = "NES 8x7"; + } + break; + default: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 640 * d / n; + video_resolutions[i].type = "Detected mode"; + break; + } + if(video_resolutions[i].conwidth > video_resolutions[i].width || video_resolutions[i].conheight > video_resolutions[i].height) + { + int f1, f2; + f1 = video_resolutions[i].conwidth > video_resolutions[i].width; + f2 = video_resolutions[i].conheight > video_resolutions[i].height; + if(f1 > f2) + { + video_resolutions[i].conwidth = video_resolutions[i].width; + video_resolutions[i].conheight = video_resolutions[i].conheight / f1; + } + else + { + video_resolutions[i].conwidth = video_resolutions[i].conwidth / f2; + video_resolutions[i].conheight = video_resolutions[i].height; + } + } + } + } + else + { + video_resolutions = video_resolutions_hardcoded; + video_resolutions_count = sizeof(video_resolutions_hardcoded) / sizeof(*video_resolutions_hardcoded) - 1; + } + + menu_video_resolutions_forfullscreen = !!vid_fullscreen.integer; + M_Menu_Video_FindResolution(vid.width, vid.height, vid_pixelheight.value); + + // use -forceqmenu to use always the normal quake menu (it sets forceqmenu to 1) +// COMMANDLINEOPTION: Client: -forceqmenu disables menu.dat (same as +forceqmenu 1) + if(COM_CheckParm("-forceqmenu")) + Cvar_SetValueQuick(&forceqmenu,1); + // use -useqmenu for debugging proposes, cause it starts + // the normal quake menu only the first time +// COMMANDLINEOPTION: Client: -useqmenu causes the first time you open the menu to use the quake menu, then reverts to menu.dat (if forceqmenu is 0) + if(COM_CheckParm("-useqmenu")) + MR_SetRouting (TRUE); + else + MR_SetRouting (FALSE); +} diff --git a/misc/source/darkplaces-src/menu.h b/misc/source/darkplaces-src/menu.h new file mode 100644 index 00000000..1ca8799b --- /dev/null +++ b/misc/source/darkplaces-src/menu.h @@ -0,0 +1,101 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef MENU_H +#define MENU_H + +#define M_PROG_FILENAME "menu.dat" +#define M_NAME "menu" + +enum m_state_e { + m_none, + m_main, + m_demo, + m_singleplayer, + m_transfusion_episode, + m_transfusion_skill, + m_load, + m_save, + m_multiplayer, + m_setup, + m_options, + m_video, + m_keys, + m_help, + m_credits, + m_quit, + m_lanconfig, + m_gameoptions, + m_slist, + m_options_effects, + m_options_graphics, + m_options_colorcontrol, + m_reset, + m_modlist +}; + +extern enum m_state_e m_state; +extern char m_return_reason[128]; +void M_Update_Return_Reason(const char *s); + +/* +// hard-coded menus +// +void M_Init (void); +void M_KeyEvent (int key); +void M_Draw (void); +void M_ToggleMenu (int mode); + +// +// menu prog menu +// +void MP_Init (void); +void MP_KeyEvent (int key); +void MP_Draw (void); +void MP_ToggleMenu (int mode); +void MP_Shutdown (void);*/ + +// +// menu router +// + +void MR_Init_Commands (void); +void MR_Init (void); +void MR_Restart (void); +extern void (*MR_KeyEvent) (int key, int ascii, qboolean downevent); +extern void (*MR_Draw) (void); +extern void (*MR_ToggleMenu) (int mode); +extern void (*MR_Shutdown) (void); +extern void (*MR_NewMap) (void); + +typedef struct video_resolution_s +{ + const char *type; + int width, height; + int conwidth, conheight; + double pixelheight; ///< pixel aspect +} +video_resolution_t; +extern video_resolution_t *video_resolutions; +extern int video_resolutions_count; +extern video_resolution_t video_resolutions_hardcoded[]; +extern int video_resolutions_hardcoded_count; +#endif + diff --git a/misc/source/darkplaces-src/meshqueue.c b/misc/source/darkplaces-src/meshqueue.c new file mode 100644 index 00000000..d21ba627 --- /dev/null +++ b/misc/source/darkplaces-src/meshqueue.c @@ -0,0 +1,133 @@ + +#include "quakedef.h" +#include "meshqueue.h" + +typedef struct meshqueue_s +{ + struct meshqueue_s *next; + void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfaceindices); + const entity_render_t *ent; + int surfacenumber; + const rtlight_t *rtlight; + float dist; +} +meshqueue_t; + +int trans_sortarraysize; +meshqueue_t **trans_hash = NULL; +meshqueue_t ***trans_hashpointer = NULL; +extern cvar_t r_transparent_sortarraysize; +extern cvar_t r_transparent_sortmaxdist; + +float mqt_viewplanedist; +float mqt_viewmaxdist; +meshqueue_t *mqt_array; +int mqt_count; +int mqt_total; + +void R_MeshQueue_BeginScene(void) +{ + mqt_count = 0; + mqt_viewplanedist = DotProduct(r_refdef.view.origin, r_refdef.view.forward); + mqt_viewmaxdist = 0; +} + +void R_MeshQueue_AddTransparent(const vec3_t center, void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist), const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight) +{ + meshqueue_t *mq; + if (mqt_count >= mqt_total || !mqt_array) + { + int newtotal = max(1024, mqt_total * 2); + meshqueue_t *newarray = (meshqueue_t *)Mem_Alloc(cls.permanentmempool, newtotal * sizeof(meshqueue_t)); + if (mqt_array) + { + memcpy(newarray, mqt_array, mqt_total * sizeof(meshqueue_t)); + Mem_Free(mqt_array); + } + mqt_array = newarray; + mqt_total = newtotal; + } + mq = &mqt_array[mqt_count++]; + mq->callback = callback; + mq->ent = ent; + mq->surfacenumber = surfacenumber; + mq->rtlight = rtlight; + mq->dist = DotProduct(center, r_refdef.view.forward) - mqt_viewplanedist; + mq->next = NULL; + mqt_viewmaxdist = max(mqt_viewmaxdist, mq->dist); +} + +void R_MeshQueue_RenderTransparent(void) +{ + int i, hashindex, maxhashindex, batchnumsurfaces; + float distscale; + const entity_render_t *ent; + const rtlight_t *rtlight; + void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfaceindices); + int batchsurfaceindex[MESHQUEUE_TRANSPARENT_BATCHSIZE]; + meshqueue_t *mqt; + + if (!mqt_count) + return; + + // check for bad cvars + if (r_transparent_sortarraysize.integer < 1 || r_transparent_sortarraysize.integer > 32768) + Cvar_SetValueQuick(&r_transparent_sortarraysize, bound(1, r_transparent_sortarraysize.integer, 32768)); + if (r_transparent_sortmaxdist.integer < 1 || r_transparent_sortmaxdist.integer > 32768) + Cvar_SetValueQuick(&r_transparent_sortmaxdist, bound(1, r_transparent_sortmaxdist.integer, 32768)); + + // update hash array + if (trans_sortarraysize != r_transparent_sortarraysize.integer) + { + trans_sortarraysize = r_transparent_sortarraysize.integer; + if (trans_hash) + Mem_Free(trans_hash); + trans_hash = (meshqueue_t **)Mem_Alloc(cls.permanentmempool, sizeof(trans_hash) * trans_sortarraysize); + if (trans_hashpointer) + Mem_Free(trans_hashpointer); + trans_hashpointer = (meshqueue_t ***)Mem_Alloc(cls.permanentmempool, sizeof(trans_hashpointer) * trans_sortarraysize); + } + + // build index + memset(trans_hash, 0, sizeof(trans_hash) * trans_sortarraysize); + for (i = 0; i < trans_sortarraysize; i++) + trans_hashpointer[i] = &trans_hash[i]; + distscale = (trans_sortarraysize - 1) / min(mqt_viewmaxdist, r_transparent_sortmaxdist.integer); + maxhashindex = trans_sortarraysize - 1; + for (i = 0, mqt = mqt_array; i < mqt_count; i++, mqt++) + { + hashindex = bound(0, (int)(min(mqt->dist, r_transparent_sortmaxdist.integer) * distscale), maxhashindex); + // link to tail of hash chain (to preserve render order) + mqt->next = NULL; + *trans_hashpointer[hashindex] = mqt; + trans_hashpointer[hashindex] = &mqt->next; + } + callback = NULL; + ent = NULL; + rtlight = NULL; + batchnumsurfaces = 0; + + // draw + for (i = maxhashindex; i >= 0; i--) + { + if (trans_hash[i]) + { + for (mqt = trans_hash[i]; mqt; mqt = mqt->next) + { + if (ent != mqt->ent || rtlight != mqt->rtlight || callback != mqt->callback || batchnumsurfaces >= MESHQUEUE_TRANSPARENT_BATCHSIZE) + { + if (batchnumsurfaces) + callback(ent, rtlight, batchnumsurfaces, batchsurfaceindex); + batchnumsurfaces = 0; + ent = mqt->ent; + rtlight = mqt->rtlight; + callback = mqt->callback; + } + batchsurfaceindex[batchnumsurfaces++] = mqt->surfacenumber; + } + } + } + if (batchnumsurfaces) + callback(ent, rtlight, batchnumsurfaces, batchsurfaceindex); + mqt_count = 0; +} diff --git a/misc/source/darkplaces-src/meshqueue.h b/misc/source/darkplaces-src/meshqueue.h new file mode 100644 index 00000000..f54c4ce3 --- /dev/null +++ b/misc/source/darkplaces-src/meshqueue.h @@ -0,0 +1,12 @@ + +#ifndef MESHQUEUE_H +#define MESHQUEUE_H + +// VorteX: seems this value is hardcoded in other several defines as it's changing makes mess +#define MESHQUEUE_TRANSPARENT_BATCHSIZE 256 + +void R_MeshQueue_BeginScene(void); +void R_MeshQueue_AddTransparent(const vec3_t center, void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist), const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight); +void R_MeshQueue_RenderTransparent(void); + +#endif diff --git a/misc/source/darkplaces-src/mingw_note.txt b/misc/source/darkplaces-src/mingw_note.txt new file mode 100644 index 00000000..c5ca2c77 --- /dev/null +++ b/misc/source/darkplaces-src/mingw_note.txt @@ -0,0 +1,16 @@ +For compiling Darkplaces with MinGW, you need the following files which do not +come in the standard MinGW installation: + - include/ddraw.h + - include/dinput.h + - include/dsound.h +They are part of the DirectX SDK but can also be found in the original release +of Quake 1 source code (ftp://ftp.idsoftware.com/idstuff/source/q1source.zip). + +Assuming the MinGW binaries are in your PATH, you compile Darkplaces by typing +"make release". Note that "make" may be named "mingw32-make", so you may want +to try "mingw32-make release" if the first command fails to run. + +For cross-compiling Win32 binaries on Linux using MinGW, you need to force the +makefile to use the MinGW compilation parameters, otherwise it will autodetect +the operating system it runs on and will use the corresponding parameters. You +can force it by appending "DP_MAKE_TARGET=mingw" at the end of the command line. diff --git a/misc/source/darkplaces-src/mod_skeletal_animatevertices_generic.c b/misc/source/darkplaces-src/mod_skeletal_animatevertices_generic.c new file mode 100644 index 00000000..24cc8a9e --- /dev/null +++ b/misc/source/darkplaces-src/mod_skeletal_animatevertices_generic.c @@ -0,0 +1,213 @@ +#include "mod_skeletal_animatevertices_generic.h" + +typedef struct +{ + float f[12]; +} +float12_t; + +void Mod_Skeletal_AnimateVertices_Generic(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + // vertex weighted skeletal + int i, k; + int blends; + float12_t *bonepose; + float12_t *boneposerelative; + float m[12]; + const blendweights_t * RESTRICT weights; + + if (!model->surfmesh.num_vertices) + return; + + //unsigned long long ts = rdtsc(); + bonepose = (float12_t *) Mod_Skeletal_AnimateVertices_AllocBuffers(sizeof(float12_t) * (model->num_bones*2 + model->surfmesh.num_blends)); + boneposerelative = bonepose + model->num_bones; + + if (skeleton && !skeleton->relativetransforms) + skeleton = NULL; + + // interpolate matrices + if (skeleton) + { + for (i = 0;i < model->num_bones;i++) + { + Matrix4x4_ToArray12FloatD3D(&skeleton->relativetransforms[i], m); + if (model->data_bones[i].parent >= 0) + R_ConcatTransforms(bonepose[model->data_bones[i].parent].f, m, bonepose[i].f); + else + memcpy(bonepose[i].f, m, sizeof(m)); + + // create a relative deformation matrix to describe displacement + // from the base mesh, which is used by the actual weighting + R_ConcatTransforms(bonepose[i].f, model->data_baseboneposeinverse + i * 12, boneposerelative[i].f); + } + } + else + { + float originscale = model->num_posescale; + float x,y,z,w,lerp; + const short * RESTRICT pose6s; + + for (i = 0;i < model->num_bones;i++) + { + memset(m, 0, sizeof(m)); + for (blends = 0;blends < MAX_FRAMEBLENDS && frameblend[blends].lerp > 0;blends++) + { + pose6s = model->data_poses6s + 6 * (frameblend[blends].subframe * model->num_bones + i); + lerp = frameblend[blends].lerp; + x = pose6s[3] * (1.0f / 32767.0f); + y = pose6s[4] * (1.0f / 32767.0f); + z = pose6s[5] * (1.0f / 32767.0f); + w = 1.0f - (x*x+y*y+z*z); + w = w > 0.0f ? -sqrt(w) : 0.0f; + m[ 0] += (1-2*(y*y+z*z)) * lerp; + m[ 1] += ( 2*(x*y-z*w)) * lerp; + m[ 2] += ( 2*(x*z+y*w)) * lerp; + m[ 3] += (pose6s[0] * originscale) * lerp; + m[ 4] += ( 2*(x*y+z*w)) * lerp; + m[ 5] += (1-2*(x*x+z*z)) * lerp; + m[ 6] += ( 2*(y*z-x*w)) * lerp; + m[ 7] += (pose6s[1] * originscale) * lerp; + m[ 8] += ( 2*(x*z-y*w)) * lerp; + m[ 9] += ( 2*(y*z+x*w)) * lerp; + m[10] += (1-2*(x*x+y*y)) * lerp; + m[11] += (pose6s[2] * originscale) * lerp; + } + VectorNormalize(m ); + VectorNormalize(m + 4); + VectorNormalize(m + 8); + if (i == r_skeletal_debugbone.integer) + m[r_skeletal_debugbonecomponent.integer % 12] += r_skeletal_debugbonevalue.value; + m[3] *= r_skeletal_debugtranslatex.value; + m[7] *= r_skeletal_debugtranslatey.value; + m[11] *= r_skeletal_debugtranslatez.value; + if (model->data_bones[i].parent >= 0) + R_ConcatTransforms(bonepose[model->data_bones[i].parent].f, m, bonepose[i].f); + else + memcpy(bonepose[i].f, m, sizeof(m)); + // create a relative deformation matrix to describe displacement + // from the base mesh, which is used by the actual weighting + R_ConcatTransforms(bonepose[i].f, model->data_baseboneposeinverse + i * 12, boneposerelative[i].f); + } + } + + // generate matrices for all blend combinations + weights = model->surfmesh.data_blendweights; + for (i = 0;i < model->surfmesh.num_blends;i++, weights++) + { + float * RESTRICT b = boneposerelative[model->num_bones + i].f; + const float * RESTRICT m = boneposerelative[weights->index[0]].f; + float f = weights->influence[0] * (1.0f / 255.0f); + b[ 0] = f*m[ 0]; b[ 1] = f*m[ 1]; b[ 2] = f*m[ 2]; b[ 3] = f*m[ 3]; + b[ 4] = f*m[ 4]; b[ 5] = f*m[ 5]; b[ 6] = f*m[ 6]; b[ 7] = f*m[ 7]; + b[ 8] = f*m[ 8]; b[ 9] = f*m[ 9]; b[10] = f*m[10]; b[11] = f*m[11]; + for (k = 1;k < 4 && weights->influence[k];k++) + { + m = boneposerelative[weights->index[k]].f; + f = weights->influence[k] * (1.0f / 255.0f); + b[ 0] += f*m[ 0]; b[ 1] += f*m[ 1]; b[ 2] += f*m[ 2]; b[ 3] += f*m[ 3]; + b[ 4] += f*m[ 4]; b[ 5] += f*m[ 5]; b[ 6] += f*m[ 6]; b[ 7] += f*m[ 7]; + b[ 8] += f*m[ 8]; b[ 9] += f*m[ 9]; b[10] += f*m[10]; b[11] += f*m[11]; + } + } + +#define LOAD_MATRIX_SCALAR() const float * RESTRICT m = boneposerelative[*b].f + +#define LOAD_MATRIX3() \ + LOAD_MATRIX_SCALAR() +#define LOAD_MATRIX4() \ + LOAD_MATRIX_SCALAR() + +#define TRANSFORM_POSITION_SCALAR(in, out) \ + (out)[0] = ((in)[0] * m[0] + (in)[1] * m[1] + (in)[2] * m[ 2] + m[3]); \ + (out)[1] = ((in)[0] * m[4] + (in)[1] * m[5] + (in)[2] * m[ 6] + m[7]); \ + (out)[2] = ((in)[0] * m[8] + (in)[1] * m[9] + (in)[2] * m[10] + m[11]); +#define TRANSFORM_VECTOR_SCALAR(in, out) \ + (out)[0] = ((in)[0] * m[0] + (in)[1] * m[1] + (in)[2] * m[ 2]); \ + (out)[1] = ((in)[0] * m[4] + (in)[1] * m[5] + (in)[2] * m[ 6]); \ + (out)[2] = ((in)[0] * m[8] + (in)[1] * m[9] + (in)[2] * m[10]); + +#define TRANSFORM_POSITION(in, out) \ + TRANSFORM_POSITION_SCALAR(in, out) +#define TRANSFORM_VECTOR(in, out) \ + TRANSFORM_VECTOR_SCALAR(in, out) + + // transform vertex attributes by blended matrices + if (vertex3f) + { + const float * RESTRICT v = model->surfmesh.data_vertex3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + // special case common combinations of attributes to avoid repeated loading of matrices + if (normal3f) + { + const float * RESTRICT n = model->surfmesh.data_normal3f; + if (svector3f && tvector3f) + { + const float * RESTRICT sv = model->surfmesh.data_svector3f; + const float * RESTRICT tv = model->surfmesh.data_tvector3f; + + // Note that for SSE each iteration stores one element past end, so we break one vertex short + // and handle that with scalars in that case + for (i = 0; i < model->surfmesh.num_vertices; i++, v += 3, n += 3, sv += 3, tv += 3, b++, + vertex3f += 3, normal3f += 3, svector3f += 3, tvector3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + TRANSFORM_VECTOR(n, normal3f); + TRANSFORM_VECTOR(sv, svector3f); + TRANSFORM_VECTOR(tv, tvector3f); + } + + return; + } + + for (i = 0;i < model->surfmesh.num_vertices; i++, v += 3, n += 3, b++, vertex3f += 3, normal3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + TRANSFORM_VECTOR(n, normal3f); + } + } + else + { + for (i = 0;i < model->surfmesh.num_vertices; i++, v += 3, b++, vertex3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + } + } + } + + else if (normal3f) + { + const float * RESTRICT n = model->surfmesh.data_normal3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < model->surfmesh.num_vertices; i++, n += 3, b++, normal3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(n, normal3f); + } + } + + if (svector3f) + { + const float * RESTRICT sv = model->surfmesh.data_svector3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < model->surfmesh.num_vertices; i++, sv += 3, b++, svector3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(sv, svector3f); + } + } + + if (tvector3f) + { + const float * RESTRICT tv = model->surfmesh.data_tvector3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < model->surfmesh.num_vertices; i++, tv += 3, b++, tvector3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(tv, tvector3f); + } + } +} diff --git a/misc/source/darkplaces-src/mod_skeletal_animatevertices_generic.h b/misc/source/darkplaces-src/mod_skeletal_animatevertices_generic.h new file mode 100644 index 00000000..2ad97eb6 --- /dev/null +++ b/misc/source/darkplaces-src/mod_skeletal_animatevertices_generic.h @@ -0,0 +1,8 @@ +#ifndef MOD_SKELETAL_ANIMATEVERTICES_GENERIC_H +#define MOD_H + +#include "quakedef.h" + +void Mod_Skeletal_AnimateVertices_Generic(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); + +#endif diff --git a/misc/source/darkplaces-src/mod_skeletal_animatevertices_sse.c b/misc/source/darkplaces-src/mod_skeletal_animatevertices_sse.c new file mode 100644 index 00000000..06d74317 --- /dev/null +++ b/misc/source/darkplaces-src/mod_skeletal_animatevertices_sse.c @@ -0,0 +1,330 @@ +#include "mod_skeletal_animatevertices_sse.h" + +#ifdef SSE_POSSIBLE + +#ifdef MATRIX4x4_OPENGLORIENTATION +#error "SSE skeletal requires D3D matrix layout" +#endif + +#include + +void Mod_Skeletal_AnimateVertices_SSE(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + // vertex weighted skeletal + int i, k; + int blends; + matrix4x4_t *bonepose; + matrix4x4_t *boneposerelative; + float m[12]; + matrix4x4_t mm, mm2; + const blendweights_t * RESTRICT weights; + int num_vertices_minus_one; + + if (!model->surfmesh.num_vertices) + return; + + num_vertices_minus_one = model->surfmesh.num_vertices - 1; + + //unsigned long long ts = rdtsc(); + bonepose = (matrix4x4_t *) Mod_Skeletal_AnimateVertices_AllocBuffers(sizeof(matrix4x4_t) * (model->num_bones*2 + model->surfmesh.num_blends)); + boneposerelative = bonepose + model->num_bones; + + if (skeleton && !skeleton->relativetransforms) + skeleton = NULL; + + // interpolate matrices + if (skeleton) + { + for (i = 0;i < model->num_bones;i++) + { + // relativetransforms is in GL column-major order, which is what we need for SSE + // transposed style processing + if (model->data_bones[i].parent >= 0) + Matrix4x4_Concat(&bonepose[i], &bonepose[model->data_bones[i].parent], &skeleton->relativetransforms[i]); + else + memcpy(&bonepose[i], &skeleton->relativetransforms[i], sizeof(matrix4x4_t)); + + // create a relative deformation matrix to describe displacement + // from the base mesh, which is used by the actual weighting + Matrix4x4_FromArray12FloatD3D(&mm, model->data_baseboneposeinverse + i * 12); // baseboneposeinverse is 4x3 row-major + Matrix4x4_Concat(&mm2, &bonepose[i], &mm); + Matrix4x4_Transpose(&boneposerelative[i], &mm2); // TODO: Eliminate this transpose + } + } + else + { + float originscale = model->num_posescale; + float x,y,z,w,lerp; + const short * RESTRICT pose6s; + + for (i = 0;i < model->num_bones;i++) + { + memset(m, 0, sizeof(m)); + for (blends = 0;blends < MAX_FRAMEBLENDS && frameblend[blends].lerp > 0;blends++) + { + pose6s = model->data_poses6s + 6 * (frameblend[blends].subframe * model->num_bones + i); + lerp = frameblend[blends].lerp; + x = pose6s[3] * (1.0f / 32767.0f); + y = pose6s[4] * (1.0f / 32767.0f); + z = pose6s[5] * (1.0f / 32767.0f); + w = 1.0f - (x*x+y*y+z*z); + w = w > 0.0f ? -sqrt(w) : 0.0f; + m[ 0] += (1-2*(y*y+z*z)) * lerp; + m[ 1] += ( 2*(x*y-z*w)) * lerp; + m[ 2] += ( 2*(x*z+y*w)) * lerp; + m[ 3] += (pose6s[0] * originscale) * lerp; + m[ 4] += ( 2*(x*y+z*w)) * lerp; + m[ 5] += (1-2*(x*x+z*z)) * lerp; + m[ 6] += ( 2*(y*z-x*w)) * lerp; + m[ 7] += (pose6s[1] * originscale) * lerp; + m[ 8] += ( 2*(x*z-y*w)) * lerp; + m[ 9] += ( 2*(y*z+x*w)) * lerp; + m[10] += (1-2*(x*x+y*y)) * lerp; + m[11] += (pose6s[2] * originscale) * lerp; + } + VectorNormalize(m ); + VectorNormalize(m + 4); + VectorNormalize(m + 8); + if (i == r_skeletal_debugbone.integer) + m[r_skeletal_debugbonecomponent.integer % 12] += r_skeletal_debugbonevalue.value; + m[3] *= r_skeletal_debugtranslatex.value; + m[7] *= r_skeletal_debugtranslatey.value; + m[11] *= r_skeletal_debugtranslatez.value; + Matrix4x4_FromArray12FloatD3D(&mm, m); + if (model->data_bones[i].parent >= 0) + Matrix4x4_Concat(&bonepose[i], &bonepose[model->data_bones[i].parent], &mm); + else + memcpy(&bonepose[i], &mm, sizeof(mm)); + // create a relative deformation matrix to describe displacement + // from the base mesh, which is used by the actual weighting + Matrix4x4_FromArray12FloatD3D(&mm, model->data_baseboneposeinverse + i * 12); // baseboneposeinverse is 4x3 row-major + Matrix4x4_Concat(&mm2, &bonepose[i], &mm); + Matrix4x4_Transpose(&boneposerelative[i], &mm2); // TODO: Eliminate this transpose + } + } + + // generate matrices for all blend combinations + weights = model->surfmesh.data_blendweights; + for (i = 0;i < model->surfmesh.num_blends;i++, weights++) + { + float * RESTRICT b = &boneposerelative[model->num_bones + i].m[0][0]; + const float * RESTRICT m = &boneposerelative[weights->index[0]].m[0][0]; + float f = weights->influence[0] * (1.0f / 255.0f); + __m128 fv = _mm_set_ps1(f); + __m128 b0 = _mm_load_ps(m); + __m128 b1 = _mm_load_ps(m+4); + __m128 b2 = _mm_load_ps(m+8); + __m128 b3 = _mm_load_ps(m+12); + __m128 m0, m1, m2, m3; + b0 = _mm_mul_ps(b0, fv); + b1 = _mm_mul_ps(b1, fv); + b2 = _mm_mul_ps(b2, fv); + b3 = _mm_mul_ps(b3, fv); + for (k = 1;k < 4 && weights->influence[k];k++) + { + m = &boneposerelative[weights->index[k]].m[0][0]; + f = weights->influence[k] * (1.0f / 255.0f); + fv = _mm_set_ps1(f); + m0 = _mm_load_ps(m); + m1 = _mm_load_ps(m+4); + m2 = _mm_load_ps(m+8); + m3 = _mm_load_ps(m+12); + m0 = _mm_mul_ps(m0, fv); + m1 = _mm_mul_ps(m1, fv); + m2 = _mm_mul_ps(m2, fv); + m3 = _mm_mul_ps(m3, fv); + b0 = _mm_add_ps(m0, b0); + b1 = _mm_add_ps(m1, b1); + b2 = _mm_add_ps(m2, b2); + b3 = _mm_add_ps(m3, b3); + } + _mm_store_ps(b, b0); + _mm_store_ps(b+4, b1); + _mm_store_ps(b+8, b2); + _mm_store_ps(b+12, b3); + } + +#define LOAD_MATRIX_SCALAR() const float * RESTRICT m = &boneposerelative[*b].m[0][0] + +#define LOAD_MATRIX3() \ + const float * RESTRICT m = &boneposerelative[*b].m[0][0]; \ + /* bonepose array is 16 byte aligned */ \ + __m128 m1 = _mm_load_ps((m)); \ + __m128 m2 = _mm_load_ps((m)+4); \ + __m128 m3 = _mm_load_ps((m)+8); +#define LOAD_MATRIX4() \ + const float * RESTRICT m = &boneposerelative[*b].m[0][0]; \ + /* bonepose array is 16 byte aligned */ \ + __m128 m1 = _mm_load_ps((m)); \ + __m128 m2 = _mm_load_ps((m)+4); \ + __m128 m3 = _mm_load_ps((m)+8); \ + __m128 m4 = _mm_load_ps((m)+12) + + /* Note that matrix is 4x4 and transposed compared to non-USE_SSE codepath */ +#define TRANSFORM_POSITION_SCALAR(in, out) \ + (out)[0] = ((in)[0] * m[0] + (in)[1] * m[4] + (in)[2] * m[ 8] + m[12]); \ + (out)[1] = ((in)[0] * m[1] + (in)[1] * m[5] + (in)[2] * m[ 9] + m[13]); \ + (out)[2] = ((in)[0] * m[2] + (in)[1] * m[6] + (in)[2] * m[10] + m[14]); +#define TRANSFORM_VECTOR_SCALAR(in, out) \ + (out)[0] = ((in)[0] * m[0] + (in)[1] * m[4] + (in)[2] * m[ 8]); \ + (out)[1] = ((in)[0] * m[1] + (in)[1] * m[5] + (in)[2] * m[ 9]); \ + (out)[2] = ((in)[0] * m[2] + (in)[1] * m[6] + (in)[2] * m[10]); + +#define TRANSFORM_POSITION(in, out) { \ + __m128 pin = _mm_loadu_ps(in); /* we ignore the value in the last element (x from the next vertex) */ \ + __m128 x = _mm_shuffle_ps(pin, pin, 0x0); \ + __m128 t1 = _mm_mul_ps(x, m1); \ + \ + /* y, + x */ \ + __m128 y = _mm_shuffle_ps(pin, pin, 0x55); \ + __m128 t2 = _mm_mul_ps(y, m2); \ + __m128 t3 = _mm_add_ps(t1, t2); \ + \ + /* z, + (y+x) */ \ + __m128 z = _mm_shuffle_ps(pin, pin, 0xaa); \ + __m128 t4 = _mm_mul_ps(z, m3); \ + __m128 t5 = _mm_add_ps(t3, t4); \ + \ + /* + m3 */ \ + __m128 pout = _mm_add_ps(t5, m4); \ + _mm_storeu_ps((out), pout); \ + } + +#define TRANSFORM_VECTOR(in, out) { \ + __m128 vin = _mm_loadu_ps(in); \ + \ + /* x */ \ + __m128 x = _mm_shuffle_ps(vin, vin, 0x0); \ + __m128 t1 = _mm_mul_ps(x, m1); \ + \ + /* y, + x */ \ + __m128 y = _mm_shuffle_ps(vin, vin, 0x55); \ + __m128 t2 = _mm_mul_ps(y, m2); \ + __m128 t3 = _mm_add_ps(t1, t2); \ + \ + /* nz, + (ny + nx) */ \ + __m128 z = _mm_shuffle_ps(vin, vin, 0xaa); \ + __m128 t4 = _mm_mul_ps(z, m3); \ + __m128 vout = _mm_add_ps(t3, t4); \ + _mm_storeu_ps((out), vout); \ + } + + // transform vertex attributes by blended matrices + if (vertex3f) + { + const float * RESTRICT v = model->surfmesh.data_vertex3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + // special case common combinations of attributes to avoid repeated loading of matrices + if (normal3f) + { + const float * RESTRICT n = model->surfmesh.data_normal3f; + if (svector3f && tvector3f) + { + const float * RESTRICT sv = model->surfmesh.data_svector3f; + const float * RESTRICT tv = model->surfmesh.data_tvector3f; + + // Note that for SSE each iteration stores one element past end, so we break one vertex short + // and handle that with scalars in that case + for (i = 0; i < num_vertices_minus_one; i++, v += 3, n += 3, sv += 3, tv += 3, b++, + vertex3f += 3, normal3f += 3, svector3f += 3, tvector3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + TRANSFORM_VECTOR(n, normal3f); + TRANSFORM_VECTOR(sv, svector3f); + TRANSFORM_VECTOR(tv, tvector3f); + } + + // Last vertex needs to be done with scalars to avoid reading/writing 1 word past end of arrays + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_POSITION_SCALAR(v, vertex3f); + TRANSFORM_VECTOR_SCALAR(n, normal3f); + TRANSFORM_VECTOR_SCALAR(sv, svector3f); + TRANSFORM_VECTOR_SCALAR(tv, tvector3f); + } + //printf("elapsed ticks: %llu\n", rdtsc() - ts); // XXX + return; + } + + for (i = 0;i < num_vertices_minus_one; i++, v += 3, n += 3, b++, vertex3f += 3, normal3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + TRANSFORM_VECTOR(n, normal3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_POSITION_SCALAR(v, vertex3f); + TRANSFORM_VECTOR_SCALAR(n, normal3f); + } + } + else + { + for (i = 0;i < num_vertices_minus_one; i++, v += 3, b++, vertex3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_POSITION_SCALAR(v, vertex3f); + } + } + } + + else if (normal3f) + { + const float * RESTRICT n = model->surfmesh.data_normal3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < num_vertices_minus_one; i++, n += 3, b++, normal3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(n, normal3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_VECTOR_SCALAR(n, normal3f); + } + } + + if (svector3f) + { + const float * RESTRICT sv = model->surfmesh.data_svector3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < num_vertices_minus_one; i++, sv += 3, b++, svector3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(sv, svector3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_VECTOR_SCALAR(sv, svector3f); + } + } + + if (tvector3f) + { + const float * RESTRICT tv = model->surfmesh.data_tvector3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < num_vertices_minus_one; i++, tv += 3, b++, tvector3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(tv, tvector3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_VECTOR_SCALAR(tv, tvector3f); + } + } + +#undef LOAD_MATRIX3 +#undef LOAD_MATRIX4 +#undef TRANSFORM_POSITION +#undef TRANSFORM_VECTOR +#undef LOAD_MATRIX_SCALAR +#undef TRANSFORM_POSITION_SCALAR +#undef TRANSFORM_VECTOR_SCALAR +} + +#endif diff --git a/misc/source/darkplaces-src/mod_skeletal_animatevertices_sse.h b/misc/source/darkplaces-src/mod_skeletal_animatevertices_sse.h new file mode 100644 index 00000000..7de55ca6 --- /dev/null +++ b/misc/source/darkplaces-src/mod_skeletal_animatevertices_sse.h @@ -0,0 +1,10 @@ +#ifndef MOD_SKELTAL_ANIMATEVERTICES_SSE_H +#define MOD_SKELTAL_ANIMATEVERTICES_SSE_H + +#include "quakedef.h" + +#ifdef SSE_POSSIBLE +void Mod_Skeletal_AnimateVertices_SSE(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); +#endif + +#endif diff --git a/misc/source/darkplaces-src/model_alias.c b/misc/source/darkplaces-src/model_alias.c new file mode 100644 index 00000000..2231f3df --- /dev/null +++ b/misc/source/darkplaces-src/model_alias.c @@ -0,0 +1,3548 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "image.h" +#include "r_shadow.h" +#include "mod_skeletal_animatevertices_generic.h" +#ifdef SSE_POSSIBLE +#include "mod_skeletal_animatevertices_sse.h" +#endif + +#ifdef SSE_POSSIBLE +static qboolean r_skeletal_use_sse_defined = false; +cvar_t r_skeletal_use_sse = {0, "r_skeletal_use_sse", "1", "use SSE for skeletal model animation"}; +#endif +cvar_t r_skeletal_debugbone = {0, "r_skeletal_debugbone", "-1", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugbonecomponent = {0, "r_skeletal_debugbonecomponent", "3", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugbonevalue = {0, "r_skeletal_debugbonevalue", "100", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugtranslatex = {0, "r_skeletal_debugtranslatex", "1", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugtranslatey = {0, "r_skeletal_debugtranslatey", "1", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugtranslatez = {0, "r_skeletal_debugtranslatez", "1", "development cvar for testing skeletal model code"}; +cvar_t mod_alias_supporttagscale = {0, "mod_alias_supporttagscale", "1", "support scaling factors in bone/tag attachment matrices as supported by MD3"}; + +float mod_md3_sin[320]; + +static size_t Mod_Skeltal_AnimateVertices_maxbonepose = 0; +static void *Mod_Skeltal_AnimateVertices_bonepose = NULL; +void Mod_Skeletal_FreeBuffers(void) +{ + if(Mod_Skeltal_AnimateVertices_bonepose) + Mem_Free(Mod_Skeltal_AnimateVertices_bonepose); + Mod_Skeltal_AnimateVertices_maxbonepose = 0; + Mod_Skeltal_AnimateVertices_bonepose = NULL; +} +void *Mod_Skeletal_AnimateVertices_AllocBuffers(size_t nbytes) +{ + if(Mod_Skeltal_AnimateVertices_maxbonepose < nbytes) + { + if(Mod_Skeltal_AnimateVertices_bonepose) + Mem_Free(Mod_Skeltal_AnimateVertices_bonepose); + Mod_Skeltal_AnimateVertices_bonepose = Z_Malloc(nbytes); + Mod_Skeltal_AnimateVertices_maxbonepose = nbytes; + } + return Mod_Skeltal_AnimateVertices_bonepose; +} + +void Mod_Skeletal_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ +#ifdef SSE_POSSIBLE + if(r_skeletal_use_sse_defined) + if(r_skeletal_use_sse.integer) + { + Mod_Skeletal_AnimateVertices_SSE(model, frameblend, skeleton, vertex3f, normal3f, svector3f, tvector3f); + return; + } +#endif + Mod_Skeletal_AnimateVertices_Generic(model, frameblend, skeleton, vertex3f, normal3f, svector3f, tvector3f); +} + +void Mod_AliasInit (void) +{ + int i; + Cvar_RegisterVariable(&r_skeletal_debugbone); + Cvar_RegisterVariable(&r_skeletal_debugbonecomponent); + Cvar_RegisterVariable(&r_skeletal_debugbonevalue); + Cvar_RegisterVariable(&r_skeletal_debugtranslatex); + Cvar_RegisterVariable(&r_skeletal_debugtranslatey); + Cvar_RegisterVariable(&r_skeletal_debugtranslatez); + Cvar_RegisterVariable(&mod_alias_supporttagscale); + for (i = 0;i < 320;i++) + mod_md3_sin[i] = sin(i * M_PI * 2.0f / 256.0); +#ifdef SSE_POSSIBLE + if(Sys_HaveSSE()) + { + Con_Printf("Skeletal animation uses SSE code path\n"); + r_skeletal_use_sse_defined = true; + Cvar_RegisterVariable(&r_skeletal_use_sse); + } + else + Con_Printf("Skeletal animation uses generic code path (SSE disabled or not detected)\n"); +#else + Con_Printf("Skeletal animation uses generic code path (SSE not compiled in)\n"); +#endif +} + +int Mod_Skeletal_AddBlend(dp_model_t *model, const blendweights_t *newweights) +{ + int i; + blendweights_t *weights; + if(!newweights->influence[1]) + return newweights->index[0]; + weights = model->surfmesh.data_blendweights; + for (i = 0;i < model->surfmesh.num_blends;i++, weights++) + { + if (!memcmp(weights, newweights, sizeof(blendweights_t))) + return model->num_bones + i; + } + model->surfmesh.num_blends++; + memcpy(weights, newweights, sizeof(blendweights_t)); + return model->num_bones + i; +} + +int Mod_Skeletal_CompressBlend(dp_model_t *model, const int *newindex, const float *newinfluence) +{ + int i, total; + float scale; + blendweights_t newweights; + if(!newinfluence[1]) + return newindex[0]; + scale = 0; + for (i = 0;i < 4;i++) + scale += newinfluence[i]; + scale = 255.0f / scale; + total = 0; + for (i = 0;i < 4;i++) + { + newweights.index[i] = newindex[i]; + newweights.influence[i] = (unsigned char)(newinfluence[i] * scale); + total += newweights.influence[i]; + } + while (total > 255) + { + for (i = 0;i < 4;i++) + { + if(newweights.influence[i] > 0 && total > 255) + { + newweights.influence[i]--; + total--; + } + } + } + while (total < 255) + { + for (i = 0; i < 4;i++) + { + if(newweights.influence[i] < 255 && total < 255) + { + newweights.influence[i]++; + total++; + } + } + } + return Mod_Skeletal_AddBlend(model, &newweights); +} + +void Mod_MD3_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + // vertex morph + int i, numblends, blendnum; + int numverts = model->surfmesh.num_vertices; + numblends = 0; + for (blendnum = 0;blendnum < MAX_FRAMEBLENDS;blendnum++) + { + //VectorMA(translate, model->surfmesh.num_morphmdlframetranslate, frameblend[blendnum].lerp, translate); + if (frameblend[blendnum].lerp > 0) + numblends = blendnum + 1; + } + // special case for the first blend because it avoids some adds and the need to memset the arrays first + for (blendnum = 0;blendnum < numblends;blendnum++) + { + const md3vertex_t *verts = model->surfmesh.data_morphmd3vertex + numverts * frameblend[blendnum].subframe; + if (vertex3f) + { + float scale = frameblend[blendnum].lerp * (1.0f / 64.0f); + if (blendnum == 0) + { + for (i = 0;i < numverts;i++) + { + vertex3f[i * 3 + 0] = verts[i].origin[0] * scale; + vertex3f[i * 3 + 1] = verts[i].origin[1] * scale; + vertex3f[i * 3 + 2] = verts[i].origin[2] * scale; + } + } + else + { + for (i = 0;i < numverts;i++) + { + vertex3f[i * 3 + 0] += verts[i].origin[0] * scale; + vertex3f[i * 3 + 1] += verts[i].origin[1] * scale; + vertex3f[i * 3 + 2] += verts[i].origin[2] * scale; + } + } + } + // the yaw and pitch stored in md3 models are 8bit quantized angles + // (0-255), and as such a lookup table is very well suited to + // decoding them, and since cosine is equivalent to sine with an + // extra 45 degree rotation, this uses one lookup table for both + // sine and cosine with a +64 bias to get cosine. + if (normal3f) + { + float lerp = frameblend[blendnum].lerp; + if (blendnum == 0) + { + for (i = 0;i < numverts;i++) + { + normal3f[i * 3 + 0] = mod_md3_sin[verts[i].yaw + 64] * mod_md3_sin[verts[i].pitch ] * lerp; + normal3f[i * 3 + 1] = mod_md3_sin[verts[i].yaw ] * mod_md3_sin[verts[i].pitch ] * lerp; + normal3f[i * 3 + 2] = mod_md3_sin[verts[i].pitch + 64] * lerp; + } + } + else + { + for (i = 0;i < numverts;i++) + { + normal3f[i * 3 + 0] += mod_md3_sin[verts[i].yaw + 64] * mod_md3_sin[verts[i].pitch ] * lerp; + normal3f[i * 3 + 1] += mod_md3_sin[verts[i].yaw ] * mod_md3_sin[verts[i].pitch ] * lerp; + normal3f[i * 3 + 2] += mod_md3_sin[verts[i].pitch + 64] * lerp; + } + } + } + if (svector3f) + { + const texvecvertex_t *texvecvert = model->surfmesh.data_morphtexvecvertex + numverts * frameblend[blendnum].subframe; + float f = frameblend[blendnum].lerp * (1.0f / 127.0f); + if (blendnum == 0) + { + for (i = 0;i < numverts;i++, texvecvert++) + { + VectorScale(texvecvert->svec, f, svector3f + i*3); + VectorScale(texvecvert->tvec, f, tvector3f + i*3); + } + } + else + { + for (i = 0;i < numverts;i++, texvecvert++) + { + VectorMA(svector3f + i*3, f, texvecvert->svec, svector3f + i*3); + VectorMA(tvector3f + i*3, f, texvecvert->tvec, tvector3f + i*3); + } + } + } + } +} +void Mod_MDL_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + // vertex morph + int i, numblends, blendnum; + int numverts = model->surfmesh.num_vertices; + float translate[3]; + VectorClear(translate); + numblends = 0; + // blend the frame translates to avoid redundantly doing so on each vertex + // (a bit of a brain twister but it works) + for (blendnum = 0;blendnum < MAX_FRAMEBLENDS;blendnum++) + { + if (model->surfmesh.data_morphmd2framesize6f) + VectorMA(translate, frameblend[blendnum].lerp, model->surfmesh.data_morphmd2framesize6f + frameblend[blendnum].subframe * 6 + 3, translate); + else + VectorMA(translate, frameblend[blendnum].lerp, model->surfmesh.num_morphmdlframetranslate, translate); + if (frameblend[blendnum].lerp > 0) + numblends = blendnum + 1; + } + // special case for the first blend because it avoids some adds and the need to memset the arrays first + for (blendnum = 0;blendnum < numblends;blendnum++) + { + const trivertx_t *verts = model->surfmesh.data_morphmdlvertex + numverts * frameblend[blendnum].subframe; + if (vertex3f) + { + float scale[3]; + if (model->surfmesh.data_morphmd2framesize6f) + VectorScale(model->surfmesh.data_morphmd2framesize6f + frameblend[blendnum].subframe * 6, frameblend[blendnum].lerp, scale); + else + VectorScale(model->surfmesh.num_morphmdlframescale, frameblend[blendnum].lerp, scale); + if (blendnum == 0) + { + for (i = 0;i < numverts;i++) + { + vertex3f[i * 3 + 0] = translate[0] + verts[i].v[0] * scale[0]; + vertex3f[i * 3 + 1] = translate[1] + verts[i].v[1] * scale[1]; + vertex3f[i * 3 + 2] = translate[2] + verts[i].v[2] * scale[2]; + } + } + else + { + for (i = 0;i < numverts;i++) + { + vertex3f[i * 3 + 0] += verts[i].v[0] * scale[0]; + vertex3f[i * 3 + 1] += verts[i].v[1] * scale[1]; + vertex3f[i * 3 + 2] += verts[i].v[2] * scale[2]; + } + } + } + // the vertex normals in mdl models are an index into a table of + // 162 unique values, this very crude quantization reduces the + // vertex normal to only one byte, which saves a lot of space but + // also makes lighting pretty coarse + if (normal3f) + { + float lerp = frameblend[blendnum].lerp; + if (blendnum == 0) + { + for (i = 0;i < numverts;i++) + { + const float *vn = m_bytenormals[verts[i].lightnormalindex]; + VectorScale(vn, lerp, normal3f + i*3); + } + } + else + { + for (i = 0;i < numverts;i++) + { + const float *vn = m_bytenormals[verts[i].lightnormalindex]; + VectorMA(normal3f + i*3, lerp, vn, normal3f + i*3); + } + } + } + if (svector3f) + { + const texvecvertex_t *texvecvert = model->surfmesh.data_morphtexvecvertex + numverts * frameblend[blendnum].subframe; + float f = frameblend[blendnum].lerp * (1.0f / 127.0f); + if (blendnum == 0) + { + for (i = 0;i < numverts;i++, texvecvert++) + { + VectorScale(texvecvert->svec, f, svector3f + i*3); + VectorScale(texvecvert->tvec, f, tvector3f + i*3); + } + } + else + { + for (i = 0;i < numverts;i++, texvecvert++) + { + VectorMA(svector3f + i*3, f, texvecvert->svec, svector3f + i*3); + VectorMA(tvector3f + i*3, f, texvecvert->tvec, tvector3f + i*3); + } + } + } + } +} + +int Mod_Alias_GetTagMatrix(const dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, int tagindex, matrix4x4_t *outmatrix) +{ + matrix4x4_t temp; + matrix4x4_t parentbonematrix; + matrix4x4_t tempbonematrix; + matrix4x4_t bonematrix; + matrix4x4_t blendmatrix; + int blendindex; + int parenttagindex; + int k; + float lerp; + const float *input; + float blendtag[12]; + *outmatrix = identitymatrix; + if (skeleton && skeleton->relativetransforms) + { + if (tagindex < 0 || tagindex >= skeleton->model->num_bones) + return 4; + *outmatrix = skeleton->relativetransforms[tagindex]; + while ((tagindex = model->data_bones[tagindex].parent) >= 0) + { + temp = *outmatrix; + Matrix4x4_Concat(outmatrix, &skeleton->relativetransforms[tagindex], &temp); + } + } + else if (model->num_bones) + { + if (tagindex < 0 || tagindex >= model->num_bones) + return 4; + Matrix4x4_Clear(&blendmatrix); + for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) + { + lerp = frameblend[blendindex].lerp; + Matrix4x4_FromBonePose6s(&bonematrix, model->num_posescale, model->data_poses6s + 6 * (frameblend[blendindex].subframe * model->num_bones + tagindex)); + parenttagindex = tagindex; + while ((parenttagindex = model->data_bones[parenttagindex].parent) >= 0) + { + Matrix4x4_FromBonePose6s(&parentbonematrix, model->num_posescale, model->data_poses6s + 6 * (frameblend[blendindex].subframe * model->num_bones + parenttagindex)); + tempbonematrix = bonematrix; + Matrix4x4_Concat(&bonematrix, &parentbonematrix, &tempbonematrix); + } + Matrix4x4_Accumulate(&blendmatrix, &bonematrix, lerp); + } + *outmatrix = blendmatrix; + } + else if (model->num_tags) + { + if (tagindex < 0 || tagindex >= model->num_tags) + return 4; + for (k = 0;k < 12;k++) + blendtag[k] = 0; + for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) + { + lerp = frameblend[blendindex].lerp; + input = model->data_tags[frameblend[blendindex].subframe * model->num_tags + tagindex].matrixgl; + for (k = 0;k < 12;k++) + blendtag[k] += input[k] * lerp; + } + Matrix4x4_FromArray12FloatGL(outmatrix, blendtag); + } + + if(!mod_alias_supporttagscale.integer) + Matrix4x4_Normalize3(outmatrix, outmatrix); + + return 0; +} + +int Mod_Alias_GetExtendedTagInfoForIndex(const dp_model_t *model, unsigned int skin, const frameblend_t *frameblend, const skeleton_t *skeleton, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) +{ + int blendindex; + int k; + float lerp; + matrix4x4_t bonematrix; + matrix4x4_t blendmatrix; + const float *input; + float blendtag[12]; + + if (skeleton && skeleton->relativetransforms) + { + if (tagindex < 0 || tagindex >= skeleton->model->num_bones) + return 1; + *parentindex = skeleton->model->data_bones[tagindex].parent; + *tagname = skeleton->model->data_bones[tagindex].name; + *tag_localmatrix = skeleton->relativetransforms[tagindex]; + return 0; + } + else if (model->num_bones) + { + if (tagindex < 0 || tagindex >= model->num_bones) + return 1; + *parentindex = model->data_bones[tagindex].parent; + *tagname = model->data_bones[tagindex].name; + Matrix4x4_Clear(&blendmatrix); + for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) + { + lerp = frameblend[blendindex].lerp; + Matrix4x4_FromBonePose6s(&bonematrix, model->num_posescale, model->data_poses6s + 6 * (frameblend[blendindex].subframe * model->num_bones + tagindex)); + Matrix4x4_Accumulate(&blendmatrix, &bonematrix, lerp); + } + *tag_localmatrix = blendmatrix; + return 0; + } + else if (model->num_tags) + { + if (tagindex < 0 || tagindex >= model->num_tags) + return 1; + *parentindex = -1; + *tagname = model->data_tags[tagindex].name; + for (k = 0;k < 12;k++) + blendtag[k] = 0; + for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) + { + lerp = frameblend[blendindex].lerp; + input = model->data_tags[frameblend[blendindex].subframe * model->num_tags + tagindex].matrixgl; + for (k = 0;k < 12;k++) + blendtag[k] += input[k] * lerp; + } + Matrix4x4_FromArray12FloatGL(tag_localmatrix, blendtag); + return 0; + } + + return 2; +} + +int Mod_Alias_GetTagIndexForName(const dp_model_t *model, unsigned int skin, const char *tagname) +{ + int i; + if(skin >= (unsigned int)model->numskins) + skin = 0; + if (model->num_bones) + for (i = 0;i < model->num_bones;i++) + if (!strcasecmp(tagname, model->data_bones[i].name)) + return i + 1; + if (model->num_tags) + for (i = 0;i < model->num_tags;i++) + if (!strcasecmp(tagname, model->data_tags[i].name)) + return i + 1; + return 0; +} + +static void Mod_BuildBaseBonePoses(void) +{ + int boneindex; + matrix4x4_t *basebonepose; + float *outinvmatrix = loadmodel->data_baseboneposeinverse; + matrix4x4_t bonematrix; + matrix4x4_t tempbonematrix; + if (!loadmodel->num_bones) + return; + basebonepose = (matrix4x4_t *)Mem_Alloc(tempmempool, loadmodel->num_bones * sizeof(matrix4x4_t)); + for (boneindex = 0;boneindex < loadmodel->num_bones;boneindex++) + { + Matrix4x4_FromBonePose6s(&bonematrix, loadmodel->num_posescale, loadmodel->data_poses6s + 6 * boneindex); + if (loadmodel->data_bones[boneindex].parent >= 0) + { + tempbonematrix = bonematrix; + Matrix4x4_Concat(&bonematrix, basebonepose + loadmodel->data_bones[boneindex].parent, &tempbonematrix); + } + basebonepose[boneindex] = bonematrix; + Matrix4x4_Invert_Simple(&tempbonematrix, basebonepose + boneindex); + Matrix4x4_ToArray12FloatD3D(&tempbonematrix, outinvmatrix + 12*boneindex); + } + Mem_Free(basebonepose); +} + +static void Mod_Alias_CalculateBoundingBox(void) +{ + int vnum; + qboolean firstvertex = true; + float dist, yawradius, radius; + float *v; + float *vertex3f; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + memset(frameblend, 0, sizeof(frameblend)); + frameblend[0].lerp = 1; + vertex3f = (float *) Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(float[3])); + VectorClear(loadmodel->normalmins); + VectorClear(loadmodel->normalmaxs); + yawradius = 0; + radius = 0; + for (frameblend[0].subframe = 0;frameblend[0].subframe < loadmodel->num_poses;frameblend[0].subframe++) + { + loadmodel->AnimateVertices(loadmodel, frameblend, NULL, vertex3f, NULL, NULL, NULL); + for (vnum = 0, v = vertex3f;vnum < loadmodel->surfmesh.num_vertices;vnum++, v += 3) + { + if (firstvertex) + { + firstvertex = false; + VectorCopy(v, loadmodel->normalmins); + VectorCopy(v, loadmodel->normalmaxs); + } + else + { + if (loadmodel->normalmins[0] > v[0]) loadmodel->normalmins[0] = v[0]; + if (loadmodel->normalmins[1] > v[1]) loadmodel->normalmins[1] = v[1]; + if (loadmodel->normalmins[2] > v[2]) loadmodel->normalmins[2] = v[2]; + if (loadmodel->normalmaxs[0] < v[0]) loadmodel->normalmaxs[0] = v[0]; + if (loadmodel->normalmaxs[1] < v[1]) loadmodel->normalmaxs[1] = v[1]; + if (loadmodel->normalmaxs[2] < v[2]) loadmodel->normalmaxs[2] = v[2]; + } + dist = v[0] * v[0] + v[1] * v[1]; + if (yawradius < dist) + yawradius = dist; + dist += v[2] * v[2]; + if (radius < dist) + radius = dist; + } + } + if (vertex3f) + Mem_Free(vertex3f); + radius = sqrt(radius); + yawradius = sqrt(yawradius); + loadmodel->yawmins[0] = loadmodel->yawmins[1] = -yawradius; + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = yawradius; + loadmodel->yawmins[2] = loadmodel->normalmins[2]; + loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; + loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -radius; + loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = radius; + loadmodel->radius = radius; + loadmodel->radius2 = radius * radius; +} + +static void Mod_Alias_MorphMesh_CompileFrames(void) +{ + int i, j; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + unsigned char *datapointer; + memset(frameblend, 0, sizeof(frameblend)); + frameblend[0].lerp = 1; + datapointer = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * (sizeof(float[3]) * 4 + loadmodel->surfmesh.num_morphframes * sizeof(texvecvertex_t))); + loadmodel->surfmesh.data_vertex3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_morphtexvecvertex = (texvecvertex_t *)datapointer;datapointer += loadmodel->surfmesh.num_morphframes * loadmodel->surfmesh.num_vertices * sizeof(texvecvertex_t); + // this counts down from the last frame to the first so that the final data in surfmesh is for frame zero (which is what the renderer expects to be there) + for (i = loadmodel->surfmesh.num_morphframes-1;i >= 0;i--) + { + frameblend[0].subframe = i; + loadmodel->AnimateVertices(loadmodel, frameblend, NULL, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_normal3f, NULL, NULL); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + // encode the svector and tvector in 3 byte format for permanent storage + for (j = 0;j < loadmodel->surfmesh.num_vertices;j++) + { + VectorScaleCast(loadmodel->surfmesh.data_svector3f + j * 3, 127.0f, signed char, loadmodel->surfmesh.data_morphtexvecvertex[i*loadmodel->surfmesh.num_vertices+j].svec); + VectorScaleCast(loadmodel->surfmesh.data_tvector3f + j * 3, 127.0f, signed char, loadmodel->surfmesh.data_morphtexvecvertex[i*loadmodel->surfmesh.num_vertices+j].tvec); + } + } +} + +static void Mod_MDLMD2MD3_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + int i; + float segmentmins[3], segmentmaxs[3]; + msurface_t *surface; + static int maxvertices = 0; + static float *vertex3f = NULL; + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + if (maxvertices < model->surfmesh.num_vertices) + { + if (vertex3f) + Z_Free(vertex3f); + maxvertices = (model->surfmesh.num_vertices + 255) & ~255; + vertex3f = (float *)Z_Malloc(maxvertices * sizeof(float[3])); + } + segmentmins[0] = min(start[0], end[0]) - 1; + segmentmins[1] = min(start[1], end[1]) - 1; + segmentmins[2] = min(start[2], end[2]) - 1; + segmentmaxs[0] = max(start[0], end[0]) + 1; + segmentmaxs[1] = max(start[1], end[1]) + 1; + segmentmaxs[2] = max(start[2], end[2]) + 1; + model->AnimateVertices(model, frameblend, skeleton, vertex3f, NULL, NULL, NULL); + for (i = 0, surface = model->data_surfaces;i < model->num_surfaces;i++, surface++) + Collision_TraceLineTriangleMeshFloat(trace, start, end, model->surfmesh.num_triangles, model->surfmesh.data_element3i, vertex3f, 0, NULL, SUPERCONTENTS_SOLID | (surface->texture->basematerialflags & MATERIALFLAGMASK_TRANSLUCENT ? 0 : SUPERCONTENTS_OPAQUE), 0, surface->texture, segmentmins, segmentmaxs); +} + +static int maxvertices = 0; +static float *vertex3f = NULL; + +static void Mod_MDLMD2MD3_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask) +{ + int i; + vec3_t shiftstart, shiftend; + float segmentmins[3], segmentmaxs[3]; + msurface_t *surface; + colboxbrushf_t thisbrush_start, thisbrush_end; + vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; + + if (VectorCompare(boxmins, boxmaxs)) + { + VectorAdd(start, boxmins, shiftstart); + VectorAdd(end, boxmins, shiftend); + Mod_MDLMD2MD3_TraceLine(model, frameblend, skeleton, trace, shiftstart, shiftend, hitsupercontentsmask); + VectorSubtract(trace->endpos, boxmins, trace->endpos); + return; + } + + // box trace, performed as brush trace + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + if (maxvertices < model->surfmesh.num_vertices) + { + if (vertex3f) + Z_Free(vertex3f); + maxvertices = (model->surfmesh.num_vertices + 255) & ~255; + vertex3f = (float *)Z_Malloc(maxvertices * sizeof(float[3])); + } + segmentmins[0] = min(start[0], end[0]) + boxmins[0] - 1; + segmentmins[1] = min(start[1], end[1]) + boxmins[1] - 1; + segmentmins[2] = min(start[2], end[2]) + boxmins[2] - 1; + segmentmaxs[0] = max(start[0], end[0]) + boxmaxs[0] + 1; + segmentmaxs[1] = max(start[1], end[1]) + boxmaxs[1] + 1; + segmentmaxs[2] = max(start[2], end[2]) + boxmaxs[2] + 1; + VectorAdd(start, boxmins, boxstartmins); + VectorAdd(start, boxmaxs, boxstartmaxs); + VectorAdd(end, boxmins, boxendmins); + VectorAdd(end, boxmaxs, boxendmaxs); + Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); + if (maxvertices < model->surfmesh.num_vertices) + { + if (vertex3f) + Z_Free(vertex3f); + maxvertices = (model->surfmesh.num_vertices + 255) & ~255; + vertex3f = (float *)Z_Malloc(maxvertices * sizeof(float[3])); + } + model->AnimateVertices(model, frameblend, skeleton, vertex3f, NULL, NULL, NULL); + for (i = 0, surface = model->data_surfaces;i < model->num_surfaces;i++, surface++) + Collision_TraceBrushTriangleMeshFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, model->surfmesh.num_triangles, model->surfmesh.data_element3i, vertex3f, 0, NULL, SUPERCONTENTS_SOLID | (surface->texture->basematerialflags & MATERIALFLAGMASK_TRANSLUCENT ? 0 : SUPERCONTENTS_OPAQUE), 0, surface->texture, segmentmins, segmentmaxs); +} + +static void Mod_ConvertAliasVerts (int inverts, trivertx_t *v, trivertx_t *out, int *vertremap) +{ + int i, j; + for (i = 0;i < inverts;i++) + { + if (vertremap[i] < 0 && vertremap[i+inverts] < 0) // only used vertices need apply... + continue; + j = vertremap[i]; // not onseam + if (j >= 0) + out[j] = v[i]; + j = vertremap[i+inverts]; // onseam + if (j >= 0) + out[j] = v[i]; + } +} + +static void Mod_MDL_LoadFrames (unsigned char* datapointer, int inverts, int *vertremap) +{ + int i, f, pose, groupframes; + float interval; + daliasframetype_t *pframetype; + daliasframe_t *pinframe; + daliasgroup_t *group; + daliasinterval_t *intervals; + animscene_t *scene; + pose = 0; + scene = loadmodel->animscenes; + for (f = 0;f < loadmodel->numframes;f++) + { + pframetype = (daliasframetype_t *)datapointer; + datapointer += sizeof(daliasframetype_t); + if (LittleLong (pframetype->type) == ALIAS_SINGLE) + { + // a single frame is still treated as a group + interval = 0.1f; + groupframes = 1; + } + else + { + // read group header + group = (daliasgroup_t *)datapointer; + datapointer += sizeof(daliasgroup_t); + groupframes = LittleLong (group->numframes); + + // intervals (time per frame) + intervals = (daliasinterval_t *)datapointer; + datapointer += sizeof(daliasinterval_t) * groupframes; + + interval = LittleFloat (intervals->interval); // FIXME: support variable framerate groups + if (interval < 0.01f) + { + Con_Printf("%s has an invalid interval %f, changing to 0.1\n", loadmodel->name, interval); + interval = 0.1f; + } + } + + // get scene name from first frame + pinframe = (daliasframe_t *)datapointer; + + strlcpy(scene->name, pinframe->name, sizeof(scene->name)); + scene->firstframe = pose; + scene->framecount = groupframes; + scene->framerate = 1.0f / interval; + scene->loop = true; + scene++; + + // read frames + for (i = 0;i < groupframes;i++) + { + pinframe = (daliasframe_t *)datapointer; + datapointer += sizeof(daliasframe_t); + Mod_ConvertAliasVerts(inverts, (trivertx_t *)datapointer, loadmodel->surfmesh.data_morphmdlvertex + pose * loadmodel->surfmesh.num_vertices, vertremap); + datapointer += sizeof(trivertx_t) * inverts; + pose++; + } + } +} + +static void Mod_BuildAliasSkinFromSkinFrame(texture_t *texture, skinframe_t *skinframe) +{ + if (cls.state == ca_dedicated) + return; + // hack + if (!skinframe) + skinframe = R_SkinFrame_LoadMissing(); + memset(texture, 0, sizeof(*texture)); + texture->currentframe = texture; + //texture->animated = false; + texture->numskinframes = 1; + texture->skinframerate = 1; + texture->skinframes[0] = skinframe; + texture->currentskinframe = skinframe; + //texture->backgroundnumskinframes = 0; + //texture->customblendfunc[0] = 0; + //texture->customblendfunc[1] = 0; + //texture->surfaceflags = 0; + //texture->supercontents = 0; + //texture->surfaceparms = 0; + //texture->textureflags = 0; + + texture->basematerialflags = MATERIALFLAG_WALL; + if (texture->currentskinframe->hasalpha) + texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + texture->currentmaterialflags = texture->basematerialflags; + texture->offsetmapping = OFFSETMAPPING_DEFAULT; + texture->offsetscale = 1; + texture->specularscalemod = 1; + texture->specularpowermod = 1; + texture->surfaceflags = 0; + texture->supercontents = SUPERCONTENTS_SOLID; + if (!(texture->basematerialflags & MATERIALFLAG_BLENDED)) + texture->supercontents |= SUPERCONTENTS_OPAQUE; +} + +void Mod_BuildAliasSkinsFromSkinFiles(texture_t *skin, skinfile_t *skinfile, const char *meshname, const char *shadername) +{ + int i; + static char stripbuf[MAX_QPATH]; + skinfileitem_t *skinfileitem; + if(developer_extra.integer) + Con_DPrintf("Looking up texture for %s (default: %s)\n", meshname, shadername); + if (skinfile) + { + // the skin += loadmodel->num_surfaces part of this is because data_textures on alias models is arranged as [numskins][numsurfaces] + for (i = 0;skinfile;skinfile = skinfile->next, i++, skin += loadmodel->num_surfaces) + { + memset(skin, 0, sizeof(*skin)); + // see if a mesh + for (skinfileitem = skinfile->items;skinfileitem;skinfileitem = skinfileitem->next) + { + // leave the skin unitialized (nodraw) if the replacement is "common/nodraw" or "textures/common/nodraw" + if (!strcmp(skinfileitem->name, meshname)) + { + Image_StripImageExtension(skinfileitem->replacement, stripbuf, sizeof(stripbuf)); + if(developer_extra.integer) + Con_DPrintf("--> got %s from skin file\n", stripbuf); + Mod_LoadTextureFromQ3Shader(skin, stripbuf, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); + break; + } + } + if (!skinfileitem) + { + // don't render unmentioned meshes + Mod_BuildAliasSkinFromSkinFrame(skin, NULL); + if(developer_extra.integer) + Con_DPrintf("--> skipping\n"); + skin->basematerialflags = skin->currentmaterialflags = MATERIALFLAG_NOSHADOW | MATERIALFLAG_NODRAW; + } + } + } + else + { + if(developer_extra.integer) + Con_DPrintf("--> using default\n"); + Image_StripImageExtension(shadername, stripbuf, sizeof(stripbuf)); + Mod_LoadTextureFromQ3Shader(skin, stripbuf, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); + } +} + +#define BOUNDI(VALUE,MIN,MAX) if (VALUE < MIN || VALUE >= MAX) Host_Error("model %s has an invalid ##VALUE (%d exceeds %d - %d)", loadmodel->name, VALUE, MIN, MAX); +#define BOUNDF(VALUE,MIN,MAX) if (VALUE < MIN || VALUE >= MAX) Host_Error("model %s has an invalid ##VALUE (%f exceeds %f - %f)", loadmodel->name, VALUE, MIN, MAX); +void Mod_IDP0_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, version, totalskins, skinwidth, skinheight, groupframes, groupskins, numverts; + float scales, scalet, interval; + msurface_t *surface; + unsigned char *data; + mdl_t *pinmodel; + stvert_t *pinstverts; + dtriangle_t *pintriangles; + daliasskintype_t *pinskintype; + daliasskingroup_t *pinskingroup; + daliasskininterval_t *pinskinintervals; + daliasframetype_t *pinframetype; + daliasgroup_t *pinframegroup; + unsigned char *datapointer, *startframes, *startskins; + char name[MAX_QPATH]; + skinframe_t *tempskinframe; + animscene_t *tempskinscenes; + texture_t *tempaliasskins; + float *vertst; + int *vertonseam, *vertremap; + skinfile_t *skinfiles; + + datapointer = (unsigned char *)buffer; + pinmodel = (mdl_t *)datapointer; + datapointer += sizeof(mdl_t); + + version = LittleLong (pinmodel->version); + if (version != ALIAS_VERSION) + Host_Error ("%s has wrong version number (%i should be %i)", + loadmodel->name, version, ALIAS_VERSION); + + loadmodel->modeldatatypestring = "MDL"; + + loadmodel->type = mod_alias; + loadmodel->AnimateVertices = Mod_MDL_AnimateVertices; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + // FIXME add TraceBrush! + loadmodel->PointSuperContents = NULL; + + loadmodel->num_surfaces = 1; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->sortedmodelsurfaces[0] = 0; + + loadmodel->numskins = LittleLong(pinmodel->numskins); + BOUNDI(loadmodel->numskins,0,65536); + skinwidth = LittleLong (pinmodel->skinwidth); + BOUNDI(skinwidth,0,65536); + skinheight = LittleLong (pinmodel->skinheight); + BOUNDI(skinheight,0,65536); + numverts = LittleLong(pinmodel->numverts); + BOUNDI(numverts,0,65536); + loadmodel->surfmesh.num_triangles = LittleLong(pinmodel->numtris); + BOUNDI(loadmodel->surfmesh.num_triangles,0,65536); + loadmodel->numframes = LittleLong(pinmodel->numframes); + BOUNDI(loadmodel->numframes,0,65536); + loadmodel->synctype = (synctype_t)LittleLong (pinmodel->synctype); + BOUNDI((int)loadmodel->synctype,0,2); + // convert model flags to EF flags (MF_ROCKET becomes EF_ROCKET, etc) + i = LittleLong (pinmodel->flags); + loadmodel->effects = ((i & 255) << 24) | (i & 0x00FFFF00); + + for (i = 0;i < 3;i++) + { + loadmodel->surfmesh.num_morphmdlframescale[i] = LittleFloat (pinmodel->scale[i]); + loadmodel->surfmesh.num_morphmdlframetranslate[i] = LittleFloat (pinmodel->scale_origin[i]); + } + + startskins = datapointer; + totalskins = 0; + for (i = 0;i < loadmodel->numskins;i++) + { + pinskintype = (daliasskintype_t *)datapointer; + datapointer += sizeof(daliasskintype_t); + if (LittleLong(pinskintype->type) == ALIAS_SKIN_SINGLE) + groupskins = 1; + else + { + pinskingroup = (daliasskingroup_t *)datapointer; + datapointer += sizeof(daliasskingroup_t); + groupskins = LittleLong(pinskingroup->numskins); + datapointer += sizeof(daliasskininterval_t) * groupskins; + } + + for (j = 0;j < groupskins;j++) + { + datapointer += skinwidth * skinheight; + totalskins++; + } + } + + pinstverts = (stvert_t *)datapointer; + datapointer += sizeof(stvert_t) * numverts; + + pintriangles = (dtriangle_t *)datapointer; + datapointer += sizeof(dtriangle_t) * loadmodel->surfmesh.num_triangles; + + startframes = datapointer; + loadmodel->surfmesh.num_morphframes = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + pinframetype = (daliasframetype_t *)datapointer; + datapointer += sizeof(daliasframetype_t); + if (LittleLong (pinframetype->type) == ALIAS_SINGLE) + groupframes = 1; + else + { + pinframegroup = (daliasgroup_t *)datapointer; + datapointer += sizeof(daliasgroup_t); + groupframes = LittleLong(pinframegroup->numframes); + datapointer += sizeof(daliasinterval_t) * groupframes; + } + + for (j = 0;j < groupframes;j++) + { + datapointer += sizeof(daliasframe_t); + datapointer += sizeof(trivertx_t) * numverts; + loadmodel->surfmesh.num_morphframes++; + } + } + loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; + + // store texture coordinates into temporary array, they will be stored + // after usage is determined (triangle data) + vertst = (float *)Mem_Alloc(tempmempool, numverts * 2 * sizeof(float[2])); + vertremap = (int *)Mem_Alloc(tempmempool, numverts * 3 * sizeof(int)); + vertonseam = vertremap + numverts * 2; + + scales = 1.0 / skinwidth; + scalet = 1.0 / skinheight; + for (i = 0;i < numverts;i++) + { + vertonseam[i] = LittleLong(pinstverts[i].onseam); + vertst[i*2+0] = (LittleLong(pinstverts[i].s) + 0.5) * scales; + vertst[i*2+1] = (LittleLong(pinstverts[i].t) + 0.5) * scalet; + vertst[(i+numverts)*2+0] = vertst[i*2+0] + 0.5; + vertst[(i+numverts)*2+1] = vertst[i*2+1]; + } + +// load triangle data + loadmodel->surfmesh.data_element3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * loadmodel->surfmesh.num_triangles); + + // read the triangle elements + for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) + for (j = 0;j < 3;j++) + loadmodel->surfmesh.data_element3i[i*3+j] = LittleLong(pintriangles[i].vertindex[j]); + // validate (note numverts is used because this is the original data) + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, numverts, __FILE__, __LINE__); + // now butcher the elements according to vertonseam and tri->facesfront + // and then compact the vertex set to remove duplicates + for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) + if (!LittleLong(pintriangles[i].facesfront)) // backface + for (j = 0;j < 3;j++) + if (vertonseam[loadmodel->surfmesh.data_element3i[i*3+j]]) + loadmodel->surfmesh.data_element3i[i*3+j] += numverts; + // count the usage + // (this uses vertremap to count usage to save some memory) + for (i = 0;i < numverts*2;i++) + vertremap[i] = 0; + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + vertremap[loadmodel->surfmesh.data_element3i[i]]++; + // build remapping table and compact array + loadmodel->surfmesh.num_vertices = 0; + for (i = 0;i < numverts*2;i++) + { + if (vertremap[i]) + { + vertremap[i] = loadmodel->surfmesh.num_vertices; + vertst[loadmodel->surfmesh.num_vertices*2+0] = vertst[i*2+0]; + vertst[loadmodel->surfmesh.num_vertices*2+1] = vertst[i*2+1]; + loadmodel->surfmesh.num_vertices++; + } + else + vertremap[i] = -1; // not used at all + } + // remap the elements to the new vertex set + for (i = 0;i < loadmodel->surfmesh.num_triangles * 3;i++) + loadmodel->surfmesh.data_element3i[i] = vertremap[loadmodel->surfmesh.data_element3i[i]]; + // store the texture coordinates + loadmodel->surfmesh.data_texcoordtexture2f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[2]) * loadmodel->surfmesh.num_vertices); + for (i = 0;i < loadmodel->surfmesh.num_vertices;i++) + { + loadmodel->surfmesh.data_texcoordtexture2f[i*2+0] = vertst[i*2+0]; + loadmodel->surfmesh.data_texcoordtexture2f[i*2+1] = vertst[i*2+1]; + } + + // generate ushort elements array if possible + if (loadmodel->surfmesh.num_vertices <= 65536) + loadmodel->surfmesh.data_element3s = (unsigned short *)Mem_Alloc(loadmodel->mempool, sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles); + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + +// load the frames + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); + loadmodel->surfmesh.data_morphmdlvertex = (trivertx_t *)Mem_Alloc(loadmodel->mempool, sizeof(trivertx_t) * loadmodel->surfmesh.num_morphframes * loadmodel->surfmesh.num_vertices); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_triangles * sizeof(int[3])); + } + Mod_MDL_LoadFrames (startframes, numverts, vertremap); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + Mod_Alias_CalculateBoundingBox(); + Mod_Alias_MorphMesh_CompileFrames(); + + Mem_Free(vertst); + Mem_Free(vertremap); + + // load the skins + skinfiles = Mod_LoadSkinFiles(); + if (skinfiles) + { + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numskins * sizeof(animscene_t)); + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures, skinfiles, "default", ""); + Mod_FreeSkinFiles(skinfiles); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + } + else + { + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numskins * sizeof(animscene_t)); + loadmodel->num_textures = loadmodel->num_surfaces * totalskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * totalskins * sizeof(texture_t)); + totalskins = 0; + datapointer = startskins; + for (i = 0;i < loadmodel->numskins;i++) + { + pinskintype = (daliasskintype_t *)datapointer; + datapointer += sizeof(daliasskintype_t); + + if (pinskintype->type == ALIAS_SKIN_SINGLE) + { + groupskins = 1; + interval = 0.1f; + } + else + { + pinskingroup = (daliasskingroup_t *)datapointer; + datapointer += sizeof(daliasskingroup_t); + + groupskins = LittleLong (pinskingroup->numskins); + + pinskinintervals = (daliasskininterval_t *)datapointer; + datapointer += sizeof(daliasskininterval_t) * groupskins; + + interval = LittleFloat(pinskinintervals[0].interval); + if (interval < 0.01f) + { + Con_Printf("%s has an invalid interval %f, changing to 0.1\n", loadmodel->name, interval); + interval = 0.1f; + } + } + + dpsnprintf(loadmodel->skinscenes[i].name, sizeof(loadmodel->skinscenes[i].name), "skin %i", i); + loadmodel->skinscenes[i].firstframe = totalskins; + loadmodel->skinscenes[i].framecount = groupskins; + loadmodel->skinscenes[i].framerate = 1.0f / interval; + loadmodel->skinscenes[i].loop = true; + + for (j = 0;j < groupskins;j++) + { + if (groupskins > 1) + dpsnprintf (name, sizeof(name), "%s_%i_%i", loadmodel->name, i, j); + else + dpsnprintf (name, sizeof(name), "%s_%i", loadmodel->name, i); + if (!Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, name, false, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS)) + Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, R_SkinFrame_LoadInternalQuake(name, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_PICMIP, true, r_fullbrights.integer, (unsigned char *)datapointer, skinwidth, skinheight)); + datapointer += skinwidth * skinheight; + totalskins++; + } + } + // check for skins that don't exist in the model, but do exist as external images + // (this was added because yummyluv kept pestering me about support for it) + // TODO: support shaders here? + while ((tempskinframe = R_SkinFrame_LoadExternal(va("%s_%i", loadmodel->name, loadmodel->numskins), (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS, false))) + { + // expand the arrays to make room + tempskinscenes = loadmodel->skinscenes; + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, (loadmodel->numskins + 1) * sizeof(animscene_t)); + memcpy(loadmodel->skinscenes, tempskinscenes, loadmodel->numskins * sizeof(animscene_t)); + Mem_Free(tempskinscenes); + + tempaliasskins = loadmodel->data_textures; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * (totalskins + 1) * sizeof(texture_t)); + memcpy(loadmodel->data_textures, tempaliasskins, loadmodel->num_surfaces * totalskins * sizeof(texture_t)); + Mem_Free(tempaliasskins); + + // store the info about the new skin + Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, tempskinframe); + strlcpy(loadmodel->skinscenes[loadmodel->numskins].name, name, sizeof(loadmodel->skinscenes[loadmodel->numskins].name)); + loadmodel->skinscenes[loadmodel->numskins].firstframe = totalskins; + loadmodel->skinscenes[loadmodel->numskins].framecount = 1; + loadmodel->skinscenes[loadmodel->numskins].framerate = 10.0f; + loadmodel->skinscenes[loadmodel->numskins].loop = true; + + //increase skin counts + loadmodel->numskins++; + totalskins++; + + // fix up the pointers since they are pointing at the old textures array + // FIXME: this is a hack! + for (j = 0;j < loadmodel->numskins * loadmodel->num_surfaces;j++) + loadmodel->data_textures[j].currentframe = &loadmodel->data_textures[j]; + } + } + + surface = loadmodel->data_surfaces; + surface->texture = loadmodel->data_textures; + surface->num_firsttriangle = 0; + surface->num_triangles = loadmodel->surfmesh.num_triangles; + surface->num_firstvertex = 0; + surface->num_vertices = loadmodel->surfmesh.num_vertices; + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } +} + +void Mod_IDP2_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, hashindex, numxyz, numst, xyz, st, skinwidth, skinheight, *vertremap, version, end; + float iskinwidth, iskinheight; + unsigned char *data; + msurface_t *surface; + md2_t *pinmodel; + unsigned char *base, *datapointer; + md2frame_t *pinframe; + char *inskin; + md2triangle_t *intri; + unsigned short *inst; + struct md2verthash_s + { + struct md2verthash_s *next; + unsigned short xyz; + unsigned short st; + } + *hash, **md2verthash, *md2verthashdata; + skinfile_t *skinfiles; + + pinmodel = (md2_t *)buffer; + base = (unsigned char *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD2ALIAS_VERSION) + Host_Error ("%s has wrong version number (%i should be %i)", + loadmodel->name, version, MD2ALIAS_VERSION); + + loadmodel->modeldatatypestring = "MD2"; + + loadmodel->type = mod_alias; + loadmodel->AnimateVertices = Mod_MDL_AnimateVertices; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + + if (LittleLong(pinmodel->num_tris) < 1 || LittleLong(pinmodel->num_tris) > 65536) + Host_Error ("%s has invalid number of triangles: %i", loadmodel->name, LittleLong(pinmodel->num_tris)); + if (LittleLong(pinmodel->num_xyz) < 1 || LittleLong(pinmodel->num_xyz) > 65536) + Host_Error ("%s has invalid number of vertices: %i", loadmodel->name, LittleLong(pinmodel->num_xyz)); + if (LittleLong(pinmodel->num_frames) < 1 || LittleLong(pinmodel->num_frames) > 65536) + Host_Error ("%s has invalid number of frames: %i", loadmodel->name, LittleLong(pinmodel->num_frames)); + if (LittleLong(pinmodel->num_skins) < 0 || LittleLong(pinmodel->num_skins) > 256) + Host_Error ("%s has invalid number of skins: %i", loadmodel->name, LittleLong(pinmodel->num_skins)); + + end = LittleLong(pinmodel->ofs_end); + if (LittleLong(pinmodel->num_skins) >= 1 && (LittleLong(pinmodel->ofs_skins) <= 0 || LittleLong(pinmodel->ofs_skins) >= end)) + Host_Error ("%s is not a valid model", loadmodel->name); + if (LittleLong(pinmodel->ofs_st) <= 0 || LittleLong(pinmodel->ofs_st) >= end) + Host_Error ("%s is not a valid model", loadmodel->name); + if (LittleLong(pinmodel->ofs_tris) <= 0 || LittleLong(pinmodel->ofs_tris) >= end) + Host_Error ("%s is not a valid model", loadmodel->name); + if (LittleLong(pinmodel->ofs_frames) <= 0 || LittleLong(pinmodel->ofs_frames) >= end) + Host_Error ("%s is not a valid model", loadmodel->name); + if (LittleLong(pinmodel->ofs_glcmds) <= 0 || LittleLong(pinmodel->ofs_glcmds) >= end) + Host_Error ("%s is not a valid model", loadmodel->name); + + loadmodel->numskins = LittleLong(pinmodel->num_skins); + numxyz = LittleLong(pinmodel->num_xyz); + numst = LittleLong(pinmodel->num_st); + loadmodel->surfmesh.num_triangles = LittleLong(pinmodel->num_tris); + loadmodel->numframes = LittleLong(pinmodel->num_frames); + loadmodel->surfmesh.num_morphframes = loadmodel->numframes; + loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; + skinwidth = LittleLong(pinmodel->skinwidth); + skinheight = LittleLong(pinmodel->skinheight); + iskinwidth = 1.0f / skinwidth; + iskinheight = 1.0f / skinheight; + + loadmodel->num_surfaces = 1; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->numframes * sizeof(animscene_t) + loadmodel->numframes * sizeof(float[6]) + loadmodel->surfmesh.num_triangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? loadmodel->surfmesh.num_triangles * sizeof(int[3]) : 0)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->sortedmodelsurfaces[0] = 0; + loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); + loadmodel->surfmesh.data_morphmd2framesize6f = (float *)data;data += loadmodel->numframes * sizeof(float[6]); + loadmodel->surfmesh.data_element3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); + } + + loadmodel->synctype = ST_RAND; + + // load the skins + inskin = (char *)(base + LittleLong(pinmodel->ofs_skins)); + skinfiles = Mod_LoadSkinFiles(); + if (skinfiles) + { + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures, skinfiles, "default", ""); + Mod_FreeSkinFiles(skinfiles); + } + else if (loadmodel->numskins) + { + // skins found (most likely not a player model) + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); + for (i = 0;i < loadmodel->numskins;i++, inskin += MD2_SKINNAME) + Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i * loadmodel->num_surfaces, inskin, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); + } + else + { + // no skins (most likely a player model) + loadmodel->numskins = 1; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); + Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures, NULL); + } + + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // load the triangles and stvert data + inst = (unsigned short *)(base + LittleLong(pinmodel->ofs_st)); + intri = (md2triangle_t *)(base + LittleLong(pinmodel->ofs_tris)); + md2verthash = (struct md2verthash_s **)Mem_Alloc(tempmempool, 65536 * sizeof(hash)); + md2verthashdata = (struct md2verthash_s *)Mem_Alloc(tempmempool, loadmodel->surfmesh.num_triangles * 3 * sizeof(*hash)); + // swap the triangle list + loadmodel->surfmesh.num_vertices = 0; + for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) + { + for (j = 0;j < 3;j++) + { + xyz = (unsigned short) LittleShort (intri[i].index_xyz[j]); + st = (unsigned short) LittleShort (intri[i].index_st[j]); + if (xyz >= numxyz) + { + Con_Printf("%s has an invalid xyz index (%i) on triangle %i, resetting to 0\n", loadmodel->name, xyz, i); + xyz = 0; + } + if (st >= numst) + { + Con_Printf("%s has an invalid st index (%i) on triangle %i, resetting to 0\n", loadmodel->name, st, i); + st = 0; + } + hashindex = (xyz * 256 + st) & 65535; + for (hash = md2verthash[hashindex];hash;hash = hash->next) + if (hash->xyz == xyz && hash->st == st) + break; + if (hash == NULL) + { + hash = md2verthashdata + loadmodel->surfmesh.num_vertices++; + hash->xyz = xyz; + hash->st = st; + hash->next = md2verthash[hashindex]; + md2verthash[hashindex] = hash; + } + loadmodel->surfmesh.data_element3i[i*3+j] = (hash - md2verthashdata); + } + } + + vertremap = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(int)); + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(float[2]) + loadmodel->surfmesh.num_vertices * loadmodel->surfmesh.num_morphframes * sizeof(trivertx_t)); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[2]); + loadmodel->surfmesh.data_morphmdlvertex = (trivertx_t *)data;data += loadmodel->surfmesh.num_vertices * loadmodel->surfmesh.num_morphframes * sizeof(trivertx_t); + for (i = 0;i < loadmodel->surfmesh.num_vertices;i++) + { + int sts, stt; + hash = md2verthashdata + i; + vertremap[i] = hash->xyz; + sts = LittleShort(inst[hash->st*2+0]); + stt = LittleShort(inst[hash->st*2+1]); + if (sts < 0 || sts >= skinwidth || stt < 0 || stt >= skinheight) + { + Con_Printf("%s has an invalid skin coordinate (%i %i) on vert %i, changing to 0 0\n", loadmodel->name, sts, stt, i); + sts = 0; + stt = 0; + } + loadmodel->surfmesh.data_texcoordtexture2f[i*2+0] = sts * iskinwidth; + loadmodel->surfmesh.data_texcoordtexture2f[i*2+1] = stt * iskinheight; + } + + Mem_Free(md2verthash); + Mem_Free(md2verthashdata); + + // generate ushort elements array if possible + if (loadmodel->surfmesh.num_vertices <= 65536) + loadmodel->surfmesh.data_element3s = (unsigned short *)Mem_Alloc(loadmodel->mempool, sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles); + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + + // load the frames + datapointer = (base + LittleLong(pinmodel->ofs_frames)); + for (i = 0;i < loadmodel->surfmesh.num_morphframes;i++) + { + int k; + trivertx_t *v; + trivertx_t *out; + pinframe = (md2frame_t *)datapointer; + datapointer += sizeof(md2frame_t); + // store the frame scale/translate into the appropriate array + for (j = 0;j < 3;j++) + { + loadmodel->surfmesh.data_morphmd2framesize6f[i*6+j] = LittleFloat(pinframe->scale[j]); + loadmodel->surfmesh.data_morphmd2framesize6f[i*6+3+j] = LittleFloat(pinframe->translate[j]); + } + // convert the vertices + v = (trivertx_t *)datapointer; + out = loadmodel->surfmesh.data_morphmdlvertex + i * loadmodel->surfmesh.num_vertices; + for (k = 0;k < loadmodel->surfmesh.num_vertices;k++) + out[k] = v[vertremap[k]]; + datapointer += numxyz * sizeof(trivertx_t); + + strlcpy(loadmodel->animscenes[i].name, pinframe->name, sizeof(loadmodel->animscenes[i].name)); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].framerate = 10; + loadmodel->animscenes[i].loop = true; + } + + Mem_Free(vertremap); + + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + Mod_Alias_CalculateBoundingBox(); + Mod_Alias_MorphMesh_CompileFrames(); + + surface = loadmodel->data_surfaces; + surface->texture = loadmodel->data_textures; + surface->num_firsttriangle = 0; + surface->num_triangles = loadmodel->surfmesh.num_triangles; + surface->num_firstvertex = 0; + surface->num_vertices = loadmodel->surfmesh.num_vertices; + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } +} + +void Mod_IDP3_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, k, version, meshvertices, meshtriangles; + unsigned char *data; + msurface_t *surface; + md3modelheader_t *pinmodel; + md3frameinfo_t *pinframe; + md3mesh_t *pinmesh; + md3tag_t *pintag; + skinfile_t *skinfiles; + + pinmodel = (md3modelheader_t *)buffer; + + if (memcmp(pinmodel->identifier, "IDP3", 4)) + Host_Error ("%s is not a MD3 (IDP3) file", loadmodel->name); + version = LittleLong (pinmodel->version); + if (version != MD3VERSION) + Host_Error ("%s has wrong version number (%i should be %i)", + loadmodel->name, version, MD3VERSION); + + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + loadmodel->modeldatatypestring = "MD3"; + + loadmodel->type = mod_alias; + loadmodel->AnimateVertices = Mod_MD3_AnimateVertices; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + loadmodel->synctype = ST_RAND; + // convert model flags to EF flags (MF_ROCKET becomes EF_ROCKET, etc) + i = LittleLong (pinmodel->flags); + loadmodel->effects = ((i & 255) << 24) | (i & 0x00FFFF00); + + // set up some global info about the model + loadmodel->numframes = LittleLong(pinmodel->num_frames); + loadmodel->num_surfaces = LittleLong(pinmodel->num_meshes); + + // make skinscenes for the skins (no groups) + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // load frameinfo + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numframes * sizeof(animscene_t)); + for (i = 0, pinframe = (md3frameinfo_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_frameinfo));i < loadmodel->numframes;i++, pinframe++) + { + strlcpy(loadmodel->animscenes[i].name, pinframe->name, sizeof(loadmodel->animscenes[i].name)); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].framerate = 10; + loadmodel->animscenes[i].loop = true; + } + + // load tags + loadmodel->num_tagframes = loadmodel->numframes; + loadmodel->num_tags = LittleLong(pinmodel->num_tags); + loadmodel->data_tags = (aliastag_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_tagframes * loadmodel->num_tags * sizeof(aliastag_t)); + for (i = 0, pintag = (md3tag_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_tags));i < loadmodel->num_tagframes * loadmodel->num_tags;i++, pintag++) + { + strlcpy(loadmodel->data_tags[i].name, pintag->name, sizeof(loadmodel->data_tags[i].name)); + for (j = 0;j < 9;j++) + loadmodel->data_tags[i].matrixgl[j] = LittleFloat(pintag->rotationmatrix[j]); + for (j = 0;j < 3;j++) + loadmodel->data_tags[i].matrixgl[9+j] = LittleFloat(pintag->origin[j]); + //Con_Printf("model \"%s\" frame #%i tag #%i \"%s\"\n", loadmodel->name, i / loadmodel->num_tags, i % loadmodel->num_tags, loadmodel->data_tags[i].name); + } + + // load meshes + meshvertices = 0; + meshtriangles = 0; + for (i = 0, pinmesh = (md3mesh_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_meshes));i < loadmodel->num_surfaces;i++, pinmesh = (md3mesh_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_end))) + { + if (memcmp(pinmesh->identifier, "IDP3", 4)) + Host_Error("Mod_IDP3_Load: invalid mesh identifier (not IDP3)"); + if (LittleLong(pinmesh->num_frames) != loadmodel->numframes) + Host_Error("Mod_IDP3_Load: mesh numframes differs from header"); + meshvertices += LittleLong(pinmesh->num_vertices); + meshtriangles += LittleLong(pinmesh->num_triangles); + } + + loadmodel->nummodelsurfaces = loadmodel->num_surfaces; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + meshvertices * sizeof(float[2]) + meshvertices * loadmodel->numframes * sizeof(md3vertex_t)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + loadmodel->surfmesh.num_morphframes = loadmodel->numframes; // TODO: remove? + loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; + loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); + loadmodel->surfmesh.data_morphmd3vertex = (md3vertex_t *)data;data += meshvertices * loadmodel->numframes * sizeof(md3vertex_t); + if (meshvertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); + } + + meshvertices = 0; + meshtriangles = 0; + for (i = 0, pinmesh = (md3mesh_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_meshes));i < loadmodel->num_surfaces;i++, pinmesh = (md3mesh_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_end))) + { + if (memcmp(pinmesh->identifier, "IDP3", 4)) + Host_Error("Mod_IDP3_Load: invalid mesh identifier (not IDP3)"); + loadmodel->sortedmodelsurfaces[i] = i; + surface = loadmodel->data_surfaces + i; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = meshtriangles; + surface->num_triangles = LittleLong(pinmesh->num_triangles); + surface->num_firstvertex = meshvertices; + surface->num_vertices = LittleLong(pinmesh->num_vertices); + meshvertices += surface->num_vertices; + meshtriangles += surface->num_triangles; + + for (j = 0;j < surface->num_triangles * 3;j++) + loadmodel->surfmesh.data_element3i[j + surface->num_firsttriangle * 3] = surface->num_firstvertex + LittleLong(((int *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_elements)))[j]); + for (j = 0;j < surface->num_vertices;j++) + { + loadmodel->surfmesh.data_texcoordtexture2f[(j + surface->num_firstvertex) * 2 + 0] = LittleFloat(((float *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_texcoords)))[j * 2 + 0]); + loadmodel->surfmesh.data_texcoordtexture2f[(j + surface->num_firstvertex) * 2 + 1] = LittleFloat(((float *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_texcoords)))[j * 2 + 1]); + } + for (j = 0;j < loadmodel->numframes;j++) + { + const md3vertex_t *in = (md3vertex_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_framevertices)) + j * surface->num_vertices; + md3vertex_t *out = loadmodel->surfmesh.data_morphmd3vertex + surface->num_firstvertex + j * loadmodel->surfmesh.num_vertices; + for (k = 0;k < surface->num_vertices;k++, in++, out++) + { + out->origin[0] = LittleShort(in->origin[0]); + out->origin[1] = LittleShort(in->origin[1]); + out->origin[2] = LittleShort(in->origin[2]); + out->pitch = in->pitch; + out->yaw = in->yaw; + } + } + + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, pinmesh->name, LittleLong(pinmesh->num_shaders) >= 1 ? ((md3shader_t *)((unsigned char *) pinmesh + LittleLong(pinmesh->lump_shaders)))->name : ""); + + Mod_ValidateElements(loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3, surface->num_triangles, surface->num_firstvertex, surface->num_vertices, __FILE__, __LINE__); + } + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + Mod_Alias_MorphMesh_CompileFrames(); + Mod_Alias_CalculateBoundingBox(); + Mod_FreeSkinFiles(skinfiles); + Mod_MakeSortedSurfaces(loadmodel); + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 + || (loadmodel->animscenes && loadmodel->animscenes[0].framecount > 1); + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } +} + +void Mod_ZYMOTICMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + zymtype1header_t *pinmodel, *pheader; + unsigned char *pbase; + int i, j, k, numposes, meshvertices, meshtriangles, *bonecount, *vertbonecounts, count, *renderlist, *renderlistend, *outelements; + float modelradius, corner[2], *poses, *intexcoord2f, *outtexcoord2f, *bonepose, f, biggestorigin, tempvec[3], modelscale; + zymvertex_t *verts, *vertdata; + zymscene_t *scene; + zymbone_t *bone; + char *shadername; + skinfile_t *skinfiles; + unsigned char *data; + msurface_t *surface; + + pinmodel = (zymtype1header_t *)buffer; + pbase = (unsigned char *)buffer; + if (memcmp(pinmodel->id, "ZYMOTICMODEL", 12)) + Host_Error ("Mod_ZYMOTICMODEL_Load: %s is not a zymotic model", loadmodel->name); + if (BigLong(pinmodel->type) != 1) + Host_Error ("Mod_ZYMOTICMODEL_Load: only type 1 (skeletal pose) models are currently supported (name = %s)", loadmodel->name); + + loadmodel->modeldatatypestring = "ZYM"; + + loadmodel->type = mod_alias; + loadmodel->synctype = ST_RAND; + + // byteswap header + pheader = pinmodel; + pheader->type = BigLong(pinmodel->type); + pheader->filesize = BigLong(pinmodel->filesize); + pheader->mins[0] = BigFloat(pinmodel->mins[0]); + pheader->mins[1] = BigFloat(pinmodel->mins[1]); + pheader->mins[2] = BigFloat(pinmodel->mins[2]); + pheader->maxs[0] = BigFloat(pinmodel->maxs[0]); + pheader->maxs[1] = BigFloat(pinmodel->maxs[1]); + pheader->maxs[2] = BigFloat(pinmodel->maxs[2]); + pheader->radius = BigFloat(pinmodel->radius); + pheader->numverts = BigLong(pinmodel->numverts); + pheader->numtris = BigLong(pinmodel->numtris); + pheader->numshaders = BigLong(pinmodel->numshaders); + pheader->numbones = BigLong(pinmodel->numbones); + pheader->numscenes = BigLong(pinmodel->numscenes); + pheader->lump_scenes.start = BigLong(pinmodel->lump_scenes.start); + pheader->lump_scenes.length = BigLong(pinmodel->lump_scenes.length); + pheader->lump_poses.start = BigLong(pinmodel->lump_poses.start); + pheader->lump_poses.length = BigLong(pinmodel->lump_poses.length); + pheader->lump_bones.start = BigLong(pinmodel->lump_bones.start); + pheader->lump_bones.length = BigLong(pinmodel->lump_bones.length); + pheader->lump_vertbonecounts.start = BigLong(pinmodel->lump_vertbonecounts.start); + pheader->lump_vertbonecounts.length = BigLong(pinmodel->lump_vertbonecounts.length); + pheader->lump_verts.start = BigLong(pinmodel->lump_verts.start); + pheader->lump_verts.length = BigLong(pinmodel->lump_verts.length); + pheader->lump_texcoords.start = BigLong(pinmodel->lump_texcoords.start); + pheader->lump_texcoords.length = BigLong(pinmodel->lump_texcoords.length); + pheader->lump_render.start = BigLong(pinmodel->lump_render.start); + pheader->lump_render.length = BigLong(pinmodel->lump_render.length); + pheader->lump_shaders.start = BigLong(pinmodel->lump_shaders.start); + pheader->lump_shaders.length = BigLong(pinmodel->lump_shaders.length); + pheader->lump_trizone.start = BigLong(pinmodel->lump_trizone.start); + pheader->lump_trizone.length = BigLong(pinmodel->lump_trizone.length); + + if (pheader->numtris < 1 || pheader->numverts < 3 || pheader->numshaders < 1) + { + Con_Printf("%s has no geometry\n", loadmodel->name); + return; + } + if (pheader->numscenes < 1 || pheader->lump_poses.length < (int)sizeof(float[3][4])) + { + Con_Printf("%s has no animations\n", loadmodel->name); + return; + } + + loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + + loadmodel->numframes = pheader->numscenes; + loadmodel->num_surfaces = pheader->numshaders; + + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + // make skinscenes for the skins (no groups) + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // model bbox + modelradius = pheader->radius; + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = pheader->mins[i]; + loadmodel->normalmaxs[i] = pheader->maxs[i]; + loadmodel->rotatedmins[i] = -modelradius; + loadmodel->rotatedmaxs[i] = modelradius; + } + corner[0] = max(fabs(loadmodel->normalmins[0]), fabs(loadmodel->normalmaxs[0])); + corner[1] = max(fabs(loadmodel->normalmins[1]), fabs(loadmodel->normalmaxs[1])); + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); + if (loadmodel->yawmaxs[0] > modelradius) + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = modelradius; + loadmodel->yawmins[0] = loadmodel->yawmins[1] = -loadmodel->yawmaxs[0]; + loadmodel->yawmins[2] = loadmodel->normalmins[2]; + loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; + loadmodel->radius = modelradius; + loadmodel->radius2 = modelradius * modelradius; + + // go through the lumps, swapping things + + //zymlump_t lump_scenes; // zymscene_t scene[numscenes]; // name and other information for each scene (see zymscene struct) + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); + scene = (zymscene_t *) (pheader->lump_scenes.start + pbase); + numposes = pheader->lump_poses.length / pheader->numbones / sizeof(float[3][4]); + for (i = 0;i < pheader->numscenes;i++) + { + memcpy(loadmodel->animscenes[i].name, scene->name, 32); + loadmodel->animscenes[i].firstframe = BigLong(scene->start); + loadmodel->animscenes[i].framecount = BigLong(scene->length); + loadmodel->animscenes[i].framerate = BigFloat(scene->framerate); + loadmodel->animscenes[i].loop = (BigLong(scene->flags) & ZYMSCENEFLAG_NOLOOP) == 0; + if ((unsigned int) loadmodel->animscenes[i].firstframe >= (unsigned int) numposes) + Host_Error("%s scene->firstframe (%i) >= numposes (%i)", loadmodel->name, loadmodel->animscenes[i].firstframe, numposes); + if ((unsigned int) loadmodel->animscenes[i].firstframe + (unsigned int) loadmodel->animscenes[i].framecount > (unsigned int) numposes) + Host_Error("%s scene->firstframe (%i) + framecount (%i) >= numposes (%i)", loadmodel->name, loadmodel->animscenes[i].firstframe, loadmodel->animscenes[i].framecount, numposes); + if (loadmodel->animscenes[i].framerate < 0) + Host_Error("%s scene->framerate (%f) < 0", loadmodel->name, loadmodel->animscenes[i].framerate); + scene++; + } + + //zymlump_t lump_bones; // zymbone_t bone[numbones]; + loadmodel->num_bones = pheader->numbones; + loadmodel->data_bones = (aliasbone_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_bones * sizeof(aliasbone_t)); + bone = (zymbone_t *) (pheader->lump_bones.start + pbase); + for (i = 0;i < pheader->numbones;i++) + { + memcpy(loadmodel->data_bones[i].name, bone[i].name, sizeof(bone[i].name)); + loadmodel->data_bones[i].flags = BigLong(bone[i].flags); + loadmodel->data_bones[i].parent = BigLong(bone[i].parent); + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); + } + + //zymlump_t lump_vertbonecounts; // int vertbonecounts[numvertices]; // how many bones influence each vertex (separate mainly to make this compress better) + vertbonecounts = (int *)Mem_Alloc(loadmodel->mempool, pheader->numverts * sizeof(int)); + bonecount = (int *) (pheader->lump_vertbonecounts.start + pbase); + for (i = 0;i < pheader->numverts;i++) + { + vertbonecounts[i] = BigLong(bonecount[i]); + if (vertbonecounts[i] != 1) + Host_Error("%s bonecount[%i] != 1 (vertex weight support is impossible in this format)", loadmodel->name, i); + } + + loadmodel->num_poses = pheader->lump_poses.length / sizeof(float[3][4]) / loadmodel->num_bones; + + meshvertices = pheader->numverts; + meshtriangles = pheader->numtris; + + loadmodel->nummodelsurfaces = loadmodel->num_surfaces; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + meshvertices * sizeof(float[14]) + meshvertices * sizeof(unsigned short) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[6]) + loadmodel->num_bones * sizeof(float[12])); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); + loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); + loadmodel->surfmesh.num_blends = 0; + loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); + if (loadmodel->surfmesh.num_vertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); + } + loadmodel->data_poses6s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[6]); + loadmodel->surfmesh.data_blendweights = NULL; + + //zymlump_t lump_poses; // float pose[numposes][numbones][3][4]; // animation data + poses = (float *) (pheader->lump_poses.start + pbase); + // figure out scale of model from root bone, for compatibility with old zmodel versions + tempvec[0] = BigFloat(poses[0]); + tempvec[1] = BigFloat(poses[1]); + tempvec[2] = BigFloat(poses[2]); + modelscale = VectorLength(tempvec); + biggestorigin = 0; + for (i = 0;i < loadmodel->num_bones * numposes * 12;i++) + { + f = fabs(BigFloat(poses[i])); + biggestorigin = max(biggestorigin, f); + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + for (i = 0;i < numposes;i++) + { + const float *frameposes = (float *) (pheader->lump_poses.start + pbase) + 12*i*loadmodel->num_bones; + for (j = 0;j < loadmodel->num_bones;j++) + { + float pose[12]; + matrix4x4_t posematrix; + for (k = 0;k < 12;k++) + pose[k] = BigFloat(frameposes[j*12+k]); + //if (j < loadmodel->num_bones) + // Con_Printf("%s: bone %i = %f %f %f %f : %f %f %f %f : %f %f %f %f : scale = %f\n", loadmodel->name, j, pose[0], pose[1], pose[2], pose[3], pose[4], pose[5], pose[6], pose[7], pose[8], pose[9], pose[10], pose[11], VectorLength(pose)); + // scale child bones to match the root scale + if (loadmodel->data_bones[j].parent >= 0) + { + pose[3] *= modelscale; + pose[7] *= modelscale; + pose[11] *= modelscale; + } + // normalize rotation matrix + VectorNormalize(pose + 0); + VectorNormalize(pose + 4); + VectorNormalize(pose + 8); + Matrix4x4_FromArray12FloatD3D(&posematrix, pose); + Matrix4x4_ToBonePose6s(&posematrix, loadmodel->num_poseinvscale, loadmodel->data_poses6s + 6*(i*loadmodel->num_bones+j)); + } + } + + //zymlump_t lump_verts; // zymvertex_t vert[numvertices]; // see vertex struct + verts = (zymvertex_t *)Mem_Alloc(loadmodel->mempool, pheader->lump_verts.length); + vertdata = (zymvertex_t *) (pheader->lump_verts.start + pbase); + // reconstruct frame 0 matrices to allow reconstruction of the base mesh + // (converting from weight-blending skeletal animation to + // deformation-based skeletal animation) + bonepose = (float *)Z_Malloc(loadmodel->num_bones * sizeof(float[12])); + for (i = 0;i < loadmodel->num_bones;i++) + { + float m[12]; + for (k = 0;k < 12;k++) + m[k] = BigFloat(poses[i*12+k]); + if (loadmodel->data_bones[i].parent >= 0) + R_ConcatTransforms(bonepose + 12 * loadmodel->data_bones[i].parent, m, bonepose + 12 * i); + else + for (k = 0;k < 12;k++) + bonepose[12*i+k] = m[k]; + } + for (j = 0;j < pheader->numverts;j++) + { + // this format really should have had a per vertexweight weight value... + // but since it does not, the weighting is completely ignored and + // only one weight is allowed per vertex + int boneindex = BigLong(vertdata[j].bonenum); + const float *m = bonepose + 12 * boneindex; + float relativeorigin[3]; + relativeorigin[0] = BigFloat(vertdata[j].origin[0]); + relativeorigin[1] = BigFloat(vertdata[j].origin[1]); + relativeorigin[2] = BigFloat(vertdata[j].origin[2]); + // transform the vertex bone weight into the base mesh + loadmodel->surfmesh.data_vertex3f[j*3+0] = relativeorigin[0] * m[0] + relativeorigin[1] * m[1] + relativeorigin[2] * m[ 2] + m[ 3]; + loadmodel->surfmesh.data_vertex3f[j*3+1] = relativeorigin[0] * m[4] + relativeorigin[1] * m[5] + relativeorigin[2] * m[ 6] + m[ 7]; + loadmodel->surfmesh.data_vertex3f[j*3+2] = relativeorigin[0] * m[8] + relativeorigin[1] * m[9] + relativeorigin[2] * m[10] + m[11]; + // store the weight as the primary weight on this vertex + loadmodel->surfmesh.blends[j] = boneindex; + } + Z_Free(bonepose); + // normals and tangents are calculated after elements are loaded + + //zymlump_t lump_texcoords; // float texcoords[numvertices][2]; + outtexcoord2f = loadmodel->surfmesh.data_texcoordtexture2f; + intexcoord2f = (float *) (pheader->lump_texcoords.start + pbase); + for (i = 0;i < pheader->numverts;i++) + { + outtexcoord2f[i*2+0] = BigFloat(intexcoord2f[i*2+0]); + // flip T coordinate for OpenGL + outtexcoord2f[i*2+1] = 1 - BigFloat(intexcoord2f[i*2+1]); + } + + //zymlump_t lump_trizone; // byte trizone[numtris]; // see trizone explanation + //loadmodel->alias.zymdata_trizone = Mem_Alloc(loadmodel->mempool, pheader->numtris); + //memcpy(loadmodel->alias.zymdata_trizone, (void *) (pheader->lump_trizone.start + pbase), pheader->numtris); + + //zymlump_t lump_shaders; // char shadername[numshaders][32]; // shaders used on this model + //zymlump_t lump_render; // int renderlist[rendersize]; // sorted by shader with run lengths (int count), shaders are sequentially used, each run can be used with glDrawElements (each triangle is 3 int indices) + // byteswap, validate, and swap winding order of tris + count = pheader->numshaders * sizeof(int) + pheader->numtris * sizeof(int[3]); + if (pheader->lump_render.length != count) + Host_Error("%s renderlist is wrong size (%i bytes, should be %i bytes)", loadmodel->name, pheader->lump_render.length, count); + renderlist = (int *) (pheader->lump_render.start + pbase); + renderlistend = (int *) ((unsigned char *) renderlist + pheader->lump_render.length); + meshtriangles = 0; + for (i = 0;i < loadmodel->num_surfaces;i++) + { + int firstvertex, lastvertex; + if (renderlist >= renderlistend) + Host_Error("%s corrupt renderlist (wrong size)", loadmodel->name); + count = BigLong(*renderlist);renderlist++; + if (renderlist + count * 3 > renderlistend || (i == pheader->numshaders - 1 && renderlist + count * 3 != renderlistend)) + Host_Error("%s corrupt renderlist (wrong size)", loadmodel->name); + + loadmodel->sortedmodelsurfaces[i] = i; + surface = loadmodel->data_surfaces + i; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = meshtriangles; + surface->num_triangles = count; + meshtriangles += surface->num_triangles; + + // load the elements + outelements = loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3; + for (j = 0;j < surface->num_triangles;j++, renderlist += 3) + { + outelements[j*3+2] = BigLong(renderlist[0]); + outelements[j*3+1] = BigLong(renderlist[1]); + outelements[j*3+0] = BigLong(renderlist[2]); + } + // validate the elements and find the used vertex range + firstvertex = meshvertices; + lastvertex = 0; + for (j = 0;j < surface->num_triangles * 3;j++) + { + if ((unsigned int)outelements[j] >= (unsigned int)meshvertices) + Host_Error("%s corrupt renderlist (out of bounds index)", loadmodel->name); + firstvertex = min(firstvertex, outelements[j]); + lastvertex = max(lastvertex, outelements[j]); + } + surface->num_firstvertex = firstvertex; + surface->num_vertices = lastvertex + 1 - firstvertex; + + // since zym models do not have named sections, reuse their shader + // name as the section name + shadername = (char *) (pheader->lump_shaders.start + pbase) + i * 32; + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, shadername, shadername); + } + Mod_FreeSkinFiles(skinfiles); + Mem_Free(vertbonecounts); + Mem_Free(verts); + Mod_MakeSortedSurfaces(loadmodel); + + // compute all the mesh information that was not loaded from the file + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); + Mod_BuildBaseBonePoses(); + Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } +} + +void Mod_DARKPLACESMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + dpmheader_t *pheader; + dpmframe_t *frames; + dpmbone_t *bone; + dpmmesh_t *dpmmesh; + unsigned char *pbase; + int i, j, k, meshvertices, meshtriangles; + skinfile_t *skinfiles; + unsigned char *data; + float *bonepose; + float biggestorigin, tempvec[3], modelscale; + float f; + float *poses; + + pheader = (dpmheader_t *)buffer; + pbase = (unsigned char *)buffer; + if (memcmp(pheader->id, "DARKPLACESMODEL\0", 16)) + Host_Error ("Mod_DARKPLACESMODEL_Load: %s is not a darkplaces model", loadmodel->name); + if (BigLong(pheader->type) != 2) + Host_Error ("Mod_DARKPLACESMODEL_Load: only type 2 (hierarchical skeletal pose) models are currently supported (name = %s)", loadmodel->name); + + loadmodel->modeldatatypestring = "DPM"; + + loadmodel->type = mod_alias; + loadmodel->synctype = ST_RAND; + + // byteswap header + pheader->type = BigLong(pheader->type); + pheader->filesize = BigLong(pheader->filesize); + pheader->mins[0] = BigFloat(pheader->mins[0]); + pheader->mins[1] = BigFloat(pheader->mins[1]); + pheader->mins[2] = BigFloat(pheader->mins[2]); + pheader->maxs[0] = BigFloat(pheader->maxs[0]); + pheader->maxs[1] = BigFloat(pheader->maxs[1]); + pheader->maxs[2] = BigFloat(pheader->maxs[2]); + pheader->yawradius = BigFloat(pheader->yawradius); + pheader->allradius = BigFloat(pheader->allradius); + pheader->num_bones = BigLong(pheader->num_bones); + pheader->num_meshs = BigLong(pheader->num_meshs); + pheader->num_frames = BigLong(pheader->num_frames); + pheader->ofs_bones = BigLong(pheader->ofs_bones); + pheader->ofs_meshs = BigLong(pheader->ofs_meshs); + pheader->ofs_frames = BigLong(pheader->ofs_frames); + + if (pheader->num_bones < 1 || pheader->num_meshs < 1) + { + Con_Printf("%s has no geometry\n", loadmodel->name); + return; + } + if (pheader->num_frames < 1) + { + Con_Printf("%s has no frames\n", loadmodel->name); + return; + } + + loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + + // model bbox + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = pheader->mins[i]; + loadmodel->normalmaxs[i] = pheader->maxs[i]; + loadmodel->yawmins[i] = i != 2 ? -pheader->yawradius : pheader->mins[i]; + loadmodel->yawmaxs[i] = i != 2 ? pheader->yawradius : pheader->maxs[i]; + loadmodel->rotatedmins[i] = -pheader->allradius; + loadmodel->rotatedmaxs[i] = pheader->allradius; + } + loadmodel->radius = pheader->allradius; + loadmodel->radius2 = pheader->allradius * pheader->allradius; + + // load external .skin files if present + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + meshvertices = 0; + meshtriangles = 0; + + // gather combined statistics from the meshes + dpmmesh = (dpmmesh_t *) (pbase + pheader->ofs_meshs); + for (i = 0;i < (int)pheader->num_meshs;i++) + { + int numverts = BigLong(dpmmesh->num_verts); + meshvertices += numverts; + meshtriangles += BigLong(dpmmesh->num_tris); + dpmmesh++; + } + + loadmodel->numframes = pheader->num_frames; + loadmodel->num_bones = pheader->num_bones; + loadmodel->num_poses = loadmodel->numframes; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces = pheader->num_meshs; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + // do most allocations as one merged chunk + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + meshvertices * (sizeof(float[14]) + sizeof(unsigned short)) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[6]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); + loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); + loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); + loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); + loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); + loadmodel->surfmesh.num_blends = 0; + loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); + if (meshvertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); + } + loadmodel->data_poses6s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[6]); + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, meshvertices * sizeof(blendweights_t)); + + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // load the bone info + bone = (dpmbone_t *) (pbase + pheader->ofs_bones); + for (i = 0;i < loadmodel->num_bones;i++) + { + memcpy(loadmodel->data_bones[i].name, bone[i].name, sizeof(bone[i].name)); + loadmodel->data_bones[i].flags = BigLong(bone[i].flags); + loadmodel->data_bones[i].parent = BigLong(bone[i].parent); + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); + } + + // load the frames + frames = (dpmframe_t *) (pbase + pheader->ofs_frames); + // figure out scale of model from root bone, for compatibility with old dpmodel versions + poses = (float *) (pbase + BigLong(frames[0].ofs_bonepositions)); + tempvec[0] = BigFloat(poses[0]); + tempvec[1] = BigFloat(poses[1]); + tempvec[2] = BigFloat(poses[2]); + modelscale = VectorLength(tempvec); + biggestorigin = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + memcpy(loadmodel->animscenes[i].name, frames[i].name, sizeof(frames[i].name)); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].loop = true; + loadmodel->animscenes[i].framerate = 10; + // load the bone poses for this frame + poses = (float *) (pbase + BigLong(frames[i].ofs_bonepositions)); + for (j = 0;j < loadmodel->num_bones*12;j++) + { + f = fabs(BigFloat(poses[j])); + biggestorigin = max(biggestorigin, f); + } + // stuff not processed here: mins, maxs, yawradius, allradius + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + for (i = 0;i < loadmodel->numframes;i++) + { + const float *frameposes = (float *) (pbase + BigLong(frames[i].ofs_bonepositions)); + for (j = 0;j < loadmodel->num_bones;j++) + { + float pose[12]; + matrix4x4_t posematrix; + for (k = 0;k < 12;k++) + pose[k] = BigFloat(frameposes[j*12+k]); + // scale child bones to match the root scale + if (loadmodel->data_bones[j].parent >= 0) + { + pose[3] *= modelscale; + pose[7] *= modelscale; + pose[11] *= modelscale; + } + // normalize rotation matrix + VectorNormalize(pose + 0); + VectorNormalize(pose + 4); + VectorNormalize(pose + 8); + Matrix4x4_FromArray12FloatD3D(&posematrix, pose); + Matrix4x4_ToBonePose6s(&posematrix, loadmodel->num_poseinvscale, loadmodel->data_poses6s + 6*(i*loadmodel->num_bones+j)); + } + } + + // load the meshes now + dpmmesh = (dpmmesh_t *) (pbase + pheader->ofs_meshs); + meshvertices = 0; + meshtriangles = 0; + // reconstruct frame 0 matrices to allow reconstruction of the base mesh + // (converting from weight-blending skeletal animation to + // deformation-based skeletal animation) + poses = (float *) (pbase + BigLong(frames[0].ofs_bonepositions)); + bonepose = (float *)Z_Malloc(loadmodel->num_bones * sizeof(float[12])); + for (i = 0;i < loadmodel->num_bones;i++) + { + float m[12]; + for (k = 0;k < 12;k++) + m[k] = BigFloat(poses[i*12+k]); + if (loadmodel->data_bones[i].parent >= 0) + R_ConcatTransforms(bonepose + 12 * loadmodel->data_bones[i].parent, m, bonepose + 12 * i); + else + for (k = 0;k < 12;k++) + bonepose[12*i+k] = m[k]; + } + for (i = 0;i < loadmodel->num_surfaces;i++, dpmmesh++) + { + const int *inelements; + int *outelements; + const float *intexcoord; + msurface_t *surface; + + loadmodel->sortedmodelsurfaces[i] = i; + surface = loadmodel->data_surfaces + i; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = meshtriangles; + surface->num_triangles = BigLong(dpmmesh->num_tris); + surface->num_firstvertex = meshvertices; + surface->num_vertices = BigLong(dpmmesh->num_verts); + meshvertices += surface->num_vertices; + meshtriangles += surface->num_triangles; + + inelements = (int *) (pbase + BigLong(dpmmesh->ofs_indices)); + outelements = loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3; + for (j = 0;j < surface->num_triangles;j++) + { + // swap element order to flip triangles, because Quake uses clockwise (rare) and dpm uses counterclockwise (standard) + outelements[0] = surface->num_firstvertex + BigLong(inelements[2]); + outelements[1] = surface->num_firstvertex + BigLong(inelements[1]); + outelements[2] = surface->num_firstvertex + BigLong(inelements[0]); + inelements += 3; + outelements += 3; + } + + intexcoord = (float *) (pbase + BigLong(dpmmesh->ofs_texcoords)); + for (j = 0;j < surface->num_vertices*2;j++) + loadmodel->surfmesh.data_texcoordtexture2f[j + surface->num_firstvertex * 2] = BigFloat(intexcoord[j]); + + data = (unsigned char *) (pbase + BigLong(dpmmesh->ofs_verts)); + for (j = surface->num_firstvertex;j < surface->num_firstvertex + surface->num_vertices;j++) + { + int weightindex[4] = { 0, 0, 0, 0 }; + float weightinfluence[4] = { 0, 0, 0, 0 }; + int l; + int numweights = BigLong(((dpmvertex_t *)data)->numbones); + data += sizeof(dpmvertex_t); + for (k = 0;k < numweights;k++) + { + const dpmbonevert_t *vert = (dpmbonevert_t *) data; + int boneindex = BigLong(vert->bonenum); + const float *m = bonepose + 12 * boneindex; + float influence = BigFloat(vert->influence); + float relativeorigin[3], relativenormal[3]; + relativeorigin[0] = BigFloat(vert->origin[0]); + relativeorigin[1] = BigFloat(vert->origin[1]); + relativeorigin[2] = BigFloat(vert->origin[2]); + relativenormal[0] = BigFloat(vert->normal[0]); + relativenormal[1] = BigFloat(vert->normal[1]); + relativenormal[2] = BigFloat(vert->normal[2]); + // blend the vertex bone weights into the base mesh + loadmodel->surfmesh.data_vertex3f[j*3+0] += relativeorigin[0] * m[0] + relativeorigin[1] * m[1] + relativeorigin[2] * m[ 2] + influence * m[ 3]; + loadmodel->surfmesh.data_vertex3f[j*3+1] += relativeorigin[0] * m[4] + relativeorigin[1] * m[5] + relativeorigin[2] * m[ 6] + influence * m[ 7]; + loadmodel->surfmesh.data_vertex3f[j*3+2] += relativeorigin[0] * m[8] + relativeorigin[1] * m[9] + relativeorigin[2] * m[10] + influence * m[11]; + loadmodel->surfmesh.data_normal3f[j*3+0] += relativenormal[0] * m[0] + relativenormal[1] * m[1] + relativenormal[2] * m[ 2]; + loadmodel->surfmesh.data_normal3f[j*3+1] += relativenormal[0] * m[4] + relativenormal[1] * m[5] + relativenormal[2] * m[ 6]; + loadmodel->surfmesh.data_normal3f[j*3+2] += relativenormal[0] * m[8] + relativenormal[1] * m[9] + relativenormal[2] * m[10]; + if (!k) + { + // store the first (and often only) weight + weightinfluence[0] = influence; + weightindex[0] = boneindex; + } + else + { + // sort the new weight into this vertex's weight table + // (which only accepts up to 4 bones per vertex) + for (l = 0;l < 4;l++) + { + if (weightinfluence[l] < influence) + { + // move weaker influence weights out of the way first + int l2; + for (l2 = 3;l2 > l;l2--) + { + weightinfluence[l2] = weightinfluence[l2-1]; + weightindex[l2] = weightindex[l2-1]; + } + // store the new weight + weightinfluence[l] = influence; + weightindex[l] = boneindex; + break; + } + } + } + data += sizeof(dpmbonevert_t); + } + loadmodel->surfmesh.blends[j] = Mod_Skeletal_CompressBlend(loadmodel, weightindex, weightinfluence); + } + + // since dpm models do not have named sections, reuse their shader name as the section name + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, dpmmesh->shadername, dpmmesh->shadername); + + Mod_ValidateElements(loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3, surface->num_triangles, surface->num_firstvertex, surface->num_vertices, __FILE__, __LINE__); + } + if (loadmodel->surfmesh.num_blends < meshvertices) + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Realloc(loadmodel->mempool, loadmodel->surfmesh.data_blendweights, loadmodel->surfmesh.num_blends * sizeof(blendweights_t)); + Z_Free(bonepose); + Mod_FreeSkinFiles(skinfiles); + Mod_MakeSortedSurfaces(loadmodel); + + // compute all the mesh information that was not loaded from the file + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + Mod_BuildBaseBonePoses(); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } +} + +// no idea why PSK/PSA files contain weird quaternions but they do... +#define PSKQUATNEGATIONS +void Mod_PSKMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, index, version, recordsize, numrecords, meshvertices, meshtriangles; + int numpnts, numvtxw, numfaces, nummatts, numbones, numrawweights, numanimbones, numanims, numanimkeys; + fs_offset_t filesize; + pskpnts_t *pnts; + pskvtxw_t *vtxw; + pskface_t *faces; + pskmatt_t *matts; + pskboneinfo_t *bones; + pskrawweights_t *rawweights; + //pskboneinfo_t *animbones; + pskaniminfo_t *anims; + pskanimkeys_t *animkeys; + void *animfilebuffer, *animbuffer, *animbufferend; + unsigned char *data; + pskchunk_t *pchunk; + skinfile_t *skinfiles; + char animname[MAX_QPATH]; + size_t size; + float biggestorigin; + + pchunk = (pskchunk_t *)buffer; + if (strcmp(pchunk->id, "ACTRHEAD")) + Host_Error ("Mod_PSKMODEL_Load: %s is not an Unreal Engine ActorX (.psk + .psa) model", loadmodel->name); + + loadmodel->modeldatatypestring = "PSK"; + + loadmodel->type = mod_alias; + loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + loadmodel->synctype = ST_RAND; + + FS_StripExtension(loadmodel->name, animname, sizeof(animname)); + strlcat(animname, ".psa", sizeof(animname)); + animbuffer = animfilebuffer = FS_LoadFile(animname, loadmodel->mempool, false, &filesize); + animbufferend = (void *)((unsigned char*)animbuffer + (int)filesize); + if (animbuffer == NULL) + Host_Error("%s: can't find .psa file (%s)", loadmodel->name, animname); + + numpnts = 0; + pnts = NULL; + numvtxw = 0; + vtxw = NULL; + numfaces = 0; + faces = NULL; + nummatts = 0; + matts = NULL; + numbones = 0; + bones = NULL; + numrawweights = 0; + rawweights = NULL; + numanims = 0; + anims = NULL; + numanimkeys = 0; + animkeys = NULL; + + while (buffer < bufferend) + { + pchunk = (pskchunk_t *)buffer; + buffer = (void *)((unsigned char *)buffer + sizeof(pskchunk_t)); + version = LittleLong(pchunk->version); + recordsize = LittleLong(pchunk->recordsize); + numrecords = LittleLong(pchunk->numrecords); + if (developer_extra.integer) + Con_DPrintf("%s: %s %x: %i * %i = %i\n", loadmodel->name, pchunk->id, version, recordsize, numrecords, recordsize * numrecords); + if (version != 0x1e83b9 && version != 0x1e9179 && version != 0x2e && version != 0x12f2bc && version != 0x12f2f0) + Con_Printf ("%s: chunk %s has unknown version %x (0x1e83b9, 0x1e9179, 0x2e, 0x12f2bc, 0x12f2f0 are currently supported), trying to load anyway!\n", loadmodel->name, pchunk->id, version); + if (!strcmp(pchunk->id, "ACTRHEAD")) + { + // nothing to do + } + else if (!strcmp(pchunk->id, "PNTS0000")) + { + pskpnts_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numpnts = numrecords; + pnts = (pskpnts_t *)buffer; + for (index = 0, p = (pskpnts_t *)buffer;index < numrecords;index++, p++) + { + p->origin[0] = LittleFloat(p->origin[0]); + p->origin[1] = LittleFloat(p->origin[1]); + p->origin[2] = LittleFloat(p->origin[2]); + } + buffer = p; + } + else if (!strcmp(pchunk->id, "VTXW0000")) + { + pskvtxw_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numvtxw = numrecords; + vtxw = (pskvtxw_t *)buffer; + for (index = 0, p = (pskvtxw_t *)buffer;index < numrecords;index++, p++) + { + p->pntsindex = LittleShort(p->pntsindex); + p->texcoord[0] = LittleFloat(p->texcoord[0]); + p->texcoord[1] = LittleFloat(p->texcoord[1]); + if (p->pntsindex >= numpnts) + { + Con_Printf("%s: vtxw->pntsindex %i >= numpnts %i\n", loadmodel->name, p->pntsindex, numpnts); + p->pntsindex = 0; + } + } + buffer = p; + } + else if (!strcmp(pchunk->id, "FACE0000")) + { + pskface_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numfaces = numrecords; + faces = (pskface_t *)buffer; + for (index = 0, p = (pskface_t *)buffer;index < numrecords;index++, p++) + { + p->vtxwindex[0] = LittleShort(p->vtxwindex[0]); + p->vtxwindex[1] = LittleShort(p->vtxwindex[1]); + p->vtxwindex[2] = LittleShort(p->vtxwindex[2]); + p->group = LittleLong(p->group); + if (p->vtxwindex[0] >= numvtxw) + { + Con_Printf("%s: face->vtxwindex[0] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[0], numvtxw); + p->vtxwindex[0] = 0; + } + if (p->vtxwindex[1] >= numvtxw) + { + Con_Printf("%s: face->vtxwindex[1] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[1], numvtxw); + p->vtxwindex[1] = 0; + } + if (p->vtxwindex[2] >= numvtxw) + { + Con_Printf("%s: face->vtxwindex[2] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[2], numvtxw); + p->vtxwindex[2] = 0; + } + } + buffer = p; + } + else if (!strcmp(pchunk->id, "MATT0000")) + { + pskmatt_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + nummatts = numrecords; + matts = (pskmatt_t *)buffer; + for (index = 0, p = (pskmatt_t *)buffer;index < numrecords;index++, p++) + { + // nothing to do + } + buffer = p; + } + else if (!strcmp(pchunk->id, "REFSKELT")) + { + pskboneinfo_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numbones = numrecords; + bones = (pskboneinfo_t *)buffer; + for (index = 0, p = (pskboneinfo_t *)buffer;index < numrecords;index++, p++) + { + p->numchildren = LittleLong(p->numchildren); + p->parent = LittleLong(p->parent); + p->basepose.quat[0] = LittleFloat(p->basepose.quat[0]); + p->basepose.quat[1] = LittleFloat(p->basepose.quat[1]); + p->basepose.quat[2] = LittleFloat(p->basepose.quat[2]); + p->basepose.quat[3] = LittleFloat(p->basepose.quat[3]); + p->basepose.origin[0] = LittleFloat(p->basepose.origin[0]); + p->basepose.origin[1] = LittleFloat(p->basepose.origin[1]); + p->basepose.origin[2] = LittleFloat(p->basepose.origin[2]); + p->basepose.unknown = LittleFloat(p->basepose.unknown); + p->basepose.size[0] = LittleFloat(p->basepose.size[0]); + p->basepose.size[1] = LittleFloat(p->basepose.size[1]); + p->basepose.size[2] = LittleFloat(p->basepose.size[2]); +#ifdef PSKQUATNEGATIONS + if (index) + { + p->basepose.quat[0] *= -1; + p->basepose.quat[1] *= -1; + p->basepose.quat[2] *= -1; + } + else + { + p->basepose.quat[0] *= 1; + p->basepose.quat[1] *= -1; + p->basepose.quat[2] *= 1; + } +#endif + if (p->parent < 0 || p->parent >= numbones) + { + Con_Printf("%s: bone->parent %i >= numbones %i\n", loadmodel->name, p->parent, numbones); + p->parent = 0; + } + } + buffer = p; + } + else if (!strcmp(pchunk->id, "RAWWEIGHTS")) + { + pskrawweights_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numrawweights = numrecords; + rawweights = (pskrawweights_t *)buffer; + for (index = 0, p = (pskrawweights_t *)buffer;index < numrecords;index++, p++) + { + p->weight = LittleFloat(p->weight); + p->pntsindex = LittleLong(p->pntsindex); + p->boneindex = LittleLong(p->boneindex); + if (p->pntsindex < 0 || p->pntsindex >= numpnts) + { + Con_Printf("%s: weight->pntsindex %i >= numpnts %i\n", loadmodel->name, p->pntsindex, numpnts); + p->pntsindex = 0; + } + if (p->boneindex < 0 || p->boneindex >= numbones) + { + Con_Printf("%s: weight->boneindex %i >= numbones %i\n", loadmodel->name, p->boneindex, numbones); + p->boneindex = 0; + } + } + buffer = p; + } + } + + while (animbuffer < animbufferend) + { + pchunk = (pskchunk_t *)animbuffer; + animbuffer = (void *)((unsigned char *)animbuffer + sizeof(pskchunk_t)); + version = LittleLong(pchunk->version); + recordsize = LittleLong(pchunk->recordsize); + numrecords = LittleLong(pchunk->numrecords); + if (developer_extra.integer) + Con_DPrintf("%s: %s %x: %i * %i = %i\n", animname, pchunk->id, version, recordsize, numrecords, recordsize * numrecords); + if (version != 0x1e83b9 && version != 0x1e9179 && version != 0x2e && version != 0x12f2bc && version != 0x12f2f0) + Con_Printf ("%s: chunk %s has unknown version %x (0x1e83b9, 0x1e9179, 0x2e, 0x12f2bc, 0x12f2f0 are currently supported), trying to load anyway!\n", animname, pchunk->id, version); + if (!strcmp(pchunk->id, "ANIMHEAD")) + { + // nothing to do + } + else if (!strcmp(pchunk->id, "BONENAMES")) + { + pskboneinfo_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); + // byteswap in place and keep the pointer + numanimbones = numrecords; + //animbones = (pskboneinfo_t *)animbuffer; + // NOTE: supposedly psa does not need to match the psk model, the + // bones missing from the psa would simply use their base + // positions from the psk, but this is hard for me to implement + // and people can easily make animations that match. + if (numanimbones != numbones) + Host_Error("%s: this loader only supports animations with the same bones as the mesh", loadmodel->name); + for (index = 0, p = (pskboneinfo_t *)animbuffer;index < numrecords;index++, p++) + { + p->numchildren = LittleLong(p->numchildren); + p->parent = LittleLong(p->parent); + p->basepose.quat[0] = LittleFloat(p->basepose.quat[0]); + p->basepose.quat[1] = LittleFloat(p->basepose.quat[1]); + p->basepose.quat[2] = LittleFloat(p->basepose.quat[2]); + p->basepose.quat[3] = LittleFloat(p->basepose.quat[3]); + p->basepose.origin[0] = LittleFloat(p->basepose.origin[0]); + p->basepose.origin[1] = LittleFloat(p->basepose.origin[1]); + p->basepose.origin[2] = LittleFloat(p->basepose.origin[2]); + p->basepose.unknown = LittleFloat(p->basepose.unknown); + p->basepose.size[0] = LittleFloat(p->basepose.size[0]); + p->basepose.size[1] = LittleFloat(p->basepose.size[1]); + p->basepose.size[2] = LittleFloat(p->basepose.size[2]); +#ifdef PSKQUATNEGATIONS + if (index) + { + p->basepose.quat[0] *= -1; + p->basepose.quat[1] *= -1; + p->basepose.quat[2] *= -1; + } + else + { + p->basepose.quat[0] *= 1; + p->basepose.quat[1] *= -1; + p->basepose.quat[2] *= 1; + } +#endif + if (p->parent < 0 || p->parent >= numanimbones) + { + Con_Printf("%s: bone->parent %i >= numanimbones %i\n", animname, p->parent, numanimbones); + p->parent = 0; + } + // check that bones are the same as in the base + if (strcmp(p->name, bones[index].name) || p->parent != bones[index].parent) + Host_Error("%s: this loader only supports animations with the same bones as the mesh", animname); + } + animbuffer = p; + } + else if (!strcmp(pchunk->id, "ANIMINFO")) + { + pskaniminfo_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); + // byteswap in place and keep the pointer + numanims = numrecords; + anims = (pskaniminfo_t *)animbuffer; + for (index = 0, p = (pskaniminfo_t *)animbuffer;index < numrecords;index++, p++) + { + p->numbones = LittleLong(p->numbones); + p->playtime = LittleFloat(p->playtime); + p->fps = LittleFloat(p->fps); + p->firstframe = LittleLong(p->firstframe); + p->numframes = LittleLong(p->numframes); + if (p->numbones != numbones) + Con_Printf("%s: animinfo->numbones != numbones, trying to load anyway!\n", animname); + } + animbuffer = p; + } + else if (!strcmp(pchunk->id, "ANIMKEYS")) + { + pskanimkeys_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); + numanimkeys = numrecords; + animkeys = (pskanimkeys_t *)animbuffer; + for (index = 0, p = (pskanimkeys_t *)animbuffer;index < numrecords;index++, p++) + { + p->origin[0] = LittleFloat(p->origin[0]); + p->origin[1] = LittleFloat(p->origin[1]); + p->origin[2] = LittleFloat(p->origin[2]); + p->quat[0] = LittleFloat(p->quat[0]); + p->quat[1] = LittleFloat(p->quat[1]); + p->quat[2] = LittleFloat(p->quat[2]); + p->quat[3] = LittleFloat(p->quat[3]); + p->frametime = LittleFloat(p->frametime); +#ifdef PSKQUATNEGATIONS + if (index % numbones) + { + p->quat[0] *= -1; + p->quat[1] *= -1; + p->quat[2] *= -1; + } + else + { + p->quat[0] *= 1; + p->quat[1] *= -1; + p->quat[2] *= 1; + } +#endif + } + animbuffer = p; + // TODO: allocate bonepose stuff + } + else + Con_Printf("%s: unknown chunk ID \"%s\"\n", animname, pchunk->id); + } + + if (!numpnts || !pnts || !numvtxw || !vtxw || !numfaces || !faces || !nummatts || !matts || !numbones || !bones || !numrawweights || !rawweights || !numanims || !anims || !numanimkeys || !animkeys) + Host_Error("%s: missing required chunks", loadmodel->name); + + loadmodel->numframes = 0; + for (index = 0;index < numanims;index++) + loadmodel->numframes += anims[index].numframes; + + if (numanimkeys != numbones * loadmodel->numframes) + Host_Error("%s: %s has incorrect number of animation keys", animname, pchunk->id); + + meshvertices = numvtxw; + meshtriangles = numfaces; + + // load external .skin files if present + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + loadmodel->num_bones = numbones; + loadmodel->num_poses = loadmodel->numframes; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces = nummatts; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + // do most allocations as one merged chunk + size = loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + loadmodel->surfmesh.num_triangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? loadmodel->surfmesh.num_triangles * sizeof(int[3]) : 0) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[2]) + loadmodel->surfmesh.num_vertices * sizeof(unsigned short) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[6]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t) + ((loadmodel->surfmesh.num_vertices <= 65536) ? (loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3])) : 0); + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, size); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.data_element3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_vertex3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[2]); + loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); + loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); + loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); + loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); + loadmodel->surfmesh.num_blends = 0; + loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); + if (loadmodel->surfmesh.num_vertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); + } + loadmodel->data_poses6s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[6]); + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(blendweights_t)); + + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // create surfaces + for (index = 0, i = 0;index < nummatts;index++) + { + // since psk models do not have named sections, reuse their shader name as the section name + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + index, skinfiles, matts[index].name, matts[index].name); + loadmodel->sortedmodelsurfaces[index] = index; + loadmodel->data_surfaces[index].texture = loadmodel->data_textures + index; + loadmodel->data_surfaces[index].num_firstvertex = 0; + loadmodel->data_surfaces[index].num_vertices = loadmodel->surfmesh.num_vertices; + } + + // copy over the vertex locations and texcoords + for (index = 0;index < numvtxw;index++) + { + loadmodel->surfmesh.data_vertex3f[index*3+0] = pnts[vtxw[index].pntsindex].origin[0]; + loadmodel->surfmesh.data_vertex3f[index*3+1] = pnts[vtxw[index].pntsindex].origin[1]; + loadmodel->surfmesh.data_vertex3f[index*3+2] = pnts[vtxw[index].pntsindex].origin[2]; + loadmodel->surfmesh.data_texcoordtexture2f[index*2+0] = vtxw[index].texcoord[0]; + loadmodel->surfmesh.data_texcoordtexture2f[index*2+1] = vtxw[index].texcoord[1]; + } + + // loading the faces is complicated because we need to sort them into surfaces by mattindex + for (index = 0;index < numfaces;index++) + loadmodel->data_surfaces[faces[index].mattindex].num_triangles++; + for (index = 0, i = 0;index < nummatts;index++) + { + loadmodel->data_surfaces[index].num_firsttriangle = i; + i += loadmodel->data_surfaces[index].num_triangles; + loadmodel->data_surfaces[index].num_triangles = 0; + } + for (index = 0;index < numfaces;index++) + { + i = (loadmodel->data_surfaces[faces[index].mattindex].num_firsttriangle + loadmodel->data_surfaces[faces[index].mattindex].num_triangles++)*3; + loadmodel->surfmesh.data_element3i[i+0] = faces[index].vtxwindex[0]; + loadmodel->surfmesh.data_element3i[i+1] = faces[index].vtxwindex[1]; + loadmodel->surfmesh.data_element3i[i+2] = faces[index].vtxwindex[2]; + } + + // copy over the bones + for (index = 0;index < numbones;index++) + { + strlcpy(loadmodel->data_bones[index].name, bones[index].name, sizeof(loadmodel->data_bones[index].name)); + loadmodel->data_bones[index].parent = (index || bones[index].parent > 0) ? bones[index].parent : -1; + if (loadmodel->data_bones[index].parent >= index) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, index, index); + } + + // sort the psk point weights into the vertex weight tables + // (which only accept up to 4 bones per vertex) + for (index = 0;index < numvtxw;index++) + { + int weightindex[4] = { 0, 0, 0, 0 }; + float weightinfluence[4] = { 0, 0, 0, 0 }; + int l; + for (j = 0;j < numrawweights;j++) + { + if (rawweights[j].pntsindex == vtxw[index].pntsindex) + { + int boneindex = rawweights[j].boneindex; + float influence = rawweights[j].weight; + for (l = 0;l < 4;l++) + { + if (weightinfluence[l] < influence) + { + // move lower influence weights out of the way first + int l2; + for (l2 = 3;l2 > l;l2--) + { + weightinfluence[l2] = weightinfluence[l2-1]; + weightindex[l2] = weightindex[l2-1]; + } + // store the new weight + weightinfluence[l] = influence; + weightindex[l] = boneindex; + break; + } + } + } + } + loadmodel->surfmesh.blends[index] = Mod_Skeletal_CompressBlend(loadmodel, weightindex, weightinfluence); + } + if (loadmodel->surfmesh.num_blends < loadmodel->surfmesh.num_vertices) + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Realloc(loadmodel->mempool, loadmodel->surfmesh.data_blendweights, loadmodel->surfmesh.num_blends * sizeof(blendweights_t)); + + // set up the animscenes based on the anims + for (index = 0, i = 0;index < numanims;index++) + { + for (j = 0;j < anims[index].numframes;j++, i++) + { + dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "%s_%d", anims[index].name, j); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].loop = true; + loadmodel->animscenes[i].framerate = anims[index].fps; + } + } + + // calculate the scaling value for bone origins so they can be compressed to short + biggestorigin = 0; + for (index = 0;index < numanimkeys;index++) + { + pskanimkeys_t *k = animkeys + index; + biggestorigin = max(biggestorigin, fabs(k->origin[0])); + biggestorigin = max(biggestorigin, fabs(k->origin[1])); + biggestorigin = max(biggestorigin, fabs(k->origin[2])); + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + + // load the poses from the animkeys + for (index = 0;index < numanimkeys;index++) + { + pskanimkeys_t *k = animkeys + index; + float quat[4]; + Vector4Copy(k->quat, quat); + if (quat[3] > 0) + Vector4Negate(quat, quat); + Vector4Normalize2(quat, quat); + // compress poses to the short[6] format for longterm storage + loadmodel->data_poses6s[index*6+0] = k->origin[0] * loadmodel->num_poseinvscale; + loadmodel->data_poses6s[index*6+1] = k->origin[1] * loadmodel->num_poseinvscale; + loadmodel->data_poses6s[index*6+2] = k->origin[2] * loadmodel->num_poseinvscale; + loadmodel->data_poses6s[index*6+3] = quat[0] * 32767.0f; + loadmodel->data_poses6s[index*6+4] = quat[1] * 32767.0f; + loadmodel->data_poses6s[index*6+5] = quat[2] * 32767.0f; + } + Mod_FreeSkinFiles(skinfiles); + Mem_Free(animfilebuffer); + Mod_MakeSortedSurfaces(loadmodel); + + // compute all the mesh information that was not loaded from the file + // TODO: honor smoothing groups somehow? + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); + Mod_BuildBaseBonePoses(); + Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + Mod_Alias_CalculateBoundingBox(); + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } +} + +void Mod_INTERQUAKEMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + unsigned char *data; + const char *text; + unsigned char *pbase, *pend; + iqmheader_t *header; + skinfile_t *skinfiles; + int i, j, k, meshvertices, meshtriangles; + float *vposition = NULL, *vtexcoord = NULL, *vnormal = NULL, *vtangent = NULL; + unsigned char *vblendindexes = NULL, *vblendweights = NULL; + iqmjoint1_t *joint1 = NULL; + iqmjoint_t *joint = NULL; + iqmpose1_t *pose1 = NULL; + iqmpose_t *pose = NULL; + iqmanim_t *anim; + iqmmesh_t *mesh; + iqmbounds_t *bounds; + iqmvertexarray_t *va; + unsigned short *framedata; + float biggestorigin; + const int *inelements; + int *outelements; + float *outvertex, *outnormal, *outtexcoord, *outsvector, *outtvector; + + pbase = (unsigned char *)buffer; + pend = (unsigned char *)bufferend; + header = (iqmheader_t *)buffer; + if (memcmp(header->id, "INTERQUAKEMODEL", 16)) + Host_Error ("Mod_INTERQUAKEMODEL_Load: %s is not an Inter-Quake Model", loadmodel->name); + if (LittleLong(header->version) != 1 && LittleLong(header->version) != 2) + Host_Error ("Mod_INTERQUAKEMODEL_Load: only version 1 and 2 models are currently supported (name = %s)", loadmodel->name); + + loadmodel->modeldatatypestring = "IQM"; + + loadmodel->type = mod_alias; + loadmodel->synctype = ST_RAND; + + // byteswap header + header->version = LittleLong(header->version); + header->filesize = LittleLong(header->filesize); + header->flags = LittleLong(header->flags); + header->num_text = LittleLong(header->num_text); + header->ofs_text = LittleLong(header->ofs_text); + header->num_meshes = LittleLong(header->num_meshes); + header->ofs_meshes = LittleLong(header->ofs_meshes); + header->num_vertexarrays = LittleLong(header->num_vertexarrays); + header->num_vertexes = LittleLong(header->num_vertexes); + header->ofs_vertexarrays = LittleLong(header->ofs_vertexarrays); + header->num_triangles = LittleLong(header->num_triangles); + header->ofs_triangles = LittleLong(header->ofs_triangles); + header->ofs_neighbors = LittleLong(header->ofs_neighbors); + header->num_joints = LittleLong(header->num_joints); + header->ofs_joints = LittleLong(header->ofs_joints); + header->num_poses = LittleLong(header->num_poses); + header->ofs_poses = LittleLong(header->ofs_poses); + header->num_anims = LittleLong(header->num_anims); + header->ofs_anims = LittleLong(header->ofs_anims); + header->num_frames = LittleLong(header->num_frames); + header->num_framechannels = LittleLong(header->num_framechannels); + header->ofs_frames = LittleLong(header->ofs_frames); + header->ofs_bounds = LittleLong(header->ofs_bounds); + header->num_comment = LittleLong(header->num_comment); + header->ofs_comment = LittleLong(header->ofs_comment); + header->num_extensions = LittleLong(header->num_extensions); + header->ofs_extensions = LittleLong(header->ofs_extensions); + + if (header->num_triangles < 1 || header->num_vertexes < 3 || header->num_vertexarrays < 1 || header->num_meshes < 1) + { + Con_Printf("%s has no geometry\n", loadmodel->name); + return; + } + if (header->num_frames < 1 || header->num_anims < 1) + { + Con_Printf("%s has no animations\n", loadmodel->name); + return; + } + + if (pbase + header->ofs_text + header->num_text > pend || + pbase + header->ofs_meshes + header->num_meshes*sizeof(iqmmesh_t) > pend || + pbase + header->ofs_vertexarrays + header->num_vertexarrays*sizeof(iqmvertexarray_t) > pend || + pbase + header->ofs_triangles + header->num_triangles*sizeof(int[3]) > pend || + (header->ofs_neighbors && pbase + header->ofs_neighbors + header->num_triangles*sizeof(int[3]) > pend) || + pbase + header->ofs_joints + header->num_joints*sizeof(iqmjoint_t) > pend || + pbase + header->ofs_poses + header->num_poses*sizeof(iqmpose_t) > pend || + pbase + header->ofs_anims + header->num_anims*sizeof(iqmanim_t) > pend || + pbase + header->ofs_frames + header->num_frames*header->num_framechannels*sizeof(unsigned short) > pend || + (header->ofs_bounds && pbase + header->ofs_bounds + header->num_frames*sizeof(iqmbounds_t) > pend) || + pbase + header->ofs_comment + header->num_comment > pend) + { + Con_Printf("%s has invalid size or offset information\n", loadmodel->name); + return; + } + + va = (iqmvertexarray_t *)(pbase + header->ofs_vertexarrays); + for (i = 0;i < (int)header->num_vertexarrays;i++) + { + size_t vsize; + va[i].type = LittleLong(va[i].type); + va[i].flags = LittleLong(va[i].flags); + va[i].format = LittleLong(va[i].format); + va[i].size = LittleLong(va[i].size); + va[i].offset = LittleLong(va[i].offset); + vsize = header->num_vertexes*va[i].size; + switch (va[i].format) + { + case IQM_FLOAT: vsize *= sizeof(float); break; + case IQM_UBYTE: vsize *= sizeof(unsigned char); break; + default: continue; + } + if (pbase + va[i].offset + vsize > pend) + continue; + switch (va[i].type) + { + case IQM_POSITION: + if (va[i].format == IQM_FLOAT && va[i].size == 3) + vposition = (float *)(pbase + va[i].offset); + break; + case IQM_TEXCOORD: + if (va[i].format == IQM_FLOAT && va[i].size == 2) + vtexcoord = (float *)(pbase + va[i].offset); + break; + case IQM_NORMAL: + if (va[i].format == IQM_FLOAT && va[i].size == 3) + vnormal = (float *)(pbase + va[i].offset); + break; + case IQM_TANGENT: + if (va[i].format == IQM_FLOAT && va[i].size == 4) + vtangent = (float *)(pbase + va[i].offset); + break; + case IQM_BLENDINDEXES: + if (va[i].format == IQM_UBYTE && va[i].size == 4) + vblendindexes = (unsigned char *)(pbase + va[i].offset); + break; + case IQM_BLENDWEIGHTS: + if (va[i].format == IQM_UBYTE && va[i].size == 4) + vblendweights = (unsigned char *)(pbase + va[i].offset); + break; + } + } + if (!vposition || !vtexcoord || !vblendindexes || !vblendweights) + { + Con_Printf("%s is missing vertex array data\n", loadmodel->name); + return; + } + + text = header->num_text && header->ofs_text ? (const char *)(pbase + header->ofs_text) : ""; + + loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + + // load external .skin files if present + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + loadmodel->numframes = header->num_anims; + loadmodel->num_bones = header->num_joints; + loadmodel->num_poses = header->num_frames; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces = header->num_meshes; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + + meshvertices = header->num_vertexes; + meshtriangles = header->num_triangles; + + // do most allocations as one merged chunk + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + meshvertices * (sizeof(float[14]) + sizeof(unsigned short)) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[6]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); + loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); + loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); + loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); + loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); + loadmodel->surfmesh.num_blends = 0; + loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); + if (meshvertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); + } + loadmodel->data_poses6s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[6]); + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, meshvertices * sizeof(blendweights_t)); + + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // load the bone info + if (header->version == 1) + { + joint1 = (iqmjoint1_t *) (pbase + header->ofs_joints); + for (i = 0;i < loadmodel->num_bones;i++) + { + matrix4x4_t relbase, relinvbase, pinvbase, invbase; + joint1[i].name = LittleLong(joint1[i].name); + joint1[i].parent = LittleLong(joint1[i].parent); + for (j = 0;j < 3;j++) + { + joint1[i].origin[j] = LittleFloat(joint1[i].origin[j]); + joint1[i].rotation[j] = LittleFloat(joint1[i].rotation[j]); + joint1[i].scale[j] = LittleFloat(joint1[i].scale[j]); + } + strlcpy(loadmodel->data_bones[i].name, &text[joint1[i].name], sizeof(loadmodel->data_bones[i].name)); + loadmodel->data_bones[i].parent = joint1[i].parent; + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); + Matrix4x4_FromDoom3Joint(&relbase, joint1[i].origin[0], joint1[i].origin[1], joint1[i].origin[2], joint1[i].rotation[0], joint1[i].rotation[1], joint1[i].rotation[2]); + Matrix4x4_Invert_Simple(&relinvbase, &relbase); + if (loadmodel->data_bones[i].parent >= 0) + { + Matrix4x4_FromArray12FloatD3D(&pinvbase, loadmodel->data_baseboneposeinverse + 12*loadmodel->data_bones[i].parent); + Matrix4x4_Concat(&invbase, &relinvbase, &pinvbase); + Matrix4x4_ToArray12FloatD3D(&invbase, loadmodel->data_baseboneposeinverse + 12*i); + } + else Matrix4x4_ToArray12FloatD3D(&relinvbase, loadmodel->data_baseboneposeinverse + 12*i); + } + } + else + { + joint = (iqmjoint_t *) (pbase + header->ofs_joints); + for (i = 0;i < loadmodel->num_bones;i++) + { + matrix4x4_t relbase, relinvbase, pinvbase, invbase; + joint[i].name = LittleLong(joint[i].name); + joint[i].parent = LittleLong(joint[i].parent); + for (j = 0;j < 3;j++) + { + joint[i].origin[j] = LittleFloat(joint[i].origin[j]); + joint[i].rotation[j] = LittleFloat(joint[i].rotation[j]); + joint[i].scale[j] = LittleFloat(joint[i].scale[j]); + } + joint[i].rotation[3] = LittleFloat(joint[i].rotation[3]); + strlcpy(loadmodel->data_bones[i].name, &text[joint[i].name], sizeof(loadmodel->data_bones[i].name)); + loadmodel->data_bones[i].parent = joint[i].parent; + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); + if (joint[i].rotation[3] > 0) + Vector4Negate(joint[i].rotation, joint[i].rotation); + Vector4Normalize2(joint[i].rotation, joint[i].rotation); + Matrix4x4_FromDoom3Joint(&relbase, joint[i].origin[0], joint[i].origin[1], joint[i].origin[2], joint[i].rotation[0], joint[i].rotation[1], joint[i].rotation[2]); + Matrix4x4_Invert_Simple(&relinvbase, &relbase); + if (loadmodel->data_bones[i].parent >= 0) + { + Matrix4x4_FromArray12FloatD3D(&pinvbase, loadmodel->data_baseboneposeinverse + 12*loadmodel->data_bones[i].parent); + Matrix4x4_Concat(&invbase, &relinvbase, &pinvbase); + Matrix4x4_ToArray12FloatD3D(&invbase, loadmodel->data_baseboneposeinverse + 12*i); + } + else Matrix4x4_ToArray12FloatD3D(&relinvbase, loadmodel->data_baseboneposeinverse + 12*i); + } + } + + // set up the animscenes based on the anims + anim = (iqmanim_t *) (pbase + header->ofs_anims); + for (i = 0;i < (int)header->num_anims;i++) + { + anim[i].name = LittleLong(anim[i].name); + anim[i].first_frame = LittleLong(anim[i].first_frame); + anim[i].num_frames = LittleLong(anim[i].num_frames); + anim[i].framerate = LittleFloat(anim[i].framerate); + anim[i].flags = LittleLong(anim[i].flags); + strlcpy(loadmodel->animscenes[i].name, &text[anim[i].name], sizeof(loadmodel->animscenes[i].name)); + loadmodel->animscenes[i].firstframe = anim[i].first_frame; + loadmodel->animscenes[i].framecount = anim[i].num_frames; + loadmodel->animscenes[i].loop = ((anim[i].flags & IQM_LOOP) != 0); + loadmodel->animscenes[i].framerate = anim[i].framerate; + } + + biggestorigin = 0; + if (header->version == 1) + { + pose1 = (iqmpose1_t *) (pbase + header->ofs_poses); + for (i = 0;i < (int)header->num_poses;i++) + { + float f; + pose1[i].parent = LittleLong(pose1[i].parent); + pose1[i].channelmask = LittleLong(pose1[i].channelmask); + for (j = 0;j < 9;j++) + { + pose1[i].channeloffset[j] = LittleFloat(pose1[i].channeloffset[j]); + pose1[i].channelscale[j] = LittleFloat(pose1[i].channelscale[j]); + } + f = fabs(pose1[i].channeloffset[0]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[1]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[2]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[0] + 0xFFFF*pose1[i].channelscale[0]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[1] + 0xFFFF*pose1[i].channelscale[1]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[2] + 0xFFFF*pose1[i].channelscale[2]); biggestorigin = max(biggestorigin, f); + } + } + else + { + pose = (iqmpose_t *) (pbase + header->ofs_poses); + for (i = 0;i < (int)header->num_poses;i++) + { + float f; + pose[i].parent = LittleLong(pose[i].parent); + pose[i].channelmask = LittleLong(pose[i].channelmask); + for (j = 0;j < 10;j++) + { + pose[i].channeloffset[j] = LittleFloat(pose[i].channeloffset[j]); + pose[i].channelscale[j] = LittleFloat(pose[i].channelscale[j]); + } + f = fabs(pose[i].channeloffset[0]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[1]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[2]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[0] + 0xFFFF*pose[i].channelscale[0]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[1] + 0xFFFF*pose[i].channelscale[1]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[2] + 0xFFFF*pose[i].channelscale[2]); biggestorigin = max(biggestorigin, f); + } + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + + // load the pose data + framedata = (unsigned short *) (pbase + header->ofs_frames); + if (header->version == 1) + { + for (i = 0, k = 0;i < (int)header->num_frames;i++) + { + for (j = 0;j < (int)header->num_poses;j++, k++) + { + loadmodel->data_poses6s[k*6 + 0] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[0] + (pose1[j].channelmask&1 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[0] : 0)); + loadmodel->data_poses6s[k*6 + 1] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[1] + (pose1[j].channelmask&2 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[1] : 0)); + loadmodel->data_poses6s[k*6 + 2] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[2] + (pose1[j].channelmask&4 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[2] : 0)); + loadmodel->data_poses6s[k*6 + 3] = 32767.0f * (pose1[j].channeloffset[3] + (pose1[j].channelmask&8 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[3] : 0)); + loadmodel->data_poses6s[k*6 + 4] = 32767.0f * (pose1[j].channeloffset[4] + (pose1[j].channelmask&16 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[4] : 0)); + loadmodel->data_poses6s[k*6 + 5] = 32767.0f * (pose1[j].channeloffset[5] + (pose1[j].channelmask&32 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[5] : 0)); + // skip scale data for now + if(pose1[j].channelmask&64) framedata++; + if(pose1[j].channelmask&128) framedata++; + if(pose1[j].channelmask&256) framedata++; + } + } + } + else + { + for (i = 0, k = 0;i < (int)header->num_frames;i++) + { + for (j = 0;j < (int)header->num_poses;j++, k++) + { + float rot[4]; + loadmodel->data_poses6s[k*6 + 0] = loadmodel->num_poseinvscale * (pose[j].channeloffset[0] + (pose[j].channelmask&1 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[0] : 0)); + loadmodel->data_poses6s[k*6 + 1] = loadmodel->num_poseinvscale * (pose[j].channeloffset[1] + (pose[j].channelmask&2 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[1] : 0)); + loadmodel->data_poses6s[k*6 + 2] = loadmodel->num_poseinvscale * (pose[j].channeloffset[2] + (pose[j].channelmask&4 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[2] : 0)); + rot[0] = pose[j].channeloffset[3] + (pose[j].channelmask&8 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[3] : 0); + rot[1] = pose[j].channeloffset[4] + (pose[j].channelmask&16 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[4] : 0); + rot[2] = pose[j].channeloffset[5] + (pose[j].channelmask&32 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[5] : 0); + rot[3] = pose[j].channeloffset[6] + (pose[j].channelmask&64 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[6] : 0); + if (rot[3] > 0) + Vector4Negate(rot, rot); + Vector4Normalize2(rot, rot); + loadmodel->data_poses6s[k*6 + 3] = 32767.0f * rot[0]; + loadmodel->data_poses6s[k*6 + 4] = 32767.0f * rot[1]; + loadmodel->data_poses6s[k*6 + 5] = 32767.0f * rot[2]; + // skip scale data for now + if(pose[j].channelmask&128) framedata++; + if(pose[j].channelmask&256) framedata++; + if(pose[j].channelmask&512) framedata++; + } + } + } + + // load bounding box data + if (header->ofs_bounds) + { + float xyradius = 0, radius = 0; + bounds = (iqmbounds_t *) (pbase + header->ofs_bounds); + VectorClear(loadmodel->normalmins); + VectorClear(loadmodel->normalmaxs); + for (i = 0; i < (int)header->num_frames;i++) + { + bounds[i].mins[0] = LittleFloat(bounds[i].mins[0]); + bounds[i].mins[1] = LittleFloat(bounds[i].mins[1]); + bounds[i].mins[2] = LittleFloat(bounds[i].mins[2]); + bounds[i].maxs[0] = LittleFloat(bounds[i].maxs[0]); + bounds[i].maxs[1] = LittleFloat(bounds[i].maxs[1]); + bounds[i].maxs[2] = LittleFloat(bounds[i].maxs[2]); + bounds[i].xyradius = LittleFloat(bounds[i].xyradius); + bounds[i].radius = LittleFloat(bounds[i].radius); + if (!i) + { + VectorCopy(bounds[i].mins, loadmodel->normalmins); + VectorCopy(bounds[i].maxs, loadmodel->normalmaxs); + } + else + { + if (loadmodel->normalmins[0] > bounds[i].mins[0]) loadmodel->normalmins[0] = bounds[i].mins[0]; + if (loadmodel->normalmins[1] > bounds[i].mins[1]) loadmodel->normalmins[1] = bounds[i].mins[1]; + if (loadmodel->normalmins[2] > bounds[i].mins[2]) loadmodel->normalmins[2] = bounds[i].mins[2]; + if (loadmodel->normalmaxs[0] < bounds[i].maxs[0]) loadmodel->normalmaxs[0] = bounds[i].maxs[0]; + if (loadmodel->normalmaxs[1] < bounds[i].maxs[1]) loadmodel->normalmaxs[1] = bounds[i].maxs[1]; + if (loadmodel->normalmaxs[2] < bounds[i].maxs[2]) loadmodel->normalmaxs[2] = bounds[i].maxs[2]; + } + if (bounds[i].xyradius > xyradius) + xyradius = bounds[i].xyradius; + if (bounds[i].radius > radius) + radius = bounds[i].radius; + } + loadmodel->yawmins[0] = loadmodel->yawmins[1] = -xyradius; + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = xyradius; + loadmodel->yawmins[2] = loadmodel->normalmins[2]; + loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; + loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -radius; + loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = radius; + loadmodel->radius = radius; + loadmodel->radius2 = radius * radius; + } + + // load triangle data + inelements = (const int *) (pbase + header->ofs_triangles); + outelements = loadmodel->surfmesh.data_element3i; + for (i = 0;i < (int)header->num_triangles;i++) + { + outelements[0] = LittleLong(inelements[0]); + outelements[1] = LittleLong(inelements[1]); + outelements[2] = LittleLong(inelements[2]); + outelements += 3; + inelements += 3; + } + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, header->num_vertexes, __FILE__, __LINE__); + + if (header->ofs_neighbors && loadmodel->surfmesh.data_neighbor3i) + { + inelements = (const int *) (pbase + header->ofs_neighbors); + outelements = loadmodel->surfmesh.data_neighbor3i; + for (i = 0;i < (int)header->num_triangles;i++) + { + outelements[0] = LittleLong(inelements[0]); + outelements[1] = LittleLong(inelements[1]); + outelements[2] = LittleLong(inelements[2]); + outelements += 3; + inelements += 3; + } + } + + // load vertex data + outvertex = loadmodel->surfmesh.data_vertex3f; + for (i = 0;i < (int)header->num_vertexes;i++) + { + outvertex[0] = LittleFloat(vposition[0]); + outvertex[1] = LittleFloat(vposition[1]); + outvertex[2] = LittleFloat(vposition[2]); + vposition += 3; + outvertex += 3; + } + + outtexcoord = loadmodel->surfmesh.data_texcoordtexture2f; + for (i = 0;i < (int)header->num_vertexes;i++) + { + outtexcoord[0] = LittleFloat(vtexcoord[0]); + outtexcoord[1] = LittleFloat(vtexcoord[1]); + vtexcoord += 2; + outtexcoord += 2; + } + + if(vnormal) + { + outnormal = loadmodel->surfmesh.data_normal3f; + for (i = 0;i < (int)header->num_vertexes;i++) + { + outnormal[0] = LittleFloat(vnormal[0]); + outnormal[1] = LittleFloat(vnormal[1]); + outnormal[2] = LittleFloat(vnormal[2]); + vnormal += 3; + outnormal += 3; + } + } + + if(vnormal && vtangent) + { + outnormal = loadmodel->surfmesh.data_normal3f; + outsvector = loadmodel->surfmesh.data_svector3f; + outtvector = loadmodel->surfmesh.data_tvector3f; + for (i = 0;i < (int)header->num_vertexes;i++) + { + outsvector[0] = LittleFloat(vtangent[0]); + outsvector[1] = LittleFloat(vtangent[1]); + outsvector[2] = LittleFloat(vtangent[2]); + if(LittleFloat(vtangent[3]) < 0) + CrossProduct(outsvector, outnormal, outtvector); + else + CrossProduct(outnormal, outsvector, outtvector); + vtangent += 4; + outnormal += 3; + outsvector += 3; + outtvector += 3; + } + } + + for (i = 0; i < (int)header->num_vertexes;i++) + { + blendweights_t weights; + memcpy(weights.index, vblendindexes + i*4, 4); + memcpy(weights.influence, vblendweights + i*4, 4); + loadmodel->surfmesh.blends[i] = Mod_Skeletal_AddBlend(loadmodel, &weights); + } + + // load meshes + mesh = (iqmmesh_t *) (pbase + header->ofs_meshes); + for (i = 0;i < (int)header->num_meshes;i++) + { + msurface_t *surface; + + mesh[i].name = LittleLong(mesh[i].name); + mesh[i].material = LittleLong(mesh[i].material); + mesh[i].first_vertex = LittleLong(mesh[i].first_vertex); + mesh[i].num_vertexes = LittleLong(mesh[i].num_vertexes); + mesh[i].first_triangle = LittleLong(mesh[i].first_triangle); + mesh[i].num_triangles = LittleLong(mesh[i].num_triangles); + + loadmodel->sortedmodelsurfaces[i] = i; + surface = loadmodel->data_surfaces + i; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = mesh[i].first_triangle; + surface->num_triangles = mesh[i].num_triangles; + surface->num_firstvertex = mesh[i].first_vertex; + surface->num_vertices = mesh[i].num_vertexes; + + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, &text[mesh[i].name], &text[mesh[i].material]); + } + + Mod_FreeSkinFiles(skinfiles); + Mod_MakeSortedSurfaces(loadmodel); + + // compute all the mesh information that was not loaded from the file + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + if (!vnormal) + Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + if (!vnormal || !vtangent) + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (!header->ofs_neighbors && loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + if (!header->ofs_bounds) + Mod_Alias_CalculateBoundingBox(); + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } +} + + diff --git a/misc/source/darkplaces-src/model_alias.h b/misc/source/darkplaces-src/model_alias.h new file mode 100644 index 00000000..ddf8e5c0 --- /dev/null +++ b/misc/source/darkplaces-src/model_alias.h @@ -0,0 +1,248 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef MODEL_ALIAS_H +#define MODEL_ALIAS_H + +/* +============================================================================== + +ALIAS MODELS + +Alias models are position independent, so the cache manager can move them. +============================================================================== +*/ + +#include "modelgen.h" + +typedef struct daliashdr_s +{ + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + synctype_t synctype; + int flags; + float size; +} +daliashdr_t; + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +// LordHavoc: grabbed this from the Q2 utility source, +// renamed a things to avoid conflicts + +#define MD2ALIAS_VERSION 8 +#define MD2_SKINNAME 64 + +typedef struct md2stvert_s +{ + short s; + short t; +} md2stvert_t; + +typedef struct md2triangle_s +{ + short index_xyz[3]; + short index_st[3]; +} md2triangle_t; + +typedef struct md2frame_s +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing +} md2frame_t; + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct md2_s +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file +} md2_t; + +// all md3 ints, floats, and shorts, are little endian, and thus need to be +// passed through LittleLong/LittleFloat/LittleShort to avoid breaking on +// bigendian machines +#define MD3VERSION 15 +#define MD3NAME 64 +#define MD3FRAMENAME 16 + +// the origin is at 1/64th scale +// the pitch and yaw are encoded as 8 bits each +typedef struct md3vertex_s +{ + short origin[3]; + unsigned char pitch; + unsigned char yaw; +} +md3vertex_t; + +// one per frame +typedef struct md3frameinfo_s +{ + float mins[3]; + float maxs[3]; + float origin[3]; + float radius; + char name[MD3FRAMENAME]; +} +md3frameinfo_t; + +// one per tag per frame +typedef struct md3tag_s +{ + char name[MD3NAME]; + float origin[3]; + float rotationmatrix[9]; +} +md3tag_t; + +// one per shader per mesh +typedef struct md3shader_s +{ + char name[MD3NAME]; + // engine field (yes this empty int does exist in the file) + int shadernum; +} +md3shader_t; + +// one per mesh per model +// +// note that the lump_ offsets in this struct are relative to the beginning +// of the mesh struct +// +// to find the next mesh in the file, you must go to lump_end, which puts you +// at the beginning of the next mesh +typedef struct md3mesh_s +{ + char identifier[4]; // "IDP3" + char name[MD3NAME]; + int flags; + int num_frames; + int num_shaders; + int num_vertices; + int num_triangles; + int lump_elements; + int lump_shaders; + int lump_texcoords; + int lump_framevertices; + int lump_end; +} +md3mesh_t; + +// this struct is at the beginning of the md3 file +// +// note that the lump_ offsets in this struct are relative to the beginning +// of the header struct (which is the beginning of the file) +typedef struct md3modelheader_s +{ + char identifier[4]; // "IDP3" + int version; // 15 + char name[MD3NAME]; + int flags; + int num_frames; + int num_tags; + int num_meshes; + int num_skins; + int lump_frameinfo; + int lump_tags; + int lump_meshes; + int lump_end; +} +md3modelheader_t; + +typedef struct aliastag_s +{ + char name[MD3NAME]; + float matrixgl[12]; +} +aliastag_t; + +typedef struct aliasbone_s +{ + char name[MD3NAME]; + int flags; + int parent; // -1 for no parent +} +aliasbone_t; + +#include "model_zymotic.h" + +#include "model_dpmodel.h" + +#include "model_psk.h" + +#include "model_iqm.h" + +// for decoding md3 model latlong vertex normals +extern float mod_md3_sin[320]; + +extern cvar_t r_skeletal_debugbone; +extern cvar_t r_skeletal_debugbonecomponent; +extern cvar_t r_skeletal_debugbonevalue; +extern cvar_t r_skeletal_debugtranslatex; +extern cvar_t r_skeletal_debugtranslatey; +extern cvar_t r_skeletal_debugtranslatez; + +void *Mod_Skeletal_AnimateVertices_AllocBuffers(size_t nbytes); + +#endif + diff --git a/misc/source/darkplaces-src/model_brush.c b/misc/source/darkplaces-src/model_brush.c new file mode 100644 index 00000000..6a329ccd --- /dev/null +++ b/misc/source/darkplaces-src/model_brush.c @@ -0,0 +1,7837 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "image.h" +#include "r_shadow.h" +#include "polygon.h" +#include "curves.h" +#include "wad.h" + + +//cvar_t r_subdivide_size = {CVAR_SAVE, "r_subdivide_size", "128", "how large water polygons should be (smaller values produce more polygons which give better warping effects)"}; +cvar_t mod_bsp_portalize = {0, "mod_bsp_portalize", "1", "enables portal generation from BSP tree (may take several seconds per map), used by r_drawportals, r_useportalculling, r_shadow_realtime_world_compileportalculling, sv_cullentities_portal"}; +cvar_t r_novis = {0, "r_novis", "0", "draws whole level, see also sv_cullentities_pvs 0"}; +cvar_t r_nosurftextures = {0, "r_nosurftextures", "0", "pretends there was no texture lump found in the q1bsp/hlbsp loading (useful for debugging this rare case)"}; +cvar_t r_subdivisions_tolerance = {0, "r_subdivisions_tolerance", "4", "maximum error tolerance on curve subdivision for rendering purposes (in other words, the curves will be given as many polygons as necessary to represent curves at this quality)"}; +cvar_t r_subdivisions_mintess = {0, "r_subdivisions_mintess", "0", "minimum number of subdivisions (values above 0 will smooth curves that don't need it)"}; +cvar_t r_subdivisions_maxtess = {0, "r_subdivisions_maxtess", "1024", "maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing)"}; +cvar_t r_subdivisions_maxvertices = {0, "r_subdivisions_maxvertices", "65536", "maximum vertices allowed per subdivided curve"}; +cvar_t r_subdivisions_collision_tolerance = {0, "r_subdivisions_collision_tolerance", "15", "maximum error tolerance on curve subdivision for collision purposes (usually a larger error tolerance than for rendering)"}; +cvar_t r_subdivisions_collision_mintess = {0, "r_subdivisions_collision_mintess", "0", "minimum number of subdivisions (values above 0 will smooth curves that don't need it)"}; +cvar_t r_subdivisions_collision_maxtess = {0, "r_subdivisions_collision_maxtess", "1024", "maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing)"}; +cvar_t r_subdivisions_collision_maxvertices = {0, "r_subdivisions_collision_maxvertices", "4225", "maximum vertices allowed per subdivided curve"}; +cvar_t r_trippy = {0, "r_trippy", "0", "easter egg"}; +cvar_t mod_noshader_default_offsetmapping = {CVAR_SAVE, "mod_noshader_default_offsetmapping", "1", "use offsetmapping by default on all surfaces that are not using q3 shader files"}; +cvar_t mod_q3bsp_curves_collisions = {0, "mod_q3bsp_curves_collisions", "1", "enables collisions with curves (SLOW)"}; +cvar_t mod_q3bsp_curves_collisions_stride = {0, "mod_q3bsp_curves_collisions_stride", "16", "collisions against curves: optimize performance by doing a combined collision check for this triangle amount first (-1 avoids any box tests)"}; +cvar_t mod_q3bsp_curves_stride = {0, "mod_q3bsp_curves_stride", "16", "particle effect collisions against curves: optimize performance by doing a combined collision check for this triangle amount first (-1 avoids any box tests)"}; +cvar_t mod_q3bsp_optimizedtraceline = {0, "mod_q3bsp_optimizedtraceline", "1", "whether to use optimized traceline code for line traces (as opposed to tracebox code)"}; +cvar_t mod_q3bsp_debugtracebrush = {0, "mod_q3bsp_debugtracebrush", "0", "selects different tracebrush bsp recursion algorithms (for debugging purposes only)"}; +cvar_t mod_q3bsp_lightmapmergepower = {CVAR_SAVE, "mod_q3bsp_lightmapmergepower", "4", "merges the quake3 128x128 lightmap textures into larger lightmap group textures to speed up rendering, 1 = 256x256, 2 = 512x512, 3 = 1024x1024, 4 = 2048x2048, 5 = 4096x4096, ..."}; +cvar_t mod_q3bsp_nolightmaps = {CVAR_SAVE, "mod_q3bsp_nolightmaps", "0", "do not load lightmaps in Q3BSP maps (to save video RAM, but be warned: it looks ugly)"}; +cvar_t mod_q3bsp_tracelineofsight_brushes = {0, "mod_q3bsp_tracelineofsight_brushes", "0", "enables culling of entities behind detail brushes, curves, etc"}; +cvar_t mod_q3shader_default_offsetmapping = {CVAR_SAVE, "mod_q3shader_default_offsetmapping", "1", "use offsetmapping by default on all surfaces that are using q3 shader files"}; +cvar_t mod_q3shader_default_polygonfactor = {0, "mod_q3shader_default_polygonfactor", "0", "biases depth values of 'polygonoffset' shaders to prevent z-fighting artifacts"}; +cvar_t mod_q3shader_default_polygonoffset = {0, "mod_q3shader_default_polygonoffset", "-2", "biases depth values of 'polygonoffset' shaders to prevent z-fighting artifacts"}; + +cvar_t mod_q1bsp_polygoncollisions = {0, "mod_q1bsp_polygoncollisions", "0", "disables use of precomputed cliphulls and instead collides with polygons (uses Bounding Interval Hierarchy optimizations)"}; +cvar_t mod_collision_bih = {0, "mod_collision_bih", "1", "enables use of generated Bounding Interval Hierarchy tree instead of compiled bsp tree in collision code"}; +cvar_t mod_recalculatenodeboxes = {0, "mod_recalculatenodeboxes", "1", "enables use of generated node bounding boxes based on BSP tree portal reconstruction, rather than the node boxes supplied by the map compiler"}; + +static texture_t mod_q1bsp_texture_solid; +static texture_t mod_q1bsp_texture_sky; +static texture_t mod_q1bsp_texture_lava; +static texture_t mod_q1bsp_texture_slime; +static texture_t mod_q1bsp_texture_water; + +void Mod_BrushInit(void) +{ +// Cvar_RegisterVariable(&r_subdivide_size); + Cvar_RegisterVariable(&mod_bsp_portalize); + Cvar_RegisterVariable(&r_novis); + Cvar_RegisterVariable(&r_nosurftextures); + Cvar_RegisterVariable(&r_subdivisions_tolerance); + Cvar_RegisterVariable(&r_subdivisions_mintess); + Cvar_RegisterVariable(&r_subdivisions_maxtess); + Cvar_RegisterVariable(&r_subdivisions_maxvertices); + Cvar_RegisterVariable(&r_subdivisions_collision_tolerance); + Cvar_RegisterVariable(&r_subdivisions_collision_mintess); + Cvar_RegisterVariable(&r_subdivisions_collision_maxtess); + Cvar_RegisterVariable(&r_subdivisions_collision_maxvertices); + Cvar_RegisterVariable(&r_trippy); + Cvar_RegisterVariable(&mod_noshader_default_offsetmapping); + Cvar_RegisterVariable(&mod_q3bsp_curves_collisions); + Cvar_RegisterVariable(&mod_q3bsp_curves_collisions_stride); + Cvar_RegisterVariable(&mod_q3bsp_curves_stride); + Cvar_RegisterVariable(&mod_q3bsp_optimizedtraceline); + Cvar_RegisterVariable(&mod_q3bsp_debugtracebrush); + Cvar_RegisterVariable(&mod_q3bsp_lightmapmergepower); + Cvar_RegisterVariable(&mod_q3bsp_nolightmaps); + Cvar_RegisterVariable(&mod_q3bsp_tracelineofsight_brushes); + Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping); + Cvar_RegisterVariable(&mod_q3shader_default_polygonfactor); + Cvar_RegisterVariable(&mod_q3shader_default_polygonoffset); + Cvar_RegisterVariable(&mod_q1bsp_polygoncollisions); + Cvar_RegisterVariable(&mod_collision_bih); + Cvar_RegisterVariable(&mod_recalculatenodeboxes); + + memset(&mod_q1bsp_texture_solid, 0, sizeof(mod_q1bsp_texture_solid)); + strlcpy(mod_q1bsp_texture_solid.name, "solid" , sizeof(mod_q1bsp_texture_solid.name)); + mod_q1bsp_texture_solid.surfaceflags = 0; + mod_q1bsp_texture_solid.supercontents = SUPERCONTENTS_SOLID; + + mod_q1bsp_texture_sky = mod_q1bsp_texture_solid; + strlcpy(mod_q1bsp_texture_sky.name, "sky", sizeof(mod_q1bsp_texture_sky.name)); + mod_q1bsp_texture_sky.surfaceflags = Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT | Q3SURFACEFLAG_NOMARKS | Q3SURFACEFLAG_NODLIGHT | Q3SURFACEFLAG_NOLIGHTMAP; + mod_q1bsp_texture_sky.supercontents = SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP; + + mod_q1bsp_texture_lava = mod_q1bsp_texture_solid; + strlcpy(mod_q1bsp_texture_lava.name, "*lava", sizeof(mod_q1bsp_texture_lava.name)); + mod_q1bsp_texture_lava.surfaceflags = Q3SURFACEFLAG_NOMARKS; + mod_q1bsp_texture_lava.supercontents = SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP; + + mod_q1bsp_texture_slime = mod_q1bsp_texture_solid; + strlcpy(mod_q1bsp_texture_slime.name, "*slime", sizeof(mod_q1bsp_texture_slime.name)); + mod_q1bsp_texture_slime.surfaceflags = Q3SURFACEFLAG_NOMARKS; + mod_q1bsp_texture_slime.supercontents = SUPERCONTENTS_SLIME; + + mod_q1bsp_texture_water = mod_q1bsp_texture_solid; + strlcpy(mod_q1bsp_texture_water.name, "*water", sizeof(mod_q1bsp_texture_water.name)); + mod_q1bsp_texture_water.surfaceflags = Q3SURFACEFLAG_NOMARKS; + mod_q1bsp_texture_water.supercontents = SUPERCONTENTS_WATER; +} + +static mleaf_t *Mod_Q1BSP_PointInLeaf(dp_model_t *model, const vec3_t p) +{ + mnode_t *node; + + if (model == NULL) + return NULL; + + // LordHavoc: modified to start at first clip node, + // in other words: first node of the (sub)model + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + while (node->plane) + node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist]; + + return (mleaf_t *)node; +} + +static void Mod_Q1BSP_AmbientSoundLevelsForPoint(dp_model_t *model, const vec3_t p, unsigned char *out, int outsize) +{ + int i; + mleaf_t *leaf; + leaf = Mod_Q1BSP_PointInLeaf(model, p); + if (leaf) + { + i = min(outsize, (int)sizeof(leaf->ambient_sound_level)); + if (i) + { + memcpy(out, leaf->ambient_sound_level, i); + out += i; + outsize -= i; + } + } + if (outsize) + memset(out, 0, outsize); +} + +static int Mod_Q1BSP_FindBoxClusters(dp_model_t *model, const vec3_t mins, const vec3_t maxs, int maxclusters, int *clusterlist) +{ + int numclusters = 0; + int nodestackindex = 0; + mnode_t *node, *nodestack[1024]; + if (!model->brush.num_pvsclusters) + return -1; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + for (;;) + { +#if 1 + if (node->plane) + { + // node - recurse down the BSP tree + int sides = BoxOnPlaneSide(mins, maxs, node->plane); + if (sides < 3) + { + if (sides == 0) + return -1; // ERROR: NAN bounding box! + // box is on one side of plane, take that path + node = node->children[sides-1]; + } + else + { + // box crosses plane, take one path and remember the other + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + } + continue; + } + else + { + // leaf - add clusterindex to list + if (numclusters < maxclusters) + clusterlist[numclusters] = ((mleaf_t *)node)->clusterindex; + numclusters++; + } +#else + if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) + { + if (node->plane) + { + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + continue; + } + else + { + // leaf - add clusterindex to list + if (numclusters < maxclusters) + clusterlist[numclusters] = ((mleaf_t *)node)->clusterindex; + numclusters++; + } + } +#endif + // try another path we didn't take earlier + if (nodestackindex == 0) + break; + node = nodestack[--nodestackindex]; + } + // return number of clusters found (even if more than the maxclusters) + return numclusters; +} + +static int Mod_Q1BSP_BoxTouchingPVS(dp_model_t *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs) +{ + int nodestackindex = 0; + mnode_t *node, *nodestack[1024]; + if (!model->brush.num_pvsclusters) + return true; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + for (;;) + { +#if 1 + if (node->plane) + { + // node - recurse down the BSP tree + int sides = BoxOnPlaneSide(mins, maxs, node->plane); + if (sides < 3) + { + if (sides == 0) + return -1; // ERROR: NAN bounding box! + // box is on one side of plane, take that path + node = node->children[sides-1]; + } + else + { + // box crosses plane, take one path and remember the other + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + } + continue; + } + else + { + // leaf - check cluster bit + int clusterindex = ((mleaf_t *)node)->clusterindex; + if (CHECKPVSBIT(pvs, clusterindex)) + { + // it is visible, return immediately with the news + return true; + } + } +#else + if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) + { + if (node->plane) + { + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + continue; + } + else + { + // leaf - check cluster bit + int clusterindex = ((mleaf_t *)node)->clusterindex; + if (CHECKPVSBIT(pvs, clusterindex)) + { + // it is visible, return immediately with the news + return true; + } + } + } +#endif + // nothing to see here, try another path we didn't take earlier + if (nodestackindex == 0) + break; + node = nodestack[--nodestackindex]; + } + // it is not visible + return false; +} + +static int Mod_Q1BSP_BoxTouchingLeafPVS(dp_model_t *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs) +{ + int nodestackindex = 0; + mnode_t *node, *nodestack[1024]; + if (!model->brush.num_leafs) + return true; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + for (;;) + { +#if 1 + if (node->plane) + { + // node - recurse down the BSP tree + int sides = BoxOnPlaneSide(mins, maxs, node->plane); + if (sides < 3) + { + if (sides == 0) + return -1; // ERROR: NAN bounding box! + // box is on one side of plane, take that path + node = node->children[sides-1]; + } + else + { + // box crosses plane, take one path and remember the other + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + } + continue; + } + else + { + // leaf - check cluster bit + int clusterindex = ((mleaf_t *)node) - model->brush.data_leafs; + if (CHECKPVSBIT(pvs, clusterindex)) + { + // it is visible, return immediately with the news + return true; + } + } +#else + if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) + { + if (node->plane) + { + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + continue; + } + else + { + // leaf - check cluster bit + int clusterindex = ((mleaf_t *)node) - model->brush.data_leafs; + if (CHECKPVSBIT(pvs, clusterindex)) + { + // it is visible, return immediately with the news + return true; + } + } + } +#endif + // nothing to see here, try another path we didn't take earlier + if (nodestackindex == 0) + break; + node = nodestack[--nodestackindex]; + } + // it is not visible + return false; +} + +static int Mod_Q1BSP_BoxTouchingVisibleLeafs(dp_model_t *model, const unsigned char *visibleleafs, const vec3_t mins, const vec3_t maxs) +{ + int nodestackindex = 0; + mnode_t *node, *nodestack[1024]; + if (!model->brush.num_leafs) + return true; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + for (;;) + { +#if 1 + if (node->plane) + { + // node - recurse down the BSP tree + int sides = BoxOnPlaneSide(mins, maxs, node->plane); + if (sides < 3) + { + if (sides == 0) + return -1; // ERROR: NAN bounding box! + // box is on one side of plane, take that path + node = node->children[sides-1]; + } + else + { + // box crosses plane, take one path and remember the other + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + } + continue; + } + else + { + // leaf - check if it is visible + if (visibleleafs[(mleaf_t *)node - model->brush.data_leafs]) + { + // it is visible, return immediately with the news + return true; + } + } +#else + if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) + { + if (node->plane) + { + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + continue; + } + else + { + // leaf - check if it is visible + if (visibleleafs[(mleaf_t *)node - model->brush.data_leafs]) + { + // it is visible, return immediately with the news + return true; + } + } + } +#endif + // nothing to see here, try another path we didn't take earlier + if (nodestackindex == 0) + break; + node = nodestack[--nodestackindex]; + } + // it is not visible + return false; +} + +typedef struct findnonsolidlocationinfo_s +{ + vec3_t center; + vec3_t absmin, absmax; + vec_t radius; + vec3_t nudge; + vec_t bestdist; + dp_model_t *model; +} +findnonsolidlocationinfo_t; + +static void Mod_Q1BSP_FindNonSolidLocation_r_Triangle(findnonsolidlocationinfo_t *info, msurface_t *surface, int k) +{ + int i, *tri; + float dist, f, vert[3][3], edge[3][3], facenormal[3], edgenormal[3][3], point[3]; + + tri = (info->model->surfmesh.data_element3i + 3 * surface->num_firsttriangle) + k * 3; + VectorCopy((info->model->surfmesh.data_vertex3f + tri[0] * 3), vert[0]); + VectorCopy((info->model->surfmesh.data_vertex3f + tri[1] * 3), vert[1]); + VectorCopy((info->model->surfmesh.data_vertex3f + tri[2] * 3), vert[2]); + VectorSubtract(vert[1], vert[0], edge[0]); + VectorSubtract(vert[2], vert[1], edge[1]); + CrossProduct(edge[1], edge[0], facenormal); + if (facenormal[0] || facenormal[1] || facenormal[2]) + { + VectorNormalize(facenormal); + f = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal); + if (f <= info->bestdist && f >= -info->bestdist) + { + VectorSubtract(vert[0], vert[2], edge[2]); + VectorNormalize(edge[0]); + VectorNormalize(edge[1]); + VectorNormalize(edge[2]); + CrossProduct(facenormal, edge[0], edgenormal[0]); + CrossProduct(facenormal, edge[1], edgenormal[1]); + CrossProduct(facenormal, edge[2], edgenormal[2]); + // face distance + if (DotProduct(info->center, edgenormal[0]) < DotProduct(vert[0], edgenormal[0]) + && DotProduct(info->center, edgenormal[1]) < DotProduct(vert[1], edgenormal[1]) + && DotProduct(info->center, edgenormal[2]) < DotProduct(vert[2], edgenormal[2])) + { + // we got lucky, the center is within the face + dist = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal); + if (dist < 0) + { + dist = -dist; + if (info->bestdist > dist) + { + info->bestdist = dist; + VectorScale(facenormal, (info->radius - -dist), info->nudge); + } + } + else + { + if (info->bestdist > dist) + { + info->bestdist = dist; + VectorScale(facenormal, (info->radius - dist), info->nudge); + } + } + } + else + { + // check which edge or vertex the center is nearest + for (i = 0;i < 3;i++) + { + f = DotProduct(info->center, edge[i]); + if (f >= DotProduct(vert[0], edge[i]) + && f <= DotProduct(vert[1], edge[i])) + { + // on edge + VectorMA(info->center, -f, edge[i], point); + dist = sqrt(DotProduct(point, point)); + if (info->bestdist > dist) + { + info->bestdist = dist; + VectorScale(point, (info->radius / dist), info->nudge); + } + // skip both vertex checks + // (both are further away than this edge) + i++; + } + else + { + // not on edge, check first vertex of edge + VectorSubtract(info->center, vert[i], point); + dist = sqrt(DotProduct(point, point)); + if (info->bestdist > dist) + { + info->bestdist = dist; + VectorScale(point, (info->radius / dist), info->nudge); + } + } + } + } + } + } +} + +static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *info, mleaf_t *leaf) +{ + int surfacenum, k, *mark; + msurface_t *surface; + for (surfacenum = 0, mark = leaf->firstleafsurface;surfacenum < leaf->numleafsurfaces;surfacenum++, mark++) + { + surface = info->model->data_surfaces + *mark; + if (surface->texture->supercontents & SUPERCONTENTS_SOLID) + { + if(surface->deprecatedq3num_bboxstride > 0) + { + int i, cnt, tri; + cnt = (surface->num_triangles + surface->deprecatedq3num_bboxstride - 1) / surface->deprecatedq3num_bboxstride; + for(i = 0; i < cnt; ++i) + { + if(BoxesOverlap(surface->deprecatedq3data_bbox6f + i * 6, surface->deprecatedq3data_bbox6f + i * 6 + 3, info->absmin, info->absmax)) + { + for(k = 0; k < surface->deprecatedq3num_bboxstride; ++k) + { + tri = i * surface->deprecatedq3num_bboxstride + k; + if(tri >= surface->num_triangles) + break; + Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, tri); + } + } + } + } + else + { + for (k = 0;k < surface->num_triangles;k++) + { + Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, k); + } + } + } + } +} + +static void Mod_Q1BSP_FindNonSolidLocation_r(findnonsolidlocationinfo_t *info, mnode_t *node) +{ + if (node->plane) + { + float f = PlaneDiff(info->center, node->plane); + if (f >= -info->bestdist) + Mod_Q1BSP_FindNonSolidLocation_r(info, node->children[0]); + if (f <= info->bestdist) + Mod_Q1BSP_FindNonSolidLocation_r(info, node->children[1]); + } + else + { + if (((mleaf_t *)node)->numleafsurfaces) + Mod_Q1BSP_FindNonSolidLocation_r_Leaf(info, (mleaf_t *)node); + } +} + +static void Mod_Q1BSP_FindNonSolidLocation(dp_model_t *model, const vec3_t in, vec3_t out, float radius) +{ + int i; + findnonsolidlocationinfo_t info; + if (model == NULL) + { + VectorCopy(in, out); + return; + } + VectorCopy(in, info.center); + info.radius = radius; + info.model = model; + i = 0; + do + { + VectorClear(info.nudge); + info.bestdist = radius; + VectorCopy(info.center, info.absmin); + VectorCopy(info.center, info.absmax); + info.absmin[0] -= info.radius + 1; + info.absmin[1] -= info.radius + 1; + info.absmin[2] -= info.radius + 1; + info.absmax[0] += info.radius + 1; + info.absmax[1] += info.radius + 1; + info.absmax[2] += info.radius + 1; + Mod_Q1BSP_FindNonSolidLocation_r(&info, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode); + VectorAdd(info.center, info.nudge, info.center); + } + while (info.bestdist < radius && ++i < 10); + VectorCopy(info.center, out); +} + +int Mod_Q1BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents) +{ + switch(nativecontents) + { + case CONTENTS_EMPTY: + return 0; + case CONTENTS_SOLID: + return SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; + case CONTENTS_WATER: + return SUPERCONTENTS_WATER; + case CONTENTS_SLIME: + return SUPERCONTENTS_SLIME; + case CONTENTS_LAVA: + return SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP; + case CONTENTS_SKY: + return SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP | SUPERCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque + } + return 0; +} + +int Mod_Q1BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents) +{ + if (supercontents & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY)) + return CONTENTS_SOLID; + if (supercontents & SUPERCONTENTS_SKY) + return CONTENTS_SKY; + if (supercontents & SUPERCONTENTS_LAVA) + return CONTENTS_LAVA; + if (supercontents & SUPERCONTENTS_SLIME) + return CONTENTS_SLIME; + if (supercontents & SUPERCONTENTS_WATER) + return CONTENTS_WATER; + return CONTENTS_EMPTY; +} + +typedef struct RecursiveHullCheckTraceInfo_s +{ + // the hull we're tracing through + const hull_t *hull; + + // the trace structure to fill in + trace_t *trace; + + // start, end, and end - start (in model space) + double start[3]; + double end[3]; + double dist[3]; +} +RecursiveHullCheckTraceInfo_t; + +// 1/32 epsilon to keep floating point happy +#define DIST_EPSILON (0.03125) + +#define HULLCHECKSTATE_EMPTY 0 +#define HULLCHECKSTATE_SOLID 1 +#define HULLCHECKSTATE_DONE 2 + +extern cvar_t collision_prefernudgedfraction; +static int Mod_Q1BSP_RecursiveHullCheck(RecursiveHullCheckTraceInfo_t *t, int num, double p1f, double p2f, double p1[3], double p2[3]) +{ + // status variables, these don't need to be saved on the stack when + // recursing... but are because this should be thread-safe + // (note: tracing against a bbox is not thread-safe, yet) + int ret; + mplane_t *plane; + double t1, t2; + + // variables that need to be stored on the stack when recursing + mclipnode_t *node; + int side; + double midf, mid[3]; + + // LordHavoc: a goto! everyone flee in terror... :) +loc0: + // check for empty + if (num < 0) + { + num = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); + if (!t->trace->startfound) + { + t->trace->startfound = true; + t->trace->startsupercontents |= num; + } + if (num & SUPERCONTENTS_LIQUIDSMASK) + t->trace->inwater = true; + if (num == 0) + t->trace->inopen = true; + if (num & SUPERCONTENTS_SOLID) + t->trace->hittexture = &mod_q1bsp_texture_solid; + else if (num & SUPERCONTENTS_SKY) + t->trace->hittexture = &mod_q1bsp_texture_sky; + else if (num & SUPERCONTENTS_LAVA) + t->trace->hittexture = &mod_q1bsp_texture_lava; + else if (num & SUPERCONTENTS_SLIME) + t->trace->hittexture = &mod_q1bsp_texture_slime; + else + t->trace->hittexture = &mod_q1bsp_texture_water; + t->trace->hitq3surfaceflags = t->trace->hittexture->surfaceflags; + t->trace->hitsupercontents = num; + if (num & t->trace->hitsupercontentsmask) + { + // if the first leaf is solid, set startsolid + if (t->trace->allsolid) + t->trace->startsolid = true; +#if COLLISIONPARANOID >= 3 + Con_Print("S"); +#endif + return HULLCHECKSTATE_SOLID; + } + else + { + t->trace->allsolid = false; +#if COLLISIONPARANOID >= 3 + Con_Print("E"); +#endif + return HULLCHECKSTATE_EMPTY; + } + } + + // find the point distances + node = t->hull->clipnodes + num; + + plane = t->hull->planes + node->planenum; + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + } + + if (t1 < 0) + { + if (t2 < 0) + { +#if COLLISIONPARANOID >= 3 + Con_Print("<"); +#endif + num = node->children[1]; + goto loc0; + } + side = 1; + } + else + { + if (t2 >= 0) + { +#if COLLISIONPARANOID >= 3 + Con_Print(">"); +#endif + num = node->children[0]; + goto loc0; + } + side = 0; + } + + // the line intersects, find intersection point + // LordHavoc: this uses the original trace for maximum accuracy +#if COLLISIONPARANOID >= 3 + Con_Print("M"); +#endif + if (plane->type < 3) + { + t1 = t->start[plane->type] - plane->dist; + t2 = t->end[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, t->start) - plane->dist; + t2 = DotProduct (plane->normal, t->end) - plane->dist; + } + + midf = t1 / (t1 - t2); + midf = bound(p1f, midf, p2f); + VectorMA(t->start, midf, t->dist, mid); + + // recurse both sides, front side first + ret = Mod_Q1BSP_RecursiveHullCheck(t, node->children[side], p1f, midf, p1, mid); + // if this side is not empty, return what it is (solid or done) + if (ret != HULLCHECKSTATE_EMPTY) + return ret; + + ret = Mod_Q1BSP_RecursiveHullCheck(t, node->children[side ^ 1], midf, p2f, mid, p2); + // if other side is not solid, return what it is (empty or done) + if (ret != HULLCHECKSTATE_SOLID) + return ret; + + // front is air and back is solid, this is the impact point... + if (side) + { + t->trace->plane.dist = -plane->dist; + VectorNegate (plane->normal, t->trace->plane.normal); + } + else + { + t->trace->plane.dist = plane->dist; + VectorCopy (plane->normal, t->trace->plane.normal); + } + + // calculate the true fraction + t1 = DotProduct(t->trace->plane.normal, t->start) - t->trace->plane.dist; + t2 = DotProduct(t->trace->plane.normal, t->end) - t->trace->plane.dist; + midf = t1 / (t1 - t2); + t->trace->realfraction = bound(0, midf, 1); + + // calculate the return fraction which is nudged off the surface a bit + midf = (t1 - DIST_EPSILON) / (t1 - t2); + t->trace->fraction = bound(0, midf, 1); + + if (collision_prefernudgedfraction.integer) + t->trace->realfraction = t->trace->fraction; + +#if COLLISIONPARANOID >= 3 + Con_Print("D"); +#endif + return HULLCHECKSTATE_DONE; +} + +//#if COLLISIONPARANOID < 2 +static int Mod_Q1BSP_RecursiveHullCheckPoint(RecursiveHullCheckTraceInfo_t *t, int num) +{ + mplane_t *plane; + mclipnode_t *nodes = t->hull->clipnodes; + mplane_t *planes = t->hull->planes; + vec3_t point; + VectorCopy(t->start, point); + while (num >= 0) + { + plane = planes + nodes[num].planenum; + num = nodes[num].children[(plane->type < 3 ? point[plane->type] : DotProduct(plane->normal, point)) < plane->dist]; + } + num = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); + t->trace->startsupercontents |= num; + if (num & SUPERCONTENTS_LIQUIDSMASK) + t->trace->inwater = true; + if (num == 0) + t->trace->inopen = true; + if (num & t->trace->hitsupercontentsmask) + { + t->trace->allsolid = t->trace->startsolid = true; + return HULLCHECKSTATE_SOLID; + } + else + { + t->trace->allsolid = t->trace->startsolid = false; + return HULLCHECKSTATE_EMPTY; + } +} +//#endif + +static void Mod_Q1BSP_TracePoint(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask) +{ + RecursiveHullCheckTraceInfo_t rhc; + + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + rhc.trace = trace; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 + VectorCopy(start, rhc.start); + VectorCopy(start, rhc.end); + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); +} + +static void Mod_Q1BSP_TraceLine(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + RecursiveHullCheckTraceInfo_t rhc; + + if (VectorCompare(start, end)) + { + Mod_Q1BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + return; + } + + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + rhc.trace = trace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 + VectorCopy(start, rhc.start); + VectorCopy(end, rhc.end); + VectorSubtract(rhc.end, rhc.start, rhc.dist); +#if COLLISIONPARANOID >= 2 + Con_Printf("t(%f %f %f,%f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[2]); + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + { + + double test[3]; + trace_t testtrace; + VectorLerp(rhc.start, rhc.trace->fraction, rhc.end, test); + memset(&testtrace, 0, sizeof(trace_t)); + rhc.trace = &testtrace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + VectorCopy(test, rhc.start); + VectorCopy(test, rhc.end); + VectorClear(rhc.dist); + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); + //Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, test, test); + if (!trace->startsolid && testtrace.startsolid) + Con_Printf(" - ended in solid!\n"); + } + Con_Print("\n"); +#else + if (VectorLength2(rhc.dist)) + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + else + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); +#endif +} + +static void Mod_Q1BSP_TraceBox(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask) +{ + // this function currently only supports same size start and end + double boxsize[3]; + RecursiveHullCheckTraceInfo_t rhc; + + if (VectorCompare(boxmins, boxmaxs)) + { + if (VectorCompare(start, end)) + Mod_Q1BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + else + Mod_Q1BSP_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + return; + } + + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + rhc.trace = trace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + VectorSubtract(boxmaxs, boxmins, boxsize); + if (boxsize[0] < 3) + rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 + else if (model->brush.ishlbsp) + { + // LordHavoc: this has to have a minor tolerance (the .1) because of + // minor float precision errors from the box being transformed around + if (boxsize[0] < 32.1) + { + if (boxsize[2] < 54) // pick the nearest of 36 or 72 + rhc.hull = &model->brushq1.hulls[3]; // 32x32x36 + else + rhc.hull = &model->brushq1.hulls[1]; // 32x32x72 + } + else + rhc.hull = &model->brushq1.hulls[2]; // 64x64x64 + } + else + { + // LordHavoc: this has to have a minor tolerance (the .1) because of + // minor float precision errors from the box being transformed around + if (boxsize[0] < 32.1) + rhc.hull = &model->brushq1.hulls[1]; // 32x32x56 + else + rhc.hull = &model->brushq1.hulls[2]; // 64x64x88 + } + VectorMAMAM(1, start, 1, boxmins, -1, rhc.hull->clip_mins, rhc.start); + VectorMAMAM(1, end, 1, boxmins, -1, rhc.hull->clip_mins, rhc.end); + VectorSubtract(rhc.end, rhc.start, rhc.dist); +#if COLLISIONPARANOID >= 2 + Con_Printf("t(%f %f %f,%f %f %f,%i %f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[2], rhc.hull - model->brushq1.hulls, rhc.hull->clip_mins[0], rhc.hull->clip_mins[1], rhc.hull->clip_mins[2]); + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + { + + double test[3]; + trace_t testtrace; + VectorLerp(rhc.start, rhc.trace->fraction, rhc.end, test); + memset(&testtrace, 0, sizeof(trace_t)); + rhc.trace = &testtrace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + VectorCopy(test, rhc.start); + VectorCopy(test, rhc.end); + VectorClear(rhc.dist); + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); + //Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, test, test); + if (!trace->startsolid && testtrace.startsolid) + Con_Printf(" - ended in solid!\n"); + } + Con_Print("\n"); +#else + if (VectorLength2(rhc.dist)) + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + else + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); +#endif +} + +static int Mod_Q1BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point) +{ + int num = model->brushq1.hulls[0].firstclipnode; + mplane_t *plane; + mclipnode_t *nodes = model->brushq1.hulls[0].clipnodes; + mplane_t *planes = model->brushq1.hulls[0].planes; + while (num >= 0) + { + plane = planes + nodes[num].planenum; + num = nodes[num].children[(plane->type < 3 ? point[plane->type] : DotProduct(plane->normal, point)) < plane->dist]; + } + return Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); +} + +void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture) +{ +#if 1 + colbrushf_t cbox; + colplanef_t cbox_planes[6]; + cbox.isaabb = true; + cbox.hasaabbplanes = true; + cbox.supercontents = boxsupercontents; + cbox.numplanes = 6; + cbox.numpoints = 0; + cbox.numtriangles = 0; + cbox.planes = cbox_planes; + cbox.points = NULL; + cbox.elements = NULL; + cbox.markframe = 0; + cbox.mins[0] = 0; + cbox.mins[1] = 0; + cbox.mins[2] = 0; + cbox.maxs[0] = 0; + cbox.maxs[1] = 0; + cbox.maxs[2] = 0; + cbox_planes[0].normal[0] = 1;cbox_planes[0].normal[1] = 0;cbox_planes[0].normal[2] = 0;cbox_planes[0].dist = cmaxs[0] - mins[0]; + cbox_planes[1].normal[0] = -1;cbox_planes[1].normal[1] = 0;cbox_planes[1].normal[2] = 0;cbox_planes[1].dist = maxs[0] - cmins[0]; + cbox_planes[2].normal[0] = 0;cbox_planes[2].normal[1] = 1;cbox_planes[2].normal[2] = 0;cbox_planes[2].dist = cmaxs[1] - mins[1]; + cbox_planes[3].normal[0] = 0;cbox_planes[3].normal[1] = -1;cbox_planes[3].normal[2] = 0;cbox_planes[3].dist = maxs[1] - cmins[1]; + cbox_planes[4].normal[0] = 0;cbox_planes[4].normal[1] = 0;cbox_planes[4].normal[2] = 1;cbox_planes[4].dist = cmaxs[2] - mins[2]; + cbox_planes[5].normal[0] = 0;cbox_planes[5].normal[1] = 0;cbox_planes[5].normal[2] = -1;cbox_planes[5].dist = maxs[2] - cmins[2]; + cbox_planes[0].q3surfaceflags = boxq3surfaceflags;cbox_planes[0].texture = boxtexture; + cbox_planes[1].q3surfaceflags = boxq3surfaceflags;cbox_planes[1].texture = boxtexture; + cbox_planes[2].q3surfaceflags = boxq3surfaceflags;cbox_planes[2].texture = boxtexture; + cbox_planes[3].q3surfaceflags = boxq3surfaceflags;cbox_planes[3].texture = boxtexture; + cbox_planes[4].q3surfaceflags = boxq3surfaceflags;cbox_planes[4].texture = boxtexture; + cbox_planes[5].q3surfaceflags = boxq3surfaceflags;cbox_planes[5].texture = boxtexture; + memset(trace, 0, sizeof(trace_t)); + trace->hitsupercontentsmask = hitsupercontentsmask; + trace->fraction = 1; + trace->realfraction = 1; + Collision_TraceLineBrushFloat(trace, start, end, &cbox, &cbox); +#else + RecursiveHullCheckTraceInfo_t rhc; + static hull_t box_hull; + static mclipnode_t box_clipnodes[6]; + static mplane_t box_planes[6]; + // fill in a default trace + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + //To keep everything totally uniform, bounding boxes are turned into small + //BSP trees instead of being compared directly. + // create a temp hull from bounding box sizes + box_planes[0].dist = cmaxs[0] - mins[0]; + box_planes[1].dist = cmins[0] - maxs[0]; + box_planes[2].dist = cmaxs[1] - mins[1]; + box_planes[3].dist = cmins[1] - maxs[1]; + box_planes[4].dist = cmaxs[2] - mins[2]; + box_planes[5].dist = cmins[2] - maxs[2]; +#if COLLISIONPARANOID >= 3 + Con_Printf("box_planes %f:%f %f:%f %f:%f\ncbox %f %f %f:%f %f %f\nbox %f %f %f:%f %f %f\n", box_planes[0].dist, box_planes[1].dist, box_planes[2].dist, box_planes[3].dist, box_planes[4].dist, box_planes[5].dist, cmins[0], cmins[1], cmins[2], cmaxs[0], cmaxs[1], cmaxs[2], mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); +#endif + + if (box_hull.clipnodes == NULL) + { + int i, side; + + //Set up the planes and clipnodes so that the six floats of a bounding box + //can just be stored out and get a proper hull_t structure. + + box_hull.clipnodes = box_clipnodes; + box_hull.planes = box_planes; + box_hull.firstclipnode = 0; + box_hull.lastclipnode = 5; + + for (i = 0;i < 6;i++) + { + box_clipnodes[i].planenum = i; + + side = i&1; + + box_clipnodes[i].children[side] = CONTENTS_EMPTY; + if (i != 5) + box_clipnodes[i].children[side^1] = i + 1; + else + box_clipnodes[i].children[side^1] = CONTENTS_SOLID; + + box_planes[i].type = i>>1; + box_planes[i].normal[i>>1] = 1; + } + } + + // trace a line through the generated clipping hull + //rhc.boxsupercontents = boxsupercontents; + rhc.hull = &box_hull; + rhc.trace = trace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + VectorCopy(start, rhc.start); + VectorCopy(end, rhc.end); + VectorSubtract(rhc.end, rhc.start, rhc.dist); + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + //VectorMA(rhc.start, rhc.trace->fraction, rhc.dist, rhc.trace->endpos); + if (rhc.trace->startsupercontents) + rhc.trace->startsupercontents = boxsupercontents; +#endif +} + +void Collision_ClipTrace_Point(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture) +{ + memset(trace, 0, sizeof(trace_t)); + trace->fraction = 1; + trace->realfraction = 1; + if (BoxesOverlap(start, start, cmins, cmaxs)) + { + trace->startsupercontents |= boxsupercontents; + if (hitsupercontentsmask & boxsupercontents) + { + trace->startsolid = true; + trace->allsolid = true; + } + } +} + +static qboolean Mod_Q1BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end) +{ + trace_t trace; + Mod_Q1BSP_TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK); + return trace.fraction == 1; +} + +static int Mod_Q1BSP_LightPoint_RecursiveBSPNode(dp_model_t *model, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal, const mnode_t *node, float x, float y, float startz, float endz) +{ + int side; + float front, back; + float mid, distz = endz - startz; + +loc0: + if (!node->plane) + return false; // didn't hit anything + + switch (node->plane->type) + { + case PLANE_X: + node = node->children[x < node->plane->dist]; + goto loc0; + case PLANE_Y: + node = node->children[y < node->plane->dist]; + goto loc0; + case PLANE_Z: + side = startz < node->plane->dist; + if ((endz < node->plane->dist) == side) + { + node = node->children[side]; + goto loc0; + } + // found an intersection + mid = node->plane->dist; + break; + default: + back = front = x * node->plane->normal[0] + y * node->plane->normal[1]; + front += startz * node->plane->normal[2]; + back += endz * node->plane->normal[2]; + side = front < node->plane->dist; + if ((back < node->plane->dist) == side) + { + node = node->children[side]; + goto loc0; + } + // found an intersection + mid = startz + distz * (front - node->plane->dist) / (front - back); + break; + } + + // go down front side + if (node->children[side]->plane && Mod_Q1BSP_LightPoint_RecursiveBSPNode(model, ambientcolor, diffusecolor, diffusenormal, node->children[side], x, y, startz, mid)) + return true; // hit something + else + { + // check for impact on this node + if (node->numsurfaces) + { + int i, dsi, dti, lmwidth, lmheight; + float ds, dt; + msurface_t *surface; + unsigned char *lightmap; + int maps, line3, size3; + float dsfrac; + float dtfrac; + float scale, w, w00, w01, w10, w11; + + surface = model->data_surfaces + node->firstsurface; + for (i = 0;i < node->numsurfaces;i++, surface++) + { + if (!(surface->texture->basematerialflags & MATERIALFLAG_WALL) || !surface->lightmapinfo || !surface->lightmapinfo->samples) + continue; // no lightmaps + + // location we want to sample in the lightmap + ds = ((x * surface->lightmapinfo->texinfo->vecs[0][0] + y * surface->lightmapinfo->texinfo->vecs[0][1] + mid * surface->lightmapinfo->texinfo->vecs[0][2] + surface->lightmapinfo->texinfo->vecs[0][3]) - surface->lightmapinfo->texturemins[0]) * 0.0625f; + dt = ((x * surface->lightmapinfo->texinfo->vecs[1][0] + y * surface->lightmapinfo->texinfo->vecs[1][1] + mid * surface->lightmapinfo->texinfo->vecs[1][2] + surface->lightmapinfo->texinfo->vecs[1][3]) - surface->lightmapinfo->texturemins[1]) * 0.0625f; + + // check the bounds + dsi = (int)ds; + dti = (int)dt; + lmwidth = ((surface->lightmapinfo->extents[0]>>4)+1); + lmheight = ((surface->lightmapinfo->extents[1]>>4)+1); + + // is it in bounds? + if (dsi >= 0 && dsi < lmwidth-1 && dti >= 0 && dti < lmheight-1) + { + // calculate bilinear interpolation factors + // and also multiply by fixedpoint conversion factors + dsfrac = ds - dsi; + dtfrac = dt - dti; + w00 = (1 - dsfrac) * (1 - dtfrac) * (1.0f / 32768.0f); + w01 = ( dsfrac) * (1 - dtfrac) * (1.0f / 32768.0f); + w10 = (1 - dsfrac) * ( dtfrac) * (1.0f / 32768.0f); + w11 = ( dsfrac) * ( dtfrac) * (1.0f / 32768.0f); + + // values for pointer math + line3 = lmwidth * 3; // LordHavoc: *3 for colored lighting + size3 = lmwidth * lmheight * 3; // LordHavoc: *3 for colored lighting + + // look up the pixel + lightmap = surface->lightmapinfo->samples + dti * line3 + dsi*3; // LordHavoc: *3 for colored lighting + + // bilinear filter each lightmap style, and sum them + for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++) + { + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]]; + w = w00 * scale;VectorMA(ambientcolor, w, lightmap , ambientcolor); + w = w01 * scale;VectorMA(ambientcolor, w, lightmap + 3 , ambientcolor); + w = w10 * scale;VectorMA(ambientcolor, w, lightmap + line3 , ambientcolor); + w = w11 * scale;VectorMA(ambientcolor, w, lightmap + line3 + 3, ambientcolor); + lightmap += size3; + } + + return true; // success + } + } + } + + // go down back side + node = node->children[side ^ 1]; + startz = mid; + distz = endz - startz; + goto loc0; + } +} + +void Mod_Q1BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal) +{ + // pretend lighting is coming down from above (due to lack of a lightgrid to know primary lighting direction) + VectorSet(diffusenormal, 0, 0, 1); + + if (!model->brushq1.lightdata) + { + VectorSet(ambientcolor, 1, 1, 1); + VectorSet(diffusecolor, 0, 0, 0); + return; + } + + Mod_Q1BSP_LightPoint_RecursiveBSPNode(model, ambientcolor, diffusecolor, diffusenormal, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode, p[0], p[1], p[2] + 0.125, p[2] - 65536); +} + +static const texture_t *Mod_Q1BSP_TraceLineAgainstSurfacesFindTextureOnNode(RecursiveHullCheckTraceInfo_t *t, const dp_model_t *model, const mnode_t *node, double mid[3]) +{ + int i; + int j; + int k; + const msurface_t *surface; + float normal[3]; + float v0[3]; + float v1[3]; + float edgedir[3]; + float edgenormal[3]; + float p[4]; + float midf; + float t1; + float t2; + VectorCopy(mid, p); + p[3] = 1; + surface = model->data_surfaces + node->firstsurface; + for (i = 0;i < node->numsurfaces;i++, surface++) + { + // skip surfaces whose bounding box does not include the point +// if (!BoxesOverlap(mid, mid, surface->mins, surface->maxs)) +// continue; + // skip faces with contents we don't care about + if (!(t->trace->hitsupercontentsmask & surface->texture->supercontents)) + continue; + // get the surface normal - since it is flat we know any vertex normal will suffice + VectorCopy(model->surfmesh.data_normal3f + 3 * surface->num_firstvertex, normal); + // skip backfaces + if (DotProduct(t->dist, normal) > 0) + continue; + // iterate edges and see if the point is outside one of them + for (j = 0, k = surface->num_vertices - 1;j < surface->num_vertices;k = j, j++) + { + VectorCopy(model->surfmesh.data_vertex3f + 3 * (surface->num_firstvertex + k), v0); + VectorCopy(model->surfmesh.data_vertex3f + 3 * (surface->num_firstvertex + j), v1); + VectorSubtract(v0, v1, edgedir); + CrossProduct(edgedir, normal, edgenormal); + if (DotProduct(edgenormal, p) > DotProduct(edgenormal, v0)) + break; + } + // if the point is outside one of the edges, it is not within the surface + if (j < surface->num_vertices) + continue; + + // we hit a surface, this is the impact point... + VectorCopy(normal, t->trace->plane.normal); + t->trace->plane.dist = DotProduct(normal, p); + + // calculate the true fraction + t1 = DotProduct(t->start, t->trace->plane.normal) - t->trace->plane.dist; + t2 = DotProduct(t->end, t->trace->plane.normal) - t->trace->plane.dist; + midf = t1 / (t1 - t2); + t->trace->realfraction = midf; + + // calculate the return fraction which is nudged off the surface a bit + midf = (t1 - DIST_EPSILON) / (t1 - t2); + t->trace->fraction = bound(0, midf, 1); + + if (collision_prefernudgedfraction.integer) + t->trace->realfraction = t->trace->fraction; + + t->trace->hittexture = surface->texture->currentframe; + t->trace->hitq3surfaceflags = t->trace->hittexture->surfaceflags; + t->trace->hitsupercontents = t->trace->hittexture->supercontents; + return surface->texture->currentframe; + } + return NULL; +} + +static int Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(RecursiveHullCheckTraceInfo_t *t, const dp_model_t *model, const mnode_t *node, const double p1[3], const double p2[3]) +{ + const mplane_t *plane; + double t1, t2; + int side; + double midf, mid[3]; + const mleaf_t *leaf; + + while (node->plane) + { + plane = node->plane; + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + } + if (t1 < 0) + { + if (t2 < 0) + { + node = node->children[1]; + continue; + } + side = 1; + } + else + { + if (t2 >= 0) + { + node = node->children[0]; + continue; + } + side = 0; + } + + // the line intersects, find intersection point + // LordHavoc: this uses the original trace for maximum accuracy + if (plane->type < 3) + { + t1 = t->start[plane->type] - plane->dist; + t2 = t->end[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, t->start) - plane->dist; + t2 = DotProduct (plane->normal, t->end) - plane->dist; + } + + midf = t1 / (t1 - t2); + VectorMA(t->start, midf, t->dist, mid); + + // recurse both sides, front side first, return if we hit a surface + if (Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(t, model, node->children[side], p1, mid) == HULLCHECKSTATE_DONE) + return HULLCHECKSTATE_DONE; + + // test each surface on the node + Mod_Q1BSP_TraceLineAgainstSurfacesFindTextureOnNode(t, model, node, mid); + if (t->trace->hittexture) + return HULLCHECKSTATE_DONE; + + // recurse back side + return Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(t, model, node->children[side ^ 1], mid, p2); + } + leaf = (const mleaf_t *)node; + side = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, leaf->contents); + if (!t->trace->startfound) + { + t->trace->startfound = true; + t->trace->startsupercontents |= side; + } + if (side & SUPERCONTENTS_LIQUIDSMASK) + t->trace->inwater = true; + if (side == 0) + t->trace->inopen = true; + if (side & t->trace->hitsupercontentsmask) + { + // if the first leaf is solid, set startsolid + if (t->trace->allsolid) + t->trace->startsolid = true; + return HULLCHECKSTATE_SOLID; + } + else + { + t->trace->allsolid = false; + return HULLCHECKSTATE_EMPTY; + } +} + +static void Mod_Q1BSP_TraceLineAgainstSurfaces(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + RecursiveHullCheckTraceInfo_t rhc; + + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + rhc.trace = trace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 + VectorCopy(start, rhc.start); + VectorCopy(end, rhc.end); + VectorSubtract(rhc.end, rhc.start, rhc.dist); + Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(&rhc, model, model->brush.data_nodes + rhc.hull->firstclipnode, rhc.start, rhc.end); + VectorMA(rhc.start, rhc.trace->fraction, rhc.dist, rhc.trace->endpos); +} + +static void Mod_Q1BSP_DecompressVis(const unsigned char *in, const unsigned char *inend, unsigned char *out, unsigned char *outend) +{ + int c; + unsigned char *outstart = out; + while (out < outend) + { + if (in == inend) + { + Con_Printf("Mod_Q1BSP_DecompressVis: input underrun on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); + return; + } + c = *in++; + if (c) + *out++ = c; + else + { + if (in == inend) + { + Con_Printf("Mod_Q1BSP_DecompressVis: input underrun (during zero-run) on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); + return; + } + for (c = *in++;c > 0;c--) + { + if (out == outend) + { + Con_Printf("Mod_Q1BSP_DecompressVis: output overrun on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); + return; + } + *out++ = 0; + } + } + } +} + +/* +============= +R_Q1BSP_LoadSplitSky + +A sky texture is 256*128, with the right side being a masked overlay +============== +*/ +void R_Q1BSP_LoadSplitSky (unsigned char *src, int width, int height, int bytesperpixel) +{ + int x, y; + int w = width/2; + int h = height; + unsigned int *solidpixels = (unsigned int *)Mem_Alloc(tempmempool, w*h*sizeof(unsigned char[4])); + unsigned int *alphapixels = (unsigned int *)Mem_Alloc(tempmempool, w*h*sizeof(unsigned char[4])); + + // allocate a texture pool if we need it + if (loadmodel->texturepool == NULL && cls.state != ca_dedicated) + loadmodel->texturepool = R_AllocTexturePool(); + + if (bytesperpixel == 4) + { + for (y = 0;y < h;y++) + { + for (x = 0;x < w;x++) + { + solidpixels[y*w+x] = ((unsigned *)src)[y*width+x+w]; + alphapixels[y*w+x] = ((unsigned *)src)[y*width+x]; + } + } + } + else + { + // make an average value for the back to avoid + // a fringe on the top level + int p, r, g, b; + union + { + unsigned int i; + unsigned char b[4]; + } + bgra; + r = g = b = 0; + for (y = 0;y < h;y++) + { + for (x = 0;x < w;x++) + { + p = src[x*width+y+w]; + r += palette_rgb[p][0]; + g += palette_rgb[p][1]; + b += palette_rgb[p][2]; + } + } + bgra.b[2] = r/(w*h); + bgra.b[1] = g/(w*h); + bgra.b[0] = b/(w*h); + bgra.b[3] = 0; + for (y = 0;y < h;y++) + { + for (x = 0;x < w;x++) + { + solidpixels[y*w+x] = palette_bgra_complete[src[y*width+x+w]]; + p = src[y*width+x]; + alphapixels[y*w+x] = p ? palette_bgra_complete[p] : bgra.i; + } + } + } + + loadmodel->brush.solidskyskinframe = R_SkinFrame_LoadInternalBGRA("sky_solidtexture", 0 , (unsigned char *) solidpixels, w, h, vid.sRGB3D); + loadmodel->brush.alphaskyskinframe = R_SkinFrame_LoadInternalBGRA("sky_alphatexture", TEXF_ALPHA, (unsigned char *) alphapixels, w, h, vid.sRGB3D); + Mem_Free(solidpixels); + Mem_Free(alphapixels); +} + +static void Mod_Q1BSP_LoadTextures(lump_t *l) +{ + int i, j, k, num, max, altmax, mtwidth, mtheight, *dofs, incomplete; + skinframe_t *skinframe; + miptex_t *dmiptex; + texture_t *tx, *tx2, *anims[10], *altanims[10]; + texture_t backuptex; + dmiptexlump_t *m; + unsigned char *data, *mtdata; + const char *s; + char mapname[MAX_QPATH], name[MAX_QPATH]; + unsigned char zeroopaque[4], zerotrans[4]; + Vector4Set(zeroopaque, 0, 0, 0, 255); + Vector4Set(zerotrans, 0, 0, 0, 128); + + loadmodel->data_textures = NULL; + + // add two slots for notexture walls and notexture liquids + if (l->filelen) + { + m = (dmiptexlump_t *)(mod_base + l->fileofs); + m->nummiptex = LittleLong (m->nummiptex); + loadmodel->num_textures = m->nummiptex + 2; + loadmodel->num_texturesperskin = loadmodel->num_textures; + } + else + { + m = NULL; + loadmodel->num_textures = 2; + loadmodel->num_texturesperskin = loadmodel->num_textures; + } + + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_textures * sizeof(texture_t)); + + // fill out all slots with notexture + if (cls.state != ca_dedicated) + skinframe = R_SkinFrame_LoadMissing(); + else + skinframe = NULL; + for (i = 0, tx = loadmodel->data_textures;i < loadmodel->num_textures;i++, tx++) + { + strlcpy(tx->name, "NO TEXTURE FOUND", sizeof(tx->name)); + tx->width = 16; + tx->height = 16; + if (cls.state != ca_dedicated) + { + tx->numskinframes = 1; + tx->skinframerate = 1; + tx->skinframes[0] = skinframe; + tx->currentskinframe = tx->skinframes[0]; + } + tx->basematerialflags = MATERIALFLAG_WALL; + if (i == loadmodel->num_textures - 1) + { + tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW; + tx->supercontents = mod_q1bsp_texture_water.supercontents; + tx->surfaceflags = mod_q1bsp_texture_water.surfaceflags; + } + else + { + tx->supercontents = mod_q1bsp_texture_solid.supercontents; + tx->surfaceflags = mod_q1bsp_texture_solid.surfaceflags; + } + tx->currentframe = tx; + + // clear water settings + tx->reflectmin = 0; + tx->reflectmax = 1; + tx->refractfactor = 1; + Vector4Set(tx->refractcolor4f, 1, 1, 1, 1); + tx->reflectfactor = 1; + Vector4Set(tx->reflectcolor4f, 1, 1, 1, 1); + tx->r_water_wateralpha = 1; + tx->offsetmapping = OFFSETMAPPING_DEFAULT; + tx->offsetscale = 1; + tx->specularscalemod = 1; + tx->specularpowermod = 1; + } + + if (!m) + { + Con_Printf("%s: no miptex lump to load textures from\n", loadmodel->name); + return; + } + + s = loadmodel->name; + if (!strncasecmp(s, "maps/", 5)) + s += 5; + FS_StripExtension(s, mapname, sizeof(mapname)); + + // just to work around bounds checking when debugging with it (array index out of bounds error thing) + dofs = m->dataofs; + // LordHavoc: mostly rewritten map texture loader + for (i = 0;i < m->nummiptex;i++) + { + dofs[i] = LittleLong(dofs[i]); + if (r_nosurftextures.integer) + continue; + if (dofs[i] == -1) + { + Con_DPrintf("%s: miptex #%i missing\n", loadmodel->name, i); + continue; + } + dmiptex = (miptex_t *)((unsigned char *)m + dofs[i]); + + // copy name, but only up to 16 characters + // (the output buffer can hold more than this, but the input buffer is + // only 16) + for (j = 0;j < 16 && dmiptex->name[j];j++) + name[j] = dmiptex->name[j]; + name[j] = 0; + + if (!name[0]) + { + dpsnprintf(name, sizeof(name), "unnamed%i", i); + Con_DPrintf("%s: warning: renaming unnamed texture to %s\n", loadmodel->name, name); + } + + mtwidth = LittleLong(dmiptex->width); + mtheight = LittleLong(dmiptex->height); + mtdata = NULL; + j = LittleLong(dmiptex->offsets[0]); + if (j) + { + // texture included + if (j < 40 || j + mtwidth * mtheight > l->filelen) + { + Con_Printf("%s: Texture \"%s\" is corrupt or incomplete\n", loadmodel->name, dmiptex->name); + continue; + } + mtdata = (unsigned char *)dmiptex + j; + } + + if ((mtwidth & 15) || (mtheight & 15)) + Con_DPrintf("%s: warning: texture \"%s\" is not 16 aligned\n", loadmodel->name, dmiptex->name); + + // LordHavoc: force all names to lowercase + for (j = 0;name[j];j++) + if (name[j] >= 'A' && name[j] <= 'Z') + name[j] += 'a' - 'A'; + + // LordHavoc: backup the texture_t because q3 shader loading overwrites it + backuptex = loadmodel->data_textures[i]; + if (dmiptex->name[0] && Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i, name, false, false, 0)) + continue; + loadmodel->data_textures[i] = backuptex; + + tx = loadmodel->data_textures + i; + strlcpy(tx->name, name, sizeof(tx->name)); + tx->width = mtwidth; + tx->height = mtheight; + + if (tx->name[0] == '*') + { + if (!strncmp(tx->name, "*lava", 5)) + { + tx->supercontents = mod_q1bsp_texture_lava.supercontents; + tx->surfaceflags = mod_q1bsp_texture_lava.surfaceflags; + } + else if (!strncmp(tx->name, "*slime", 6)) + { + tx->supercontents = mod_q1bsp_texture_slime.supercontents; + tx->surfaceflags = mod_q1bsp_texture_slime.surfaceflags; + } + else + { + tx->supercontents = mod_q1bsp_texture_water.supercontents; + tx->surfaceflags = mod_q1bsp_texture_water.surfaceflags; + } + } + else if (!strncmp(tx->name, "sky", 3)) + { + tx->supercontents = mod_q1bsp_texture_sky.supercontents; + tx->surfaceflags = mod_q1bsp_texture_sky.surfaceflags; + } + else + { + tx->supercontents = mod_q1bsp_texture_solid.supercontents; + tx->surfaceflags = mod_q1bsp_texture_solid.surfaceflags; + } + + if (cls.state != ca_dedicated) + { + // LordHavoc: HL sky textures are entirely different than quake + if (!loadmodel->brush.ishlbsp && !strncmp(tx->name, "sky", 3) && mtwidth == mtheight * 2) + { + data = loadimagepixelsbgra(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s/%s", mapname, tx->name), false, false, false, NULL); + if (!data) + data = loadimagepixelsbgra(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s", tx->name), false, false, false, NULL); + if (data && image_width == image_height * 2) + { + R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4); + Mem_Free(data); + } + else if (mtdata != NULL) + R_Q1BSP_LoadSplitSky(mtdata, mtwidth, mtheight, 1); + } + else + { + skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s/%s", mapname, tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS, false); + if (!skinframe) + skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s", tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS, false); + if (skinframe) + tx->offsetmapping = OFFSETMAPPING_DEFAULT; // allow offsetmapping on external textures without a q3 shader + if (!skinframe) + { + // did not find external texture, load it from the bsp or wad3 + if (loadmodel->brush.ishlbsp) + { + // internal texture overrides wad + unsigned char *pixels, *freepixels; + pixels = freepixels = NULL; + if (mtdata) + pixels = W_ConvertWAD3TextureBGRA(dmiptex); + if (pixels == NULL) + pixels = freepixels = W_GetTextureBGRA(tx->name); + if (pixels != NULL) + { + tx->width = image_width; + tx->height = image_height; + skinframe = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP, pixels, image_width, image_height, true); + } + if (freepixels) + Mem_Free(freepixels); + } + else if (mtdata) // texture included + skinframe = R_SkinFrame_LoadInternalQuake(tx->name, TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP, false, r_fullbrights.integer, mtdata, tx->width, tx->height); + } + // if skinframe is still NULL the "missing" texture will be used + if (skinframe) + tx->skinframes[0] = skinframe; + } + // LordHavoc: some Tenebrae textures get replaced by black + if (!strncmp(tx->name, "*glassmirror", 12)) // Tenebrae + tx->skinframes[0] = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_MIPMAP | TEXF_ALPHA, zerotrans, 1, 1, false); + else if (!strncmp(tx->name, "mirror", 6)) // Tenebrae + tx->skinframes[0] = R_SkinFrame_LoadInternalBGRA(tx->name, 0, zeroopaque, 1, 1, false); + } + + tx->basematerialflags = MATERIALFLAG_WALL; + if (tx->name[0] == '*') + { + // LordHavoc: some turbulent textures should not be affected by wateralpha + if (!strncmp(tx->name, "*glassmirror", 12)) // Tenebrae + tx->basematerialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_REFLECTION; + else if (!strncmp(tx->name,"*lava",5) + || !strncmp(tx->name,"*teleport",9) + || !strncmp(tx->name,"*rift",5)) // Scourge of Armagon texture + tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW; + else + tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW | MATERIALFLAG_WATERALPHA | MATERIALFLAG_WATERSHADER; + if (tx->skinframes[0] && tx->skinframes[0]->hasalpha) + tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + } + else if (!strncmp(tx->name, "mirror", 6)) // Tenebrae + { + // replace the texture with black + tx->basematerialflags |= MATERIALFLAG_REFLECTION; + } + else if (!strncmp(tx->name, "sky", 3)) + tx->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; + else if (!strcmp(tx->name, "caulk")) + tx->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; + else if (tx->skinframes[0] && tx->skinframes[0]->hasalpha) + tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + + // start out with no animation + tx->currentframe = tx; + tx->currentskinframe = tx->skinframes[0]; + tx->currentmaterialflags = tx->basematerialflags; + } + + // sequence the animations + for (i = 0;i < m->nummiptex;i++) + { + tx = loadmodel->data_textures + i; + if (!tx || tx->name[0] != '+' || tx->name[1] == 0 || tx->name[2] == 0) + continue; + if (tx->anim_total[0] || tx->anim_total[1]) + continue; // already sequenced + + // find the number of frames in the animation + memset(anims, 0, sizeof(anims)); + memset(altanims, 0, sizeof(altanims)); + + for (j = i;j < m->nummiptex;j++) + { + tx2 = loadmodel->data_textures + j; + if (!tx2 || tx2->name[0] != '+' || strcmp(tx2->name+2, tx->name+2)) + continue; + + num = tx2->name[1]; + if (num >= '0' && num <= '9') + anims[num - '0'] = tx2; + else if (num >= 'a' && num <= 'j') + altanims[num - 'a'] = tx2; + else + Con_Printf("Bad animating texture %s\n", tx->name); + } + + max = altmax = 0; + for (j = 0;j < 10;j++) + { + if (anims[j]) + max = j + 1; + if (altanims[j]) + altmax = j + 1; + } + //Con_Printf("linking animation %s (%i:%i frames)\n\n", tx->name, max, altmax); + + incomplete = false; + for (j = 0;j < max;j++) + { + if (!anims[j]) + { + Con_Printf("Missing frame %i of %s\n", j, tx->name); + incomplete = true; + } + } + for (j = 0;j < altmax;j++) + { + if (!altanims[j]) + { + Con_Printf("Missing altframe %i of %s\n", j, tx->name); + incomplete = true; + } + } + if (incomplete) + continue; + + if (altmax < 1) + { + // if there is no alternate animation, duplicate the primary + // animation into the alternate + altmax = max; + for (k = 0;k < 10;k++) + altanims[k] = anims[k]; + } + + // link together the primary animation + for (j = 0;j < max;j++) + { + tx2 = anims[j]; + tx2->animated = true; + tx2->anim_total[0] = max; + tx2->anim_total[1] = altmax; + for (k = 0;k < 10;k++) + { + tx2->anim_frames[0][k] = anims[k]; + tx2->anim_frames[1][k] = altanims[k]; + } + } + + // if there really is an alternate anim... + if (anims[0] != altanims[0]) + { + // link together the alternate animation + for (j = 0;j < altmax;j++) + { + tx2 = altanims[j]; + tx2->animated = true; + // the primary/alternate are reversed here + tx2->anim_total[0] = altmax; + tx2->anim_total[1] = max; + for (k = 0;k < 10;k++) + { + tx2->anim_frames[0][k] = altanims[k]; + tx2->anim_frames[1][k] = anims[k]; + } + } + } + } +} + +static void Mod_Q1BSP_LoadLighting(lump_t *l) +{ + int i; + unsigned char *in, *out, *data, d; + char litfilename[MAX_QPATH]; + char dlitfilename[MAX_QPATH]; + fs_offset_t filesize; + if (loadmodel->brush.ishlbsp) // LordHavoc: load the colored lighting data straight + { + loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, l->filelen); + for (i=0; ifilelen; i++) + loadmodel->brushq1.lightdata[i] = mod_base[l->fileofs+i] >>= 1; + } + else // LordHavoc: bsp version 29 (normal white lighting) + { + // LordHavoc: hope is not lost yet, check for a .lit file to load + strlcpy (litfilename, loadmodel->name, sizeof (litfilename)); + FS_StripExtension (litfilename, litfilename, sizeof (litfilename)); + strlcpy (dlitfilename, litfilename, sizeof (dlitfilename)); + strlcat (litfilename, ".lit", sizeof (litfilename)); + strlcat (dlitfilename, ".dlit", sizeof (dlitfilename)); + data = (unsigned char*) FS_LoadFile(litfilename, tempmempool, false, &filesize); + if (data) + { + if (filesize == (fs_offset_t)(8 + l->filelen * 3) && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') + { + i = LittleLong(((int *)data)[1]); + if (i == 1) + { + if (developer_loading.integer) + Con_Printf("loaded %s\n", litfilename); + loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, filesize - 8); + memcpy(loadmodel->brushq1.lightdata, data + 8, filesize - 8); + Mem_Free(data); + data = (unsigned char*) FS_LoadFile(dlitfilename, tempmempool, false, &filesize); + if (data) + { + if (filesize == (fs_offset_t)(8 + l->filelen * 3) && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') + { + i = LittleLong(((int *)data)[1]); + if (i == 1) + { + if (developer_loading.integer) + Con_Printf("loaded %s\n", dlitfilename); + loadmodel->brushq1.nmaplightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, filesize - 8); + memcpy(loadmodel->brushq1.nmaplightdata, data + 8, filesize - 8); + loadmodel->brushq3.deluxemapping_modelspace = false; + loadmodel->brushq3.deluxemapping = true; + } + } + Mem_Free(data); + data = NULL; + } + return; + } + else + Con_Printf("Unknown .lit file version (%d)\n", i); + } + else if (filesize == 8) + Con_Print("Empty .lit file, ignoring\n"); + else + Con_Printf("Corrupt .lit file (file size %i bytes, should be %i bytes), ignoring\n", (int) filesize, (int) (8 + l->filelen * 3)); + if (data) + { + Mem_Free(data); + data = NULL; + } + } + // LordHavoc: oh well, expand the white lighting data + if (!l->filelen) + return; + loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, l->filelen*3); + in = mod_base + l->fileofs; + out = loadmodel->brushq1.lightdata; + for (i = 0;i < l->filelen;i++) + { + d = *in++; + *out++ = d; + *out++ = d; + *out++ = d; + } + } +} + +static void Mod_Q1BSP_LoadVisibility(lump_t *l) +{ + loadmodel->brushq1.num_compressedpvs = 0; + loadmodel->brushq1.data_compressedpvs = NULL; + if (!l->filelen) + return; + loadmodel->brushq1.num_compressedpvs = l->filelen; + loadmodel->brushq1.data_compressedpvs = (unsigned char *)Mem_Alloc(loadmodel->mempool, l->filelen); + memcpy(loadmodel->brushq1.data_compressedpvs, mod_base + l->fileofs, l->filelen); +} + +// used only for HalfLife maps +static void Mod_Q1BSP_ParseWadsFromEntityLump(const char *data) +{ + char key[128], value[4096]; + int i, j, k; + if (!data) + return; + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + if (com_token[0] != '{') + return; // error + while (1) + { + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strlcpy(key, com_token + 1, sizeof(key)); + else + strlcpy(key, com_token, sizeof(key)); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + dpsnprintf(value, sizeof(value), "%s", com_token); + if (!strcmp("wad", key)) // for HalfLife maps + { + if (loadmodel->brush.ishlbsp) + { + j = 0; + for (i = 0;i < (int)sizeof(value);i++) + if (value[i] != ';' && value[i] != '\\' && value[i] != '/' && value[i] != ':') + break; + if (value[i]) + { + for (;i < (int)sizeof(value);i++) + { + // ignore path - the \\ check is for HalfLife... stupid windoze 'programmers'... + if (value[i] == '\\' || value[i] == '/' || value[i] == ':') + j = i+1; + else if (value[i] == ';' || value[i] == 0) + { + k = value[i]; + value[i] = 0; + W_LoadTextureWadFile(&value[j], false); + j = i+1; + if (!k) + break; + } + } + } + } + } + } +} + +static void Mod_Q1BSP_LoadEntities(lump_t *l) +{ + loadmodel->brush.entities = NULL; + if (!l->filelen) + return; + loadmodel->brush.entities = (char *)Mem_Alloc(loadmodel->mempool, l->filelen + 1); + memcpy(loadmodel->brush.entities, mod_base + l->fileofs, l->filelen); + loadmodel->brush.entities[l->filelen] = 0; + if (loadmodel->brush.ishlbsp) + Mod_Q1BSP_ParseWadsFromEntityLump(loadmodel->brush.entities); +} + + +static void Mod_Q1BSP_LoadVertexes(lump_t *l) +{ + dvertex_t *in; + mvertex_t *out; + int i, count; + + in = (dvertex_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadVertexes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mvertex_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brushq1.vertexes = out; + loadmodel->brushq1.numvertexes = count; + + for ( i=0 ; iposition[0] = LittleFloat(in->point[0]); + out->position[1] = LittleFloat(in->point[1]); + out->position[2] = LittleFloat(in->point[2]); + } +} + +// The following two functions should be removed and MSG_* or SZ_* function sets adjusted so they +// can be used for this +// REMOVEME +int SB_ReadInt (unsigned char **buffer) +{ + int i; + i = ((*buffer)[0]) + 256*((*buffer)[1]) + 65536*((*buffer)[2]) + 16777216*((*buffer)[3]); + (*buffer) += 4; + return i; +} + +// REMOVEME +float SB_ReadFloat (unsigned char **buffer) +{ + union + { + int i; + float f; + } u; + + u.i = SB_ReadInt (buffer); + return u.f; +} + +static void Mod_Q1BSP_LoadSubmodels(lump_t *l, hullinfo_t *hullinfo) +{ + unsigned char *index; + dmodel_t *out; + int i, j, count; + + index = (unsigned char *)(mod_base + l->fileofs); + if (l->filelen % (48+4*hullinfo->filehulls)) + Host_Error ("Mod_Q1BSP_LoadSubmodels: funny lump size in %s", loadmodel->name); + + count = l->filelen / (48+4*hullinfo->filehulls); + out = (dmodel_t *)Mem_Alloc (loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brushq1.submodels = out; + loadmodel->brush.numsubmodels = count; + + for (i = 0; i < count; i++, out++) + { + // spread out the mins / maxs by a pixel + out->mins[0] = SB_ReadFloat (&index) - 1; + out->mins[1] = SB_ReadFloat (&index) - 1; + out->mins[2] = SB_ReadFloat (&index) - 1; + out->maxs[0] = SB_ReadFloat (&index) + 1; + out->maxs[1] = SB_ReadFloat (&index) + 1; + out->maxs[2] = SB_ReadFloat (&index) + 1; + out->origin[0] = SB_ReadFloat (&index); + out->origin[1] = SB_ReadFloat (&index); + out->origin[2] = SB_ReadFloat (&index); + for (j = 0; j < hullinfo->filehulls; j++) + out->headnode[j] = SB_ReadInt (&index); + out->visleafs = SB_ReadInt (&index); + out->firstface = SB_ReadInt (&index); + out->numfaces = SB_ReadInt (&index); + } +} + +static void Mod_Q1BSP_LoadEdges(lump_t *l) +{ + dedge_t *in; + medge_t *out; + int i, count; + + in = (dedge_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadEdges: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (medge_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brushq1.edges = out; + loadmodel->brushq1.numedges = count; + + for ( i=0 ; iv[0] = (unsigned short)LittleShort(in->v[0]); + out->v[1] = (unsigned short)LittleShort(in->v[1]); + if (out->v[0] >= loadmodel->brushq1.numvertexes || out->v[1] >= loadmodel->brushq1.numvertexes) + { + Con_Printf("Mod_Q1BSP_LoadEdges: %s has invalid vertex indices in edge %i (vertices %i %i >= numvertices %i)\n", loadmodel->name, i, out->v[0], out->v[1], loadmodel->brushq1.numvertexes); + if(!loadmodel->brushq1.numvertexes) + Host_Error("Mod_Q1BSP_LoadEdges: %s has edges but no vertexes, cannot fix\n", loadmodel->name); + + out->v[0] = 0; + out->v[1] = 0; + } + } +} + +static void Mod_Q1BSP_LoadTexinfo(lump_t *l) +{ + texinfo_t *in; + mtexinfo_t *out; + int i, j, k, count, miptex; + + in = (texinfo_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadTexinfo: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mtexinfo_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brushq1.texinfo = out; + loadmodel->brushq1.numtexinfo = count; + + for (i = 0;i < count;i++, in++, out++) + { + for (k = 0;k < 2;k++) + for (j = 0;j < 4;j++) + out->vecs[k][j] = LittleFloat(in->vecs[k][j]); + + miptex = LittleLong(in->miptex); + out->flags = LittleLong(in->flags); + + out->texture = NULL; + if (loadmodel->data_textures) + { + if ((unsigned int) miptex >= (unsigned int) loadmodel->num_textures) + Con_Printf("error in model \"%s\": invalid miptex index %i(of %i)\n", loadmodel->name, miptex, loadmodel->num_textures); + else + out->texture = loadmodel->data_textures + miptex; + } + if (out->flags & TEX_SPECIAL) + { + // if texture chosen is NULL or the shader needs a lightmap, + // force to notexture water shader + if (out->texture == NULL) + out->texture = loadmodel->data_textures + (loadmodel->num_textures - 1); + } + else + { + // if texture chosen is NULL, force to notexture + if (out->texture == NULL) + out->texture = loadmodel->data_textures + (loadmodel->num_textures - 2); + } + } +} + +#if 0 +void BoundPoly(int numverts, float *verts, vec3_t mins, vec3_t maxs) +{ + int i, j; + float *v; + + mins[0] = mins[1] = mins[2] = 9999; + maxs[0] = maxs[1] = maxs[2] = -9999; + v = verts; + for (i = 0;i < numverts;i++) + { + for (j = 0;j < 3;j++, v++) + { + if (*v < mins[j]) + mins[j] = *v; + if (*v > maxs[j]) + maxs[j] = *v; + } + } +} + +#define MAX_SUBDIVPOLYTRIANGLES 4096 +#define MAX_SUBDIVPOLYVERTS(MAX_SUBDIVPOLYTRIANGLES * 3) + +static int subdivpolyverts, subdivpolytriangles; +static int subdivpolyindex[MAX_SUBDIVPOLYTRIANGLES][3]; +static float subdivpolyvert[MAX_SUBDIVPOLYVERTS][3]; + +static int subdivpolylookupvert(vec3_t v) +{ + int i; + for (i = 0;i < subdivpolyverts;i++) + if (subdivpolyvert[i][0] == v[0] + && subdivpolyvert[i][1] == v[1] + && subdivpolyvert[i][2] == v[2]) + return i; + if (subdivpolyverts >= MAX_SUBDIVPOLYVERTS) + Host_Error("SubDividePolygon: ran out of vertices in buffer, please increase your r_subdivide_size"); + VectorCopy(v, subdivpolyvert[subdivpolyverts]); + return subdivpolyverts++; +} + +static void SubdividePolygon(int numverts, float *verts) +{ + int i, i1, i2, i3, f, b, c, p; + vec3_t mins, maxs, front[256], back[256]; + float m, *pv, *cv, dist[256], frac; + + if (numverts > 250) + Host_Error("SubdividePolygon: ran out of verts in buffer"); + + BoundPoly(numverts, verts, mins, maxs); + + for (i = 0;i < 3;i++) + { + m = (mins[i] + maxs[i]) * 0.5; + m = r_subdivide_size.value * floor(m/r_subdivide_size.value + 0.5); + if (maxs[i] - m < 8) + continue; + if (m - mins[i] < 8) + continue; + + // cut it + for (cv = verts, c = 0;c < numverts;c++, cv += 3) + dist[c] = cv[i] - m; + + f = b = 0; + for (p = numverts - 1, c = 0, pv = verts + p * 3, cv = verts;c < numverts;p = c, c++, pv = cv, cv += 3) + { + if (dist[p] >= 0) + { + VectorCopy(pv, front[f]); + f++; + } + if (dist[p] <= 0) + { + VectorCopy(pv, back[b]); + b++; + } + if (dist[p] == 0 || dist[c] == 0) + continue; + if ((dist[p] > 0) != (dist[c] > 0) ) + { + // clip point + frac = dist[p] / (dist[p] - dist[c]); + front[f][0] = back[b][0] = pv[0] + frac * (cv[0] - pv[0]); + front[f][1] = back[b][1] = pv[1] + frac * (cv[1] - pv[1]); + front[f][2] = back[b][2] = pv[2] + frac * (cv[2] - pv[2]); + f++; + b++; + } + } + + SubdividePolygon(f, front[0]); + SubdividePolygon(b, back[0]); + return; + } + + i1 = subdivpolylookupvert(verts); + i2 = subdivpolylookupvert(verts + 3); + for (i = 2;i < numverts;i++) + { + if (subdivpolytriangles >= MAX_SUBDIVPOLYTRIANGLES) + { + Con_Print("SubdividePolygon: ran out of triangles in buffer, please increase your r_subdivide_size\n"); + return; + } + + i3 = subdivpolylookupvert(verts + i * 3); + subdivpolyindex[subdivpolytriangles][0] = i1; + subdivpolyindex[subdivpolytriangles][1] = i2; + subdivpolyindex[subdivpolytriangles][2] = i3; + i2 = i3; + subdivpolytriangles++; + } +} + +//Breaks a polygon up along axial 64 unit +//boundaries so that turbulent and sky warps +//can be done reasonably. +static void Mod_Q1BSP_GenerateWarpMesh(msurface_t *surface) +{ + int i, j; + surfvertex_t *v; + surfmesh_t *mesh; + + subdivpolytriangles = 0; + subdivpolyverts = 0; + SubdividePolygon(surface->num_vertices, (surface->mesh->data_vertex3f + 3 * surface->num_firstvertex)); + if (subdivpolytriangles < 1) + Host_Error("Mod_Q1BSP_GenerateWarpMesh: no triangles?"); + + surface->mesh = mesh = Mem_Alloc(loadmodel->mempool, sizeof(surfmesh_t) + subdivpolytriangles * sizeof(int[3]) + subdivpolyverts * sizeof(surfvertex_t)); + mesh->num_vertices = subdivpolyverts; + mesh->num_triangles = subdivpolytriangles; + mesh->vertex = (surfvertex_t *)(mesh + 1); + mesh->index = (int *)(mesh->vertex + mesh->num_vertices); + memset(mesh->vertex, 0, mesh->num_vertices * sizeof(surfvertex_t)); + + for (i = 0;i < mesh->num_triangles;i++) + for (j = 0;j < 3;j++) + mesh->index[i*3+j] = subdivpolyindex[i][j]; + + for (i = 0, v = mesh->vertex;i < subdivpolyverts;i++, v++) + { + VectorCopy(subdivpolyvert[i], v->v); + v->st[0] = DotProduct(v->v, surface->lightmapinfo->texinfo->vecs[0]); + v->st[1] = DotProduct(v->v, surface->lightmapinfo->texinfo->vecs[1]); + } +} +#endif + +extern cvar_t gl_max_lightmapsize; +static void Mod_Q1BSP_LoadFaces(lump_t *l) +{ + dface_t *in; + msurface_t *surface; + int i, j, count, surfacenum, planenum, smax, tmax, ssize, tsize, firstedge, numedges, totalverts, totaltris, lightmapnumber, lightmapsize, totallightmapsamples; + float texmins[2], texmaxs[2], val; + rtexture_t *lightmaptexture, *deluxemaptexture; + + in = (dface_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + loadmodel->data_surfaces = (msurface_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_t)); + loadmodel->data_surfaces_lightmapinfo = (msurface_lightmapinfo_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_lightmapinfo_t)); + + loadmodel->num_surfaces = count; + + loadmodel->brushq1.firstrender = true; + loadmodel->brushq1.lightmapupdateflags = (unsigned char *)Mem_Alloc(loadmodel->mempool, count*sizeof(unsigned char)); + + totalverts = 0; + totaltris = 0; + for (surfacenum = 0, in = (dface_t *)(mod_base + l->fileofs);surfacenum < count;surfacenum++, in++) + { + numedges = (unsigned short)LittleShort(in->numedges); + totalverts += numedges; + totaltris += numedges - 2; + } + + Mod_AllocSurfMesh(loadmodel->mempool, totalverts, totaltris, true, false, false); + + lightmaptexture = NULL; + deluxemaptexture = r_texture_blanknormalmap; + lightmapnumber = 0; + lightmapsize = bound(256, gl_max_lightmapsize.integer, (int)vid.maxtexturesize_2d); + totallightmapsamples = 0; + + totalverts = 0; + totaltris = 0; + for (surfacenum = 0, in = (dface_t *)(mod_base + l->fileofs), surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, in++, surface++) + { + surface->lightmapinfo = loadmodel->data_surfaces_lightmapinfo + surfacenum; + + // FIXME: validate edges, texinfo, etc? + firstedge = LittleLong(in->firstedge); + numedges = (unsigned short)LittleShort(in->numedges); + if ((unsigned int) firstedge > (unsigned int) loadmodel->brushq1.numsurfedges || (unsigned int) numedges > (unsigned int) loadmodel->brushq1.numsurfedges || (unsigned int) firstedge + (unsigned int) numedges > (unsigned int) loadmodel->brushq1.numsurfedges) + Host_Error("Mod_Q1BSP_LoadFaces: invalid edge range (firstedge %i, numedges %i, model edges %i)", firstedge, numedges, loadmodel->brushq1.numsurfedges); + i = (unsigned short)LittleShort(in->texinfo); + if ((unsigned int) i >= (unsigned int) loadmodel->brushq1.numtexinfo) + Host_Error("Mod_Q1BSP_LoadFaces: invalid texinfo index %i(model has %i texinfos)", i, loadmodel->brushq1.numtexinfo); + surface->lightmapinfo->texinfo = loadmodel->brushq1.texinfo + i; + surface->texture = surface->lightmapinfo->texinfo->texture; + + planenum = (unsigned short)LittleShort(in->planenum); + if ((unsigned int) planenum >= (unsigned int) loadmodel->brush.num_planes) + Host_Error("Mod_Q1BSP_LoadFaces: invalid plane index %i (model has %i planes)", planenum, loadmodel->brush.num_planes); + + //surface->flags = surface->texture->flags; + //if (LittleShort(in->side)) + // surface->flags |= SURF_PLANEBACK; + //surface->plane = loadmodel->brush.data_planes + planenum; + + surface->num_firstvertex = totalverts; + surface->num_vertices = numedges; + surface->num_firsttriangle = totaltris; + surface->num_triangles = numedges - 2; + totalverts += numedges; + totaltris += numedges - 2; + + // convert edges back to a normal polygon + for (i = 0;i < surface->num_vertices;i++) + { + int lindex = loadmodel->brushq1.surfedges[firstedge + i]; + float s, t; + // note: the q1bsp format does not allow a 0 surfedge (it would have no negative counterpart) + if (lindex >= 0) + VectorCopy(loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[lindex].v[0]].position, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3); + else + VectorCopy(loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[-lindex].v[1]].position, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3); + s = DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]; + t = DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]; + (loadmodel->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[i * 2 + 0] = s / surface->texture->width; + (loadmodel->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[i * 2 + 1] = t / surface->texture->height; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 0] = 0; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 1] = 0; + (loadmodel->surfmesh.data_lightmapoffsets + surface->num_firstvertex)[i] = 0; + } + + for (i = 0;i < surface->num_triangles;i++) + { + (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 0] = 0 + surface->num_firstvertex; + (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 1] = i + 1 + surface->num_firstvertex; + (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 2] = i + 2 + surface->num_firstvertex; + } + + // compile additional data about the surface geometry + Mod_BuildNormals(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, loadmodel->surfmesh.data_vertex3f, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle), loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle), loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + BoxFromPoints(surface->mins, surface->maxs, surface->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex)); + + // generate surface extents information + texmins[0] = texmaxs[0] = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]; + texmins[1] = texmaxs[1] = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]; + for (i = 1;i < surface->num_vertices;i++) + { + for (j = 0;j < 2;j++) + { + val = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3, surface->lightmapinfo->texinfo->vecs[j]) + surface->lightmapinfo->texinfo->vecs[j][3]; + texmins[j] = min(texmins[j], val); + texmaxs[j] = max(texmaxs[j], val); + } + } + for (i = 0;i < 2;i++) + { + surface->lightmapinfo->texturemins[i] = (int) floor(texmins[i] / 16.0) * 16; + surface->lightmapinfo->extents[i] = (int) ceil(texmaxs[i] / 16.0) * 16 - surface->lightmapinfo->texturemins[i]; + } + + smax = surface->lightmapinfo->extents[0] >> 4; + tmax = surface->lightmapinfo->extents[1] >> 4; + ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; + tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; + + // lighting info + for (i = 0;i < MAXLIGHTMAPS;i++) + surface->lightmapinfo->styles[i] = in->styles[i]; + surface->lightmaptexture = NULL; + surface->deluxemaptexture = r_texture_blanknormalmap; + i = LittleLong(in->lightofs); + if (i == -1) + { + surface->lightmapinfo->samples = NULL; +#if 1 + // give non-lightmapped water a 1x white lightmap + if (surface->texture->name[0] == '*' && (surface->lightmapinfo->texinfo->flags & TEX_SPECIAL) && ssize <= 256 && tsize <= 256) + { + surface->lightmapinfo->samples = (unsigned char *)Mem_Alloc(loadmodel->mempool, ssize * tsize * 3); + surface->lightmapinfo->styles[0] = 0; + memset(surface->lightmapinfo->samples, 128, ssize * tsize * 3); + } +#endif + } + else if (loadmodel->brush.ishlbsp) // LordHavoc: HalfLife map (bsp version 30) + surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + i; + else // LordHavoc: white lighting (bsp version 29) + { + surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + (i * 3); + if (loadmodel->brushq1.nmaplightdata) + surface->lightmapinfo->nmapsamples = loadmodel->brushq1.nmaplightdata + (i * 3); + } + + // check if we should apply a lightmap to this + if (!(surface->lightmapinfo->texinfo->flags & TEX_SPECIAL) || surface->lightmapinfo->samples) + { + if (ssize > 256 || tsize > 256) + Host_Error("Bad surface extents"); + + if (lightmapsize < ssize) + lightmapsize = ssize; + if (lightmapsize < tsize) + lightmapsize = tsize; + + totallightmapsamples += ssize*tsize; + + // force lightmap upload on first time seeing the surface + // + // additionally this is used by the later code to see if a + // lightmap is needed on this surface (rather than duplicating the + // logic above) + loadmodel->brushq1.lightmapupdateflags[surfacenum] = true; + loadmodel->lit = true; + } + } + + // small maps (such as ammo boxes especially) don't need big lightmap + // textures, so this code tries to guess a good size based on + // totallightmapsamples (size of the lightmaps lump basically), as well as + // trying to max out the size if there is a lot of lightmap data to store + // additionally, never choose a lightmapsize that is smaller than the + // largest surface encountered (as it would fail) + i = lightmapsize; + for (lightmapsize = 64; (lightmapsize < i) && (lightmapsize < bound(128, gl_max_lightmapsize.integer, (int)vid.maxtexturesize_2d)) && (totallightmapsamples > lightmapsize*lightmapsize); lightmapsize*=2) + ; + + // now that we've decided the lightmap texture size, we can do the rest + if (cls.state != ca_dedicated) + { + int stainmapsize = 0; + mod_alloclightmap_state_t allocState; + + Mod_AllocLightmap_Init(&allocState, lightmapsize, lightmapsize); + for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++) + { + int i, iu, iv, lightmapx = 0, lightmapy = 0; + float u, v, ubase, vbase, uscale, vscale; + + if (!loadmodel->brushq1.lightmapupdateflags[surfacenum]) + continue; + + smax = surface->lightmapinfo->extents[0] >> 4; + tmax = surface->lightmapinfo->extents[1] >> 4; + ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; + tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; + stainmapsize += ssize * tsize * 3; + + if (!lightmaptexture || !Mod_AllocLightmap_Block(&allocState, ssize, tsize, &lightmapx, &lightmapy)) + { + // allocate a texture pool if we need it + if (loadmodel->texturepool == NULL) + loadmodel->texturepool = R_AllocTexturePool(); + // could not find room, make a new lightmap + loadmodel->brushq3.num_mergedlightmaps = lightmapnumber + 1; + loadmodel->brushq3.data_lightmaps = (rtexture_t **)Mem_Realloc(loadmodel->mempool, loadmodel->brushq3.data_lightmaps, loadmodel->brushq3.num_mergedlightmaps * sizeof(loadmodel->brushq3.data_lightmaps[0])); + loadmodel->brushq3.data_deluxemaps = (rtexture_t **)Mem_Realloc(loadmodel->mempool, loadmodel->brushq3.data_deluxemaps, loadmodel->brushq3.num_mergedlightmaps * sizeof(loadmodel->brushq3.data_deluxemaps[0])); + loadmodel->brushq3.data_lightmaps[lightmapnumber] = lightmaptexture = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%i", lightmapnumber), lightmapsize, lightmapsize, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_ALLOWUPDATES, -1, NULL); + if (loadmodel->brushq1.nmaplightdata) + loadmodel->brushq3.data_deluxemaps[lightmapnumber] = deluxemaptexture = R_LoadTexture2D(loadmodel->texturepool, va("deluxemap%i", lightmapnumber), lightmapsize, lightmapsize, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_ALLOWUPDATES, -1, NULL); + lightmapnumber++; + Mod_AllocLightmap_Reset(&allocState); + Mod_AllocLightmap_Block(&allocState, ssize, tsize, &lightmapx, &lightmapy); + } + surface->lightmaptexture = lightmaptexture; + surface->deluxemaptexture = deluxemaptexture; + surface->lightmapinfo->lightmaporigin[0] = lightmapx; + surface->lightmapinfo->lightmaporigin[1] = lightmapy; + + uscale = 1.0f / (float)lightmapsize; + vscale = 1.0f / (float)lightmapsize; + ubase = lightmapx * uscale; + vbase = lightmapy * vscale; + + for (i = 0;i < surface->num_vertices;i++) + { + u = ((DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]) + 8 - surface->lightmapinfo->texturemins[0]) * (1.0 / 16.0); + v = ((DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]) + 8 - surface->lightmapinfo->texturemins[1]) * (1.0 / 16.0); + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 0] = u * uscale + ubase; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 1] = v * vscale + vbase; + // LordHavoc: calc lightmap data offset for vertex lighting to use + iu = (int) u; + iv = (int) v; + (loadmodel->surfmesh.data_lightmapoffsets + surface->num_firstvertex)[i] = (bound(0, iv, tmax) * ssize + bound(0, iu, smax)) * 3; + } + } + + if (cl_stainmaps.integer) + { + // allocate stainmaps for permanent marks on walls and clear white + unsigned char *stainsamples = NULL; + stainsamples = (unsigned char *)Mem_Alloc(loadmodel->mempool, stainmapsize); + memset(stainsamples, 255, stainmapsize); + // assign pointers + for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++) + { + if (!loadmodel->brushq1.lightmapupdateflags[surfacenum]) + continue; + ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; + tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; + surface->lightmapinfo->stainsamples = stainsamples; + stainsamples += ssize * tsize * 3; + } + } + } + + // generate ushort elements array if possible + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; +} + +static void Mod_Q1BSP_LoadNodes_RecursiveSetParent(mnode_t *node, mnode_t *parent) +{ + //if (node->parent) + // Host_Error("Mod_Q1BSP_LoadNodes_RecursiveSetParent: runaway recursion"); + node->parent = parent; + if (node->plane) + { + // this is a node, recurse to children + Mod_Q1BSP_LoadNodes_RecursiveSetParent(node->children[0], node); + Mod_Q1BSP_LoadNodes_RecursiveSetParent(node->children[1], node); + // combine supercontents of children + node->combinedsupercontents = node->children[0]->combinedsupercontents | node->children[1]->combinedsupercontents; + } + else + { + int j; + mleaf_t *leaf = (mleaf_t *)node; + // if this is a leaf, calculate supercontents mask from all collidable + // primitives in the leaf (brushes and collision surfaces) + // also flag if the leaf contains any collision surfaces + leaf->combinedsupercontents = 0; + // combine the supercontents values of all brushes in this leaf + for (j = 0;j < leaf->numleafbrushes;j++) + leaf->combinedsupercontents |= loadmodel->brush.data_brushes[leaf->firstleafbrush[j]].texture->supercontents; + // check if this leaf contains any collision surfaces (q3 patches) + for (j = 0;j < leaf->numleafsurfaces;j++) + { + msurface_t *surface = loadmodel->data_surfaces + leaf->firstleafsurface[j]; + if (surface->num_collisiontriangles) + { + leaf->containscollisionsurfaces = true; + leaf->combinedsupercontents |= surface->texture->supercontents; + } + } + } +} + +static void Mod_Q1BSP_LoadNodes(lump_t *l) +{ + int i, j, count, p; + dnode_t *in; + mnode_t *out; + + in = (dnode_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadNodes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + if (count == 0) + Host_Error("Mod_Q1BSP_LoadNodes: missing BSP tree in %s",loadmodel->name); + out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brush.data_nodes = out; + loadmodel->brush.num_nodes = count; + + for ( i=0 ; imins[j] = LittleShort(in->mins[j]); + out->maxs[j] = LittleShort(in->maxs[j]); + } + + p = LittleLong(in->planenum); + out->plane = loadmodel->brush.data_planes + p; + + out->firstsurface = (unsigned short)LittleShort(in->firstface); + out->numsurfaces = (unsigned short)LittleShort(in->numfaces); + + for (j=0 ; j<2 ; j++) + { + // LordHavoc: this code supports broken bsp files produced by + // arguire qbsp which can produce more than 32768 nodes, any value + // below count is assumed to be a node number, any other value is + // assumed to be a leaf number + p = (unsigned short)LittleShort(in->children[j]); + if (p < count) + { + if (p < loadmodel->brush.num_nodes) + out->children[j] = loadmodel->brush.data_nodes + p; + else + { + Con_Printf("Mod_Q1BSP_LoadNodes: invalid node index %i (file has only %i nodes)\n", p, loadmodel->brush.num_nodes); + // map it to the solid leaf + out->children[j] = (mnode_t *)loadmodel->brush.data_leafs; + } + } + else + { + // note this uses 65535 intentionally, -1 is leaf 0 + p = 65535 - p; + if (p < loadmodel->brush.num_leafs) + out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + p); + else + { + Con_Printf("Mod_Q1BSP_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->brush.num_leafs); + // map it to the solid leaf + out->children[j] = (mnode_t *)loadmodel->brush.data_leafs; + } + } + } + } + + Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL); // sets nodes and leafs +} + +static void Mod_Q1BSP_LoadLeafs(lump_t *l) +{ + dleaf_t *in; + mleaf_t *out; + int i, j, count, p; + + in = (dleaf_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadLeafs: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brush.data_leafs = out; + loadmodel->brush.num_leafs = count; + // get visleafs from the submodel data + loadmodel->brush.num_pvsclusters = loadmodel->brushq1.submodels[0].visleafs; + loadmodel->brush.num_pvsclusterbytes = (loadmodel->brush.num_pvsclusters+7)>>3; + loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_pvsclusters * loadmodel->brush.num_pvsclusterbytes); + memset(loadmodel->brush.data_pvsclusters, 0xFF, loadmodel->brush.num_pvsclusters * loadmodel->brush.num_pvsclusterbytes); + + for ( i=0 ; imins[j] = LittleShort(in->mins[j]); + out->maxs[j] = LittleShort(in->maxs[j]); + } + + // FIXME: this function could really benefit from some error checking + + out->contents = LittleLong(in->contents); + + out->firstleafsurface = loadmodel->brush.data_leafsurfaces + (unsigned short)LittleShort(in->firstmarksurface); + out->numleafsurfaces = (unsigned short)LittleShort(in->nummarksurfaces); + if ((unsigned short)LittleShort(in->firstmarksurface) + out->numleafsurfaces > loadmodel->brush.num_leafsurfaces) + { + Con_Printf("Mod_Q1BSP_LoadLeafs: invalid leafsurface range %i:%i outside range %i:%i\n", (int)(out->firstleafsurface - loadmodel->brush.data_leafsurfaces), (int)(out->firstleafsurface + out->numleafsurfaces - loadmodel->brush.data_leafsurfaces), 0, loadmodel->brush.num_leafsurfaces); + out->firstleafsurface = NULL; + out->numleafsurfaces = 0; + } + + out->clusterindex = i - 1; + if (out->clusterindex >= loadmodel->brush.num_pvsclusters) + out->clusterindex = -1; + + p = LittleLong(in->visofs); + // ignore visofs errors on leaf 0 (solid) + if (p >= 0 && out->clusterindex >= 0) + { + if (p >= loadmodel->brushq1.num_compressedpvs) + Con_Print("Mod_Q1BSP_LoadLeafs: invalid visofs\n"); + else + Mod_Q1BSP_DecompressVis(loadmodel->brushq1.data_compressedpvs + p, loadmodel->brushq1.data_compressedpvs + loadmodel->brushq1.num_compressedpvs, loadmodel->brush.data_pvsclusters + out->clusterindex * loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.data_pvsclusters + (out->clusterindex + 1) * loadmodel->brush.num_pvsclusterbytes); + } + + for (j = 0;j < 4;j++) + out->ambient_sound_level[j] = in->ambient_level[j]; + + // FIXME: Insert caustics here + } +} + +qboolean Mod_Q1BSP_CheckWaterAlphaSupport(void) +{ + int i, j; + mleaf_t *leaf; + const unsigned char *pvs; + // if there's no vis data, assume supported (because everything is visible all the time) + if (!loadmodel->brush.data_pvsclusters) + return true; + // check all liquid leafs to see if they can see into empty leafs, if any + // can we can assume this map supports r_wateralpha + for (i = 0, leaf = loadmodel->brush.data_leafs;i < loadmodel->brush.num_leafs;i++, leaf++) + { + if ((leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME) && leaf->clusterindex >= 0) + { + pvs = loadmodel->brush.data_pvsclusters + leaf->clusterindex * loadmodel->brush.num_pvsclusterbytes; + for (j = 0;j < loadmodel->brush.num_leafs;j++) + if (CHECKPVSBIT(pvs, loadmodel->brush.data_leafs[j].clusterindex) && loadmodel->brush.data_leafs[j].contents == CONTENTS_EMPTY) + return true; + } + } + return false; +} + +static void Mod_Q1BSP_LoadClipnodes(lump_t *l, hullinfo_t *hullinfo) +{ + dclipnode_t *in; + mclipnode_t *out; + int i, count; + hull_t *hull; + + in = (dclipnode_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadClipnodes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mclipnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brushq1.clipnodes = out; + loadmodel->brushq1.numclipnodes = count; + + for (i = 1; i < MAX_MAP_HULLS; i++) + { + hull = &loadmodel->brushq1.hulls[i]; + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = count-1; + hull->planes = loadmodel->brush.data_planes; + hull->clip_mins[0] = hullinfo->hullsizes[i][0][0]; + hull->clip_mins[1] = hullinfo->hullsizes[i][0][1]; + hull->clip_mins[2] = hullinfo->hullsizes[i][0][2]; + hull->clip_maxs[0] = hullinfo->hullsizes[i][1][0]; + hull->clip_maxs[1] = hullinfo->hullsizes[i][1][1]; + hull->clip_maxs[2] = hullinfo->hullsizes[i][1][2]; + VectorSubtract(hull->clip_maxs, hull->clip_mins, hull->clip_size); + } + + for (i=0 ; iplanenum = LittleLong(in->planenum); + // LordHavoc: this code supports arguire qbsp's broken clipnodes indices (more than 32768 clipnodes), values above count are assumed to be contents values + out->children[0] = (unsigned short)LittleShort(in->children[0]); + out->children[1] = (unsigned short)LittleShort(in->children[1]); + if (out->children[0] >= count) + out->children[0] -= 65536; + if (out->children[1] >= count) + out->children[1] -= 65536; + if (out->planenum < 0 || out->planenum >= loadmodel->brush.num_planes) + Host_Error("Corrupt clipping hull(out of range planenum)"); + } +} + +//Duplicate the drawing hull structure as a clipping hull +static void Mod_Q1BSP_MakeHull0(void) +{ + mnode_t *in; + mclipnode_t *out; + int i; + hull_t *hull; + + hull = &loadmodel->brushq1.hulls[0]; + + in = loadmodel->brush.data_nodes; + out = (mclipnode_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_nodes * sizeof(*out)); + + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = loadmodel->brush.num_nodes - 1; + hull->planes = loadmodel->brush.data_planes; + + for (i = 0;i < loadmodel->brush.num_nodes;i++, out++, in++) + { + out->planenum = in->plane - loadmodel->brush.data_planes; + out->children[0] = in->children[0]->plane ? in->children[0] - loadmodel->brush.data_nodes : ((mleaf_t *)in->children[0])->contents; + out->children[1] = in->children[1]->plane ? in->children[1] - loadmodel->brush.data_nodes : ((mleaf_t *)in->children[1])->contents; + } +} + +static void Mod_Q1BSP_LoadLeaffaces(lump_t *l) +{ + int i, j; + short *in; + + in = (short *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadLeaffaces: funny lump size in %s",loadmodel->name); + loadmodel->brush.num_leafsurfaces = l->filelen / sizeof(*in); + loadmodel->brush.data_leafsurfaces = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_leafsurfaces * sizeof(int)); + + for (i = 0;i < loadmodel->brush.num_leafsurfaces;i++) + { + j = (unsigned short) LittleShort(in[i]); + if (j >= loadmodel->num_surfaces) + Host_Error("Mod_Q1BSP_LoadLeaffaces: bad surface number"); + loadmodel->brush.data_leafsurfaces[i] = j; + } +} + +static void Mod_Q1BSP_LoadSurfedges(lump_t *l) +{ + int i; + int *in; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadSurfedges: funny lump size in %s",loadmodel->name); + loadmodel->brushq1.numsurfedges = l->filelen / sizeof(*in); + loadmodel->brushq1.surfedges = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numsurfedges * sizeof(int)); + + for (i = 0;i < loadmodel->brushq1.numsurfedges;i++) + loadmodel->brushq1.surfedges[i] = LittleLong(in[i]); +} + + +static void Mod_Q1BSP_LoadPlanes(lump_t *l) +{ + int i; + mplane_t *out; + dplane_t *in; + + in = (dplane_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q1BSP_LoadPlanes: funny lump size in %s", loadmodel->name); + + loadmodel->brush.num_planes = l->filelen / sizeof(*in); + loadmodel->brush.data_planes = out = (mplane_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_planes * sizeof(*out)); + + for (i = 0;i < loadmodel->brush.num_planes;i++, in++, out++) + { + out->normal[0] = LittleFloat(in->normal[0]); + out->normal[1] = LittleFloat(in->normal[1]); + out->normal[2] = LittleFloat(in->normal[2]); + out->dist = LittleFloat(in->dist); + + PlaneClassify(out); + } +} + +static void Mod_Q1BSP_LoadMapBrushes(void) +{ +#if 0 +// unfinished + int submodel, numbrushes; + qboolean firstbrush; + char *text, *maptext; + char mapfilename[MAX_QPATH]; + FS_StripExtension (loadmodel->name, mapfilename, sizeof (mapfilename)); + strlcat (mapfilename, ".map", sizeof (mapfilename)); + maptext = (unsigned char*) FS_LoadFile(mapfilename, tempmempool, false, NULL); + if (!maptext) + return; + text = maptext; + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + submodel = 0; + for (;;) + { + if (!COM_ParseToken_Simple(&data, false, false)) + break; + if (com_token[0] != '{') + return; // error + // entity + firstbrush = true; + numbrushes = 0; + maxbrushes = 256; + brushes = Mem_Alloc(loadmodel->mempool, maxbrushes * sizeof(mbrush_t)); + for (;;) + { + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + if (com_token[0] == '}') + break; // end of entity + if (com_token[0] == '{') + { + // brush + if (firstbrush) + { + if (submodel) + { + if (submodel > loadmodel->brush.numsubmodels) + { + Con_Printf("Mod_Q1BSP_LoadMapBrushes: .map has more submodels than .bsp!\n"); + model = NULL; + } + else + model = loadmodel->brush.submodels[submodel]; + } + else + model = loadmodel; + } + for (;;) + { + if (!COM_ParseToken_Simple(&data, false, false)) + return; // error + if (com_token[0] == '}') + break; // end of brush + // each brush face should be this format: + // ( x y z ) ( x y z ) ( x y z ) texture scroll_s scroll_t rotateangle scale_s scale_t + // FIXME: support hl .map format + for (pointnum = 0;pointnum < 3;pointnum++) + { + COM_ParseToken_Simple(&data, false, false); + for (componentnum = 0;componentnum < 3;componentnum++) + { + COM_ParseToken_Simple(&data, false, false); + point[pointnum][componentnum] = atof(com_token); + } + COM_ParseToken_Simple(&data, false, false); + } + COM_ParseToken_Simple(&data, false, false); + strlcpy(facetexture, com_token, sizeof(facetexture)); + COM_ParseToken_Simple(&data, false, false); + //scroll_s = atof(com_token); + COM_ParseToken_Simple(&data, false, false); + //scroll_t = atof(com_token); + COM_ParseToken_Simple(&data, false, false); + //rotate = atof(com_token); + COM_ParseToken_Simple(&data, false, false); + //scale_s = atof(com_token); + COM_ParseToken_Simple(&data, false, false); + //scale_t = atof(com_token); + TriangleNormal(point[0], point[1], point[2], planenormal); + VectorNormalizeDouble(planenormal); + planedist = DotProduct(point[0], planenormal); + //ChooseTexturePlane(planenormal, texturevector[0], texturevector[1]); + } + continue; + } + } + } +#endif +} + + +#define MAX_PORTALPOINTS 64 + +typedef struct portal_s +{ + mplane_t plane; + mnode_t *nodes[2]; // [0] = front side of plane + struct portal_s *next[2]; + int numpoints; + double points[3*MAX_PORTALPOINTS]; + struct portal_s *chain; // all portals are linked into a list +} +portal_t; + +static memexpandablearray_t portalarray; + +static void Mod_Q1BSP_RecursiveRecalcNodeBBox(mnode_t *node) +{ + // process only nodes (leafs already had their box calculated) + if (!node->plane) + return; + + // calculate children first + Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[0]); + Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[1]); + + // make combined bounding box from children + node->mins[0] = min(node->children[0]->mins[0], node->children[1]->mins[0]); + node->mins[1] = min(node->children[0]->mins[1], node->children[1]->mins[1]); + node->mins[2] = min(node->children[0]->mins[2], node->children[1]->mins[2]); + node->maxs[0] = max(node->children[0]->maxs[0], node->children[1]->maxs[0]); + node->maxs[1] = max(node->children[0]->maxs[1], node->children[1]->maxs[1]); + node->maxs[2] = max(node->children[0]->maxs[2], node->children[1]->maxs[2]); +} + +static void Mod_Q1BSP_FinalizePortals(void) +{ + int i, j, numportals, numpoints, portalindex, portalrange = Mem_ExpandableArray_IndexRange(&portalarray); + portal_t *p; + mportal_t *portal; + mvertex_t *point; + mleaf_t *leaf, *endleaf; + + // tally up portal and point counts and recalculate bounding boxes for all + // leafs (because qbsp is very sloppy) + leaf = loadmodel->brush.data_leafs; + endleaf = leaf + loadmodel->brush.num_leafs; + if (mod_recalculatenodeboxes.integer) + { + for (;leaf < endleaf;leaf++) + { + VectorSet(leaf->mins, 2000000000, 2000000000, 2000000000); + VectorSet(leaf->maxs, -2000000000, -2000000000, -2000000000); + } + } + numportals = 0; + numpoints = 0; + for (portalindex = 0;portalindex < portalrange;portalindex++) + { + p = (portal_t*)Mem_ExpandableArray_RecordAtIndex(&portalarray, portalindex); + if (!p) + continue; + // note: this check must match the one below or it will usually corrupt memory + // the nodes[0] != nodes[1] check is because leaf 0 is the shared solid leaf, it can have many portals inside with leaf 0 on both sides + if (p->numpoints >= 3 && p->nodes[0] != p->nodes[1] && ((mleaf_t *)p->nodes[0])->clusterindex >= 0 && ((mleaf_t *)p->nodes[1])->clusterindex >= 0) + { + numportals += 2; + numpoints += p->numpoints * 2; + } + } + loadmodel->brush.data_portals = (mportal_t *)Mem_Alloc(loadmodel->mempool, numportals * sizeof(mportal_t) + numpoints * sizeof(mvertex_t)); + loadmodel->brush.num_portals = numportals; + loadmodel->brush.data_portalpoints = (mvertex_t *)((unsigned char *) loadmodel->brush.data_portals + numportals * sizeof(mportal_t)); + loadmodel->brush.num_portalpoints = numpoints; + // clear all leaf portal chains + for (i = 0;i < loadmodel->brush.num_leafs;i++) + loadmodel->brush.data_leafs[i].portals = NULL; + // process all portals in the global portal chain, while freeing them + portal = loadmodel->brush.data_portals; + point = loadmodel->brush.data_portalpoints; + for (portalindex = 0;portalindex < portalrange;portalindex++) + { + p = (portal_t*)Mem_ExpandableArray_RecordAtIndex(&portalarray, portalindex); + if (!p) + continue; + if (p->numpoints >= 3 && p->nodes[0] != p->nodes[1]) + { + // note: this check must match the one above or it will usually corrupt memory + // the nodes[0] != nodes[1] check is because leaf 0 is the shared solid leaf, it can have many portals inside with leaf 0 on both sides + if (((mleaf_t *)p->nodes[0])->clusterindex >= 0 && ((mleaf_t *)p->nodes[1])->clusterindex >= 0) + { + // first make the back to front portal(forward portal) + portal->points = point; + portal->numpoints = p->numpoints; + portal->plane.dist = p->plane.dist; + VectorCopy(p->plane.normal, portal->plane.normal); + portal->here = (mleaf_t *)p->nodes[1]; + portal->past = (mleaf_t *)p->nodes[0]; + // copy points + for (j = 0;j < portal->numpoints;j++) + { + VectorCopy(p->points + j*3, point->position); + point++; + } + BoxFromPoints(portal->mins, portal->maxs, portal->numpoints, portal->points->position); + PlaneClassify(&portal->plane); + + // link into leaf's portal chain + portal->next = portal->here->portals; + portal->here->portals = portal; + + // advance to next portal + portal++; + + // then make the front to back portal(backward portal) + portal->points = point; + portal->numpoints = p->numpoints; + portal->plane.dist = -p->plane.dist; + VectorNegate(p->plane.normal, portal->plane.normal); + portal->here = (mleaf_t *)p->nodes[0]; + portal->past = (mleaf_t *)p->nodes[1]; + // copy points + for (j = portal->numpoints - 1;j >= 0;j--) + { + VectorCopy(p->points + j*3, point->position); + point++; + } + BoxFromPoints(portal->mins, portal->maxs, portal->numpoints, portal->points->position); + PlaneClassify(&portal->plane); + + // link into leaf's portal chain + portal->next = portal->here->portals; + portal->here->portals = portal; + + // advance to next portal + portal++; + } + // add the portal's polygon points to the leaf bounding boxes + if (mod_recalculatenodeboxes.integer) + { + for (i = 0;i < 2;i++) + { + leaf = (mleaf_t *)p->nodes[i]; + for (j = 0;j < p->numpoints;j++) + { + if (leaf->mins[0] > p->points[j*3+0]) leaf->mins[0] = p->points[j*3+0]; + if (leaf->mins[1] > p->points[j*3+1]) leaf->mins[1] = p->points[j*3+1]; + if (leaf->mins[2] > p->points[j*3+2]) leaf->mins[2] = p->points[j*3+2]; + if (leaf->maxs[0] < p->points[j*3+0]) leaf->maxs[0] = p->points[j*3+0]; + if (leaf->maxs[1] < p->points[j*3+1]) leaf->maxs[1] = p->points[j*3+1]; + if (leaf->maxs[2] < p->points[j*3+2]) leaf->maxs[2] = p->points[j*3+2]; + } + } + } + } + } + // now recalculate the node bounding boxes from the leafs + if (mod_recalculatenodeboxes.integer) + Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode); +} + +/* +============= +AddPortalToNodes +============= +*/ +static void AddPortalToNodes(portal_t *p, mnode_t *front, mnode_t *back) +{ + if (!front) + Host_Error("AddPortalToNodes: NULL front node"); + if (!back) + Host_Error("AddPortalToNodes: NULL back node"); + if (p->nodes[0] || p->nodes[1]) + Host_Error("AddPortalToNodes: already included"); + // note: front == back is handled gracefully, because leaf 0 is the shared solid leaf, it can often have portals with the same leaf on both sides + + p->nodes[0] = front; + p->next[0] = (portal_t *)front->portals; + front->portals = (mportal_t *)p; + + p->nodes[1] = back; + p->next[1] = (portal_t *)back->portals; + back->portals = (mportal_t *)p; +} + +/* +============= +RemovePortalFromNode +============= +*/ +static void RemovePortalFromNodes(portal_t *portal) +{ + int i; + mnode_t *node; + void **portalpointer; + portal_t *t; + for (i = 0;i < 2;i++) + { + node = portal->nodes[i]; + + portalpointer = (void **) &node->portals; + while (1) + { + t = (portal_t *)*portalpointer; + if (!t) + Host_Error("RemovePortalFromNodes: portal not in leaf"); + + if (t == portal) + { + if (portal->nodes[0] == node) + { + *portalpointer = portal->next[0]; + portal->nodes[0] = NULL; + } + else if (portal->nodes[1] == node) + { + *portalpointer = portal->next[1]; + portal->nodes[1] = NULL; + } + else + Host_Error("RemovePortalFromNodes: portal not bounding leaf"); + break; + } + + if (t->nodes[0] == node) + portalpointer = (void **) &t->next[0]; + else if (t->nodes[1] == node) + portalpointer = (void **) &t->next[1]; + else + Host_Error("RemovePortalFromNodes: portal not bounding leaf"); + } + } +} + +#define PORTAL_DIST_EPSILON (1.0 / 32.0) +static double *portalpointsbuffer; +static int portalpointsbufferoffset; +static int portalpointsbuffersize; +static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node) +{ + int i, side; + mnode_t *front, *back, *other_node; + mplane_t clipplane, *plane; + portal_t *portal, *nextportal, *nodeportal, *splitportal, *temp; + int numfrontpoints, numbackpoints; + double *frontpoints, *backpoints; + + // if a leaf, we're done + if (!node->plane) + return; + + // get some space for our clipping operations to use + if (portalpointsbuffersize < portalpointsbufferoffset + 6*MAX_PORTALPOINTS) + { + portalpointsbuffersize = portalpointsbufferoffset * 2; + portalpointsbuffer = (double *)Mem_Realloc(loadmodel->mempool, portalpointsbuffer, portalpointsbuffersize * sizeof(*portalpointsbuffer)); + } + frontpoints = portalpointsbuffer + portalpointsbufferoffset; + portalpointsbufferoffset += 3*MAX_PORTALPOINTS; + backpoints = portalpointsbuffer + portalpointsbufferoffset; + portalpointsbufferoffset += 3*MAX_PORTALPOINTS; + + plane = node->plane; + + front = node->children[0]; + back = node->children[1]; + if (front == back) + Host_Error("Mod_Q1BSP_RecursiveNodePortals: corrupt node hierarchy"); + + // create the new portal by generating a polygon for the node plane, + // and clipping it by all of the other portals(which came from nodes above this one) + nodeportal = (portal_t *)Mem_ExpandableArray_AllocRecord(&portalarray); + nodeportal->plane = *plane; + + // TODO: calculate node bounding boxes during recursion and calculate a maximum plane size accordingly to improve precision (as most maps do not need 1 billion unit plane polygons) + PolygonD_QuadForPlane(nodeportal->points, nodeportal->plane.normal[0], nodeportal->plane.normal[1], nodeportal->plane.normal[2], nodeportal->plane.dist, 1024.0*1024.0*1024.0); + nodeportal->numpoints = 4; + side = 0; // shut up compiler warning + for (portal = (portal_t *)node->portals;portal;portal = portal->next[side]) + { + clipplane = portal->plane; + if (portal->nodes[0] == portal->nodes[1]) + Host_Error("Mod_Q1BSP_RecursiveNodePortals: portal has same node on both sides(1)"); + if (portal->nodes[0] == node) + side = 0; + else if (portal->nodes[1] == node) + { + clipplane.dist = -clipplane.dist; + VectorNegate(clipplane.normal, clipplane.normal); + side = 1; + } + else + Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal"); + + for (i = 0;i < nodeportal->numpoints*3;i++) + frontpoints[i] = nodeportal->points[i]; + PolygonD_Divide(nodeportal->numpoints, frontpoints, clipplane.normal[0], clipplane.normal[1], clipplane.normal[2], clipplane.dist, PORTAL_DIST_EPSILON, MAX_PORTALPOINTS, nodeportal->points, &nodeportal->numpoints, 0, NULL, NULL, NULL); + if (nodeportal->numpoints <= 0 || nodeportal->numpoints >= MAX_PORTALPOINTS) + break; + } + + if (nodeportal->numpoints < 3) + { + Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal was clipped away\n"); + nodeportal->numpoints = 0; + } + else if (nodeportal->numpoints >= MAX_PORTALPOINTS) + { + Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal has too many points\n"); + nodeportal->numpoints = 0; + } + + AddPortalToNodes(nodeportal, front, back); + + // split the portals of this node along this node's plane and assign them to the children of this node + // (migrating the portals downward through the tree) + for (portal = (portal_t *)node->portals;portal;portal = nextportal) + { + if (portal->nodes[0] == portal->nodes[1]) + Host_Error("Mod_Q1BSP_RecursiveNodePortals: portal has same node on both sides(2)"); + if (portal->nodes[0] == node) + side = 0; + else if (portal->nodes[1] == node) + side = 1; + else + Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal"); + nextportal = portal->next[side]; + if (!portal->numpoints) + continue; + + other_node = portal->nodes[!side]; + RemovePortalFromNodes(portal); + + // cut the portal into two portals, one on each side of the node plane + PolygonD_Divide(portal->numpoints, portal->points, plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, PORTAL_DIST_EPSILON, MAX_PORTALPOINTS, frontpoints, &numfrontpoints, MAX_PORTALPOINTS, backpoints, &numbackpoints, NULL); + + if (!numfrontpoints) + { + if (side == 0) + AddPortalToNodes(portal, back, other_node); + else + AddPortalToNodes(portal, other_node, back); + continue; + } + if (!numbackpoints) + { + if (side == 0) + AddPortalToNodes(portal, front, other_node); + else + AddPortalToNodes(portal, other_node, front); + continue; + } + + // the portal is split + splitportal = (portal_t *)Mem_ExpandableArray_AllocRecord(&portalarray); + temp = splitportal->chain; + *splitportal = *portal; + splitportal->chain = temp; + for (i = 0;i < numbackpoints*3;i++) + splitportal->points[i] = backpoints[i]; + splitportal->numpoints = numbackpoints; + for (i = 0;i < numfrontpoints*3;i++) + portal->points[i] = frontpoints[i]; + portal->numpoints = numfrontpoints; + + if (side == 0) + { + AddPortalToNodes(portal, front, other_node); + AddPortalToNodes(splitportal, back, other_node); + } + else + { + AddPortalToNodes(portal, other_node, front); + AddPortalToNodes(splitportal, other_node, back); + } + } + + Mod_Q1BSP_RecursiveNodePortals(front); + Mod_Q1BSP_RecursiveNodePortals(back); + + portalpointsbufferoffset -= 6*MAX_PORTALPOINTS; +} + +static void Mod_Q1BSP_MakePortals(void) +{ + Mem_ExpandableArray_NewArray(&portalarray, loadmodel->mempool, sizeof(portal_t), 1020*1024/sizeof(portal_t)); + portalpointsbufferoffset = 0; + portalpointsbuffersize = 6*MAX_PORTALPOINTS*128; + portalpointsbuffer = (double *)Mem_Alloc(loadmodel->mempool, portalpointsbuffersize * sizeof(*portalpointsbuffer)); + Mod_Q1BSP_RecursiveNodePortals(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode); + Mem_Free(portalpointsbuffer); + portalpointsbuffer = NULL; + portalpointsbufferoffset = 0; + portalpointsbuffersize = 0; + Mod_Q1BSP_FinalizePortals(); + Mem_ExpandableArray_FreeArray(&portalarray); +} + +//Returns PVS data for a given point +//(note: can return NULL) +static unsigned char *Mod_Q1BSP_GetPVS(dp_model_t *model, const vec3_t p) +{ + mnode_t *node; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + while (node->plane) + node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist]; + if (((mleaf_t *)node)->clusterindex >= 0) + return model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes; + else + return NULL; +} + +static void Mod_Q1BSP_FatPVS_RecursiveBSPNode(dp_model_t *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbytes, mnode_t *node) +{ + while (node->plane) + { + float d = PlaneDiff(org, node->plane); + if (d > radius) + node = node->children[0]; + else if (d < -radius) + node = node->children[1]; + else + { + // go down both sides + Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, pvsbytes, node->children[0]); + node = node->children[1]; + } + } + // if this leaf is in a cluster, accumulate the pvs bits + if (((mleaf_t *)node)->clusterindex >= 0) + { + int i; + unsigned char *pvs = model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes; + for (i = 0;i < pvsbytes;i++) + pvsbuffer[i] |= pvs[i]; + } +} + +//Calculates a PVS that is the inclusive or of all leafs within radius pixels +//of the given point. +static int Mod_Q1BSP_FatPVS(dp_model_t *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbufferlength, qboolean merge) +{ + int bytes = model->brush.num_pvsclusterbytes; + bytes = min(bytes, pvsbufferlength); + if (r_novis.integer || r_trippy.integer || !model->brush.num_pvsclusters || !Mod_Q1BSP_GetPVS(model, org)) + { + memset(pvsbuffer, 0xFF, bytes); + return bytes; + } + if (!merge) + memset(pvsbuffer, 0, bytes); + Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode); + return bytes; +} + +static void Mod_Q1BSP_RoundUpToHullSize(dp_model_t *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs) +{ + vec3_t size; + const hull_t *hull; + + VectorSubtract(inmaxs, inmins, size); + if (cmodel->brush.ishlbsp) + { + if (size[0] < 3) + hull = &cmodel->brushq1.hulls[0]; // 0x0x0 + else if (size[0] <= 32) + { + if (size[2] < 54) // pick the nearest of 36 or 72 + hull = &cmodel->brushq1.hulls[3]; // 32x32x36 + else + hull = &cmodel->brushq1.hulls[1]; // 32x32x72 + } + else + hull = &cmodel->brushq1.hulls[2]; // 64x64x64 + } + else + { + if (size[0] < 3) + hull = &cmodel->brushq1.hulls[0]; // 0x0x0 + else if (size[0] <= 32) + hull = &cmodel->brushq1.hulls[1]; // 32x32x56 + else + hull = &cmodel->brushq1.hulls[2]; // 64x64x88 + } + VectorCopy(inmins, outmins); + VectorAdd(inmins, hull->clip_size, outmaxs); +} + +static int Mod_Q1BSP_CreateShadowMesh(dp_model_t *mod) +{ + int j; + int numshadowmeshtriangles = 0; + msurface_t *surface; + if (cls.state == ca_dedicated) + return 0; + // make a single combined shadow mesh to allow optimized shadow volume creation + + for (j = 0, surface = mod->data_surfaces;j < mod->num_surfaces;j++, surface++) + { + surface->num_firstshadowmeshtriangle = numshadowmeshtriangles; + numshadowmeshtriangles += surface->num_triangles; + } + mod->brush.shadowmesh = Mod_ShadowMesh_Begin(mod->mempool, numshadowmeshtriangles * 3, numshadowmeshtriangles, NULL, NULL, NULL, false, false, true); + for (j = 0, surface = mod->data_surfaces;j < mod->num_surfaces;j++, surface++) + if (surface->num_triangles > 0) + Mod_ShadowMesh_AddMesh(mod->mempool, mod->brush.shadowmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); + mod->brush.shadowmesh = Mod_ShadowMesh_Finish(mod->mempool, mod->brush.shadowmesh, false, r_enableshadowvolumes.integer != 0, false); + if (mod->brush.shadowmesh && mod->brush.shadowmesh->neighbor3i) + Mod_BuildTriangleNeighbors(mod->brush.shadowmesh->neighbor3i, mod->brush.shadowmesh->element3i, mod->brush.shadowmesh->numtriangles); + + return numshadowmeshtriangles; +} + +void Mod_CollisionBIH_TraceLineAgainstSurfaces(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); + +void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, k; + dheader_t *header; + dmodel_t *bm; + float dist, modelyawradius, modelradius; + msurface_t *surface; + hullinfo_t hullinfo; + int totalstylesurfaces, totalstyles, stylecounts[256], remapstyles[256]; + model_brush_lightstyleinfo_t styleinfo[256]; + unsigned char *datapointer; + + mod->modeldatatypestring = "Q1BSP"; + + mod->type = mod_brushq1; + + header = (dheader_t *)buffer; + + i = LittleLong(header->version); + if (i != BSPVERSION && i != 30) + Host_Error("Mod_Q1BSP_Load: %s has wrong version number(%i should be %i(Quake) or 30(HalfLife)", mod->name, i, BSPVERSION); + mod->brush.ishlbsp = i == 30; + +// fill in hull info + VectorClear (hullinfo.hullsizes[0][0]); + VectorClear (hullinfo.hullsizes[0][1]); + if (mod->brush.ishlbsp) + { + mod->modeldatatypestring = "HLBSP"; + + hullinfo.filehulls = 4; + VectorSet (hullinfo.hullsizes[1][0], -16, -16, -36); + VectorSet (hullinfo.hullsizes[1][1], 16, 16, 36); + VectorSet (hullinfo.hullsizes[2][0], -32, -32, -32); + VectorSet (hullinfo.hullsizes[2][1], 32, 32, 32); + VectorSet (hullinfo.hullsizes[3][0], -16, -16, -18); + VectorSet (hullinfo.hullsizes[3][1], 16, 16, 18); + } + else + { + hullinfo.filehulls = 4; + VectorSet (hullinfo.hullsizes[1][0], -16, -16, -24); + VectorSet (hullinfo.hullsizes[1][1], 16, 16, 32); + VectorSet (hullinfo.hullsizes[2][0], -32, -32, -24); + VectorSet (hullinfo.hullsizes[2][1], 32, 32, 64); + } + +// read lumps + mod_base = (unsigned char*)buffer; + for (i = 0; i < HEADER_LUMPS; i++) + { + header->lumps[i].fileofs = LittleLong(header->lumps[i].fileofs); + header->lumps[i].filelen = LittleLong(header->lumps[i].filelen); + } + + mod->soundfromcenter = true; + mod->TraceBox = Mod_Q1BSP_TraceBox; + if (sv_gameplayfix_q1bsptracelinereportstexture.integer) + mod->TraceLine = Mod_Q1BSP_TraceLineAgainstSurfaces; // LordHavoc: use the surface-hitting version of TraceLine in all cases + else + mod->TraceLine = Mod_Q1BSP_TraceLine; + mod->TracePoint = Mod_Q1BSP_TracePoint; + mod->PointSuperContents = Mod_Q1BSP_PointSuperContents; + mod->TraceLineAgainstSurfaces = Mod_Q1BSP_TraceLineAgainstSurfaces; + mod->brush.TraceLineOfSight = Mod_Q1BSP_TraceLineOfSight; + mod->brush.SuperContentsFromNativeContents = Mod_Q1BSP_SuperContentsFromNativeContents; + mod->brush.NativeContentsFromSuperContents = Mod_Q1BSP_NativeContentsFromSuperContents; + mod->brush.GetPVS = Mod_Q1BSP_GetPVS; + mod->brush.FatPVS = Mod_Q1BSP_FatPVS; + mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS; + mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS; + mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs; + mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters; + mod->brush.LightPoint = Mod_Q1BSP_LightPoint; + mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation; + mod->brush.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint; + mod->brush.RoundUpToHullSize = Mod_Q1BSP_RoundUpToHullSize; + mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf; + mod->Draw = R_Q1BSP_Draw; + mod->DrawDepth = R_Q1BSP_DrawDepth; + mod->DrawDebug = R_Q1BSP_DrawDebug; + mod->DrawPrepass = R_Q1BSP_DrawPrepass; + mod->GetLightInfo = R_Q1BSP_GetLightInfo; + mod->CompileShadowMap = R_Q1BSP_CompileShadowMap; + mod->DrawShadowMap = R_Q1BSP_DrawShadowMap; + mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + mod->DrawLight = R_Q1BSP_DrawLight; + +// load into heap + + mod->brush.qw_md4sum = 0; + mod->brush.qw_md4sum2 = 0; + for (i = 0;i < HEADER_LUMPS;i++) + { + int temp; + if (i == LUMP_ENTITIES) + continue; + temp = Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + mod->brush.qw_md4sum ^= LittleLong(temp); + if (i == LUMP_VISIBILITY || i == LUMP_LEAFS || i == LUMP_NODES) + continue; + temp = Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + mod->brush.qw_md4sum2 ^= LittleLong(temp); + } + + Mod_Q1BSP_LoadEntities(&header->lumps[LUMP_ENTITIES]); + Mod_Q1BSP_LoadVertexes(&header->lumps[LUMP_VERTEXES]); + Mod_Q1BSP_LoadEdges(&header->lumps[LUMP_EDGES]); + Mod_Q1BSP_LoadSurfedges(&header->lumps[LUMP_SURFEDGES]); + Mod_Q1BSP_LoadTextures(&header->lumps[LUMP_TEXTURES]); + Mod_Q1BSP_LoadLighting(&header->lumps[LUMP_LIGHTING]); + Mod_Q1BSP_LoadPlanes(&header->lumps[LUMP_PLANES]); + Mod_Q1BSP_LoadTexinfo(&header->lumps[LUMP_TEXINFO]); + Mod_Q1BSP_LoadFaces(&header->lumps[LUMP_FACES]); + Mod_Q1BSP_LoadLeaffaces(&header->lumps[LUMP_MARKSURFACES]); + Mod_Q1BSP_LoadVisibility(&header->lumps[LUMP_VISIBILITY]); + // load submodels before leafs because they contain the number of vis leafs + Mod_Q1BSP_LoadSubmodels(&header->lumps[LUMP_MODELS], &hullinfo); + Mod_Q1BSP_LoadLeafs(&header->lumps[LUMP_LEAFS]); + Mod_Q1BSP_LoadNodes(&header->lumps[LUMP_NODES]); + Mod_Q1BSP_LoadClipnodes(&header->lumps[LUMP_CLIPNODES], &hullinfo); + + // check if the map supports transparent water rendering + loadmodel->brush.supportwateralpha = Mod_Q1BSP_CheckWaterAlphaSupport(); + + if (mod->brushq1.data_compressedpvs) + Mem_Free(mod->brushq1.data_compressedpvs); + mod->brushq1.data_compressedpvs = NULL; + mod->brushq1.num_compressedpvs = 0; + + Mod_Q1BSP_MakeHull0(); + if (mod_bsp_portalize.integer) + Mod_Q1BSP_MakePortals(); + + mod->numframes = 2; // regular and alternate animation + mod->numskins = 1; + + // make a single combined shadow mesh to allow optimized shadow volume creation + Mod_Q1BSP_CreateShadowMesh(loadmodel); + + if (loadmodel->brush.numsubmodels) + loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); + + // LordHavoc: to clear the fog around the original quake submodel code, I + // will explain: + // first of all, some background info on the submodels: + // model 0 is the map model (the world, named maps/e1m1.bsp for example) + // model 1 and higher are submodels (doors and the like, named *1, *2, etc) + // now the weird for loop itself: + // the loop functions in an odd way, on each iteration it sets up the + // current 'mod' model (which despite the confusing code IS the model of + // the number i), at the end of the loop it duplicates the model to become + // the next submodel, and loops back to set up the new submodel. + + // LordHavoc: now the explanation of my sane way (which works identically): + // set up the world model, then on each submodel copy from the world model + // and set up the submodel with the respective model info. + totalstylesurfaces = 0; + totalstyles = 0; + for (i = 0;i < mod->brush.numsubmodels;i++) + { + memset(stylecounts, 0, sizeof(stylecounts)); + for (k = 0;k < mod->brushq1.submodels[i].numfaces;k++) + { + surface = mod->data_surfaces + mod->brushq1.submodels[i].firstface + k; + for (j = 0;j < MAXLIGHTMAPS;j++) + stylecounts[surface->lightmapinfo->styles[j]]++; + } + for (k = 0;k < 255;k++) + { + totalstyles++; + if (stylecounts[k]) + totalstylesurfaces += stylecounts[k]; + } + } + datapointer = (unsigned char *)Mem_Alloc(mod->mempool, mod->num_surfaces * sizeof(int) + totalstyles * sizeof(model_brush_lightstyleinfo_t) + totalstylesurfaces * sizeof(int *)); + for (i = 0;i < mod->brush.numsubmodels;i++) + { + // LordHavoc: this code was originally at the end of this loop, but + // has been transformed to something more readable at the start here. + + if (i > 0) + { + char name[10]; + // duplicate the basic information + dpsnprintf(name, sizeof(name), "*%i", i); + mod = Mod_FindName(name, loadmodel->name); + // copy the base model to this one + *mod = *loadmodel; + // rename the clone back to its proper name + strlcpy(mod->name, name, sizeof(mod->name)); + mod->brush.parentmodel = loadmodel; + // textures and memory belong to the main model + mod->texturepool = NULL; + mod->mempool = NULL; + mod->brush.GetPVS = NULL; + mod->brush.FatPVS = NULL; + mod->brush.BoxTouchingPVS = NULL; + mod->brush.BoxTouchingLeafPVS = NULL; + mod->brush.BoxTouchingVisibleLeafs = NULL; + mod->brush.FindBoxClusters = NULL; + mod->brush.LightPoint = NULL; + mod->brush.AmbientSoundLevelsForPoint = NULL; + } + + mod->brush.submodel = i; + + if (loadmodel->brush.submodels) + loadmodel->brush.submodels[i] = mod; + + bm = &mod->brushq1.submodels[i]; + + mod->brushq1.hulls[0].firstclipnode = bm->headnode[0]; + for (j=1 ; jbrushq1.hulls[j].firstclipnode = bm->headnode[j]; + mod->brushq1.hulls[j].lastclipnode = mod->brushq1.numclipnodes - 1; + } + + mod->firstmodelsurface = bm->firstface; + mod->nummodelsurfaces = bm->numfaces; + + // set node/leaf parents for this submodel + Mod_Q1BSP_LoadNodes_RecursiveSetParent(mod->brush.data_nodes + mod->brushq1.hulls[0].firstclipnode, NULL); + + // make the model surface list (used by shadowing/lighting) + mod->sortedmodelsurfaces = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int); + Mod_MakeSortedSurfaces(mod); + + // copy the submodel bounds, then enlarge the yaw and rotated bounds according to radius + // (previously this code measured the radius of the vertices of surfaces in the submodel, but that broke submodels that contain only CLIP brushes, which do not produce surfaces) + VectorCopy(bm->mins, mod->normalmins); + VectorCopy(bm->maxs, mod->normalmaxs); + dist = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); + modelyawradius = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); + modelyawradius = dist*dist+modelyawradius*modelyawradius; + modelradius = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); + modelradius = modelyawradius + modelradius * modelradius; + modelyawradius = sqrt(modelyawradius); + modelradius = sqrt(modelradius); + mod->yawmins[0] = mod->yawmins[1] = -modelyawradius; + mod->yawmins[2] = mod->normalmins[2]; + mod->yawmaxs[0] = mod->yawmaxs[1] = modelyawradius; + mod->yawmaxs[2] = mod->normalmaxs[2]; + mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; + mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; + mod->radius = modelradius; + mod->radius2 = modelradius * modelradius; + + // this gets altered below if sky or water is used + mod->DrawSky = NULL; + mod->DrawAddWaterPlanes = NULL; + + // scan surfaces for sky and water and flag the submodel as possessing these features or not + // build lightstyle lists for quick marking of dirty lightmaps when lightstyles flicker + if (mod->nummodelsurfaces) + { + for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++) + if (surface->texture->basematerialflags & MATERIALFLAG_SKY) + break; + if (j < mod->nummodelsurfaces) + mod->DrawSky = R_Q1BSP_DrawSky; + + for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++) + if (surface->texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + break; + if (j < mod->nummodelsurfaces) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + + // build lightstyle update chains + // (used to rapidly mark lightmapupdateflags on many surfaces + // when d_lightstylevalue changes) + memset(stylecounts, 0, sizeof(stylecounts)); + for (k = 0;k < mod->nummodelsurfaces;k++) + { + surface = mod->data_surfaces + mod->firstmodelsurface + k; + for (j = 0;j < MAXLIGHTMAPS;j++) + stylecounts[surface->lightmapinfo->styles[j]]++; + } + mod->brushq1.num_lightstyles = 0; + for (k = 0;k < 255;k++) + { + if (stylecounts[k]) + { + styleinfo[mod->brushq1.num_lightstyles].style = k; + styleinfo[mod->brushq1.num_lightstyles].value = 0; + styleinfo[mod->brushq1.num_lightstyles].numsurfaces = 0; + styleinfo[mod->brushq1.num_lightstyles].surfacelist = (int *)datapointer;datapointer += stylecounts[k] * sizeof(int); + remapstyles[k] = mod->brushq1.num_lightstyles; + mod->brushq1.num_lightstyles++; + } + } + for (k = 0;k < mod->nummodelsurfaces;k++) + { + surface = mod->data_surfaces + mod->firstmodelsurface + k; + for (j = 0;j < MAXLIGHTMAPS;j++) + { + if (surface->lightmapinfo->styles[j] != 255) + { + int r = remapstyles[surface->lightmapinfo->styles[j]]; + styleinfo[r].surfacelist[styleinfo[r].numsurfaces++] = mod->firstmodelsurface + k; + } + } + } + mod->brushq1.data_lightstyleinfo = (model_brush_lightstyleinfo_t *)datapointer;datapointer += mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t); + memcpy(mod->brushq1.data_lightstyleinfo, styleinfo, mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t)); + } + else + { + // LordHavoc: empty submodel(lacrima.bsp has such a glitch) + Con_Printf("warning: empty submodel *%i in %s\n", i+1, loadmodel->name); + } + //mod->brushq1.num_visleafs = bm->visleafs; + + // build a Bounding Interval Hierarchy for culling triangles in light rendering + Mod_MakeCollisionBIH(mod, true, &mod->render_bih); + + if (mod_q1bsp_polygoncollisions.integer) + { + mod->collision_bih = mod->render_bih; + // point traces and contents checks still use the bsp tree + mod->TraceLine = Mod_CollisionBIH_TraceLine; + mod->TraceBox = Mod_CollisionBIH_TraceBox; + mod->TraceBrush = Mod_CollisionBIH_TraceBrush; + mod->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLineAgainstSurfaces; + } + + // generate VBOs and other shared data before cloning submodels + if (i == 0) + { + Mod_BuildVBOs(); + Mod_Q1BSP_LoadMapBrushes(); + //Mod_Q1BSP_ProcessLightList(); + } + } + + Con_DPrintf("Stats for q1bsp model \"%s\": %i faces, %i nodes, %i leafs, %i visleafs, %i visleafportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); +} + +static void Mod_Q2BSP_LoadEntities(lump_t *l) +{ +} + +static void Mod_Q2BSP_LoadPlanes(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadPlanes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadVertices(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadVertices: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadVisibility(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadVisibility: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadNodes(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadNodes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadTexInfo(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadTexInfo: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadFaces(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadLighting(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadLighting: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadLeafs(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadLeafs: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadLeafFaces(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadLeafFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadLeafBrushes(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadLeafBrushes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadEdges(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadEdges: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadSurfEdges(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadSurfEdges: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadBrushes(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadBrushes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadBrushSides(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadAreas(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadAreas: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadAreaPortals(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadAreaPortals: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadModels(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadModels: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +void static Mod_Q2BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i; + q2dheader_t *header; + + Host_Error("Mod_Q2BSP_Load: not yet implemented"); + + mod->modeldatatypestring = "Q2BSP"; + + mod->type = mod_brushq2; + + header = (q2dheader_t *)buffer; + + i = LittleLong(header->version); + if (i != Q2BSPVERSION) + Host_Error("Mod_Q2BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q2BSPVERSION); + + mod_base = (unsigned char *)header; + + // swap all the lumps + for (i = 0;i < (int) sizeof(*header) / 4;i++) + ((int *)header)[i] = LittleLong(((int *)header)[i]); + + mod->brush.qw_md4sum = 0; + mod->brush.qw_md4sum2 = 0; + for (i = 0;i < Q2HEADER_LUMPS;i++) + { + if (i == Q2LUMP_ENTITIES) + continue; + mod->brush.qw_md4sum ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + if (i == Q2LUMP_VISIBILITY || i == Q2LUMP_LEAFS || i == Q2LUMP_NODES) + continue; + mod->brush.qw_md4sum2 ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + } + + Mod_Q2BSP_LoadEntities(&header->lumps[Q2LUMP_ENTITIES]); + Mod_Q2BSP_LoadPlanes(&header->lumps[Q2LUMP_PLANES]); + Mod_Q2BSP_LoadVertices(&header->lumps[Q2LUMP_VERTEXES]); + Mod_Q2BSP_LoadVisibility(&header->lumps[Q2LUMP_VISIBILITY]); + Mod_Q2BSP_LoadNodes(&header->lumps[Q2LUMP_NODES]); + Mod_Q2BSP_LoadTexInfo(&header->lumps[Q2LUMP_TEXINFO]); + Mod_Q2BSP_LoadFaces(&header->lumps[Q2LUMP_FACES]); + Mod_Q2BSP_LoadLighting(&header->lumps[Q2LUMP_LIGHTING]); + Mod_Q2BSP_LoadLeafs(&header->lumps[Q2LUMP_LEAFS]); + Mod_Q2BSP_LoadLeafFaces(&header->lumps[Q2LUMP_LEAFFACES]); + Mod_Q2BSP_LoadLeafBrushes(&header->lumps[Q2LUMP_LEAFBRUSHES]); + Mod_Q2BSP_LoadEdges(&header->lumps[Q2LUMP_EDGES]); + Mod_Q2BSP_LoadSurfEdges(&header->lumps[Q2LUMP_SURFEDGES]); + Mod_Q2BSP_LoadBrushes(&header->lumps[Q2LUMP_BRUSHES]); + Mod_Q2BSP_LoadBrushSides(&header->lumps[Q2LUMP_BRUSHSIDES]); + Mod_Q2BSP_LoadAreas(&header->lumps[Q2LUMP_AREAS]); + Mod_Q2BSP_LoadAreaPortals(&header->lumps[Q2LUMP_AREAPORTALS]); + // LordHavoc: must go last because this makes the submodels + Mod_Q2BSP_LoadModels(&header->lumps[Q2LUMP_MODELS]); +} + +static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents); +static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents); + +static void Mod_Q3BSP_LoadEntities(lump_t *l) +{ + const char *data; + char key[128], value[MAX_INPUTLINE]; + float v[3]; + loadmodel->brushq3.num_lightgrid_cellsize[0] = 64; + loadmodel->brushq3.num_lightgrid_cellsize[1] = 64; + loadmodel->brushq3.num_lightgrid_cellsize[2] = 128; + if (!l->filelen) + return; + loadmodel->brush.entities = (char *)Mem_Alloc(loadmodel->mempool, l->filelen + 1); + memcpy(loadmodel->brush.entities, mod_base + l->fileofs, l->filelen); + loadmodel->brush.entities[l->filelen] = 0; + data = loadmodel->brush.entities; + // some Q3 maps override the lightgrid_cellsize with a worldspawn key + // VorteX: q3map2 FS-R generates tangentspace deluxemaps for q3bsp and sets 'deluxeMaps' key + loadmodel->brushq3.deluxemapping = false; + if (data && COM_ParseToken_Simple(&data, false, false) && com_token[0] == '{') + { + while (1) + { + if (!COM_ParseToken_Simple(&data, false, false)) + break; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strlcpy(key, com_token + 1, sizeof(key)); + else + strlcpy(key, com_token, sizeof(key)); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + if (!COM_ParseToken_Simple(&data, false, false)) + break; // error + strlcpy(value, com_token, sizeof(value)); + if (!strcasecmp("gridsize", key)) // this one is case insensitive to 100% match q3map2 + { +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif +#if 0 + if (sscanf(value, "%f %f %f", &v[0], &v[1], &v[2]) == 3 && v[0] != 0 && v[1] != 0 && v[2] != 0) + VectorCopy(v, loadmodel->brushq3.num_lightgrid_cellsize); +#else + VectorSet(v, 64, 64, 128); + if(sscanf(value, "%f %f %f", &v[0], &v[1], &v[2]) != 3) + Con_Printf("Mod_Q3BSP_LoadEntities: funny gridsize \"%s\" in %s, interpreting as \"%f %f %f\" to match q3map2's parsing\n", value, loadmodel->name, v[0], v[1], v[2]); + if (v[0] != 0 && v[1] != 0 && v[2] != 0) + VectorCopy(v, loadmodel->brushq3.num_lightgrid_cellsize); +#endif + } + else if (!strcmp("deluxeMaps", key)) + { + if (!strcmp(com_token, "1")) + { + loadmodel->brushq3.deluxemapping = true; + loadmodel->brushq3.deluxemapping_modelspace = true; + } + else if (!strcmp(com_token, "2")) + { + loadmodel->brushq3.deluxemapping = true; + loadmodel->brushq3.deluxemapping_modelspace = false; + } + } + } + } +} + +static void Mod_Q3BSP_LoadTextures(lump_t *l) +{ + q3dtexture_t *in; + texture_t *out; + int i, count; + + in = (q3dtexture_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadTextures: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (texture_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->data_textures = out; + loadmodel->num_textures = count; + loadmodel->num_texturesperskin = loadmodel->num_textures; + + for (i = 0;i < count;i++) + { + strlcpy (out[i].name, in[i].name, sizeof (out[i].name)); + out[i].surfaceflags = LittleLong(in[i].surfaceflags); + out[i].supercontents = Mod_Q3BSP_SuperContentsFromNativeContents(loadmodel, LittleLong(in[i].contents)); + Mod_LoadTextureFromQ3Shader(out + i, out[i].name, true, true, TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS); + // restore the surfaceflags and supercontents + out[i].surfaceflags = LittleLong(in[i].surfaceflags); + out[i].supercontents = Mod_Q3BSP_SuperContentsFromNativeContents(loadmodel, LittleLong(in[i].contents)); + } +} + +static void Mod_Q3BSP_LoadPlanes(lump_t *l) +{ + q3dplane_t *in; + mplane_t *out; + int i, count; + + in = (q3dplane_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadPlanes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mplane_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_planes = out; + loadmodel->brush.num_planes = count; + + for (i = 0;i < count;i++, in++, out++) + { + out->normal[0] = LittleFloat(in->normal[0]); + out->normal[1] = LittleFloat(in->normal[1]); + out->normal[2] = LittleFloat(in->normal[2]); + out->dist = LittleFloat(in->dist); + PlaneClassify(out); + } +} + +static void Mod_Q3BSP_LoadBrushSides(lump_t *l) +{ + q3dbrushside_t *in; + q3mbrushside_t *out; + int i, n, count; + + in = (q3dbrushside_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_brushsides = out; + loadmodel->brush.num_brushsides = count; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(in->planeindex); + if (n < 0 || n >= loadmodel->brush.num_planes) + Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); + out->plane = loadmodel->brush.data_planes + n; + n = LittleLong(in->textureindex); + if (n < 0 || n >= loadmodel->num_textures) + Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); + out->texture = loadmodel->data_textures + n; + } +} + +static void Mod_Q3BSP_LoadBrushSides_IG(lump_t *l) +{ + q3dbrushside_ig_t *in; + q3mbrushside_t *out; + int i, n, count; + + in = (q3dbrushside_ig_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_brushsides = out; + loadmodel->brush.num_brushsides = count; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(in->planeindex); + if (n < 0 || n >= loadmodel->brush.num_planes) + Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); + out->plane = loadmodel->brush.data_planes + n; + n = LittleLong(in->textureindex); + if (n < 0 || n >= loadmodel->num_textures) + Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); + out->texture = loadmodel->data_textures + n; + } +} + +static void Mod_Q3BSP_LoadBrushes(lump_t *l) +{ + q3dbrush_t *in; + q3mbrush_t *out; + int i, j, n, c, count, maxplanes, q3surfaceflags; + colplanef_t *planes; + + in = (q3dbrush_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadBrushes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3mbrush_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_brushes = out; + loadmodel->brush.num_brushes = count; + + maxplanes = 0; + planes = NULL; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(in->firstbrushside); + c = LittleLong(in->numbrushsides); + if (n < 0 || n + c > loadmodel->brush.num_brushsides) + Host_Error("Mod_Q3BSP_LoadBrushes: invalid brushside range %i : %i (%i brushsides)", n, n + c, loadmodel->brush.num_brushsides); + out->firstbrushside = loadmodel->brush.data_brushsides + n; + out->numbrushsides = c; + n = LittleLong(in->textureindex); + if (n < 0 || n >= loadmodel->num_textures) + Host_Error("Mod_Q3BSP_LoadBrushes: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); + out->texture = loadmodel->data_textures + n; + + // make a list of mplane_t structs to construct a colbrush from + if (maxplanes < out->numbrushsides) + { + maxplanes = out->numbrushsides; + if (planes) + Mem_Free(planes); + planes = (colplanef_t *)Mem_Alloc(tempmempool, sizeof(colplanef_t) * maxplanes); + } + q3surfaceflags = 0; + for (j = 0;j < out->numbrushsides;j++) + { + VectorCopy(out->firstbrushside[j].plane->normal, planes[j].normal); + planes[j].dist = out->firstbrushside[j].plane->dist; + planes[j].q3surfaceflags = out->firstbrushside[j].texture->surfaceflags; + planes[j].texture = out->firstbrushside[j].texture; + q3surfaceflags |= planes[j].q3surfaceflags; + } + // make the colbrush from the planes + out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents, q3surfaceflags, out->texture, true); + + // this whole loop can take a while (e.g. on redstarrepublic4) + CL_KeepaliveMessage(false); + } + if (planes) + Mem_Free(planes); +} + +static void Mod_Q3BSP_LoadEffects(lump_t *l) +{ + q3deffect_t *in; + q3deffect_t *out; + int i, n, count; + + in = (q3deffect_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadEffects: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3deffect_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brushq3.data_effects = out; + loadmodel->brushq3.num_effects = count; + + for (i = 0;i < count;i++, in++, out++) + { + strlcpy (out->shadername, in->shadername, sizeof (out->shadername)); + n = LittleLong(in->brushindex); + if (n >= loadmodel->brush.num_brushes) + { + Con_Printf("Mod_Q3BSP_LoadEffects: invalid brushindex %i (%i brushes), setting to -1\n", n, loadmodel->brush.num_brushes); + n = -1; + } + out->brushindex = n; + out->unknown = LittleLong(in->unknown); + } +} + +static void Mod_Q3BSP_LoadVertices(lump_t *l) +{ + q3dvertex_t *in; + int i, count; + + in = (q3dvertex_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadVertices: funny lump size in %s",loadmodel->name); + loadmodel->brushq3.num_vertices = count = l->filelen / sizeof(*in); + loadmodel->brushq3.data_vertex3f = (float *)Mem_Alloc(loadmodel->mempool, count * (sizeof(float) * (3 + 3 + 2 + 2 + 4))); + loadmodel->brushq3.data_normal3f = loadmodel->brushq3.data_vertex3f + count * 3; + loadmodel->brushq3.data_texcoordtexture2f = loadmodel->brushq3.data_normal3f + count * 3; + loadmodel->brushq3.data_texcoordlightmap2f = loadmodel->brushq3.data_texcoordtexture2f + count * 2; + loadmodel->brushq3.data_color4f = loadmodel->brushq3.data_texcoordlightmap2f + count * 2; + + for (i = 0;i < count;i++, in++) + { + loadmodel->brushq3.data_vertex3f[i * 3 + 0] = LittleFloat(in->origin3f[0]); + loadmodel->brushq3.data_vertex3f[i * 3 + 1] = LittleFloat(in->origin3f[1]); + loadmodel->brushq3.data_vertex3f[i * 3 + 2] = LittleFloat(in->origin3f[2]); + loadmodel->brushq3.data_normal3f[i * 3 + 0] = LittleFloat(in->normal3f[0]); + loadmodel->brushq3.data_normal3f[i * 3 + 1] = LittleFloat(in->normal3f[1]); + loadmodel->brushq3.data_normal3f[i * 3 + 2] = LittleFloat(in->normal3f[2]); + loadmodel->brushq3.data_texcoordtexture2f[i * 2 + 0] = LittleFloat(in->texcoord2f[0]); + loadmodel->brushq3.data_texcoordtexture2f[i * 2 + 1] = LittleFloat(in->texcoord2f[1]); + loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 0] = LittleFloat(in->lightmap2f[0]); + loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 1] = LittleFloat(in->lightmap2f[1]); + // svector/tvector are calculated later in face loading + loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f); + loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f); + loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f); + loadmodel->brushq3.data_color4f[i * 4 + 3] = in->color4ub[3] * (1.0f / 255.0f); + if(in->color4ub[0] != 255 || in->color4ub[1] != 255 || in->color4ub[2] != 255) + loadmodel->lit = true; + } +} + +static void Mod_Q3BSP_LoadTriangles(lump_t *l) +{ + int *in; + int *out; + int i, count; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(int[3])) + Host_Error("Mod_Q3BSP_LoadTriangles: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + + if(!loadmodel->brushq3.num_vertices) + { + if (count) + Con_Printf("Mod_Q3BSP_LoadTriangles: %s has triangles but no vertexes, broken compiler, ignoring problem\n", loadmodel->name); + loadmodel->brushq3.num_triangles = 0; + return; + } + + out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + loadmodel->brushq3.num_triangles = count / 3; + loadmodel->brushq3.data_element3i = out; + + for (i = 0;i < count;i++, in++, out++) + { + *out = LittleLong(*in); + if (*out < 0 || *out >= loadmodel->brushq3.num_vertices) + { + Con_Printf("Mod_Q3BSP_LoadTriangles: invalid vertexindex %i (%i vertices), setting to 0\n", *out, loadmodel->brushq3.num_vertices); + *out = 0; + } + } +} + +static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump) +{ + q3dlightmap_t *input_pointer; + int i; + int j; + int k; + int count; + int powerx; + int powery; + int powerxy; + int powerdxy; + int endlightmap; + int mergegoal; + int lightmapindex; + int realcount; + int realindex; + int mergedwidth; + int mergedheight; + int mergedcolumns; + int mergedrows; + int mergedrowsxcolumns; + int size; + int bytesperpixel; + int rgbmap[3]; + unsigned char *c; + unsigned char *mergedpixels; + unsigned char *mergeddeluxepixels; + unsigned char *mergebuf; + char mapname[MAX_QPATH]; + qboolean external; + unsigned char *inpixels[10000]; // max count q3map2 can output (it uses 4 digits) + + // defaults for q3bsp + size = 128; + bytesperpixel = 3; + rgbmap[0] = 2; + rgbmap[1] = 1; + rgbmap[2] = 0; + external = false; + loadmodel->brushq3.lightmapsize = 128; + + if (cls.state == ca_dedicated) + return; + + if(mod_q3bsp_nolightmaps.integer) + { + return; + } + else if(l->filelen) + { + // prefer internal LMs for compatibility (a BSP contains no info on whether external LMs exist) + if (developer_loading.integer) + Con_Printf("Using internal lightmaps\n"); + input_pointer = (q3dlightmap_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*input_pointer)) + Host_Error("Mod_Q3BSP_LoadLightmaps: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*input_pointer); + for(i = 0; i < count; ++i) + inpixels[i] = input_pointer[i].rgb; + } + else + { + // no internal lightmaps + // try external lightmaps + if (developer_loading.integer) + Con_Printf("Using external lightmaps\n"); + FS_StripExtension(loadmodel->name, mapname, sizeof(mapname)); + inpixels[0] = loadimagepixelsbgra(va("%s/lm_%04d", mapname, 0), false, false, false, NULL); + if(!inpixels[0]) + return; + + // using EXTERNAL lightmaps instead + if(image_width != (int) CeilPowerOf2(image_width) || image_width != image_height) + { + Mem_Free(inpixels[0]); + Host_Error("Mod_Q3BSP_LoadLightmaps: invalid external lightmap size in %s",loadmodel->name); + } + + size = image_width; + bytesperpixel = 4; + rgbmap[0] = 0; + rgbmap[1] = 1; + rgbmap[2] = 2; + external = true; + + for(count = 1; ; ++count) + { + inpixels[count] = loadimagepixelsbgra(va("%s/lm_%04d", mapname, count), false, false, false, NULL); + if(!inpixels[count]) + break; // we got all of them + if(image_width != size || image_height != size) + { + Mem_Free(inpixels[count]); + inpixels[count] = NULL; + Con_Printf("Mod_Q3BSP_LoadLightmaps: mismatched lightmap size in %s - external lightmap %s/lm_%04d does not match earlier ones\n", loadmodel->name, mapname, count); + break; + } + } + } + + loadmodel->brushq3.lightmapsize = size; + loadmodel->brushq3.num_originallightmaps = count; + + // now check the surfaces to see if any of them index an odd numbered + // lightmap, if so this is not a deluxemapped bsp file + // + // also check what lightmaps are actually used, because q3map2 sometimes + // (always?) makes an unused one at the end, which + // q3map2 sometimes (or always?) makes a second blank lightmap for no + // reason when only one lightmap is used, which can throw off the + // deluxemapping detection method, so check 2-lightmap bsp's specifically + // to see if the second lightmap is blank, if so it is not deluxemapped. + // VorteX: autodetect only if previous attempt to find "deluxeMaps" key + // in Mod_Q3BSP_LoadEntities was failed + if (!loadmodel->brushq3.deluxemapping) + { + loadmodel->brushq3.deluxemapping = !(count & 1); + loadmodel->brushq3.deluxemapping_modelspace = true; + endlightmap = 0; + if (loadmodel->brushq3.deluxemapping) + { + int facecount = faceslump->filelen / sizeof(q3dface_t); + q3dface_t *faces = (q3dface_t *)(mod_base + faceslump->fileofs); + for (i = 0;i < facecount;i++) + { + j = LittleLong(faces[i].lightmapindex); + if (j >= 0) + { + endlightmap = max(endlightmap, j + 1); + if ((j & 1) || j + 1 >= count) + { + loadmodel->brushq3.deluxemapping = false; + break; + } + } + } + } + + // q3map2 sometimes (or always?) makes a second blank lightmap for no + // reason when only one lightmap is used, which can throw off the + // deluxemapping detection method, so check 2-lightmap bsp's specifically + // to see if the second lightmap is blank, if so it is not deluxemapped. + // + // further research has shown q3map2 sometimes creates a deluxemap and two + // blank lightmaps, which must be handled properly as well + if (endlightmap == 1 && count > 1) + { + c = inpixels[1]; + for (i = 0;i < size*size;i++) + { + if (c[bytesperpixel*i + rgbmap[0]]) + break; + if (c[bytesperpixel*i + rgbmap[1]]) + break; + if (c[bytesperpixel*i + rgbmap[2]]) + break; + } + if (i == size*size) + { + // all pixels in the unused lightmap were black... + loadmodel->brushq3.deluxemapping = false; + } + } + } + + Con_DPrintf("%s is %sdeluxemapped\n", loadmodel->name, loadmodel->brushq3.deluxemapping ? "" : "not "); + + // figure out what the most reasonable merge power is within limits + + // find the appropriate NxN dimensions to merge to, to avoid wasted space + realcount = count >> (int)loadmodel->brushq3.deluxemapping; + + // figure out how big the merged texture has to be + mergegoal = 128< size && mergegoal * mergegoal / 4 >= size * size * realcount) + mergegoal /= 2; + mergedwidth = mergegoal; + mergedheight = mergegoal; + // choose non-square size (2x1 aspect) if only half the space is used; + // this really only happens when the entire set fits in one texture, if + // there are multiple textures, we don't worry about shrinking the last + // one to fit, because the driver prefers the same texture size on + // consecutive draw calls... + if (mergedwidth * mergedheight / 2 >= size*size*realcount) + mergedheight /= 2; + + loadmodel->brushq3.num_lightmapmergedwidthpower = 0; + loadmodel->brushq3.num_lightmapmergedheightpower = 0; + while (mergedwidth > size<brushq3.num_lightmapmergedwidthpower) + loadmodel->brushq3.num_lightmapmergedwidthpower++; + while (mergedheight > size<brushq3.num_lightmapmergedheightpower) + loadmodel->brushq3.num_lightmapmergedheightpower++; + loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower = loadmodel->brushq3.num_lightmapmergedwidthpower + loadmodel->brushq3.num_lightmapmergedheightpower + (loadmodel->brushq3.deluxemapping ? 1 : 0); + + powerx = loadmodel->brushq3.num_lightmapmergedwidthpower; + powery = loadmodel->brushq3.num_lightmapmergedheightpower; + powerxy = powerx+powery; + powerdxy = loadmodel->brushq3.deluxemapping + powerxy; + + mergedcolumns = 1 << powerx; + mergedrows = 1 << powery; + mergedrowsxcolumns = 1 << powerxy; + + loadmodel->brushq3.num_mergedlightmaps = (realcount + (1 << powerxy) - 1) >> powerxy; + loadmodel->brushq3.data_lightmaps = (rtexture_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); + if (loadmodel->brushq3.deluxemapping) + loadmodel->brushq3.data_deluxemaps = (rtexture_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); + + // allocate a texture pool if we need it + if (loadmodel->texturepool == NULL && cls.state != ca_dedicated) + loadmodel->texturepool = R_AllocTexturePool(); + + mergedpixels = (unsigned char *) Mem_Alloc(tempmempool, mergedwidth * mergedheight * 4); + mergeddeluxepixels = loadmodel->brushq3.deluxemapping ? (unsigned char *) Mem_Alloc(tempmempool, mergedwidth * mergedheight * 4) : NULL; + for (i = 0;i < count;i++) + { + // figure out which merged lightmap texture this fits into + realindex = i >> (int)loadmodel->brushq3.deluxemapping; + lightmapindex = i >> powerdxy; + + // choose the destination address + mergebuf = (loadmodel->brushq3.deluxemapping && (i & 1)) ? mergeddeluxepixels : mergedpixels; + mergebuf += 4 * (realindex & (mergedcolumns-1))*size + 4 * ((realindex >> powerx) & (mergedrows-1))*mergedwidth*size; + if ((i & 1) == 0 || !loadmodel->brushq3.deluxemapping) + Con_Printf("copying original lightmap %i (%ix%i) to %i (at %i,%i)\n", i, size, size, lightmapindex, (realindex & (mergedcolumns-1))*size, ((realindex >> powerx) & (mergedrows-1))*size); + + // convert pixels from RGB or BGRA while copying them into the destination rectangle + for (j = 0;j < size;j++) + for (k = 0;k < size;k++) + { + mergebuf[(j*mergedwidth+k)*4+0] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[0]]; + mergebuf[(j*mergedwidth+k)*4+1] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[1]]; + mergebuf[(j*mergedwidth+k)*4+2] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[2]]; + mergebuf[(j*mergedwidth+k)*4+3] = 255; + } + + // upload texture if this was the last tile being written to the texture + if (((realindex + 1) & (mergedrowsxcolumns - 1)) == 0 || (realindex + 1) == realcount) + { + if (loadmodel->brushq3.deluxemapping && (i & 1)) + loadmodel->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("deluxemap%04i", lightmapindex), mergedwidth, mergedheight, mergeddeluxepixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bspdeluxemaps.integer ? TEXF_COMPRESS : 0), -1, NULL); + else + loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%04i", lightmapindex), mergedwidth, mergedheight, mergedpixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), -1, NULL); + } + } + + if (mergeddeluxepixels) + Mem_Free(mergeddeluxepixels); + Mem_Free(mergedpixels); + if(external) + { + for(i = 0; i < count; ++i) + Mem_Free(inpixels[i]); + } +} + +static void Mod_Q3BSP_BuildBBoxes(const int *element3i, int num_triangles, const float *vertex3f, float **collisionbbox6f, int *collisionstride, int stride) +{ + int j, k, cnt, tri; + float *mins, *maxs; + const float *vert; + *collisionstride = stride; + if(stride > 0) + { + cnt = (num_triangles + stride - 1) / stride; + *collisionbbox6f = (float *) Mem_Alloc(loadmodel->mempool, sizeof(float[6]) * cnt); + for(j = 0; j < cnt; ++j) + { + mins = &((*collisionbbox6f)[6 * j + 0]); + maxs = &((*collisionbbox6f)[6 * j + 3]); + for(k = 0; k < stride; ++k) + { + tri = j * stride + k; + if(tri >= num_triangles) + break; + vert = &(vertex3f[element3i[3 * tri + 0] * 3]); + if(!k || vert[0] < mins[0]) mins[0] = vert[0]; + if(!k || vert[1] < mins[1]) mins[1] = vert[1]; + if(!k || vert[2] < mins[2]) mins[2] = vert[2]; + if(!k || vert[0] > maxs[0]) maxs[0] = vert[0]; + if(!k || vert[1] > maxs[1]) maxs[1] = vert[1]; + if(!k || vert[2] > maxs[2]) maxs[2] = vert[2]; + vert = &(vertex3f[element3i[3 * tri + 1] * 3]); + if(vert[0] < mins[0]) mins[0] = vert[0]; + if(vert[1] < mins[1]) mins[1] = vert[1]; + if(vert[2] < mins[2]) mins[2] = vert[2]; + if(vert[0] > maxs[0]) maxs[0] = vert[0]; + if(vert[1] > maxs[1]) maxs[1] = vert[1]; + if(vert[2] > maxs[2]) maxs[2] = vert[2]; + vert = &(vertex3f[element3i[3 * tri + 2] * 3]); + if(vert[0] < mins[0]) mins[0] = vert[0]; + if(vert[1] < mins[1]) mins[1] = vert[1]; + if(vert[2] < mins[2]) mins[2] = vert[2]; + if(vert[0] > maxs[0]) maxs[0] = vert[0]; + if(vert[1] > maxs[1]) maxs[1] = vert[1]; + if(vert[2] > maxs[2]) maxs[2] = vert[2]; + } + } + } + else + *collisionbbox6f = NULL; +} + +typedef struct patchtess_s +{ + patchinfo_t info; + + // Auxiliary data used only by patch loading code in Mod_Q3BSP_LoadFaces + int surface_id; + float lodgroup[6]; + float *originalvertex3f; +} patchtess_t; + +#define PATCHTESS_SAME_LODGROUP(a,b) \ + ( \ + (a).lodgroup[0] == (b).lodgroup[0] && \ + (a).lodgroup[1] == (b).lodgroup[1] && \ + (a).lodgroup[2] == (b).lodgroup[2] && \ + (a).lodgroup[3] == (b).lodgroup[3] && \ + (a).lodgroup[4] == (b).lodgroup[4] && \ + (a).lodgroup[5] == (b).lodgroup[5] \ + ) + +static void Mod_Q3BSP_LoadFaces(lump_t *l) +{ + q3dface_t *in, *oldin; + msurface_t *out, *oldout; + int i, oldi, j, n, count, invalidelements, patchsize[2], finalwidth, finalheight, xtess, ytess, finalvertices, finaltriangles, firstvertex, firstelement, type, oldnumtriangles, oldnumtriangles2, meshvertices, meshtriangles, collisionvertices, collisiontriangles, numvertices, numtriangles, cxtess, cytess; + float lightmaptcbase[2], lightmaptcscale[2]; + //int *originalelement3i; + //int *originalneighbor3i; + float *originalvertex3f; + //float *originalsvector3f; + //float *originaltvector3f; + float *originalnormal3f; + float *originalcolor4f; + float *originaltexcoordtexture2f; + float *originaltexcoordlightmap2f; + float *surfacecollisionvertex3f; + int *surfacecollisionelement3i; + float *v; + patchtess_t *patchtess = NULL; + int patchtesscount = 0; + qboolean again; + + in = (q3dface_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (msurface_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->data_surfaces = out; + loadmodel->num_surfaces = count; + + if(count > 0) + patchtess = (patchtess_t*) Mem_Alloc(tempmempool, count * sizeof(*patchtess)); + + i = 0; + oldi = i; + oldin = in; + oldout = out; + meshvertices = 0; + meshtriangles = 0; + for (;i < count;i++, in++, out++) + { + // check face type first + type = LittleLong(in->type); + if (type != Q3FACETYPE_FLAT + && type != Q3FACETYPE_PATCH + && type != Q3FACETYPE_MESH + && type != Q3FACETYPE_FLARE) + { + Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i: unknown face type %i\n", i, type); + continue; + } + + n = LittleLong(in->textureindex); + if (n < 0 || n >= loadmodel->num_textures) + { + Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i: invalid textureindex %i (%i textures)\n", i, n, loadmodel->num_textures); + continue; + } + out->texture = loadmodel->data_textures + n; + n = LittleLong(in->effectindex); + if (n < -1 || n >= loadmodel->brushq3.num_effects) + { + if (developer_extra.integer) + Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid effectindex %i (%i effects)\n", i, out->texture->name, n, loadmodel->brushq3.num_effects); + n = -1; + } + if (n == -1) + out->effect = NULL; + else + out->effect = loadmodel->brushq3.data_effects + n; + + if (cls.state != ca_dedicated) + { + out->lightmaptexture = NULL; + out->deluxemaptexture = r_texture_blanknormalmap; + n = LittleLong(in->lightmapindex); + if (n < 0) + n = -1; + else if (n >= loadmodel->brushq3.num_originallightmaps) + { + if(loadmodel->brushq3.num_originallightmaps != 0) + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid lightmapindex %i (%i lightmaps)\n", i, out->texture->name, n, loadmodel->brushq3.num_originallightmaps); + n = -1; + } + else + { + out->lightmaptexture = loadmodel->brushq3.data_lightmaps[n >> loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower]; + if (loadmodel->brushq3.deluxemapping) + out->deluxemaptexture = loadmodel->brushq3.data_deluxemaps[n >> loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower]; + loadmodel->lit = true; + } + } + + firstvertex = LittleLong(in->firstvertex); + numvertices = LittleLong(in->numvertices); + firstelement = LittleLong(in->firstelement); + numtriangles = LittleLong(in->numelements) / 3; + if (numtriangles * 3 != LittleLong(in->numelements)) + { + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): numelements %i is not a multiple of 3\n", i, out->texture->name, LittleLong(in->numelements)); + continue; + } + if (firstvertex < 0 || firstvertex + numvertices > loadmodel->brushq3.num_vertices) + { + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid vertex range %i : %i (%i vertices)\n", i, out->texture->name, firstvertex, firstvertex + numvertices, loadmodel->brushq3.num_vertices); + continue; + } + if (firstelement < 0 || firstelement + numtriangles * 3 > loadmodel->brushq3.num_triangles * 3) + { + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid element range %i : %i (%i elements)\n", i, out->texture->name, firstelement, firstelement + numtriangles * 3, loadmodel->brushq3.num_triangles * 3); + continue; + } + switch(type) + { + case Q3FACETYPE_FLAT: + case Q3FACETYPE_MESH: + // no processing necessary + break; + case Q3FACETYPE_PATCH: + patchsize[0] = LittleLong(in->specific.patch.patchsize[0]); + patchsize[1] = LittleLong(in->specific.patch.patchsize[1]); + if (numvertices != (patchsize[0] * patchsize[1]) || patchsize[0] < 3 || patchsize[1] < 3 || !(patchsize[0] & 1) || !(patchsize[1] & 1) || patchsize[0] * patchsize[1] >= min(r_subdivisions_maxvertices.integer, r_subdivisions_collision_maxvertices.integer)) + { + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid patchsize %ix%i\n", i, out->texture->name, patchsize[0], patchsize[1]); + continue; + } + originalvertex3f = loadmodel->brushq3.data_vertex3f + firstvertex * 3; + + // convert patch to Q3FACETYPE_MESH + xtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value); + ytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value); + // bound to user settings + xtess = bound(r_subdivisions_mintess.integer, xtess, r_subdivisions_maxtess.integer); + ytess = bound(r_subdivisions_mintess.integer, ytess, r_subdivisions_maxtess.integer); + // bound to sanity settings + xtess = bound(0, xtess, 1024); + ytess = bound(0, ytess, 1024); + + // lower quality collision patches! Same procedure as before, but different cvars + // convert patch to Q3FACETYPE_MESH + cxtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value); + cytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value); + // bound to user settings + cxtess = bound(r_subdivisions_collision_mintess.integer, cxtess, r_subdivisions_collision_maxtess.integer); + cytess = bound(r_subdivisions_collision_mintess.integer, cytess, r_subdivisions_collision_maxtess.integer); + // bound to sanity settings + cxtess = bound(0, cxtess, 1024); + cytess = bound(0, cytess, 1024); + + // store it for the LOD grouping step + patchtess[patchtesscount].info.xsize = patchsize[0]; + patchtess[patchtesscount].info.ysize = patchsize[1]; + patchtess[patchtesscount].info.lods[PATCH_LOD_VISUAL].xtess = xtess; + patchtess[patchtesscount].info.lods[PATCH_LOD_VISUAL].ytess = ytess; + patchtess[patchtesscount].info.lods[PATCH_LOD_COLLISION].xtess = cxtess; + patchtess[patchtesscount].info.lods[PATCH_LOD_COLLISION].ytess = cytess; + + patchtess[patchtesscount].surface_id = i; + patchtess[patchtesscount].lodgroup[0] = LittleFloat(in->specific.patch.mins[0]); + patchtess[patchtesscount].lodgroup[1] = LittleFloat(in->specific.patch.mins[1]); + patchtess[patchtesscount].lodgroup[2] = LittleFloat(in->specific.patch.mins[2]); + patchtess[patchtesscount].lodgroup[3] = LittleFloat(in->specific.patch.maxs[0]); + patchtess[patchtesscount].lodgroup[4] = LittleFloat(in->specific.patch.maxs[1]); + patchtess[patchtesscount].lodgroup[5] = LittleFloat(in->specific.patch.maxs[2]); + patchtess[patchtesscount].originalvertex3f = originalvertex3f; + ++patchtesscount; + break; + case Q3FACETYPE_FLARE: + if (developer_extra.integer) + Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): Q3FACETYPE_FLARE not supported (yet)\n", i, out->texture->name); + // don't render it + continue; + } + out->num_vertices = numvertices; + out->num_triangles = numtriangles; + meshvertices += out->num_vertices; + meshtriangles += out->num_triangles; + } + + // Fix patches tesselations so that they make no seams + do + { + again = false; + for(i = 0; i < patchtesscount; ++i) + { + for(j = i+1; j < patchtesscount; ++j) + { + if (!PATCHTESS_SAME_LODGROUP(patchtess[i], patchtess[j])) + continue; + + if (Q3PatchAdjustTesselation(3, &patchtess[i].info, patchtess[i].originalvertex3f, &patchtess[j].info, patchtess[j].originalvertex3f) ) + again = true; + } + } + } + while (again); + + // Calculate resulting number of triangles + collisionvertices = 0; + collisiontriangles = 0; + for(i = 0; i < patchtesscount; ++i) + { + finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_VISUAL].xtess); + finalheight = Q3PatchDimForTess(patchtess[i].info.ysize,patchtess[i].info.lods[PATCH_LOD_VISUAL].ytess); + numvertices = finalwidth * finalheight; + numtriangles = (finalwidth - 1) * (finalheight - 1) * 2; + + oldout[patchtess[i].surface_id].num_vertices = numvertices; + oldout[patchtess[i].surface_id].num_triangles = numtriangles; + meshvertices += oldout[patchtess[i].surface_id].num_vertices; + meshtriangles += oldout[patchtess[i].surface_id].num_triangles; + + finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_COLLISION].xtess); + finalheight = Q3PatchDimForTess(patchtess[i].info.ysize,patchtess[i].info.lods[PATCH_LOD_COLLISION].ytess); + numvertices = finalwidth * finalheight; + numtriangles = (finalwidth - 1) * (finalheight - 1) * 2; + + oldout[patchtess[i].surface_id].num_collisionvertices = numvertices; + oldout[patchtess[i].surface_id].num_collisiontriangles = numtriangles; + collisionvertices += oldout[patchtess[i].surface_id].num_collisionvertices; + collisiontriangles += oldout[patchtess[i].surface_id].num_collisiontriangles; + } + + i = oldi; + in = oldin; + out = oldout; + Mod_AllocSurfMesh(loadmodel->mempool, meshvertices, meshtriangles, false, true, false); + if (collisiontriangles) + { + loadmodel->brush.data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, collisionvertices * sizeof(float[3])); + loadmodel->brush.data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, collisiontriangles * sizeof(int[3])); + } + meshvertices = 0; + meshtriangles = 0; + collisionvertices = 0; + collisiontriangles = 0; + for (;i < count && meshvertices + out->num_vertices <= loadmodel->surfmesh.num_vertices;i++, in++, out++) + { + if (out->num_vertices < 3 || out->num_triangles < 1) + continue; + + type = LittleLong(in->type); + firstvertex = LittleLong(in->firstvertex); + firstelement = LittleLong(in->firstelement); + out->num_firstvertex = meshvertices; + out->num_firsttriangle = meshtriangles; + out->num_firstcollisiontriangle = collisiontriangles; + switch(type) + { + case Q3FACETYPE_FLAT: + case Q3FACETYPE_MESH: + // no processing necessary, except for lightmap merging + for (j = 0;j < out->num_vertices;j++) + { + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 0] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 0]; + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 1] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 1]; + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 2] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 2]; + (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 0] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 0]; + (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 1] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 1]; + (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 2] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 2]; + (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex)[j * 2 + 0] = loadmodel->brushq3.data_texcoordtexture2f[(firstvertex + j) * 2 + 0]; + (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex)[j * 2 + 1] = loadmodel->brushq3.data_texcoordtexture2f[(firstvertex + j) * 2 + 1]; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex)[j * 2 + 0] = loadmodel->brushq3.data_texcoordlightmap2f[(firstvertex + j) * 2 + 0]; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex)[j * 2 + 1] = loadmodel->brushq3.data_texcoordlightmap2f[(firstvertex + j) * 2 + 1]; + (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 0] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 0]; + (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 1] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 1]; + (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 2] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 2]; + (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 3] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 3]; + } + for (j = 0;j < out->num_triangles*3;j++) + (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] = loadmodel->brushq3.data_element3i[firstelement + j] + out->num_firstvertex; + break; + case Q3FACETYPE_PATCH: + patchsize[0] = LittleLong(in->specific.patch.patchsize[0]); + patchsize[1] = LittleLong(in->specific.patch.patchsize[1]); + originalvertex3f = loadmodel->brushq3.data_vertex3f + firstvertex * 3; + originalnormal3f = loadmodel->brushq3.data_normal3f + firstvertex * 3; + originaltexcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f + firstvertex * 2; + originaltexcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f + firstvertex * 2; + originalcolor4f = loadmodel->brushq3.data_color4f + firstvertex * 4; + + xtess = ytess = cxtess = cytess = -1; + for(j = 0; j < patchtesscount; ++j) + if(patchtess[j].surface_id == i) + { + xtess = patchtess[j].info.lods[PATCH_LOD_VISUAL].xtess; + ytess = patchtess[j].info.lods[PATCH_LOD_VISUAL].ytess; + cxtess = patchtess[j].info.lods[PATCH_LOD_COLLISION].xtess; + cytess = patchtess[j].info.lods[PATCH_LOD_COLLISION].ytess; + break; + } + if(xtess == -1) + { + Con_Printf("ERROR: patch %d isn't preprocessed?!?\n", i); + xtess = ytess = cxtess = cytess = 0; + } + + finalwidth = Q3PatchDimForTess(patchsize[0],xtess); //((patchsize[0] - 1) * xtess) + 1; + finalheight = Q3PatchDimForTess(patchsize[1],ytess); //((patchsize[1] - 1) * ytess) + 1; + finalvertices = finalwidth * finalheight; + oldnumtriangles = finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2; + type = Q3FACETYPE_MESH; + // generate geometry + // (note: normals are skipped because they get recalculated) + Q3PatchTesselateFloat(3, sizeof(float[3]), (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, xtess, ytess); + Q3PatchTesselateFloat(3, sizeof(float[3]), (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[3]), originalnormal3f, xtess, ytess); + Q3PatchTesselateFloat(2, sizeof(float[2]), (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[2]), originaltexcoordtexture2f, xtess, ytess); + Q3PatchTesselateFloat(2, sizeof(float[2]), (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[2]), originaltexcoordlightmap2f, xtess, ytess); + Q3PatchTesselateFloat(4, sizeof(float[4]), (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[4]), originalcolor4f, xtess, ytess); + Q3PatchTriangleElements((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), finalwidth, finalheight, out->num_firstvertex); + + out->num_triangles = Mod_RemoveDegenerateTriangles(out->num_triangles, (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), loadmodel->surfmesh.data_vertex3f); + + if (developer_extra.integer) + { + if (out->num_triangles < finaltriangles) + Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles, %i degenerate triangles removed (leaving %i)\n", patchsize[0], patchsize[1], out->num_vertices, finaltriangles, finaltriangles - out->num_triangles, out->num_triangles); + else + Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles\n", patchsize[0], patchsize[1], out->num_vertices, out->num_triangles); + } + // q3map does not put in collision brushes for curves... ugh + // build the lower quality collision geometry + finalwidth = Q3PatchDimForTess(patchsize[0],cxtess); //((patchsize[0] - 1) * cxtess) + 1; + finalheight = Q3PatchDimForTess(patchsize[1],cytess); //((patchsize[1] - 1) * cytess) + 1; + finalvertices = finalwidth * finalheight; + oldnumtriangles2 = finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2; + + // legacy collision geometry implementation + out->deprecatedq3data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[3]) * finalvertices); + out->deprecatedq3data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * finaltriangles); + out->num_collisionvertices = finalvertices; + out->num_collisiontriangles = finaltriangles; + Q3PatchTesselateFloat(3, sizeof(float[3]), out->deprecatedq3data_collisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess); + Q3PatchTriangleElements(out->deprecatedq3data_collisionelement3i, finalwidth, finalheight, 0); + + //Mod_SnapVertices(3, out->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), 0.25); + Mod_SnapVertices(3, finalvertices, out->deprecatedq3data_collisionvertex3f, 1); + + out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(finaltriangles, out->deprecatedq3data_collisionelement3i, out->deprecatedq3data_collisionelement3i, out->deprecatedq3data_collisionvertex3f); + + // now optimize the collision mesh by finding triangle bboxes... + Mod_Q3BSP_BuildBBoxes(out->deprecatedq3data_collisionelement3i, out->num_collisiontriangles, out->deprecatedq3data_collisionvertex3f, &out->deprecatedq3data_collisionbbox6f, &out->deprecatedq3num_collisionbboxstride, mod_q3bsp_curves_collisions_stride.integer); + Mod_Q3BSP_BuildBBoxes(loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle, out->num_triangles, loadmodel->surfmesh.data_vertex3f, &out->deprecatedq3data_bbox6f, &out->deprecatedq3num_bboxstride, mod_q3bsp_curves_stride.integer); + + // store collision geometry for BIH collision tree + surfacecollisionvertex3f = loadmodel->brush.data_collisionvertex3f + collisionvertices * 3; + surfacecollisionelement3i = loadmodel->brush.data_collisionelement3i + collisiontriangles * 3; + Q3PatchTesselateFloat(3, sizeof(float[3]), surfacecollisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess); + Q3PatchTriangleElements(surfacecollisionelement3i, finalwidth, finalheight, collisionvertices); + Mod_SnapVertices(3, finalvertices, surfacecollisionvertex3f, 1); +#if 1 + // remove this once the legacy code is removed + { + int nc = out->num_collisiontriangles; +#endif + out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(finaltriangles, surfacecollisionelement3i, surfacecollisionelement3i, loadmodel->brush.data_collisionvertex3f); +#if 1 + if(nc != out->num_collisiontriangles) + { + Con_Printf("number of collision triangles differs between BIH and BSP. FAIL.\n"); + } + } +#endif + + if (developer_extra.integer) + Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve became %i:%i vertices / %i:%i triangles (%i:%i degenerate)\n", patchsize[0], patchsize[1], out->num_vertices, out->num_collisionvertices, oldnumtriangles, oldnumtriangles2, oldnumtriangles - out->num_triangles, oldnumtriangles2 - out->num_collisiontriangles); + + collisionvertices += finalvertices; + collisiontriangles += out->num_collisiontriangles; + break; + default: + break; + } + meshvertices += out->num_vertices; + meshtriangles += out->num_triangles; + for (j = 0, invalidelements = 0;j < out->num_triangles * 3;j++) + if ((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] < out->num_firstvertex || (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] >= out->num_firstvertex + out->num_vertices) + invalidelements++; + if (invalidelements) + { + Con_Printf("Mod_Q3BSP_LoadFaces: Warning: face #%i has %i invalid elements, type = %i, texture->name = \"%s\", texture->surfaceflags = %i, firstvertex = %i, numvertices = %i, firstelement = %i, numelements = %i, elements list:\n", i, invalidelements, type, out->texture->name, out->texture->surfaceflags, firstvertex, out->num_vertices, firstelement, out->num_triangles * 3); + for (j = 0;j < out->num_triangles * 3;j++) + { + Con_Printf(" %i", (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] - out->num_firstvertex); + if ((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] < out->num_firstvertex || (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] >= out->num_firstvertex + out->num_vertices) + (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] = out->num_firstvertex; + } + Con_Print("\n"); + } + // calculate a bounding box + VectorClear(out->mins); + VectorClear(out->maxs); + if (out->num_vertices) + { + if (cls.state != ca_dedicated && out->lightmaptexture) + { + // figure out which part of the merged lightmap this fits into + int lightmapindex = LittleLong(in->lightmapindex) >> (loadmodel->brushq3.deluxemapping ? 1 : 0); + int mergewidth = R_TextureWidth(out->lightmaptexture) / loadmodel->brushq3.lightmapsize; + int mergeheight = R_TextureHeight(out->lightmaptexture) / loadmodel->brushq3.lightmapsize; + lightmapindex &= mergewidth * mergeheight - 1; + lightmaptcscale[0] = 1.0f / mergewidth; + lightmaptcscale[1] = 1.0f / mergeheight; + lightmaptcbase[0] = (lightmapindex % mergewidth) * lightmaptcscale[0]; + lightmaptcbase[1] = (lightmapindex / mergewidth) * lightmaptcscale[1]; + // modify the lightmap texcoords to match this region of the merged lightmap + for (j = 0, v = loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex;j < out->num_vertices;j++, v += 2) + { + v[0] = v[0] * lightmaptcscale[0] + lightmaptcbase[0]; + v[1] = v[1] * lightmaptcscale[1] + lightmaptcbase[1]; + } + } + VectorCopy((loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), out->mins); + VectorCopy((loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), out->maxs); + for (j = 1, v = (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex) + 3;j < out->num_vertices;j++, v += 3) + { + out->mins[0] = min(out->mins[0], v[0]); + out->maxs[0] = max(out->maxs[0], v[0]); + out->mins[1] = min(out->mins[1], v[1]); + out->maxs[1] = max(out->maxs[1], v[1]); + out->mins[2] = min(out->mins[2], v[2]); + out->maxs[2] = max(out->maxs[2], v[2]); + } + out->mins[0] -= 1.0f; + out->mins[1] -= 1.0f; + out->mins[2] -= 1.0f; + out->maxs[0] += 1.0f; + out->maxs[1] += 1.0f; + out->maxs[2] += 1.0f; + } + // set lightmap styles for consistency with q1bsp + //out->lightmapinfo->styles[0] = 0; + //out->lightmapinfo->styles[1] = 255; + //out->lightmapinfo->styles[2] = 255; + //out->lightmapinfo->styles[3] = 255; + } + + i = oldi; + out = oldout; + for (;i < count;i++, out++) + { + if(out->num_vertices && out->num_triangles) + continue; + if(out->num_vertices == 0) + { + Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s) has no vertices, ignoring\n", i, out->texture ? out->texture->name : "(none)"); + if(out->num_triangles == 0) + Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s) has no triangles, ignoring\n", i, out->texture ? out->texture->name : "(none)"); + } + else if(out->num_triangles == 0) + Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s, near %f %f %f) has no triangles, ignoring\n", i, out->texture ? out->texture->name : "(none)", + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[0 * 3 + 0], + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[1 * 3 + 0], + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[2 * 3 + 0]); + } + + // for per pixel lighting + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + + // generate ushort elements array if possible + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + + // free the no longer needed vertex data + loadmodel->brushq3.num_vertices = 0; + if (loadmodel->brushq3.data_vertex3f) + Mem_Free(loadmodel->brushq3.data_vertex3f); + loadmodel->brushq3.data_vertex3f = NULL; + loadmodel->brushq3.data_normal3f = NULL; + loadmodel->brushq3.data_texcoordtexture2f = NULL; + loadmodel->brushq3.data_texcoordlightmap2f = NULL; + loadmodel->brushq3.data_color4f = NULL; + // free the no longer needed triangle data + loadmodel->brushq3.num_triangles = 0; + if (loadmodel->brushq3.data_element3i) + Mem_Free(loadmodel->brushq3.data_element3i); + loadmodel->brushq3.data_element3i = NULL; + + if(patchtess) + Mem_Free(patchtess); +} + +static void Mod_Q3BSP_LoadModels(lump_t *l) +{ + q3dmodel_t *in; + q3dmodel_t *out; + int i, j, n, c, count; + + in = (q3dmodel_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadModels: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3dmodel_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brushq3.data_models = out; + loadmodel->brushq3.num_models = count; + + for (i = 0;i < count;i++, in++, out++) + { + for (j = 0;j < 3;j++) + { + out->mins[j] = LittleFloat(in->mins[j]); + out->maxs[j] = LittleFloat(in->maxs[j]); + } + n = LittleLong(in->firstface); + c = LittleLong(in->numfaces); + if (n < 0 || n + c > loadmodel->num_surfaces) + Host_Error("Mod_Q3BSP_LoadModels: invalid face range %i : %i (%i faces)", n, n + c, loadmodel->num_surfaces); + out->firstface = n; + out->numfaces = c; + n = LittleLong(in->firstbrush); + c = LittleLong(in->numbrushes); + if (n < 0 || n + c > loadmodel->brush.num_brushes) + Host_Error("Mod_Q3BSP_LoadModels: invalid brush range %i : %i (%i brushes)", n, n + c, loadmodel->brush.num_brushes); + out->firstbrush = n; + out->numbrushes = c; + } +} + +static void Mod_Q3BSP_LoadLeafBrushes(lump_t *l) +{ + int *in; + int *out; + int i, n, count; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadLeafBrushes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_leafbrushes = out; + loadmodel->brush.num_leafbrushes = count; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(*in); + if (n < 0 || n >= loadmodel->brush.num_brushes) + Host_Error("Mod_Q3BSP_LoadLeafBrushes: invalid brush index %i (%i brushes)", n, loadmodel->brush.num_brushes); + *out = n; + } +} + +static void Mod_Q3BSP_LoadLeafFaces(lump_t *l) +{ + int *in; + int *out; + int i, n, count; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadLeafFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_leafsurfaces = out; + loadmodel->brush.num_leafsurfaces = count; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(*in); + if (n < 0 || n >= loadmodel->num_surfaces) + Host_Error("Mod_Q3BSP_LoadLeafFaces: invalid face index %i (%i faces)", n, loadmodel->num_surfaces); + *out = n; + } +} + +static void Mod_Q3BSP_LoadLeafs(lump_t *l) +{ + q3dleaf_t *in; + mleaf_t *out; + int i, j, n, c, count; + + in = (q3dleaf_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadLeafs: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_leafs = out; + loadmodel->brush.num_leafs = count; + + for (i = 0;i < count;i++, in++, out++) + { + out->parent = NULL; + out->plane = NULL; + out->clusterindex = LittleLong(in->clusterindex); + out->areaindex = LittleLong(in->areaindex); + for (j = 0;j < 3;j++) + { + // yes the mins/maxs are ints + out->mins[j] = LittleLong(in->mins[j]) - 1; + out->maxs[j] = LittleLong(in->maxs[j]) + 1; + } + n = LittleLong(in->firstleafface); + c = LittleLong(in->numleaffaces); + if (n < 0 || n + c > loadmodel->brush.num_leafsurfaces) + Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafsurface range %i : %i (%i leafsurfaces)", n, n + c, loadmodel->brush.num_leafsurfaces); + out->firstleafsurface = loadmodel->brush.data_leafsurfaces + n; + out->numleafsurfaces = c; + n = LittleLong(in->firstleafbrush); + c = LittleLong(in->numleafbrushes); + if (n < 0 || n + c > loadmodel->brush.num_leafbrushes) + Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafbrush range %i : %i (%i leafbrushes)", n, n + c, loadmodel->brush.num_leafbrushes); + out->firstleafbrush = loadmodel->brush.data_leafbrushes + n; + out->numleafbrushes = c; + } +} + +static void Mod_Q3BSP_LoadNodes(lump_t *l) +{ + q3dnode_t *in; + mnode_t *out; + int i, j, n, count; + + in = (q3dnode_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadNodes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + if (count == 0) + Host_Error("Mod_Q3BSP_LoadNodes: missing BSP tree in %s",loadmodel->name); + out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_nodes = out; + loadmodel->brush.num_nodes = count; + + for (i = 0;i < count;i++, in++, out++) + { + out->parent = NULL; + n = LittleLong(in->planeindex); + if (n < 0 || n >= loadmodel->brush.num_planes) + Host_Error("Mod_Q3BSP_LoadNodes: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); + out->plane = loadmodel->brush.data_planes + n; + for (j = 0;j < 2;j++) + { + n = LittleLong(in->childrenindex[j]); + if (n >= 0) + { + if (n >= loadmodel->brush.num_nodes) + Host_Error("Mod_Q3BSP_LoadNodes: invalid child node index %i (%i nodes)", n, loadmodel->brush.num_nodes); + out->children[j] = loadmodel->brush.data_nodes + n; + } + else + { + n = -1 - n; + if (n >= loadmodel->brush.num_leafs) + Host_Error("Mod_Q3BSP_LoadNodes: invalid child leaf index %i (%i leafs)", n, loadmodel->brush.num_leafs); + out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + n); + } + } + for (j = 0;j < 3;j++) + { + // yes the mins/maxs are ints + out->mins[j] = LittleLong(in->mins[j]) - 1; + out->maxs[j] = LittleLong(in->maxs[j]) + 1; + } + } + + // set the parent pointers + Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL); +} + +static void Mod_Q3BSP_LoadLightGrid(lump_t *l) +{ + q3dlightgrid_t *in; + q3dlightgrid_t *out; + int count; + + in = (q3dlightgrid_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadLightGrid: funny lump size in %s",loadmodel->name); + loadmodel->brushq3.num_lightgrid_scale[0] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[0]; + loadmodel->brushq3.num_lightgrid_scale[1] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[1]; + loadmodel->brushq3.num_lightgrid_scale[2] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[2]; + loadmodel->brushq3.num_lightgrid_imins[0] = (int)ceil(loadmodel->brushq3.data_models->mins[0] * loadmodel->brushq3.num_lightgrid_scale[0]); + loadmodel->brushq3.num_lightgrid_imins[1] = (int)ceil(loadmodel->brushq3.data_models->mins[1] * loadmodel->brushq3.num_lightgrid_scale[1]); + loadmodel->brushq3.num_lightgrid_imins[2] = (int)ceil(loadmodel->brushq3.data_models->mins[2] * loadmodel->brushq3.num_lightgrid_scale[2]); + loadmodel->brushq3.num_lightgrid_imaxs[0] = (int)floor(loadmodel->brushq3.data_models->maxs[0] * loadmodel->brushq3.num_lightgrid_scale[0]); + loadmodel->brushq3.num_lightgrid_imaxs[1] = (int)floor(loadmodel->brushq3.data_models->maxs[1] * loadmodel->brushq3.num_lightgrid_scale[1]); + loadmodel->brushq3.num_lightgrid_imaxs[2] = (int)floor(loadmodel->brushq3.data_models->maxs[2] * loadmodel->brushq3.num_lightgrid_scale[2]); + loadmodel->brushq3.num_lightgrid_isize[0] = loadmodel->brushq3.num_lightgrid_imaxs[0] - loadmodel->brushq3.num_lightgrid_imins[0] + 1; + loadmodel->brushq3.num_lightgrid_isize[1] = loadmodel->brushq3.num_lightgrid_imaxs[1] - loadmodel->brushq3.num_lightgrid_imins[1] + 1; + loadmodel->brushq3.num_lightgrid_isize[2] = loadmodel->brushq3.num_lightgrid_imaxs[2] - loadmodel->brushq3.num_lightgrid_imins[2] + 1; + count = loadmodel->brushq3.num_lightgrid_isize[0] * loadmodel->brushq3.num_lightgrid_isize[1] * loadmodel->brushq3.num_lightgrid_isize[2]; + Matrix4x4_CreateScale3(&loadmodel->brushq3.num_lightgrid_indexfromworld, loadmodel->brushq3.num_lightgrid_scale[0], loadmodel->brushq3.num_lightgrid_scale[1], loadmodel->brushq3.num_lightgrid_scale[2]); + Matrix4x4_ConcatTranslate(&loadmodel->brushq3.num_lightgrid_indexfromworld, -loadmodel->brushq3.num_lightgrid_imins[0] * loadmodel->brushq3.num_lightgrid_cellsize[0], -loadmodel->brushq3.num_lightgrid_imins[1] * loadmodel->brushq3.num_lightgrid_cellsize[1], -loadmodel->brushq3.num_lightgrid_imins[2] * loadmodel->brushq3.num_lightgrid_cellsize[2]); + + // if lump is empty there is nothing to load, we can deal with that in the LightPoint code + if (l->filelen) + { + if (l->filelen < count * (int)sizeof(*in)) + { + Con_Printf("Mod_Q3BSP_LoadLightGrid: invalid lightgrid lump size %i bytes, should be %i bytes (%ix%ix%i)", l->filelen, (int)(count * sizeof(*in)), loadmodel->brushq3.num_lightgrid_isize[0], loadmodel->brushq3.num_lightgrid_isize[1], loadmodel->brushq3.num_lightgrid_isize[2]); + return; // ignore the grid if we cannot understand it + } + if (l->filelen != count * (int)sizeof(*in)) + Con_Printf("Mod_Q3BSP_LoadLightGrid: Warning: calculated lightgrid size %i bytes does not match lump size %i\n", (int)(count * sizeof(*in)), l->filelen); + out = (q3dlightgrid_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + loadmodel->brushq3.data_lightgrid = out; + loadmodel->brushq3.num_lightgrid = count; + // no swapping or validation necessary + memcpy(out, in, count * (int)sizeof(*out)); + } +} + +static void Mod_Q3BSP_LoadPVS(lump_t *l) +{ + q3dpvs_t *in; + int totalchains; + + if (l->filelen == 0) + { + int i; + // unvised maps often have cluster indices even without pvs, so check + // leafs to find real number of clusters + loadmodel->brush.num_pvsclusters = 1; + for (i = 0;i < loadmodel->brush.num_leafs;i++) + loadmodel->brush.num_pvsclusters = max(loadmodel->brush.num_pvsclusters, loadmodel->brush.data_leafs[i].clusterindex + 1); + + // create clusters + loadmodel->brush.num_pvsclusterbytes = (loadmodel->brush.num_pvsclusters + 7) / 8; + totalchains = loadmodel->brush.num_pvsclusterbytes * loadmodel->brush.num_pvsclusters; + loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, totalchains); + memset(loadmodel->brush.data_pvsclusters, 0xFF, totalchains); + return; + } + + in = (q3dpvs_t *)(mod_base + l->fileofs); + if (l->filelen < 9) + Host_Error("Mod_Q3BSP_LoadPVS: funny lump size in %s",loadmodel->name); + + loadmodel->brush.num_pvsclusters = LittleLong(in->numclusters); + loadmodel->brush.num_pvsclusterbytes = LittleLong(in->chainlength); + if (loadmodel->brush.num_pvsclusterbytes < ((loadmodel->brush.num_pvsclusters + 7) / 8)) + Host_Error("Mod_Q3BSP_LoadPVS: (chainlength = %i) < ((numclusters = %i) + 7) / 8", loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.num_pvsclusters); + totalchains = loadmodel->brush.num_pvsclusterbytes * loadmodel->brush.num_pvsclusters; + if (l->filelen < totalchains + (int)sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadPVS: lump too small ((numclusters = %i) * (chainlength = %i) + sizeof(q3dpvs_t) == %i bytes, lump is %i bytes)", loadmodel->brush.num_pvsclusters, loadmodel->brush.num_pvsclusterbytes, (int)(totalchains + sizeof(*in)), l->filelen); + + loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, totalchains); + memcpy(loadmodel->brush.data_pvsclusters, (unsigned char *)(in + 1), totalchains); +} + +static void Mod_Q3BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal) +{ + int i, j, k, index[3]; + float transformed[3], blend1, blend2, blend, stylescale = 1; + q3dlightgrid_t *a, *s; + + // scale lighting by lightstyle[0] so that darkmode in dpmod works properly + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + // LordHavoc: FIXME: is this true? + stylescale = 1; // added while render + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + stylescale = r_refdef.scene.rtlightstylevalue[0]; + break; + } + + if (!model->brushq3.num_lightgrid) + { + ambientcolor[0] = stylescale; + ambientcolor[1] = stylescale; + ambientcolor[2] = stylescale; + return; + } + + Matrix4x4_Transform(&model->brushq3.num_lightgrid_indexfromworld, p, transformed); + //Matrix4x4_Print(&model->brushq3.num_lightgrid_indexfromworld); + //Con_Printf("%f %f %f transformed %f %f %f clamped ", p[0], p[1], p[2], transformed[0], transformed[1], transformed[2]); + transformed[0] = bound(0, transformed[0], model->brushq3.num_lightgrid_isize[0] - 1); + transformed[1] = bound(0, transformed[1], model->brushq3.num_lightgrid_isize[1] - 1); + transformed[2] = bound(0, transformed[2], model->brushq3.num_lightgrid_isize[2] - 1); + index[0] = (int)floor(transformed[0]); + index[1] = (int)floor(transformed[1]); + index[2] = (int)floor(transformed[2]); + //Con_Printf("%f %f %f index %i %i %i:\n", transformed[0], transformed[1], transformed[2], index[0], index[1], index[2]); + + // now lerp the values + VectorClear(diffusenormal); + a = &model->brushq3.data_lightgrid[(index[2] * model->brushq3.num_lightgrid_isize[1] + index[1]) * model->brushq3.num_lightgrid_isize[0] + index[0]]; + for (k = 0;k < 2;k++) + { + blend1 = (k ? (transformed[2] - index[2]) : (1 - (transformed[2] - index[2]))); + if (blend1 < 0.001f || index[2] + k >= model->brushq3.num_lightgrid_isize[2]) + continue; + for (j = 0;j < 2;j++) + { + blend2 = blend1 * (j ? (transformed[1] - index[1]) : (1 - (transformed[1] - index[1]))); + if (blend2 < 0.001f || index[1] + j >= model->brushq3.num_lightgrid_isize[1]) + continue; + for (i = 0;i < 2;i++) + { + blend = blend2 * (i ? (transformed[0] - index[0]) : (1 - (transformed[0] - index[0]))) * stylescale; + if (blend < 0.001f || index[0] + i >= model->brushq3.num_lightgrid_isize[0]) + continue; + s = a + (k * model->brushq3.num_lightgrid_isize[1] + j) * model->brushq3.num_lightgrid_isize[0] + i; + VectorMA(ambientcolor, blend * (1.0f / 128.0f), s->ambientrgb, ambientcolor); + VectorMA(diffusecolor, blend * (1.0f / 128.0f), s->diffusergb, diffusecolor); + // this uses the mod_md3_sin table because the values are + // already in the 0-255 range, the 64+ bias fetches a cosine + // instead of a sine value + diffusenormal[0] += blend * (mod_md3_sin[64 + s->diffuseyaw] * mod_md3_sin[s->diffusepitch]); + diffusenormal[1] += blend * (mod_md3_sin[ s->diffuseyaw] * mod_md3_sin[s->diffusepitch]); + diffusenormal[2] += blend * (mod_md3_sin[64 + s->diffusepitch]); + //Con_Printf("blend %f: ambient %i %i %i, diffuse %i %i %i, diffusepitch %i diffuseyaw %i (%f %f, normal %f %f %f)\n", blend, s->ambientrgb[0], s->ambientrgb[1], s->ambientrgb[2], s->diffusergb[0], s->diffusergb[1], s->diffusergb[2], s->diffusepitch, s->diffuseyaw, pitch, yaw, (cos(yaw) * cospitch), (sin(yaw) * cospitch), (-sin(pitch))); + } + } + } + + // normalize the light direction before turning + VectorNormalize(diffusenormal); + //Con_Printf("result: ambient %f %f %f diffuse %f %f %f diffusenormal %f %f %f\n", ambientcolor[0], ambientcolor[1], ambientcolor[2], diffusecolor[0], diffusecolor[1], diffusecolor[2], diffusenormal[0], diffusenormal[1], diffusenormal[2]); +} + +static int Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p1[3], double p2[3]) +{ + double t1, t2; + double midf, mid[3]; + int ret, side; + + // check for empty + while (node->plane) + { + // find the point distances + mplane_t *plane = node->plane; + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + } + + if (t1 < 0) + { + if (t2 < 0) + { + node = node->children[1]; + continue; + } + side = 1; + } + else + { + if (t2 >= 0) + { + node = node->children[0]; + continue; + } + side = 0; + } + + midf = t1 / (t1 - t2); + VectorLerp(p1, midf, p2, mid); + + // recurse both sides, front side first + // return 2 if empty is followed by solid (hit something) + // do not return 2 if both are solid or both empty, + // or if start is solid and end is empty + // as these degenerate cases usually indicate the eye is in solid and + // should see the target point anyway + ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ], p1, mid); + if (ret != 0) + return ret; + ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2); + if (ret != 1) + return ret; + return 2; + } + return ((mleaf_t *)node)->clusterindex < 0; +} + +static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end) +{ + if (model->brush.submodel || mod_q3bsp_tracelineofsight_brushes.integer) + { + trace_t trace; + model->TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK); + return trace.fraction == 1; + } + else + { + double tracestart[3], traceend[3]; + VectorCopy(start, tracestart); + VectorCopy(end, traceend); + return !Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend); + } +} + +void Mod_CollisionBIH_TracePoint(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask) +{ + const bih_t *bih; + const bih_leaf_t *leaf; + const bih_node_t *node; + const colbrushf_t *brush; + int axis; + int nodenum; + int nodestackpos = 0; + int nodestack[1024]; + + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + + bih = &model->collision_bih; + if(!bih->nodes) + return; + + nodenum = bih->rootnode; + nodestack[nodestackpos++] = nodenum; + while (nodestackpos) + { + nodenum = nodestack[--nodestackpos]; + node = bih->nodes + nodenum; +#if 1 + if (!BoxesOverlap(start, start, node->mins, node->maxs)) + continue; +#endif + if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) + { + axis = node->type - BIH_SPLITX; + if (start[axis] >= node->frontmin) + nodestack[nodestackpos++] = node->front; + if (start[axis] <= node->backmax) + nodestack[nodestackpos++] = node->back; + } + else if (node->type == BIH_UNORDERED) + { + for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) + { + leaf = bih->leafs + node->children[axis]; +#if 1 + if (!BoxesOverlap(start, start, leaf->mins, leaf->maxs)) + continue; +#endif + switch(leaf->type) + { + case BIH_BRUSH: + brush = model->brush.data_brushes[leaf->itemindex].colbrushf; + Collision_TracePointBrushFloat(trace, start, brush); + break; + case BIH_COLLISIONTRIANGLE: + // collision triangle - skipped because they have no volume + break; + case BIH_RENDERTRIANGLE: + // render triangle - skipped because they have no volume + break; + } + } + } + } +} + +void Mod_CollisionBIH_TraceLineShared(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, const bih_t *bih) +{ + const bih_leaf_t *leaf; + const bih_node_t *node; + const colbrushf_t *brush; + const int *e; + const texture_t *texture; + vec3_t nodebigmins, nodebigmaxs, nodestart, nodeend, sweepnodemins, sweepnodemaxs; + vec_t d1, d2, d3, d4, f, nodestackline[1024][6]; + int axis, nodenum, nodestackpos = 0, nodestack[1024]; + + if(!bih->nodes) + return; + + if (VectorCompare(start, end)) + { + Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + return; + } + + nodenum = bih->rootnode; + + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + + // push first node + nodestackline[nodestackpos][0] = start[0]; + nodestackline[nodestackpos][1] = start[1]; + nodestackline[nodestackpos][2] = start[2]; + nodestackline[nodestackpos][3] = end[0]; + nodestackline[nodestackpos][4] = end[1]; + nodestackline[nodestackpos][5] = end[2]; + nodestack[nodestackpos++] = nodenum; + while (nodestackpos) + { + nodenum = nodestack[--nodestackpos]; + node = bih->nodes + nodenum; + VectorCopy(nodestackline[nodestackpos], nodestart); + VectorCopy(nodestackline[nodestackpos] + 3, nodeend); + sweepnodemins[0] = min(nodestart[0], nodeend[0]); sweepnodemins[1] = min(nodestart[1], nodeend[1]); sweepnodemins[2] = min(nodestart[2], nodeend[2]); sweepnodemaxs[0] = max(nodestart[0], nodeend[0]); sweepnodemaxs[1] = max(nodestart[1], nodeend[1]); sweepnodemaxs[2] = max(nodestart[2], nodeend[2]); + if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, node->mins, node->maxs)) + continue; + if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) + { + // recurse children of the split + axis = node->type - BIH_SPLITX; + d1 = node->backmax - nodestart[axis]; + d2 = node->backmax - nodeend[axis]; + d3 = nodestart[axis] - node->frontmin; + d4 = nodeend[axis] - node->frontmin; + switch((d1 < 0) | ((d2 < 0) << 1) | ((d3 < 0) << 2) | ((d4 < 0) << 3)) + { + case 0: /* >>>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 1: /* <>>> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 2: /* ><>> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 3: /* <<>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 4: /* >><> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 5: /* <><> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 6: /* ><<> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 7: /* <<<> */ f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 8: /* >>>< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 9: /* <>>< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 10: /* ><>< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 11: /* <<>< */ f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 12: /* >><< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 13: /* <><< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 14: /* ><<< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 15: /* <<<< */ break; + } + } + else if (node->type == BIH_UNORDERED) + { + // calculate sweep bounds for this node + // copy node bounds into local variables + VectorCopy(node->mins, nodebigmins); + VectorCopy(node->maxs, nodebigmaxs); + // clip line to this node bounds + axis = 0; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + axis = 1; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + axis = 2; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + // some of the line intersected the enlarged node box + // calculate sweep bounds for this node + sweepnodemins[0] = min(nodestart[0], nodeend[0]); sweepnodemins[1] = min(nodestart[1], nodeend[1]); sweepnodemins[2] = min(nodestart[2], nodeend[2]); sweepnodemaxs[0] = max(nodestart[0], nodeend[0]); sweepnodemaxs[1] = max(nodestart[1], nodeend[1]); sweepnodemaxs[2] = max(nodestart[2], nodeend[2]); + for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) + { + leaf = bih->leafs + node->children[axis]; + if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, leaf->mins, leaf->maxs)) + continue; + switch(leaf->type) + { + case BIH_BRUSH: + brush = model->brush.data_brushes[leaf->itemindex].colbrushf; + Collision_TraceLineBrushFloat(trace, start, end, brush, brush); + break; + case BIH_COLLISIONTRIANGLE: + if (!mod_q3bsp_curves_collisions.integer) + continue; + e = model->brush.data_collisionelement3i + 3*leaf->itemindex; + texture = model->data_textures + leaf->textureindex; + Collision_TraceLineTriangleFloat(trace, start, end, model->brush.data_collisionvertex3f + e[0] * 3, model->brush.data_collisionvertex3f + e[1] * 3, model->brush.data_collisionvertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); + break; + case BIH_RENDERTRIANGLE: + e = model->surfmesh.data_element3i + 3*leaf->itemindex; + texture = model->data_textures + leaf->textureindex; + Collision_TraceLineTriangleFloat(trace, start, end, model->surfmesh.data_vertex3f + e[0] * 3, model->surfmesh.data_vertex3f + e[1] * 3, model->surfmesh.data_vertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); + break; + } + } + } + } +} + +void Mod_CollisionBIH_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + if (VectorCompare(start, end)) + { + Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + return; + } + Mod_CollisionBIH_TraceLineShared(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, &model->collision_bih); +} + +void Mod_CollisionBIH_TraceBrush(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, colbrushf_t *thisbrush_start, colbrushf_t *thisbrush_end, int hitsupercontentsmask) +{ + const bih_t *bih; + const bih_leaf_t *leaf; + const bih_node_t *node; + const colbrushf_t *brush; + const int *e; + const texture_t *texture; + vec3_t start, end, startmins, startmaxs, endmins, endmaxs, mins, maxs; + vec3_t nodebigmins, nodebigmaxs, nodestart, nodeend, sweepnodemins, sweepnodemaxs; + vec_t d1, d2, d3, d4, f, nodestackline[1024][6]; + int axis, nodenum, nodestackpos = 0, nodestack[1024]; + + if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(thisbrush_start->mins, thisbrush_start->maxs) && VectorCompare(thisbrush_end->mins, thisbrush_end->maxs)) + { + if (VectorCompare(thisbrush_start->mins, thisbrush_end->mins)) + Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, thisbrush_start->mins, hitsupercontentsmask); + else + Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, thisbrush_start->mins, thisbrush_end->mins, hitsupercontentsmask); + return; + } + + bih = &model->collision_bih; + if(!bih->nodes) + return; + nodenum = bih->rootnode; + + // box trace, performed as brush trace + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + + // calculate tracebox-like parameters for efficient culling + VectorMAM(0.5f, thisbrush_start->mins, 0.5f, thisbrush_start->maxs, start); + VectorMAM(0.5f, thisbrush_end->mins, 0.5f, thisbrush_end->maxs, end); + VectorSubtract(thisbrush_start->mins, start, startmins); + VectorSubtract(thisbrush_start->maxs, start, startmaxs); + VectorSubtract(thisbrush_end->mins, end, endmins); + VectorSubtract(thisbrush_end->maxs, end, endmaxs); + mins[0] = min(startmins[0], endmins[0]); + mins[1] = min(startmins[1], endmins[1]); + mins[2] = min(startmins[2], endmins[2]); + maxs[0] = max(startmaxs[0], endmaxs[0]); + maxs[1] = max(startmaxs[1], endmaxs[1]); + maxs[2] = max(startmaxs[2], endmaxs[2]); + + // push first node + nodestackline[nodestackpos][0] = start[0]; + nodestackline[nodestackpos][1] = start[1]; + nodestackline[nodestackpos][2] = start[2]; + nodestackline[nodestackpos][3] = end[0]; + nodestackline[nodestackpos][4] = end[1]; + nodestackline[nodestackpos][5] = end[2]; + nodestack[nodestackpos++] = nodenum; + while (nodestackpos) + { + nodenum = nodestack[--nodestackpos]; + node = bih->nodes + nodenum; + VectorCopy(nodestackline[nodestackpos], nodestart); + VectorCopy(nodestackline[nodestackpos] + 3, nodeend); + sweepnodemins[0] = min(nodestart[0], nodeend[0]) + mins[0]; sweepnodemins[1] = min(nodestart[1], nodeend[1]) + mins[1]; sweepnodemins[2] = min(nodestart[2], nodeend[2]) + mins[2]; sweepnodemaxs[0] = max(nodestart[0], nodeend[0]) + maxs[0]; sweepnodemaxs[1] = max(nodestart[1], nodeend[1]) + maxs[1]; sweepnodemaxs[2] = max(nodestart[2], nodeend[2]) + maxs[2]; + if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, node->mins, node->maxs)) + continue; + if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) + { + // recurse children of the split + axis = node->type - BIH_SPLITX; + d1 = node->backmax - nodestart[axis] - mins[axis]; + d2 = node->backmax - nodeend[axis] - mins[axis]; + d3 = nodestart[axis] - node->frontmin + maxs[axis]; + d4 = nodeend[axis] - node->frontmin + maxs[axis]; + switch((d1 < 0) | ((d2 < 0) << 1) | ((d3 < 0) << 2) | ((d4 < 0) << 3)) + { + case 0: /* >>>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 1: /* <>>> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 2: /* ><>> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 3: /* <<>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 4: /* >><> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 5: /* <><> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 6: /* ><<> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 7: /* <<<> */ f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 8: /* >>>< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 9: /* <>>< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 10: /* ><>< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 11: /* <<>< */ f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 12: /* >><< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 13: /* <><< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 14: /* ><<< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 15: /* <<<< */ break; + } + } + else if (node->type == BIH_UNORDERED) + { + // calculate sweep bounds for this node + // copy node bounds into local variables and expand to get Minkowski Sum of the two shapes + VectorSubtract(node->mins, maxs, nodebigmins); + VectorSubtract(node->maxs, mins, nodebigmaxs); + // clip line to this node bounds + axis = 0; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + axis = 1; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + axis = 2; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + // some of the line intersected the enlarged node box + // calculate sweep bounds for this node + sweepnodemins[0] = min(nodestart[0], nodeend[0]) + mins[0]; sweepnodemins[1] = min(nodestart[1], nodeend[1]) + mins[1]; sweepnodemins[2] = min(nodestart[2], nodeend[2]) + mins[2]; sweepnodemaxs[0] = max(nodestart[0], nodeend[0]) + maxs[0]; sweepnodemaxs[1] = max(nodestart[1], nodeend[1]) + maxs[1]; sweepnodemaxs[2] = max(nodestart[2], nodeend[2]) + maxs[2]; + for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) + { + leaf = bih->leafs + node->children[axis]; + if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, leaf->mins, leaf->maxs)) + continue; + switch(leaf->type) + { + case BIH_BRUSH: + brush = model->brush.data_brushes[leaf->itemindex].colbrushf; + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush, brush); + break; + case BIH_COLLISIONTRIANGLE: + if (!mod_q3bsp_curves_collisions.integer) + continue; + e = model->brush.data_collisionelement3i + 3*leaf->itemindex; + texture = model->data_textures + leaf->textureindex; + Collision_TraceBrushTriangleFloat(trace, thisbrush_start, thisbrush_end, model->brush.data_collisionvertex3f + e[0] * 3, model->brush.data_collisionvertex3f + e[1] * 3, model->brush.data_collisionvertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); + break; + case BIH_RENDERTRIANGLE: + e = model->surfmesh.data_element3i + 3*leaf->itemindex; + texture = model->data_textures + leaf->textureindex; + Collision_TraceBrushTriangleFloat(trace, thisbrush_start, thisbrush_end, model->surfmesh.data_vertex3f + e[0] * 3, model->surfmesh.data_vertex3f + e[1] * 3, model->surfmesh.data_vertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); + break; + } + } + } + } +} + +void Mod_CollisionBIH_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask) +{ + colboxbrushf_t thisbrush_start, thisbrush_end; + vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; + + // box trace, performed as brush trace + VectorAdd(start, boxmins, boxstartmins); + VectorAdd(start, boxmaxs, boxstartmaxs); + VectorAdd(end, boxmins, boxendmins); + VectorAdd(end, boxmaxs, boxendmaxs); + Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); + Mod_CollisionBIH_TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask); +} + + +int Mod_CollisionBIH_PointSuperContents(struct model_s *model, int frame, const vec3_t point) +{ + trace_t trace; + Mod_CollisionBIH_TracePoint(model, NULL, NULL, &trace, point, 0); + return trace.startsupercontents; +} + +void Mod_CollisionBIH_TracePoint_Mesh(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask) +{ +#if 0 + // broken - needs to be modified to count front faces and backfaces to figure out if it is in solid + vec3_t end; + int hitsupercontents; + VectorSet(end, start[0], start[1], model->normalmins[2]); +#endif + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; +#if 0 + Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + hitsupercontents = trace->hitsupercontents; + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + trace->startsupercontents = hitsupercontents; +#endif +} + +int Mod_CollisionBIH_PointSuperContents_Mesh(struct model_s *model, int frame, const vec3_t start) +{ +#if 0 + // broken - needs to be modified to count front faces and backfaces to figure out if it is in solid + trace_t trace; + vec3_t end; + VectorSet(end, start[0], start[1], model->normalmins[2]); + memset(&trace, 0, sizeof(trace)); + trace.fraction = 1; + trace.realfraction = 1; + trace.hitsupercontentsmask = 0; + Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + return trace.hitsupercontents; +#else + return 0; +#endif +} + +static void Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t point, int markframe) +{ + int i; + mleaf_t *leaf; + colbrushf_t *brush; + // find which leaf the point is in + while (node->plane) + node = node->children[(node->plane->type < 3 ? point[node->plane->type] : DotProduct(point, node->plane->normal)) < node->plane->dist]; + // point trace the brushes + leaf = (mleaf_t *)node; + for (i = 0;i < leaf->numleafbrushes;i++) + { + brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; + if (brush && brush->markframe != markframe && BoxesOverlap(point, point, brush->mins, brush->maxs)) + { + brush->markframe = markframe; + Collision_TracePointBrushFloat(trace, point, brush); + } + } + // can't do point traces on curves (they have no thickness) +} + +static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t start, const vec3_t end, vec_t startfrac, vec_t endfrac, const vec3_t linestart, const vec3_t lineend, int markframe, const vec3_t segmentmins, const vec3_t segmentmaxs) +{ + int i, startside, endside; + float dist1, dist2, midfrac, mid[3], nodesegmentmins[3], nodesegmentmaxs[3]; + mleaf_t *leaf; + msurface_t *surface; + mplane_t *plane; + colbrushf_t *brush; + // walk the tree until we hit a leaf, recursing for any split cases + while (node->plane) + { +#if 0 + if (!BoxesOverlap(segmentmins, segmentmaxs, node->mins, node->maxs)) + return; + Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[0], start, end, startfrac, endfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); + node = node->children[1]; +#else + // abort if this part of the bsp tree can not be hit by this trace +// if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) +// return; + plane = node->plane; + // axial planes are much more common than non-axial, so an optimized + // axial case pays off here + if (plane->type < 3) + { + dist1 = start[plane->type] - plane->dist; + dist2 = end[plane->type] - plane->dist; + } + else + { + dist1 = DotProduct(start, plane->normal) - plane->dist; + dist2 = DotProduct(end, plane->normal) - plane->dist; + } + startside = dist1 < 0; + endside = dist2 < 0; + if (startside == endside) + { + // most of the time the line fragment is on one side of the plane + node = node->children[startside]; + } + else + { + // line crosses node plane, split the line + dist1 = PlaneDiff(linestart, plane); + dist2 = PlaneDiff(lineend, plane); + midfrac = dist1 / (dist1 - dist2); + VectorLerp(linestart, midfrac, lineend, mid); + // take the near side first + Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[startside], start, mid, startfrac, midfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); + // if we found an impact on the front side, don't waste time + // exploring the far side + if (midfrac <= trace->realfraction) + Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[endside], mid, end, midfrac, endfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); + return; + } +#endif + } + // abort if this part of the bsp tree can not be hit by this trace +// if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) +// return; + // hit a leaf + nodesegmentmins[0] = min(start[0], end[0]) - 1; + nodesegmentmins[1] = min(start[1], end[1]) - 1; + nodesegmentmins[2] = min(start[2], end[2]) - 1; + nodesegmentmaxs[0] = max(start[0], end[0]) + 1; + nodesegmentmaxs[1] = max(start[1], end[1]) + 1; + nodesegmentmaxs[2] = max(start[2], end[2]) + 1; + // line trace the brushes + leaf = (mleaf_t *)node; +#if 0 + if (!BoxesOverlap(segmentmins, segmentmaxs, leaf->mins, leaf->maxs)) + return; +#endif + for (i = 0;i < leaf->numleafbrushes;i++) + { + brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; + if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs)) + { + brush->markframe = markframe; + Collision_TraceLineBrushFloat(trace, linestart, lineend, brush, brush); + } + } + // can't do point traces on curves (they have no thickness) + if (leaf->containscollisionsurfaces && mod_q3bsp_curves_collisions.integer && !VectorCompare(start, end)) + { + // line trace the curves + for (i = 0;i < leaf->numleafsurfaces;i++) + { + surface = model->data_surfaces + leaf->firstleafsurface[i]; + if (surface->num_collisiontriangles && surface->deprecatedq3collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs)) + { + surface->deprecatedq3collisionmarkframe = markframe; + Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); + } + } + } +} + +static void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int markframe, const vec3_t segmentmins, const vec3_t segmentmaxs) +{ + int i; + int sides; + mleaf_t *leaf; + colbrushf_t *brush; + msurface_t *surface; + mplane_t *plane; + float nodesegmentmins[3], nodesegmentmaxs[3]; + // walk the tree until we hit a leaf, recursing for any split cases + while (node->plane) + { +#if 0 + if (!BoxesOverlap(segmentmins, segmentmaxs, node->mins, node->maxs)) + return; + Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, node->children[0], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs); + node = node->children[1]; +#else + // abort if this part of the bsp tree can not be hit by this trace +// if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) +// return; + plane = node->plane; + // axial planes are much more common than non-axial, so an optimized + // axial case pays off here + if (plane->type < 3) + { + // this is an axial plane, compare bounding box directly to it and + // recurse sides accordingly + // recurse down node sides + // use an inlined axial BoxOnPlaneSide to slightly reduce overhead + //sides = BoxOnPlaneSide(nodesegmentmins, nodesegmentmaxs, plane); + //sides = ((segmentmaxs[plane->type] >= plane->dist) | ((segmentmins[plane->type] < plane->dist) << 1)); + sides = ((segmentmaxs[plane->type] >= plane->dist) + ((segmentmins[plane->type] < plane->dist) * 2)); + } + else + { + // this is a non-axial plane, so check if the start and end boxes + // are both on one side of the plane to handle 'diagonal' cases + sides = BoxOnPlaneSide(thisbrush_start->mins, thisbrush_start->maxs, plane) | BoxOnPlaneSide(thisbrush_end->mins, thisbrush_end->maxs, plane); + } + if (sides == 3) + { + // segment crosses plane + Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, node->children[0], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs); + sides = 2; + } + // if sides == 0 then the trace itself is bogus (Not A Number values), + // in this case we simply pretend the trace hit nothing + if (sides == 0) + return; // ERROR: NAN bounding box! + // take whichever side the segment box is on + node = node->children[sides - 1]; +#endif + } + // abort if this part of the bsp tree can not be hit by this trace +// if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) +// return; + nodesegmentmins[0] = max(segmentmins[0], node->mins[0] - 1); + nodesegmentmins[1] = max(segmentmins[1], node->mins[1] - 1); + nodesegmentmins[2] = max(segmentmins[2], node->mins[2] - 1); + nodesegmentmaxs[0] = min(segmentmaxs[0], node->maxs[0] + 1); + nodesegmentmaxs[1] = min(segmentmaxs[1], node->maxs[1] + 1); + nodesegmentmaxs[2] = min(segmentmaxs[2], node->maxs[2] + 1); + // hit a leaf + leaf = (mleaf_t *)node; +#if 0 + if (!BoxesOverlap(segmentmins, segmentmaxs, leaf->mins, leaf->maxs)) + return; +#endif + for (i = 0;i < leaf->numleafbrushes;i++) + { + brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; + if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs)) + { + brush->markframe = markframe; + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush, brush); + } + } + if (leaf->containscollisionsurfaces && mod_q3bsp_curves_collisions.integer) + { + for (i = 0;i < leaf->numleafsurfaces;i++) + { + surface = model->data_surfaces + leaf->firstleafsurface[i]; + if (surface->num_collisiontriangles && surface->deprecatedq3collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs)) + { + surface->deprecatedq3collisionmarkframe = markframe; + Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); + } + } + } +} + + +static int markframe = 0; + +static void Mod_Q3BSP_TracePoint(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask) +{ + int i; + q3mbrush_t *brush; + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + if (mod_collision_bih.integer) + Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + else if (model->brush.submodel) + { + for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) + if (brush->colbrushf) + Collision_TracePointBrushFloat(trace, start, brush->colbrushf); + } + else + Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, ++markframe); +} + +static void Mod_Q3BSP_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + int i; + float segmentmins[3], segmentmaxs[3]; + msurface_t *surface; + q3mbrush_t *brush; + + if (VectorCompare(start, end)) + { + Mod_Q3BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + return; + } + + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + segmentmins[0] = min(start[0], end[0]) - 1; + segmentmins[1] = min(start[1], end[1]) - 1; + segmentmins[2] = min(start[2], end[2]) - 1; + segmentmaxs[0] = max(start[0], end[0]) + 1; + segmentmaxs[1] = max(start[1], end[1]) + 1; + segmentmaxs[2] = max(start[2], end[2]) + 1; + if (mod_collision_bih.integer) + Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + else if (model->brush.submodel) + { + for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) + if (brush->colbrushf && BoxesOverlap(segmentmins, segmentmaxs, brush->colbrushf->mins, brush->colbrushf->maxs)) + Collision_TraceLineBrushFloat(trace, start, end, brush->colbrushf, brush->colbrushf); + if (mod_q3bsp_curves_collisions.integer) + for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++) + if (surface->num_collisiontriangles && BoxesOverlap(segmentmins, segmentmaxs, surface->mins, surface->maxs)) + Collision_TraceLineTriangleMeshFloat(trace, start, end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); + } + else + Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, end, 0, 1, start, end, ++markframe, segmentmins, segmentmaxs); +} + +void Mod_Q3BSP_TraceBrush(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, colbrushf_t *start, colbrushf_t *end, int hitsupercontentsmask) +{ + float segmentmins[3], segmentmaxs[3]; + int i; + msurface_t *surface; + q3mbrush_t *brush; + + if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(start->mins, start->maxs) && VectorCompare(end->mins, end->maxs)) + { + if (VectorCompare(start->mins, end->mins)) + Mod_Q3BSP_TracePoint(model, frameblend, skeleton, trace, start->mins, hitsupercontentsmask); + else + Mod_Q3BSP_TraceLine(model, frameblend, skeleton, trace, start->mins, end->mins, hitsupercontentsmask); + return; + } + + // box trace, performed as brush trace + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + segmentmins[0] = min(start->mins[0], end->mins[0]); + segmentmins[1] = min(start->mins[1], end->mins[1]); + segmentmins[2] = min(start->mins[2], end->mins[2]); + segmentmaxs[0] = max(start->maxs[0], end->maxs[0]); + segmentmaxs[1] = max(start->maxs[1], end->maxs[1]); + segmentmaxs[2] = max(start->maxs[2], end->maxs[2]); + if (mod_collision_bih.integer) + Mod_CollisionBIH_TraceBrush(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + else if (model->brush.submodel) + { + for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) + if (brush->colbrushf && BoxesOverlap(segmentmins, segmentmaxs, brush->colbrushf->mins, brush->colbrushf->maxs)) + Collision_TraceBrushBrushFloat(trace, start, end, brush->colbrushf, brush->colbrushf); + if (mod_q3bsp_curves_collisions.integer) + for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++) + if (surface->num_collisiontriangles && BoxesOverlap(segmentmins, segmentmaxs, surface->mins, surface->maxs)) + Collision_TraceBrushTriangleMeshFloat(trace, start, end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); + } + else + Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, end, ++markframe, segmentmins, segmentmaxs); +} + +static void Mod_Q3BSP_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask) +{ + colboxbrushf_t thisbrush_start, thisbrush_end; + vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; + + // box trace, performed as brush trace + VectorAdd(start, boxmins, boxstartmins); + VectorAdd(start, boxmaxs, boxstartmaxs); + VectorAdd(end, boxmins, boxendmins); + VectorAdd(end, boxmaxs, boxendmaxs); + Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); + Mod_Q3BSP_TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask); +} + +static int Mod_Q3BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point) +{ + int i; + int supercontents = 0; + q3mbrush_t *brush; + if (mod_collision_bih.integer) + { + trace_t trace; + Mod_Q3BSP_TracePoint(model, NULL, NULL, &trace, point, 0); + supercontents = trace.startsupercontents; + } + // test if the point is inside each brush + else if (model->brush.submodel) + { + // submodels are effectively one leaf + for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) + if (brush->colbrushf && Collision_PointInsideBrushFloat(point, brush->colbrushf)) + supercontents |= brush->colbrushf->supercontents; + } + else + { + mnode_t *node = model->brush.data_nodes; + mleaf_t *leaf; + // find which leaf the point is in + while (node->plane) + node = node->children[(node->plane->type < 3 ? point[node->plane->type] : DotProduct(point, node->plane->normal)) < node->plane->dist]; + leaf = (mleaf_t *)node; + // now check the brushes in the leaf + for (i = 0;i < leaf->numleafbrushes;i++) + { + brush = model->brush.data_brushes + leaf->firstleafbrush[i]; + if (brush->colbrushf && Collision_PointInsideBrushFloat(point, brush->colbrushf)) + supercontents |= brush->colbrushf->supercontents; + } + } + return supercontents; +} + +void Mod_CollisionBIH_TraceLineAgainstSurfaces(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + Mod_CollisionBIH_TraceLineShared(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, &model->render_bih); +} + + +bih_t *Mod_MakeCollisionBIH(dp_model_t *model, qboolean userendersurfaces, bih_t *out) +{ + int j; + int bihnumleafs; + int bihmaxnodes; + int brushindex; + int triangleindex; + int bihleafindex; + int nummodelbrushes = model->nummodelbrushes; + int nummodelsurfaces = model->nummodelsurfaces; + const int *e; + const int *collisionelement3i; + const float *collisionvertex3f; + const int *renderelement3i; + const float *rendervertex3f; + bih_leaf_t *bihleafs; + bih_node_t *bihnodes; + int *temp_leafsort; + int *temp_leafsortscratch; + const msurface_t *surface; + const q3mbrush_t *brush; + + // find out how many BIH leaf nodes we need + bihnumleafs = 0; + if (userendersurfaces) + { + for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) + bihnumleafs += surface->num_triangles; + } + else + { + for (brushindex = 0, brush = model->brush.data_brushes + brushindex+model->firstmodelbrush;brushindex < nummodelbrushes;brushindex++, brush++) + if (brush->colbrushf) + bihnumleafs++; + for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) + { + if (surface->texture->basematerialflags & MATERIALFLAG_MESHCOLLISIONS) + bihnumleafs += surface->num_triangles + surface->num_collisiontriangles; + else + bihnumleafs += surface->num_collisiontriangles; + } + } + + if (!bihnumleafs) + return NULL; + + // allocate the memory for the BIH leaf nodes + bihleafs = (bih_leaf_t *)Mem_Alloc(loadmodel->mempool, sizeof(bih_leaf_t) * bihnumleafs); + + // now populate the BIH leaf nodes + bihleafindex = 0; + + // add render surfaces + renderelement3i = model->surfmesh.data_element3i; + rendervertex3f = model->surfmesh.data_vertex3f; + for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) + { + for (triangleindex = 0, e = renderelement3i + 3*surface->num_firsttriangle;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + if (!userendersurfaces && !(surface->texture->basematerialflags & MATERIALFLAG_MESHCOLLISIONS)) + continue; + bihleafs[bihleafindex].type = BIH_RENDERTRIANGLE; + bihleafs[bihleafindex].textureindex = surface->texture - model->data_textures; + bihleafs[bihleafindex].surfaceindex = surface - model->data_surfaces; + bihleafs[bihleafindex].itemindex = triangleindex+surface->num_firsttriangle; + bihleafs[bihleafindex].mins[0] = min(rendervertex3f[3*e[0]+0], min(rendervertex3f[3*e[1]+0], rendervertex3f[3*e[2]+0])) - 1; + bihleafs[bihleafindex].mins[1] = min(rendervertex3f[3*e[0]+1], min(rendervertex3f[3*e[1]+1], rendervertex3f[3*e[2]+1])) - 1; + bihleafs[bihleafindex].mins[2] = min(rendervertex3f[3*e[0]+2], min(rendervertex3f[3*e[1]+2], rendervertex3f[3*e[2]+2])) - 1; + bihleafs[bihleafindex].maxs[0] = max(rendervertex3f[3*e[0]+0], max(rendervertex3f[3*e[1]+0], rendervertex3f[3*e[2]+0])) + 1; + bihleafs[bihleafindex].maxs[1] = max(rendervertex3f[3*e[0]+1], max(rendervertex3f[3*e[1]+1], rendervertex3f[3*e[2]+1])) + 1; + bihleafs[bihleafindex].maxs[2] = max(rendervertex3f[3*e[0]+2], max(rendervertex3f[3*e[1]+2], rendervertex3f[3*e[2]+2])) + 1; + bihleafindex++; + } + } + + if (!userendersurfaces) + { + // add collision brushes + for (brushindex = 0, brush = model->brush.data_brushes + brushindex+model->firstmodelbrush;brushindex < nummodelbrushes;brushindex++, brush++) + { + if (!brush->colbrushf) + continue; + bihleafs[bihleafindex].type = BIH_BRUSH; + bihleafs[bihleafindex].textureindex = brush->texture - model->data_textures; + bihleafs[bihleafindex].surfaceindex = -1; + bihleafs[bihleafindex].itemindex = brushindex+model->firstmodelbrush; + VectorCopy(brush->colbrushf->mins, bihleafs[bihleafindex].mins); + VectorCopy(brush->colbrushf->maxs, bihleafs[bihleafindex].maxs); + bihleafindex++; + } + + // add collision surfaces + collisionelement3i = model->brush.data_collisionelement3i; + collisionvertex3f = model->brush.data_collisionvertex3f; + for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) + { + for (triangleindex = 0, e = collisionelement3i + 3*surface->num_firstcollisiontriangle;triangleindex < surface->num_collisiontriangles;triangleindex++, e += 3) + { + bihleafs[bihleafindex].type = BIH_COLLISIONTRIANGLE; + bihleafs[bihleafindex].textureindex = surface->texture - model->data_textures; + bihleafs[bihleafindex].surfaceindex = surface - model->data_surfaces; + bihleafs[bihleafindex].itemindex = triangleindex+surface->num_firstcollisiontriangle; + bihleafs[bihleafindex].mins[0] = min(collisionvertex3f[3*e[0]+0], min(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0])) - 1; + bihleafs[bihleafindex].mins[1] = min(collisionvertex3f[3*e[0]+1], min(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1])) - 1; + bihleafs[bihleafindex].mins[2] = min(collisionvertex3f[3*e[0]+2], min(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2])) - 1; + bihleafs[bihleafindex].maxs[0] = max(collisionvertex3f[3*e[0]+0], max(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0])) + 1; + bihleafs[bihleafindex].maxs[1] = max(collisionvertex3f[3*e[0]+1], max(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1])) + 1; + bihleafs[bihleafindex].maxs[2] = max(collisionvertex3f[3*e[0]+2], max(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2])) + 1; + bihleafindex++; + } + } + } + + // allocate buffers for the produced and temporary data + bihmaxnodes = bihnumleafs + 1; + bihnodes = (bih_node_t *)Mem_Alloc(loadmodel->mempool, sizeof(bih_node_t) * bihmaxnodes); + temp_leafsort = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int) * bihnumleafs * 2); + temp_leafsortscratch = temp_leafsort + bihnumleafs; + + // now build it + BIH_Build(out, bihnumleafs, bihleafs, bihmaxnodes, bihnodes, temp_leafsort, temp_leafsortscratch); + + // we're done with the temporary data + Mem_Free(temp_leafsort); + + // resize the BIH nodes array if it over-allocated + if (out->maxnodes > out->numnodes) + { + out->maxnodes = out->numnodes; + out->nodes = (bih_node_t *)Mem_Realloc(loadmodel->mempool, out->nodes, out->numnodes * sizeof(bih_node_t)); + } + + return out; +} + +static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents) +{ + int supercontents = 0; + if (nativecontents & CONTENTSQ3_SOLID) + supercontents |= SUPERCONTENTS_SOLID; + if (nativecontents & CONTENTSQ3_WATER) + supercontents |= SUPERCONTENTS_WATER; + if (nativecontents & CONTENTSQ3_SLIME) + supercontents |= SUPERCONTENTS_SLIME; + if (nativecontents & CONTENTSQ3_LAVA) + supercontents |= SUPERCONTENTS_LAVA; + if (nativecontents & CONTENTSQ3_BODY) + supercontents |= SUPERCONTENTS_BODY; + if (nativecontents & CONTENTSQ3_CORPSE) + supercontents |= SUPERCONTENTS_CORPSE; + if (nativecontents & CONTENTSQ3_NODROP) + supercontents |= SUPERCONTENTS_NODROP; + if (nativecontents & CONTENTSQ3_PLAYERCLIP) + supercontents |= SUPERCONTENTS_PLAYERCLIP; + if (nativecontents & CONTENTSQ3_MONSTERCLIP) + supercontents |= SUPERCONTENTS_MONSTERCLIP; + if (nativecontents & CONTENTSQ3_DONOTENTER) + supercontents |= SUPERCONTENTS_DONOTENTER; + if (nativecontents & CONTENTSQ3_BOTCLIP) + supercontents |= SUPERCONTENTS_BOTCLIP; + if (!(nativecontents & CONTENTSQ3_TRANSLUCENT)) + supercontents |= SUPERCONTENTS_OPAQUE; + return supercontents; +} + +static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents) +{ + int nativecontents = 0; + if (supercontents & SUPERCONTENTS_SOLID) + nativecontents |= CONTENTSQ3_SOLID; + if (supercontents & SUPERCONTENTS_WATER) + nativecontents |= CONTENTSQ3_WATER; + if (supercontents & SUPERCONTENTS_SLIME) + nativecontents |= CONTENTSQ3_SLIME; + if (supercontents & SUPERCONTENTS_LAVA) + nativecontents |= CONTENTSQ3_LAVA; + if (supercontents & SUPERCONTENTS_BODY) + nativecontents |= CONTENTSQ3_BODY; + if (supercontents & SUPERCONTENTS_CORPSE) + nativecontents |= CONTENTSQ3_CORPSE; + if (supercontents & SUPERCONTENTS_NODROP) + nativecontents |= CONTENTSQ3_NODROP; + if (supercontents & SUPERCONTENTS_PLAYERCLIP) + nativecontents |= CONTENTSQ3_PLAYERCLIP; + if (supercontents & SUPERCONTENTS_MONSTERCLIP) + nativecontents |= CONTENTSQ3_MONSTERCLIP; + if (supercontents & SUPERCONTENTS_DONOTENTER) + nativecontents |= CONTENTSQ3_DONOTENTER; + if (supercontents & SUPERCONTENTS_BOTCLIP) + nativecontents |= CONTENTSQ3_BOTCLIP; + if (!(supercontents & SUPERCONTENTS_OPAQUE)) + nativecontents |= CONTENTSQ3_TRANSLUCENT; + return nativecontents; +} + +void Mod_Q3BSP_RecursiveFindNumLeafs(mnode_t *node) +{ + int numleafs; + while (node->plane) + { + Mod_Q3BSP_RecursiveFindNumLeafs(node->children[0]); + node = node->children[1]; + } + numleafs = ((mleaf_t *)node - loadmodel->brush.data_leafs) + 1; + if (loadmodel->brush.num_leafs < numleafs) + loadmodel->brush.num_leafs = numleafs; +} + +void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, lumps; + q3dheader_t *header; + float corner[3], yawradius, modelradius; + + mod->modeldatatypestring = "Q3BSP"; + + mod->type = mod_brushq3; + mod->numframes = 2; // although alternate textures are not supported it is annoying to complain about no such frame 1 + mod->numskins = 1; + + header = (q3dheader_t *)buffer; + if((char *) bufferend < (char *) buffer + sizeof(q3dheader_t)) + Host_Error("Mod_Q3BSP_Load: %s is smaller than its header", mod->name); + + i = LittleLong(header->version); + if (i != Q3BSPVERSION && i != Q3BSPVERSION_IG && i != Q3BSPVERSION_LIVE) + Host_Error("Mod_Q3BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q3BSPVERSION); + + mod->soundfromcenter = true; + mod->TraceBox = Mod_Q3BSP_TraceBox; + mod->TraceBrush = Mod_Q3BSP_TraceBrush; + mod->TraceLine = Mod_Q3BSP_TraceLine; + mod->TracePoint = Mod_Q3BSP_TracePoint; + mod->PointSuperContents = Mod_Q3BSP_PointSuperContents; + mod->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLine; + mod->brush.TraceLineOfSight = Mod_Q3BSP_TraceLineOfSight; + mod->brush.SuperContentsFromNativeContents = Mod_Q3BSP_SuperContentsFromNativeContents; + mod->brush.NativeContentsFromSuperContents = Mod_Q3BSP_NativeContentsFromSuperContents; + mod->brush.GetPVS = Mod_Q1BSP_GetPVS; + mod->brush.FatPVS = Mod_Q1BSP_FatPVS; + mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS; + mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS; + mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs; + mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters; + mod->brush.LightPoint = Mod_Q3BSP_LightPoint; + mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation; + mod->brush.AmbientSoundLevelsForPoint = NULL; + mod->brush.RoundUpToHullSize = NULL; + mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf; + mod->Draw = R_Q1BSP_Draw; + mod->DrawDepth = R_Q1BSP_DrawDepth; + mod->DrawDebug = R_Q1BSP_DrawDebug; + mod->DrawPrepass = R_Q1BSP_DrawPrepass; + mod->GetLightInfo = R_Q1BSP_GetLightInfo; + mod->CompileShadowMap = R_Q1BSP_CompileShadowMap; + mod->DrawShadowMap = R_Q1BSP_DrawShadowMap; + mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + mod->DrawLight = R_Q1BSP_DrawLight; + + mod_base = (unsigned char *)header; + + // swap all the lumps + header->ident = LittleLong(header->ident); + header->version = LittleLong(header->version); + lumps = (header->version == Q3BSPVERSION_LIVE) ? Q3HEADER_LUMPS_LIVE : Q3HEADER_LUMPS; + for (i = 0;i < lumps;i++) + { + j = (header->lumps[i].fileofs = LittleLong(header->lumps[i].fileofs)); + if((char *) bufferend < (char *) buffer + j) + Host_Error("Mod_Q3BSP_Load: %s has a lump that starts outside the file!", mod->name); + j += (header->lumps[i].filelen = LittleLong(header->lumps[i].filelen)); + if((char *) bufferend < (char *) buffer + j) + Host_Error("Mod_Q3BSP_Load: %s has a lump that ends outside the file!", mod->name); + } + /* + * NO, do NOT clear them! + * they contain actual data referenced by other stuff. + * Instead, before using the advertisements lump, check header->versio + * again! + * Sorry, but otherwise it breaks memory of the first lump. + for (i = lumps;i < Q3HEADER_LUMPS_MAX;i++) + { + header->lumps[i].fileofs = 0; + header->lumps[i].filelen = 0; + } + */ + + mod->brush.qw_md4sum = 0; + mod->brush.qw_md4sum2 = 0; + for (i = 0;i < lumps;i++) + { + if (i == Q3LUMP_ENTITIES) + continue; + mod->brush.qw_md4sum ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + if (i == Q3LUMP_PVS || i == Q3LUMP_LEAFS || i == Q3LUMP_NODES) + continue; + mod->brush.qw_md4sum2 ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + + // all this checksumming can take a while, so let's send keepalives here too + CL_KeepaliveMessage(false); + } + + Mod_Q3BSP_LoadEntities(&header->lumps[Q3LUMP_ENTITIES]); + Mod_Q3BSP_LoadTextures(&header->lumps[Q3LUMP_TEXTURES]); + Mod_Q3BSP_LoadPlanes(&header->lumps[Q3LUMP_PLANES]); + if (header->version == Q3BSPVERSION_IG) + Mod_Q3BSP_LoadBrushSides_IG(&header->lumps[Q3LUMP_BRUSHSIDES]); + else + Mod_Q3BSP_LoadBrushSides(&header->lumps[Q3LUMP_BRUSHSIDES]); + Mod_Q3BSP_LoadBrushes(&header->lumps[Q3LUMP_BRUSHES]); + Mod_Q3BSP_LoadEffects(&header->lumps[Q3LUMP_EFFECTS]); + Mod_Q3BSP_LoadVertices(&header->lumps[Q3LUMP_VERTICES]); + Mod_Q3BSP_LoadTriangles(&header->lumps[Q3LUMP_TRIANGLES]); + Mod_Q3BSP_LoadLightmaps(&header->lumps[Q3LUMP_LIGHTMAPS], &header->lumps[Q3LUMP_FACES]); + Mod_Q3BSP_LoadFaces(&header->lumps[Q3LUMP_FACES]); + Mod_Q3BSP_LoadModels(&header->lumps[Q3LUMP_MODELS]); + Mod_Q3BSP_LoadLeafBrushes(&header->lumps[Q3LUMP_LEAFBRUSHES]); + Mod_Q3BSP_LoadLeafFaces(&header->lumps[Q3LUMP_LEAFFACES]); + Mod_Q3BSP_LoadLeafs(&header->lumps[Q3LUMP_LEAFS]); + Mod_Q3BSP_LoadNodes(&header->lumps[Q3LUMP_NODES]); + Mod_Q3BSP_LoadLightGrid(&header->lumps[Q3LUMP_LIGHTGRID]); + Mod_Q3BSP_LoadPVS(&header->lumps[Q3LUMP_PVS]); + loadmodel->brush.numsubmodels = loadmodel->brushq3.num_models; + + // the MakePortals code works fine on the q3bsp data as well + if (mod_bsp_portalize.integer) + Mod_Q1BSP_MakePortals(); + + // FIXME: shader alpha should replace r_wateralpha support in q3bsp + loadmodel->brush.supportwateralpha = true; + + // make a single combined shadow mesh to allow optimized shadow volume creation + Mod_Q1BSP_CreateShadowMesh(loadmodel); + + loadmodel->brush.num_leafs = 0; + Mod_Q3BSP_RecursiveFindNumLeafs(loadmodel->brush.data_nodes); + + if (loadmodel->brush.numsubmodels) + loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); + + mod = loadmodel; + for (i = 0;i < loadmodel->brush.numsubmodels;i++) + { + if (i > 0) + { + char name[10]; + // duplicate the basic information + dpsnprintf(name, sizeof(name), "*%i", i); + mod = Mod_FindName(name, loadmodel->name); + // copy the base model to this one + *mod = *loadmodel; + // rename the clone back to its proper name + strlcpy(mod->name, name, sizeof(mod->name)); + mod->brush.parentmodel = loadmodel; + // textures and memory belong to the main model + mod->texturepool = NULL; + mod->mempool = NULL; + mod->brush.GetPVS = NULL; + mod->brush.FatPVS = NULL; + mod->brush.BoxTouchingPVS = NULL; + mod->brush.BoxTouchingLeafPVS = NULL; + mod->brush.BoxTouchingVisibleLeafs = NULL; + mod->brush.FindBoxClusters = NULL; + mod->brush.LightPoint = NULL; + mod->brush.AmbientSoundLevelsForPoint = NULL; + } + mod->brush.submodel = i; + if (loadmodel->brush.submodels) + loadmodel->brush.submodels[i] = mod; + + // make the model surface list (used by shadowing/lighting) + mod->firstmodelsurface = mod->brushq3.data_models[i].firstface; + mod->nummodelsurfaces = mod->brushq3.data_models[i].numfaces; + mod->firstmodelbrush = mod->brushq3.data_models[i].firstbrush; + mod->nummodelbrushes = mod->brushq3.data_models[i].numbrushes; + mod->sortedmodelsurfaces = (int *)Mem_Alloc(loadmodel->mempool, mod->nummodelsurfaces * sizeof(*mod->sortedmodelsurfaces)); + Mod_MakeSortedSurfaces(mod); + + VectorCopy(mod->brushq3.data_models[i].mins, mod->normalmins); + VectorCopy(mod->brushq3.data_models[i].maxs, mod->normalmaxs); + // enlarge the bounding box to enclose all geometry of this model, + // because q3map2 sometimes lies (mostly to affect the lightgrid), + // which can in turn mess up the farclip (as well as culling when + // outside the level - an unimportant concern) + + //printf("Editing model %d... BEFORE re-bounding: %f %f %f - %f %f %f\n", i, mod->normalmins[0], mod->normalmins[1], mod->normalmins[2], mod->normalmaxs[0], mod->normalmaxs[1], mod->normalmaxs[2]); + for (j = 0;j < mod->nummodelsurfaces;j++) + { + const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; + const float *v = mod->surfmesh.data_vertex3f + 3 * surface->num_firstvertex; + int k; + if (!surface->num_vertices) + continue; + for (k = 0;k < surface->num_vertices;k++, v += 3) + { + mod->normalmins[0] = min(mod->normalmins[0], v[0]); + mod->normalmins[1] = min(mod->normalmins[1], v[1]); + mod->normalmins[2] = min(mod->normalmins[2], v[2]); + mod->normalmaxs[0] = max(mod->normalmaxs[0], v[0]); + mod->normalmaxs[1] = max(mod->normalmaxs[1], v[1]); + mod->normalmaxs[2] = max(mod->normalmaxs[2], v[2]); + } + } + //printf("Editing model %d... AFTER re-bounding: %f %f %f - %f %f %f\n", i, mod->normalmins[0], mod->normalmins[1], mod->normalmins[2], mod->normalmaxs[0], mod->normalmaxs[1], mod->normalmaxs[2]); + corner[0] = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); + corner[1] = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); + corner[2] = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); + modelradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]+corner[2]*corner[2]); + yawradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); + mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; + mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; + mod->yawmaxs[0] = mod->yawmaxs[1] = yawradius; + mod->yawmins[0] = mod->yawmins[1] = -yawradius; + mod->yawmins[2] = mod->normalmins[2]; + mod->yawmaxs[2] = mod->normalmaxs[2]; + mod->radius = modelradius; + mod->radius2 = modelradius * modelradius; + + // this gets altered below if sky or water is used + mod->DrawSky = NULL; + mod->DrawAddWaterPlanes = NULL; + + for (j = 0;j < mod->nummodelsurfaces;j++) + if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & MATERIALFLAG_SKY) + break; + if (j < mod->nummodelsurfaces) + mod->DrawSky = R_Q1BSP_DrawSky; + + for (j = 0;j < mod->nummodelsurfaces;j++) + if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + break; + if (j < mod->nummodelsurfaces) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + + Mod_MakeCollisionBIH(mod, false, &mod->collision_bih); + Mod_MakeCollisionBIH(mod, true, &mod->render_bih); + + // generate VBOs and other shared data before cloning submodels + if (i == 0) + Mod_BuildVBOs(); + } + + Con_DPrintf("Stats for q3bsp model \"%s\": %i faces, %i nodes, %i leafs, %i clusters, %i clusterportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); +} + +void Mod_IBSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i = LittleLong(((int *)buffer)[1]); + if (i == Q3BSPVERSION || i == Q3BSPVERSION_IG || i == Q3BSPVERSION_LIVE) + Mod_Q3BSP_Load(mod,buffer, bufferend); + else if (i == Q2BSPVERSION) + Mod_Q2BSP_Load(mod,buffer, bufferend); + else + Host_Error("Mod_IBSP_Load: unknown/unsupported version %i", i); +} + +void Mod_MAP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + Host_Error("Mod_MAP_Load: not yet implemented"); +} + +typedef struct objvertex_s +{ + int nextindex; + int submodelindex; + int textureindex; + float v[3]; + float vt[2]; + float vn[3]; +} +objvertex_t; + +static unsigned char nobsp_pvs[1] = {1}; + +void Mod_OBJ_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + const char *textbase = (char *)buffer, *text = textbase; + char *s; + char *argv[512]; + char line[1024]; + char materialname[MAX_QPATH]; + int i, j, l, numvertices, firstvertex, firsttriangle, elementindex, vertexindex, surfacevertices, surfacetriangles, surfaceelements, submodelindex = 0; + int index1, index2, index3; + objvertex_t vfirst, vprev, vcurrent; + int argc; + int linelen; + int numtriangles = 0; + int maxtriangles = 0; + objvertex_t *vertices = NULL; + int linenumber = 0; + int maxtextures = 0, numtextures = 0, textureindex = 0; + int maxv = 0, numv = 1; + int maxvt = 0, numvt = 1; + int maxvn = 0, numvn = 1; + char *texturenames = NULL; + float dist, modelradius, modelyawradius, yawradius; + float *v = NULL; + float *vt = NULL; + float *vn = NULL; + float mins[3]; + float maxs[3]; + float corner[3]; + objvertex_t *thisvertex = NULL; + int vertexhashindex; + int *vertexhashtable = NULL; + objvertex_t *vertexhashdata = NULL; + objvertex_t *vdata = NULL; + int vertexhashsize = 0; + int vertexhashcount = 0; + skinfile_t *skinfiles = NULL; + unsigned char *data = NULL; + int *submodelfirstsurface; + msurface_t *surface; + msurface_t *tempsurfaces; + + memset(&vfirst, 0, sizeof(vfirst)); + memset(&vprev, 0, sizeof(vprev)); + memset(&vcurrent, 0, sizeof(vcurrent)); + + dpsnprintf(materialname, sizeof(materialname), "%s", loadmodel->name); + + loadmodel->modeldatatypestring = "OBJ"; + + loadmodel->type = mod_obj; + loadmodel->soundfromcenter = true; + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLine; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + loadmodel->brush.TraceLineOfSight = NULL; + loadmodel->brush.SuperContentsFromNativeContents = NULL; + loadmodel->brush.NativeContentsFromSuperContents = NULL; + loadmodel->brush.GetPVS = NULL; + loadmodel->brush.FatPVS = NULL; + loadmodel->brush.BoxTouchingPVS = NULL; + loadmodel->brush.BoxTouchingLeafPVS = NULL; + loadmodel->brush.BoxTouchingVisibleLeafs = NULL; + loadmodel->brush.FindBoxClusters = NULL; + loadmodel->brush.LightPoint = NULL; + loadmodel->brush.FindNonSolidLocation = NULL; + loadmodel->brush.AmbientSoundLevelsForPoint = NULL; + loadmodel->brush.RoundUpToHullSize = NULL; + loadmodel->brush.PointInLeaf = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->GetLightInfo = R_Q1BSP_GetLightInfo; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + // make skinscenes for the skins (no groups) + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + VectorClear(mins); + VectorClear(maxs); + + // we always have model 0, i.e. the first "submodel" + loadmodel->brush.numsubmodels = 1; + + // parse the OBJ text now + for(;;) + { + static char emptyarg[1] = ""; + if (!*text) + break; + linenumber++; + linelen = 0; + for (linelen = 0;text[linelen] && text[linelen] != '\r' && text[linelen] != '\n';linelen++) + line[linelen] = text[linelen]; + line[linelen] = 0; + for (argc = 0;argc < 4;argc++) + argv[argc] = emptyarg; + argc = 0; + s = line; + while (*s == ' ' || *s == '\t') + s++; + while (*s) + { + argv[argc++] = s; + while (*s > ' ') + s++; + if (!*s) + break; + *s++ = 0; + while (*s == ' ' || *s == '\t') + s++; + } + text += linelen; + if (*text == '\r') + text++; + if (*text == '\n') + text++; + if (!argc) + continue; + if (argv[0][0] == '#') + continue; + if (!strcmp(argv[0], "v")) + { + if (maxv <= numv) + { + maxv = max(maxv * 2, 1024); + v = (float *)Mem_Realloc(tempmempool, v, maxv * sizeof(float[3])); + } + v[numv*3+0] = atof(argv[1]); + v[numv*3+2] = atof(argv[2]); + v[numv*3+1] = atof(argv[3]); + numv++; + } + else if (!strcmp(argv[0], "vt")) + { + if (maxvt <= numvt) + { + maxvt = max(maxvt * 2, 1024); + vt = (float *)Mem_Realloc(tempmempool, vt, maxvt * sizeof(float[2])); + } + vt[numvt*2+0] = atof(argv[1]); + vt[numvt*2+1] = 1-atof(argv[2]); + numvt++; + } + else if (!strcmp(argv[0], "vn")) + { + if (maxvn <= numvn) + { + maxvn = max(maxvn * 2, 1024); + vn = (float *)Mem_Realloc(tempmempool, vn, maxvn * sizeof(float[3])); + } + vn[numvn*3+0] = atof(argv[1]); + vn[numvn*3+2] = atof(argv[2]); + vn[numvn*3+1] = atof(argv[3]); + numvn++; + } + else if (!strcmp(argv[0], "f")) + { + if (!numtextures) + { + if (maxtextures <= numtextures) + { + maxtextures = max(maxtextures * 2, 256); + texturenames = (char *)Mem_Realloc(loadmodel->mempool, texturenames, maxtextures * MAX_QPATH); + } + textureindex = numtextures++; + strlcpy(texturenames + textureindex*MAX_QPATH, loadmodel->name, MAX_QPATH); + } + for (j = 1;j < argc;j++) + { + index1 = atoi(argv[j]); + while(argv[j][0] && argv[j][0] != '/') + argv[j]++; + if (argv[j][0]) + argv[j]++; + index2 = atoi(argv[j]); + while(argv[j][0] && argv[j][0] != '/') + argv[j]++; + if (argv[j][0]) + argv[j]++; + index3 = atoi(argv[j]); + // negative refers to a recent vertex + // zero means not specified + // positive means an absolute vertex index + if (index1 < 0) + index1 = numv - index1; + if (index2 < 0) + index2 = numvt - index2; + if (index3 < 0) + index3 = numvn - index3; + vcurrent.nextindex = -1; + vcurrent.textureindex = textureindex; + vcurrent.submodelindex = submodelindex; + if (v && index1 >= 0 && index1 < numv) + VectorCopy(v + 3*index1, vcurrent.v); + if (vt && index2 >= 0 && index2 < numvt) + Vector2Copy(vt + 2*index2, vcurrent.vt); + if (vn && index3 >= 0 && index3 < numvn) + VectorCopy(vn + 3*index3, vcurrent.vn); + if (numtriangles == 0) + { + VectorCopy(vcurrent.v, mins); + VectorCopy(vcurrent.v, maxs); + } + else + { + mins[0] = min(mins[0], vcurrent.v[0]); + mins[1] = min(mins[1], vcurrent.v[1]); + mins[2] = min(mins[2], vcurrent.v[2]); + maxs[0] = max(maxs[0], vcurrent.v[0]); + maxs[1] = max(maxs[1], vcurrent.v[1]); + maxs[2] = max(maxs[2], vcurrent.v[2]); + } + if (j == 1) + vfirst = vcurrent; + else if (j >= 3) + { + if (maxtriangles <= numtriangles) + { + maxtriangles = max(maxtriangles * 2, 32768); + vertices = (objvertex_t*)Mem_Realloc(loadmodel->mempool, vertices, maxtriangles * sizeof(objvertex_t[3])); + } + vertices[numtriangles*3+0] = vfirst; + vertices[numtriangles*3+1] = vprev; + vertices[numtriangles*3+2] = vcurrent; + numtriangles++; + } + vprev = vcurrent; + } + } + else if (!strcmp(argv[0], "o") || !strcmp(argv[0], "g")) + { + submodelindex = atof(argv[1]); + loadmodel->brush.numsubmodels = max(submodelindex + 1, loadmodel->brush.numsubmodels); + } + else if (!strcmp(argv[0], "usemtl")) + { + for (i = 0;i < numtextures;i++) + if (!strcmp(texturenames+i*MAX_QPATH, argv[1])) + break; + if (i < numtextures) + textureindex = i; + else + { + if (maxtextures <= numtextures) + { + maxtextures = max(maxtextures * 2, 256); + texturenames = (char *)Mem_Realloc(loadmodel->mempool, texturenames, maxtextures * MAX_QPATH); + } + textureindex = numtextures++; + strlcpy(texturenames + textureindex*MAX_QPATH, argv[1], MAX_QPATH); + } + } + } + + // now that we have the OBJ data loaded as-is, we can convert it + + // copy the model bounds, then enlarge the yaw and rotated bounds according to radius + VectorCopy(mins, loadmodel->normalmins); + VectorCopy(maxs, loadmodel->normalmaxs); + dist = max(fabs(loadmodel->normalmins[0]), fabs(loadmodel->normalmaxs[0])); + modelyawradius = max(fabs(loadmodel->normalmins[1]), fabs(loadmodel->normalmaxs[1])); + modelyawradius = dist*dist+modelyawradius*modelyawradius; + modelradius = max(fabs(loadmodel->normalmins[2]), fabs(loadmodel->normalmaxs[2])); + modelradius = modelyawradius + modelradius * modelradius; + modelyawradius = sqrt(modelyawradius); + modelradius = sqrt(modelradius); + loadmodel->yawmins[0] = loadmodel->yawmins[1] = -modelyawradius; + loadmodel->yawmins[2] = loadmodel->normalmins[2]; + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = modelyawradius; + loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; + loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -modelradius; + loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = modelradius; + loadmodel->radius = modelradius; + loadmodel->radius2 = modelradius * modelradius; + + // allocate storage for triangles + loadmodel->surfmesh.data_element3i = (int *)Mem_Alloc(loadmodel->mempool, numtriangles * sizeof(int[3])); + // allocate vertex hash structures to build an optimal vertex subset + vertexhashsize = numtriangles*2; + vertexhashtable = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int) * vertexhashsize); + memset(vertexhashtable, 0xFF, sizeof(int) * vertexhashsize); + vertexhashdata = (objvertex_t *)Mem_Alloc(loadmodel->mempool, sizeof(*vertexhashdata) * numtriangles*3); + vertexhashcount = 0; + + // gather surface stats for assigning vertex/triangle ranges + firstvertex = 0; + firsttriangle = 0; + elementindex = 0; + loadmodel->num_surfaces = 0; + // allocate storage for the worst case number of surfaces, later we resize + tempsurfaces = (msurface_t *)Mem_Alloc(loadmodel->mempool, numtextures * loadmodel->brush.numsubmodels * sizeof(msurface_t)); + submodelfirstsurface = (int *)Mem_Alloc(loadmodel->mempool, (loadmodel->brush.numsubmodels+1) * sizeof(int)); + surface = tempsurfaces; + for (submodelindex = 0;submodelindex < loadmodel->brush.numsubmodels;submodelindex++) + { + submodelfirstsurface[submodelindex] = loadmodel->num_surfaces; + for (textureindex = 0;textureindex < numtextures;textureindex++) + { + for (vertexindex = 0;vertexindex < numtriangles*3;vertexindex++) + { + thisvertex = vertices + vertexindex; + if (thisvertex->submodelindex == submodelindex && thisvertex->textureindex == textureindex) + break; + } + // skip the surface creation if there are no triangles for it + if (vertexindex == numtriangles*3) + continue; + // create a surface for these vertices + surfacevertices = 0; + surfaceelements = 0; + // we hack in a texture index in the surface to be fixed up later... + surface->texture = (texture_t *)((size_t)textureindex); + // calculate bounds as we go + VectorCopy(thisvertex->v, surface->mins); + VectorCopy(thisvertex->v, surface->maxs); + for (;vertexindex < numtriangles*3;vertexindex++) + { + thisvertex = vertices + vertexindex; + if (thisvertex->submodelindex != submodelindex) + continue; + if (thisvertex->textureindex != textureindex) + continue; + // add vertex to surface bounds + surface->mins[0] = min(surface->mins[0], thisvertex->v[0]); + surface->mins[1] = min(surface->mins[1], thisvertex->v[1]); + surface->mins[2] = min(surface->mins[2], thisvertex->v[2]); + surface->maxs[0] = max(surface->maxs[0], thisvertex->v[0]); + surface->maxs[1] = max(surface->maxs[1], thisvertex->v[1]); + surface->maxs[2] = max(surface->maxs[2], thisvertex->v[2]); + // add the vertex if it is not found in the merged set, and + // get its index (triangle element) for the surface + vertexhashindex = (unsigned int)(thisvertex->v[0] * 3571 + thisvertex->v[0] * 1777 + thisvertex->v[0] * 457) % (unsigned int)vertexhashsize; + for (i = vertexhashtable[vertexhashindex];i >= 0;i = vertexhashdata[i].nextindex) + { + vdata = vertexhashdata + i; + if (vdata->submodelindex == thisvertex->submodelindex && vdata->textureindex == thisvertex->textureindex && VectorCompare(thisvertex->v, vdata->v) && VectorCompare(thisvertex->vn, vdata->vn) && Vector2Compare(thisvertex->vt, vdata->vt)) + break; + } + if (i < 0) + { + i = vertexhashcount++; + vdata = vertexhashdata + i; + *vdata = *thisvertex; + vdata->nextindex = vertexhashtable[vertexhashindex]; + vertexhashtable[vertexhashindex] = i; + surfacevertices++; + } + loadmodel->surfmesh.data_element3i[elementindex++] = i; + surfaceelements++; + } + surfacetriangles = surfaceelements / 3; + surface->num_vertices = surfacevertices; + surface->num_triangles = surfacetriangles; + surface->num_firstvertex = firstvertex; + surface->num_firsttriangle = firsttriangle; + firstvertex += surface->num_vertices; + firsttriangle += surface->num_triangles; + surface++; + loadmodel->num_surfaces++; + } + } + submodelfirstsurface[submodelindex] = loadmodel->num_surfaces; + numvertices = firstvertex; + loadmodel->data_surfaces = (msurface_t *)Mem_Realloc(loadmodel->mempool, tempsurfaces, loadmodel->num_surfaces * sizeof(msurface_t)); + tempsurfaces = NULL; + + // allocate storage for final mesh data + loadmodel->num_textures = numtextures * loadmodel->numskins; + loadmodel->num_texturesperskin = numtextures; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + numtriangles * sizeof(int[3]) + (numvertices <= 65536 ? numtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? numtriangles * sizeof(int[3]) : 0) + numvertices * sizeof(float[14]) + loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); + loadmodel->brush.submodels = (dp_model_t **)data;data += loadmodel->brush.numsubmodels * sizeof(dp_model_t *); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = numvertices; + loadmodel->surfmesh.num_triangles = numtriangles; + if (r_enableshadowvolumes.integer) + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += numtriangles * sizeof(int[3]); + loadmodel->surfmesh.data_vertex3f = (float *)data;data += numvertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += numvertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += numvertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += numvertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += numvertices * sizeof(float[2]); + if (loadmodel->surfmesh.num_vertices <= 65536) + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); + + for (j = 0;j < loadmodel->surfmesh.num_vertices;j++) + { + VectorCopy(vertexhashdata[j].v, loadmodel->surfmesh.data_vertex3f + 3*j); + VectorCopy(vertexhashdata[j].vn, loadmodel->surfmesh.data_normal3f + 3*j); + Vector2Copy(vertexhashdata[j].vt, loadmodel->surfmesh.data_texcoordtexture2f + 2*j); + } + + // load the textures + for (textureindex = 0;textureindex < numtextures;textureindex++) + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + textureindex, skinfiles, texturenames + textureindex*MAX_QPATH, texturenames + textureindex*MAX_QPATH); + Mod_FreeSkinFiles(skinfiles); + + // set the surface textures to their real values now that we loaded them... + for (i = 0;i < loadmodel->num_surfaces;i++) + loadmodel->data_surfaces[i].texture = loadmodel->data_textures + (size_t)loadmodel->data_surfaces[i].texture; + + // free data + Mem_Free(vertices); + Mem_Free(texturenames); + Mem_Free(v); + Mem_Free(vt); + Mem_Free(vn); + Mem_Free(vertexhashtable); + Mem_Free(vertexhashdata); + + // make a single combined shadow mesh to allow optimized shadow volume creation + Mod_Q1BSP_CreateShadowMesh(loadmodel); + + // compute all the mesh information that was not loaded from the file + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); + // generate normals if the file did not have them + if (!VectorLength2(loadmodel->surfmesh.data_normal3f)) + Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + + // if this is a worldmodel and has no BSP tree, create a fake one for the purpose + loadmodel->brush.num_visleafs = 1; + loadmodel->brush.num_leafs = 1; + loadmodel->brush.num_nodes = 0; + loadmodel->brush.num_leafsurfaces = loadmodel->num_surfaces; + loadmodel->brush.data_leafs = (mleaf_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_leafs * sizeof(mleaf_t)); + loadmodel->brush.data_nodes = (mnode_t *)loadmodel->brush.data_leafs; + loadmodel->brush.num_pvsclusters = 1; + loadmodel->brush.num_pvsclusterbytes = 1; + loadmodel->brush.data_pvsclusters = nobsp_pvs; + //if (loadmodel->num_nodes) loadmodel->data_nodes = (mnode_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_nodes * sizeof(mnode_t)); + //loadmodel->data_leafsurfaces = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->num_leafsurfaces * sizeof(int)); + loadmodel->brush.data_leafsurfaces = loadmodel->sortedmodelsurfaces; + VectorCopy(loadmodel->normalmins, loadmodel->brush.data_leafs->mins); + VectorCopy(loadmodel->normalmaxs, loadmodel->brush.data_leafs->maxs); + loadmodel->brush.data_leafs->combinedsupercontents = 0; // FIXME? + loadmodel->brush.data_leafs->clusterindex = 0; + loadmodel->brush.data_leafs->areaindex = 0; + loadmodel->brush.data_leafs->numleafsurfaces = loadmodel->brush.num_leafsurfaces; + loadmodel->brush.data_leafs->firstleafsurface = loadmodel->brush.data_leafsurfaces; + loadmodel->brush.data_leafs->numleafbrushes = 0; + loadmodel->brush.data_leafs->firstleafbrush = NULL; + loadmodel->brush.supportwateralpha = true; + + if (loadmodel->brush.numsubmodels) + loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); + + mod = loadmodel; + for (i = 0;i < loadmodel->brush.numsubmodels;i++) + { + if (i > 0) + { + char name[10]; + // duplicate the basic information + dpsnprintf(name, sizeof(name), "*%i", i); + mod = Mod_FindName(name, loadmodel->name); + // copy the base model to this one + *mod = *loadmodel; + // rename the clone back to its proper name + strlcpy(mod->name, name, sizeof(mod->name)); + mod->brush.parentmodel = loadmodel; + // textures and memory belong to the main model + mod->texturepool = NULL; + mod->mempool = NULL; + mod->brush.GetPVS = NULL; + mod->brush.FatPVS = NULL; + mod->brush.BoxTouchingPVS = NULL; + mod->brush.BoxTouchingLeafPVS = NULL; + mod->brush.BoxTouchingVisibleLeafs = NULL; + mod->brush.FindBoxClusters = NULL; + mod->brush.LightPoint = NULL; + mod->brush.AmbientSoundLevelsForPoint = NULL; + } + mod->brush.submodel = i; + if (loadmodel->brush.submodels) + loadmodel->brush.submodels[i] = mod; + + // make the model surface list (used by shadowing/lighting) + mod->firstmodelsurface = submodelfirstsurface[i]; + mod->nummodelsurfaces = submodelfirstsurface[i+1] - submodelfirstsurface[i]; + mod->firstmodelbrush = 0; + mod->nummodelbrushes = 0; + mod->sortedmodelsurfaces = loadmodel->sortedmodelsurfaces + mod->firstmodelsurface; + Mod_MakeSortedSurfaces(mod); + + VectorClear(mod->normalmins); + VectorClear(mod->normalmaxs); + l = false; + for (j = 0;j < mod->nummodelsurfaces;j++) + { + const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; + const float *v = mod->surfmesh.data_vertex3f + 3 * surface->num_firstvertex; + int k; + if (!surface->num_vertices) + continue; + if (!l) + { + l = true; + VectorCopy(v, mod->normalmins); + VectorCopy(v, mod->normalmaxs); + } + for (k = 0;k < surface->num_vertices;k++, v += 3) + { + mod->normalmins[0] = min(mod->normalmins[0], v[0]); + mod->normalmins[1] = min(mod->normalmins[1], v[1]); + mod->normalmins[2] = min(mod->normalmins[2], v[2]); + mod->normalmaxs[0] = max(mod->normalmaxs[0], v[0]); + mod->normalmaxs[1] = max(mod->normalmaxs[1], v[1]); + mod->normalmaxs[2] = max(mod->normalmaxs[2], v[2]); + } + } + corner[0] = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); + corner[1] = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); + corner[2] = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); + modelradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]+corner[2]*corner[2]); + yawradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); + mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; + mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; + mod->yawmaxs[0] = mod->yawmaxs[1] = yawradius; + mod->yawmins[0] = mod->yawmins[1] = -yawradius; + mod->yawmins[2] = mod->normalmins[2]; + mod->yawmaxs[2] = mod->normalmaxs[2]; + mod->radius = modelradius; + mod->radius2 = modelradius * modelradius; + + // this gets altered below if sky or water is used + mod->DrawSky = NULL; + mod->DrawAddWaterPlanes = NULL; + + for (j = 0;j < mod->nummodelsurfaces;j++) + if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & MATERIALFLAG_SKY) + break; + if (j < mod->nummodelsurfaces) + mod->DrawSky = R_Q1BSP_DrawSky; + + for (j = 0;j < mod->nummodelsurfaces;j++) + if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + break; + if (j < mod->nummodelsurfaces) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + + Mod_MakeCollisionBIH(mod, true, &mod->collision_bih); + mod->render_bih = mod->collision_bih; + + // generate VBOs and other shared data before cloning submodels + if (i == 0) + Mod_BuildVBOs(); + } + mod = loadmodel; + Mem_Free(submodelfirstsurface); + + Con_DPrintf("Stats for obj model \"%s\": %i faces, %i nodes, %i leafs, %i clusters, %i clusterportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); +} + +qboolean Mod_CanSeeBox_Trace(int numsamples, float t, dp_model_t *model, vec3_t eye, vec3_t minsX, vec3_t maxsX) +{ + // we already have done PVS culling at this point... + // so we don't need to do it again. + + int i; + vec3_t testorigin, mins, maxs; + + testorigin[0] = (minsX[0] + maxsX[0]) * 0.5; + testorigin[1] = (minsX[1] + maxsX[1]) * 0.5; + testorigin[2] = (minsX[2] + maxsX[2]) * 0.5; + + if(model->brush.TraceLineOfSight(model, eye, testorigin)) + return 1; + + // expand the box a little + mins[0] = (t+1) * minsX[0] - t * maxsX[0]; + maxs[0] = (t+1) * maxsX[0] - t * minsX[0]; + mins[1] = (t+1) * minsX[1] - t * maxsX[1]; + maxs[1] = (t+1) * maxsX[1] - t * minsX[1]; + mins[2] = (t+1) * minsX[2] - t * maxsX[2]; + maxs[2] = (t+1) * maxsX[2] - t * minsX[2]; + + for(i = 0; i != numsamples; ++i) + { + testorigin[0] = lhrandom(mins[0], maxs[0]); + testorigin[1] = lhrandom(mins[1], maxs[1]); + testorigin[2] = lhrandom(mins[2], maxs[2]); + + if(model->brush.TraceLineOfSight(model, eye, testorigin)) + return 1; + } + + return 0; +} + diff --git a/misc/source/darkplaces-src/model_brush.h b/misc/source/darkplaces-src/model_brush.h new file mode 100644 index 00000000..906594ad --- /dev/null +++ b/misc/source/darkplaces-src/model_brush.h @@ -0,0 +1,714 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef MODEL_BRUSH_H +#define MODEL_BRUSH_H + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + + +// +// in memory representation +// +typedef struct mvertex_s +{ + vec3_t position; +} +mvertex_t; + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + + +// plane_t structure +typedef struct mplane_s +{ + vec3_t normal; + float dist; + // for texture axis selection and fast side tests + int type; // set by PlaneClassify() + int signbits; // set by PlaneClassify() +} +mplane_t; + +#define SHADERSTAGE_SKY 0 +#define SHADERSTAGE_NORMAL 1 +#define SHADERSTAGE_COUNT 2 + +//#define SURF_PLANEBACK 2 + +// indicates that all triangles of the surface should be added to the BIH collision system +#define MATERIALFLAG_MESHCOLLISIONS 1 +// use alpha blend on this material +#define MATERIALFLAG_ALPHA 2 +// use additive blend on this material +#define MATERIALFLAG_ADD 4 +// turn off depth test on this material +#define MATERIALFLAG_NODEPTHTEST 8 +// multiply alpha by r_wateralpha cvar +#define MATERIALFLAG_WATERALPHA 16 +// draw with no lighting +#define MATERIALFLAG_FULLBRIGHT 32 +// drawn as a normal surface (alternative to SKY) +#define MATERIALFLAG_WALL 64 +// this surface shows the sky in its place, alternative to WALL +// skipped if transparent +#define MATERIALFLAG_SKY 128 +// swirling water effect (used with MATERIALFLAG_WALL) +#define MATERIALFLAG_WATERSCROLL 256 +// skips drawing the surface +#define MATERIALFLAG_NODRAW 512 +// probably used only on q1bsp water +#define MATERIALFLAG_LIGHTBOTHSIDES 1024 +// use alpha test on this material +#define MATERIALFLAG_ALPHATEST 2048 +// treat this material as a blended transparency (as opposed to an alpha test +// transparency), this causes special fog behavior, and disables glDepthMask +#define MATERIALFLAG_BLENDED 4096 +// render using a custom blendfunc +#define MATERIALFLAG_CUSTOMBLEND 8192 +// do not cast shadows from this material +#define MATERIALFLAG_NOSHADOW 16384 +// render using vertex alpha (q3bsp) as texture blend parameter between foreground (normal) skinframe and background skinframe +#define MATERIALFLAG_VERTEXTEXTUREBLEND 32768 +// disables GL_CULL_FACE on this texture (making it double sided) +#define MATERIALFLAG_NOCULLFACE 65536 +// render with a very short depth range (like 10% of normal), this causes entities to appear infront of most of the scene +#define MATERIALFLAG_SHORTDEPTHRANGE 131072 +// render water, comprising refraction and reflection (note: this is always opaque, the shader does the alpha effect) +#define MATERIALFLAG_WATERSHADER 262144 +// render refraction (note: this is just a way to distort the background, otherwise useless) +#define MATERIALFLAG_REFRACTION 524288 +// render reflection +#define MATERIALFLAG_REFLECTION 1048576 +// use model lighting on this material (q1bsp lightmap sampling or q3bsp lightgrid, implies FULLBRIGHT is false) +#define MATERIALFLAG_MODELLIGHT 4194304 +// add directional model lighting to this material (q3bsp lightgrid only) +#define MATERIALFLAG_MODELLIGHT_DIRECTIONAL 8388608 +// causes RSurf_GetCurrentTexture to leave alone certain fields +#define MATERIALFLAG_CUSTOMSURFACE 16777216 +// causes MATERIALFLAG_BLENDED to render a depth pass before rendering, hiding backfaces and other hidden geometry +#define MATERIALFLAG_TRANSDEPTH 33554432 +// like refraction, but doesn't distort etc. +#define MATERIALFLAG_CAMERA 67108864 +// disable rtlight on surface, use R_LightPoint instead +#define MATERIALFLAG_NORTLIGHT 134217728 +// combined mask of all attributes that require depth sorted rendering +#define MATERIALFLAGMASK_DEPTHSORTED (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST) +// combined mask of all attributes that cause some sort of transparency +#define MATERIALFLAGMASK_TRANSLUCENT (MATERIALFLAG_WATERALPHA | MATERIALFLAG_SKY | MATERIALFLAG_NODRAW | MATERIALFLAG_ALPHATEST | MATERIALFLAG_BLENDED | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION) + +typedef struct medge_s +{ + unsigned short v[2]; +} +medge_t; + +struct entity_render_s; +struct texture_s; +struct msurface_s; + +typedef struct mnode_s +{ + //this part shared between node and leaf + mplane_t *plane; // != NULL + struct mnode_s *parent; + struct mportal_s *portals; + // for bounding box culling + vec3_t mins; + vec3_t maxs; + // supercontents from all brushes inside this node or leaf + int combinedsupercontents; + + // this part unique to node + struct mnode_s *children[2]; + + // q1bsp specific + unsigned short firstsurface; + unsigned short numsurfaces; +} +mnode_t; + +typedef struct mleaf_s +{ + //this part shared between node and leaf + mplane_t *plane; // == NULL + struct mnode_s *parent; + struct mportal_s *portals; + // for bounding box culling + vec3_t mins; + vec3_t maxs; + // supercontents from all brushes inside this node or leaf + int combinedsupercontents; + + // this part unique to leaf + // common + int clusterindex; // -1 is not in pvs, >= 0 is pvs bit number + int areaindex; // q3bsp + int containscollisionsurfaces; // indicates whether the leafsurfaces contains q3 patches + int numleafsurfaces; + int *firstleafsurface; + int numleafbrushes; // q3bsp + int *firstleafbrush; // q3bsp + unsigned char ambient_sound_level[NUM_AMBIENTS]; // q1bsp + int contents; // q1bsp: // TODO: remove (only used temporarily during loading when making collision hull 0) + int portalmarkid; // q1bsp // used by see-polygon-through-portals visibility checker +} +mleaf_t; + +typedef struct mclipnode_s +{ + int planenum; + int children[2]; // negative numbers are contents +} mclipnode_t; + +typedef struct hull_s +{ + mclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; + vec3_t clip_size; +} +hull_t; + +typedef struct mportal_s +{ + struct mportal_s *next; // the next portal on this leaf + mleaf_t *here; // the leaf this portal is on + mleaf_t *past; // the leaf through this portal (infront) + int numpoints; + mvertex_t *points; + vec3_t mins, maxs; // culling + mplane_t plane; +} +mportal_t; + +typedef struct svbspmesh_s +{ + struct svbspmesh_s *next; + int numverts, maxverts; + int numtriangles, maxtriangles; + float *verts; + int *elements; +} +svbspmesh_t; + +// Q2 bsp stuff + +#define Q2BSPVERSION 38 + +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits + +//============================================================================= + +#define Q2LUMP_ENTITIES 0 +#define Q2LUMP_PLANES 1 +#define Q2LUMP_VERTEXES 2 +#define Q2LUMP_VISIBILITY 3 +#define Q2LUMP_NODES 4 +#define Q2LUMP_TEXINFO 5 +#define Q2LUMP_FACES 6 +#define Q2LUMP_LIGHTING 7 +#define Q2LUMP_LEAFS 8 +#define Q2LUMP_LEAFFACES 9 +#define Q2LUMP_LEAFBRUSHES 10 +#define Q2LUMP_EDGES 11 +#define Q2LUMP_SURFEDGES 12 +#define Q2LUMP_MODELS 13 +#define Q2LUMP_BRUSHES 14 +#define Q2LUMP_BRUSHSIDES 15 +#define Q2LUMP_POP 16 +#define Q2LUMP_AREAS 17 +#define Q2LUMP_AREAPORTALS 18 +#define Q2HEADER_LUMPS 19 + +typedef struct q2dheader_s +{ + int ident; + int version; + lump_t lumps[Q2HEADER_LUMPS]; +} q2dheader_t; + +typedef struct q2dmodel_s +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} q2dmodel_t; + +// planes (x&~1) and (x&~1)+1 are always opposites + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#define Q2CONTENTS_SOLID 1 // an eye is never valid in a solid +#define Q2CONTENTS_WINDOW 2 // translucent, but not watery +#define Q2CONTENTS_AUX 4 +#define Q2CONTENTS_LAVA 8 +#define Q2CONTENTS_SLIME 16 +#define Q2CONTENTS_WATER 32 +#define Q2CONTENTS_MIST 64 +#define Q2LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define Q2CONTENTS_AREAPORTAL 0x8000 + +#define Q2CONTENTS_PLAYERCLIP 0x10000 +#define Q2CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define Q2CONTENTS_CURRENT_0 0x40000 +#define Q2CONTENTS_CURRENT_90 0x80000 +#define Q2CONTENTS_CURRENT_180 0x100000 +#define Q2CONTENTS_CURRENT_270 0x200000 +#define Q2CONTENTS_CURRENT_UP 0x400000 +#define Q2CONTENTS_CURRENT_DOWN 0x800000 + +#define Q2CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define Q2CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define Q2CONTENTS_DEADMONSTER 0x4000000 +#define Q2CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define Q2CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define Q2CONTENTS_LADDER 0x20000000 + + + +#define Q2SURF_LIGHT 0x1 // value will hold the light strength + +#define Q2SURF_SLICK 0x2 // effects game physics + +#define Q2SURF_SKY 0x4 // don't draw, but add to skybox +#define Q2SURF_WARP 0x8 // turbulent water warp +#define Q2SURF_TRANS33 0x10 +#define Q2SURF_TRANS66 0x20 +#define Q2SURF_FLOWING 0x40 // scroll towards angle +#define Q2SURF_NODRAW 0x80 // don't bother referencing the texture + + + + +typedef struct q2dnode_s +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} q2dnode_t; + + +typedef struct q2texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} q2texinfo_t; + +typedef struct q2dleaf_s +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} q2dleaf_t; + +typedef struct q2dbrushside_s +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} q2dbrushside_t; + +typedef struct q2dbrush_s +{ + int firstside; + int numsides; + int contents; +} q2dbrush_t; + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define Q2DVIS_PVS 0 +#define Q2DVIS_PHS 1 +typedef struct q2dvis_s +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} q2dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct q2dareaportal_s +{ + int portalnum; + int otherarea; +} q2dareaportal_t; + +typedef struct q2darea_s +{ + int numareaportals; + int firstareaportal; +} q2darea_t; + + +//Q3 bsp stuff + +#define Q3BSPVERSION 46 +#define Q3BSPVERSION_LIVE 47 +#define Q3BSPVERSION_IG 48 + +#define Q3LUMP_ENTITIES 0 // entities to spawn (used by server and client) +#define Q3LUMP_TEXTURES 1 // textures used (used by faces) +#define Q3LUMP_PLANES 2 // planes used (used by bsp nodes) +#define Q3LUMP_NODES 3 // bsp nodes (used by bsp nodes, bsp leafs, rendering, collisions) +#define Q3LUMP_LEAFS 4 // bsp leafs (used by bsp nodes) +#define Q3LUMP_LEAFFACES 5 // array of ints indexing faces (used by leafs) +#define Q3LUMP_LEAFBRUSHES 6 // array of ints indexing brushes (used by leafs) +#define Q3LUMP_MODELS 7 // models (used by rendering, collisions) +#define Q3LUMP_BRUSHES 8 // brushes (used by effects, collisions) +#define Q3LUMP_BRUSHSIDES 9 // brush faces (used by brushes) +#define Q3LUMP_VERTICES 10 // mesh vertices (used by faces) +#define Q3LUMP_TRIANGLES 11 // mesh triangles (used by faces) +#define Q3LUMP_EFFECTS 12 // fog (used by faces) +#define Q3LUMP_FACES 13 // surfaces (used by leafs) +#define Q3LUMP_LIGHTMAPS 14 // lightmap textures (used by faces) +#define Q3LUMP_LIGHTGRID 15 // lighting as a voxel grid (used by rendering) +#define Q3LUMP_PVS 16 // potentially visible set; bit[clusters][clusters] (used by rendering) +#define Q3HEADER_LUMPS 17 +#define Q3LUMP_ADVERTISEMENTS 17 // quake live stuff written by zeroradiant's q3map2 (ignored by DP) +#define Q3HEADER_LUMPS_LIVE 18 +#define Q3HEADER_LUMPS_MAX 18 + +typedef struct q3dheader_s +{ + int ident; + int version; + lump_t lumps[Q3HEADER_LUMPS_MAX]; +} q3dheader_t; + +typedef struct q3dtexture_s +{ + char name[Q3PATHLENGTH]; + int surfaceflags; + int contents; +} +q3dtexture_t; + +// note: planes are paired, the pair of planes with i and i ^ 1 are opposites. +typedef struct q3dplane_s +{ + float normal[3]; + float dist; +} +q3dplane_t; + +typedef struct q3dnode_s +{ + int planeindex; + int childrenindex[2]; + int mins[3]; + int maxs[3]; +} +q3dnode_t; + +typedef struct q3dleaf_s +{ + int clusterindex; // pvs index + int areaindex; // area index + int mins[3]; + int maxs[3]; + int firstleafface; + int numleaffaces; + int firstleafbrush; + int numleafbrushes; +} +q3dleaf_t; + +typedef struct q3dmodel_s +{ + float mins[3]; + float maxs[3]; + int firstface; + int numfaces; + int firstbrush; + int numbrushes; +} +q3dmodel_t; + +typedef struct q3dbrush_s +{ + int firstbrushside; + int numbrushsides; + int textureindex; +} +q3dbrush_t; + +typedef struct q3dbrushside_s +{ + int planeindex; + int textureindex; +} +q3dbrushside_t; + +typedef struct q3dbrushside_ig_s +{ + int planeindex; + int textureindex; + int surfaceflags; +} +q3dbrushside_ig_t; + +typedef struct q3dvertex_s +{ + float origin3f[3]; + float texcoord2f[2]; + float lightmap2f[2]; + float normal3f[3]; + unsigned char color4ub[4]; +} +q3dvertex_t; + +typedef struct q3dmeshvertex_s +{ + int offset; // first vertex index of mesh +} +q3dmeshvertex_t; + +typedef struct q3deffect_s +{ + char shadername[Q3PATHLENGTH]; + int brushindex; + int unknown; // I read this is always 5 except in q3dm8 which has one effect with -1 +} +q3deffect_t; + +#define Q3FACETYPE_FLAT 1 // common +#define Q3FACETYPE_PATCH 2 // common +#define Q3FACETYPE_MESH 3 // common +#define Q3FACETYPE_FLARE 4 // rare (is this ever used?) + +typedef struct q3dface_s +{ + int textureindex; + int effectindex; // -1 if none + int type; // Q3FACETYPE + int firstvertex; + int numvertices; + int firstelement; + int numelements; + int lightmapindex; // -1 if none + int lightmap_base[2]; + int lightmap_size[2]; + union + { + struct + { + // corrupt or don't care + int blah[14]; + } + unknown; + struct + { + // Q3FACETYPE_FLAT + // mesh is a collection of triangles on a plane, renderable as a mesh (NOT a polygon) + float lightmap_origin[3]; + float lightmap_vectors[2][3]; + float normal[3]; + int unused1[2]; + } + flat; + struct + { + // Q3FACETYPE_PATCH + // patch renders as a bezier mesh, with adjustable tesselation + // level (optionally based on LOD using the bbox and polygon + // count to choose a tesselation level) + // note: multiple patches may have the same bbox to cause them to + // be LOD adjusted together as a group + int unused1[3]; + float mins[3]; // LOD bbox + float maxs[3]; // LOD bbox + int unused2[3]; + int patchsize[2]; // dimensions of vertex grid + } + patch; + struct + { + // Q3FACETYPE_MESH + // mesh renders as simply a triangle mesh + int unused1[3]; + float mins[3]; + float maxs[3]; + int unused2[5]; + } + mesh; + struct + { + // Q3FACETYPE_FLARE + // flare renders as a simple sprite at origin, no geometry + // exists, nor does it have a radius, a cvar controls the radius + // and another cvar controls distance fade + // (they were not used in Q3 I'm told) + float origin[3]; + int unused1[11]; + } + flare; + } + specific; +} +q3dface_t; + +typedef struct q3dlightmap_s +{ + unsigned char rgb[128*128*3]; +} +q3dlightmap_t; + +typedef struct q3dlightgrid_s +{ + unsigned char ambientrgb[3]; + unsigned char diffusergb[3]; + unsigned char diffusepitch; + unsigned char diffuseyaw; +} +q3dlightgrid_t; + +typedef struct q3dpvs_s +{ + int numclusters; + int chainlength; + // unsigned char chains[]; + // containing bits in 0-7 order (not 7-0 order), + // pvschains[mycluster * chainlength + (thatcluster >> 3)] & (1 << (thatcluster & 7)) +} +q3dpvs_t; + +// surfaceflags from bsp +#define Q3SURFACEFLAG_NODAMAGE 1 +#define Q3SURFACEFLAG_SLICK 2 +#define Q3SURFACEFLAG_SKY 4 +#define Q3SURFACEFLAG_LADDER 8 +#define Q3SURFACEFLAG_NOIMPACT 16 +#define Q3SURFACEFLAG_NOMARKS 32 +#define Q3SURFACEFLAG_FLESH 64 +#define Q3SURFACEFLAG_NODRAW 128 +#define Q3SURFACEFLAG_HINT 256 +#define Q3SURFACEFLAG_SKIP 512 +#define Q3SURFACEFLAG_NOLIGHTMAP 1024 +#define Q3SURFACEFLAG_POINTLIGHT 2048 +#define Q3SURFACEFLAG_METALSTEPS 4096 +#define Q3SURFACEFLAG_NOSTEPS 8192 +#define Q3SURFACEFLAG_NONSOLID 16384 +#define Q3SURFACEFLAG_LIGHTFILTER 32768 +#define Q3SURFACEFLAG_ALPHASHADOW 65536 +#define Q3SURFACEFLAG_NODLIGHT 131072 +#define Q3SURFACEFLAG_DUST 262144 + +// surfaceparms from shaders +#define Q3SURFACEPARM_ALPHASHADOW 1 +#define Q3SURFACEPARM_AREAPORTAL 2 +#define Q3SURFACEPARM_CLUSTERPORTAL 4 +#define Q3SURFACEPARM_DETAIL 8 +#define Q3SURFACEPARM_DONOTENTER 16 +#define Q3SURFACEPARM_FOG 32 +#define Q3SURFACEPARM_LAVA 64 +#define Q3SURFACEPARM_LIGHTFILTER 128 +#define Q3SURFACEPARM_METALSTEPS 256 +#define Q3SURFACEPARM_NODAMAGE 512 +#define Q3SURFACEPARM_NODLIGHT 1024 +#define Q3SURFACEPARM_NODRAW 2048 +#define Q3SURFACEPARM_NODROP 4096 +#define Q3SURFACEPARM_NOIMPACT 8192 +#define Q3SURFACEPARM_NOLIGHTMAP 16384 +#define Q3SURFACEPARM_NOMARKS 32768 +#define Q3SURFACEPARM_NOMIPMAPS 65536 +#define Q3SURFACEPARM_NONSOLID 131072 +#define Q3SURFACEPARM_ORIGIN 262144 +#define Q3SURFACEPARM_PLAYERCLIP 524288 +#define Q3SURFACEPARM_SKY 1048576 +#define Q3SURFACEPARM_SLICK 2097152 +#define Q3SURFACEPARM_SLIME 4194304 +#define Q3SURFACEPARM_STRUCTURAL 8388608 +#define Q3SURFACEPARM_TRANS 16777216 +#define Q3SURFACEPARM_WATER 33554432 +#define Q3SURFACEPARM_POINTLIGHT 67108864 +#define Q3SURFACEPARM_HINT 134217728 +#define Q3SURFACEPARM_DUST 268435456 +#define Q3SURFACEPARM_BOTCLIP 536870912 +#define Q3SURFACEPARM_LIGHTGRID 1073741824 +#define Q3SURFACEPARM_ANTIPORTAL 2147483648u + +typedef struct q3mbrush_s +{ + struct colbrushf_s *colbrushf; + int numbrushsides; + struct q3mbrushside_s *firstbrushside; + struct texture_s *texture; +} +q3mbrush_t; + +typedef struct q3mbrushside_s +{ + struct mplane_s *plane; + struct texture_s *texture; +} +q3mbrushside_t; + +// the first cast is to shut up a stupid warning by clang, the second cast is to make both sides have the same type +#define CHECKPVSBIT(pvs,b) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] & (1 << ((b) & 7))) : (unsigned char) false) +#define SETPVSBIT(pvs,b) (void) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] |= (1 << ((b) & 7))) : (unsigned char) false) +#define CLEARPVSBIT(pvs,b) (void) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] &= ~(1 << ((b) & 7))) : (unsigned char) false) + +#endif + diff --git a/misc/source/darkplaces-src/model_dpmodel.h b/misc/source/darkplaces-src/model_dpmodel.h new file mode 100644 index 00000000..52ad0ce0 --- /dev/null +++ b/misc/source/darkplaces-src/model_dpmodel.h @@ -0,0 +1,123 @@ + +#ifndef MODEL_DPMODEL_H +#define MODEL_DPMODEL_H + +/* +type 2 model (hierarchical skeletal pose) +within this specification, int is assumed to be 32bit, float is assumed to be 32bit, char is assumed to be 8bit, text is assumed to be an array of chars with NULL termination +all values are big endian (also known as network byte ordering), NOT x86 little endian +general notes: +a pose is a 3x4 matrix (rotation matrix, and translate vector) +parent bones must always be lower in number than their children, models will be rejected if this is not obeyed (can be fixed by modelling utilities) +utility notes: +if a hard edge is desired (faceted lighting, or a jump to another set of skin coordinates), vertices must be duplicated +ability to visually edit groupids of triangles is highly recommended +bones should be markable as 'attach' somehow (up to the utility) and thus protected from culling of unused resources +frame 0 is always the base pose (the one the skeleton was built for) +game notes: +the loader should be very thorough about error checking, all vertex and bone indices should be validated, etc +the gamecode can look up bone numbers by name using a builtin function, for use in attachment situations (the client should have the same model as the host of the gamecode in question - that is to say if the server gamecode is setting the bone number, the client and server must have vaguely compatible models so the client understands, and if the client gamecode is setting the bone number, the server could have a completely different model with no harm done) +the triangle groupid values are up to the gamecode, it is recommended that gamecode process this in an object-oriented fashion (I.E. bullet hits entity, call that entity's function for getting properties of that groupid) +frame 0 should be usable, not skipped +speed optimizations for the saver to do: +remove all unused data (unused bones, vertices, etc, be sure to check if bones are used for attachments however) +sort triangles into strips +sort vertices according to first use in a triangle (caching benefits) after sorting triangles +speed optimizations for the loader to do: +if the model only has one frame, process it at load time to create a simple static vertex mesh to render (this is a hassle, but it is rewarding to optimize all such models) +rendering process: +1*. one or two poses are looked up by number +2*. boneposes (matrices) are interpolated, building bone matrix array +3. bones are parsed sequentially, each bone's matrix is transformed by it's parent bone (which can be -1; the model to world matrix) +4. meshs are parsed sequentially, as follows: + 1. vertices are parsed sequentially and may be influenced by more than one bone (the results of the 3x4 matrix transform will be added together - weighting is already built into these) + 2. shader is looked up and called, passing vertex buffer (temporary) and triangle indices (which are stored in the mesh) +5. rendering is complete +* - these stages can be replaced with completely dynamic animation instead of pose animations. +*/ +// header for the entire file +typedef struct dpmheader_s +{ + char id[16]; // "DARKPLACESMODEL\0", length 16 + unsigned int type; // 2 (hierarchical skeletal pose) + unsigned int filesize; // size of entire model file + float mins[3], maxs[3], yawradius, allradius; // for clipping uses + // these offsets are relative to the file + unsigned int num_bones; + unsigned int num_meshs; + unsigned int num_frames; + unsigned int ofs_bones; // dpmbone_t bone[num_bones]; + unsigned int ofs_meshs; // dpmmesh_t mesh[num_meshs]; + unsigned int ofs_frames; // dpmframe_t frame[num_frames]; +} +dpmheader_t; +// there may be more than one of these +typedef struct dpmmesh_s +{ + // these offsets are relative to the file + char shadername[32]; // name of the shader to use + unsigned int num_verts; + unsigned int num_tris; + unsigned int ofs_verts; // dpmvertex_t vert[numvertices]; // see vertex struct + unsigned int ofs_texcoords; // float texcoords[numvertices][2]; + unsigned int ofs_indices; // unsigned int indices[numtris*3]; // designed for glDrawElements (each triangle is 3 unsigned int indices) + unsigned int ofs_groupids; // unsigned int groupids[numtris]; // the meaning of these values is entirely up to the gamecode and modeler +} +dpmmesh_t; +// if set on a bone, it must be protected from removal +#define DPMBONEFLAG_ATTACHMENT 1 +// one per bone +typedef struct dpmbone_s +{ + // name examples: upperleftarm leftfinger1 leftfinger2 hand, etc + char name[32]; + // parent bone number + signed int parent; + // flags for the bone + unsigned int flags; +} +dpmbone_t; +// a bonepose matrix is intended to be used like this: +// (n = output vertex, v = input vertex, m = matrix, f = influence) +// n[0] = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2] + f * m[0][3]; +// n[1] = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2] + f * m[1][3]; +// n[2] = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2] + f * m[2][3]; +typedef struct dpmbonepose_s +{ + float matrix[3][4]; +} +dpmbonepose_t; +// immediately followed by bone positions for the frame +typedef struct dpmframe_s +{ + // name examples: idle_1 idle_2 idle_3 shoot_1 shoot_2 shoot_3, etc + char name[32]; + float mins[3], maxs[3], yawradius, allradius; + int ofs_bonepositions; // dpmbonepose_t bonepositions[bones]; +} +dpmframe_t; +// one or more of these per vertex +typedef struct dpmbonevert_s +{ + // this pairing of origin and influence is intentional + // (in SSE or 3DNow! assembly it can be done as a quad vector op + // (or two dual vector ops) very easily) + float origin[3]; // vertex location (these blend) + float influence; // influence fraction (these must add up to 1) + // this pairing of normal and bonenum is intentional + // (in SSE or 3DNow! assembly it can be done as a quad vector op + // (or two dual vector ops) very easily, the bonenum is ignored) + float normal[3]; // surface normal (these blend) + unsigned int bonenum; // number of the bone +} +dpmbonevert_t; +// variable size, parsed sequentially +typedef struct dpmvertex_s +{ + unsigned int numbones; + // immediately followed by 1 or more dpmbonevert_t structures +} +dpmvertex_t; + +#endif + diff --git a/misc/source/darkplaces-src/model_iqm.h b/misc/source/darkplaces-src/model_iqm.h new file mode 100644 index 00000000..1dbb940c --- /dev/null +++ b/misc/source/darkplaces-src/model_iqm.h @@ -0,0 +1,127 @@ +#ifndef __MODEL_IQM_H__ +#define __MODEL_IQM_H__ + +typedef struct iqmheader_s +{ + char id[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_neighbors; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; +} +iqmheader_t; + +typedef struct iqmmesh_s +{ + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; +} +iqmmesh_t; + +#define IQM_POSITION 0 +#define IQM_TEXCOORD 1 +#define IQM_NORMAL 2 +#define IQM_TANGENT 3 +#define IQM_BLENDINDEXES 4 +#define IQM_BLENDWEIGHTS 5 +#define IQM_COLOR 6 +#define IQM_CUSTOM 0x10 + +#define IQM_BYTE 0 +#define IQM_UBYTE 1 +#define IQM_SHORT 2 +#define IQM_USHORT 3 +#define IQM_INT 4 +#define IQM_UINT 5 +#define IQM_HALF 6 +#define IQM_FLOAT 7 +#define IQM_DOUBLE 8 + +// animflags +#define IQM_LOOP 1 + +typedef struct iqmtriangle_s +{ + unsigned int vertex[3]; +} +iqmtriangle_t; + +typedef struct iqmjoint1_s +{ + unsigned int name; + signed int parent; + float origin[3], rotation[3], scale[3]; +} +iqmjoint1_t; + +typedef struct iqmjoint_s +{ + unsigned int name; + signed int parent; + float origin[3], rotation[4], scale[3]; +} +iqmjoint_t; + +typedef struct iqmpose1_s +{ + signed int parent; + unsigned int channelmask; + float channeloffset[9], channelscale[9]; +} +iqmpose1_t; + +typedef struct iqmpose_s +{ + signed int parent; + unsigned int channelmask; + float channeloffset[10], channelscale[10]; +} +iqmpose_t; + +typedef struct iqmanim_s +{ + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; +} +iqmanim_t; + +typedef struct iqmvertexarray_s +{ + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; +} +iqmvertexarray_t; + +typedef struct iqmextension_s +{ + unsigned int name; + unsigned int num_data, ofs_data; + unsigned int ofs_extensions; // pointer to next extension +} +iqmextension_t; + +typedef struct iqmbounds_s +{ + float mins[3], maxs[3]; + float xyradius, radius; +} +iqmbounds_t; + +#endif + diff --git a/misc/source/darkplaces-src/model_psk.h b/misc/source/darkplaces-src/model_psk.h new file mode 100644 index 00000000..480386f0 --- /dev/null +++ b/misc/source/darkplaces-src/model_psk.h @@ -0,0 +1,117 @@ + +#ifndef MODEL_PSK_H +#define MODEL_PSK_H + +typedef struct pskchunk_s +{ + // id is one of the following: + // .psk: + // ACTRHEAD (recordsize = 0, numrecords = 0) + // PNTS0000 (recordsize = 12, pskpnts_t) + // VTXW0000 (recordsize = 16, pskvtxw_t) + // FACE0000 (recordsize = 12, pskface_t) + // MATT0000 (recordsize = 88, pskmatt_t) + // REFSKELT (recordsize = 120, pskboneinfo_t) + // RAWWEIGHTS (recordsize = 12, pskrawweights_t) + // .psa: + // ANIMHEAD (recordsize = 0, numrecords = 0) + // BONENAMES (recordsize = 120, pskboneinfo_t) + // ANIMINFO (recordsize = 168, pskaniminfo_t) + // ANIMKEYS (recordsize = 32, pskanimkeys_t) + char id[20]; + // in .psk always 0x1e83b9 + // in .psa always 0x2e + int version; + int recordsize; + int numrecords; +} +pskchunk_t; + +typedef struct pskpnts_s +{ + float origin[3]; +} +pskpnts_t; + +typedef struct pskvtxw_s +{ + unsigned short pntsindex; // index into PNTS0000 chunk + unsigned char unknown1[2]; // seems to be garbage + float texcoord[2]; + unsigned char mattindex; // index into MATT0000 chunk + unsigned char unknown2; // always 0? + unsigned char unknown3[2]; // seems to be garbage +} +pskvtxw_t; + +typedef struct pskface_s +{ + unsigned short vtxwindex[3]; // triangle + unsigned char mattindex; // index into MATT0000 chunk + unsigned char unknown; // seems to be garbage + unsigned int group; // faces seem to be grouped, possibly for smoothing? +} +pskface_t; + +typedef struct pskmatt_s +{ + char name[64]; + int unknown[6]; // observed 0 0 0 0 5 0 +} +pskmatt_t; + +typedef struct pskpose_s +{ + float quat[4]; + float origin[3]; + float unknown; // probably a float, always seems to be 0 + float size[3]; +} +pskpose_t; + +typedef struct pskboneinfo_s +{ + char name[64]; + int unknown1; + int numchildren; + int parent; // root bones have 0 here + pskpose_t basepose; +} +pskboneinfo_t; + +typedef struct pskrawweights_s +{ + float weight; + int pntsindex; + int boneindex; +} +pskrawweights_t; + +typedef struct pskaniminfo_s +{ + char name[64]; + char group[64]; + int numbones; + int unknown1; + int unknown2; + int unknown3; + float unknown4; + float playtime; // not really needed + float fps; // frames per second + int unknown5; + int firstframe; + int numframes; + // firstanimkeys = (firstframe + frameindex) * numbones +} +pskaniminfo_t; + +typedef struct pskanimkeys_s +{ + float origin[3]; + float quat[4]; + float frametime; +} +pskanimkeys_t; + +#endif + diff --git a/misc/source/darkplaces-src/model_shared.c b/misc/source/darkplaces-src/model_shared.c new file mode 100644 index 00000000..d4021ee0 --- /dev/null +++ b/misc/source/darkplaces-src/model_shared.c @@ -0,0 +1,4262 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// models.c -- model loading and caching + +// models are the only shared resource between a client and server running +// on the same machine. + +#include "quakedef.h" +#include "image.h" +#include "r_shadow.h" +#include "polygon.h" + +cvar_t r_enableshadowvolumes = {CVAR_SAVE, "r_enableshadowvolumes", "1", "Enables use of Stencil Shadow Volume shadowing methods, saves some memory if turned off"}; +cvar_t r_mipskins = {CVAR_SAVE, "r_mipskins", "0", "mipmaps model skins so they render faster in the distance and do not display noise artifacts, can cause discoloration of skins if they contain undesirable border colors"}; +cvar_t r_mipnormalmaps = {CVAR_SAVE, "r_mipnormalmaps", "1", "mipmaps normalmaps (turning it off looks sharper but may have aliasing)"}; +cvar_t mod_generatelightmaps_unitspersample = {CVAR_SAVE, "mod_generatelightmaps_unitspersample", "8", "lightmap resolution"}; +cvar_t mod_generatelightmaps_borderpixels = {CVAR_SAVE, "mod_generatelightmaps_borderpixels", "2", "extra space around polygons to prevent sampling artifacts"}; +cvar_t mod_generatelightmaps_texturesize = {CVAR_SAVE, "mod_generatelightmaps_texturesize", "1024", "size of lightmap textures"}; +cvar_t mod_generatelightmaps_lightmapsamples = {CVAR_SAVE, "mod_generatelightmaps_lightmapsamples", "16", "number of shadow tests done per lightmap pixel"}; +cvar_t mod_generatelightmaps_vertexsamples = {CVAR_SAVE, "mod_generatelightmaps_vertexsamples", "16", "number of shadow tests done per vertex"}; +cvar_t mod_generatelightmaps_gridsamples = {CVAR_SAVE, "mod_generatelightmaps_gridsamples", "64", "number of shadow tests done per lightgrid cell"}; +cvar_t mod_generatelightmaps_lightmapradius = {CVAR_SAVE, "mod_generatelightmaps_lightmapradius", "16", "sampling area around each lightmap pixel"}; +cvar_t mod_generatelightmaps_vertexradius = {CVAR_SAVE, "mod_generatelightmaps_vertexradius", "16", "sampling area around each vertex"}; +cvar_t mod_generatelightmaps_gridradius = {CVAR_SAVE, "mod_generatelightmaps_gridradius", "64", "sampling area around each lightgrid cell center"}; + +dp_model_t *loadmodel; + +static mempool_t *mod_mempool; +static memexpandablearray_t models; + +static mempool_t* q3shaders_mem; +typedef struct q3shader_hash_entry_s +{ + q3shaderinfo_t shader; + struct q3shader_hash_entry_s* chain; +} q3shader_hash_entry_t; +#define Q3SHADER_HASH_SIZE 1021 +typedef struct q3shader_data_s +{ + memexpandablearray_t hash_entries; + q3shader_hash_entry_t hash[Q3SHADER_HASH_SIZE]; + memexpandablearray_t char_ptrs; +} q3shader_data_t; +static q3shader_data_t* q3shader_data; + +static void mod_start(void) +{ + int i, count; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + SCR_PushLoadingScreen(false, "Loading models", 1.0); + count = 0; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') + if (mod->used) + ++count; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') + if (mod->used) + { + SCR_PushLoadingScreen(true, mod->name, 1.0 / count); + Mod_LoadModel(mod, true, false); + SCR_PopLoadingScreen(false); + } + SCR_PopLoadingScreen(false); +} + +static void mod_shutdown(void) +{ + int i; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && (mod->loaded || mod->mempool)) + Mod_UnloadModel(mod); + + Mod_FreeQ3Shaders(); + Mod_Skeletal_FreeBuffers(); +} + +static void mod_newmap(void) +{ + msurface_t *surface; + int i, j, k, surfacenum, ssize, tsize; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->mempool) + { + for (j = 0;j < mod->num_textures && mod->data_textures;j++) + { + for (k = 0;k < mod->data_textures[j].numskinframes;k++) + R_SkinFrame_MarkUsed(mod->data_textures[j].skinframes[k]); + for (k = 0;k < mod->data_textures[j].backgroundnumskinframes;k++) + R_SkinFrame_MarkUsed(mod->data_textures[j].backgroundskinframes[k]); + } + if (mod->brush.solidskyskinframe) + R_SkinFrame_MarkUsed(mod->brush.solidskyskinframe); + if (mod->brush.alphaskyskinframe) + R_SkinFrame_MarkUsed(mod->brush.alphaskyskinframe); + } + } + + if (!cl_stainmaps_clearonload.integer) + return; + + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->mempool && mod->data_surfaces) + { + for (surfacenum = 0, surface = mod->data_surfaces;surfacenum < mod->num_surfaces;surfacenum++, surface++) + { + if (surface->lightmapinfo && surface->lightmapinfo->stainsamples) + { + ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; + tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; + memset(surface->lightmapinfo->stainsamples, 255, ssize * tsize * 3); + mod->brushq1.lightmapupdateflags[surfacenum] = true; + } + } + } + } +} + +/* +=============== +Mod_Init +=============== +*/ +static void Mod_Print(void); +static void Mod_Precache (void); +static void Mod_Decompile_f(void); +static void Mod_GenerateLightmaps_f(void); +void Mod_Init (void) +{ + mod_mempool = Mem_AllocPool("modelinfo", 0, NULL); + Mem_ExpandableArray_NewArray(&models, mod_mempool, sizeof(dp_model_t), 16); + + Mod_BrushInit(); + Mod_AliasInit(); + Mod_SpriteInit(); + + Cvar_RegisterVariable(&r_enableshadowvolumes); + Cvar_RegisterVariable(&r_mipskins); + Cvar_RegisterVariable(&r_mipnormalmaps); + Cvar_RegisterVariable(&mod_generatelightmaps_unitspersample); + Cvar_RegisterVariable(&mod_generatelightmaps_borderpixels); + Cvar_RegisterVariable(&mod_generatelightmaps_texturesize); + + Cvar_RegisterVariable(&mod_generatelightmaps_lightmapsamples); + Cvar_RegisterVariable(&mod_generatelightmaps_vertexsamples); + Cvar_RegisterVariable(&mod_generatelightmaps_gridsamples); + Cvar_RegisterVariable(&mod_generatelightmaps_lightmapradius); + Cvar_RegisterVariable(&mod_generatelightmaps_vertexradius); + Cvar_RegisterVariable(&mod_generatelightmaps_gridradius); + + Cmd_AddCommand ("modellist", Mod_Print, "prints a list of loaded models"); + Cmd_AddCommand ("modelprecache", Mod_Precache, "load a model"); + Cmd_AddCommand ("modeldecompile", Mod_Decompile_f, "exports a model in several formats for editing purposes"); + Cmd_AddCommand ("mod_generatelightmaps", Mod_GenerateLightmaps_f, "rebuilds lighting on current worldmodel"); +} + +void Mod_RenderInit(void) +{ + R_RegisterModule("Models", mod_start, mod_shutdown, mod_newmap, NULL, NULL); +} + +void Mod_UnloadModel (dp_model_t *mod) +{ + char name[MAX_QPATH]; + qboolean used; + dp_model_t *parentmodel; + + if (developer_loading.integer) + Con_Printf("unloading model %s\n", mod->name); + + strlcpy(name, mod->name, sizeof(name)); + parentmodel = mod->brush.parentmodel; + used = mod->used; + if (mod->mempool) + { + if (mod->surfmesh.vertex3fbuffer) + R_Mesh_DestroyMeshBuffer(mod->surfmesh.vertex3fbuffer); + mod->surfmesh.vertex3fbuffer = NULL; + if (mod->surfmesh.vertexmeshbuffer) + R_Mesh_DestroyMeshBuffer(mod->surfmesh.vertexmeshbuffer); + mod->surfmesh.vertexmeshbuffer = NULL; + if (mod->surfmesh.data_element3i_indexbuffer) + R_Mesh_DestroyMeshBuffer(mod->surfmesh.data_element3i_indexbuffer); + mod->surfmesh.data_element3i_indexbuffer = NULL; + if (mod->surfmesh.data_element3s_indexbuffer) + R_Mesh_DestroyMeshBuffer(mod->surfmesh.data_element3s_indexbuffer); + mod->surfmesh.data_element3s_indexbuffer = NULL; + if (mod->surfmesh.vbo_vertexbuffer) + R_Mesh_DestroyMeshBuffer(mod->surfmesh.vbo_vertexbuffer); + mod->surfmesh.vbo_vertexbuffer = NULL; + } + // free textures/memory attached to the model + R_FreeTexturePool(&mod->texturepool); + Mem_FreePool(&mod->mempool); + // clear the struct to make it available + memset(mod, 0, sizeof(dp_model_t)); + // restore the fields we want to preserve + strlcpy(mod->name, name, sizeof(mod->name)); + mod->brush.parentmodel = parentmodel; + mod->used = used; + mod->loaded = false; +} + +void R_Model_Null_Draw(entity_render_t *ent) +{ + return; +} + + +typedef void (*mod_framegroupify_parsegroups_t) (unsigned int i, int start, int len, float fps, qboolean loop, void *pass); + +int Mod_FrameGroupify_ParseGroups(const char *buf, mod_framegroupify_parsegroups_t cb, void *pass) +{ + const char *bufptr; + int start, len; + float fps; + unsigned int i; + qboolean loop; + + bufptr = buf; + i = 0; + for(;;) + { + // an anim scene! + if (!COM_ParseToken_Simple(&bufptr, true, false)) + break; + if (!strcmp(com_token, "\n")) + continue; // empty line + start = atoi(com_token); + if (!COM_ParseToken_Simple(&bufptr, true, false)) + break; + if (!strcmp(com_token, "\n")) + { + Con_Printf("framegroups file: missing number of frames\n"); + continue; + } + len = atoi(com_token); + if (!COM_ParseToken_Simple(&bufptr, true, false)) + break; + // we default to looping as it's usually wanted, so to NOT loop you append a 0 + if (strcmp(com_token, "\n")) + { + fps = atof(com_token); + if (!COM_ParseToken_Simple(&bufptr, true, false)) + break; + if (strcmp(com_token, "\n")) + loop = atoi(com_token) != 0; + else + loop = true; + } + else + { + fps = 20; + loop = true; + } + + if(cb) + cb(i, start, len, fps, loop, pass); + ++i; + } + + return i; +} + +void Mod_FrameGroupify_ParseGroups_Count (unsigned int i, int start, int len, float fps, qboolean loop, void *pass) +{ + unsigned int *cnt = (unsigned int *) pass; + ++*cnt; +} + +void Mod_FrameGroupify_ParseGroups_Store (unsigned int i, int start, int len, float fps, qboolean loop, void *pass) +{ + dp_model_t *mod = (dp_model_t *) pass; + animscene_t *anim = &mod->animscenes[i]; + dpsnprintf(anim->name, sizeof(anim[i].name), "groupified_%d_anim", i); + anim->firstframe = bound(0, start, mod->num_poses - 1); + anim->framecount = bound(1, len, mod->num_poses - anim->firstframe); + anim->framerate = max(1, fps); + anim->loop = !!loop; + //Con_Printf("frame group %d is %d %d %f %d\n", i, start, len, fps, loop); +} + +void Mod_FrameGroupify(dp_model_t *mod, const char *buf) +{ + unsigned int cnt; + + // 0. count + cnt = Mod_FrameGroupify_ParseGroups(buf, NULL, NULL); + if(!cnt) + { + Con_Printf("no scene found in framegroups file, aborting\n"); + return; + } + mod->numframes = cnt; + + // 1. reallocate + // (we do not free the previous animscenes, but model unloading will free the pool owning them, so it's okay) + mod->animscenes = (animscene_t *) Mem_Alloc(mod->mempool, sizeof(animscene_t) * mod->numframes); + + // 2. parse + Mod_FrameGroupify_ParseGroups(buf, Mod_FrameGroupify_ParseGroups_Store, mod); +} + +void Mod_FindPotentialDeforms(dp_model_t *mod) +{ + int i, j; + texture_t *texture; + mod->wantnormals = false; + mod->wanttangents = false; + for (i = 0;i < mod->num_textures;i++) + { + texture = mod->data_textures + i; + if (texture->tcgen.tcgen == Q3TCGEN_ENVIRONMENT) + mod->wantnormals = true; + for (j = 0;j < Q3MAXDEFORMS;j++) + { + if (texture->deforms[j].deform == Q3DEFORM_AUTOSPRITE) + { + mod->wanttangents = true; + mod->wantnormals = true; + break; + } + if (texture->deforms[j].deform != Q3DEFORM_NONE) + mod->wantnormals = true; + } + } +} + +/* +================== +Mod_LoadModel + +Loads a model +================== +*/ +dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk) +{ + int num; + unsigned int crc; + void *buf; + fs_offset_t filesize = 0; + + mod->used = true; + + if (mod->name[0] == '*') // submodel + return mod; + + if (!strcmp(mod->name, "null")) + { + if(mod->loaded) + return mod; + + if (mod->loaded || mod->mempool) + Mod_UnloadModel(mod); + + if (developer_loading.integer) + Con_Printf("loading model %s\n", mod->name); + + mod->used = true; + mod->crc = (unsigned int)-1; + mod->loaded = false; + + VectorClear(mod->normalmins); + VectorClear(mod->normalmaxs); + VectorClear(mod->yawmins); + VectorClear(mod->yawmaxs); + VectorClear(mod->rotatedmins); + VectorClear(mod->rotatedmaxs); + + mod->modeldatatypestring = "null"; + mod->type = mod_null; + mod->Draw = R_Model_Null_Draw; + mod->numframes = 2; + mod->numskins = 1; + + // no fatal errors occurred, so this model is ready to use. + mod->loaded = true; + + return mod; + } + + crc = 0; + buf = NULL; + + // even if the model is loaded it still may need reloading... + + // if it is not loaded or checkdisk is true we need to calculate the crc + if (!mod->loaded || checkdisk) + { + if (checkdisk && mod->loaded) + Con_DPrintf("checking model %s\n", mod->name); + buf = FS_LoadFile (mod->name, tempmempool, false, &filesize); + if (buf) + { + crc = CRC_Block((unsigned char *)buf, filesize); + // we need to reload the model if the crc does not match + if (mod->crc != crc) + mod->loaded = false; + } + } + + // if the model is already loaded and checks passed, just return + if (mod->loaded) + { + if (buf) + Mem_Free(buf); + return mod; + } + + if (developer_loading.integer) + Con_Printf("loading model %s\n", mod->name); + + SCR_PushLoadingScreen(true, mod->name, 1); + + // LordHavoc: unload the existing model in this slot (if there is one) + if (mod->loaded || mod->mempool) + Mod_UnloadModel(mod); + + // load the model + mod->used = true; + mod->crc = crc; + // errors can prevent the corresponding mod->loaded = true; + mod->loaded = false; + + // default model radius and bounding box (mainly for missing models) + mod->radius = 16; + VectorSet(mod->normalmins, -mod->radius, -mod->radius, -mod->radius); + VectorSet(mod->normalmaxs, mod->radius, mod->radius, mod->radius); + VectorSet(mod->yawmins, -mod->radius, -mod->radius, -mod->radius); + VectorSet(mod->yawmaxs, mod->radius, mod->radius, mod->radius); + VectorSet(mod->rotatedmins, -mod->radius, -mod->radius, -mod->radius); + VectorSet(mod->rotatedmaxs, mod->radius, mod->radius, mod->radius); + + if (!q3shaders_mem) + { + // load q3 shaders for the first time, or after a level change + Mod_LoadQ3Shaders(); + } + + if (buf) + { + char *bufend = (char *)buf + filesize; + + // all models use memory, so allocate a memory pool + mod->mempool = Mem_AllocPool(mod->name, 0, NULL); + + num = LittleLong(*((int *)buf)); + // call the apropriate loader + loadmodel = mod; + if (!strcasecmp(FS_FileExtension(mod->name), "obj")) Mod_OBJ_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDPO", 4)) Mod_IDP0_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDP2", 4)) Mod_IDP2_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDP3", 4)) Mod_IDP3_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDSP", 4)) Mod_IDSP_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDS2", 4)) Mod_IDS2_Load(mod, buf, bufend); + else if (!memcmp(buf, "IBSP", 4)) Mod_IBSP_Load(mod, buf, bufend); + else if (!memcmp(buf, "ZYMOTICMODEL", 12)) Mod_ZYMOTICMODEL_Load(mod, buf, bufend); + else if (!memcmp(buf, "DARKPLACESMODEL", 16)) Mod_DARKPLACESMODEL_Load(mod, buf, bufend); + else if (!memcmp(buf, "ACTRHEAD", 8)) Mod_PSKMODEL_Load(mod, buf, bufend); + else if (!memcmp(buf, "INTERQUAKEMODEL", 16)) Mod_INTERQUAKEMODEL_Load(mod, buf, bufend); + else if (strlen(mod->name) >= 4 && !strcmp(mod->name + strlen(mod->name) - 4, ".map")) Mod_MAP_Load(mod, buf, bufend); + else if (num == BSPVERSION || num == 30) Mod_Q1BSP_Load(mod, buf, bufend); + else Con_Printf("Mod_LoadModel: model \"%s\" is of unknown/unsupported type\n", mod->name); + Mem_Free(buf); + + Mod_FindPotentialDeforms(mod); + + buf = FS_LoadFile (va("%s.framegroups", mod->name), tempmempool, false, &filesize); + if(buf) + { + Mod_FrameGroupify(mod, (const char *)buf); + Mem_Free(buf); + } + + Mod_BuildVBOs(); + } + else if (crash) + { + // LordHavoc: Sys_Error was *ANNOYING* + Con_Printf ("Mod_LoadModel: %s not found\n", mod->name); + } + + // no fatal errors occurred, so this model is ready to use. + mod->loaded = true; + + SCR_PopLoadingScreen(false); + + return mod; +} + +void Mod_ClearUsed(void) +{ + int i; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0]) + mod->used = false; +} + +void Mod_PurgeUnused(void) +{ + int i; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && !mod->used) + { + Mod_UnloadModel(mod); + Mem_ExpandableArray_FreeRecord(&models, mod); + } + } +} + +/* +================== +Mod_FindName + +================== +*/ +dp_model_t *Mod_FindName(const char *name, const char *parentname) +{ + int i; + int nummodels; + dp_model_t *mod; + + if (!parentname) + parentname = ""; + + // if we're not dedicatd, the renderer calls will crash without video + Host_StartVideo(); + + nummodels = Mem_ExpandableArray_IndexRange(&models); + + if (!name[0]) + Host_Error ("Mod_ForName: NULL name"); + + // search the currently loaded models + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && !strcmp(mod->name, name) && ((!mod->brush.parentmodel && !parentname[0]) || (mod->brush.parentmodel && parentname[0] && !strcmp(mod->brush.parentmodel->name, parentname)))) + { + mod->used = true; + return mod; + } + } + + // no match found, create a new one + mod = (dp_model_t *) Mem_ExpandableArray_AllocRecord(&models); + strlcpy(mod->name, name, sizeof(mod->name)); + if (parentname[0]) + mod->brush.parentmodel = Mod_FindName(parentname, NULL); + else + mod->brush.parentmodel = NULL; + mod->loaded = false; + mod->used = true; + return mod; +} + +/* +================== +Mod_ForName + +Loads in a model for the given name +================== +*/ +dp_model_t *Mod_ForName(const char *name, qboolean crash, qboolean checkdisk, const char *parentname) +{ + dp_model_t *model; + model = Mod_FindName(name, parentname); + if (!model->loaded || checkdisk) + Mod_LoadModel(model, crash, checkdisk); + return model; +} + +/* +================== +Mod_Reload + +Reloads all models if they have changed +================== +*/ +void Mod_Reload(void) +{ + int i, count; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + SCR_PushLoadingScreen(false, "Reloading models", 1.0); + count = 0; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*' && mod->used) + ++count; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*' && mod->used) + { + SCR_PushLoadingScreen(true, mod->name, 1.0 / count); + Mod_LoadModel(mod, true, true); + SCR_PopLoadingScreen(false); + } + SCR_PopLoadingScreen(false); +} + +unsigned char *mod_base; + + +//============================================================================= + +/* +================ +Mod_Print +================ +*/ +static void Mod_Print(void) +{ + int i; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + Con_Print("Loaded models:\n"); + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') + { + if (mod->brush.numsubmodels) + Con_Printf("%4iK %s (%i submodels)\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name, mod->brush.numsubmodels); + else + Con_Printf("%4iK %s\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name); + } + } +} + +/* +================ +Mod_Precache +================ +*/ +static void Mod_Precache(void) +{ + if (Cmd_Argc() == 2) + Mod_ForName(Cmd_Argv(1), false, true, Cmd_Argv(1)[0] == '*' ? cl.model_name[1] : NULL); + else + Con_Print("usage: modelprecache \n"); +} + +int Mod_BuildVertexRemapTableFromElements(int numelements, const int *elements, int numvertices, int *remapvertices) +{ + int i, count; + unsigned char *used; + used = (unsigned char *)Mem_Alloc(tempmempool, numvertices); + memset(used, 0, numvertices); + for (i = 0;i < numelements;i++) + used[elements[i]] = 1; + for (i = 0, count = 0;i < numvertices;i++) + remapvertices[i] = used[i] ? count++ : -1; + Mem_Free(used); + return count; +} + +#if 1 +// fast way, using an edge hash +#define TRIANGLEEDGEHASH 8192 +void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles) +{ + int i, j, p, e1, e2, *n, hashindex, count, match; + const int *e; + typedef struct edgehashentry_s + { + struct edgehashentry_s *next; + int triangle; + int element[2]; + } + edgehashentry_t; + static edgehashentry_t **edgehash; + edgehashentry_t *edgehashentries, *hash; + if (!numtriangles) + return; + edgehash = (edgehashentry_t **)Mem_Alloc(tempmempool, TRIANGLEEDGEHASH * sizeof(*edgehash)); + // if there are too many triangles for the stack array, allocate larger buffer + edgehashentries = (edgehashentry_t *)Mem_Alloc(tempmempool, numtriangles * 3 * sizeof(edgehashentry_t)); + // find neighboring triangles + for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) + { + for (j = 0, p = 2;j < 3;p = j, j++) + { + e1 = e[p]; + e2 = e[j]; + // this hash index works for both forward and backward edges + hashindex = (unsigned int)(e1 + e2) % TRIANGLEEDGEHASH; + hash = edgehashentries + i * 3 + j; + hash->next = edgehash[hashindex]; + edgehash[hashindex] = hash; + hash->triangle = i; + hash->element[0] = e1; + hash->element[1] = e2; + } + } + for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) + { + for (j = 0, p = 2;j < 3;p = j, j++) + { + e1 = e[p]; + e2 = e[j]; + // this hash index works for both forward and backward edges + hashindex = (unsigned int)(e1 + e2) % TRIANGLEEDGEHASH; + count = 0; + match = -1; + for (hash = edgehash[hashindex];hash;hash = hash->next) + { + if (hash->element[0] == e2 && hash->element[1] == e1) + { + if (hash->triangle != i) + match = hash->triangle; + count++; + } + else if ((hash->element[0] == e1 && hash->element[1] == e2)) + count++; + } + // detect edges shared by three triangles and make them seams + if (count > 2) + match = -1; + n[p] = match; + } + + // also send a keepalive here (this can take a while too!) + CL_KeepaliveMessage(false); + } + // free the allocated buffer + Mem_Free(edgehashentries); + Mem_Free(edgehash); +} +#else +// very slow but simple way +static int Mod_FindTriangleWithEdge(const int *elements, int numtriangles, int start, int end, int ignore) +{ + int i, match, count; + count = 0; + match = -1; + for (i = 0;i < numtriangles;i++, elements += 3) + { + if ((elements[0] == start && elements[1] == end) + || (elements[1] == start && elements[2] == end) + || (elements[2] == start && elements[0] == end)) + { + if (i != ignore) + match = i; + count++; + } + else if ((elements[1] == start && elements[0] == end) + || (elements[2] == start && elements[1] == end) + || (elements[0] == start && elements[2] == end)) + count++; + } + // detect edges shared by three triangles and make them seams + if (count > 2) + match = -1; + return match; +} + +void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles) +{ + int i, *n; + const int *e; + for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) + { + n[0] = Mod_FindTriangleWithEdge(elements, numtriangles, e[1], e[0], i); + n[1] = Mod_FindTriangleWithEdge(elements, numtriangles, e[2], e[1], i); + n[2] = Mod_FindTriangleWithEdge(elements, numtriangles, e[0], e[2], i); + } +} +#endif + +void Mod_ValidateElements(int *elements, int numtriangles, int firstvertex, int numverts, const char *filename, int fileline) +{ + int i, warned = false, endvertex = firstvertex + numverts; + for (i = 0;i < numtriangles * 3;i++) + { + if (elements[i] < firstvertex || elements[i] >= endvertex) + { + if (!warned) + { + warned = true; + Con_Printf("Mod_ValidateElements: out of bounds elements detected at %s:%d\n", filename, fileline); + } + elements[i] = firstvertex; + } + } +} + +// warning: this is an expensive function! +void Mod_BuildNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const int *elements, float *normal3f, qboolean areaweighting) +{ + int i, j; + const int *element; + float *vectorNormal; + float areaNormal[3]; + // clear the vectors + memset(normal3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); + // process each vertex of each triangle and accumulate the results + // use area-averaging, to make triangles with a big area have a bigger + // weighting on the vertex normal than triangles with a small area + // to do so, just add the 'normals' together (the bigger the area + // the greater the length of the normal is + element = elements; + for (i = 0; i < numtriangles; i++, element += 3) + { + TriangleNormal( + vertex3f + element[0] * 3, + vertex3f + element[1] * 3, + vertex3f + element[2] * 3, + areaNormal + ); + + if (!areaweighting) + VectorNormalize(areaNormal); + + for (j = 0;j < 3;j++) + { + vectorNormal = normal3f + element[j] * 3; + vectorNormal[0] += areaNormal[0]; + vectorNormal[1] += areaNormal[1]; + vectorNormal[2] += areaNormal[2]; + } + } + // and just normalize the accumulated vertex normal in the end + vectorNormal = normal3f + 3 * firstvertex; + for (i = 0; i < numvertices; i++, vectorNormal += 3) + VectorNormalize(vectorNormal); +} + +void Mod_BuildBumpVectors(const float *v0, const float *v1, const float *v2, const float *tc0, const float *tc1, const float *tc2, float *svector3f, float *tvector3f, float *normal3f) +{ + float f, tangentcross[3], v10[3], v20[3], tc10[2], tc20[2]; + // 79 add/sub/negate/multiply (1 cycle), 1 compare (3 cycle?), total cycles not counting load/store/exchange roughly 82 cycles + // 6 add, 28 subtract, 39 multiply, 1 compare, 50% chance of 6 negates + + // 6 multiply, 9 subtract + VectorSubtract(v1, v0, v10); + VectorSubtract(v2, v0, v20); + normal3f[0] = v20[1] * v10[2] - v20[2] * v10[1]; + normal3f[1] = v20[2] * v10[0] - v20[0] * v10[2]; + normal3f[2] = v20[0] * v10[1] - v20[1] * v10[0]; + // 12 multiply, 10 subtract + tc10[1] = tc1[1] - tc0[1]; + tc20[1] = tc2[1] - tc0[1]; + svector3f[0] = tc10[1] * v20[0] - tc20[1] * v10[0]; + svector3f[1] = tc10[1] * v20[1] - tc20[1] * v10[1]; + svector3f[2] = tc10[1] * v20[2] - tc20[1] * v10[2]; + tc10[0] = tc1[0] - tc0[0]; + tc20[0] = tc2[0] - tc0[0]; + tvector3f[0] = tc10[0] * v20[0] - tc20[0] * v10[0]; + tvector3f[1] = tc10[0] * v20[1] - tc20[0] * v10[1]; + tvector3f[2] = tc10[0] * v20[2] - tc20[0] * v10[2]; + // 12 multiply, 4 add, 6 subtract + f = DotProduct(svector3f, normal3f); + svector3f[0] -= f * normal3f[0]; + svector3f[1] -= f * normal3f[1]; + svector3f[2] -= f * normal3f[2]; + f = DotProduct(tvector3f, normal3f); + tvector3f[0] -= f * normal3f[0]; + tvector3f[1] -= f * normal3f[1]; + tvector3f[2] -= f * normal3f[2]; + // if texture is mapped the wrong way (counterclockwise), the tangents + // have to be flipped, this is detected by calculating a normal from the + // two tangents, and seeing if it is opposite the surface normal + // 9 multiply, 2 add, 3 subtract, 1 compare, 50% chance of: 6 negates + CrossProduct(tvector3f, svector3f, tangentcross); + if (DotProduct(tangentcross, normal3f) < 0) + { + VectorNegate(svector3f, svector3f); + VectorNegate(tvector3f, tvector3f); + } +} + +// warning: this is a very expensive function! +void Mod_BuildTextureVectorsFromNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const float *texcoord2f, const float *normal3f, const int *elements, float *svector3f, float *tvector3f, qboolean areaweighting) +{ + int i, tnum; + float sdir[3], tdir[3], normal[3], *sv, *tv; + const float *v0, *v1, *v2, *tc0, *tc1, *tc2, *n; + float f, tangentcross[3], v10[3], v20[3], tc10[2], tc20[2]; + const int *e; + // clear the vectors + memset(svector3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); + memset(tvector3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); + // process each vertex of each triangle and accumulate the results + for (tnum = 0, e = elements;tnum < numtriangles;tnum++, e += 3) + { + v0 = vertex3f + e[0] * 3; + v1 = vertex3f + e[1] * 3; + v2 = vertex3f + e[2] * 3; + tc0 = texcoord2f + e[0] * 2; + tc1 = texcoord2f + e[1] * 2; + tc2 = texcoord2f + e[2] * 2; + + // 79 add/sub/negate/multiply (1 cycle), 1 compare (3 cycle?), total cycles not counting load/store/exchange roughly 82 cycles + // 6 add, 28 subtract, 39 multiply, 1 compare, 50% chance of 6 negates + + // calculate the edge directions and surface normal + // 6 multiply, 9 subtract + VectorSubtract(v1, v0, v10); + VectorSubtract(v2, v0, v20); + normal[0] = v20[1] * v10[2] - v20[2] * v10[1]; + normal[1] = v20[2] * v10[0] - v20[0] * v10[2]; + normal[2] = v20[0] * v10[1] - v20[1] * v10[0]; + + // calculate the tangents + // 12 multiply, 10 subtract + tc10[1] = tc1[1] - tc0[1]; + tc20[1] = tc2[1] - tc0[1]; + sdir[0] = tc10[1] * v20[0] - tc20[1] * v10[0]; + sdir[1] = tc10[1] * v20[1] - tc20[1] * v10[1]; + sdir[2] = tc10[1] * v20[2] - tc20[1] * v10[2]; + tc10[0] = tc1[0] - tc0[0]; + tc20[0] = tc2[0] - tc0[0]; + tdir[0] = tc10[0] * v20[0] - tc20[0] * v10[0]; + tdir[1] = tc10[0] * v20[1] - tc20[0] * v10[1]; + tdir[2] = tc10[0] * v20[2] - tc20[0] * v10[2]; + + // if texture is mapped the wrong way (counterclockwise), the tangents + // have to be flipped, this is detected by calculating a normal from the + // two tangents, and seeing if it is opposite the surface normal + // 9 multiply, 2 add, 3 subtract, 1 compare, 50% chance of: 6 negates + CrossProduct(tdir, sdir, tangentcross); + if (DotProduct(tangentcross, normal) < 0) + { + VectorNegate(sdir, sdir); + VectorNegate(tdir, tdir); + } + + if (!areaweighting) + { + VectorNormalize(sdir); + VectorNormalize(tdir); + } + for (i = 0;i < 3;i++) + { + VectorAdd(svector3f + e[i]*3, sdir, svector3f + e[i]*3); + VectorAdd(tvector3f + e[i]*3, tdir, tvector3f + e[i]*3); + } + } + // make the tangents completely perpendicular to the surface normal, and + // then normalize them + // 16 assignments, 2 divide, 2 sqrt, 2 negates, 14 adds, 24 multiplies + for (i = 0, sv = svector3f + 3 * firstvertex, tv = tvector3f + 3 * firstvertex, n = normal3f + 3 * firstvertex;i < numvertices;i++, sv += 3, tv += 3, n += 3) + { + f = -DotProduct(sv, n); + VectorMA(sv, f, n, sv); + VectorNormalize(sv); + f = -DotProduct(tv, n); + VectorMA(tv, f, n, tv); + VectorNormalize(tv); + } +} + +void Mod_AllocSurfMesh(mempool_t *mempool, int numvertices, int numtriangles, qboolean lightmapoffsets, qboolean vertexcolors, qboolean neighbors) +{ + unsigned char *data; + data = (unsigned char *)Mem_Alloc(mempool, numvertices * (3 + 3 + 3 + 3 + 2 + 2 + (vertexcolors ? 4 : 0)) * sizeof(float) + numvertices * (lightmapoffsets ? 1 : 0) * sizeof(int) + numtriangles * (3 + (neighbors ? 3 : 0)) * sizeof(int) + (numvertices <= 65536 ? numtriangles * sizeof(unsigned short[3]) : 0)); + loadmodel->surfmesh.num_vertices = numvertices; + loadmodel->surfmesh.num_triangles = numtriangles; + if (loadmodel->surfmesh.num_vertices) + { + loadmodel->surfmesh.data_vertex3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_svector3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_tvector3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_normal3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data, data += sizeof(float[2]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_texcoordlightmap2f = (float *)data, data += sizeof(float[2]) * loadmodel->surfmesh.num_vertices; + if (vertexcolors) + loadmodel->surfmesh.data_lightmapcolor4f = (float *)data, data += sizeof(float[4]) * loadmodel->surfmesh.num_vertices; + if (lightmapoffsets) + loadmodel->surfmesh.data_lightmapoffsets = (int *)data, data += sizeof(int) * loadmodel->surfmesh.num_vertices; + } + if (loadmodel->surfmesh.num_triangles) + { + loadmodel->surfmesh.data_element3i = (int *)data, data += sizeof(int[3]) * loadmodel->surfmesh.num_triangles; + if (neighbors) + loadmodel->surfmesh.data_neighbor3i = (int *)data, data += sizeof(int[3]) * loadmodel->surfmesh.num_triangles; + if (loadmodel->surfmesh.num_vertices <= 65536) + loadmodel->surfmesh.data_element3s = (unsigned short *)data, data += sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles; + } +} + +shadowmesh_t *Mod_ShadowMesh_Alloc(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable) +{ + shadowmesh_t *newmesh; + unsigned char *data; + int size; + size = sizeof(shadowmesh_t); + size += maxverts * sizeof(float[3]); + if (light) + size += maxverts * sizeof(float[11]); + size += maxtriangles * sizeof(int[3]); + if (maxverts <= 65536) + size += maxtriangles * sizeof(unsigned short[3]); + if (neighbors) + size += maxtriangles * sizeof(int[3]); + if (expandable) + size += SHADOWMESHVERTEXHASH * sizeof(shadowmeshvertexhash_t *) + maxverts * sizeof(shadowmeshvertexhash_t); + data = (unsigned char *)Mem_Alloc(mempool, size); + newmesh = (shadowmesh_t *)data;data += sizeof(*newmesh); + newmesh->map_diffuse = map_diffuse; + newmesh->map_specular = map_specular; + newmesh->map_normal = map_normal; + newmesh->maxverts = maxverts; + newmesh->maxtriangles = maxtriangles; + newmesh->numverts = 0; + newmesh->numtriangles = 0; + memset(newmesh->sideoffsets, 0, sizeof(newmesh->sideoffsets)); + memset(newmesh->sidetotals, 0, sizeof(newmesh->sidetotals)); + + newmesh->vertex3f = (float *)data;data += maxverts * sizeof(float[3]); + if (light) + { + newmesh->svector3f = (float *)data;data += maxverts * sizeof(float[3]); + newmesh->tvector3f = (float *)data;data += maxverts * sizeof(float[3]); + newmesh->normal3f = (float *)data;data += maxverts * sizeof(float[3]); + newmesh->texcoord2f = (float *)data;data += maxverts * sizeof(float[2]); + } + newmesh->element3i = (int *)data;data += maxtriangles * sizeof(int[3]); + if (neighbors) + { + newmesh->neighbor3i = (int *)data;data += maxtriangles * sizeof(int[3]); + } + if (expandable) + { + newmesh->vertexhashtable = (shadowmeshvertexhash_t **)data;data += SHADOWMESHVERTEXHASH * sizeof(shadowmeshvertexhash_t *); + newmesh->vertexhashentries = (shadowmeshvertexhash_t *)data;data += maxverts * sizeof(shadowmeshvertexhash_t); + } + if (maxverts <= 65536) + newmesh->element3s = (unsigned short *)data;data += maxtriangles * sizeof(unsigned short[3]); + return newmesh; +} + +shadowmesh_t *Mod_ShadowMesh_ReAlloc(mempool_t *mempool, shadowmesh_t *oldmesh, int light, int neighbors) +{ + shadowmesh_t *newmesh; + newmesh = Mod_ShadowMesh_Alloc(mempool, oldmesh->numverts, oldmesh->numtriangles, oldmesh->map_diffuse, oldmesh->map_specular, oldmesh->map_normal, light, neighbors, false); + newmesh->numverts = oldmesh->numverts; + newmesh->numtriangles = oldmesh->numtriangles; + memcpy(newmesh->sideoffsets, oldmesh->sideoffsets, sizeof(oldmesh->sideoffsets)); + memcpy(newmesh->sidetotals, oldmesh->sidetotals, sizeof(oldmesh->sidetotals)); + + memcpy(newmesh->vertex3f, oldmesh->vertex3f, oldmesh->numverts * sizeof(float[3])); + if (newmesh->svector3f && oldmesh->svector3f) + { + memcpy(newmesh->svector3f, oldmesh->svector3f, oldmesh->numverts * sizeof(float[3])); + memcpy(newmesh->tvector3f, oldmesh->tvector3f, oldmesh->numverts * sizeof(float[3])); + memcpy(newmesh->normal3f, oldmesh->normal3f, oldmesh->numverts * sizeof(float[3])); + memcpy(newmesh->texcoord2f, oldmesh->texcoord2f, oldmesh->numverts * sizeof(float[2])); + } + memcpy(newmesh->element3i, oldmesh->element3i, oldmesh->numtriangles * sizeof(int[3])); + if (newmesh->neighbor3i && oldmesh->neighbor3i) + memcpy(newmesh->neighbor3i, oldmesh->neighbor3i, oldmesh->numtriangles * sizeof(int[3])); + return newmesh; +} + +int Mod_ShadowMesh_AddVertex(shadowmesh_t *mesh, float *vertex14f) +{ + int hashindex, vnum; + shadowmeshvertexhash_t *hash; + // this uses prime numbers intentionally + hashindex = (unsigned int) (vertex14f[0] * 2003 + vertex14f[1] * 4001 + vertex14f[2] * 7919) % SHADOWMESHVERTEXHASH; + for (hash = mesh->vertexhashtable[hashindex];hash;hash = hash->next) + { + vnum = (hash - mesh->vertexhashentries); + if ((mesh->vertex3f == NULL || (mesh->vertex3f[vnum * 3 + 0] == vertex14f[0] && mesh->vertex3f[vnum * 3 + 1] == vertex14f[1] && mesh->vertex3f[vnum * 3 + 2] == vertex14f[2])) + && (mesh->svector3f == NULL || (mesh->svector3f[vnum * 3 + 0] == vertex14f[3] && mesh->svector3f[vnum * 3 + 1] == vertex14f[4] && mesh->svector3f[vnum * 3 + 2] == vertex14f[5])) + && (mesh->tvector3f == NULL || (mesh->tvector3f[vnum * 3 + 0] == vertex14f[6] && mesh->tvector3f[vnum * 3 + 1] == vertex14f[7] && mesh->tvector3f[vnum * 3 + 2] == vertex14f[8])) + && (mesh->normal3f == NULL || (mesh->normal3f[vnum * 3 + 0] == vertex14f[9] && mesh->normal3f[vnum * 3 + 1] == vertex14f[10] && mesh->normal3f[vnum * 3 + 2] == vertex14f[11])) + && (mesh->texcoord2f == NULL || (mesh->texcoord2f[vnum * 2 + 0] == vertex14f[12] && mesh->texcoord2f[vnum * 2 + 1] == vertex14f[13]))) + return hash - mesh->vertexhashentries; + } + vnum = mesh->numverts++; + hash = mesh->vertexhashentries + vnum; + hash->next = mesh->vertexhashtable[hashindex]; + mesh->vertexhashtable[hashindex] = hash; + if (mesh->vertex3f) {mesh->vertex3f[vnum * 3 + 0] = vertex14f[0];mesh->vertex3f[vnum * 3 + 1] = vertex14f[1];mesh->vertex3f[vnum * 3 + 2] = vertex14f[2];} + if (mesh->svector3f) {mesh->svector3f[vnum * 3 + 0] = vertex14f[3];mesh->svector3f[vnum * 3 + 1] = vertex14f[4];mesh->svector3f[vnum * 3 + 2] = vertex14f[5];} + if (mesh->tvector3f) {mesh->tvector3f[vnum * 3 + 0] = vertex14f[6];mesh->tvector3f[vnum * 3 + 1] = vertex14f[7];mesh->tvector3f[vnum * 3 + 2] = vertex14f[8];} + if (mesh->normal3f) {mesh->normal3f[vnum * 3 + 0] = vertex14f[9];mesh->normal3f[vnum * 3 + 1] = vertex14f[10];mesh->normal3f[vnum * 3 + 2] = vertex14f[11];} + if (mesh->texcoord2f) {mesh->texcoord2f[vnum * 2 + 0] = vertex14f[12];mesh->texcoord2f[vnum * 2 + 1] = vertex14f[13];} + return vnum; +} + +void Mod_ShadowMesh_AddTriangle(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, float *vertex14f) +{ + if (mesh->numtriangles == 0) + { + // set the properties on this empty mesh to be more favorable... + // (note: this case only occurs for the first triangle added to a new mesh chain) + mesh->map_diffuse = map_diffuse; + mesh->map_specular = map_specular; + mesh->map_normal = map_normal; + } + while (mesh->map_diffuse != map_diffuse || mesh->map_specular != map_specular || mesh->map_normal != map_normal || mesh->numverts + 3 > mesh->maxverts || mesh->numtriangles + 1 > mesh->maxtriangles) + { + if (mesh->next == NULL) + mesh->next = Mod_ShadowMesh_Alloc(mempool, max(mesh->maxverts, 300), max(mesh->maxtriangles, 100), map_diffuse, map_specular, map_normal, mesh->svector3f != NULL, mesh->neighbor3i != NULL, true); + mesh = mesh->next; + } + mesh->element3i[mesh->numtriangles * 3 + 0] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 0); + mesh->element3i[mesh->numtriangles * 3 + 1] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 1); + mesh->element3i[mesh->numtriangles * 3 + 2] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 2); + mesh->numtriangles++; +} + +void Mod_ShadowMesh_AddMesh(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *texcoord2f, int numtris, const int *element3i) +{ + int i, j, e; + float vbuf[3*14], *v; + memset(vbuf, 0, sizeof(vbuf)); + for (i = 0;i < numtris;i++) + { + for (j = 0, v = vbuf;j < 3;j++, v += 14) + { + e = *element3i++; + if (vertex3f) + { + v[0] = vertex3f[e * 3 + 0]; + v[1] = vertex3f[e * 3 + 1]; + v[2] = vertex3f[e * 3 + 2]; + } + if (svector3f) + { + v[3] = svector3f[e * 3 + 0]; + v[4] = svector3f[e * 3 + 1]; + v[5] = svector3f[e * 3 + 2]; + } + if (tvector3f) + { + v[6] = tvector3f[e * 3 + 0]; + v[7] = tvector3f[e * 3 + 1]; + v[8] = tvector3f[e * 3 + 2]; + } + if (normal3f) + { + v[9] = normal3f[e * 3 + 0]; + v[10] = normal3f[e * 3 + 1]; + v[11] = normal3f[e * 3 + 2]; + } + if (texcoord2f) + { + v[12] = texcoord2f[e * 2 + 0]; + v[13] = texcoord2f[e * 2 + 1]; + } + } + Mod_ShadowMesh_AddTriangle(mempool, mesh, map_diffuse, map_specular, map_normal, vbuf); + } + + // the triangle calculation can take a while, so let's do a keepalive here + CL_KeepaliveMessage(false); +} + +shadowmesh_t *Mod_ShadowMesh_Begin(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable) +{ + // the preparation before shadow mesh initialization can take a while, so let's do a keepalive here + CL_KeepaliveMessage(false); + + return Mod_ShadowMesh_Alloc(mempool, maxverts, maxtriangles, map_diffuse, map_specular, map_normal, light, neighbors, expandable); +} + +static void Mod_ShadowMesh_CreateVBOs(shadowmesh_t *mesh, mempool_t *mempool) +{ + if (!mesh->numverts) + return; + + // build r_vertexmesh_t array + // (compressed interleaved array for D3D) + if (!mesh->vertexmesh && mesh->texcoord2f && vid.useinterleavedarrays) + { + int vertexindex; + int numvertices = mesh->numverts; + r_vertexmesh_t *vertexmesh; + mesh->vertexmesh = vertexmesh = (r_vertexmesh_t*)Mem_Alloc(mempool, numvertices * sizeof(*mesh->vertexmesh)); + for (vertexindex = 0;vertexindex < numvertices;vertexindex++, vertexmesh++) + { + VectorCopy(mesh->vertex3f + 3*vertexindex, vertexmesh->vertex3f); + VectorScale(mesh->svector3f + 3*vertexindex, 1.0f, vertexmesh->svector3f); + VectorScale(mesh->tvector3f + 3*vertexindex, 1.0f, vertexmesh->tvector3f); + VectorScale(mesh->normal3f + 3*vertexindex, 1.0f, vertexmesh->normal3f); + Vector2Copy(mesh->texcoord2f + 2*vertexindex, vertexmesh->texcoordtexture2f); + } + } + + // upload r_vertexmesh_t array as a buffer + if (mesh->vertexmesh && !mesh->vertexmeshbuffer) + mesh->vertexmeshbuffer = R_Mesh_CreateMeshBuffer(mesh->vertexmesh, mesh->numverts * sizeof(*mesh->vertexmesh), loadmodel->name, false, false, false); + + // upload vertex3f array as a buffer + if (mesh->vertex3f && !mesh->vertex3fbuffer) + mesh->vertex3fbuffer = R_Mesh_CreateMeshBuffer(mesh->vertex3f, mesh->numverts * sizeof(float[3]), loadmodel->name, false, false, false); + + // upload short indices as a buffer + if (mesh->element3s && !mesh->element3s_indexbuffer) + mesh->element3s_indexbuffer = R_Mesh_CreateMeshBuffer(mesh->element3s, mesh->numtriangles * sizeof(short[3]), loadmodel->name, true, false, true); + + // upload int indices as a buffer + if (mesh->element3i && !mesh->element3i_indexbuffer && !mesh->element3s) + mesh->element3i_indexbuffer = R_Mesh_CreateMeshBuffer(mesh->element3i, mesh->numtriangles * sizeof(int[3]), loadmodel->name, true, false, false); + + // vertex buffer is several arrays and we put them in the same buffer + // + // is this wise? the texcoordtexture2f array is used with dynamic + // vertex/svector/tvector/normal when rendering animated models, on the + // other hand animated models don't use a lot of vertices anyway... + if (!mesh->vbo_vertexbuffer && !vid.useinterleavedarrays) + { + size_t size; + unsigned char *mem; + size = 0; + mesh->vbooffset_vertex3f = size;if (mesh->vertex3f ) size += mesh->numverts * sizeof(float[3]); + mesh->vbooffset_svector3f = size;if (mesh->svector3f ) size += mesh->numverts * sizeof(float[3]); + mesh->vbooffset_tvector3f = size;if (mesh->tvector3f ) size += mesh->numverts * sizeof(float[3]); + mesh->vbooffset_normal3f = size;if (mesh->normal3f ) size += mesh->numverts * sizeof(float[3]); + mesh->vbooffset_texcoord2f = size;if (mesh->texcoord2f ) size += mesh->numverts * sizeof(float[2]); + mem = (unsigned char *)Mem_Alloc(tempmempool, size); + if (mesh->vertex3f ) memcpy(mem + mesh->vbooffset_vertex3f , mesh->vertex3f , mesh->numverts * sizeof(float[3])); + if (mesh->svector3f ) memcpy(mem + mesh->vbooffset_svector3f , mesh->svector3f , mesh->numverts * sizeof(float[3])); + if (mesh->tvector3f ) memcpy(mem + mesh->vbooffset_tvector3f , mesh->tvector3f , mesh->numverts * sizeof(float[3])); + if (mesh->normal3f ) memcpy(mem + mesh->vbooffset_normal3f , mesh->normal3f , mesh->numverts * sizeof(float[3])); + if (mesh->texcoord2f ) memcpy(mem + mesh->vbooffset_texcoord2f , mesh->texcoord2f , mesh->numverts * sizeof(float[2])); + mesh->vbo_vertexbuffer = R_Mesh_CreateMeshBuffer(mem, size, "shadowmesh", false, false, false); + Mem_Free(mem); + } +} + +shadowmesh_t *Mod_ShadowMesh_Finish(mempool_t *mempool, shadowmesh_t *firstmesh, qboolean light, qboolean neighbors, qboolean createvbo) +{ + shadowmesh_t *mesh, *newmesh, *nextmesh; + // reallocate meshs to conserve space + for (mesh = firstmesh, firstmesh = NULL;mesh;mesh = nextmesh) + { + nextmesh = mesh->next; + if (mesh->numverts >= 3 && mesh->numtriangles >= 1) + { + newmesh = Mod_ShadowMesh_ReAlloc(mempool, mesh, light, neighbors); + newmesh->next = firstmesh; + firstmesh = newmesh; + if (newmesh->element3s) + { + int i; + for (i = 0;i < newmesh->numtriangles*3;i++) + newmesh->element3s[i] = newmesh->element3i[i]; + } + if (createvbo) + Mod_ShadowMesh_CreateVBOs(newmesh, mempool); + } + Mem_Free(mesh); + } + + // this can take a while, so let's do a keepalive here + CL_KeepaliveMessage(false); + + return firstmesh; +} + +void Mod_ShadowMesh_CalcBBox(shadowmesh_t *firstmesh, vec3_t mins, vec3_t maxs, vec3_t center, float *radius) +{ + int i; + shadowmesh_t *mesh; + vec3_t nmins, nmaxs, ncenter, temp; + float nradius2, dist2, *v; + VectorClear(nmins); + VectorClear(nmaxs); + // calculate bbox + for (mesh = firstmesh;mesh;mesh = mesh->next) + { + if (mesh == firstmesh) + { + VectorCopy(mesh->vertex3f, nmins); + VectorCopy(mesh->vertex3f, nmaxs); + } + for (i = 0, v = mesh->vertex3f;i < mesh->numverts;i++, v += 3) + { + if (nmins[0] > v[0]) nmins[0] = v[0];if (nmaxs[0] < v[0]) nmaxs[0] = v[0]; + if (nmins[1] > v[1]) nmins[1] = v[1];if (nmaxs[1] < v[1]) nmaxs[1] = v[1]; + if (nmins[2] > v[2]) nmins[2] = v[2];if (nmaxs[2] < v[2]) nmaxs[2] = v[2]; + } + } + // calculate center and radius + ncenter[0] = (nmins[0] + nmaxs[0]) * 0.5f; + ncenter[1] = (nmins[1] + nmaxs[1]) * 0.5f; + ncenter[2] = (nmins[2] + nmaxs[2]) * 0.5f; + nradius2 = 0; + for (mesh = firstmesh;mesh;mesh = mesh->next) + { + for (i = 0, v = mesh->vertex3f;i < mesh->numverts;i++, v += 3) + { + VectorSubtract(v, ncenter, temp); + dist2 = DotProduct(temp, temp); + if (nradius2 < dist2) + nradius2 = dist2; + } + } + // return data + if (mins) + VectorCopy(nmins, mins); + if (maxs) + VectorCopy(nmaxs, maxs); + if (center) + VectorCopy(ncenter, center); + if (radius) + *radius = sqrt(nradius2); +} + +void Mod_ShadowMesh_Free(shadowmesh_t *mesh) +{ + shadowmesh_t *nextmesh; + for (;mesh;mesh = nextmesh) + { + if (mesh->vertex3fbuffer) + R_Mesh_DestroyMeshBuffer(mesh->vertex3fbuffer); + if (mesh->vertexmeshbuffer) + R_Mesh_DestroyMeshBuffer(mesh->vertexmeshbuffer); + if (mesh->element3i_indexbuffer) + R_Mesh_DestroyMeshBuffer(mesh->element3i_indexbuffer); + if (mesh->element3s_indexbuffer) + R_Mesh_DestroyMeshBuffer(mesh->element3s_indexbuffer); + if (mesh->vbo_vertexbuffer) + R_Mesh_DestroyMeshBuffer(mesh->vbo_vertexbuffer); + nextmesh = mesh->next; + Mem_Free(mesh); + } +} + +void Mod_CreateCollisionMesh(dp_model_t *mod) +{ + int k, numcollisionmeshtriangles; + qboolean usesinglecollisionmesh = false; + const msurface_t *surface = NULL; + + mempool_t *mempool = mod->mempool; + if (!mempool && mod->brush.parentmodel) + mempool = mod->brush.parentmodel->mempool; + // make a single combined collision mesh for physics engine use + // TODO rewrite this to use the collision brushes as source, to fix issues with e.g. common/caulk which creates no drawsurface + numcollisionmeshtriangles = 0; + for (k = 0;k < mod->nummodelsurfaces;k++) + { + surface = mod->data_surfaces + mod->firstmodelsurface + k; + if (!strcmp(surface->texture->name, "collision")) // found collision mesh + { + usesinglecollisionmesh = true; + numcollisionmeshtriangles = surface->num_triangles; + break; + } + if (!(surface->texture->supercontents & SUPERCONTENTS_SOLID)) + continue; + numcollisionmeshtriangles += surface->num_triangles; + } + mod->brush.collisionmesh = Mod_ShadowMesh_Begin(mempool, numcollisionmeshtriangles * 3, numcollisionmeshtriangles, NULL, NULL, NULL, false, false, true); + if (usesinglecollisionmesh) + Mod_ShadowMesh_AddMesh(mempool, mod->brush.collisionmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); + else + { + for (k = 0;k < mod->nummodelsurfaces;k++) + { + surface = mod->data_surfaces + mod->firstmodelsurface + k; + if (!(surface->texture->supercontents & SUPERCONTENTS_SOLID)) + continue; + Mod_ShadowMesh_AddMesh(mempool, mod->brush.collisionmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); + } + } + mod->brush.collisionmesh = Mod_ShadowMesh_Finish(mempool, mod->brush.collisionmesh, false, false, false); +} + +void Mod_GetTerrainVertex3fTexCoord2fFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int ix, int iy, float *vertex3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) +{ + float v[3], tc[3]; + v[0] = ix; + v[1] = iy; + if (ix >= 0 && iy >= 0 && ix < imagewidth && iy < imageheight) + v[2] = (imagepixels[((iy*imagewidth)+ix)*4+0] + imagepixels[((iy*imagewidth)+ix)*4+1] + imagepixels[((iy*imagewidth)+ix)*4+2]) * (1.0f / 765.0f); + else + v[2] = 0; + Matrix4x4_Transform(pixelstepmatrix, v, vertex3f); + Matrix4x4_Transform(pixeltexturestepmatrix, v, tc); + texcoord2f[0] = tc[0]; + texcoord2f[1] = tc[1]; +} + +void Mod_GetTerrainVertexFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int ix, int iy, float *vertex3f, float *svector3f, float *tvector3f, float *normal3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) +{ + float vup[3], vdown[3], vleft[3], vright[3]; + float tcup[3], tcdown[3], tcleft[3], tcright[3]; + float sv[3], tv[3], nl[3]; + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy, vertex3f, texcoord2f, pixelstepmatrix, pixeltexturestepmatrix); + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy - 1, vup, tcup, pixelstepmatrix, pixeltexturestepmatrix); + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy + 1, vdown, tcdown, pixelstepmatrix, pixeltexturestepmatrix); + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix - 1, iy, vleft, tcleft, pixelstepmatrix, pixeltexturestepmatrix); + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix + 1, iy, vright, tcright, pixelstepmatrix, pixeltexturestepmatrix); + Mod_BuildBumpVectors(vertex3f, vup, vright, texcoord2f, tcup, tcright, svector3f, tvector3f, normal3f); + Mod_BuildBumpVectors(vertex3f, vright, vdown, texcoord2f, tcright, tcdown, sv, tv, nl); + VectorAdd(svector3f, sv, svector3f); + VectorAdd(tvector3f, tv, tvector3f); + VectorAdd(normal3f, nl, normal3f); + Mod_BuildBumpVectors(vertex3f, vdown, vleft, texcoord2f, tcdown, tcleft, sv, tv, nl); + VectorAdd(svector3f, sv, svector3f); + VectorAdd(tvector3f, tv, tvector3f); + VectorAdd(normal3f, nl, normal3f); + Mod_BuildBumpVectors(vertex3f, vleft, vup, texcoord2f, tcleft, tcup, sv, tv, nl); + VectorAdd(svector3f, sv, svector3f); + VectorAdd(tvector3f, tv, tvector3f); + VectorAdd(normal3f, nl, normal3f); +} + +void Mod_ConstructTerrainPatchFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int x1, int y1, int width, int height, int *element3i, int *neighbor3i, float *vertex3f, float *svector3f, float *tvector3f, float *normal3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) +{ + int x, y, ix, iy, *e; + e = element3i; + for (y = 0;y < height;y++) + { + for (x = 0;x < width;x++) + { + e[0] = (y + 1) * (width + 1) + (x + 0); + e[1] = (y + 0) * (width + 1) + (x + 0); + e[2] = (y + 1) * (width + 1) + (x + 1); + e[3] = (y + 0) * (width + 1) + (x + 0); + e[4] = (y + 0) * (width + 1) + (x + 1); + e[5] = (y + 1) * (width + 1) + (x + 1); + e += 6; + } + } + Mod_BuildTriangleNeighbors(neighbor3i, element3i, width*height*2); + for (y = 0, iy = y1;y < height + 1;y++, iy++) + for (x = 0, ix = x1;x < width + 1;x++, ix++, vertex3f += 3, texcoord2f += 2, svector3f += 3, tvector3f += 3, normal3f += 3) + Mod_GetTerrainVertexFromBGRA(imagepixels, imagewidth, imageheight, ix, iy, vertex3f, texcoord2f, svector3f, tvector3f, normal3f, pixelstepmatrix, pixeltexturestepmatrix); +} + +#if 0 +void Mod_Terrain_SurfaceRecurseChunk(dp_model_t *model, int stepsize, int x, int y) +{ + float mins[3]; + float maxs[3]; + float chunkwidth = min(stepsize, model->terrain.width - 1 - x); + float chunkheight = min(stepsize, model->terrain.height - 1 - y); + float viewvector[3]; + unsigned int firstvertex; + unsigned int *e; + float *v; + if (chunkwidth < 2 || chunkheight < 2) + return; + VectorSet(mins, model->terrain.mins[0] + x * stepsize * model->terrain.scale[0], model->terrain.mins[1] + y * stepsize * model->terrain.scale[1], model->terrain.mins[2]); + VectorSet(maxs, model->terrain.mins[0] + (x+1) * stepsize * model->terrain.scale[0], model->terrain.mins[1] + (y+1) * stepsize * model->terrain.scale[1], model->terrain.maxs[2]); + viewvector[0] = bound(mins[0], localvieworigin, maxs[0]) - model->terrain.vieworigin[0]; + viewvector[1] = bound(mins[1], localvieworigin, maxs[1]) - model->terrain.vieworigin[1]; + viewvector[2] = bound(mins[2], localvieworigin, maxs[2]) - model->terrain.vieworigin[2]; + if (stepsize > 1 && VectorLength(viewvector) < stepsize*model->terrain.scale[0]*r_terrain_lodscale.value) + { + // too close for this stepsize, emit as 4 chunks instead + stepsize /= 2; + Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x, y); + Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x+stepsize, y); + Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x, y+stepsize); + Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x+stepsize, y+stepsize); + return; + } + // emit the geometry at stepsize into our vertex buffer / index buffer + // we add two columns and two rows for skirt + outwidth = chunkwidth+2; + outheight = chunkheight+2; + outwidth2 = outwidth-1; + outheight2 = outheight-1; + outwidth3 = outwidth+1; + outheight3 = outheight+1; + firstvertex = numvertices; + e = model->terrain.element3i + numtriangles; + numtriangles += chunkwidth*chunkheight*2+chunkwidth*2*2+chunkheight*2*2; + v = model->terrain.vertex3f + numvertices; + numvertices += (chunkwidth+1)*(chunkheight+1)+(chunkwidth+1)*2+(chunkheight+1)*2; + // emit the triangles (note: the skirt is treated as two extra rows and two extra columns) + for (ty = 0;ty < outheight;ty++) + { + for (tx = 0;tx < outwidth;tx++) + { + *e++ = firstvertex + (ty )*outwidth3+(tx ); + *e++ = firstvertex + (ty )*outwidth3+(tx+1); + *e++ = firstvertex + (ty+1)*outwidth3+(tx+1); + *e++ = firstvertex + (ty )*outwidth3+(tx ); + *e++ = firstvertex + (ty+1)*outwidth3+(tx+1); + *e++ = firstvertex + (ty+1)*outwidth3+(tx ); + } + } + // TODO: emit surface vertices (x+tx*stepsize, y+ty*stepsize) + for (ty = 0;ty <= outheight;ty++) + { + skirtrow = ty == 0 || ty == outheight; + ry = y+bound(1, ty, outheight)*stepsize; + for (tx = 0;tx <= outwidth;tx++) + { + skirt = skirtrow || tx == 0 || tx == outwidth; + rx = x+bound(1, tx, outwidth)*stepsize; + v[0] = rx*scale[0]; + v[1] = ry*scale[1]; + v[2] = heightmap[ry*terrainwidth+rx]*scale[2]; + v += 3; + } + } + // TODO: emit skirt vertices +} + +void Mod_Terrain_UpdateSurfacesForViewOrigin(dp_model_t *model) +{ + for (y = 0;y < model->terrain.size[1];y += model->terrain. + Mod_Terrain_SurfaceRecurseChunk(model, model->terrain.maxstepsize, x, y); + Mod_Terrain_BuildChunk(model, +} +#endif + +int Mod_LoadQ3Shaders_EnumerateWaveFunc(const char *s) +{ + int offset = 0; + if (!strncasecmp(s, "user", 4)) // parse stuff like "user1sin", always userfunc + { + offset = bound(0, s[4] - '0', 9); + offset = (offset + 1) << Q3WAVEFUNC_USER_SHIFT; + s += 4; + if(*s) + ++s; + } + if (!strcasecmp(s, "sin")) return offset | Q3WAVEFUNC_SIN; + if (!strcasecmp(s, "square")) return offset | Q3WAVEFUNC_SQUARE; + if (!strcasecmp(s, "triangle")) return offset | Q3WAVEFUNC_TRIANGLE; + if (!strcasecmp(s, "sawtooth")) return offset | Q3WAVEFUNC_SAWTOOTH; + if (!strcasecmp(s, "inversesawtooth")) return offset | Q3WAVEFUNC_INVERSESAWTOOTH; + if (!strcasecmp(s, "noise")) return offset | Q3WAVEFUNC_NOISE; + if (!strcasecmp(s, "none")) return offset | Q3WAVEFUNC_NONE; + Con_DPrintf("Mod_LoadQ3Shaders: unknown wavefunc %s\n", s); + return offset | Q3WAVEFUNC_NONE; +} + +void Mod_FreeQ3Shaders(void) +{ + Mem_FreePool(&q3shaders_mem); +} + +static void Q3Shader_AddToHash (q3shaderinfo_t* shader) +{ + unsigned short hash = CRC_Block_CaseInsensitive ((const unsigned char *)shader->name, strlen (shader->name)); + q3shader_hash_entry_t* entry = q3shader_data->hash + (hash % Q3SHADER_HASH_SIZE); + q3shader_hash_entry_t* lastEntry = NULL; + while (entry != NULL) + { + if (strcasecmp (entry->shader.name, shader->name) == 0) + { + unsigned char *start, *end, *start2; + start = (unsigned char *) (&shader->Q3SHADERINFO_COMPARE_START); + end = ((unsigned char *) (&shader->Q3SHADERINFO_COMPARE_END)) + sizeof(shader->Q3SHADERINFO_COMPARE_END); + start2 = (unsigned char *) (&entry->shader.Q3SHADERINFO_COMPARE_START); + if(memcmp(start, start2, end - start)) + Con_DPrintf("Shader '%s' already defined, ignoring mismatching redeclaration\n", shader->name); + else + Con_DPrintf("Shader '%s' already defined\n", shader->name); + return; + } + lastEntry = entry; + entry = entry->chain; + } + if (entry == NULL) + { + if (lastEntry->shader.name[0] != 0) + { + /* Add to chain */ + q3shader_hash_entry_t* newEntry = (q3shader_hash_entry_t*) + Mem_ExpandableArray_AllocRecord (&q3shader_data->hash_entries); + + while (lastEntry->chain != NULL) lastEntry = lastEntry->chain; + lastEntry->chain = newEntry; + newEntry->chain = NULL; + lastEntry = newEntry; + } + /* else: head of chain, in hash entry array */ + entry = lastEntry; + } + memcpy (&entry->shader, shader, sizeof (q3shaderinfo_t)); +} + +extern cvar_t mod_noshader_default_offsetmapping; +extern cvar_t mod_q3shader_default_offsetmapping; +extern cvar_t mod_q3shader_default_polygonoffset; +extern cvar_t mod_q3shader_default_polygonfactor; +void Mod_LoadQ3Shaders(void) +{ + int j; + int fileindex; + fssearch_t *search; + char *f; + const char *text; + q3shaderinfo_t shader; + q3shaderinfo_layer_t *layer; + int numparameters; + char parameter[TEXTURE_MAXFRAMES + 4][Q3PATHLENGTH]; + char *custsurfaceparmnames[256]; // VorteX: q3map2 has 64 but well, someone will need more + unsigned long custsurfaceparms[256]; + int numcustsurfaceparms; + + Mod_FreeQ3Shaders(); + + q3shaders_mem = Mem_AllocPool("q3shaders", 0, NULL); + q3shader_data = (q3shader_data_t*)Mem_Alloc (q3shaders_mem, + sizeof (q3shader_data_t)); + Mem_ExpandableArray_NewArray (&q3shader_data->hash_entries, + q3shaders_mem, sizeof (q3shader_hash_entry_t), 256); + Mem_ExpandableArray_NewArray (&q3shader_data->char_ptrs, + q3shaders_mem, sizeof (char**), 256); + + // parse custinfoparms.txt + numcustsurfaceparms = 0; + if ((text = f = (char *)FS_LoadFile("scripts/custinfoparms.txt", tempmempool, false, NULL)) != NULL) + { + if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) + Con_DPrintf("scripts/custinfoparms.txt: contentflags section parsing error - expected \"{\", found \"%s\"\n", com_token); + else + { + while (COM_ParseToken_QuakeC(&text, false)) + if (!strcasecmp(com_token, "}")) + break; + // custom surfaceflags section + if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) + Con_DPrintf("scripts/custinfoparms.txt: surfaceflags section parsing error - expected \"{\", found \"%s\"\n", com_token); + else + { + while(COM_ParseToken_QuakeC(&text, false)) + { + if (!strcasecmp(com_token, "}")) + break; + // register surfaceflag + if (numcustsurfaceparms >= 256) + { + Con_Printf("scripts/custinfoparms.txt: surfaceflags section parsing error - max 256 surfaceflags exceeded\n"); + break; + } + // name + j = strlen(com_token)+1; + custsurfaceparmnames[numcustsurfaceparms] = (char *)Mem_Alloc(tempmempool, j); + strlcpy(custsurfaceparmnames[numcustsurfaceparms], com_token, j+1); + // value + if (COM_ParseToken_QuakeC(&text, false)) + custsurfaceparms[numcustsurfaceparms] = strtol(com_token, NULL, 0); + else + custsurfaceparms[numcustsurfaceparms] = 0; + numcustsurfaceparms++; + } + } + } + Mem_Free(f); + } + + // parse shaders + search = FS_Search("scripts/*.shader", true, false); + if (!search) + return; + for (fileindex = 0;fileindex < search->numfilenames;fileindex++) + { + text = f = (char *)FS_LoadFile(search->filenames[fileindex], tempmempool, false, NULL); + if (!f) + continue; + while (COM_ParseToken_QuakeC(&text, false)) + { + memset (&shader, 0, sizeof(shader)); + shader.reflectmin = 0; + shader.reflectmax = 1; + shader.refractfactor = 1; + Vector4Set(shader.refractcolor4f, 1, 1, 1, 1); + shader.reflectfactor = 1; + Vector4Set(shader.reflectcolor4f, 1, 1, 1, 1); + shader.r_water_wateralpha = 1; + shader.offsetmapping = (mod_q3shader_default_offsetmapping.value) ? OFFSETMAPPING_DEFAULT : OFFSETMAPPING_OFF; + shader.offsetscale = 1; + shader.specularscalemod = 1; + shader.specularpowermod = 1; + shader.biaspolygonoffset = mod_q3shader_default_polygonoffset.value; + shader.biaspolygonfactor = mod_q3shader_default_polygonfactor.value; + + strlcpy(shader.name, com_token, sizeof(shader.name)); + if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) + { + Con_DPrintf("%s parsing error - expected \"{\", found \"%s\"\n", search->filenames[fileindex], com_token); + break; + } + while (COM_ParseToken_QuakeC(&text, false)) + { + if (!strcasecmp(com_token, "}")) + break; + if (!strcasecmp(com_token, "{")) + { + static q3shaderinfo_layer_t dummy; + if (shader.numlayers < Q3SHADER_MAXLAYERS) + { + layer = shader.layers + shader.numlayers++; + } + else + { + // parse and process it anyway, just don't store it (so a map $lightmap or such stuff still is found) + memset(&dummy, 0, sizeof(dummy)); + layer = &dummy; + } + layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITY; + layer->alphagen.alphagen = Q3ALPHAGEN_IDENTITY; + layer->tcgen.tcgen = Q3TCGEN_TEXTURE; + layer->blendfunc[0] = GL_ONE; + layer->blendfunc[1] = GL_ZERO; + while (COM_ParseToken_QuakeC(&text, false)) + { + if (!strcasecmp(com_token, "}")) + break; + if (!strcasecmp(com_token, "\n")) + continue; + numparameters = 0; + for (j = 0;strcasecmp(com_token, "\n") && strcasecmp(com_token, "}");j++) + { + if (j < TEXTURE_MAXFRAMES + 4) + { + // remap dp_water to dpwater, dp_reflect to dpreflect, etc. + if(j == 0 && !strncasecmp(com_token, "dp_", 3)) + dpsnprintf(parameter[j], sizeof(parameter[j]), "dp%s", &com_token[3]); + else + strlcpy(parameter[j], com_token, sizeof(parameter[j])); + numparameters = j + 1; + } + if (!COM_ParseToken_QuakeC(&text, true)) + break; + } + //for (j = numparameters;j < TEXTURE_MAXFRAMES + 4;j++) + // parameter[j][0] = 0; + if (developer_insane.integer) + { + Con_DPrintf("%s %i: ", shader.name, shader.numlayers - 1); + for (j = 0;j < numparameters;j++) + Con_DPrintf(" %s", parameter[j]); + Con_DPrint("\n"); + } + if (numparameters >= 2 && !strcasecmp(parameter[0], "blendfunc")) + { + if (numparameters == 2) + { + if (!strcasecmp(parameter[1], "add")) + { + layer->blendfunc[0] = GL_ONE; + layer->blendfunc[1] = GL_ONE; + } + else if (!strcasecmp(parameter[1], "filter")) + { + layer->blendfunc[0] = GL_DST_COLOR; + layer->blendfunc[1] = GL_ZERO; + } + else if (!strcasecmp(parameter[1], "blend")) + { + layer->blendfunc[0] = GL_SRC_ALPHA; + layer->blendfunc[1] = GL_ONE_MINUS_SRC_ALPHA; + } + } + else if (numparameters == 3) + { + int k; + for (k = 0;k < 2;k++) + { + if (!strcasecmp(parameter[k+1], "GL_ONE")) + layer->blendfunc[k] = GL_ONE; + else if (!strcasecmp(parameter[k+1], "GL_ZERO")) + layer->blendfunc[k] = GL_ZERO; + else if (!strcasecmp(parameter[k+1], "GL_SRC_COLOR")) + layer->blendfunc[k] = GL_SRC_COLOR; + else if (!strcasecmp(parameter[k+1], "GL_SRC_ALPHA")) + layer->blendfunc[k] = GL_SRC_ALPHA; + else if (!strcasecmp(parameter[k+1], "GL_DST_COLOR")) + layer->blendfunc[k] = GL_DST_COLOR; + else if (!strcasecmp(parameter[k+1], "GL_DST_ALPHA")) + layer->blendfunc[k] = GL_ONE_MINUS_DST_ALPHA; + else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_SRC_COLOR")) + layer->blendfunc[k] = GL_ONE_MINUS_SRC_COLOR; + else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_SRC_ALPHA")) + layer->blendfunc[k] = GL_ONE_MINUS_SRC_ALPHA; + else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_DST_COLOR")) + layer->blendfunc[k] = GL_ONE_MINUS_DST_COLOR; + else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_DST_ALPHA")) + layer->blendfunc[k] = GL_ONE_MINUS_DST_ALPHA; + else + layer->blendfunc[k] = GL_ONE; // default in case of parsing error + } + } + } + if (numparameters >= 2 && !strcasecmp(parameter[0], "alphafunc")) + layer->alphatest = true; + if (numparameters >= 2 && (!strcasecmp(parameter[0], "map") || !strcasecmp(parameter[0], "clampmap"))) + { + if (!strcasecmp(parameter[0], "clampmap")) + layer->clampmap = true; + layer->numframes = 1; + layer->framerate = 1; + layer->texturename = (char**)Mem_ExpandableArray_AllocRecord ( + &q3shader_data->char_ptrs); + layer->texturename[0] = Mem_strdup (q3shaders_mem, parameter[1]); + if (!strcasecmp(parameter[1], "$lightmap")) + shader.lighting = true; + } + else if (numparameters >= 3 && (!strcasecmp(parameter[0], "animmap") || !strcasecmp(parameter[0], "animclampmap"))) + { + int i; + layer->numframes = min(numparameters - 2, TEXTURE_MAXFRAMES); + layer->framerate = atof(parameter[1]); + layer->texturename = (char **) Mem_Alloc (q3shaders_mem, sizeof (char*) * layer->numframes); + for (i = 0;i < layer->numframes;i++) + layer->texturename[i] = Mem_strdup (q3shaders_mem, parameter[i + 2]); + } + else if (numparameters >= 2 && !strcasecmp(parameter[0], "rgbgen")) + { + int i; + for (i = 0;i < numparameters - 2 && i < Q3RGBGEN_MAXPARMS;i++) + layer->rgbgen.parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "identity")) layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITY; + else if (!strcasecmp(parameter[1], "const")) layer->rgbgen.rgbgen = Q3RGBGEN_CONST; + else if (!strcasecmp(parameter[1], "entity")) layer->rgbgen.rgbgen = Q3RGBGEN_ENTITY; + else if (!strcasecmp(parameter[1], "exactvertex")) layer->rgbgen.rgbgen = Q3RGBGEN_EXACTVERTEX; + else if (!strcasecmp(parameter[1], "identitylighting")) layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITYLIGHTING; + else if (!strcasecmp(parameter[1], "lightingdiffuse")) layer->rgbgen.rgbgen = Q3RGBGEN_LIGHTINGDIFFUSE; + else if (!strcasecmp(parameter[1], "oneminusentity")) layer->rgbgen.rgbgen = Q3RGBGEN_ONEMINUSENTITY; + else if (!strcasecmp(parameter[1], "oneminusvertex")) layer->rgbgen.rgbgen = Q3RGBGEN_ONEMINUSVERTEX; + else if (!strcasecmp(parameter[1], "vertex")) layer->rgbgen.rgbgen = Q3RGBGEN_VERTEX; + else if (!strcasecmp(parameter[1], "wave")) + { + layer->rgbgen.rgbgen = Q3RGBGEN_WAVE; + layer->rgbgen.wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); + for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) + layer->rgbgen.waveparms[i] = atof(parameter[i+3]); + } + else Con_DPrintf("%s parsing warning: unknown rgbgen %s\n", search->filenames[fileindex], parameter[1]); + } + else if (numparameters >= 2 && !strcasecmp(parameter[0], "alphagen")) + { + int i; + for (i = 0;i < numparameters - 2 && i < Q3ALPHAGEN_MAXPARMS;i++) + layer->alphagen.parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "identity")) layer->alphagen.alphagen = Q3ALPHAGEN_IDENTITY; + else if (!strcasecmp(parameter[1], "const")) layer->alphagen.alphagen = Q3ALPHAGEN_CONST; + else if (!strcasecmp(parameter[1], "entity")) layer->alphagen.alphagen = Q3ALPHAGEN_ENTITY; + else if (!strcasecmp(parameter[1], "lightingspecular")) layer->alphagen.alphagen = Q3ALPHAGEN_LIGHTINGSPECULAR; + else if (!strcasecmp(parameter[1], "oneminusentity")) layer->alphagen.alphagen = Q3ALPHAGEN_ONEMINUSENTITY; + else if (!strcasecmp(parameter[1], "oneminusvertex")) layer->alphagen.alphagen = Q3ALPHAGEN_ONEMINUSVERTEX; + else if (!strcasecmp(parameter[1], "portal")) layer->alphagen.alphagen = Q3ALPHAGEN_PORTAL; + else if (!strcasecmp(parameter[1], "vertex")) layer->alphagen.alphagen = Q3ALPHAGEN_VERTEX; + else if (!strcasecmp(parameter[1], "wave")) + { + layer->alphagen.alphagen = Q3ALPHAGEN_WAVE; + layer->alphagen.wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); + for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) + layer->alphagen.waveparms[i] = atof(parameter[i+3]); + } + else Con_DPrintf("%s parsing warning: unknown alphagen %s\n", search->filenames[fileindex], parameter[1]); + } + else if (numparameters >= 2 && (!strcasecmp(parameter[0], "texgen") || !strcasecmp(parameter[0], "tcgen"))) + { + int i; + // observed values: tcgen environment + // no other values have been observed in real shaders + for (i = 0;i < numparameters - 2 && i < Q3TCGEN_MAXPARMS;i++) + layer->tcgen.parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "base")) layer->tcgen.tcgen = Q3TCGEN_TEXTURE; + else if (!strcasecmp(parameter[1], "texture")) layer->tcgen.tcgen = Q3TCGEN_TEXTURE; + else if (!strcasecmp(parameter[1], "environment")) layer->tcgen.tcgen = Q3TCGEN_ENVIRONMENT; + else if (!strcasecmp(parameter[1], "lightmap")) layer->tcgen.tcgen = Q3TCGEN_LIGHTMAP; + else if (!strcasecmp(parameter[1], "vector")) layer->tcgen.tcgen = Q3TCGEN_VECTOR; + else Con_DPrintf("%s parsing warning: unknown tcgen mode %s\n", search->filenames[fileindex], parameter[1]); + } + else if (numparameters >= 2 && !strcasecmp(parameter[0], "tcmod")) + { + int i, tcmodindex; + // observed values: + // tcmod rotate # + // tcmod scale # # + // tcmod scroll # # + // tcmod stretch sin # # # # + // tcmod stretch triangle # # # # + // tcmod transform # # # # # # + // tcmod turb # # # # + // tcmod turb sin # # # # (this is bogus) + // no other values have been observed in real shaders + for (tcmodindex = 0;tcmodindex < Q3MAXTCMODS;tcmodindex++) + if (!layer->tcmods[tcmodindex].tcmod) + break; + if (tcmodindex < Q3MAXTCMODS) + { + for (i = 0;i < numparameters - 2 && i < Q3TCMOD_MAXPARMS;i++) + layer->tcmods[tcmodindex].parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "entitytranslate")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_ENTITYTRANSLATE; + else if (!strcasecmp(parameter[1], "rotate")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_ROTATE; + else if (!strcasecmp(parameter[1], "scale")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_SCALE; + else if (!strcasecmp(parameter[1], "scroll")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_SCROLL; + else if (!strcasecmp(parameter[1], "page")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_PAGE; + else if (!strcasecmp(parameter[1], "stretch")) + { + layer->tcmods[tcmodindex].tcmod = Q3TCMOD_STRETCH; + layer->tcmods[tcmodindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); + for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) + layer->tcmods[tcmodindex].waveparms[i] = atof(parameter[i+3]); + } + else if (!strcasecmp(parameter[1], "transform")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_TRANSFORM; + else if (!strcasecmp(parameter[1], "turb")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_TURBULENT; + else Con_DPrintf("%s parsing warning: unknown tcmod mode %s\n", search->filenames[fileindex], parameter[1]); + } + else + Con_DPrintf("%s parsing warning: too many tcmods on one layer\n", search->filenames[fileindex]); + } + // break out a level if it was a closing brace (not using the character here to not confuse vim) + if (!strcasecmp(com_token, "}")) + break; + } + if (layer->rgbgen.rgbgen == Q3RGBGEN_LIGHTINGDIFFUSE || layer->rgbgen.rgbgen == Q3RGBGEN_VERTEX) + shader.lighting = true; + if (layer->alphagen.alphagen == Q3ALPHAGEN_VERTEX) + { + if (layer == shader.layers + 0) + { + // vertex controlled transparency + shader.vertexalpha = true; + } + else + { + // multilayer terrain shader or similar + shader.textureblendalpha = true; + } + } + layer->texflags = TEXF_ALPHA; + if (!(shader.surfaceparms & Q3SURFACEPARM_NOMIPMAPS)) + layer->texflags |= TEXF_MIPMAP; + if (!(shader.textureflags & Q3TEXTUREFLAG_NOPICMIP)) + layer->texflags |= TEXF_PICMIP | TEXF_COMPRESS; + if (layer->clampmap) + layer->texflags |= TEXF_CLAMP; + continue; + } + numparameters = 0; + for (j = 0;strcasecmp(com_token, "\n") && strcasecmp(com_token, "}");j++) + { + if (j < TEXTURE_MAXFRAMES + 4) + { + // remap dp_water to dpwater, dp_reflect to dpreflect, etc. + if(j == 0 && !strncasecmp(com_token, "dp_", 3)) + dpsnprintf(parameter[j], sizeof(parameter[j]), "dp%s", &com_token[3]); + else + strlcpy(parameter[j], com_token, sizeof(parameter[j])); + numparameters = j + 1; + } + if (!COM_ParseToken_QuakeC(&text, true)) + break; + } + //for (j = numparameters;j < TEXTURE_MAXFRAMES + 4;j++) + // parameter[j][0] = 0; + if (fileindex == 0 && !strcasecmp(com_token, "}")) + break; + if (developer_insane.integer) + { + Con_DPrintf("%s: ", shader.name); + for (j = 0;j < numparameters;j++) + Con_DPrintf(" %s", parameter[j]); + Con_DPrint("\n"); + } + if (numparameters < 1) + continue; + if (!strcasecmp(parameter[0], "surfaceparm") && numparameters >= 2) + { + if (!strcasecmp(parameter[1], "alphashadow")) + shader.surfaceparms |= Q3SURFACEPARM_ALPHASHADOW; + else if (!strcasecmp(parameter[1], "areaportal")) + shader.surfaceparms |= Q3SURFACEPARM_AREAPORTAL; + else if (!strcasecmp(parameter[1], "botclip")) + shader.surfaceparms |= Q3SURFACEPARM_BOTCLIP; + else if (!strcasecmp(parameter[1], "clusterportal")) + shader.surfaceparms |= Q3SURFACEPARM_CLUSTERPORTAL; + else if (!strcasecmp(parameter[1], "detail")) + shader.surfaceparms |= Q3SURFACEPARM_DETAIL; + else if (!strcasecmp(parameter[1], "donotenter")) + shader.surfaceparms |= Q3SURFACEPARM_DONOTENTER; + else if (!strcasecmp(parameter[1], "dust")) + shader.surfaceparms |= Q3SURFACEPARM_DUST; + else if (!strcasecmp(parameter[1], "hint")) + shader.surfaceparms |= Q3SURFACEPARM_HINT; + else if (!strcasecmp(parameter[1], "fog")) + shader.surfaceparms |= Q3SURFACEPARM_FOG; + else if (!strcasecmp(parameter[1], "lava")) + shader.surfaceparms |= Q3SURFACEPARM_LAVA; + else if (!strcasecmp(parameter[1], "lightfilter")) + shader.surfaceparms |= Q3SURFACEPARM_LIGHTFILTER; + else if (!strcasecmp(parameter[1], "lightgrid")) + shader.surfaceparms |= Q3SURFACEPARM_LIGHTGRID; + else if (!strcasecmp(parameter[1], "metalsteps")) + shader.surfaceparms |= Q3SURFACEPARM_METALSTEPS; + else if (!strcasecmp(parameter[1], "nodamage")) + shader.surfaceparms |= Q3SURFACEPARM_NODAMAGE; + else if (!strcasecmp(parameter[1], "nodlight")) + shader.surfaceparms |= Q3SURFACEPARM_NODLIGHT; + else if (!strcasecmp(parameter[1], "nodraw")) + shader.surfaceparms |= Q3SURFACEPARM_NODRAW; + else if (!strcasecmp(parameter[1], "nodrop")) + shader.surfaceparms |= Q3SURFACEPARM_NODROP; + else if (!strcasecmp(parameter[1], "noimpact")) + shader.surfaceparms |= Q3SURFACEPARM_NOIMPACT; + else if (!strcasecmp(parameter[1], "nolightmap")) + shader.surfaceparms |= Q3SURFACEPARM_NOLIGHTMAP; + else if (!strcasecmp(parameter[1], "nomarks")) + shader.surfaceparms |= Q3SURFACEPARM_NOMARKS; + else if (!strcasecmp(parameter[1], "nomipmaps")) + shader.surfaceparms |= Q3SURFACEPARM_NOMIPMAPS; + else if (!strcasecmp(parameter[1], "nonsolid")) + shader.surfaceparms |= Q3SURFACEPARM_NONSOLID; + else if (!strcasecmp(parameter[1], "origin")) + shader.surfaceparms |= Q3SURFACEPARM_ORIGIN; + else if (!strcasecmp(parameter[1], "playerclip")) + shader.surfaceparms |= Q3SURFACEPARM_PLAYERCLIP; + else if (!strcasecmp(parameter[1], "sky")) + shader.surfaceparms |= Q3SURFACEPARM_SKY; + else if (!strcasecmp(parameter[1], "slick")) + shader.surfaceparms |= Q3SURFACEPARM_SLICK; + else if (!strcasecmp(parameter[1], "slime")) + shader.surfaceparms |= Q3SURFACEPARM_SLIME; + else if (!strcasecmp(parameter[1], "structural")) + shader.surfaceparms |= Q3SURFACEPARM_STRUCTURAL; + else if (!strcasecmp(parameter[1], "trans")) + shader.surfaceparms |= Q3SURFACEPARM_TRANS; + else if (!strcasecmp(parameter[1], "water")) + shader.surfaceparms |= Q3SURFACEPARM_WATER; + else if (!strcasecmp(parameter[1], "pointlight")) + shader.surfaceparms |= Q3SURFACEPARM_POINTLIGHT; + else if (!strcasecmp(parameter[1], "antiportal")) + shader.surfaceparms |= Q3SURFACEPARM_ANTIPORTAL; + else + { + // try custom surfaceparms + for (j = 0; j < numcustsurfaceparms; j++) + { + if (!strcasecmp(custsurfaceparmnames[j], parameter[1])) + { + shader.surfaceparms |= custsurfaceparms[j]; + break; + } + } + // failed all + if (j == numcustsurfaceparms) + Con_DPrintf("%s parsing warning: unknown surfaceparm \"%s\"\n", search->filenames[fileindex], parameter[1]); + } + } + else if (!strcasecmp(parameter[0], "dpshadow")) + shader.dpshadow = true; + else if (!strcasecmp(parameter[0], "dpnoshadow")) + shader.dpnoshadow = true; + else if (!strcasecmp(parameter[0], "dpnortlight")) + shader.dpnortlight = true; + else if (!strcasecmp(parameter[0], "dpreflectcube")) + strlcpy(shader.dpreflectcube, parameter[1], sizeof(shader.dpreflectcube)); + else if (!strcasecmp(parameter[0], "dpmeshcollisions")) + shader.dpmeshcollisions = true; + else if (!strcasecmp(parameter[0], "sky") && numparameters >= 2) + { + // some q3 skies don't have the sky parm set + shader.surfaceparms |= Q3SURFACEPARM_SKY; + strlcpy(shader.skyboxname, parameter[1], sizeof(shader.skyboxname)); + } + else if (!strcasecmp(parameter[0], "skyparms") && numparameters >= 2) + { + // some q3 skies don't have the sky parm set + shader.surfaceparms |= Q3SURFACEPARM_SKY; + if (!atoi(parameter[1]) && strcasecmp(parameter[1], "-")) + strlcpy(shader.skyboxname, parameter[1], sizeof(shader.skyboxname)); + } + else if (!strcasecmp(parameter[0], "cull") && numparameters >= 2) + { + if (!strcasecmp(parameter[1], "disable") || !strcasecmp(parameter[1], "none") || !strcasecmp(parameter[1], "twosided")) + shader.textureflags |= Q3TEXTUREFLAG_TWOSIDED; + } + else if (!strcasecmp(parameter[0], "nomipmaps")) + shader.surfaceparms |= Q3SURFACEPARM_NOMIPMAPS; + else if (!strcasecmp(parameter[0], "nopicmip")) + shader.textureflags |= Q3TEXTUREFLAG_NOPICMIP; + else if (!strcasecmp(parameter[0], "polygonoffset")) + shader.textureflags |= Q3TEXTUREFLAG_POLYGONOFFSET; + else if (!strcasecmp(parameter[0], "dppolygonoffset")) + { + shader.textureflags |= Q3TEXTUREFLAG_POLYGONOFFSET; + if(numparameters >= 2) + { + shader.biaspolygonfactor = atof(parameter[1]); + if(numparameters >= 3) + shader.biaspolygonoffset = atof(parameter[2]); + else + shader.biaspolygonoffset = 0; + } + } + else if (!strcasecmp(parameter[0], "dprefract") && numparameters >= 5) + { + shader.textureflags |= Q3TEXTUREFLAG_REFRACTION; + shader.refractfactor = atof(parameter[1]); + Vector4Set(shader.refractcolor4f, atof(parameter[2]), atof(parameter[3]), atof(parameter[4]), 1); + } + else if (!strcasecmp(parameter[0], "dpreflect") && numparameters >= 6) + { + shader.textureflags |= Q3TEXTUREFLAG_REFLECTION; + shader.reflectfactor = atof(parameter[1]); + Vector4Set(shader.reflectcolor4f, atof(parameter[2]), atof(parameter[3]), atof(parameter[4]), atof(parameter[5])); + } + else if (!strcasecmp(parameter[0], "dpcamera")) + { + shader.textureflags |= Q3TEXTUREFLAG_CAMERA; + } + else if (!strcasecmp(parameter[0], "dpwater") && numparameters >= 12) + { + shader.textureflags |= Q3TEXTUREFLAG_WATERSHADER; + shader.reflectmin = atof(parameter[1]); + shader.reflectmax = atof(parameter[2]); + shader.refractfactor = atof(parameter[3]); + shader.reflectfactor = atof(parameter[4]); + Vector4Set(shader.refractcolor4f, atof(parameter[5]), atof(parameter[6]), atof(parameter[7]), 1); + Vector4Set(shader.reflectcolor4f, atof(parameter[8]), atof(parameter[9]), atof(parameter[10]), 1); + shader.r_water_wateralpha = atof(parameter[11]); + } + else if (!strcasecmp(parameter[0], "dpwaterscroll") && numparameters >= 3) + { + shader.r_water_waterscroll[0] = 1/atof(parameter[1]); + shader.r_water_waterscroll[1] = 1/atof(parameter[2]); + } + else if (!strcasecmp(parameter[0], "dpglossintensitymod") && numparameters >= 2) + { + shader.specularscalemod = atof(parameter[1]); + } + else if (!strcasecmp(parameter[0], "dpglossexponentmod") && numparameters >= 2) + { + shader.specularpowermod = atof(parameter[1]); + } + else if (!strcasecmp(parameter[0], "dpoffsetmapping") && numparameters >= 3) + { + if (!strcasecmp(parameter[1], "disable") || !strcasecmp(parameter[1], "none") || !strcasecmp(parameter[1], "off")) + shader.offsetmapping = OFFSETMAPPING_OFF; + else if (!strcasecmp(parameter[1], "default")) + shader.offsetmapping = OFFSETMAPPING_DEFAULT; + else if (!strcasecmp(parameter[1], "linear")) + shader.offsetmapping = OFFSETMAPPING_LINEAR; + else if (!strcasecmp(parameter[1], "relief")) + shader.offsetmapping = OFFSETMAPPING_RELIEF; + shader.offsetscale = atof(parameter[2]); + } + else if (!strcasecmp(parameter[0], "deformvertexes") && numparameters >= 2) + { + int i, deformindex; + for (deformindex = 0;deformindex < Q3MAXDEFORMS;deformindex++) + if (!shader.deforms[deformindex].deform) + break; + if (deformindex < Q3MAXDEFORMS) + { + for (i = 0;i < numparameters - 2 && i < Q3DEFORM_MAXPARMS;i++) + shader.deforms[deformindex].parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "projectionshadow")) shader.deforms[deformindex].deform = Q3DEFORM_PROJECTIONSHADOW; + else if (!strcasecmp(parameter[1], "autosprite" )) shader.deforms[deformindex].deform = Q3DEFORM_AUTOSPRITE; + else if (!strcasecmp(parameter[1], "autosprite2" )) shader.deforms[deformindex].deform = Q3DEFORM_AUTOSPRITE2; + else if (!strcasecmp(parameter[1], "text0" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT0; + else if (!strcasecmp(parameter[1], "text1" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT1; + else if (!strcasecmp(parameter[1], "text2" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT2; + else if (!strcasecmp(parameter[1], "text3" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT3; + else if (!strcasecmp(parameter[1], "text4" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT4; + else if (!strcasecmp(parameter[1], "text5" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT5; + else if (!strcasecmp(parameter[1], "text6" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT6; + else if (!strcasecmp(parameter[1], "text7" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT7; + else if (!strcasecmp(parameter[1], "bulge" )) shader.deforms[deformindex].deform = Q3DEFORM_BULGE; + else if (!strcasecmp(parameter[1], "normal" )) shader.deforms[deformindex].deform = Q3DEFORM_NORMAL; + else if (!strcasecmp(parameter[1], "wave" )) + { + shader.deforms[deformindex].deform = Q3DEFORM_WAVE; + shader.deforms[deformindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[3]); + for (i = 0;i < numparameters - 4 && i < Q3WAVEPARMS;i++) + shader.deforms[deformindex].waveparms[i] = atof(parameter[i+4]); + } + else if (!strcasecmp(parameter[1], "move" )) + { + shader.deforms[deformindex].deform = Q3DEFORM_MOVE; + shader.deforms[deformindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[5]); + for (i = 0;i < numparameters - 6 && i < Q3WAVEPARMS;i++) + shader.deforms[deformindex].waveparms[i] = atof(parameter[i+6]); + } + } + } + } + // pick the primary layer to render with + if (shader.numlayers) + { + shader.backgroundlayer = -1; + shader.primarylayer = 0; + // if lightmap comes first this is definitely an ordinary texture + // if the first two layers have the correct blendfuncs and use vertex alpha, it is a blended terrain shader + if ((shader.layers[shader.primarylayer].texturename != NULL) + && !strcasecmp(shader.layers[shader.primarylayer].texturename[0], "$lightmap")) + { + shader.backgroundlayer = -1; + shader.primarylayer = 1; + } + else if (shader.numlayers >= 2 + && shader.layers[1].alphagen.alphagen == Q3ALPHAGEN_VERTEX + && (shader.layers[0].blendfunc[0] == GL_ONE && shader.layers[0].blendfunc[1] == GL_ZERO && !shader.layers[0].alphatest) + && ((shader.layers[1].blendfunc[0] == GL_SRC_ALPHA && shader.layers[1].blendfunc[1] == GL_ONE_MINUS_SRC_ALPHA) + || (shader.layers[1].blendfunc[0] == GL_ONE && shader.layers[1].blendfunc[1] == GL_ZERO && shader.layers[1].alphatest))) + { + // terrain blending or other effects + shader.backgroundlayer = 0; + shader.primarylayer = 1; + } + } + // fix up multiple reflection types + if(shader.textureflags & Q3TEXTUREFLAG_WATERSHADER) + shader.textureflags &= ~(Q3TEXTUREFLAG_REFRACTION | Q3TEXTUREFLAG_REFLECTION | Q3TEXTUREFLAG_CAMERA); + + Q3Shader_AddToHash (&shader); + } + Mem_Free(f); + } + FS_FreeSearch(search); + // free custinfoparm values + for (j = 0; j < numcustsurfaceparms; j++) + Mem_Free(custsurfaceparmnames[j]); +} + +q3shaderinfo_t *Mod_LookupQ3Shader(const char *name) +{ + unsigned short hash; + q3shader_hash_entry_t* entry; + if (!q3shaders_mem) + Mod_LoadQ3Shaders(); + hash = CRC_Block_CaseInsensitive ((const unsigned char *)name, strlen (name)); + entry = q3shader_data->hash + (hash % Q3SHADER_HASH_SIZE); + while (entry != NULL) + { + if (strcasecmp (entry->shader.name, name) == 0) + return &entry->shader; + entry = entry->chain; + } + return NULL; +} + +qboolean Mod_LoadTextureFromQ3Shader(texture_t *texture, const char *name, qboolean warnmissing, qboolean fallback, int defaulttexflags) +{ + int j; + int texflagsmask, texflagsor; + qboolean success = true; + q3shaderinfo_t *shader; + if (!name) + name = ""; + strlcpy(texture->name, name, sizeof(texture->name)); + shader = name[0] ? Mod_LookupQ3Shader(name) : NULL; + + texflagsmask = ~0; + if(!(defaulttexflags & TEXF_PICMIP)) + texflagsmask &= ~TEXF_PICMIP; + if(!(defaulttexflags & TEXF_COMPRESS)) + texflagsmask &= ~TEXF_COMPRESS; + texflagsor = 0; + if(defaulttexflags & TEXF_ISWORLD) + texflagsor |= TEXF_ISWORLD; + if(defaulttexflags & TEXF_ISSPRITE) + texflagsor |= TEXF_ISSPRITE; + // unless later loaded from the shader + texture->offsetmapping = (mod_noshader_default_offsetmapping.value) ? OFFSETMAPPING_DEFAULT : OFFSETMAPPING_OFF; + texture->offsetscale = 1; + texture->specularscalemod = 1; + texture->specularpowermod = 1; + // WHEN ADDING DEFAULTS HERE, REMEMBER TO SYNC TO SHADER LOADING ABOVE + // HERE, AND Q1BSP LOADING + // JUST GREP FOR "specularscalemod = 1". + + if (shader) + { + if (developer_loading.integer) + Con_Printf("%s: loaded shader for %s\n", loadmodel->name, name); + texture->surfaceparms = shader->surfaceparms; + + // allow disabling of picmip or compression by defaulttexflags + texture->textureflags = (shader->textureflags & texflagsmask) | texflagsor; + + if (shader->surfaceparms & Q3SURFACEPARM_SKY) + { + texture->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; + if (shader->skyboxname[0]) + { + // quake3 seems to append a _ to the skybox name, so this must do so as well + dpsnprintf(loadmodel->brush.skybox, sizeof(loadmodel->brush.skybox), "%s_", shader->skyboxname); + } + } + else if ((texture->surfaceflags & Q3SURFACEFLAG_NODRAW) || shader->numlayers == 0) + texture->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; + else + texture->basematerialflags = MATERIALFLAG_WALL; + + if (shader->layers[0].alphatest) + texture->basematerialflags |= MATERIALFLAG_ALPHATEST | MATERIALFLAG_NOSHADOW; + if (shader->textureflags & Q3TEXTUREFLAG_TWOSIDED) + texture->basematerialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_NOCULLFACE; + if (shader->textureflags & Q3TEXTUREFLAG_POLYGONOFFSET) + { + texture->biaspolygonoffset += shader->biaspolygonoffset; + texture->biaspolygonfactor += shader->biaspolygonfactor; + } + if (shader->textureflags & Q3TEXTUREFLAG_REFRACTION) + texture->basematerialflags |= MATERIALFLAG_REFRACTION; + if (shader->textureflags & Q3TEXTUREFLAG_REFLECTION) + texture->basematerialflags |= MATERIALFLAG_REFLECTION; + if (shader->textureflags & Q3TEXTUREFLAG_WATERSHADER) + texture->basematerialflags |= MATERIALFLAG_WATERSHADER; + if (shader->textureflags & Q3TEXTUREFLAG_CAMERA) + texture->basematerialflags |= MATERIALFLAG_CAMERA; + texture->customblendfunc[0] = GL_ONE; + texture->customblendfunc[1] = GL_ZERO; + if (shader->numlayers > 0) + { + texture->customblendfunc[0] = shader->layers[0].blendfunc[0]; + texture->customblendfunc[1] = shader->layers[0].blendfunc[1]; +/* +Q3 shader blendfuncs actually used in the game (* = supported by DP) +* additive GL_ONE GL_ONE +additive weird GL_ONE GL_SRC_ALPHA +additive weird 2 GL_ONE GL_ONE_MINUS_SRC_ALPHA +* alpha GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA +alpha inverse GL_ONE_MINUS_SRC_ALPHA GL_SRC_ALPHA +brighten GL_DST_COLOR GL_ONE +brighten GL_ONE GL_SRC_COLOR +brighten weird GL_DST_COLOR GL_ONE_MINUS_DST_ALPHA +brighten weird 2 GL_DST_COLOR GL_SRC_ALPHA +* modulate GL_DST_COLOR GL_ZERO +* modulate GL_ZERO GL_SRC_COLOR +modulate inverse GL_ZERO GL_ONE_MINUS_SRC_COLOR +modulate inverse alpha GL_ZERO GL_SRC_ALPHA +modulate weird inverse GL_ONE_MINUS_DST_COLOR GL_ZERO +* modulate x2 GL_DST_COLOR GL_SRC_COLOR +* no blend GL_ONE GL_ZERO +nothing GL_ZERO GL_ONE +*/ + // if not opaque, figure out what blendfunc to use + if (shader->layers[0].blendfunc[0] != GL_ONE || shader->layers[0].blendfunc[1] != GL_ZERO) + { + if (shader->layers[0].blendfunc[0] == GL_ONE && shader->layers[0].blendfunc[1] == GL_ONE) + texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else if (shader->layers[0].blendfunc[0] == GL_SRC_ALPHA && shader->layers[0].blendfunc[1] == GL_ONE) + texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else if (shader->layers[0].blendfunc[0] == GL_SRC_ALPHA && shader->layers[0].blendfunc[1] == GL_ONE_MINUS_SRC_ALPHA) + texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else + texture->basematerialflags |= MATERIALFLAG_CUSTOMBLEND | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + } + } + if (!shader->lighting) + texture->basematerialflags |= MATERIALFLAG_FULLBRIGHT; + if (shader->primarylayer >= 0) + { + q3shaderinfo_layer_t* primarylayer = shader->layers + shader->primarylayer; + // copy over many primarylayer parameters + texture->rgbgen = primarylayer->rgbgen; + texture->alphagen = primarylayer->alphagen; + texture->tcgen = primarylayer->tcgen; + memcpy(texture->tcmods, primarylayer->tcmods, sizeof(texture->tcmods)); + // load the textures + texture->numskinframes = primarylayer->numframes; + texture->skinframerate = primarylayer->framerate; + for (j = 0;j < primarylayer->numframes;j++) + { + if(cls.state == ca_dedicated) + { + texture->skinframes[j] = NULL; + } + else if (!(texture->skinframes[j] = R_SkinFrame_LoadExternal(primarylayer->texturename[j], (primarylayer->texflags & texflagsmask) | texflagsor, false))) + { + Con_Printf("^1%s:^7 could not load texture ^3\"%s\"^7 (frame %i) for shader ^2\"%s\"\n", loadmodel->name, primarylayer->texturename[j], j, texture->name); + texture->skinframes[j] = R_SkinFrame_LoadMissing(); + } + } + } + if (shader->backgroundlayer >= 0) + { + q3shaderinfo_layer_t* backgroundlayer = shader->layers + shader->backgroundlayer; + // copy over one secondarylayer parameter + memcpy(texture->backgroundtcmods, backgroundlayer->tcmods, sizeof(texture->backgroundtcmods)); + // load the textures + texture->backgroundnumskinframes = backgroundlayer->numframes; + texture->backgroundskinframerate = backgroundlayer->framerate; + for (j = 0;j < backgroundlayer->numframes;j++) + { + if(cls.state == ca_dedicated) + { + texture->skinframes[j] = NULL; + } + else if (!(texture->backgroundskinframes[j] = R_SkinFrame_LoadExternal(backgroundlayer->texturename[j], (backgroundlayer->texflags & texflagsmask) | texflagsor, false))) + { + Con_Printf("^1%s:^7 could not load texture ^3\"%s\"^7 (background frame %i) for shader ^2\"%s\"\n", loadmodel->name, backgroundlayer->texturename[j], j, texture->name); + texture->backgroundskinframes[j] = R_SkinFrame_LoadMissing(); + } + } + } + if (shader->dpshadow) + texture->basematerialflags &= ~MATERIALFLAG_NOSHADOW; + if (shader->dpnoshadow) + texture->basematerialflags |= MATERIALFLAG_NOSHADOW; + if (shader->dpnortlight) + texture->basematerialflags |= MATERIALFLAG_NORTLIGHT; + memcpy(texture->deforms, shader->deforms, sizeof(texture->deforms)); + texture->reflectmin = shader->reflectmin; + texture->reflectmax = shader->reflectmax; + texture->refractfactor = shader->refractfactor; + Vector4Copy(shader->refractcolor4f, texture->refractcolor4f); + texture->reflectfactor = shader->reflectfactor; + Vector4Copy(shader->reflectcolor4f, texture->reflectcolor4f); + texture->r_water_wateralpha = shader->r_water_wateralpha; + Vector2Copy(shader->r_water_waterscroll, texture->r_water_waterscroll); + texture->offsetmapping = shader->offsetmapping; + texture->offsetscale = shader->offsetscale; + texture->specularscalemod = shader->specularscalemod; + texture->specularpowermod = shader->specularpowermod; + if (shader->dpreflectcube[0]) + texture->reflectcubetexture = R_GetCubemap(shader->dpreflectcube); + + // set up default supercontents (on q3bsp this is overridden by the q3bsp loader) + texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; + if (shader->surfaceparms & Q3SURFACEPARM_LAVA ) texture->supercontents = SUPERCONTENTS_LAVA ; + if (shader->surfaceparms & Q3SURFACEPARM_SLIME ) texture->supercontents = SUPERCONTENTS_SLIME ; + if (shader->surfaceparms & Q3SURFACEPARM_WATER ) texture->supercontents = SUPERCONTENTS_WATER ; + if (shader->surfaceparms & Q3SURFACEPARM_NONSOLID ) texture->supercontents = 0 ; + if (shader->surfaceparms & Q3SURFACEPARM_PLAYERCLIP ) texture->supercontents = SUPERCONTENTS_PLAYERCLIP ; + if (shader->surfaceparms & Q3SURFACEPARM_BOTCLIP ) texture->supercontents = SUPERCONTENTS_MONSTERCLIP ; + if (shader->surfaceparms & Q3SURFACEPARM_SKY ) texture->supercontents = SUPERCONTENTS_SKY ; + + // if (shader->surfaceparms & Q3SURFACEPARM_ALPHASHADOW ) texture->supercontents |= SUPERCONTENTS_ALPHASHADOW ; + // if (shader->surfaceparms & Q3SURFACEPARM_AREAPORTAL ) texture->supercontents |= SUPERCONTENTS_AREAPORTAL ; + // if (shader->surfaceparms & Q3SURFACEPARM_CLUSTERPORTAL) texture->supercontents |= SUPERCONTENTS_CLUSTERPORTAL; + // if (shader->surfaceparms & Q3SURFACEPARM_DETAIL ) texture->supercontents |= SUPERCONTENTS_DETAIL ; + if (shader->surfaceparms & Q3SURFACEPARM_DONOTENTER ) texture->supercontents |= SUPERCONTENTS_DONOTENTER ; + // if (shader->surfaceparms & Q3SURFACEPARM_FOG ) texture->supercontents |= SUPERCONTENTS_FOG ; + if (shader->surfaceparms & Q3SURFACEPARM_LAVA ) texture->supercontents |= SUPERCONTENTS_LAVA ; + // if (shader->surfaceparms & Q3SURFACEPARM_LIGHTFILTER ) texture->supercontents |= SUPERCONTENTS_LIGHTFILTER ; + // if (shader->surfaceparms & Q3SURFACEPARM_METALSTEPS ) texture->supercontents |= SUPERCONTENTS_METALSTEPS ; + // if (shader->surfaceparms & Q3SURFACEPARM_NODAMAGE ) texture->supercontents |= SUPERCONTENTS_NODAMAGE ; + // if (shader->surfaceparms & Q3SURFACEPARM_NODLIGHT ) texture->supercontents |= SUPERCONTENTS_NODLIGHT ; + // if (shader->surfaceparms & Q3SURFACEPARM_NODRAW ) texture->supercontents |= SUPERCONTENTS_NODRAW ; + if (shader->surfaceparms & Q3SURFACEPARM_NODROP ) texture->supercontents |= SUPERCONTENTS_NODROP ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOIMPACT ) texture->supercontents |= SUPERCONTENTS_NOIMPACT ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOLIGHTMAP ) texture->supercontents |= SUPERCONTENTS_NOLIGHTMAP ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOMARKS ) texture->supercontents |= SUPERCONTENTS_NOMARKS ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOMIPMAPS ) texture->supercontents |= SUPERCONTENTS_NOMIPMAPS ; + if (shader->surfaceparms & Q3SURFACEPARM_NONSOLID ) texture->supercontents &=~SUPERCONTENTS_SOLID ; + // if (shader->surfaceparms & Q3SURFACEPARM_ORIGIN ) texture->supercontents |= SUPERCONTENTS_ORIGIN ; + if (shader->surfaceparms & Q3SURFACEPARM_PLAYERCLIP ) texture->supercontents |= SUPERCONTENTS_PLAYERCLIP ; + if (shader->surfaceparms & Q3SURFACEPARM_SKY ) texture->supercontents |= SUPERCONTENTS_SKY ; + // if (shader->surfaceparms & Q3SURFACEPARM_SLICK ) texture->supercontents |= SUPERCONTENTS_SLICK ; + if (shader->surfaceparms & Q3SURFACEPARM_SLIME ) texture->supercontents |= SUPERCONTENTS_SLIME ; + // if (shader->surfaceparms & Q3SURFACEPARM_STRUCTURAL ) texture->supercontents |= SUPERCONTENTS_STRUCTURAL ; + // if (shader->surfaceparms & Q3SURFACEPARM_TRANS ) texture->supercontents |= SUPERCONTENTS_TRANS ; + if (shader->surfaceparms & Q3SURFACEPARM_WATER ) texture->supercontents |= SUPERCONTENTS_WATER ; + // if (shader->surfaceparms & Q3SURFACEPARM_POINTLIGHT ) texture->supercontents |= SUPERCONTENTS_POINTLIGHT ; + // if (shader->surfaceparms & Q3SURFACEPARM_HINT ) texture->supercontents |= SUPERCONTENTS_HINT ; + // if (shader->surfaceparms & Q3SURFACEPARM_DUST ) texture->supercontents |= SUPERCONTENTS_DUST ; + if (shader->surfaceparms & Q3SURFACEPARM_BOTCLIP ) texture->supercontents |= SUPERCONTENTS_BOTCLIP | SUPERCONTENTS_MONSTERCLIP; + // if (shader->surfaceparms & Q3SURFACEPARM_LIGHTGRID ) texture->supercontents |= SUPERCONTENTS_LIGHTGRID ; + // if (shader->surfaceparms & Q3SURFACEPARM_ANTIPORTAL ) texture->supercontents |= SUPERCONTENTS_ANTIPORTAL ; + + if (shader->dpmeshcollisions) + texture->basematerialflags |= MATERIALFLAG_MESHCOLLISIONS; + } + else if (!strcmp(texture->name, "noshader") || !texture->name[0]) + { + if (developer_extra.integer) + Con_DPrintf("^1%s:^7 using fallback noshader material for ^3\"%s\"\n", loadmodel->name, name); + texture->surfaceparms = 0; + texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; + } + else if (!strcmp(texture->name, "common/nodraw") || !strcmp(texture->name, "textures/common/nodraw")) + { + if (developer_extra.integer) + Con_DPrintf("^1%s:^7 using fallback nodraw material for ^3\"%s\"\n", loadmodel->name, name); + texture->surfaceparms = 0; + texture->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; + texture->supercontents = SUPERCONTENTS_SOLID; + } + else + { + if (developer_extra.integer) + Con_DPrintf("^1%s:^7 No shader found for texture ^3\"%s\"\n", loadmodel->name, texture->name); + texture->surfaceparms = 0; + if (texture->surfaceflags & Q3SURFACEFLAG_NODRAW) + { + texture->basematerialflags |= MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; + texture->supercontents = SUPERCONTENTS_SOLID; + } + else if (texture->surfaceflags & Q3SURFACEFLAG_SKY) + { + texture->basematerialflags |= MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; + texture->supercontents = SUPERCONTENTS_SKY; + } + else + { + texture->basematerialflags |= MATERIALFLAG_WALL; + texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; + } + texture->numskinframes = 1; + if(cls.state == ca_dedicated) + { + texture->skinframes[0] = NULL; + success = false; + } + else + { + if (fallback) + { + if ((texture->skinframes[0] = R_SkinFrame_LoadExternal(texture->name, defaulttexflags, false))) + { + if(texture->skinframes[0]->hasalpha) + texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + } + else + success = false; + } + else + success = false; + if (!success && warnmissing) + Con_Printf("^1%s:^7 could not load texture ^3\"%s\"\n", loadmodel->name, texture->name); + } + } + // init the animation variables + texture->currentframe = texture; + if (texture->numskinframes < 1) + texture->numskinframes = 1; + if (!texture->skinframes[0]) + texture->skinframes[0] = R_SkinFrame_LoadMissing(); + texture->currentskinframe = texture->skinframes[0]; + texture->backgroundcurrentskinframe = texture->backgroundskinframes[0]; + return success; +} + +skinfile_t *Mod_LoadSkinFiles(void) +{ + int i, words, line, wordsoverflow; + char *text; + const char *data; + skinfile_t *skinfile = NULL, *first = NULL; + skinfileitem_t *skinfileitem; + char word[10][MAX_QPATH]; + +/* +sample file: +U_bodyBox,models/players/Legoman/BikerA2.tga +U_RArm,models/players/Legoman/BikerA1.tga +U_LArm,models/players/Legoman/BikerA1.tga +U_armor,common/nodraw +U_sword,common/nodraw +U_shield,common/nodraw +U_homb,common/nodraw +U_backpack,common/nodraw +U_colcha,common/nodraw +tag_head, +tag_weapon, +tag_torso, +*/ + memset(word, 0, sizeof(word)); + for (i = 0;i < 256 && (data = text = (char *)FS_LoadFile(va("%s_%i.skin", loadmodel->name, i), tempmempool, true, NULL));i++) + { + // If it's the first file we parse + if (skinfile == NULL) + { + skinfile = (skinfile_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfile_t)); + first = skinfile; + } + else + { + skinfile->next = (skinfile_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfile_t)); + skinfile = skinfile->next; + } + skinfile->next = NULL; + + for(line = 0;;line++) + { + // parse line + if (!COM_ParseToken_QuakeC(&data, true)) + break; + if (!strcmp(com_token, "\n")) + continue; + words = 0; + wordsoverflow = false; + do + { + if (words < 10) + strlcpy(word[words++], com_token, sizeof (word[0])); + else + wordsoverflow = true; + } + while (COM_ParseToken_QuakeC(&data, true) && strcmp(com_token, "\n")); + if (wordsoverflow) + { + Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: line with too many statements, skipping\n", loadmodel->name, i, line); + continue; + } + // words is always >= 1 + if (!strcmp(word[0], "replace")) + { + if (words == 3) + { + if (developer_loading.integer) + Con_Printf("Mod_LoadSkinFiles: parsed mesh \"%s\" shader replacement \"%s\"\n", word[1], word[2]); + skinfileitem = (skinfileitem_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfileitem_t)); + skinfileitem->next = skinfile->items; + skinfile->items = skinfileitem; + strlcpy (skinfileitem->name, word[1], sizeof (skinfileitem->name)); + strlcpy (skinfileitem->replacement, word[2], sizeof (skinfileitem->replacement)); + } + else + Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: wrong number of parameters to command \"%s\", see documentation in DP_GFX_SKINFILES extension in dpextensions.qc\n", loadmodel->name, i, line, word[0]); + } + else if (words >= 2 && !strncmp(word[0], "tag_", 4)) + { + // tag name, like "tag_weapon," + // not used for anything (not even in Quake3) + } + else if (words >= 2 && !strcmp(word[1], ",")) + { + // mesh shader name, like "U_RArm,models/players/Legoman/BikerA1.tga" + if (developer_loading.integer) + Con_Printf("Mod_LoadSkinFiles: parsed mesh \"%s\" shader replacement \"%s\"\n", word[0], word[2]); + skinfileitem = (skinfileitem_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfileitem_t)); + skinfileitem->next = skinfile->items; + skinfile->items = skinfileitem; + strlcpy (skinfileitem->name, word[0], sizeof (skinfileitem->name)); + strlcpy (skinfileitem->replacement, word[2], sizeof (skinfileitem->replacement)); + } + else + Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: does not look like tag or mesh specification, or replace command, see documentation in DP_GFX_SKINFILES extension in dpextensions.qc\n", loadmodel->name, i, line); + } + Mem_Free(text); + } + if (i) + loadmodel->numskins = i; + return first; +} + +void Mod_FreeSkinFiles(skinfile_t *skinfile) +{ + skinfile_t *next; + skinfileitem_t *skinfileitem, *nextitem; + for (;skinfile;skinfile = next) + { + next = skinfile->next; + for (skinfileitem = skinfile->items;skinfileitem;skinfileitem = nextitem) + { + nextitem = skinfileitem->next; + Mem_Free(skinfileitem); + } + Mem_Free(skinfile); + } +} + +int Mod_CountSkinFiles(skinfile_t *skinfile) +{ + int i; + for (i = 0;skinfile;skinfile = skinfile->next, i++); + return i; +} + +void Mod_SnapVertices(int numcomponents, int numvertices, float *vertices, float snap) +{ + int i; + double isnap = 1.0 / snap; + for (i = 0;i < numvertices*numcomponents;i++) + vertices[i] = floor(vertices[i]*isnap)*snap; +} + +int Mod_RemoveDegenerateTriangles(int numtriangles, const int *inelement3i, int *outelement3i, const float *vertex3f) +{ + int i, outtriangles; + float edgedir1[3], edgedir2[3], temp[3]; + // a degenerate triangle is one with no width (thickness, surface area) + // these are characterized by having all 3 points colinear (along a line) + // or having two points identical + // the simplest check is to calculate the triangle's area + for (i = 0, outtriangles = 0;i < numtriangles;i++, inelement3i += 3) + { + // calculate first edge + VectorSubtract(vertex3f + inelement3i[1] * 3, vertex3f + inelement3i[0] * 3, edgedir1); + VectorSubtract(vertex3f + inelement3i[2] * 3, vertex3f + inelement3i[0] * 3, edgedir2); + CrossProduct(edgedir1, edgedir2, temp); + if (VectorLength2(temp) < 0.001f) + continue; // degenerate triangle (no area) + // valid triangle (has area) + VectorCopy(inelement3i, outelement3i); + outelement3i += 3; + outtriangles++; + } + return outtriangles; +} + +void Mod_VertexRangeFromElements(int numelements, const int *elements, int *firstvertexpointer, int *lastvertexpointer) +{ + int i, e; + int firstvertex, lastvertex; + if (numelements > 0 && elements) + { + firstvertex = lastvertex = elements[0]; + for (i = 1;i < numelements;i++) + { + e = elements[i]; + firstvertex = min(firstvertex, e); + lastvertex = max(lastvertex, e); + } + } + else + firstvertex = lastvertex = 0; + if (firstvertexpointer) + *firstvertexpointer = firstvertex; + if (lastvertexpointer) + *lastvertexpointer = lastvertex; +} + +void Mod_MakeSortedSurfaces(dp_model_t *mod) +{ + // make an optimal set of texture-sorted batches to draw... + int j, t; + int *firstsurfacefortexture; + int *numsurfacesfortexture; + if (!mod->sortedmodelsurfaces) + mod->sortedmodelsurfaces = (int *) Mem_Alloc(loadmodel->mempool, mod->nummodelsurfaces * sizeof(*mod->sortedmodelsurfaces)); + firstsurfacefortexture = (int *) Mem_Alloc(tempmempool, mod->num_textures * sizeof(*firstsurfacefortexture)); + numsurfacesfortexture = (int *) Mem_Alloc(tempmempool, mod->num_textures * sizeof(*numsurfacesfortexture)); + memset(numsurfacesfortexture, 0, mod->num_textures * sizeof(*numsurfacesfortexture)); + for (j = 0;j < mod->nummodelsurfaces;j++) + { + const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; + int t = (int)(surface->texture - mod->data_textures); + numsurfacesfortexture[t]++; + } + j = 0; + for (t = 0;t < mod->num_textures;t++) + { + firstsurfacefortexture[t] = j; + j += numsurfacesfortexture[t]; + } + for (j = 0;j < mod->nummodelsurfaces;j++) + { + const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; + int t = (int)(surface->texture - mod->data_textures); + mod->sortedmodelsurfaces[firstsurfacefortexture[t]++] = j + mod->firstmodelsurface; + } + Mem_Free(firstsurfacefortexture); + Mem_Free(numsurfacesfortexture); +} + +void Mod_BuildVBOs(void) +{ + if (!loadmodel->surfmesh.num_vertices) + return; + + if (gl_paranoid.integer && loadmodel->surfmesh.data_element3s && loadmodel->surfmesh.data_element3i) + { + int i; + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + { + if (loadmodel->surfmesh.data_element3s[i] != loadmodel->surfmesh.data_element3i[i]) + { + Con_Printf("Mod_BuildVBOs: element %u is incorrect (%u should be %u)\n", i, loadmodel->surfmesh.data_element3s[i], loadmodel->surfmesh.data_element3i[i]); + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + } + } + } + + // build r_vertexmesh_t array + // (compressed interleaved array for D3D) + if (!loadmodel->surfmesh.vertexmesh && vid.useinterleavedarrays) + { + int vertexindex; + int numvertices = loadmodel->surfmesh.num_vertices; + r_vertexmesh_t *vertexmesh; + loadmodel->surfmesh.vertexmesh = vertexmesh = (r_vertexmesh_t*)Mem_Alloc(loadmodel->mempool, numvertices * sizeof(*loadmodel->surfmesh.vertexmesh)); + for (vertexindex = 0;vertexindex < numvertices;vertexindex++, vertexmesh++) + { + VectorCopy(loadmodel->surfmesh.data_vertex3f + 3*vertexindex, vertexmesh->vertex3f); + VectorScale(loadmodel->surfmesh.data_svector3f + 3*vertexindex, 1.0f, vertexmesh->svector3f); + VectorScale(loadmodel->surfmesh.data_tvector3f + 3*vertexindex, 1.0f, vertexmesh->tvector3f); + VectorScale(loadmodel->surfmesh.data_normal3f + 3*vertexindex, 1.0f, vertexmesh->normal3f); + if (loadmodel->surfmesh.data_lightmapcolor4f) + Vector4Copy(loadmodel->surfmesh.data_lightmapcolor4f + 4*vertexindex, vertexmesh->color4f); + Vector2Copy(loadmodel->surfmesh.data_texcoordtexture2f + 2*vertexindex, vertexmesh->texcoordtexture2f); + if (loadmodel->surfmesh.data_texcoordlightmap2f) + Vector2Scale(loadmodel->surfmesh.data_texcoordlightmap2f + 2*vertexindex, 1.0f, vertexmesh->texcoordlightmap2f); + } + } + + // upload r_vertexmesh_t array as a buffer + if (loadmodel->surfmesh.vertexmesh && !loadmodel->surfmesh.vertexmeshbuffer) + loadmodel->surfmesh.vertexmeshbuffer = R_Mesh_CreateMeshBuffer(loadmodel->surfmesh.vertexmesh, loadmodel->surfmesh.num_vertices * sizeof(*loadmodel->surfmesh.vertexmesh), loadmodel->name, false, false, false); + + // upload vertex3f array as a buffer + if (loadmodel->surfmesh.data_vertex3f && !loadmodel->surfmesh.vertex3fbuffer) + loadmodel->surfmesh.vertex3fbuffer = R_Mesh_CreateMeshBuffer(loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.num_vertices * sizeof(float[3]), loadmodel->name, false, false, false); + + // upload short indices as a buffer + if (loadmodel->surfmesh.data_element3s && !loadmodel->surfmesh.data_element3s_indexbuffer) + loadmodel->surfmesh.data_element3s_indexbuffer = R_Mesh_CreateMeshBuffer(loadmodel->surfmesh.data_element3s, loadmodel->surfmesh.num_triangles * sizeof(short[3]), loadmodel->name, true, false, true); + + // upload int indices as a buffer + if (loadmodel->surfmesh.data_element3i && !loadmodel->surfmesh.data_element3i_indexbuffer && !loadmodel->surfmesh.data_element3s) + loadmodel->surfmesh.data_element3i_indexbuffer = R_Mesh_CreateMeshBuffer(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles * sizeof(int[3]), loadmodel->name, true, false, false); + + // only build a vbo if one has not already been created (this is important for brush models which load specially) + // vertex buffer is several arrays and we put them in the same buffer + // + // is this wise? the texcoordtexture2f array is used with dynamic + // vertex/svector/tvector/normal when rendering animated models, on the + // other hand animated models don't use a lot of vertices anyway... + if (!loadmodel->surfmesh.vbo_vertexbuffer && !vid.useinterleavedarrays) + { + size_t size; + unsigned char *mem; + size = 0; + loadmodel->surfmesh.vbooffset_vertex3f = size;if (loadmodel->surfmesh.data_vertex3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.vbooffset_svector3f = size;if (loadmodel->surfmesh.data_svector3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.vbooffset_tvector3f = size;if (loadmodel->surfmesh.data_tvector3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.vbooffset_normal3f = size;if (loadmodel->surfmesh.data_normal3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.vbooffset_texcoordtexture2f = size;if (loadmodel->surfmesh.data_texcoordtexture2f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[2]); + loadmodel->surfmesh.vbooffset_texcoordlightmap2f = size;if (loadmodel->surfmesh.data_texcoordlightmap2f) size += loadmodel->surfmesh.num_vertices * sizeof(float[2]); + loadmodel->surfmesh.vbooffset_lightmapcolor4f = size;if (loadmodel->surfmesh.data_lightmapcolor4f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[4]); + mem = (unsigned char *)Mem_Alloc(tempmempool, size); + if (loadmodel->surfmesh.data_vertex3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_vertex3f , loadmodel->surfmesh.data_vertex3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); + if (loadmodel->surfmesh.data_svector3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_svector3f , loadmodel->surfmesh.data_svector3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); + if (loadmodel->surfmesh.data_tvector3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_tvector3f , loadmodel->surfmesh.data_tvector3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); + if (loadmodel->surfmesh.data_normal3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_normal3f , loadmodel->surfmesh.data_normal3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); + if (loadmodel->surfmesh.data_texcoordtexture2f ) memcpy(mem + loadmodel->surfmesh.vbooffset_texcoordtexture2f , loadmodel->surfmesh.data_texcoordtexture2f , loadmodel->surfmesh.num_vertices * sizeof(float[2])); + if (loadmodel->surfmesh.data_texcoordlightmap2f) memcpy(mem + loadmodel->surfmesh.vbooffset_texcoordlightmap2f, loadmodel->surfmesh.data_texcoordlightmap2f, loadmodel->surfmesh.num_vertices * sizeof(float[2])); + if (loadmodel->surfmesh.data_lightmapcolor4f ) memcpy(mem + loadmodel->surfmesh.vbooffset_lightmapcolor4f , loadmodel->surfmesh.data_lightmapcolor4f , loadmodel->surfmesh.num_vertices * sizeof(float[4])); + loadmodel->surfmesh.vbo_vertexbuffer = R_Mesh_CreateMeshBuffer(mem, size, loadmodel->name, false, false, false); + Mem_Free(mem); + } +} + +static void Mod_Decompile_OBJ(dp_model_t *model, const char *filename, const char *mtlfilename, const char *originalfilename) +{ + int submodelindex, vertexindex, surfaceindex, triangleindex, textureindex, countvertices = 0, countsurfaces = 0, countfaces = 0, counttextures = 0; + int a, b, c; + const char *texname; + const int *e; + const float *v, *vn, *vt; + size_t l; + size_t outbufferpos = 0; + size_t outbuffermax = 0x100000; + char *outbuffer = (char *) Z_Malloc(outbuffermax), *oldbuffer; + const msurface_t *surface; + const int maxtextures = 256; + char *texturenames = (char *) Z_Malloc(maxtextures * MAX_QPATH); + dp_model_t *submodel; + + // construct the mtllib file + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "# mtllib for %s exported by darkplaces engine\n", originalfilename); + if (l > 0) + outbufferpos += l; + for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->num_surfaces;surfaceindex++, surface++) + { + countsurfaces++; + countvertices += surface->num_vertices; + countfaces += surface->num_triangles; + texname = (surface->texture && surface->texture->name[0]) ? surface->texture->name : "default"; + for (textureindex = 0;textureindex < counttextures;textureindex++) + if (!strcmp(texturenames + textureindex * MAX_QPATH, texname)) + break; + if (textureindex < counttextures) + continue; // already wrote this material entry + if (textureindex >= maxtextures) + continue; // just a precaution + textureindex = counttextures++; + strlcpy(texturenames + textureindex * MAX_QPATH, texname, MAX_QPATH); + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "newmtl %s\nNs 96.078431\nKa 0 0 0\nKd 0.64 0.64 0.64\nKs 0.5 0.5 0.5\nNi 1\nd 1\nillum 2\nmap_Kd %s%s\n\n", texname, texname, strstr(texname, ".tga") ? "" : ".tga"); + if (l > 0) + outbufferpos += l; + } + + // write the mtllib file + FS_WriteFile(mtlfilename, outbuffer, outbufferpos); + + // construct the obj file + outbufferpos = 0; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "# model exported from %s by darkplaces engine\n# %i vertices, %i faces, %i surfaces\nmtllib %s\n", originalfilename, countvertices, countfaces, countsurfaces, mtlfilename); + if (l > 0) + outbufferpos += l; + + for (vertexindex = 0, v = model->surfmesh.data_vertex3f, vn = model->surfmesh.data_normal3f, vt = model->surfmesh.data_texcoordtexture2f;vertexindex < model->surfmesh.num_vertices;vertexindex++, v += 3, vn += 3, vt += 2) + { + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "v %f %f %f\nvn %f %f %f\nvt %f %f\n", v[0], v[2], v[1], vn[0], vn[2], vn[1], vt[0], 1-vt[1]); + if (l > 0) + outbufferpos += l; + } + + for (submodelindex = 0;submodelindex < max(1, model->brush.numsubmodels);submodelindex++) + { + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "o %i\n", submodelindex); + if (l > 0) + outbufferpos += l; + submodel = model->brush.numsubmodels ? model->brush.submodels[submodelindex] : model; + for (surfaceindex = 0;surfaceindex < submodel->nummodelsurfaces;surfaceindex++) + { + surface = model->data_surfaces + submodel->sortedmodelsurfaces[surfaceindex]; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "usemtl %s\n", (surface->texture && surface->texture->name[0]) ? surface->texture->name : "default"); + if (l > 0) + outbufferpos += l; + for (triangleindex = 0, e = model->surfmesh.data_element3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + a = e[0]+1; + b = e[1]+1; + c = e[2]+1; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", a,a,a,b,b,b,c,c,c); + if (l > 0) + outbufferpos += l; + } + } + } + + // write the obj file + FS_WriteFile(filename, outbuffer, outbufferpos); + + // clean up + Z_Free(outbuffer); + Z_Free(texturenames); + + // print some stats + Con_Printf("Wrote %s (%i bytes, %i vertices, %i faces, %i surfaces with %i distinct textures)\n", filename, (int)outbufferpos, countvertices, countfaces, countsurfaces, counttextures); +} + +static void Mod_Decompile_SMD(dp_model_t *model, const char *filename, int firstpose, int numposes, qboolean writetriangles) +{ + int countnodes = 0, counttriangles = 0, countframes = 0; + int surfaceindex; + int triangleindex; + int transformindex; + int poseindex; + int cornerindex; + const int *e; + size_t l; + size_t outbufferpos = 0; + size_t outbuffermax = 0x100000; + char *outbuffer = (char *) Z_Malloc(outbuffermax), *oldbuffer; + const msurface_t *surface; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "version 1\nnodes\n"); + if (l > 0) + outbufferpos += l; + for (transformindex = 0;transformindex < model->num_bones;transformindex++) + { + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + countnodes++; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i \"%s\" %3i\n", transformindex, model->data_bones[transformindex].name, model->data_bones[transformindex].parent); + if (l > 0) + outbufferpos += l; + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\nskeleton\n"); + if (l > 0) + outbufferpos += l; + for (poseindex = 0;poseindex < numposes;poseindex++) + { + countframes++; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "time %i\n", poseindex); + if (l > 0) + outbufferpos += l; + for (transformindex = 0;transformindex < model->num_bones;transformindex++) + { + float angles[3]; + float mtest[4][3]; + matrix4x4_t posematrix; + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + + // strangely the smd angles are for a transposed matrix, so we + // have to generate a transposed matrix, then convert that... + Matrix4x4_FromBonePose6s(&posematrix, model->num_posescale, model->data_poses6s + 6*(model->num_bones * poseindex + transformindex)); + Matrix4x4_ToArray12FloatGL(&posematrix, mtest[0]); + AnglesFromVectors(angles, mtest[0], mtest[2], false); + if (angles[0] >= 180) angles[0] -= 360; + if (angles[1] >= 180) angles[1] -= 360; + if (angles[2] >= 180) angles[2] -= 360; + +#if 0 +{ + float a = DEG2RAD(angles[ROLL]); + float b = DEG2RAD(angles[PITCH]); + float c = DEG2RAD(angles[YAW]); + float cy, sy, cp, sp, cr, sr; + float test[4][3]; + // smd matrix construction, for comparing + sy = sin(c); + cy = cos(c); + sp = sin(b); + cp = cos(b); + sr = sin(a); + cr = cos(a); + + test[0][0] = cp*cy; + test[0][1] = cp*sy; + test[0][2] = -sp; + test[1][0] = sr*sp*cy+cr*-sy; + test[1][1] = sr*sp*sy+cr*cy; + test[1][2] = sr*cp; + test[2][0] = (cr*sp*cy+-sr*-sy); + test[2][1] = (cr*sp*sy+-sr*cy); + test[2][2] = cr*cp; + test[3][0] = pose[9]; + test[3][1] = pose[10]; + test[3][2] = pose[11]; +} +#endif + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f\n", transformindex, mtest[3][0], mtest[3][1], mtest[3][2], DEG2RAD(angles[ROLL]), DEG2RAD(angles[PITCH]), DEG2RAD(angles[YAW])); + if (l > 0) + outbufferpos += l; + } + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\n"); + if (l > 0) + outbufferpos += l; + if (writetriangles) + { + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "triangles\n"); + if (l > 0) + outbufferpos += l; + for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->num_surfaces;surfaceindex++, surface++) + { + for (triangleindex = 0, e = model->surfmesh.data_element3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + counttriangles++; + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%s\n", surface->texture && surface->texture->name[0] ? surface->texture->name : "default.bmp"); + if (l > 0) + outbufferpos += l; + for (cornerindex = 0;cornerindex < 3;cornerindex++) + { + const int index = e[2-cornerindex]; + const float *v = model->surfmesh.data_vertex3f + index * 3; + const float *vn = model->surfmesh.data_normal3f + index * 3; + const float *vt = model->surfmesh.data_texcoordtexture2f + index * 2; + const int b = model->surfmesh.blends[index]; + if (b < model->num_bones) + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f\n" , b, v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1]); + else + { + const blendweights_t *w = model->surfmesh.data_blendweights + b - model->num_bones; + const unsigned char *wi = w->index; + const unsigned char *wf = w->influence; + if (wf[3]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 4 %i %f %i %f %i %f %i %f\n", wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f, wi[2], wf[2]/255.0f, wi[3], wf[3]/255.0f); + else if (wf[2]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 3 %i %f %i %f %i %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f, wi[2], wf[2]/255.0f); + else if (wf[1]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 2 %i %f %i %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f); + else l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1]); + } + if (l > 0) + outbufferpos += l; + } + } + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\n"); + if (l > 0) + outbufferpos += l; + } + + FS_WriteFile(filename, outbuffer, outbufferpos); + Z_Free(outbuffer); + + Con_Printf("Wrote %s (%i bytes, %i nodes, %i frames, %i triangles)\n", filename, (int)outbufferpos, countnodes, countframes, counttriangles); +} + +/* +================ +Mod_Decompile_f + +decompiles a model to editable files +================ +*/ +static void Mod_Decompile_f(void) +{ + int i, j, k, l, first, count; + dp_model_t *mod; + char inname[MAX_QPATH]; + char outname[MAX_QPATH]; + char mtlname[MAX_QPATH]; + char basename[MAX_QPATH]; + char animname[MAX_QPATH]; + char animname2[MAX_QPATH]; + char zymtextbuffer[16384]; + char dpmtextbuffer[16384]; + char framegroupstextbuffer[16384]; + int zymtextsize = 0; + int dpmtextsize = 0; + int framegroupstextsize = 0; + + if (Cmd_Argc() != 2) + { + Con_Print("usage: modeldecompile \n"); + return; + } + + strlcpy(inname, Cmd_Argv(1), sizeof(inname)); + FS_StripExtension(inname, basename, sizeof(basename)); + + mod = Mod_ForName(inname, false, true, inname[0] == '*' ? cl.model_name[1] : NULL); + if (mod->brush.submodel) + { + // if we're decompiling a submodel, be sure to give it a proper name based on its parent + FS_StripExtension(cl.model_name[1], outname, sizeof(outname)); + dpsnprintf(basename, sizeof(basename), "%s/%s", outname, mod->name); + outname[0] = 0; + } + if (!mod) + { + Con_Print("No such model\n"); + return; + } + if (!mod->surfmesh.num_triangles) + { + Con_Print("Empty model (or sprite)\n"); + return; + } + + // export OBJ if possible (not on sprites) + if (mod->surfmesh.num_triangles) + { + dpsnprintf(outname, sizeof(outname), "%s_decompiled.obj", basename); + dpsnprintf(mtlname, sizeof(mtlname), "%s_decompiled.mtl", basename); + Mod_Decompile_OBJ(mod, outname, mtlname, inname); + } + + // export SMD if possible (only for skeletal models) + if (mod->surfmesh.num_triangles && mod->num_bones) + { + dpsnprintf(outname, sizeof(outname), "%s_decompiled/ref1.smd", basename); + Mod_Decompile_SMD(mod, outname, 0, 1, true); + l = dpsnprintf(zymtextbuffer + zymtextsize, sizeof(zymtextbuffer) - zymtextsize, "output out.zym\nscale 1\norigin 0 0 0\nmesh ref1.smd\n"); + if (l > 0) zymtextsize += l; + l = dpsnprintf(dpmtextbuffer + dpmtextsize, sizeof(dpmtextbuffer) - dpmtextsize, "outputdir .\nmodel out\nscale 1\norigin 0 0 0\nscene ref1.smd\n"); + if (l > 0) dpmtextsize += l; + for (i = 0;i < mod->numframes;i = j) + { + strlcpy(animname, mod->animscenes[i].name, sizeof(animname)); + first = mod->animscenes[i].firstframe; + if (mod->animscenes[i].framecount > 1) + { + // framegroup anim + count = mod->animscenes[i].framecount; + j = i + 1; + } + else + { + // individual frame + // check for additional frames with same name + for (l = 0, k = strlen(animname);animname[l];l++) + if(animname[l] < '0' || animname[l] > '9') + k = l + 1; + if(k > 0 && animname[k-1] == '_') + --k; + animname[k] = 0; + count = mod->num_poses - first; + for (j = i + 1;j < mod->numframes;j++) + { + strlcpy(animname2, mod->animscenes[j].name, sizeof(animname2)); + for (l = 0, k = strlen(animname2);animname2[l];l++) + if(animname2[l] < '0' || animname2[l] > '9') + k = l + 1; + if(k > 0 && animname[k-1] == '_') + --k; + animname2[k] = 0; + if (strcmp(animname2, animname) || mod->animscenes[j].framecount > 1) + { + count = mod->animscenes[j].firstframe - first; + break; + } + } + // if it's only one frame, use the original frame name + if (j == i + 1) + strlcpy(animname, mod->animscenes[i].name, sizeof(animname)); + + } + dpsnprintf(outname, sizeof(outname), "%s_decompiled/%s.smd", basename, animname); + Mod_Decompile_SMD(mod, outname, first, count, false); + if (zymtextsize < (int)sizeof(zymtextbuffer) - 100) + { + l = dpsnprintf(zymtextbuffer + zymtextsize, sizeof(zymtextbuffer) - zymtextsize, "scene %s.smd fps %g %s\n", animname, mod->animscenes[i].framerate, mod->animscenes[i].loop ? "" : " noloop"); + if (l > 0) zymtextsize += l; + } + if (dpmtextsize < (int)sizeof(dpmtextbuffer) - 100) + { + l = dpsnprintf(dpmtextbuffer + dpmtextsize, sizeof(dpmtextbuffer) - dpmtextsize, "scene %s.smd fps %g %s\n", animname, mod->animscenes[i].framerate, mod->animscenes[i].loop ? "" : " noloop"); + if (l > 0) dpmtextsize += l; + } + if (framegroupstextsize < (int)sizeof(framegroupstextbuffer) - 100) + { + l = dpsnprintf(framegroupstextbuffer + framegroupstextsize, sizeof(framegroupstextbuffer) - framegroupstextsize, "%d %d %f %d // %s\n", first, count, mod->animscenes[i].framerate, mod->animscenes[i].loop, animname); + if (l > 0) framegroupstextsize += l; + } + } + if (zymtextsize) + FS_WriteFile(va("%s_decompiled/out_zym.txt", basename), zymtextbuffer, (fs_offset_t)zymtextsize); + if (dpmtextsize) + FS_WriteFile(va("%s_decompiled/out_dpm.txt", basename), dpmtextbuffer, (fs_offset_t)dpmtextsize); + if (framegroupstextsize) + FS_WriteFile(va("%s_decompiled.framegroups", basename), framegroupstextbuffer, (fs_offset_t)framegroupstextsize); + } +} + +void Mod_AllocLightmap_Init(mod_alloclightmap_state_t *state, int width, int height) +{ + int y; + memset(state, 0, sizeof(*state)); + state->width = width; + state->height = height; + state->currentY = 0; + state->rows = (mod_alloclightmap_row_t *)Mem_Alloc(loadmodel->mempool, state->height * sizeof(*state->rows)); + for (y = 0;y < state->height;y++) + { + state->rows[y].currentX = 0; + state->rows[y].rowY = -1; + } +} + +void Mod_AllocLightmap_Reset(mod_alloclightmap_state_t *state) +{ + int y; + state->currentY = 0; + for (y = 0;y < state->height;y++) + { + state->rows[y].currentX = 0; + state->rows[y].rowY = -1; + } +} + +void Mod_AllocLightmap_Free(mod_alloclightmap_state_t *state) +{ + if (state->rows) + Mem_Free(state->rows); + memset(state, 0, sizeof(*state)); +} + +qboolean Mod_AllocLightmap_Block(mod_alloclightmap_state_t *state, int blockwidth, int blockheight, int *outx, int *outy) +{ + mod_alloclightmap_row_t *row; + int y; + + row = state->rows + blockheight; + if ((row->rowY < 0) || (row->currentX + blockwidth > state->width)) + { + if (state->currentY + blockheight <= state->height) + { + // use the current allocation position + row->rowY = state->currentY; + row->currentX = 0; + state->currentY += blockheight; + } + else + { + // find another position + for (y = blockheight;y < state->height;y++) + { + if ((state->rows[y].rowY >= 0) && (state->rows[y].currentX + blockwidth <= state->width)) + { + row = state->rows + y; + break; + } + } + if (y == state->height) + return false; + } + } + *outy = row->rowY; + *outx = row->currentX; + row->currentX += blockwidth; + + return true; +} + +typedef struct lightmapsample_s +{ + float pos[3]; + float sh1[4][3]; + float *vertex_color; + unsigned char *lm_bgr; + unsigned char *lm_dir; +} +lightmapsample_t; + +typedef struct lightmapvertex_s +{ + int index; + float pos[3]; + float normal[3]; + float texcoordbase[2]; + float texcoordlightmap[2]; + float lightcolor[4]; +} +lightmapvertex_t; + +typedef struct lightmaptriangle_s +{ + int triangleindex; + int surfaceindex; + int lightmapindex; + int axis; + int lmoffset[2]; + int lmsize[2]; + // 2D modelspace coordinates of min corner + // snapped to lightmap grid but not in grid coordinates + float lmbase[2]; + // 2D modelspace to lightmap coordinate scale + float lmscale[2]; + float vertex[3][3]; + float mins[3]; + float maxs[3]; +} +lightmaptriangle_t; + +typedef struct lightmaplight_s +{ + float origin[3]; + float radius; + float iradius; + float radius2; + float color[3]; + svbsp_t svbsp; +} +lightmaplight_t; + +lightmaptriangle_t *mod_generatelightmaps_lightmaptriangles; + +#define MAX_LIGHTMAPSAMPLES 64 +static int mod_generatelightmaps_numoffsets[3]; +static float mod_generatelightmaps_offsets[3][MAX_LIGHTMAPSAMPLES][3]; + +static int mod_generatelightmaps_numlights; +static lightmaplight_t *mod_generatelightmaps_lightinfo; + +extern int R_Shadow_GetRTLightInfo(unsigned int lightindex, float *origin, float *radius, float *color); +extern cvar_t r_shadow_lightattenuationdividebias; +extern cvar_t r_shadow_lightattenuationlinearscale; + +static void Mod_GenerateLightmaps_LightPoint(dp_model_t *model, const vec3_t pos, vec3_t ambient, vec3_t diffuse, vec3_t lightdir) +{ + int i; + int index; + int result; + float relativepoint[3]; + float color[3]; + float dir[3]; + float dist; + float dist2; + float intensity; + float sample[5*3]; + float lightorigin[3]; + float lightradius; + float lightradius2; + float lightiradius; + float lightcolor[3]; + trace_t trace; + for (i = 0;i < 5*3;i++) + sample[i] = 0.0f; + for (index = 0;;index++) + { + result = R_Shadow_GetRTLightInfo(index, lightorigin, &lightradius, lightcolor); + if (result < 0) + break; + if (result == 0) + continue; + lightradius2 = lightradius * lightradius; + VectorSubtract(lightorigin, pos, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + lightiradius = 1.0f / lightradius; + dist = sqrt(dist2) * lightiradius; + intensity = (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); + if (intensity <= 0.0f) + continue; + if (model && model->TraceLine) + { + model->TraceLine(model, NULL, NULL, &trace, pos, lightorigin, SUPERCONTENTS_VISBLOCKERMASK); + if (trace.fraction < 1) + continue; + } + // scale down intensity to add to both ambient and diffuse + //intensity *= 0.5f; + VectorNormalize(relativepoint); + VectorScale(lightcolor, intensity, color); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity *= VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } + // calculate the direction we'll use to reduce the sample to a directional light source + VectorCopy(sample + 12, dir); + //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); + VectorNormalize(dir); + // extract the diffuse color along the chosen direction and scale it + diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]); + diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]); + diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]); + // subtract some of diffuse from ambient + VectorMA(sample, -0.333f, diffuse, ambient); + // store the normalized lightdir + VectorCopy(dir, lightdir); +} + +static void Mod_GenerateLightmaps_CreateLights_ComputeSVBSP_InsertSurfaces(const dp_model_t *model, svbsp_t *svbsp, const float *mins, const float *maxs) +{ + int surfaceindex; + int triangleindex; + const msurface_t *surface; + const float *vertex3f = model->surfmesh.data_vertex3f; + const int *element3i = model->surfmesh.data_element3i; + const int *e; + float v2[3][3]; + for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->nummodelsurfaces;surfaceindex++, surface++) + { + if (!BoxesOverlap(surface->mins, surface->maxs, mins, maxs)) + continue; + if (surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW) + continue; + for (triangleindex = 0, e = element3i + 3*surface->num_firsttriangle;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + VectorCopy(vertex3f + 3*e[0], v2[0]); + VectorCopy(vertex3f + 3*e[1], v2[1]); + VectorCopy(vertex3f + 3*e[2], v2[2]); + SVBSP_AddPolygon(svbsp, 3, v2[0], true, NULL, NULL, 0); + } + } +} + +static void Mod_GenerateLightmaps_CreateLights_ComputeSVBSP(dp_model_t *model, lightmaplight_t *lightinfo) +{ + int maxnodes = 1<<14; + svbsp_node_t *nodes; + float origin[3]; + float mins[3]; + float maxs[3]; + svbsp_t svbsp; + VectorSet(mins, lightinfo->origin[0] - lightinfo->radius, lightinfo->origin[1] - lightinfo->radius, lightinfo->origin[2] - lightinfo->radius); + VectorSet(maxs, lightinfo->origin[0] + lightinfo->radius, lightinfo->origin[1] + lightinfo->radius, lightinfo->origin[2] + lightinfo->radius); + VectorCopy(lightinfo->origin, origin); + nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, maxnodes * sizeof(*nodes)); + for (;;) + { + SVBSP_Init(&svbsp, origin, maxnodes, nodes); + Mod_GenerateLightmaps_CreateLights_ComputeSVBSP_InsertSurfaces(model, &svbsp, mins, maxs); + if (svbsp.ranoutofnodes) + { + maxnodes *= 16; + if (maxnodes > 1<<22) + { + Mem_Free(nodes); + return; + } + Mem_Free(nodes); + nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, maxnodes * sizeof(*nodes)); + } + else + break; + } + if (svbsp.numnodes > 0) + { + svbsp.nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, svbsp.numnodes * sizeof(*nodes)); + memcpy(svbsp.nodes, nodes, svbsp.numnodes * sizeof(*nodes)); + lightinfo->svbsp = svbsp; + } + Mem_Free(nodes); +} + +static void Mod_GenerateLightmaps_CreateLights(dp_model_t *model) +{ + int index; + int result; + lightmaplight_t *lightinfo; + float origin[3]; + float radius; + float color[3]; + mod_generatelightmaps_numlights = 0; + for (index = 0;;index++) + { + result = R_Shadow_GetRTLightInfo(index, origin, &radius, color); + if (result < 0) + break; + if (result > 0) + mod_generatelightmaps_numlights++; + } + if (mod_generatelightmaps_numlights > 0) + { + mod_generatelightmaps_lightinfo = (lightmaplight_t *)Mem_Alloc(tempmempool, mod_generatelightmaps_numlights * sizeof(*mod_generatelightmaps_lightinfo)); + lightinfo = mod_generatelightmaps_lightinfo; + for (index = 0;;index++) + { + result = R_Shadow_GetRTLightInfo(index, lightinfo->origin, &lightinfo->radius, lightinfo->color); + if (result < 0) + break; + if (result > 0) + lightinfo++; + } + } + for (index = 0, lightinfo = mod_generatelightmaps_lightinfo;index < mod_generatelightmaps_numlights;index++, lightinfo++) + { + lightinfo->iradius = 1.0f / lightinfo->radius; + lightinfo->radius2 = lightinfo->radius * lightinfo->radius; + // TODO: compute svbsp + Mod_GenerateLightmaps_CreateLights_ComputeSVBSP(model, lightinfo); + } +} + +static void Mod_GenerateLightmaps_DestroyLights(dp_model_t *model) +{ + int i; + if (mod_generatelightmaps_lightinfo) + { + for (i = 0;i < mod_generatelightmaps_numlights;i++) + if (mod_generatelightmaps_lightinfo[i].svbsp.nodes) + Mem_Free(mod_generatelightmaps_lightinfo[i].svbsp.nodes); + Mem_Free(mod_generatelightmaps_lightinfo); + } + mod_generatelightmaps_lightinfo = NULL; + mod_generatelightmaps_numlights = 0; +} + +static qboolean Mod_GenerateLightmaps_SamplePoint_SVBSP(const svbsp_t *svbsp, const float *pos) +{ + const svbsp_node_t *node; + const svbsp_node_t *nodes = svbsp->nodes; + int num = 0; + while (num >= 0) + { + node = nodes + num; + num = node->children[DotProduct(node->plane, pos) < node->plane[3]]; + } + return num == -1; // true if empty, false if solid (shadowed) +} + +static void Mod_GenerateLightmaps_SamplePoint(const float *pos, const float *normal, float *sample, int numoffsets, const float *offsets) +{ + int i; + float relativepoint[3]; + float color[3]; + float offsetpos[3]; + float dist; + float dist2; + float intensity; + int offsetindex; + int hits; + int tests; + const lightmaplight_t *lightinfo; + trace_t trace; + for (i = 0;i < 5*3;i++) + sample[i] = 0.0f; + for (i = 0, lightinfo = mod_generatelightmaps_lightinfo;i < mod_generatelightmaps_numlights;i++, lightinfo++) + { + //R_SampleRTLights(pos, sample, numoffsets, offsets); + VectorSubtract(lightinfo->origin, pos, relativepoint); + // don't accept light from behind a surface, it causes bad shading + if (normal && DotProduct(relativepoint, normal) <= 0) + continue; + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightinfo->radius2) + continue; + dist = sqrt(dist2) * lightinfo->iradius; + intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; + if (intensity <= 0) + continue; + if (cl.worldmodel && cl.worldmodel->TraceLine && numoffsets > 0) + { + hits = 0; + tests = 1; + if (Mod_GenerateLightmaps_SamplePoint_SVBSP(&lightinfo->svbsp, pos)) + hits++; + for (offsetindex = 1;offsetindex < numoffsets;offsetindex++) + { + VectorAdd(pos, offsets + 3*offsetindex, offsetpos); + if (!normal) + { + // for light grid we'd better check visibility of the offset point + cl.worldmodel->TraceLine(cl.worldmodel, NULL, NULL, &trace, pos, offsetpos, SUPERCONTENTS_VISBLOCKERMASK); + if (trace.fraction < 1) + VectorLerp(pos, trace.fraction, offsetpos, offsetpos); + } + tests++; + if (Mod_GenerateLightmaps_SamplePoint_SVBSP(&lightinfo->svbsp, offsetpos)) + hits++; + } + if (!hits) + continue; + // scale intensity according to how many rays succeeded + // we know one test is valid, half of the rest will fail... + //if (normal && tests > 1) + // intensity *= (tests - 1.0f) / tests; + intensity *= (float)hits / tests; + } + // scale down intensity to add to both ambient and diffuse + //intensity *= 0.5f; + VectorNormalize(relativepoint); + VectorScale(lightinfo->color, intensity, color); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity *= VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } +} + +static void Mod_GenerateLightmaps_LightmapSample(const float *pos, const float *normal, unsigned char *lm_bgr, unsigned char *lm_dir) +{ + float sample[5*3]; + float color[3]; + float dir[3]; + float f; + Mod_GenerateLightmaps_SamplePoint(pos, normal, sample, mod_generatelightmaps_numoffsets[0], mod_generatelightmaps_offsets[0][0]); + //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); + VectorCopy(sample + 12, dir); + VectorNormalize(dir); + //VectorAdd(dir, normal, dir); + //VectorNormalize(dir); + f = DotProduct(dir, normal); + f = max(0, f) * 255.0f; + VectorScale(sample, f, color); + //VectorCopy(normal, dir); + VectorSet(dir, (dir[0]+1.0f)*127.5f, (dir[1]+1.0f)*127.5f, (dir[2]+1.0f)*127.5f); + lm_bgr[0] = (unsigned char)bound(0.0f, color[2], 255.0f); + lm_bgr[1] = (unsigned char)bound(0.0f, color[1], 255.0f); + lm_bgr[2] = (unsigned char)bound(0.0f, color[0], 255.0f); + lm_bgr[3] = 255; + lm_dir[0] = (unsigned char)dir[2]; + lm_dir[1] = (unsigned char)dir[1]; + lm_dir[2] = (unsigned char)dir[0]; + lm_dir[3] = 255; +} + +static void Mod_GenerateLightmaps_VertexSample(const float *pos, const float *normal, float *vertex_color) +{ + float sample[5*3]; + Mod_GenerateLightmaps_SamplePoint(pos, normal, sample, mod_generatelightmaps_numoffsets[1], mod_generatelightmaps_offsets[1][0]); + VectorCopy(sample, vertex_color); +} + +static void Mod_GenerateLightmaps_GridSample(const float *pos, q3dlightgrid_t *s) +{ + float sample[5*3]; + float ambient[3]; + float diffuse[3]; + float dir[3]; + Mod_GenerateLightmaps_SamplePoint(pos, NULL, sample, mod_generatelightmaps_numoffsets[2], mod_generatelightmaps_offsets[2][0]); + // calculate the direction we'll use to reduce the sample to a directional light source + VectorCopy(sample + 12, dir); + //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); + VectorNormalize(dir); + // extract the diffuse color along the chosen direction and scale it + diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]) * 127.5f; + diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]) * 127.5f; + diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]) * 127.5f; + // scale the ambient from 0-2 to 0-255 and subtract some of diffuse + VectorScale(sample, 127.5f, ambient); + VectorMA(ambient, -0.333f, diffuse, ambient); + // encode to the grid format + s->ambientrgb[0] = (unsigned char)bound(0.0f, ambient[0], 255.0f); + s->ambientrgb[1] = (unsigned char)bound(0.0f, ambient[1], 255.0f); + s->ambientrgb[2] = (unsigned char)bound(0.0f, ambient[2], 255.0f); + s->diffusergb[0] = (unsigned char)bound(0.0f, diffuse[0], 255.0f); + s->diffusergb[1] = (unsigned char)bound(0.0f, diffuse[1], 255.0f); + s->diffusergb[2] = (unsigned char)bound(0.0f, diffuse[2], 255.0f); + if (dir[2] >= 0.99f) {s->diffusepitch = 0;s->diffuseyaw = 0;} + else if (dir[2] <= -0.99f) {s->diffusepitch = 128;s->diffuseyaw = 0;} + else {s->diffusepitch = (unsigned char)(acos(dir[2]) * (127.5f/M_PI));s->diffuseyaw = (unsigned char)(atan2(dir[1], dir[0]) * (127.5f/M_PI));} +} + +static void Mod_GenerateLightmaps_InitSampleOffsets(dp_model_t *model) +{ + float radius[3]; + float temp[3]; + int i, j; + memset(mod_generatelightmaps_offsets, 0, sizeof(mod_generatelightmaps_offsets)); + mod_generatelightmaps_numoffsets[0] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_lightmapsamples.integer); + mod_generatelightmaps_numoffsets[1] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_vertexsamples.integer); + mod_generatelightmaps_numoffsets[2] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_gridsamples.integer); + radius[0] = mod_generatelightmaps_lightmapradius.value; + radius[1] = mod_generatelightmaps_vertexradius.value; + radius[2] = mod_generatelightmaps_gridradius.value; + for (i = 0;i < 3;i++) + { + for (j = 1;j < mod_generatelightmaps_numoffsets[i];j++) + { + VectorRandom(temp); + VectorScale(temp, radius[i], mod_generatelightmaps_offsets[i][j]); + } + } +} + +static void Mod_GenerateLightmaps_DestroyLightmaps(dp_model_t *model) +{ + msurface_t *surface; + int surfaceindex; + int i; + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + surface->lightmaptexture = NULL; + surface->deluxemaptexture = NULL; + } + if (model->brushq3.data_lightmaps) + { + for (i = 0;i < model->brushq3.num_mergedlightmaps;i++) + if (model->brushq3.data_lightmaps[i]) + R_FreeTexture(model->brushq3.data_lightmaps[i]); + Mem_Free(model->brushq3.data_lightmaps); + model->brushq3.data_lightmaps = NULL; + } + if (model->brushq3.data_deluxemaps) + { + for (i = 0;i < model->brushq3.num_mergedlightmaps;i++) + if (model->brushq3.data_deluxemaps[i]) + R_FreeTexture(model->brushq3.data_deluxemaps[i]); + Mem_Free(model->brushq3.data_deluxemaps); + model->brushq3.data_deluxemaps = NULL; + } +} + +static void Mod_GenerateLightmaps_UnweldTriangles(dp_model_t *model) +{ + msurface_t *surface; + int surfaceindex; + int vertexindex; + int outvertexindex; + int i; + const int *e; + surfmesh_t oldsurfmesh; + size_t size; + unsigned char *data; + oldsurfmesh = model->surfmesh; + model->surfmesh.num_triangles = oldsurfmesh.num_triangles; + model->surfmesh.num_vertices = oldsurfmesh.num_triangles * 3; + size = 0; + size += model->surfmesh.num_vertices * sizeof(float[3]); + size += model->surfmesh.num_vertices * sizeof(float[3]); + size += model->surfmesh.num_vertices * sizeof(float[3]); + size += model->surfmesh.num_vertices * sizeof(float[3]); + size += model->surfmesh.num_vertices * sizeof(float[2]); + size += model->surfmesh.num_vertices * sizeof(float[2]); + size += model->surfmesh.num_vertices * sizeof(float[4]); + data = (unsigned char *)Mem_Alloc(model->mempool, size); + model->surfmesh.data_vertex3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); + model->surfmesh.data_normal3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); + model->surfmesh.data_svector3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); + model->surfmesh.data_tvector3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); + model->surfmesh.data_texcoordtexture2f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[2]); + model->surfmesh.data_texcoordlightmap2f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[2]); + model->surfmesh.data_lightmapcolor4f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[4]); + if (model->surfmesh.num_vertices > 65536) + model->surfmesh.data_element3s = NULL; + + if (model->surfmesh.vertexmesh) + Mem_Free(model->surfmesh.vertexmesh); + model->surfmesh.vertexmesh = NULL; + if (model->surfmesh.vertex3fbuffer) + R_Mesh_DestroyMeshBuffer(model->surfmesh.vertex3fbuffer); + model->surfmesh.vertex3fbuffer = NULL; + if (model->surfmesh.vertexmeshbuffer) + R_Mesh_DestroyMeshBuffer(model->surfmesh.vertexmeshbuffer); + model->surfmesh.vertexmeshbuffer = NULL; + if (model->surfmesh.data_element3i_indexbuffer) + R_Mesh_DestroyMeshBuffer(model->surfmesh.data_element3i_indexbuffer); + model->surfmesh.data_element3i_indexbuffer = NULL; + if (model->surfmesh.data_element3s_indexbuffer) + R_Mesh_DestroyMeshBuffer(model->surfmesh.data_element3s_indexbuffer); + model->surfmesh.data_element3s_indexbuffer = NULL; + if (model->surfmesh.vbo_vertexbuffer) + R_Mesh_DestroyMeshBuffer(model->surfmesh.vbo_vertexbuffer); + model->surfmesh.vbo_vertexbuffer = 0; + + // convert all triangles to unique vertex data + outvertexindex = 0; + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + surface->num_firstvertex = outvertexindex; + surface->num_vertices = surface->num_triangles*3; + e = oldsurfmesh.data_element3i + surface->num_firsttriangle*3; + for (i = 0;i < surface->num_triangles*3;i++) + { + vertexindex = e[i]; + model->surfmesh.data_vertex3f[outvertexindex*3+0] = oldsurfmesh.data_vertex3f[vertexindex*3+0]; + model->surfmesh.data_vertex3f[outvertexindex*3+1] = oldsurfmesh.data_vertex3f[vertexindex*3+1]; + model->surfmesh.data_vertex3f[outvertexindex*3+2] = oldsurfmesh.data_vertex3f[vertexindex*3+2]; + model->surfmesh.data_normal3f[outvertexindex*3+0] = oldsurfmesh.data_normal3f[vertexindex*3+0]; + model->surfmesh.data_normal3f[outvertexindex*3+1] = oldsurfmesh.data_normal3f[vertexindex*3+1]; + model->surfmesh.data_normal3f[outvertexindex*3+2] = oldsurfmesh.data_normal3f[vertexindex*3+2]; + model->surfmesh.data_svector3f[outvertexindex*3+0] = oldsurfmesh.data_svector3f[vertexindex*3+0]; + model->surfmesh.data_svector3f[outvertexindex*3+1] = oldsurfmesh.data_svector3f[vertexindex*3+1]; + model->surfmesh.data_svector3f[outvertexindex*3+2] = oldsurfmesh.data_svector3f[vertexindex*3+2]; + model->surfmesh.data_tvector3f[outvertexindex*3+0] = oldsurfmesh.data_tvector3f[vertexindex*3+0]; + model->surfmesh.data_tvector3f[outvertexindex*3+1] = oldsurfmesh.data_tvector3f[vertexindex*3+1]; + model->surfmesh.data_tvector3f[outvertexindex*3+2] = oldsurfmesh.data_tvector3f[vertexindex*3+2]; + model->surfmesh.data_texcoordtexture2f[outvertexindex*2+0] = oldsurfmesh.data_texcoordtexture2f[vertexindex*2+0]; + model->surfmesh.data_texcoordtexture2f[outvertexindex*2+1] = oldsurfmesh.data_texcoordtexture2f[vertexindex*2+1]; + if (oldsurfmesh.data_texcoordlightmap2f) + { + model->surfmesh.data_texcoordlightmap2f[outvertexindex*2+0] = oldsurfmesh.data_texcoordlightmap2f[vertexindex*2+0]; + model->surfmesh.data_texcoordlightmap2f[outvertexindex*2+1] = oldsurfmesh.data_texcoordlightmap2f[vertexindex*2+1]; + } + if (oldsurfmesh.data_lightmapcolor4f) + { + model->surfmesh.data_lightmapcolor4f[outvertexindex*4+0] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+0]; + model->surfmesh.data_lightmapcolor4f[outvertexindex*4+1] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+1]; + model->surfmesh.data_lightmapcolor4f[outvertexindex*4+2] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+2]; + model->surfmesh.data_lightmapcolor4f[outvertexindex*4+3] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+3]; + } + else + Vector4Set(model->surfmesh.data_lightmapcolor4f + 4*outvertexindex, 1, 1, 1, 1); + model->surfmesh.data_element3i[surface->num_firsttriangle*3+i] = outvertexindex; + outvertexindex++; + } + } + if (model->surfmesh.data_element3s) + for (i = 0;i < model->surfmesh.num_triangles*3;i++) + model->surfmesh.data_element3s[i] = model->surfmesh.data_element3i[i]; + + // find and update all submodels to use this new surfmesh data + for (i = 0;i < model->brush.numsubmodels;i++) + model->brush.submodels[i]->surfmesh = model->surfmesh; +} + +static void Mod_GenerateLightmaps_CreateTriangleInformation(dp_model_t *model) +{ + msurface_t *surface; + int surfaceindex; + int i; + int axis; + float normal[3]; + const int *e; + lightmaptriangle_t *triangle; + // generate lightmap triangle structs + mod_generatelightmaps_lightmaptriangles = (lightmaptriangle_t *)Mem_Alloc(model->mempool, model->surfmesh.num_triangles * sizeof(lightmaptriangle_t)); + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; + for (i = 0;i < surface->num_triangles;i++) + { + triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; + triangle->triangleindex = surface->num_firsttriangle+i; + triangle->surfaceindex = surfaceindex; + VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+0], triangle->vertex[0]); + VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+1], triangle->vertex[1]); + VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+2], triangle->vertex[2]); + // calculate bounds of triangle + triangle->mins[0] = min(triangle->vertex[0][0], min(triangle->vertex[1][0], triangle->vertex[2][0])); + triangle->mins[1] = min(triangle->vertex[0][1], min(triangle->vertex[1][1], triangle->vertex[2][1])); + triangle->mins[2] = min(triangle->vertex[0][2], min(triangle->vertex[1][2], triangle->vertex[2][2])); + triangle->maxs[0] = max(triangle->vertex[0][0], max(triangle->vertex[1][0], triangle->vertex[2][0])); + triangle->maxs[1] = max(triangle->vertex[0][1], max(triangle->vertex[1][1], triangle->vertex[2][1])); + triangle->maxs[2] = max(triangle->vertex[0][2], max(triangle->vertex[1][2], triangle->vertex[2][2])); + // pick an axial projection based on the triangle normal + TriangleNormal(triangle->vertex[0], triangle->vertex[1], triangle->vertex[2], normal); + axis = 0; + if (fabs(normal[1]) > fabs(normal[axis])) + axis = 1; + if (fabs(normal[2]) > fabs(normal[axis])) + axis = 2; + triangle->axis = axis; + } + } +} + +static void Mod_GenerateLightmaps_DestroyTriangleInformation(dp_model_t *model) +{ + if (mod_generatelightmaps_lightmaptriangles) + Mem_Free(mod_generatelightmaps_lightmaptriangles); + mod_generatelightmaps_lightmaptriangles = NULL; +} + +float lmaxis[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + +static void Mod_GenerateLightmaps_CreateLightmaps(dp_model_t *model) +{ + msurface_t *surface; + int surfaceindex; + int lightmapindex; + int lightmapnumber; + int i; + int j; + int k; + int x; + int y; + int axis; + int axis1; + int axis2; + int retry; + int pixeloffset; + float trianglenormal[3]; + float samplecenter[3]; + float samplenormal[3]; + float temp[3]; + float lmiscale[2]; + float slopex; + float slopey; + float slopebase; + float lmscalepixels; + float lmmins; + float lmmaxs; + float lm_basescalepixels; + int lm_borderpixels; + int lm_texturesize; + //int lm_maxpixels; + const int *e; + lightmaptriangle_t *triangle; + unsigned char *lightmappixels; + unsigned char *deluxemappixels; + mod_alloclightmap_state_t lmstate; + + // generate lightmap projection information for all triangles + if (model->texturepool == NULL) + model->texturepool = R_AllocTexturePool(); + lm_basescalepixels = 1.0f / max(0.0001f, mod_generatelightmaps_unitspersample.value); + lm_borderpixels = mod_generatelightmaps_borderpixels.integer; + lm_texturesize = bound(lm_borderpixels*2+1, 64, (int)vid.maxtexturesize_2d); + //lm_maxpixels = lm_texturesize-(lm_borderpixels*2+1); + Mod_AllocLightmap_Init(&lmstate, lm_texturesize, lm_texturesize); + lightmapnumber = 0; + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; + lmscalepixels = lm_basescalepixels; + for (retry = 0;retry < 30;retry++) + { + // after a couple failed attempts, degrade quality to make it fit + if (retry > 1) + lmscalepixels *= 0.5f; + for (i = 0;i < surface->num_triangles;i++) + { + triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; + triangle->lightmapindex = lightmapnumber; + // calculate lightmap bounds in 3D pixel coordinates, limit size, + // pick two planar axes for projection + // lightmap coordinates here are in pixels + // lightmap projections are snapped to pixel grid explicitly, such + // that two neighboring triangles sharing an edge and projection + // axis will have identical sampl espacing along their shared edge + k = 0; + for (j = 0;j < 3;j++) + { + if (j == triangle->axis) + continue; + lmmins = floor(triangle->mins[j]*lmscalepixels)-lm_borderpixels; + lmmaxs = floor(triangle->maxs[j]*lmscalepixels)+lm_borderpixels; + triangle->lmsize[k] = (int)(lmmaxs-lmmins); + triangle->lmbase[k] = lmmins/lmscalepixels; + triangle->lmscale[k] = lmscalepixels; + k++; + } + if (!Mod_AllocLightmap_Block(&lmstate, triangle->lmsize[0], triangle->lmsize[1], &triangle->lmoffset[0], &triangle->lmoffset[1])) + break; + } + // if all fit in this texture, we're done with this surface + if (i == surface->num_triangles) + break; + // if we haven't maxed out the lightmap size yet, we retry the + // entire surface batch... + if (lm_texturesize * 2 <= min(mod_generatelightmaps_texturesize.integer, (int)vid.maxtexturesize_2d)) + { + lm_texturesize *= 2; + surfaceindex = -1; + lightmapnumber = 0; + Mod_AllocLightmap_Free(&lmstate); + Mod_AllocLightmap_Init(&lmstate, lm_texturesize, lm_texturesize); + break; + } + // if we have maxed out the lightmap size, and this triangle does + // not fit in the same texture as the rest of the surface, we have + // to retry the entire surface in a new texture (can only use one) + // with multiple retries, the lightmap quality degrades until it + // fits (or gives up) + if (surfaceindex > 0) + lightmapnumber++; + Mod_AllocLightmap_Reset(&lmstate); + } + } + lightmapnumber++; + Mod_AllocLightmap_Free(&lmstate); + + // now put triangles together into lightmap textures, and do not allow + // triangles of a surface to go into different textures (as that would + // require rewriting the surface list) + model->brushq3.deluxemapping_modelspace = true; + model->brushq3.deluxemapping = true; + model->brushq3.num_mergedlightmaps = lightmapnumber; + model->brushq3.data_lightmaps = (rtexture_t **)Mem_Alloc(model->mempool, model->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); + model->brushq3.data_deluxemaps = (rtexture_t **)Mem_Alloc(model->mempool, model->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); + lightmappixels = (unsigned char *)Mem_Alloc(tempmempool, model->brushq3.num_mergedlightmaps * lm_texturesize * lm_texturesize * 4); + deluxemappixels = (unsigned char *)Mem_Alloc(tempmempool, model->brushq3.num_mergedlightmaps * lm_texturesize * lm_texturesize * 4); + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; + for (i = 0;i < surface->num_triangles;i++) + { + triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; + TriangleNormal(triangle->vertex[0], triangle->vertex[1], triangle->vertex[2], trianglenormal); + VectorNormalize(trianglenormal); + VectorCopy(trianglenormal, samplenormal); // FIXME: this is supposed to be interpolated per pixel from vertices + axis = triangle->axis; + axis1 = axis == 0 ? 1 : 0; + axis2 = axis == 2 ? 1 : 2; + lmiscale[0] = 1.0f / triangle->lmscale[0]; + lmiscale[1] = 1.0f / triangle->lmscale[1]; + if (trianglenormal[axis] < 0) + VectorNegate(trianglenormal, trianglenormal); + CrossProduct(lmaxis[axis2], trianglenormal, temp);slopex = temp[axis] / temp[axis1]; + CrossProduct(lmaxis[axis1], trianglenormal, temp);slopey = temp[axis] / temp[axis2]; + slopebase = triangle->vertex[0][axis] - triangle->vertex[0][axis1]*slopex - triangle->vertex[0][axis2]*slopey; + for (j = 0;j < 3;j++) + { + float *t2f = model->surfmesh.data_texcoordlightmap2f + e[i*3+j]*2; + t2f[0] = ((triangle->vertex[j][axis1] - triangle->lmbase[0]) * triangle->lmscale[0] + triangle->lmoffset[0]) / lm_texturesize; + t2f[1] = ((triangle->vertex[j][axis2] - triangle->lmbase[1]) * triangle->lmscale[1] + triangle->lmoffset[1]) / lm_texturesize; +#if 0 + samplecenter[axis1] = (t2f[0]*lm_texturesize-triangle->lmoffset[0])*lmiscale[0] + triangle->lmbase[0]; + samplecenter[axis2] = (t2f[1]*lm_texturesize-triangle->lmoffset[1])*lmiscale[1] + triangle->lmbase[1]; + samplecenter[axis] = samplecenter[axis1]*slopex + samplecenter[axis2]*slopey + slopebase; + Con_Printf("%f:%f %f:%f %f:%f = %f %f\n", triangle->vertex[j][axis1], samplecenter[axis1], triangle->vertex[j][axis2], samplecenter[axis2], triangle->vertex[j][axis], samplecenter[axis], t2f[0], t2f[1]); +#endif + } + +#if 0 + switch (axis) + { + default: + case 0: + forward[0] = 0; + forward[1] = 1.0f / triangle->lmscale[0]; + forward[2] = 0; + left[0] = 0; + left[1] = 0; + left[2] = 1.0f / triangle->lmscale[1]; + up[0] = 1.0f; + up[1] = 0; + up[2] = 0; + origin[0] = 0; + origin[1] = triangle->lmbase[0]; + origin[2] = triangle->lmbase[1]; + break; + case 1: + forward[0] = 1.0f / triangle->lmscale[0]; + forward[1] = 0; + forward[2] = 0; + left[0] = 0; + left[1] = 0; + left[2] = 1.0f / triangle->lmscale[1]; + up[0] = 0; + up[1] = 1.0f; + up[2] = 0; + origin[0] = triangle->lmbase[0]; + origin[1] = 0; + origin[2] = triangle->lmbase[1]; + break; + case 2: + forward[0] = 1.0f / triangle->lmscale[0]; + forward[1] = 0; + forward[2] = 0; + left[0] = 0; + left[1] = 1.0f / triangle->lmscale[1]; + left[2] = 0; + up[0] = 0; + up[1] = 0; + up[2] = 1.0f; + origin[0] = triangle->lmbase[0]; + origin[1] = triangle->lmbase[1]; + origin[2] = 0; + break; + } + Matrix4x4_FromVectors(&backmatrix, forward, left, up, origin); +#endif +#define LM_DIST_EPSILON (1.0f / 32.0f) + for (y = 0;y < triangle->lmsize[1];y++) + { + pixeloffset = ((triangle->lightmapindex * lm_texturesize + y + triangle->lmoffset[1]) * lm_texturesize + triangle->lmoffset[0]) * 4; + for (x = 0;x < triangle->lmsize[0];x++, pixeloffset += 4) + { + samplecenter[axis1] = (x+0.5f)*lmiscale[0] + triangle->lmbase[0]; + samplecenter[axis2] = (y+0.5f)*lmiscale[1] + triangle->lmbase[1]; + samplecenter[axis] = samplecenter[axis1]*slopex + samplecenter[axis2]*slopey + slopebase; + VectorMA(samplecenter, 0.125f, samplenormal, samplecenter); + Mod_GenerateLightmaps_LightmapSample(samplecenter, samplenormal, lightmappixels + pixeloffset, deluxemappixels + pixeloffset); + } + } + } + } + + for (lightmapindex = 0;lightmapindex < model->brushq3.num_mergedlightmaps;lightmapindex++) + { + model->brushq3.data_lightmaps[lightmapindex] = R_LoadTexture2D(model->texturepool, va("lightmap%i", lightmapindex), lm_texturesize, lm_texturesize, lightmappixels + lightmapindex * lm_texturesize * lm_texturesize * 4, TEXTYPE_BGRA, TEXF_FORCELINEAR, -1, NULL); + model->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(model->texturepool, va("deluxemap%i", lightmapindex), lm_texturesize, lm_texturesize, deluxemappixels + lightmapindex * lm_texturesize * lm_texturesize * 4, TEXTYPE_BGRA, TEXF_FORCELINEAR, -1, NULL); + } + + if (lightmappixels) + Mem_Free(lightmappixels); + if (deluxemappixels) + Mem_Free(deluxemappixels); + + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; + if (!surface->num_triangles) + continue; + lightmapindex = mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle].lightmapindex; + surface->lightmaptexture = model->brushq3.data_lightmaps[lightmapindex]; + surface->deluxemaptexture = model->brushq3.data_deluxemaps[lightmapindex]; + surface->lightmapinfo = NULL; + } + + model->brush.LightPoint = Mod_GenerateLightmaps_LightPoint; + model->brushq1.lightdata = NULL; + model->brushq1.lightmapupdateflags = NULL; + model->brushq1.firstrender = false; + model->brushq1.num_lightstyles = 0; + model->brushq1.data_lightstyleinfo = NULL; + for (i = 0;i < model->brush.numsubmodels;i++) + { + model->brush.submodels[i]->brushq1.lightmapupdateflags = NULL; + model->brush.submodels[i]->brushq1.firstrender = false; + model->brush.submodels[i]->brushq1.num_lightstyles = 0; + model->brush.submodels[i]->brushq1.data_lightstyleinfo = NULL; + } +} + +static void Mod_GenerateLightmaps_UpdateVertexColors(dp_model_t *model) +{ + int i; + for (i = 0;i < model->surfmesh.num_vertices;i++) + Mod_GenerateLightmaps_VertexSample(model->surfmesh.data_vertex3f + 3*i, model->surfmesh.data_normal3f + 3*i, model->surfmesh.data_lightmapcolor4f + 4*i); +} + +static void Mod_GenerateLightmaps_UpdateLightGrid(dp_model_t *model) +{ + int x; + int y; + int z; + int index = 0; + float pos[3]; + for (z = 0;z < model->brushq3.num_lightgrid_isize[2];z++) + { + pos[2] = (model->brushq3.num_lightgrid_imins[2] + z + 0.5f) * model->brushq3.num_lightgrid_cellsize[2]; + for (y = 0;y < model->brushq3.num_lightgrid_isize[1];y++) + { + pos[1] = (model->brushq3.num_lightgrid_imins[1] + y + 0.5f) * model->brushq3.num_lightgrid_cellsize[1]; + for (x = 0;x < model->brushq3.num_lightgrid_isize[0];x++, index++) + { + pos[0] = (model->brushq3.num_lightgrid_imins[0] + x + 0.5f) * model->brushq3.num_lightgrid_cellsize[0]; + Mod_GenerateLightmaps_GridSample(pos, model->brushq3.data_lightgrid + index); + } + } + } +} + +extern cvar_t mod_q3bsp_nolightmaps; +static void Mod_GenerateLightmaps(dp_model_t *model) +{ + //lightmaptriangle_t *lightmaptriangles = Mem_Alloc(model->mempool, model->surfmesh.num_triangles * sizeof(lightmaptriangle_t)); + dp_model_t *oldloadmodel = loadmodel; + loadmodel = model; + + Mod_GenerateLightmaps_InitSampleOffsets(model); + Mod_GenerateLightmaps_DestroyLightmaps(model); + Mod_GenerateLightmaps_UnweldTriangles(model); + Mod_GenerateLightmaps_CreateTriangleInformation(model); + Mod_GenerateLightmaps_CreateLights(model); + if(!mod_q3bsp_nolightmaps.integer) + Mod_GenerateLightmaps_CreateLightmaps(model); + Mod_GenerateLightmaps_UpdateVertexColors(model); + Mod_GenerateLightmaps_UpdateLightGrid(model); + Mod_GenerateLightmaps_DestroyLights(model); + Mod_GenerateLightmaps_DestroyTriangleInformation(model); + + loadmodel = oldloadmodel; +} + +static void Mod_GenerateLightmaps_f(void) +{ + if (Cmd_Argc() != 1) + { + Con_Printf("usage: mod_generatelightmaps\n"); + return; + } + if (!cl.worldmodel) + { + Con_Printf("no worldmodel loaded\n"); + return; + } + Mod_GenerateLightmaps(cl.worldmodel); +} diff --git a/misc/source/darkplaces-src/model_shared.h b/misc/source/darkplaces-src/model_shared.h new file mode 100644 index 00000000..c34890ca --- /dev/null +++ b/misc/source/darkplaces-src/model_shared.h @@ -0,0 +1,1206 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef MODEL_SHARED_H +#define MODEL_SHARED_H + +typedef enum synctype_e {ST_SYNC=0, ST_RAND } synctype_t; + +/* + +d*_t structures are on-disk representations +m*_t structures are in-memory + +*/ + +typedef enum modtype_e {mod_invalid, mod_brushq1, mod_sprite, mod_alias, mod_brushq2, mod_brushq3, mod_obj, mod_null} modtype_t; + +typedef struct animscene_s +{ + char name[32]; // for viewthing support + int firstframe; + int framecount; + int loop; // true or false + float framerate; +} +animscene_t; + +typedef struct skinframe_s +{ + rtexture_t *stain; // inverse modulate with background (used for decals and such) + rtexture_t *merged; // original texture without glow + rtexture_t *base; // original texture without pants/shirt/glow + rtexture_t *pants; // pants only (in greyscale) + rtexture_t *shirt; // shirt only (in greyscale) + rtexture_t *nmap; // normalmap (bumpmap for dot3) + rtexture_t *gloss; // glossmap (for dot3) + rtexture_t *glow; // glow only (fullbrights) + rtexture_t *fog; // alpha of the base texture (if not opaque) + rtexture_t *reflect; // colored mask for cubemap reflections + // accounting data for hash searches: + // the compare variables are used to identify internal skins from certain + // model formats + // (so that two q1bsp maps with the same texture name for different + // textures do not have any conflicts) + struct skinframe_s *next; // next on hash chain + char basename[MAX_QPATH]; // name of this + int textureflags; // texture flags to use + int comparewidth; + int compareheight; + int comparecrc; + // mark and sweep garbage collection, this value is updated to a new value + // on each level change for the used skinframes, if some are not used they + // are freed + int loadsequence; + // indicates whether this texture has transparent pixels + qboolean hasalpha; + // average texture color, if applicable + float avgcolor[4]; + // for mdl skins, we actually only upload on first use (many are never used, and they are almost never used in both base+pants+shirt and merged modes) + unsigned char *qpixels; + int qwidth; + int qheight; + qboolean qhascolormapping; + qboolean qgeneratebase; + qboolean qgeneratemerged; + qboolean qgeneratenmap; + qboolean qgenerateglow; +} +skinframe_t; + +struct md3vertex_s; +struct trivertx_s; +typedef struct texvecvertex_s +{ + signed char svec[3]; + signed char tvec[3]; +} +texvecvertex_t; + +typedef struct blendweights_s +{ + unsigned char index[4]; + unsigned char influence[4]; +} +blendweights_t; + +typedef struct r_vertexgeneric_s +{ + // 36 bytes + float vertex3f[3]; + float color4f[4]; + float texcoord2f[2]; +} +r_vertexgeneric_t; + +typedef struct r_vertexmesh_s +{ + // 80 bytes + float vertex3f[3]; + float color4f[4]; + float texcoordtexture2f[2]; + float texcoordlightmap2f[2]; + float svector3f[3]; + float tvector3f[3]; + float normal3f[3]; +} +r_vertexmesh_t; + +typedef struct r_meshbuffer_s +{ + int bufferobject; // OpenGL + void *devicebuffer; // Direct3D + size_t size; + qboolean isindexbuffer; + qboolean isdynamic; + qboolean isindex16; + char name[MAX_QPATH]; +} +r_meshbuffer_t; + +// used for mesh lists in q1bsp/q3bsp map models +// (the surfaces reference portions of these meshes) +typedef struct surfmesh_s +{ + // triangle data in system memory + int num_triangles; // number of triangles in the mesh + int *data_element3i; // int[tris*3] triangles of the mesh, 3 indices into vertex arrays for each + r_meshbuffer_t *data_element3i_indexbuffer; + size_t data_element3i_bufferoffset; + unsigned short *data_element3s; // unsigned short[tris*3] triangles of the mesh in unsigned short format (NULL if num_vertices > 65536) + r_meshbuffer_t *data_element3s_indexbuffer; + size_t data_element3s_bufferoffset; + int *data_neighbor3i; // int[tris*3] neighboring triangle on each edge (-1 if none) + // vertex data in system memory + int num_vertices; // number of vertices in the mesh + float *data_vertex3f; // float[verts*3] vertex locations + float *data_svector3f; // float[verts*3] direction of 'S' (right) texture axis for each vertex + float *data_tvector3f; // float[verts*3] direction of 'T' (down) texture axis for each vertex + float *data_normal3f; // float[verts*3] direction of 'R' (out) texture axis for each vertex + float *data_texcoordtexture2f; // float[verts*2] texcoords for surface texture + float *data_texcoordlightmap2f; // float[verts*2] texcoords for lightmap texture + float *data_lightmapcolor4f; + int *data_lightmapoffsets; // index into surface's lightmap samples for vertex lighting + // vertex buffer object (stores geometry in video memory) + r_meshbuffer_t *vbo_vertexbuffer; + size_t vbooffset_vertex3f; + size_t vbooffset_svector3f; + size_t vbooffset_tvector3f; + size_t vbooffset_normal3f; + size_t vbooffset_texcoordtexture2f; + size_t vbooffset_texcoordlightmap2f; + size_t vbooffset_lightmapcolor4f; + // morph blending, these are zero if model is skeletal or static + int num_morphframes; + struct md3vertex_s *data_morphmd3vertex; + struct trivertx_s *data_morphmdlvertex; + struct texvecvertex_s *data_morphtexvecvertex; + float *data_morphmd2framesize6f; + float num_morphmdlframescale[3]; + float num_morphmdlframetranslate[3]; + // skeletal blending, these are NULL if model is morph or static + struct blendweights_s *data_blendweights; + int num_blends; + unsigned short *blends; + // set if there is some kind of animation on this model + qboolean isanimated; + + // vertex and index buffers for rendering + r_vertexmesh_t *vertexmesh; + r_meshbuffer_t *vertex3fbuffer; + r_meshbuffer_t *vertexmeshbuffer; +} +surfmesh_t; + +#define SHADOWMESHVERTEXHASH 1024 +typedef struct shadowmeshvertexhash_s +{ + struct shadowmeshvertexhash_s *next; +} +shadowmeshvertexhash_t; + +typedef struct shadowmesh_s +{ + // next mesh in chain + struct shadowmesh_s *next; + // used for light mesh (NULL on shadow mesh) + rtexture_t *map_diffuse; + rtexture_t *map_specular; + rtexture_t *map_normal; + // buffer sizes + int numverts, maxverts; + int numtriangles, maxtriangles; + // used always + float *vertex3f; + // used for light mesh (NULL on shadow mesh) + float *svector3f; + float *tvector3f; + float *normal3f; + float *texcoord2f; + // used always + int *element3i; + r_meshbuffer_t *element3i_indexbuffer; + size_t element3i_bufferoffset; + unsigned short *element3s; + r_meshbuffer_t *element3s_indexbuffer; + size_t element3s_bufferoffset; + // used for shadow mapping cubemap side partitioning + int sideoffsets[6], sidetotals[6]; + // used for shadow mesh (NULL on light mesh) + int *neighbor3i; + // these are NULL after Mod_ShadowMesh_Finish is performed, only used + // while building meshes + shadowmeshvertexhash_t **vertexhashtable, *vertexhashentries; + r_meshbuffer_t *vbo_vertexbuffer; + size_t vbooffset_vertex3f; + size_t vbooffset_svector3f; + size_t vbooffset_tvector3f; + size_t vbooffset_normal3f; + size_t vbooffset_texcoord2f; + // vertex/index buffers for rendering + // (created by Mod_ShadowMesh_Finish if possible) + r_vertexmesh_t *vertexmesh; // usually NULL + r_meshbuffer_t *vertex3fbuffer; + r_meshbuffer_t *vertexmeshbuffer; // usually NULL +} +shadowmesh_t; + +// various flags from shaders, used for special effects not otherwise classified +// TODO: support these features more directly +#define Q3TEXTUREFLAG_TWOSIDED 1 +#define Q3TEXTUREFLAG_NOPICMIP 16 +#define Q3TEXTUREFLAG_POLYGONOFFSET 32 +#define Q3TEXTUREFLAG_REFRACTION 256 +#define Q3TEXTUREFLAG_REFLECTION 512 +#define Q3TEXTUREFLAG_WATERSHADER 1024 +#define Q3TEXTUREFLAG_CAMERA 2048 + +#define Q3PATHLENGTH 64 +#define TEXTURE_MAXFRAMES 64 +#define Q3WAVEPARMS 4 +#define Q3DEFORM_MAXPARMS 3 +#define Q3SHADER_MAXLAYERS 2 // FIXME support more than that (currently only two are used, so why keep more in RAM?) +#define Q3RGBGEN_MAXPARMS 3 +#define Q3ALPHAGEN_MAXPARMS 1 +#define Q3TCGEN_MAXPARMS 6 +#define Q3TCMOD_MAXPARMS 6 +#define Q3MAXTCMODS 8 +#define Q3MAXDEFORMS 4 + +typedef enum q3wavefunc_e +{ + Q3WAVEFUNC_NONE, + Q3WAVEFUNC_INVERSESAWTOOTH, + Q3WAVEFUNC_NOISE, + Q3WAVEFUNC_SAWTOOTH, + Q3WAVEFUNC_SIN, + Q3WAVEFUNC_SQUARE, + Q3WAVEFUNC_TRIANGLE, + Q3WAVEFUNC_COUNT +} +q3wavefunc_e; +typedef int q3wavefunc_t; +#define Q3WAVEFUNC_USER_COUNT 4 +#define Q3WAVEFUNC_USER_SHIFT 8 // use 8 bits for wave func type + +typedef enum q3deform_e +{ + Q3DEFORM_NONE, + Q3DEFORM_PROJECTIONSHADOW, + Q3DEFORM_AUTOSPRITE, + Q3DEFORM_AUTOSPRITE2, + Q3DEFORM_TEXT0, + Q3DEFORM_TEXT1, + Q3DEFORM_TEXT2, + Q3DEFORM_TEXT3, + Q3DEFORM_TEXT4, + Q3DEFORM_TEXT5, + Q3DEFORM_TEXT6, + Q3DEFORM_TEXT7, + Q3DEFORM_BULGE, + Q3DEFORM_WAVE, + Q3DEFORM_NORMAL, + Q3DEFORM_MOVE, + Q3DEFORM_COUNT +} +q3deform_t; + +typedef enum q3rgbgen_e +{ + Q3RGBGEN_IDENTITY, + Q3RGBGEN_CONST, + Q3RGBGEN_ENTITY, + Q3RGBGEN_EXACTVERTEX, + Q3RGBGEN_IDENTITYLIGHTING, + Q3RGBGEN_LIGHTINGDIFFUSE, + Q3RGBGEN_ONEMINUSENTITY, + Q3RGBGEN_ONEMINUSVERTEX, + Q3RGBGEN_VERTEX, + Q3RGBGEN_WAVE, + Q3RGBGEN_COUNT +} +q3rgbgen_t; + +typedef enum q3alphagen_e +{ + Q3ALPHAGEN_IDENTITY, + Q3ALPHAGEN_CONST, + Q3ALPHAGEN_ENTITY, + Q3ALPHAGEN_LIGHTINGSPECULAR, + Q3ALPHAGEN_ONEMINUSENTITY, + Q3ALPHAGEN_ONEMINUSVERTEX, + Q3ALPHAGEN_PORTAL, + Q3ALPHAGEN_VERTEX, + Q3ALPHAGEN_WAVE, + Q3ALPHAGEN_COUNT +} +q3alphagen_t; + +typedef enum q3tcgen_e +{ + Q3TCGEN_NONE, + Q3TCGEN_TEXTURE, // very common + Q3TCGEN_ENVIRONMENT, // common + Q3TCGEN_LIGHTMAP, + Q3TCGEN_VECTOR, + Q3TCGEN_COUNT +} +q3tcgen_t; + +typedef enum q3tcmod_e +{ + Q3TCMOD_NONE, + Q3TCMOD_ENTITYTRANSLATE, + Q3TCMOD_ROTATE, + Q3TCMOD_SCALE, + Q3TCMOD_SCROLL, + Q3TCMOD_STRETCH, + Q3TCMOD_TRANSFORM, + Q3TCMOD_TURBULENT, + Q3TCMOD_PAGE, + Q3TCMOD_COUNT +} +q3tcmod_t; + +typedef struct q3shaderinfo_layer_rgbgen_s +{ + q3rgbgen_t rgbgen; + float parms[Q3RGBGEN_MAXPARMS]; + q3wavefunc_t wavefunc; + float waveparms[Q3WAVEPARMS]; +} +q3shaderinfo_layer_rgbgen_t; + +typedef struct q3shaderinfo_layer_alphagen_s +{ + q3alphagen_t alphagen; + float parms[Q3ALPHAGEN_MAXPARMS]; + q3wavefunc_t wavefunc; + float waveparms[Q3WAVEPARMS]; +} +q3shaderinfo_layer_alphagen_t; + +typedef struct q3shaderinfo_layer_tcgen_s +{ + q3tcgen_t tcgen; + float parms[Q3TCGEN_MAXPARMS]; +} +q3shaderinfo_layer_tcgen_t; + +typedef struct q3shaderinfo_layer_tcmod_s +{ + q3tcmod_t tcmod; + float parms[Q3TCMOD_MAXPARMS]; + q3wavefunc_t wavefunc; + float waveparms[Q3WAVEPARMS]; +} +q3shaderinfo_layer_tcmod_t; + +typedef struct q3shaderinfo_layer_s +{ + int alphatest; + int clampmap; + float framerate; + int numframes; + int texflags; + char** texturename; + int blendfunc[2]; + q3shaderinfo_layer_rgbgen_t rgbgen; + q3shaderinfo_layer_alphagen_t alphagen; + q3shaderinfo_layer_tcgen_t tcgen; + q3shaderinfo_layer_tcmod_t tcmods[Q3MAXTCMODS]; +} +q3shaderinfo_layer_t; + +typedef struct q3shaderinfo_deform_s +{ + q3deform_t deform; + float parms[Q3DEFORM_MAXPARMS]; + q3wavefunc_t wavefunc; + float waveparms[Q3WAVEPARMS]; +} +q3shaderinfo_deform_t; + +typedef enum dpoffsetmapping_technique_s +{ + OFFSETMAPPING_OFF, // none + OFFSETMAPPING_DEFAULT, // cvar-set + OFFSETMAPPING_LINEAR, // linear + OFFSETMAPPING_RELIEF // relief +}dpoffsetmapping_technique_t; + + +typedef struct q3shaderinfo_s +{ + char name[Q3PATHLENGTH]; +#define Q3SHADERINFO_COMPARE_START surfaceparms + int surfaceparms; + int textureflags; + int numlayers; + qboolean lighting; + qboolean vertexalpha; + qboolean textureblendalpha; + int primarylayer, backgroundlayer; + q3shaderinfo_layer_t layers[Q3SHADER_MAXLAYERS]; + char skyboxname[Q3PATHLENGTH]; + q3shaderinfo_deform_t deforms[Q3MAXDEFORMS]; + + // dp-specific additions: + + // shadow control + qboolean dpnortlight; + qboolean dpshadow; + qboolean dpnoshadow; + + // add collisions to all triangles of the surface + qboolean dpmeshcollisions; + + // fake reflection + char dpreflectcube[Q3PATHLENGTH]; + + // reflection + float reflectmin; // when refraction is used, minimum amount of reflection (when looking straight down) + float reflectmax; // when refraction is used, maximum amount of reflection (when looking parallel to water) + float refractfactor; // amount of refraction distort (1.0 = like the cvar specifies) + vec4_t refractcolor4f; // color tint of refraction (including alpha factor) + float reflectfactor; // amount of reflection distort (1.0 = like the cvar specifies) + vec4_t reflectcolor4f; // color tint of reflection (including alpha factor) + float r_water_wateralpha; // additional wateralpha to apply when r_water is active + float r_water_waterscroll[2]; // water normalmapscrollblend - scale and speed + + // offsetmapping + dpoffsetmapping_technique_t offsetmapping; + float offsetscale; + + // polygonoffset (only used if Q3TEXTUREFLAG_POLYGONOFFSET) + float biaspolygonoffset, biaspolygonfactor; + + // gloss + float specularscalemod; + float specularpowermod; +#define Q3SHADERINFO_COMPARE_END specularpowermod +} +q3shaderinfo_t; + +typedef enum texturelayertype_e +{ + TEXTURELAYERTYPE_INVALID, + TEXTURELAYERTYPE_LITTEXTURE, + TEXTURELAYERTYPE_TEXTURE, + TEXTURELAYERTYPE_FOG +} +texturelayertype_t; + +typedef struct texturelayer_s +{ + texturelayertype_t type; + qboolean depthmask; + int blendfunc1; + int blendfunc2; + rtexture_t *texture; + matrix4x4_t texmatrix; + vec4_t color; +} +texturelayer_t; + +typedef struct texture_s +{ + // q1bsp + // name + //char name[16]; + // size + unsigned int width, height; + // SURF_ flags + //unsigned int flags; + + // base material flags + int basematerialflags; + // current material flags (updated each bmodel render) + int currentmaterialflags; + + // PolygonOffset values for rendering this material + // (these are added to the r_refdef values and submodel values) + float biaspolygonfactor; + float biaspolygonoffset; + + // textures to use when rendering this material + skinframe_t *currentskinframe; + int numskinframes; + float skinframerate; + skinframe_t *skinframes[TEXTURE_MAXFRAMES]; + // background layer (for terrain texture blending) + skinframe_t *backgroundcurrentskinframe; + int backgroundnumskinframes; + float backgroundskinframerate; + skinframe_t *backgroundskinframes[TEXTURE_MAXFRAMES]; + + // total frames in sequence and alternate sequence + int anim_total[2]; + // direct pointers to each of the frames in the sequences + // (indexed as [alternate][frame]) + struct texture_s *anim_frames[2][10]; + // set if animated or there is an alternate frame set + // (this is an optimization in the renderer) + int animated; + + // renderer checks if this texture needs updating... + int update_lastrenderframe; + void *update_lastrenderentity; + // the current alpha of this texture (may be affected by r_wateralpha) + float currentalpha; + // the current texture frame in animation + struct texture_s *currentframe; + // current texture transform matrix (used for water scrolling) + matrix4x4_t currenttexmatrix; + matrix4x4_t currentbackgroundtexmatrix; + + // various q3 shader features + q3shaderinfo_layer_rgbgen_t rgbgen; + q3shaderinfo_layer_alphagen_t alphagen; + q3shaderinfo_layer_tcgen_t tcgen; + q3shaderinfo_layer_tcmod_t tcmods[Q3MAXTCMODS]; + q3shaderinfo_layer_tcmod_t backgroundtcmods[Q3MAXTCMODS]; + q3shaderinfo_deform_t deforms[Q3MAXDEFORMS]; + + qboolean colormapping; + rtexture_t *basetexture; // original texture without pants/shirt/glow + rtexture_t *pantstexture; // pants only (in greyscale) + rtexture_t *shirttexture; // shirt only (in greyscale) + rtexture_t *nmaptexture; // normalmap (bumpmap for dot3) + rtexture_t *glosstexture; // glossmap (for dot3) + rtexture_t *glowtexture; // glow only (fullbrights) + rtexture_t *fogtexture; // alpha of the base texture (if not opaque) + rtexture_t *reflectmasktexture; // mask for fake reflections + rtexture_t *reflectcubetexture; // fake reflections cubemap + rtexture_t *backgroundbasetexture; // original texture without pants/shirt/glow + rtexture_t *backgroundnmaptexture; // normalmap (bumpmap for dot3) + rtexture_t *backgroundglosstexture; // glossmap (for dot3) + rtexture_t *backgroundglowtexture; // glow only (fullbrights) + float specularscale; + float specularpower; + // color tint (colormod * currentalpha) used for rtlighting this material + float dlightcolor[3]; + // color tint (colormod * 2) used for lightmapped lighting on this material + // includes alpha as 4th component + // replaces role of gl_Color in GLSL shader + float lightmapcolor[4]; + + // from q3 shaders + int customblendfunc[2]; + + int currentnumlayers; + texturelayer_t currentlayers[16]; + + // q3bsp + char name[64]; + int surfaceflags; + int supercontents; + int surfaceparms; + int textureflags; + + // reflection + float reflectmin; // when refraction is used, minimum amount of reflection (when looking straight down) + float reflectmax; // when refraction is used, maximum amount of reflection (when looking parallel to water) + float refractfactor; // amount of refraction distort (1.0 = like the cvar specifies) + vec4_t refractcolor4f; // color tint of refraction (including alpha factor) + float reflectfactor; // amount of reflection distort (1.0 = like the cvar specifies) + vec4_t reflectcolor4f; // color tint of reflection (including alpha factor) + float r_water_wateralpha; // additional wateralpha to apply when r_water is active + float r_water_waterscroll[2]; // scale and speed + int camera_entity; // entity number for use by cameras + + // offsetmapping + dpoffsetmapping_technique_t offsetmapping; + float offsetscale; + + // gloss + float specularscalemod; + float specularpowermod; +} + texture_t; + +typedef struct mtexinfo_s +{ + float vecs[2][4]; + texture_t *texture; + int flags; +} +mtexinfo_t; + +typedef struct msurface_lightmapinfo_s +{ + // texture mapping properties used by this surface + mtexinfo_t *texinfo; // q1bsp + // index into r_refdef.scene.lightstylevalue array, 255 means not used (black) + unsigned char styles[MAXLIGHTMAPS]; // q1bsp + // RGB lighting data [numstyles][height][width][3] + unsigned char *samples; // q1bsp + // RGB normalmap data [numstyles][height][width][3] + unsigned char *nmapsamples; // q1bsp + // stain to apply on lightmap (soot/dirt/blood/whatever) + unsigned char *stainsamples; // q1bsp + int texturemins[2]; // q1bsp + int extents[2]; // q1bsp + int lightmaporigin[2]; // q1bsp +} +msurface_lightmapinfo_t; + +struct q3deffect_s; +typedef struct msurface_s +{ + // bounding box for onscreen checks + vec3_t mins; + vec3_t maxs; + // the texture to use on the surface + texture_t *texture; + // the lightmap texture fragment to use on the rendering mesh + rtexture_t *lightmaptexture; + // the lighting direction texture fragment to use on the rendering mesh + rtexture_t *deluxemaptexture; + // lightmaptexture rebuild information not used in q3bsp + msurface_lightmapinfo_t *lightmapinfo; // q1bsp + // fog volume info in q3bsp + struct q3deffect_s *effect; // q3bsp + // mesh information for collisions (only used by q3bsp curves) + int num_firstcollisiontriangle; + int *deprecatedq3data_collisionelement3i; // q3bsp + float *deprecatedq3data_collisionvertex3f; // q3bsp + float *deprecatedq3data_collisionbbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles + float *deprecatedq3data_bbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles + + // surfaces own ranges of vertices and triangles in the model->surfmesh + int num_triangles; // number of triangles + int num_firsttriangle; // first triangle + int num_vertices; // number of vertices + int num_firstvertex; // first vertex + + // shadow volume building information + int num_firstshadowmeshtriangle; // index into model->brush.shadowmesh + + // mesh information for collisions (only used by q3bsp curves) + int num_collisiontriangles; // q3bsp + int num_collisionvertices; // q3bsp + int deprecatedq3num_collisionbboxstride; + int deprecatedq3num_bboxstride; + // FIXME: collisionmarkframe should be kept in a separate array + int deprecatedq3collisionmarkframe; // q3bsp // don't collide twice in one trace +} +msurface_t; + +#include "matrixlib.h" +#include "bih.h" + +#include "model_brush.h" +#include "model_sprite.h" +#include "model_alias.h" + +typedef struct model_sprite_s +{ + int sprnum_type; + mspriteframe_t *sprdata_frames; +} +model_sprite_t; + +struct trace_s; + +typedef struct model_brush_lightstyleinfo_s +{ + int style; + int value; + int numsurfaces; + int *surfacelist; +} +model_brush_lightstyleinfo_t; + +typedef struct model_brush_s +{ + // true if this model is a HalfLife .bsp file + qboolean ishlbsp; + // string of entity definitions (.map format) + char *entities; + + // if not NULL this is a submodel + struct model_s *parentmodel; + // (this is the number of the submodel, an index into submodels) + int submodel; + + // number of submodels in this map (just used by server to know how many + // submodels to load) + int numsubmodels; + // pointers to each of the submodels + struct model_s **submodels; + + int num_planes; + mplane_t *data_planes; + + int num_nodes; + mnode_t *data_nodes; + + // visible leafs, not counting 0 (solid) + int num_visleafs; + // number of actual leafs (including 0 which is solid) + int num_leafs; + mleaf_t *data_leafs; + + int num_leafbrushes; + int *data_leafbrushes; + + int num_leafsurfaces; + int *data_leafsurfaces; + + int num_portals; + mportal_t *data_portals; + + int num_portalpoints; + mvertex_t *data_portalpoints; + + int num_brushes; + q3mbrush_t *data_brushes; + + int num_brushsides; + q3mbrushside_t *data_brushsides; + + // pvs + int num_pvsclusters; + int num_pvsclusterbytes; + unsigned char *data_pvsclusters; + // example + //pvschain = model->brush.data_pvsclusters + mycluster * model->brush.num_pvsclusterbytes; + //if (pvschain[thatcluster >> 3] & (1 << (thatcluster & 7))) + + // collision geometry for q3 curves + int num_collisionvertices; + int num_collisiontriangles; + float *data_collisionvertex3f; + int *data_collisionelement3i; + + // a mesh containing all shadow casting geometry for the whole model (including submodels), portions of this are referenced by each surface's num_firstshadowmeshtriangle + shadowmesh_t *shadowmesh; + + // a mesh containing all SUPERCONTENTS_SOLID surfaces for this model or submodel, for physics engines to use + shadowmesh_t *collisionmesh; + + // common functions + int (*SuperContentsFromNativeContents)(struct model_s *model, int nativecontents); + int (*NativeContentsFromSuperContents)(struct model_s *model, int supercontents); + unsigned char *(*GetPVS)(struct model_s *model, const vec3_t p); + int (*FatPVS)(struct model_s *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbufferlength, qboolean merge); + int (*BoxTouchingPVS)(struct model_s *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs); + int (*BoxTouchingLeafPVS)(struct model_s *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs); + int (*BoxTouchingVisibleLeafs)(struct model_s *model, const unsigned char *visibleleafs, const vec3_t mins, const vec3_t maxs); + int (*FindBoxClusters)(struct model_s *model, const vec3_t mins, const vec3_t maxs, int maxclusters, int *clusterlist); + void (*LightPoint)(struct model_s *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal); + void (*FindNonSolidLocation)(struct model_s *model, const vec3_t in, vec3_t out, vec_t radius); + mleaf_t *(*PointInLeaf)(struct model_s *model, const float *p); + // these are actually only found on brushq1, but NULL is handled gracefully + void (*AmbientSoundLevelsForPoint)(struct model_s *model, const vec3_t p, unsigned char *out, int outsize); + void (*RoundUpToHullSize)(struct model_s *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs); + // trace a line of sight through this model (returns false if the line if sight is definitely blocked) + qboolean (*TraceLineOfSight)(struct model_s *model, const vec3_t start, const vec3_t end); + + char skybox[MAX_QPATH]; + + skinframe_t *solidskyskinframe; + skinframe_t *alphaskyskinframe; + + qboolean supportwateralpha; + + // QuakeWorld + int qw_md4sum; + int qw_md4sum2; +} +model_brush_t; + +typedef struct model_brushq1_s +{ + dmodel_t *submodels; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + mclipnode_t *clipnodes; + + hull_t hulls[MAX_MAP_HULLS]; + + int num_compressedpvs; + unsigned char *data_compressedpvs; + + int num_lightdata; + unsigned char *lightdata; + unsigned char *nmaplightdata; // deluxemap file + + // lightmap update chains for light styles + int num_lightstyles; + model_brush_lightstyleinfo_t *data_lightstyleinfo; + + // this contains bytes that are 1 if a surface needs its lightmap rebuilt + unsigned char *lightmapupdateflags; + qboolean firstrender; // causes all surface lightmaps to be loaded in first frame +} +model_brushq1_t; + +/* MSVC can't compile empty structs, so this is commented out for now +typedef struct model_brushq2_s +{ +} +model_brushq2_t; +*/ + +typedef struct model_brushq3_s +{ + int num_models; + q3dmodel_t *data_models; + + // used only during loading - freed after loading! + int num_vertices; + float *data_vertex3f; + float *data_normal3f; + float *data_texcoordtexture2f; + float *data_texcoordlightmap2f; + float *data_color4f; + + // freed after loading! + int num_triangles; + int *data_element3i; + + int num_effects; + q3deffect_t *data_effects; + + // lightmap textures + int num_originallightmaps; + int num_mergedlightmaps; + int num_lightmapmergedwidthpower; + int num_lightmapmergedheightpower; + int num_lightmapmergedwidthheightdeluxepower; + int num_lightmapmerge; + rtexture_t **data_lightmaps; + rtexture_t **data_deluxemaps; + + // voxel light data with directional shading + int num_lightgrid; + q3dlightgrid_t *data_lightgrid; + // size of each cell (may vary by map, typically 64 64 128) + float num_lightgrid_cellsize[3]; + // 1.0 / num_lightgrid_cellsize + float num_lightgrid_scale[3]; + // dimensions of the world model in lightgrid cells + int num_lightgrid_imins[3]; + int num_lightgrid_imaxs[3]; + int num_lightgrid_isize[3]; + // transform modelspace coordinates to lightgrid index + matrix4x4_t num_lightgrid_indexfromworld; + + // true if this q3bsp file has been detected as using deluxemapping + // (lightmap texture pairs, every odd one is never directly refernced, + // and contains lighting normals, not colors) + qboolean deluxemapping; + // true if the detected deluxemaps are the modelspace kind, rather than + // the faster tangentspace kind + qboolean deluxemapping_modelspace; + // size of lightmaps (128 by default, but may be another poweroftwo if + // external lightmaps are used (q3map2 -lightmapsize) + int lightmapsize; +} +model_brushq3_t; + +struct frameblend_s; +struct skeleton_s; + +typedef struct model_s +{ + // name and path of model, for example "progs/player.mdl" + char name[MAX_QPATH]; + // model needs to be loaded if this is false + qboolean loaded; + // set if the model is used in current map, models which are not, are purged + qboolean used; + // CRC of the file this model was loaded from, to reload if changed + unsigned int crc; + // mod_brush, mod_alias, mod_sprite + modtype_t type; + // memory pool for allocations + mempool_t *mempool; + // all models use textures... + rtexturepool_t *texturepool; + // EF_* flags (translated from the model file's different flags layout) + int effects; + // number of QC accessible frame(group)s in the model + int numframes; + // number of QC accessible skin(group)s in the model + int numskins; + // whether to randomize animated framegroups + synctype_t synctype; + // bounding box at angles '0 0 0' + vec3_t normalmins, normalmaxs; + // bounding box if yaw angle is not 0, but pitch and roll are + vec3_t yawmins, yawmaxs; + // bounding box if pitch or roll are used + vec3_t rotatedmins, rotatedmaxs; + // sphere radius, usable at any angles + float radius; + // squared sphere radius for easier comparisons + float radius2; + // skin animation info + animscene_t *skinscenes; // [numskins] + // skin animation info + animscene_t *animscenes; // [numframes] + // range of surface numbers in this (sub)model + int firstmodelsurface; + int nummodelsurfaces; + int *sortedmodelsurfaces; + // range of collision brush numbers in this (sub)model + int firstmodelbrush; + int nummodelbrushes; + // BIH (Bounding Interval Hierarchy) for this (sub)model + bih_t collision_bih; + bih_t render_bih; // if not set, use collision_bih instead for rendering purposes too + // for md3 models + int num_tags; + int num_tagframes; + aliastag_t *data_tags; + // for skeletal models + int num_bones; + aliasbone_t *data_bones; + float num_posescale; // scaling factor from origin in poses6s format (includes divide by 32767) + float num_poseinvscale; // scaling factor to origin in poses6s format (includes multiply by 32767) + int num_poses; + short *data_poses6s; // origin xyz, quat xyz, w implied negative, unit length, values normalized to +/-32767 range + float *data_baseboneposeinverse; + // textures of this model + int num_textures; + int num_texturesperskin; + texture_t *data_textures; + qboolean wantnormals; + qboolean wanttangents; + // surfaces of this model + int num_surfaces; + msurface_t *data_surfaces; + // optional lightmapinfo data for surface lightmap updates + msurface_lightmapinfo_t *data_surfaces_lightmapinfo; + // all surfaces belong to this mesh + surfmesh_t surfmesh; + // data type of model + const char *modeldatatypestring; + // generates vertex data for a given frameblend + void(*AnimateVertices)(const struct model_s * RESTRICT model, const struct frameblend_s * RESTRICT frameblend, const struct skeleton_s *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); + // draw the model's sky polygons (only used by brush models) + void(*DrawSky)(struct entity_render_s *ent); + // draw refraction/reflection textures for the model's water polygons (only used by brush models) + void(*DrawAddWaterPlanes)(struct entity_render_s *ent); + // draw the model using lightmap/dlight shading + void(*Draw)(struct entity_render_s *ent); + // draw the model to the depth buffer (no color rendering at all) + void(*DrawDepth)(struct entity_render_s *ent); + // draw any enabled debugging effects on this model (such as showing triangles, normals, collision brushes...) + void(*DrawDebug)(struct entity_render_s *ent); + // draw geometry textures for deferred rendering + void(*DrawPrepass)(struct entity_render_s *ent); + // compile an optimized shadowmap mesh for the model based on light source + void(*CompileShadowMap)(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); + // draw depth into a shadowmap + void(*DrawShadowMap)(int side, struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs); + // gathers info on which clusters and surfaces are lit by light, as well as calculating a bounding box + void(*GetLightInfo)(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes); + // compile a shadow volume for the model based on light source + void(*CompileShadowVolume)(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); + // draw a shadow volume for the model based on light source + void(*DrawShadowVolume)(struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const vec3_t lightmins, const vec3_t lightmaxs); + // draw the lighting on a model (through stencil) + void(*DrawLight)(struct entity_render_s *ent, int numsurfaces, const int *surfacelist, const unsigned char *trispvs); + // trace a box against this model + void (*TraceBox)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask); + void (*TraceBrush)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, struct colbrushf_s *start, struct colbrushf_s *end, int hitsupercontentsmask); + // trace a box against this model + void (*TraceLine)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); + // trace a point against this model (like PointSuperContents) + void (*TracePoint)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask); + // find the supercontents value at a point in this model + int (*PointSuperContents)(struct model_s *model, int frame, const vec3_t point); + // trace a line against geometry in this model and report correct texture (used by r_shadow_bouncegrid) + void (*TraceLineAgainstSurfaces)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); + // fields belonging to some types of model + model_sprite_t sprite; + model_brush_t brush; + model_brushq1_t brushq1; + /* MSVC can't handle an empty struct, so this is commented out for now + model_brushq2_t brushq2; + */ + model_brushq3_t brushq3; + // flags this model for offseting sounds to the model center (used by brush models) + int soundfromcenter; + + // if set, the model contains light information (lightmap, or vertexlight) + qboolean lit; +} +dp_model_t; + +//============================================================================ + +// model loading +extern dp_model_t *loadmodel; +extern unsigned char *mod_base; +// sky/water subdivision +//extern cvar_t gl_subdivide_size; +// texture fullbrights +extern cvar_t r_fullbrights; +extern cvar_t r_enableshadowvolumes; + +void Mod_Init (void); +void Mod_Reload (void); +dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk); +dp_model_t *Mod_FindName (const char *name, const char *parentname); +dp_model_t *Mod_ForName (const char *name, qboolean crash, qboolean checkdisk, const char *parentname); +void Mod_UnloadModel (dp_model_t *mod); + +void Mod_ClearUsed(void); +void Mod_PurgeUnused(void); +void Mod_RemoveStaleWorldModels(dp_model_t *skip); // only used during loading! + +extern dp_model_t *loadmodel; +extern char loadname[32]; // for hunk tags + +int Mod_BuildVertexRemapTableFromElements(int numelements, const int *elements, int numvertices, int *remapvertices); +void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles); +void Mod_ValidateElements(int *elements, int numtriangles, int firstvertex, int numverts, const char *filename, int fileline); +void Mod_BuildNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const int *elements, float *normal3f, qboolean areaweighting); +void Mod_BuildTextureVectorsFromNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const float *texcoord2f, const float *normal3f, const int *elements, float *svector3f, float *tvector3f, qboolean areaweighting); + +void Mod_AllocSurfMesh(mempool_t *mempool, int numvertices, int numtriangles, qboolean lightmapoffsets, qboolean vertexcolors, qboolean neighbors); +void Mod_MakeSortedSurfaces(dp_model_t *mod); + +// called specially by brush model loaders before generating submodels +// automatically called after model loader returns +void Mod_BuildVBOs(void); + +shadowmesh_t *Mod_ShadowMesh_Alloc(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable); +shadowmesh_t *Mod_ShadowMesh_ReAlloc(mempool_t *mempool, shadowmesh_t *oldmesh, int light, int neighbors); +int Mod_ShadowMesh_AddVertex(shadowmesh_t *mesh, float *vertex14f); +void Mod_ShadowMesh_AddTriangle(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, float *vertex14f); +void Mod_ShadowMesh_AddMesh(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *texcoord2f, int numtris, const int *element3i); +shadowmesh_t *Mod_ShadowMesh_Begin(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable); +shadowmesh_t *Mod_ShadowMesh_Finish(mempool_t *mempool, shadowmesh_t *firstmesh, qboolean light, qboolean neighbors, qboolean createvbo); +void Mod_ShadowMesh_CalcBBox(shadowmesh_t *firstmesh, vec3_t mins, vec3_t maxs, vec3_t center, float *radius); +void Mod_ShadowMesh_Free(shadowmesh_t *mesh); + +void Mod_CreateCollisionMesh(dp_model_t *mod); + +void Mod_FreeQ3Shaders(void); +void Mod_LoadQ3Shaders(void); +q3shaderinfo_t *Mod_LookupQ3Shader(const char *name); +qboolean Mod_LoadTextureFromQ3Shader(texture_t *texture, const char *name, qboolean warnmissing, qboolean fallback, int defaulttexflags); + +extern cvar_t r_mipskins; +extern cvar_t r_mipnormalmaps; + +typedef struct skinfileitem_s +{ + struct skinfileitem_s *next; + char name[MAX_QPATH]; + char replacement[MAX_QPATH]; +} +skinfileitem_t; + +typedef struct skinfile_s +{ + struct skinfile_s *next; + skinfileitem_t *items; +} +skinfile_t; + +skinfile_t *Mod_LoadSkinFiles(void); +void Mod_FreeSkinFiles(skinfile_t *skinfile); +int Mod_CountSkinFiles(skinfile_t *skinfile); +void Mod_BuildAliasSkinsFromSkinFiles(texture_t *skin, skinfile_t *skinfile, const char *meshname, const char *shadername); + +void Mod_SnapVertices(int numcomponents, int numvertices, float *vertices, float snap); +int Mod_RemoveDegenerateTriangles(int numtriangles, const int *inelement3i, int *outelement3i, const float *vertex3f); +void Mod_VertexRangeFromElements(int numelements, const int *elements, int *firstvertexpointer, int *lastvertexpointer); + +typedef struct mod_alloclightmap_row_s +{ + int rowY; + int currentX; +} +mod_alloclightmap_row_t; + +typedef struct mod_alloclightmap_state_s +{ + int width; + int height; + int currentY; + mod_alloclightmap_row_t *rows; +} +mod_alloclightmap_state_t; + +void Mod_AllocLightmap_Init(mod_alloclightmap_state_t *state, int width, int height); +void Mod_AllocLightmap_Free(mod_alloclightmap_state_t *state); +void Mod_AllocLightmap_Reset(mod_alloclightmap_state_t *state); +qboolean Mod_AllocLightmap_Block(mod_alloclightmap_state_t *state, int blockwidth, int blockheight, int *outx, int *outy); + +// bsp models +void Mod_BrushInit(void); +// used for talking to the QuakeC mainly +int Mod_Q1BSP_NativeContentsFromSuperContents(struct model_s *model, int supercontents); +int Mod_Q1BSP_SuperContentsFromNativeContents(struct model_s *model, int nativecontents); + +// a lot of model formats use the Q1BSP code, so here are the prototypes... +struct entity_render_s; +void R_Q1BSP_DrawAddWaterPlanes(struct entity_render_s *ent); +void R_Q1BSP_DrawSky(struct entity_render_s *ent); +void R_Q1BSP_Draw(struct entity_render_s *ent); +void R_Q1BSP_DrawDepth(struct entity_render_s *ent); +void R_Q1BSP_DrawDebug(struct entity_render_s *ent); +void R_Q1BSP_DrawPrepass(struct entity_render_s *ent); +void R_Q1BSP_GetLightInfo(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes); +void R_Q1BSP_CompileShadowMap(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); +void R_Q1BSP_DrawShadowMap(int side, struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs); +void R_Q1BSP_CompileShadowVolume(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); +void R_Q1BSP_DrawShadowVolume(struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const vec3_t lightmins, const vec3_t lightmaxs); +void R_Q1BSP_DrawLight(struct entity_render_s *ent, int numsurfaces, const int *surfacelist, const unsigned char *trispvs); + +// Collision optimization using Bounding Interval Hierarchy +void Mod_CollisionBIH_TracePoint(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask); +void Mod_CollisionBIH_TraceLine(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); +void Mod_CollisionBIH_TraceBox(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask); +void Mod_CollisionBIH_TraceBrush(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, struct colbrushf_s *start, struct colbrushf_s *end, int hitsupercontentsmask); +void Mod_CollisionBIH_TracePoint_Mesh(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask); +int Mod_CollisionBIH_PointSuperContents_Mesh(struct model_s *model, int frame, const vec3_t point); +bih_t *Mod_MakeCollisionBIH(dp_model_t *model, qboolean userendersurfaces, bih_t *out); + +// alias models +struct frameblend_s; +struct skeleton_s; +void Mod_AliasInit(void); +int Mod_Alias_GetTagMatrix(const dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, int tagindex, matrix4x4_t *outmatrix); +int Mod_Alias_GetTagIndexForName(const dp_model_t *model, unsigned int skin, const char *tagname); +int Mod_Alias_GetExtendedTagInfoForIndex(const dp_model_t *model, unsigned int skin, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix); + +void Mod_Skeletal_FreeBuffers(void); + +// sprite models +void Mod_SpriteInit(void); + +// loaders +void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IBSP_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_MAP_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_OBJ_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDP0_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDP2_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDP3_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_ZYMOTICMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_DARKPLACESMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_PSKMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDSP_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDS2_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_INTERQUAKEMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); + +#endif // MODEL_SHARED_H + diff --git a/misc/source/darkplaces-src/model_sprite.c b/misc/source/darkplaces-src/model_sprite.c new file mode 100644 index 00000000..7b683988 --- /dev/null +++ b/misc/source/darkplaces-src/model_sprite.c @@ -0,0 +1,479 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// models.c -- model loading and caching + +// models are the only shared resource between a client and server running +// on the same machine. + +#include "quakedef.h" +#include "image.h" + +cvar_t r_mipsprites = {CVAR_SAVE, "r_mipsprites", "1", "mipmaps sprites so they render faster in the distance and do not display noise artifacts"}; +cvar_t r_labelsprites_scale = {CVAR_SAVE, "r_labelsprites_scale", "1", "global scale to apply to label sprites before conversion to HUD coordinates"}; +cvar_t r_labelsprites_roundtopixels = {CVAR_SAVE, "r_labelsprites_roundtopixels", "1", "try to make label sprites sharper by rounding their size to 0.5x or 1x and by rounding their position to whole pixels if possible"}; +cvar_t r_overheadsprites_perspective = {CVAR_SAVE, "r_overheadsprites_perspective", "5", "fake perspective effect for SPR_OVERHEAD sprites"}; +cvar_t r_overheadsprites_pushback = {CVAR_SAVE, "r_overheadsprites_pushback", "15", "how far to pull the SPR_OVERHEAD sprites toward the eye (used to avoid intersections with 3D models)"}; +cvar_t r_overheadsprites_scalex = {CVAR_SAVE, "r_overheadsprites_scalex", "1", "additional scale for overhead sprites for x axis"}; +cvar_t r_overheadsprites_scaley = {CVAR_SAVE, "r_overheadsprites_scaley", "1", "additional scale for overhead sprites for y axis"}; +cvar_t r_track_sprites = {CVAR_SAVE, "r_track_sprites", "1", "track SPR_LABEL* sprites by putting them as indicator at the screen border to rotate to"}; +cvar_t r_track_sprites_flags = {CVAR_SAVE, "r_track_sprites_flags", "1", "1: Rotate sprites accordingly, 2: Make it a continuous rotation"}; +cvar_t r_track_sprites_scalew = {CVAR_SAVE, "r_track_sprites_scalew", "1", "width scaling of tracked sprites"}; +cvar_t r_track_sprites_scaleh = {CVAR_SAVE, "r_track_sprites_scaleh", "1", "height scaling of tracked sprites"}; + +/* +=============== +Mod_SpriteInit +=============== +*/ +void Mod_SpriteInit (void) +{ + Cvar_RegisterVariable(&r_mipsprites); + Cvar_RegisterVariable(&r_labelsprites_scale); + Cvar_RegisterVariable(&r_labelsprites_roundtopixels); + Cvar_RegisterVariable(&r_overheadsprites_perspective); + Cvar_RegisterVariable(&r_overheadsprites_pushback); + Cvar_RegisterVariable(&r_overheadsprites_scalex); + Cvar_RegisterVariable(&r_overheadsprites_scaley); + Cvar_RegisterVariable(&r_track_sprites); + Cvar_RegisterVariable(&r_track_sprites_flags); + Cvar_RegisterVariable(&r_track_sprites_scalew); + Cvar_RegisterVariable(&r_track_sprites_scaleh); +} + +static void Mod_SpriteSetupTexture(texture_t *texture, skinframe_t *skinframe, qboolean fullbright, qboolean additive) +{ + if (!skinframe) + skinframe = R_SkinFrame_LoadMissing(); + texture->offsetmapping = OFFSETMAPPING_OFF; + texture->offsetscale = 1; + texture->specularscalemod = 1; + texture->specularpowermod = 1; + texture->basematerialflags = MATERIALFLAG_WALL; + if (fullbright) + texture->basematerialflags |= MATERIALFLAG_FULLBRIGHT; + if (additive) + texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else if (skinframe->hasalpha) + texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + texture->currentmaterialflags = texture->basematerialflags; + texture->numskinframes = 1; + texture->currentskinframe = texture->skinframes[0] = skinframe; + texture->surfaceflags = 0; + texture->supercontents = SUPERCONTENTS_SOLID; + if (!(texture->basematerialflags & MATERIALFLAG_BLENDED)) + texture->supercontents |= SUPERCONTENTS_OPAQUE; +} + +extern cvar_t gl_texturecompression_sprites; + +static void Mod_Sprite_SharedSetup(const unsigned char *datapointer, int version, const unsigned int *palette, qboolean additive) +{ + int i, j, groupframes, realframes, x, y, origin[2], width, height; + qboolean fullbright; + dspriteframetype_t *pinframetype; + dspriteframe_t *pinframe; + dspritegroup_t *pingroup; + dspriteinterval_t *pinintervals; + skinframe_t *skinframe; + float modelradius, interval; + char name[MAX_QPATH], fogname[MAX_QPATH]; + const void *startframes; + int texflags = (r_mipsprites.integer ? TEXF_MIPMAP : 0) | (gl_texturecompression_sprites.integer ? TEXF_COMPRESS : 0) | TEXF_ISSPRITE | TEXF_PICMIP | TEXF_ALPHA | TEXF_CLAMP; + modelradius = 0; + + if (loadmodel->numframes < 1) + Host_Error ("Mod_Sprite_SharedSetup: Invalid # of frames: %d", loadmodel->numframes); + + // LordHavoc: hack to allow sprites to be non-fullbright + fullbright = true; + for (i = 0;i < MAX_QPATH && loadmodel->name[i];i++) + if (loadmodel->name[i] == '!') + fullbright = false; + +// +// load the frames +// + startframes = datapointer; + realframes = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + pinframetype = (dspriteframetype_t *)datapointer; + datapointer += sizeof(dspriteframetype_t); + + if (LittleLong (pinframetype->type) == SPR_SINGLE) + groupframes = 1; + else + { + pingroup = (dspritegroup_t *)datapointer; + datapointer += sizeof(dspritegroup_t); + + groupframes = LittleLong(pingroup->numframes); + + datapointer += sizeof(dspriteinterval_t) * groupframes; + } + + for (j = 0;j < groupframes;j++) + { + pinframe = (dspriteframe_t *)datapointer; + if (version == SPRITE32_VERSION) + datapointer += sizeof(dspriteframe_t) + LittleLong(pinframe->width) * LittleLong(pinframe->height) * 4; + else //if (version == SPRITE_VERSION || version == SPRITEHL_VERSION) + datapointer += sizeof(dspriteframe_t) + LittleLong(pinframe->width) * LittleLong(pinframe->height); + } + realframes += groupframes; + } + + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); + loadmodel->sprite.sprdata_frames = (mspriteframe_t *)Mem_Alloc(loadmodel->mempool, sizeof(mspriteframe_t) * realframes); + loadmodel->num_textures = realframes; + loadmodel->num_texturesperskin = 1; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, sizeof(texture_t) * loadmodel->num_textures); + + datapointer = (unsigned char *)startframes; + realframes = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + pinframetype = (dspriteframetype_t *)datapointer; + datapointer += sizeof(dspriteframetype_t); + + if (LittleLong (pinframetype->type) == SPR_SINGLE) + { + groupframes = 1; + interval = 0.1f; + } + else + { + pingroup = (dspritegroup_t *)datapointer; + datapointer += sizeof(dspritegroup_t); + + groupframes = LittleLong(pingroup->numframes); + + pinintervals = (dspriteinterval_t *)datapointer; + datapointer += sizeof(dspriteinterval_t) * groupframes; + + interval = LittleFloat(pinintervals[0].interval); + if (interval < 0.01f) + Host_Error("Mod_Sprite_SharedSetup: invalid interval"); + } + + dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "frame %i", i); + loadmodel->animscenes[i].firstframe = realframes; + loadmodel->animscenes[i].framecount = groupframes; + loadmodel->animscenes[i].framerate = 1.0f / interval; + loadmodel->animscenes[i].loop = true; + + for (j = 0;j < groupframes;j++) + { + pinframe = (dspriteframe_t *)datapointer; + datapointer += sizeof(dspriteframe_t); + + origin[0] = LittleLong (pinframe->origin[0]); + origin[1] = LittleLong (pinframe->origin[1]); + width = LittleLong (pinframe->width); + height = LittleLong (pinframe->height); + + loadmodel->sprite.sprdata_frames[realframes].left = origin[0]; + loadmodel->sprite.sprdata_frames[realframes].right = origin[0] + width; + loadmodel->sprite.sprdata_frames[realframes].up = origin[1]; + loadmodel->sprite.sprdata_frames[realframes].down = origin[1] - height; + + x = (int)max(loadmodel->sprite.sprdata_frames[realframes].left * loadmodel->sprite.sprdata_frames[realframes].left, loadmodel->sprite.sprdata_frames[realframes].right * loadmodel->sprite.sprdata_frames[realframes].right); + y = (int)max(loadmodel->sprite.sprdata_frames[realframes].up * loadmodel->sprite.sprdata_frames[realframes].up, loadmodel->sprite.sprdata_frames[realframes].down * loadmodel->sprite.sprdata_frames[realframes].down); + if (modelradius < x + y) + modelradius = x + y; + + if (cls.state != ca_dedicated) + { + skinframe = NULL; + // note: Nehahra's null.spr has width == 0 and height == 0 + if (width > 0 && height > 0) + { + if (groupframes > 1) + { + dpsnprintf (name, sizeof(name), "%s_%i_%i", loadmodel->name, i, j); + dpsnprintf (fogname, sizeof(fogname), "%s_%i_%ifog", loadmodel->name, i, j); + } + else + { + dpsnprintf (name, sizeof(name), "%s_%i", loadmodel->name, i); + dpsnprintf (fogname, sizeof(fogname), "%s_%ifog", loadmodel->name, i); + } + if (!(skinframe = R_SkinFrame_LoadExternal(name, texflags | TEXF_COMPRESS, false))) + { + unsigned char *pixels = (unsigned char *) Mem_Alloc(loadmodel->mempool, width*height*4); + if (version == SPRITE32_VERSION) + { + for (x = 0;x < width*height;x++) + { + pixels[x*4+2] = datapointer[x*4+0]; + pixels[x*4+1] = datapointer[x*4+1]; + pixels[x*4+0] = datapointer[x*4+2]; + pixels[x*4+3] = datapointer[x*4+3]; + } + } + else //if (version == SPRITEHL_VERSION || version == SPRITE_VERSION) + Image_Copy8bitBGRA(datapointer, pixels, width*height, palette ? palette : palette_bgra_transparent); + skinframe = R_SkinFrame_LoadInternalBGRA(name, texflags, pixels, width, height, false); + // texflags |= TEXF_COMPRESS; + Mem_Free(pixels); + } + } + if (skinframe == NULL) + skinframe = R_SkinFrame_LoadMissing(); + Mod_SpriteSetupTexture(&loadmodel->data_textures[realframes], skinframe, fullbright, additive); + } + + if (version == SPRITE32_VERSION) + datapointer += width * height * 4; + else //if (version == SPRITE_VERSION || version == SPRITEHL_VERSION) + datapointer += width * height; + realframes++; + } + } + + modelradius = sqrt(modelradius); + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = loadmodel->yawmins[i] = loadmodel->rotatedmins[i] = -modelradius; + loadmodel->normalmaxs[i] = loadmodel->yawmaxs[i] = loadmodel->rotatedmaxs[i] = modelradius; + } + loadmodel->radius = modelradius; + loadmodel->radius2 = modelradius * modelradius; +} + +extern void R_Model_Sprite_Draw(entity_render_t *ent); +void Mod_IDSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int version; + const unsigned char *datapointer; + + datapointer = (unsigned char *)buffer; + + loadmodel->modeldatatypestring = "SPR1"; + + loadmodel->type = mod_sprite; + + loadmodel->DrawSky = NULL; + loadmodel->Draw = R_Model_Sprite_Draw; + loadmodel->DrawDepth = NULL; + loadmodel->CompileShadowVolume = NULL; + loadmodel->DrawShadowVolume = NULL; + loadmodel->DrawLight = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + + version = LittleLong(((dsprite_t *)buffer)->version); + if (version == SPRITE_VERSION || version == SPRITE32_VERSION) + { + dsprite_t *pinqsprite; + + pinqsprite = (dsprite_t *)datapointer; + datapointer += sizeof(dsprite_t); + + loadmodel->numframes = LittleLong (pinqsprite->numframes); + loadmodel->sprite.sprnum_type = LittleLong (pinqsprite->type); + loadmodel->synctype = (synctype_t)LittleLong (pinqsprite->synctype); + + Mod_Sprite_SharedSetup(datapointer, LittleLong (pinqsprite->version), NULL, false); + } + else if (version == SPRITEHL_VERSION) + { + int i, rendermode; + unsigned char palette[256][4]; + const unsigned char *in; + dspritehl_t *pinhlsprite; + + pinhlsprite = (dspritehl_t *)datapointer; + datapointer += sizeof(dspritehl_t); + + loadmodel->numframes = LittleLong (pinhlsprite->numframes); + loadmodel->sprite.sprnum_type = LittleLong (pinhlsprite->type); + loadmodel->synctype = (synctype_t)LittleLong (pinhlsprite->synctype); + rendermode = pinhlsprite->rendermode; + + in = datapointer; + datapointer += 2; + i = in[0] + in[1] * 256; + if (i != 256) + Host_Error ("Mod_IDSP_Load: unexpected number of palette colors %i (should be 256)", i); + in = datapointer; + datapointer += 768; + switch(rendermode) + { + case SPRHL_OPAQUE: + for (i = 0;i < 256;i++) + { + palette[i][2] = in[i*3+0]; + palette[i][1] = in[i*3+1]; + palette[i][0] = in[i*3+2]; + palette[i][3] = 255; + } + break; + case SPRHL_ADDITIVE: + for (i = 0;i < 256;i++) + { + palette[i][2] = in[i*3+0]; + palette[i][1] = in[i*3+1]; + palette[i][0] = in[i*3+2]; + palette[i][3] = 255; + } + // also passes additive == true to Mod_Sprite_SharedSetup + break; + case SPRHL_INDEXALPHA: + for (i = 0;i < 256;i++) + { + palette[i][2] = in[765]; + palette[i][1] = in[766]; + palette[i][0] = in[767]; + palette[i][3] = i; + in += 3; + } + break; + case SPRHL_ALPHATEST: + for (i = 0;i < 256;i++) + { + palette[i][2] = in[i*3+0]; + palette[i][1] = in[i*3+1]; + palette[i][0] = in[i*3+2]; + palette[i][3] = 255; + } + palette[255][0] = palette[255][1] = palette[255][2] = palette[255][3] = 0; + // should this use alpha test or alpha blend? (currently blend) + break; + default: + Host_Error("Mod_IDSP_Load: unknown texFormat (%i, should be 0, 1, 2, or 3)", i); + return; + } + + Mod_Sprite_SharedSetup(datapointer, LittleLong (pinhlsprite->version), (unsigned int *)(&palette[0][0]), rendermode == SPRHL_ADDITIVE); + } + else + Host_Error("Mod_IDSP_Load: %s has wrong version number (%i). Only %i (quake), %i (HalfLife), and %i (sprite32) supported", + loadmodel->name, version, SPRITE_VERSION, SPRITEHL_VERSION, SPRITE32_VERSION); + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1; +} + + +void Mod_IDS2_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, version; + qboolean fullbright; + const dsprite2_t *pinqsprite; + skinframe_t *skinframe; + float modelradius; + int texflags = (r_mipsprites.integer ? TEXF_MIPMAP : 0) | TEXF_ISSPRITE | TEXF_PICMIP | TEXF_COMPRESS | TEXF_ALPHA | TEXF_CLAMP; + + loadmodel->modeldatatypestring = "SPR2"; + + loadmodel->type = mod_sprite; + + loadmodel->DrawSky = NULL; + loadmodel->Draw = R_Model_Sprite_Draw; + loadmodel->DrawDepth = NULL; + loadmodel->CompileShadowVolume = NULL; + loadmodel->DrawShadowVolume = NULL; + loadmodel->DrawLight = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + + pinqsprite = (dsprite2_t *)buffer; + + version = LittleLong(pinqsprite->version); + if (version != SPRITE2_VERSION) + Host_Error("Mod_IDS2_Load: %s has wrong version number (%i should be 2 (quake 2)", loadmodel->name, version); + + loadmodel->numframes = LittleLong (pinqsprite->numframes); + if (loadmodel->numframes < 1) + Host_Error ("Mod_IDS2_Load: Invalid # of frames: %d", loadmodel->numframes); + loadmodel->sprite.sprnum_type = SPR_VP_PARALLEL; + loadmodel->synctype = ST_SYNC; + + // LordHavoc: hack to allow sprites to be non-fullbright + fullbright = true; + for (i = 0;i < MAX_QPATH && loadmodel->name[i];i++) + if (loadmodel->name[i] == '!') + fullbright = false; + + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); + loadmodel->sprite.sprdata_frames = (mspriteframe_t *)Mem_Alloc(loadmodel->mempool, sizeof(mspriteframe_t) * loadmodel->numframes); + loadmodel->num_textures = loadmodel->numframes; + loadmodel->num_texturesperskin = 1; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, sizeof(texture_t) * loadmodel->num_textures); + + modelradius = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + int origin[2], x, y, width, height; + const dsprite2frame_t *pinframe; + mspriteframe_t *sprframe; + + dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "frame %i", i); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].framerate = 10; + loadmodel->animscenes[i].loop = true; + + pinframe = &pinqsprite->frames[i]; + + origin[0] = LittleLong (pinframe->origin_x); + origin[1] = LittleLong (pinframe->origin_y); + width = LittleLong (pinframe->width); + height = LittleLong (pinframe->height); + + sprframe = &loadmodel->sprite.sprdata_frames[i]; + + // note that sp2 origin[0] is positive, where as it is negative in + // spr/spr32/hlspr + sprframe->left = -origin[0]; + sprframe->right = -origin[0] + width; + sprframe->up = origin[1]; + sprframe->down = origin[1] - height; + + x = (int)max(sprframe->left * sprframe->left, sprframe->right * sprframe->right); + y = (int)max(sprframe->up * sprframe->up, sprframe->down * sprframe->down); + if (modelradius < x + y) + modelradius = x + y; + } + + if (cls.state != ca_dedicated) + { + for (i = 0;i < loadmodel->numframes;i++) + { + const dsprite2frame_t *pinframe; + pinframe = &pinqsprite->frames[i]; + if (!(skinframe = R_SkinFrame_LoadExternal(pinframe->name, texflags, false))) + { + Con_Printf("Mod_IDS2_Load: failed to load %s", pinframe->name); + skinframe = R_SkinFrame_LoadMissing(); + } + Mod_SpriteSetupTexture(&loadmodel->data_textures[i], skinframe, fullbright, false); + } + } + + modelradius = sqrt(modelradius); + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = loadmodel->yawmins[i] = loadmodel->rotatedmins[i] = -modelradius; + loadmodel->normalmaxs[i] = loadmodel->yawmaxs[i] = loadmodel->rotatedmaxs[i] = modelradius; + } + loadmodel->radius = modelradius; + loadmodel->radius2 = modelradius * modelradius; + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1; +} diff --git a/misc/source/darkplaces-src/model_sprite.h b/misc/source/darkplaces-src/model_sprite.h new file mode 100644 index 00000000..f6408dad --- /dev/null +++ b/misc/source/darkplaces-src/model_sprite.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef MODEL_SPRITE_H +#define MODEL_SPRITE_H + +/* +============================================================================== + +SPRITE MODELS + +============================================================================== +*/ + +#include "spritegn.h" + +// FIXME: shorten these? +typedef struct mspriteframe_s +{ + float up, down, left, right; +} mspriteframe_t; + +#endif + diff --git a/misc/source/darkplaces-src/model_zymotic.h b/misc/source/darkplaces-src/model_zymotic.h new file mode 100644 index 00000000..d43f72c5 --- /dev/null +++ b/misc/source/darkplaces-src/model_zymotic.h @@ -0,0 +1,68 @@ + +#ifndef MODEL_ZYMOTIC_H +#define MODEL_ZYMOTIC_H + +typedef struct zymlump_s +{ + int start; + int length; +} zymlump_t; + +typedef struct zymtype1header_s +{ + char id[12]; // "ZYMOTICMODEL", length 12, no termination + int type; // 0 (vertex morph) 1 (skeletal pose) or 2 (skeletal scripted) + int filesize; // size of entire model file + float mins[3], maxs[3], radius; // for clipping uses + int numverts; + int numtris; + int numshaders; + int numbones; // this may be zero in the vertex morph format (undecided) + int numscenes; // 0 in skeletal scripted models + +// skeletal pose header + // lump offsets are relative to the file + zymlump_t lump_scenes; // zymscene_t scene[numscenes]; // name and other information for each scene (see zymscene struct) + zymlump_t lump_poses; // float pose[numposes][numbones][6]; // animation data + zymlump_t lump_bones; // zymbone_t bone[numbones]; + zymlump_t lump_vertbonecounts; // int vertbonecounts[numvertices]; // how many bones influence each vertex (separate mainly to make this compress better) + zymlump_t lump_verts; // zymvertex_t vert[numvertices]; // see vertex struct + zymlump_t lump_texcoords; // float texcoords[numvertices][2]; + zymlump_t lump_render; // int renderlist[rendersize]; // sorted by shader with run lengths (int count), shaders are sequentially used, each run can be used with glDrawElements (each triangle is 3 int indices) + zymlump_t lump_shaders; // char shadername[numshaders][32]; // shaders used on this model + zymlump_t lump_trizone; // byte trizone[numtris]; // see trizone explanation +} +zymtype1header_t; + +#define ZYMBONEFLAG_SHARED 1 + +typedef struct zymbone_s +{ + char name[32]; + int flags; + int parent; // parent bone number +} +zymbone_t; + +// normally the scene will loop, if this is set it will stay on the final frame +#define ZYMSCENEFLAG_NOLOOP 1 + +typedef struct zymscene_s +{ + char name[32]; + float mins[3], maxs[3], radius; // for clipping + float framerate; // the scene will animate at this framerate (in frames per second) + int flags; + int start, length; // range of poses +} +zymscene_t; + +typedef struct zymvertex_s +{ + int bonenum; + float origin[3]; +} +zymvertex_t; + +#endif + diff --git a/misc/source/darkplaces-src/modelgen.h b/misc/source/darkplaces-src/modelgen.h new file mode 100644 index 00000000..0ddb7761 --- /dev/null +++ b/misc/source/darkplaces-src/modelgen.h @@ -0,0 +1,137 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// +// modelgen.h: header file for model generation program +// + +// ********************************************************* +// * This file must be identical in the modelgen directory * +// * and in the Quake directory, because it's used to * +// * pass data from one to the other via model files. * +// ********************************************************* + +#ifndef MODELGEN_H +#define MODELGEN_H + +#define ALIAS_VERSION 6 + +#define ALIAS_ONSEAM 0x0020 + +typedef enum aliasframetype_e { ALIAS_SINGLE=0, ALIAS_GROUP } aliasframetype_t; + +typedef enum aliasskintype_e { ALIAS_SKIN_SINGLE=0, ALIAS_SKIN_GROUP } aliasskintype_t; + +typedef struct mdl_s +{ + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + synctype_t synctype; + int flags; + float size; +} +mdl_t; + +// TODO: could be shorts + +typedef struct stvert_s +{ + int onseam; + int s; + int t; +} +stvert_t; + +typedef struct dtriangle_s +{ + int facesfront; + int vertindex[3]; +} +dtriangle_t; + +#define DT_FACES_FRONT 0x0010 + +// This mirrors trivert_t in trilib.h, is present so Quake knows how to +// load this data + +typedef struct trivertx_s +{ + unsigned char v[3]; + unsigned char lightnormalindex; +} +trivertx_t; + +typedef struct daliasframe_s +{ + trivertx_t bboxmin; // lightnormal isn't used + trivertx_t bboxmax; // lightnormal isn't used + char name[16]; // frame name from grabbing +} +daliasframe_t; + +typedef struct daliasgroup_s +{ + int numframes; + trivertx_t bboxmin; // lightnormal isn't used + trivertx_t bboxmax; // lightnormal isn't used +} +daliasgroup_t; + +typedef struct daliasskingroup_s +{ + int numskins; +} +daliasskingroup_t; + +typedef struct daliasinterval_s +{ + float interval; +} +daliasinterval_t; + +typedef struct daliasskininterval_s +{ + float interval; +} +daliasskininterval_t; + +typedef struct daliasframetype_s +{ + aliasframetype_t type; +} +daliasframetype_t; + +typedef struct daliasskintype_s +{ + aliasskintype_t type; +} +daliasskintype_t; + +#endif + diff --git a/misc/source/darkplaces-src/mprogdefs.h b/misc/source/darkplaces-src/mprogdefs.h new file mode 100644 index 00000000..c7993b7d --- /dev/null +++ b/misc/source/darkplaces-src/mprogdefs.h @@ -0,0 +1,22 @@ + +#ifndef MPROGDEFS_H +#define MPROGDEFS_H + +/* file generated by qcc, do not modify */ + +/* +typedef struct m_globalvars_s +{ + int pad[28]; + int self; +} m_globalvars_t; + +typedef struct m_entvars_s +{ +} m_entvars_t; + +#define M_PROGHEADER_CRC 10020 + +*/ + +#endif diff --git a/misc/source/darkplaces-src/mvm_cmds.c b/misc/source/darkplaces-src/mvm_cmds.c new file mode 100644 index 00000000..9d154b49 --- /dev/null +++ b/misc/source/darkplaces-src/mvm_cmds.c @@ -0,0 +1,1546 @@ +#include "quakedef.h" + +#include "prvm_cmds.h" +#include "clvm_cmds.h" +#include "menu.h" + +// TODO check which strings really should be engine strings + +//============================================================================ +// Menu + +const char *vm_m_extensions = +"BX_WAL_SUPPORT " +"DP_CINEMATIC_DPV " +"DP_CSQC_BINDMAPS " +"DP_CRYPTO " +"DP_GFX_FONTS " +"DP_GFX_FONTS_FREETYPE " +"DP_UTF8 " +"DP_FONT_VARIABLEWIDTH " +"DP_GECKO_SUPPORT " +"DP_MENU_EXTRESPONSEPACKET " +"DP_QC_ASINACOSATANATAN2TAN " +"DP_QC_AUTOCVARS " +"DP_QC_CMD " +"DP_QC_CRC16 " +"DP_QC_CVAR_TYPE " +"DP_QC_CVAR_DESCRIPTION " +"DP_QC_FINDCHAIN_TOFIELD " +"DP_QC_LOG " +"DP_QC_RENDER_SCENE " +"DP_QC_SPRINTF " +"DP_QC_STRFTIME " +"DP_QC_STRINGBUFFERS " +"DP_QC_STRINGBUFFERS_CVARLIST " +"DP_QC_STRINGCOLORFUNCTIONS " +"DP_QC_STRING_CASE_FUNCTIONS " +"DP_QC_STRREPLACE " +"DP_QC_TOKENIZEBYSEPARATOR " +"DP_QC_TOKENIZE_CONSOLE " +"DP_QC_UNLIMITEDTEMPSTRINGS " +"DP_QC_URI_ESCAPE " +"DP_QC_URI_GET " +"DP_QC_URI_POST " +"DP_QC_WHICHPACK " +"FTE_STRINGS " +; + +/* +========= +VM_M_setmousetarget + +setmousetarget(float target) +========= +*/ +void VM_M_setmousetarget(void) +{ + VM_SAFEPARMCOUNT(1, VM_M_setmousetarget); + + switch((int)PRVM_G_FLOAT(OFS_PARM0)) + { + case 1: + in_client_mouse = false; + break; + case 2: + in_client_mouse = true; + break; + default: + PRVM_ERROR("VM_M_setmousetarget: wrong destination %f !",PRVM_G_FLOAT(OFS_PARM0)); + } +} + +/* +========= +VM_M_getmousetarget + +float getmousetarget +========= +*/ +void VM_M_getmousetarget(void) +{ + VM_SAFEPARMCOUNT(0,VM_M_getmousetarget); + + if(in_client_mouse) + PRVM_G_FLOAT(OFS_RETURN) = 2; + else + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + + + +/* +========= +VM_M_setkeydest + +setkeydest(float dest) +========= +*/ +void VM_M_setkeydest(void) +{ + VM_SAFEPARMCOUNT(1,VM_M_setkeydest); + + switch((int)PRVM_G_FLOAT(OFS_PARM0)) + { + case 0: + // key_game + key_dest = key_game; + break; + case 2: + // key_menu + key_dest = key_menu; + break; + case 3: + // key_menu_grabbed + key_dest = key_menu_grabbed; + break; + case 1: + // key_message + // key_dest = key_message + // break; + default: + PRVM_ERROR("VM_M_setkeydest: wrong destination %f !", PRVM_G_FLOAT(OFS_PARM0)); + } +} + +/* +========= +VM_M_getkeydest + +float getkeydest +========= +*/ +void VM_M_getkeydest(void) +{ + VM_SAFEPARMCOUNT(0,VM_M_getkeydest); + + // key_game = 0, key_message = 1, key_menu = 2, key_menu_grabbed = 3, unknown = -1 + switch(key_dest) + { + case key_game: + PRVM_G_FLOAT(OFS_RETURN) = 0; + break; + case key_menu: + PRVM_G_FLOAT(OFS_RETURN) = 2; + break; + case key_menu_grabbed: + PRVM_G_FLOAT(OFS_RETURN) = 3; + break; + case key_message: + // not supported + // PRVM_G_FLOAT(OFS_RETURN) = 1; + // break; + default: + PRVM_G_FLOAT(OFS_RETURN) = -1; + } +} + + +/* +========= +VM_M_getresolution + +vector getresolution(float number) +========= +*/ +void VM_M_getresolution(void) +{ + int nr, fs; + VM_SAFEPARMCOUNTRANGE(1, 2, VM_getresolution); + + nr = (int)PRVM_G_FLOAT(OFS_PARM0); + + fs = ((prog->argc <= 1) || ((int)PRVM_G_FLOAT(OFS_PARM1))); + + if(nr < 0 || nr >= (fs ? video_resolutions_count : video_resolutions_hardcoded_count)) + { + PRVM_G_VECTOR(OFS_RETURN)[0] = 0; + PRVM_G_VECTOR(OFS_RETURN)[1] = 0; + PRVM_G_VECTOR(OFS_RETURN)[2] = 0; + } + else + { + video_resolution_t *r = &((fs ? video_resolutions : video_resolutions_hardcoded)[nr]); + PRVM_G_VECTOR(OFS_RETURN)[0] = r->width; + PRVM_G_VECTOR(OFS_RETURN)[1] = r->height; + PRVM_G_VECTOR(OFS_RETURN)[2] = r->pixelheight; + } +} + +void VM_M_getgamedirinfo(void) +{ + int nr, item; + VM_SAFEPARMCOUNT(2, VM_getgamedirinfo); + + nr = (int)PRVM_G_FLOAT(OFS_PARM0); + item = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; + + if(nr >= 0 && nr < fs_all_gamedirs_count) + { + if(item == 0) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( fs_all_gamedirs[nr].name ); + else if(item == 1) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( fs_all_gamedirs[nr].description ); + } +} + +/* +========= +VM_M_getserverliststat + +float getserverliststat(float type) +========= +*/ +/* + type: +0 serverlist_viewcount +1 serverlist_totalcount +2 masterquerycount +3 masterreplycount +4 serverquerycount +5 serverreplycount +6 sortfield +7 sortflags +*/ +void VM_M_getserverliststat( void ) +{ + int type; + VM_SAFEPARMCOUNT ( 1, VM_M_getserverliststat ); + + PRVM_G_FLOAT( OFS_RETURN ) = 0; + + type = (int)PRVM_G_FLOAT( OFS_PARM0 ); + switch(type) + { + case 0: + PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_viewcount; + return; + case 1: + PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_cachecount; + return; + case 2: + PRVM_G_FLOAT ( OFS_RETURN ) = masterquerycount; + return; + case 3: + PRVM_G_FLOAT ( OFS_RETURN ) = masterreplycount; + return; + case 4: + PRVM_G_FLOAT ( OFS_RETURN ) = serverquerycount; + return; + case 5: + PRVM_G_FLOAT ( OFS_RETURN ) = serverreplycount; + return; + case 6: + PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_sortbyfield; + return; + case 7: + PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_sortflags; + return; + default: + VM_Warning( "VM_M_getserverliststat: bad type %i!\n", type ); + } +} + +/* +======================== +VM_M_resetserverlistmasks + +resetserverlistmasks() +======================== +*/ +void VM_M_resetserverlistmasks( void ) +{ + VM_SAFEPARMCOUNT(0, VM_M_resetserverlistmasks); + ServerList_ResetMasks(); +} + + +/* +======================== +VM_M_setserverlistmaskstring + +setserverlistmaskstring(float mask, float fld, string str, float op) +0-511 and +512 - 1024 or +======================== +*/ +void VM_M_setserverlistmaskstring( void ) +{ + const char *str; + int masknr; + serverlist_mask_t *mask; + int field; + + VM_SAFEPARMCOUNT( 4, VM_M_setserverlistmaskstring ); + str = PRVM_G_STRING( OFS_PARM2 ); + + masknr = (int)PRVM_G_FLOAT( OFS_PARM0 ); + if( masknr >= 0 && masknr <= SERVERLIST_ANDMASKCOUNT ) + mask = &serverlist_andmasks[masknr]; + else if( masknr >= 512 && masknr - 512 <= SERVERLIST_ORMASKCOUNT ) + mask = &serverlist_ormasks[masknr - 512 ]; + else + { + VM_Warning( "VM_M_setserverlistmaskstring: invalid mask number %i\n", masknr ); + return; + } + + field = (int) PRVM_G_FLOAT( OFS_PARM1 ); + + switch( field ) { + case SLIF_CNAME: + strlcpy( mask->info.cname, str, sizeof(mask->info.cname) ); + break; + case SLIF_NAME: + strlcpy( mask->info.name, str, sizeof(mask->info.name) ); + break; + case SLIF_QCSTATUS: + strlcpy( mask->info.qcstatus, str, sizeof(mask->info.qcstatus) ); + break; + case SLIF_PLAYERS: + strlcpy( mask->info.players, str, sizeof(mask->info.players) ); + break; + case SLIF_MAP: + strlcpy( mask->info.map, str, sizeof(mask->info.map) ); + break; + case SLIF_MOD: + strlcpy( mask->info.mod, str, sizeof(mask->info.mod) ); + break; + case SLIF_GAME: + strlcpy( mask->info.game, str, sizeof(mask->info.game) ); + break; + default: + VM_Warning( "VM_M_setserverlistmaskstring: Bad field number %i passed!\n", field ); + return; + } + + mask->active = true; + mask->tests[field] = (serverlist_maskop_t)((int)PRVM_G_FLOAT( OFS_PARM3 )); +} + +/* +======================== +VM_M_setserverlistmasknumber + +setserverlistmasknumber(float mask, float fld, float num, float op) + +0-511 and +512 - 1024 or +======================== +*/ +void VM_M_setserverlistmasknumber( void ) +{ + int number; + serverlist_mask_t *mask; + int masknr; + int field; + VM_SAFEPARMCOUNT( 4, VM_M_setserverlistmasknumber ); + + masknr = (int)PRVM_G_FLOAT( OFS_PARM0 ); + if( masknr >= 0 && masknr <= SERVERLIST_ANDMASKCOUNT ) + mask = &serverlist_andmasks[masknr]; + else if( masknr >= 512 && masknr - 512 <= SERVERLIST_ORMASKCOUNT ) + mask = &serverlist_ormasks[masknr - 512 ]; + else + { + VM_Warning( "VM_M_setserverlistmasknumber: invalid mask number %i\n", masknr ); + return; + } + + number = (int)PRVM_G_FLOAT( OFS_PARM2 ); + field = (int) PRVM_G_FLOAT( OFS_PARM1 ); + + switch( field ) { + case SLIF_MAXPLAYERS: + mask->info.maxplayers = number; + break; + case SLIF_NUMPLAYERS: + mask->info.numplayers = number; + break; + case SLIF_NUMBOTS: + mask->info.numbots = number; + break; + case SLIF_NUMHUMANS: + mask->info.numhumans = number; + break; + case SLIF_PING: + mask->info.ping = number; + break; + case SLIF_PROTOCOL: + mask->info.protocol = number; + break; + case SLIF_FREESLOTS: + mask->info.freeslots = number; + break; + case SLIF_ISFAVORITE: + mask->info.isfavorite = number != 0; + break; + default: + VM_Warning( "VM_M_setserverlistmasknumber: Bad field number %i passed!\n", field ); + return; + } + + mask->active = true; + mask->tests[field] = (serverlist_maskop_t)((int)PRVM_G_FLOAT( OFS_PARM3 )); +} + + +/* +======================== +VM_M_resortserverlist + +resortserverlist +======================== +*/ +void VM_M_resortserverlist( void ) +{ + VM_SAFEPARMCOUNT(0, VM_M_resortserverlist); + ServerList_RebuildViewList(); +} + +/* +========= +VM_M_getserverliststring + +string getserverliststring(float field, float hostnr) +========= +*/ +void VM_M_getserverliststring(void) +{ + serverlist_entry_t *cache; + int hostnr; + + VM_SAFEPARMCOUNT(2, VM_M_getserverliststring); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + + hostnr = (int)PRVM_G_FLOAT(OFS_PARM1); + + if(hostnr < 0 || hostnr >= serverlist_viewcount) + { + Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); + return; + } + cache = ServerList_GetViewEntry(hostnr); + switch( (int) PRVM_G_FLOAT(OFS_PARM0) ) { + case SLIF_CNAME: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( cache->info.cname ); + break; + case SLIF_NAME: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( cache->info.name ); + break; + case SLIF_QCSTATUS: + PRVM_G_INT (OFS_RETURN ) = PRVM_SetTempString (cache->info.qcstatus ); + break; + case SLIF_PLAYERS: + PRVM_G_INT (OFS_RETURN ) = PRVM_SetTempString (cache->info.players ); + break; + case SLIF_GAME: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( cache->info.game ); + break; + case SLIF_MOD: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( cache->info.mod ); + break; + case SLIF_MAP: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( cache->info.map ); + break; + // TODO remove this again + case 1024: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( cache->line1 ); + break; + case 1025: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( cache->line2 ); + break; + default: + Con_Print("VM_M_getserverliststring: bad field number passed!\n"); + } +} + +/* +========= +VM_M_getserverlistnumber + +float getserverlistnumber(float field, float hostnr) +========= +*/ +void VM_M_getserverlistnumber(void) +{ + serverlist_entry_t *cache; + int hostnr; + + VM_SAFEPARMCOUNT(2, VM_M_getserverliststring); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + + hostnr = (int)PRVM_G_FLOAT(OFS_PARM1); + + if(hostnr < 0 || hostnr >= serverlist_viewcount) + { + Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); + return; + } + cache = ServerList_GetViewEntry(hostnr); + switch( (int) PRVM_G_FLOAT(OFS_PARM0) ) { + case SLIF_MAXPLAYERS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.maxplayers; + break; + case SLIF_NUMPLAYERS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numplayers; + break; + case SLIF_NUMBOTS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numbots; + break; + case SLIF_NUMHUMANS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numhumans; + break; + case SLIF_FREESLOTS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.freeslots; + break; + case SLIF_PING: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.ping; + break; + case SLIF_PROTOCOL: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.protocol; + break; + case SLIF_ISFAVORITE: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.isfavorite; + break; + default: + Con_Print("VM_M_getserverlistnumber: bad field number passed!\n"); + } +} + +/* +======================== +VM_M_setserverlistsort + +setserverlistsort(float field, float flags) +======================== +*/ +void VM_M_setserverlistsort( void ) +{ + VM_SAFEPARMCOUNT( 2, VM_M_setserverlistsort ); + + serverlist_sortbyfield = (serverlist_infofield_t)((int)PRVM_G_FLOAT( OFS_PARM0 )); + serverlist_sortflags = (int) PRVM_G_FLOAT( OFS_PARM1 ); +} + +/* +======================== +VM_M_refreshserverlist + +refreshserverlist() +======================== +*/ +void VM_M_refreshserverlist( void ) +{ + VM_SAFEPARMCOUNT( 0, VM_M_refreshserverlist ); + ServerList_QueryList(false, true, false, false); +} + +/* +======================== +VM_M_getserverlistindexforkey + +float getserverlistindexforkey(string key) +======================== +*/ +void VM_M_getserverlistindexforkey( void ) +{ + const char *key; + VM_SAFEPARMCOUNT( 1, VM_M_getserverlistindexforkey ); + + key = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( key ); + + if( !strcmp( key, "cname" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_CNAME; + else if( !strcmp( key, "ping" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PING; + else if( !strcmp( key, "game" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_GAME; + else if( !strcmp( key, "mod" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MOD; + else if( !strcmp( key, "map" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MAP; + else if( !strcmp( key, "name" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NAME; + else if( !strcmp( key, "qcstatus" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_QCSTATUS; + else if( !strcmp( key, "players" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PLAYERS; + else if( !strcmp( key, "maxplayers" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MAXPLAYERS; + else if( !strcmp( key, "numplayers" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMPLAYERS; + else if( !strcmp( key, "numbots" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMBOTS; + else if( !strcmp( key, "numhumans" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMHUMANS; + else if( !strcmp( key, "freeslots" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_FREESLOTS; + else if( !strcmp( key, "protocol" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PROTOCOL; + else if( !strcmp( key, "isfavorite" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_ISFAVORITE; + else + PRVM_G_FLOAT( OFS_RETURN ) = -1; +} + +/* +======================== +VM_M_addwantedserverlistkey + +addwantedserverlistkey(string key) +======================== +*/ +void VM_M_addwantedserverlistkey( void ) +{ + VM_SAFEPARMCOUNT( 1, VM_M_addwantedserverlistkey ); +} + +/* +=============================================================================== +MESSAGE WRITING + +used only for client and menu +server uses VM_SV_... + +Write*(* data, float type, float to) + +=============================================================================== +*/ + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string + +sizebuf_t *VM_M_WriteDest (void) +{ + int dest; + int destclient; + + if(!sv.active) + PRVM_ERROR("VM_M_WriteDest: game is not server (%s)", PRVM_NAME); + + dest = (int)PRVM_G_FLOAT(OFS_PARM1); + switch (dest) + { + case MSG_BROADCAST: + return &sv.datagram; + + case MSG_ONE: + destclient = (int) PRVM_G_FLOAT(OFS_PARM2); + if (destclient < 0 || destclient >= svs.maxclients || !svs.clients[destclient].active || !svs.clients[destclient].netconnection) + PRVM_ERROR("VM_clientcommand: %s: invalid client !", PRVM_NAME); + + return &svs.clients[destclient].netconnection->message; + + case MSG_ALL: + return &sv.reliable_datagram; + + case MSG_INIT: + return &sv.signon; + + default: + PRVM_ERROR ("WriteDest: bad destination"); + break; + } + + return NULL; +} + +void VM_M_WriteByte (void) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteByte); + MSG_WriteByte (VM_M_WriteDest(), (int)PRVM_G_FLOAT(OFS_PARM0)); +} + +void VM_M_WriteChar (void) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteChar); + MSG_WriteChar (VM_M_WriteDest(), (int)PRVM_G_FLOAT(OFS_PARM0)); +} + +void VM_M_WriteShort (void) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteShort); + MSG_WriteShort (VM_M_WriteDest(), (int)PRVM_G_FLOAT(OFS_PARM0)); +} + +void VM_M_WriteLong (void) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteLong); + MSG_WriteLong (VM_M_WriteDest(), (int)PRVM_G_FLOAT(OFS_PARM0)); +} + +void VM_M_WriteAngle (void) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteAngle); + MSG_WriteAngle (VM_M_WriteDest(), PRVM_G_FLOAT(OFS_PARM0), sv.protocol); +} + +void VM_M_WriteCoord (void) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteCoord); + MSG_WriteCoord (VM_M_WriteDest(), PRVM_G_FLOAT(OFS_PARM0), sv.protocol); +} + +void VM_M_WriteString (void) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteString); + MSG_WriteString (VM_M_WriteDest(), PRVM_G_STRING(OFS_PARM0)); +} + +void VM_M_WriteEntity (void) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteEntity); + MSG_WriteShort (VM_M_WriteDest(), PRVM_G_EDICTNUM(OFS_PARM0)); +} + +/* +================= +VM_M_copyentity + +copies data from one entity to another + +copyentity(entity src, entity dst) +================= +*/ +static void VM_M_copyentity (void) +{ + prvm_edict_t *in, *out; + VM_SAFEPARMCOUNT(2,VM_M_copyentity); + in = PRVM_G_EDICT(OFS_PARM0); + out = PRVM_G_EDICT(OFS_PARM1); + memcpy(out->fields.vp, in->fields.vp, prog->entityfields * 4); +} + +//#66 vector() getmousepos (EXT_CSQC) +static void VM_M_getmousepos(void) +{ + VM_SAFEPARMCOUNT(0,VM_M_getmousepos); + + if (key_consoleactive || (key_dest != key_menu && key_dest != key_menu_grabbed)) + VectorSet(PRVM_G_VECTOR(OFS_RETURN), 0, 0, 0); + else if (in_client_mouse) + VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_windowmouse_x * vid_conwidth.integer / vid.width, in_windowmouse_y * vid_conheight.integer / vid.height, 0); + else + VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height, 0); +} + +void VM_M_crypto_getkeyfp(void) +{ + lhnetaddress_t addr; + const char *s; + char keyfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getkeyfp); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, keyfp, sizeof(keyfp), NULL, 0, NULL)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( keyfp ); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +void VM_M_crypto_getidfp(void) +{ + lhnetaddress_t addr; + const char *s; + char idfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getidfp); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, idfp, sizeof(idfp), NULL)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( idfp ); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +void VM_M_crypto_getencryptlevel(void) +{ + lhnetaddress_t addr; + const char *s; + int aeslevel; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getencryptlevel); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, NULL, 0, &aeslevel)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(aeslevel ? va("%d AES128", aeslevel) : "0"); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +void VM_M_crypto_getmykeyfp(void) +{ + int i; + char keyfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); + + i = PRVM_G_FLOAT( OFS_PARM0 ); + switch(Crypto_RetrieveLocalKey(i, keyfp, sizeof(keyfp), NULL, 0)) + { + case -1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(""); + break; + case 0: + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; + break; + default: + case 1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(keyfp); + break; + } +} +void VM_M_crypto_getmyidfp(void) +{ + int i; + char idfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); + + i = PRVM_G_FLOAT( OFS_PARM0 ); + switch(Crypto_RetrieveLocalKey(i, NULL, 0, idfp, sizeof(idfp))) + { + case -1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(""); + break; + case 0: + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; + break; + default: + case 1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(idfp); + break; + } +} + +prvm_builtin_t vm_m_builtins[] = { +NULL, // #0 NULL function (not callable) +VM_checkextension, // #1 +VM_error, // #2 +VM_objerror, // #3 +VM_print, // #4 +VM_bprint, // #5 +VM_sprint, // #6 +VM_centerprint, // #7 +VM_normalize, // #8 +VM_vlen, // #9 +VM_vectoyaw, // #10 +VM_vectoangles, // #11 +VM_random, // #12 +VM_localcmd, // #13 +VM_cvar, // #14 +VM_cvar_set, // #15 +VM_dprint, // #16 +VM_ftos, // #17 +VM_fabs, // #18 +VM_vtos, // #19 +VM_etos, // #20 +VM_stof, // #21 +VM_spawn, // #22 +VM_remove, // #23 +VM_find, // #24 +VM_findfloat, // #25 +VM_findchain, // #26 +VM_findchainfloat, // #27 +VM_precache_file, // #28 +VM_precache_sound, // #29 +VM_coredump, // #30 +VM_traceon, // #31 +VM_traceoff, // #32 +VM_eprint, // #33 +VM_rint, // #34 +VM_floor, // #35 +VM_ceil, // #36 +VM_nextent, // #37 +VM_sin, // #38 +VM_cos, // #39 +VM_sqrt, // #40 +VM_randomvec, // #41 +VM_registercvar, // #42 +VM_min, // #43 +VM_max, // #44 +VM_bound, // #45 +VM_pow, // #46 +VM_M_copyentity, // #47 +VM_fopen, // #48 +VM_fclose, // #49 +VM_fgets, // #50 +VM_fputs, // #51 +VM_strlen, // #52 +VM_strcat, // #53 +VM_substring, // #54 +VM_stov, // #55 +VM_strzone, // #56 +VM_strunzone, // #57 +VM_tokenize, // #58 +VM_argv, // #59 +VM_isserver, // #60 +VM_clientcount, // #61 +VM_clientstate, // #62 +VM_clcommand, // #63 +VM_changelevel, // #64 +VM_localsound, // #65 +VM_M_getmousepos, // #66 +VM_gettime, // #67 +VM_loadfromdata, // #68 +VM_loadfromfile, // #69 +VM_modulo, // #70 +VM_cvar_string, // #71 +VM_crash, // #72 +VM_stackdump, // #73 +VM_search_begin, // #74 +VM_search_end, // #75 +VM_search_getsize, // #76 +VM_search_getfilename, // #77 +VM_chr, // #78 +VM_itof, // #79 +VM_ftoe, // #80 +VM_itof, // #81 isString +VM_altstr_count, // #82 +VM_altstr_prepare, // #83 +VM_altstr_get, // #84 +VM_altstr_set, // #85 +VM_altstr_ins, // #86 +VM_findflags, // #87 +VM_findchainflags, // #88 +VM_cvar_defstring, // #89 +// deactivate support for model rendering in the menu until someone has time to do it right [3/2/2008 Andreas] +#if 0 +VM_CL_setmodel, // #90 void(entity e, string m) setmodel (QUAKE) +VM_CL_precache_model, // #91 void(string s) precache_model (QUAKE) +VM_CL_setorigin, // #92 void(entity e, vector o) setorigin (QUAKE) +#else +NULL, +NULL, +NULL, +#endif +NULL, // #93 +NULL, // #94 +NULL, // #95 +NULL, // #96 +NULL, // #97 +NULL, // #98 +NULL, // #99 +NULL, // #100 +NULL, // #101 +NULL, // #102 +NULL, // #103 +NULL, // #104 +NULL, // #105 +NULL, // #106 +NULL, // #107 +NULL, // #108 +NULL, // #109 +NULL, // #110 +NULL, // #111 +NULL, // #112 +NULL, // #113 +NULL, // #114 +NULL, // #115 +NULL, // #116 +NULL, // #117 +NULL, // #118 +NULL, // #119 +NULL, // #120 +NULL, // #121 +NULL, // #122 +NULL, // #123 +NULL, // #124 +NULL, // #125 +NULL, // #126 +NULL, // #127 +NULL, // #128 +NULL, // #129 +NULL, // #130 +NULL, // #131 +NULL, // #132 +NULL, // #133 +NULL, // #134 +NULL, // #135 +NULL, // #136 +NULL, // #137 +NULL, // #138 +NULL, // #139 +NULL, // #140 +NULL, // #141 +NULL, // #142 +NULL, // #143 +NULL, // #144 +NULL, // #145 +NULL, // #146 +NULL, // #147 +NULL, // #148 +NULL, // #149 +NULL, // #150 +NULL, // #151 +NULL, // #152 +NULL, // #153 +NULL, // #154 +NULL, // #155 +NULL, // #156 +NULL, // #157 +NULL, // #158 +NULL, // #159 +NULL, // #160 +NULL, // #161 +NULL, // #162 +NULL, // #163 +NULL, // #164 +NULL, // #165 +NULL, // #166 +NULL, // #167 +NULL, // #168 +NULL, // #169 +NULL, // #170 +NULL, // #171 +NULL, // #172 +NULL, // #173 +NULL, // #174 +NULL, // #175 +NULL, // #176 +NULL, // #177 +NULL, // #178 +NULL, // #179 +NULL, // #180 +NULL, // #181 +NULL, // #182 +NULL, // #183 +NULL, // #184 +NULL, // #185 +NULL, // #186 +NULL, // #187 +NULL, // #188 +NULL, // #189 +NULL, // #190 +NULL, // #191 +NULL, // #192 +NULL, // #193 +NULL, // #194 +NULL, // #195 +NULL, // #196 +NULL, // #197 +NULL, // #198 +NULL, // #199 +NULL, // #200 +NULL, // #201 +NULL, // #202 +NULL, // #203 +NULL, // #204 +NULL, // #205 +NULL, // #206 +NULL, // #207 +NULL, // #208 +NULL, // #209 +NULL, // #210 +NULL, // #211 +NULL, // #212 +NULL, // #213 +NULL, // #214 +NULL, // #215 +NULL, // #216 +NULL, // #217 +NULL, // #218 +NULL, // #219 +NULL, // #220 +VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) +VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) +VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) +VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) +VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) +VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) +VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) +VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) +VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) +VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) +NULL, // #231 +NULL, // #232 +NULL, // #233 +NULL, // #234 +NULL, // #235 +NULL, // #236 +NULL, // #237 +NULL, // #238 +NULL, // #239 +NULL, // #240 +NULL, // #241 +NULL, // #242 +NULL, // #243 +NULL, // #244 +NULL, // #245 +NULL, // #246 +NULL, // #247 +NULL, // #248 +NULL, // #249 +NULL, // #250 +NULL, // #251 +NULL, // #252 +NULL, // #253 +NULL, // #254 +NULL, // #255 +NULL, // #256 +NULL, // #257 +NULL, // #258 +NULL, // #259 +NULL, // #260 +NULL, // #261 +NULL, // #262 +NULL, // #263 +NULL, // #264 +NULL, // #265 +NULL, // #266 +NULL, // #267 +NULL, // #268 +NULL, // #269 +NULL, // #270 +NULL, // #271 +NULL, // #272 +NULL, // #273 +NULL, // #274 +NULL, // #275 +NULL, // #276 +NULL, // #277 +NULL, // #278 +NULL, // #279 +NULL, // #280 +NULL, // #281 +NULL, // #282 +NULL, // #283 +NULL, // #284 +NULL, // #285 +NULL, // #286 +NULL, // #287 +NULL, // #288 +NULL, // #289 +NULL, // #290 +NULL, // #291 +NULL, // #292 +NULL, // #293 +NULL, // #294 +NULL, // #295 +NULL, // #296 +NULL, // #297 +NULL, // #298 +NULL, // #299 +// deactivate support for model rendering in the menu until someone has time to do it right [3/2/2008 Andreas] +#if 0 +// CSQC range #300-#399 +VM_CL_R_ClearScene, // #300 void() clearscene (DP_QC_RENDER_SCENE) +VM_CL_R_AddEntities, // #301 void(float mask) addentities (DP_QC_RENDER_SCENE) +VM_CL_R_AddEntity, // #302 void(entity ent) addentity (DP_QC_RENDER_SCENE) +VM_CL_R_SetView, // #303 float(float property, ...) setproperty (DP_QC_RENDER_SCENE) +VM_CL_R_RenderScene, // #304 void() renderscene (DP_QC_RENDER_SCENE) +VM_CL_R_AddDynamicLight, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (DP_QC_RENDER_SCENE) +VM_CL_R_PolygonBegin, // #306 void(string texturename, float flag[, float is2d, float lines]) R_BeginPolygon (DP_QC_RENDER_SCENE) +VM_CL_R_PolygonVertex, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex (DP_QC_RENDER_SCENE) +VM_CL_R_PolygonEnd, // #308 void() R_EndPolygon +NULL/*VM_CL_R_LoadWorldModel*/, // #309 void(string modelname) R_LoadWorldModel +// TODO: rearrange and merge all builtin lists and share as many extensions as possible between all VM instances [1/27/2008 Andreas] +VM_CL_setattachment, // #310 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) (DP_QC_RENDER_SCENE) +VM_CL_gettagindex, // #311 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) (DP_QC_RENDER_SCENE) +VM_CL_gettaginfo, // #312 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) (DP_QC_RENDER_SCENE) +#else +// CSQC range #300-#399 +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +#endif +NULL, // #313 +NULL, // #314 +NULL, // #315 +NULL, // #316 +NULL, // #317 +NULL, // #318 +NULL, // #319 +NULL, // #320 +NULL, // #321 +NULL, // #322 +NULL, // #323 +NULL, // #324 +NULL, // #325 +NULL, // #326 +NULL, // #327 +NULL, // #328 +NULL, // #329 +NULL, // #330 +NULL, // #331 +NULL, // #332 +NULL, // #333 +NULL, // #334 +NULL, // #335 +NULL, // #336 +NULL, // #337 +NULL, // #338 +NULL, // #339 +VM_keynumtostring, // #340 string keynumtostring(float keynum) +VM_stringtokeynum, // #341 float stringtokeynum(string key) +VM_getkeybind, // #342 string(float keynum[, float bindmap]) getkeybind (EXT_CSQC) +NULL, // #343 +NULL, // #344 +NULL, // #345 +NULL, // #346 +NULL, // #347 +NULL, // #348 +VM_CL_isdemo, // #349 +NULL, // #350 +NULL, // #351 +NULL, // #352 +VM_wasfreed, // #353 float(entity ent) wasfreed +NULL, // #354 +VM_CL_videoplaying, // #355 +VM_findfont, // #356 float(string fontname) loadfont (DP_GFX_FONTS) +VM_loadfont, // #357 float(string fontname, string fontmaps, string sizes, float slot) loadfont (DP_GFX_FONTS) +NULL, // #358 +NULL, // #359 +NULL, // #360 +NULL, // #361 +NULL, // #362 +NULL, // #363 +NULL, // #364 +NULL, // #365 +NULL, // #366 +NULL, // #367 +NULL, // #368 +NULL, // #369 +NULL, // #370 +NULL, // #371 +NULL, // #372 +NULL, // #373 +NULL, // #374 +NULL, // #375 +NULL, // #376 +NULL, // #377 +NULL, // #378 +NULL, // #379 +NULL, // #380 +NULL, // #381 +NULL, // #382 +NULL, // #383 +NULL, // #384 +NULL, // #385 +NULL, // #386 +NULL, // #387 +NULL, // #388 +NULL, // #389 +NULL, // #390 +NULL, // #391 +NULL, // #392 +NULL, // #393 +NULL, // #394 +NULL, // #395 +NULL, // #396 +NULL, // #397 +NULL, // #398 +NULL, // #399 +NULL, // #400 +VM_M_WriteByte, // #401 +VM_M_WriteChar, // #402 +VM_M_WriteShort, // #403 +VM_M_WriteLong, // #404 +VM_M_WriteAngle, // #405 +VM_M_WriteCoord, // #406 +VM_M_WriteString, // #407 +VM_M_WriteEntity, // #408 +NULL, // #409 +NULL, // #410 +NULL, // #411 +NULL, // #412 +NULL, // #413 +NULL, // #414 +NULL, // #415 +NULL, // #416 +NULL, // #417 +NULL, // #418 +NULL, // #419 +NULL, // #420 +NULL, // #421 +NULL, // #422 +NULL, // #423 +NULL, // #424 +NULL, // #425 +NULL, // #426 +NULL, // #427 +NULL, // #428 +NULL, // #429 +NULL, // #430 +NULL, // #431 +NULL, // #432 +NULL, // #433 +NULL, // #434 +NULL, // #435 +NULL, // #436 +NULL, // #437 +NULL, // #438 +NULL, // #439 +VM_buf_create, // #440 float() buf_create (DP_QC_STRINGBUFFERS) +VM_buf_del, // #441 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) +VM_buf_getsize, // #442 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) +VM_buf_copy, // #443 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) +VM_buf_sort, // #444 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) +VM_buf_implode, // #445 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) +VM_bufstr_get, // #446 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) +VM_bufstr_set, // #447 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) +VM_bufstr_add, // #448 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) +VM_bufstr_free, // #449 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) +NULL, // #450 +VM_iscachedpic, // #451 draw functions... +VM_precache_pic, // #452 +VM_freepic, // #453 +VM_drawcharacter, // #454 +VM_drawstring, // #455 +VM_drawpic, // #456 +VM_drawfill, // #457 +VM_drawsetcliparea, // #458 +VM_drawresetcliparea, // #459 +VM_getimagesize, // #460 +VM_cin_open, // #461 +VM_cin_close, // #462 +VM_cin_setstate, // #463 +VM_cin_getstate, // #464 +VM_cin_restart, // #465 +VM_drawline, // #466 +VM_drawcolorcodedstring, // #467 +VM_stringwidth, // #468 +VM_drawsubpic, // #469 +VM_drawrotpic, // #470 +VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) +VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) +VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) +VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) +VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) +VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) +VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_QC_STRINGCOLORFUNCTIONS) +VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) +VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) +VM_strtolower, // #480 string(string s) VM_strtolower : DRESK - Return string as lowercase +VM_strtoupper, // #481 string(string s) VM_strtoupper : DRESK - Return string as uppercase +NULL, // #482 +NULL, // #483 +VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) +VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) +NULL, // #486 +VM_gecko_create, // #487 float gecko_create( string name ) +VM_gecko_destroy, // #488 void gecko_destroy( string name ) +VM_gecko_navigate, // #489 void gecko_navigate( string name, string URI ) +VM_gecko_keyevent, // #490 float gecko_keyevent( string name, float key, float eventtype ) +VM_gecko_movemouse, // #491 void gecko_mousemove( string name, float x, float y ) +VM_gecko_resize, // #492 void gecko_resize( string name, float w, float h ) +VM_gecko_get_texture_extent, // #493 vector gecko_get_texture_extent( string name ) +VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) +VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) +NULL, // #496 +NULL, // #497 +NULL, // #498 +NULL, // #499 +NULL, // #500 +NULL, // #501 +NULL, // #502 +VM_whichpack, // #503 string(string) whichpack = #503; +NULL, // #504 +NULL, // #505 +NULL, // #506 +NULL, // #507 +NULL, // #508 +NULL, // #509 +VM_uri_escape, // #510 string(string in) uri_escape = #510; +VM_uri_unescape, // #511 string(string in) uri_unescape = #511; +VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) +VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) +VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) +VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) +VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) +NULL, // #519 +NULL, // #520 +NULL, // #521 +NULL, // #522 +NULL, // #523 +NULL, // #524 +NULL, // #525 +NULL, // #526 +NULL, // #527 +NULL, // #528 +NULL, // #529 +NULL, // #530 +NULL, // #531 +VM_log, // #532 +VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) +VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) +NULL, // #535 +NULL, // #536 +NULL, // #537 +NULL, // #538 +NULL, // #539 +NULL, // #540 +NULL, // #541 +NULL, // #542 +NULL, // #543 +NULL, // #544 +NULL, // #545 +NULL, // #546 +NULL, // #547 +NULL, // #548 +NULL, // #549 +NULL, // #550 +NULL, // #551 +NULL, // #552 +NULL, // #553 +NULL, // #554 +NULL, // #555 +NULL, // #556 +NULL, // #557 +NULL, // #558 +NULL, // #559 +NULL, // #560 +NULL, // #561 +NULL, // #562 +NULL, // #563 +NULL, // #564 +NULL, // #565 +NULL, // #566 +NULL, // #567 +NULL, // #568 +NULL, // #569 +NULL, // #570 +NULL, // #571 +NULL, // #572 +NULL, // #573 +NULL, // #574 +NULL, // #575 +NULL, // #576 +NULL, // #577 +NULL, // #578 +NULL, // #579 +NULL, // #580 +NULL, // #581 +NULL, // #582 +NULL, // #583 +NULL, // #584 +NULL, // #585 +NULL, // #586 +NULL, // #587 +NULL, // #588 +NULL, // #589 +NULL, // #590 +NULL, // #591 +NULL, // #592 +NULL, // #593 +NULL, // #594 +NULL, // #595 +NULL, // #596 +NULL, // #597 +NULL, // #598 +NULL, // #599 +NULL, // #600 +VM_M_setkeydest, // #601 void setkeydest(float dest) +VM_M_getkeydest, // #602 float getkeydest(void) +VM_M_setmousetarget, // #603 void setmousetarget(float trg) +VM_M_getmousetarget, // #604 float getmousetarget(void) +VM_callfunction, // #605 void callfunction(...) +VM_writetofile, // #606 void writetofile(float fhandle, entity ent) +VM_isfunction, // #607 float isfunction(string function_name) +VM_M_getresolution, // #608 vector getresolution(float number, [float forfullscreen]) +VM_keynumtostring, // #609 string keynumtostring(float keynum) +VM_findkeysforcommand, // #610 string findkeysforcommand(string command[, float bindmap]) +VM_M_getserverliststat, // #611 float gethostcachevalue(float type) +VM_M_getserverliststring, // #612 string gethostcachestring(float type, float hostnr) +VM_parseentitydata, // #613 void parseentitydata(entity ent, string data) +VM_stringtokeynum, // #614 float stringtokeynum(string key) +VM_M_resetserverlistmasks, // #615 void resethostcachemasks(void) +VM_M_setserverlistmaskstring, // #616 void sethostcachemaskstring(float mask, float fld, string str, float op) +VM_M_setserverlistmasknumber, // #617 void sethostcachemasknumber(float mask, float fld, float num, float op) +VM_M_resortserverlist, // #618 void resorthostcache(void) +VM_M_setserverlistsort, // #619 void sethostcachesort(float fld, float descending) +VM_M_refreshserverlist, // #620 void refreshhostcache(void) +VM_M_getserverlistnumber, // #621 float gethostcachenumber(float fld, float hostnr) +VM_M_getserverlistindexforkey,// #622 float gethostcacheindexforkey(string key) +VM_M_addwantedserverlistkey, // #623 void addwantedhostcachekey(string key) +VM_CL_getextresponse, // #624 string getextresponse(void) +VM_netaddress_resolve, // #625 string netaddress_resolve(string, float) +VM_M_getgamedirinfo, // #626 string getgamedirinfo(float n, float prop) +VM_sprintf, // #627 string sprintf(string format, ...) +NULL, // #628 +NULL, // #629 +VM_setkeybind, // #630 float(float key, string bind[, float bindmap]) setkeybind +VM_getbindmaps, // #631 vector(void) getbindmap +VM_setbindmaps, // #632 float(vector bm) setbindmap +VM_M_crypto_getkeyfp, // #633 string(string addr) crypto_getkeyfp +VM_M_crypto_getidfp, // #634 string(string addr) crypto_getidfp +VM_M_crypto_getencryptlevel, // #635 string(string addr) crypto_getencryptlevel +VM_M_crypto_getmykeyfp, // #636 string(float addr) crypto_getmykeyfp +VM_M_crypto_getmyidfp, // #637 string(float addr) crypto_getmyidfp +NULL +}; + +const int vm_m_numbuiltins = sizeof(vm_m_builtins) / sizeof(prvm_builtin_t); + +void VM_M_Cmd_Init(void) +{ + r_refdef_scene_t *scene; + + VM_Cmd_Init(); + VM_Polygons_Reset(); + + scene = R_GetScenePointer( RST_MENU ); + + memset (scene, 0, sizeof (*scene)); + + scene->maxtempentities = 128; + scene->tempentities = (entity_render_t*) Mem_Alloc(prog->progs_mempool, sizeof(entity_render_t) * scene->maxtempentities); + + scene->maxentities = MAX_EDICTS + 256 + 512; + scene->entities = (entity_render_t **)Mem_Alloc(prog->progs_mempool, sizeof(entity_render_t *) * scene->maxentities); + + scene->ambient = 32.0f; +} + +void VM_M_Cmd_Reset(void) +{ + // note: the menu's render entities are automatically freed when the prog's pool is freed + + //VM_Cmd_Init(); + VM_Cmd_Reset(); + VM_Polygons_Reset(); +} diff --git a/misc/source/darkplaces-src/netconn.c b/misc/source/darkplaces-src/netconn.c new file mode 100644 index 00000000..65b64830 --- /dev/null +++ b/misc/source/darkplaces-src/netconn.c @@ -0,0 +1,3666 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2002 Mathieu Olivier +Copyright (C) 2003 Forest Hale + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "lhnet.h" + +// for secure rcon authentication +#include "hmac.h" +#include "mdfour.h" +#include + +#define QWMASTER_PORT 27000 +#define DPMASTER_PORT 27950 + +// note this defaults on for dedicated servers, off for listen servers +cvar_t sv_public = {0, "sv_public", "0", "1: advertises this server on the master server (so that players can find it in the server browser); 0: allow direct queries only; -1: do not respond to direct queries; -2: do not allow anyone to connect; -3: already block at getchallenge level"}; +cvar_t sv_public_rejectreason = {0, "sv_public_rejectreason", "The server is closing.", "Rejection reason for connects when sv_public is -2"}; +static cvar_t sv_heartbeatperiod = {CVAR_SAVE, "sv_heartbeatperiod", "120", "how often to send heartbeat in seconds (only used if sv_public is 1)"}; +extern cvar_t sv_status_privacy; + +static cvar_t sv_masters [] = +{ + {CVAR_SAVE, "sv_master1", "", "user-chosen master server 1"}, + {CVAR_SAVE, "sv_master2", "", "user-chosen master server 2"}, + {CVAR_SAVE, "sv_master3", "", "user-chosen master server 3"}, + {CVAR_SAVE, "sv_master4", "", "user-chosen master server 4"}, + {0, "sv_masterextra1", "69.59.212.88", "ghdigital.com - default master server 1 (admin: LordHavoc)"}, // admin: LordHavoc + {0, "sv_masterextra2", "64.22.107.125", "dpmaster.deathmask.net - default master server 2 (admin: Willis)"}, // admin: Willis + {0, "sv_masterextra3", "92.62.40.73", "dpmaster.tchr.no - default master server 3 (admin: tChr)"}, // admin: tChr +#ifdef SUPPORTIPV6 + {0, "sv_masterextra4", "[2001:41d0:2:1628::4450]:27950", "dpmaster.div0.qc.to - default master server 4 (admin: divVerent)"}, // admin: divVerent +#endif + {0, NULL, NULL, NULL} +}; + +static cvar_t sv_qwmasters [] = +{ + {CVAR_SAVE, "sv_qwmaster1", "", "user-chosen qwmaster server 1"}, + {CVAR_SAVE, "sv_qwmaster2", "", "user-chosen qwmaster server 2"}, + {CVAR_SAVE, "sv_qwmaster3", "", "user-chosen qwmaster server 3"}, + {CVAR_SAVE, "sv_qwmaster4", "", "user-chosen qwmaster server 4"}, + {0, "sv_qwmasterextra1", "master.quakeservers.net:27000", "Global master server. (admin: unknown)"}, + {0, "sv_qwmasterextra2", "asgaard.morphos-team.net:27000", "Global master server. (admin: unknown)"}, + {0, "sv_qwmasterextra3", "qwmaster.ocrana.de:27000", "German master server. (admin: unknown)"}, + {0, "sv_qwmasterextra4", "masterserver.exhale.de:27000", "German master server. (admin: unknown)"}, + {0, "sv_qwmasterextra5", "kubus.rulez.pl:27000", "Poland master server. (admin: unknown)"}, + {0, NULL, NULL, NULL} +}; + +static double nextheartbeattime = 0; + +sizebuf_t net_message; +static unsigned char net_message_buf[NET_MAXMESSAGE]; + +cvar_t net_messagetimeout = {0, "net_messagetimeout","300", "drops players who have not sent any packets for this many seconds"}; +cvar_t net_connecttimeout = {0, "net_connecttimeout","15", "after requesting a connection, the client must reply within this many seconds or be dropped (cuts down on connect floods). Must be above 10 seconds."}; +cvar_t net_connectfloodblockingtimeout = {0, "net_connectfloodblockingtimeout", "5", "when a connection packet is received, it will block all future connect packets from that IP address for this many seconds (cuts down on connect floods)"}; +cvar_t hostname = {CVAR_SAVE, "hostname", "UNNAMED", "server message to show in server browser"}; +cvar_t developer_networking = {0, "developer_networking", "0", "prints all received and sent packets (recommended only for debugging)"}; + +cvar_t cl_netlocalping = {0, "cl_netlocalping","0", "lags local loopback connection by this much ping time (useful to play more fairly on your own server with people with higher pings)"}; +static cvar_t cl_netpacketloss_send = {0, "cl_netpacketloss_send","0", "drops this percentage of outgoing packets, useful for testing network protocol robustness (jerky movement, prediction errors, etc)"}; +static cvar_t cl_netpacketloss_receive = {0, "cl_netpacketloss_receive","0", "drops this percentage of incoming packets, useful for testing network protocol robustness (jerky movement, effects failing to start, sounds failing to play, etc)"}; +static cvar_t net_slist_queriespersecond = {0, "net_slist_queriespersecond", "20", "how many server information requests to send per second"}; +static cvar_t net_slist_queriesperframe = {0, "net_slist_queriesperframe", "4", "maximum number of server information requests to send each rendered frame (guards against low framerates causing problems)"}; +static cvar_t net_slist_timeout = {0, "net_slist_timeout", "4", "how long to listen for a server information response before giving up"}; +static cvar_t net_slist_pause = {0, "net_slist_pause", "0", "when set to 1, the server list won't update until it is set back to 0"}; +static cvar_t net_slist_maxtries = {0, "net_slist_maxtries", "3", "how many times to ask the same server for information (more times gives better ping reports but takes longer)"}; +static cvar_t net_slist_favorites = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "net_slist_favorites", "", "contains a list of IP addresses and ports to always query explicitly"}; +static cvar_t gameversion = {0, "gameversion", "0", "version of game data (mod-specific) to be sent to querying clients"}; +static cvar_t gameversion_min = {0, "gameversion_min", "-1", "minimum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"}; +static cvar_t gameversion_max = {0, "gameversion_max", "-1", "maximum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"}; +static cvar_t rcon_restricted_password = {CVAR_PRIVATE, "rcon_restricted_password", "", "password to authenticate rcon commands in restricted mode; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; +static cvar_t rcon_restricted_commands = {0, "rcon_restricted_commands", "", "allowed commands for rcon when the restricted mode password was used"}; +static cvar_t rcon_secure_maxdiff = {0, "rcon_secure_maxdiff", "5", "maximum time difference between rcon request and server system clock (to protect against replay attack)"}; +extern cvar_t rcon_secure; +extern cvar_t rcon_secure_challengetimeout; + +/* statistic counters */ +static int packetsSent = 0; +static int packetsReSent = 0; +static int packetsReceived = 0; +static int receivedDuplicateCount = 0; +static int droppedDatagrams = 0; + +static int unreliableMessagesSent = 0; +static int unreliableMessagesReceived = 0; +static int reliableMessagesSent = 0; +static int reliableMessagesReceived = 0; + +double masterquerytime = -1000; +int masterquerycount = 0; +int masterreplycount = 0; +int serverquerycount = 0; +int serverreplycount = 0; + +challenge_t challenge[MAX_CHALLENGES]; + +/// this is only false if there are still servers left to query +static qboolean serverlist_querysleep = true; +static qboolean serverlist_paused = false; +/// this is pushed a second or two ahead of realtime whenever a master server +/// reply is received, to avoid issuing queries while master replies are still +/// flooding in (which would make a mess of the ping times) +static double serverlist_querywaittime = 0; + +static unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; +static unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; +static unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; +static unsigned char cryptoreadbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + +static int cl_numsockets; +static lhnetsocket_t *cl_sockets[16]; +static int sv_numsockets; +static lhnetsocket_t *sv_sockets[16]; + +netconn_t *netconn_list = NULL; +mempool_t *netconn_mempool = NULL; + +cvar_t cl_netport = {0, "cl_port", "0", "forces client to use chosen port number if not 0"}; +cvar_t sv_netport = {0, "port", "26000", "server port for players to connect to"}; +cvar_t net_address = {0, "net_address", "", "network address to open ipv4 ports on (if empty, use default interfaces)"}; +cvar_t net_address_ipv6 = {0, "net_address_ipv6", "", "network address to open ipv6 ports on (if empty, use default interfaces)"}; + +char cl_net_extresponse[NET_EXTRESPONSE_MAX][1400]; +int cl_net_extresponse_count = 0; +int cl_net_extresponse_last = 0; + +char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400]; +int sv_net_extresponse_count = 0; +int sv_net_extresponse_last = 0; + +// ServerList interface +serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT]; +serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT]; + +serverlist_infofield_t serverlist_sortbyfield; +int serverlist_sortflags; + +int serverlist_viewcount = 0; +unsigned short serverlist_viewlist[SERVERLIST_VIEWLISTSIZE]; + +int serverlist_maxcachecount = 0; +int serverlist_cachecount = 0; +serverlist_entry_t *serverlist_cache = NULL; + +qboolean serverlist_consoleoutput; + +static int nFavorites = 0; +static lhnetaddress_t favorites[MAX_FAVORITESERVERS]; +static int nFavorites_idfp = 0; +static char favorites_idfp[MAX_FAVORITESERVERS][FP64_SIZE+1]; + +void NetConn_UpdateFavorites(void) +{ + const char *p; + nFavorites = 0; + nFavorites_idfp = 0; + p = net_slist_favorites.string; + while((size_t) nFavorites < sizeof(favorites) / sizeof(*favorites) && COM_ParseToken_Console(&p)) + { + if(com_token[0] != '[' && strlen(com_token) == FP64_SIZE && !strchr(com_token, '.')) + // currently 44 bytes, longest possible IPv6 address: 39 bytes, so this works + // (if v6 address contains port, it must start with '[') + { + strlcpy(favorites_idfp[nFavorites_idfp], com_token, sizeof(favorites_idfp[nFavorites_idfp])); + ++nFavorites_idfp; + } + else + { + if(LHNETADDRESS_FromString(&favorites[nFavorites], com_token, 26000)) + ++nFavorites; + } + } +} + +/// helper function to insert a value into the viewset +/// spare entries will be removed +static void _ServerList_ViewList_Helper_InsertBefore( int index, serverlist_entry_t *entry ) +{ + int i; + if( serverlist_viewcount < SERVERLIST_VIEWLISTSIZE ) { + i = serverlist_viewcount++; + } else { + i = SERVERLIST_VIEWLISTSIZE - 1; + } + + for( ; i > index ; i-- ) + serverlist_viewlist[ i ] = serverlist_viewlist[ i - 1 ]; + + serverlist_viewlist[index] = (int)(entry - serverlist_cache); +} + +/// we suppose serverlist_viewcount to be valid, ie > 0 +static void _ServerList_ViewList_Helper_Remove( int index ) +{ + serverlist_viewcount--; + for( ; index < serverlist_viewcount ; index++ ) + serverlist_viewlist[index] = serverlist_viewlist[index + 1]; +} + +/// \returns true if A should be inserted before B +static qboolean _ServerList_Entry_Compare( serverlist_entry_t *A, serverlist_entry_t *B ) +{ + int result = 0; // > 0 if for numbers A > B and for text if A < B + + if( serverlist_sortflags & SLSF_FAVORITESFIRST ) + { + if(A->info.isfavorite != B->info.isfavorite) + return A->info.isfavorite; + } + + switch( serverlist_sortbyfield ) { + case SLIF_PING: + result = A->info.ping - B->info.ping; + break; + case SLIF_MAXPLAYERS: + result = A->info.maxplayers - B->info.maxplayers; + break; + case SLIF_NUMPLAYERS: + result = A->info.numplayers - B->info.numplayers; + break; + case SLIF_NUMBOTS: + result = A->info.numbots - B->info.numbots; + break; + case SLIF_NUMHUMANS: + result = A->info.numhumans - B->info.numhumans; + break; + case SLIF_FREESLOTS: + result = A->info.freeslots - B->info.freeslots; + break; + case SLIF_PROTOCOL: + result = A->info.protocol - B->info.protocol; + break; + case SLIF_CNAME: + result = strcmp( B->info.cname, A->info.cname ); + break; + case SLIF_GAME: + result = strcasecmp( B->info.game, A->info.game ); + break; + case SLIF_MAP: + result = strcasecmp( B->info.map, A->info.map ); + break; + case SLIF_MOD: + result = strcasecmp( B->info.mod, A->info.mod ); + break; + case SLIF_NAME: + result = strcasecmp( B->info.name, A->info.name ); + break; + case SLIF_QCSTATUS: + result = strcasecmp( B->info.qcstatus, A->info.qcstatus ); // not really THAT useful, though + break; + case SLIF_ISFAVORITE: + result = !!B->info.isfavorite - !!A->info.isfavorite; + break; + default: + Con_DPrint( "_ServerList_Entry_Compare: Bad serverlist_sortbyfield!\n" ); + break; + } + + if (result != 0) + { + if( serverlist_sortflags & SLSF_DESCENDING ) + return result > 0; + else + return result < 0; + } + + // if the chosen sort key is identical, sort by index + // (makes this a stable sort, so that later replies from servers won't + // shuffle the servers around when they have the same ping) + return A < B; +} + +static qboolean _ServerList_CompareInt( int A, serverlist_maskop_t op, int B ) +{ + // This should actually be done with some intermediate and end-of-function return + switch( op ) { + case SLMO_LESS: + return A < B; + case SLMO_LESSEQUAL: + return A <= B; + case SLMO_EQUAL: + return A == B; + case SLMO_GREATER: + return A > B; + case SLMO_NOTEQUAL: + return A != B; + case SLMO_GREATEREQUAL: + case SLMO_CONTAINS: + case SLMO_NOTCONTAIN: + case SLMO_STARTSWITH: + case SLMO_NOTSTARTSWITH: + return A >= B; + default: + Con_DPrint( "_ServerList_CompareInt: Bad op!\n" ); + return false; + } +} + +static qboolean _ServerList_CompareStr( const char *A, serverlist_maskop_t op, const char *B ) +{ + int i; + char bufferA[ 1400 ], bufferB[ 1400 ]; // should be more than enough + COM_StringDecolorize(A, 0, bufferA, sizeof(bufferA), false); + for (i = 0;i < (int)sizeof(bufferA)-1 && bufferA[i];i++) + bufferA[i] = (bufferA[i] >= 'A' && bufferA[i] <= 'Z') ? (bufferA[i] + 'a' - 'A') : bufferA[i]; + bufferA[i] = 0; + for (i = 0;i < (int)sizeof(bufferB)-1 && B[i];i++) + bufferB[i] = (B[i] >= 'A' && B[i] <= 'Z') ? (B[i] + 'a' - 'A') : B[i]; + bufferB[i] = 0; + + // Same here, also using an intermediate & final return would be more appropriate + // A info B mask + switch( op ) { + case SLMO_CONTAINS: + return *bufferB && !!strstr( bufferA, bufferB ); // we want a real bool + case SLMO_NOTCONTAIN: + return !*bufferB || !strstr( bufferA, bufferB ); + case SLMO_STARTSWITH: + //Con_Printf("startsWith: %s %s\n", bufferA, bufferB); + return *bufferB && !memcmp(bufferA, bufferB, strlen(bufferB)); + case SLMO_NOTSTARTSWITH: + return !*bufferB || memcmp(bufferA, bufferB, strlen(bufferB)); + case SLMO_LESS: + return strcmp( bufferA, bufferB ) < 0; + case SLMO_LESSEQUAL: + return strcmp( bufferA, bufferB ) <= 0; + case SLMO_EQUAL: + return strcmp( bufferA, bufferB ) == 0; + case SLMO_GREATER: + return strcmp( bufferA, bufferB ) > 0; + case SLMO_NOTEQUAL: + return strcmp( bufferA, bufferB ) != 0; + case SLMO_GREATEREQUAL: + return strcmp( bufferA, bufferB ) >= 0; + default: + Con_DPrint( "_ServerList_CompareStr: Bad op!\n" ); + return false; + } +} + +static qboolean _ServerList_Entry_Mask( serverlist_mask_t *mask, serverlist_info_t *info ) +{ + if( !_ServerList_CompareInt( info->ping, mask->tests[SLIF_PING], mask->info.ping ) ) + return false; + if( !_ServerList_CompareInt( info->maxplayers, mask->tests[SLIF_MAXPLAYERS], mask->info.maxplayers ) ) + return false; + if( !_ServerList_CompareInt( info->numplayers, mask->tests[SLIF_NUMPLAYERS], mask->info.numplayers ) ) + return false; + if( !_ServerList_CompareInt( info->numbots, mask->tests[SLIF_NUMBOTS], mask->info.numbots ) ) + return false; + if( !_ServerList_CompareInt( info->numhumans, mask->tests[SLIF_NUMHUMANS], mask->info.numhumans ) ) + return false; + if( !_ServerList_CompareInt( info->freeslots, mask->tests[SLIF_FREESLOTS], mask->info.freeslots ) ) + return false; + if( !_ServerList_CompareInt( info->protocol, mask->tests[SLIF_PROTOCOL], mask->info.protocol )) + return false; + if( *mask->info.cname + && !_ServerList_CompareStr( info->cname, mask->tests[SLIF_CNAME], mask->info.cname ) ) + return false; + if( *mask->info.game + && !_ServerList_CompareStr( info->game, mask->tests[SLIF_GAME], mask->info.game ) ) + return false; + if( *mask->info.mod + && !_ServerList_CompareStr( info->mod, mask->tests[SLIF_MOD], mask->info.mod ) ) + return false; + if( *mask->info.map + && !_ServerList_CompareStr( info->map, mask->tests[SLIF_MAP], mask->info.map ) ) + return false; + if( *mask->info.name + && !_ServerList_CompareStr( info->name, mask->tests[SLIF_NAME], mask->info.name ) ) + return false; + if( *mask->info.qcstatus + && !_ServerList_CompareStr( info->qcstatus, mask->tests[SLIF_QCSTATUS], mask->info.qcstatus ) ) + return false; + if( *mask->info.players + && !_ServerList_CompareStr( info->players, mask->tests[SLIF_PLAYERS], mask->info.players ) ) + return false; + if( !_ServerList_CompareInt( info->isfavorite, mask->tests[SLIF_ISFAVORITE], mask->info.isfavorite )) + return false; + return true; +} + +static void ServerList_ViewList_Insert( serverlist_entry_t *entry ) +{ + int start, end, mid, i; + lhnetaddress_t addr; + + // reject incompatible servers + if( + entry->info.gameversion != gameversion.integer + && + !( + gameversion_min.integer >= 0 // min/max range set by user/mod? + && gameversion_max.integer >= 0 + && gameversion_min.integer >= entry->info.gameversion // version of server in min/max range? + && gameversion_max.integer <= entry->info.gameversion + ) + ) + return; + + // refresh the "favorite" status + entry->info.isfavorite = false; + if(LHNETADDRESS_FromString(&addr, entry->info.cname, 26000)) + { + char idfp[FP64_SIZE+1]; + for(i = 0; i < nFavorites; ++i) + { + if(LHNETADDRESS_Compare(&addr, &favorites[i]) == 0) + { + entry->info.isfavorite = true; + break; + } + } + if(Crypto_RetrieveHostKey(&addr, 0, NULL, 0, idfp, sizeof(idfp), NULL)) + { + for(i = 0; i < nFavorites_idfp; ++i) + { + if(!strcmp(idfp, favorites_idfp[i])) + { + entry->info.isfavorite = true; + break; + } + } + } + } + + // FIXME: change this to be more readable (...) + // now check whether it passes through the masks + for( start = 0 ; start < SERVERLIST_ANDMASKCOUNT && serverlist_andmasks[start].active; start++ ) + if( !_ServerList_Entry_Mask( &serverlist_andmasks[start], &entry->info ) ) + return; + + for( start = 0 ; start < SERVERLIST_ORMASKCOUNT && serverlist_ormasks[start].active ; start++ ) + if( _ServerList_Entry_Mask( &serverlist_ormasks[start], &entry->info ) ) + break; + if( start == SERVERLIST_ORMASKCOUNT || (start > 0 && !serverlist_ormasks[start].active) ) + return; + + if( !serverlist_viewcount ) { + _ServerList_ViewList_Helper_InsertBefore( 0, entry ); + return; + } + // ok, insert it, we just need to find out where exactly: + + // two special cases + // check whether to insert it as new first item + if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(0) ) ) { + _ServerList_ViewList_Helper_InsertBefore( 0, entry ); + return; + } // check whether to insert it as new last item + else if( !_ServerList_Entry_Compare( entry, ServerList_GetViewEntry(serverlist_viewcount - 1) ) ) { + _ServerList_ViewList_Helper_InsertBefore( serverlist_viewcount, entry ); + return; + } + start = 0; + end = serverlist_viewcount - 1; + while( end > start + 1 ) + { + mid = (start + end) / 2; + // test the item that lies in the middle between start and end + if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(mid) ) ) + // the item has to be in the upper half + end = mid; + else + // the item has to be in the lower half + start = mid; + } + _ServerList_ViewList_Helper_InsertBefore( start + 1, entry ); +} + +static void ServerList_ViewList_Remove( serverlist_entry_t *entry ) +{ + int i; + for( i = 0; i < serverlist_viewcount; i++ ) + { + if (ServerList_GetViewEntry(i) == entry) + { + _ServerList_ViewList_Helper_Remove(i); + break; + } + } +} + +void ServerList_RebuildViewList(void) +{ + int i; + + serverlist_viewcount = 0; + for( i = 0 ; i < serverlist_cachecount ; i++ ) { + serverlist_entry_t *entry = &serverlist_cache[i]; + // also display entries that are currently being refreshed [11/8/2007 Black] + if( entry->query == SQS_QUERIED || entry->query == SQS_REFRESHING ) + ServerList_ViewList_Insert( entry ); + } +} + +void ServerList_ResetMasks(void) +{ + int i; + + memset( &serverlist_andmasks, 0, sizeof( serverlist_andmasks ) ); + memset( &serverlist_ormasks, 0, sizeof( serverlist_ormasks ) ); + // numbots needs to be compared to -1 to always succeed + for(i = 0; i < SERVERLIST_ANDMASKCOUNT; ++i) + serverlist_andmasks[i].info.numbots = -1; + for(i = 0; i < SERVERLIST_ORMASKCOUNT; ++i) + serverlist_ormasks[i].info.numbots = -1; +} + +void ServerList_GetPlayerStatistics(int *numplayerspointer, int *maxplayerspointer) +{ + int i; + int numplayers = 0, maxplayers = 0; + for (i = 0;i < serverlist_cachecount;i++) + { + if (serverlist_cache[i].query == SQS_QUERIED) + { + numplayers += serverlist_cache[i].info.numhumans; + maxplayers += serverlist_cache[i].info.maxplayers; + } + } + *numplayerspointer = numplayers; + *maxplayerspointer = maxplayers; +} + +#if 0 +static void _ServerList_Test(void) +{ + int i; + if (serverlist_maxcachecount <= 1024) + { + serverlist_maxcachecount = 1024; + serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); + } + for( i = 0 ; i < 1024 ; i++ ) { + memset( &serverlist_cache[serverlist_cachecount], 0, sizeof( serverlist_entry_t ) ); + serverlist_cache[serverlist_cachecount].info.ping = 1000 + 1024 - i; + dpsnprintf( serverlist_cache[serverlist_cachecount].info.name, sizeof(serverlist_cache[serverlist_cachecount].info.name), "Black's ServerList Test %i", i ); + serverlist_cache[serverlist_cachecount].finished = true; + dpsnprintf( serverlist_cache[serverlist_cachecount].line1, sizeof(serverlist_cache[serverlist_cachecount].info.line1), "%i %s", serverlist_cache[serverlist_cachecount].info.ping, serverlist_cache[serverlist_cachecount].info.name ); + ServerList_ViewList_Insert( &serverlist_cache[serverlist_cachecount] ); + serverlist_cachecount++; + } +} +#endif + +void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryqw, qboolean consoleoutput) +{ + masterquerytime = realtime; + masterquerycount = 0; + masterreplycount = 0; + if( resetcache ) { + serverquerycount = 0; + serverreplycount = 0; + serverlist_cachecount = 0; + serverlist_viewcount = 0; + serverlist_maxcachecount = 0; + serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); + } else { + // refresh all entries + int n; + for( n = 0 ; n < serverlist_cachecount ; n++ ) { + serverlist_entry_t *entry = &serverlist_cache[ n ]; + entry->query = SQS_REFRESHING; + entry->querycounter = 0; + } + } + serverlist_consoleoutput = consoleoutput; + + //_ServerList_Test(); + + NetConn_QueryMasters(querydp, queryqw); +} + +// rest + +int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress) +{ + int length = LHNET_Read(mysocket, data, maxlength, peeraddress); + int i; + if (length == 0) + return 0; + if (cl_netpacketloss_receive.integer) + for (i = 0;i < cl_numsockets;i++) + if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_receive.integer) + return 0; + if (developer_networking.integer) + { + char addressstring[128], addressstring2[128]; + LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true); + if (length > 0) + { + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i from %s:\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length, addressstring2); + Com_HexDumpToConsole((unsigned char *)data, length); + } + else + Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length); + } + return length; +} + +int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress) +{ + int ret; + int i; + if (cl_netpacketloss_send.integer) + for (i = 0;i < cl_numsockets;i++) + if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_send.integer) + return length; + ret = LHNET_Write(mysocket, data, length, peeraddress); + if (developer_networking.integer) + { + char addressstring[128], addressstring2[128]; + LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true); + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + Con_Printf("LHNET_Write(%p (%s), %p, %i, %p (%s)) = %i%s\n", (void *)mysocket, addressstring, (void *)data, length, (void *)peeraddress, addressstring2, length, ret == length ? "" : " (ERROR)"); + Com_HexDumpToConsole((unsigned char *)data, length); + } + return ret; +} + +int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress) +{ + // note this does not include the trailing NULL because we add that in the parser + return NetConn_Write(mysocket, string, (int)strlen(string), peeraddress); +} + +qboolean NetConn_CanSend(netconn_t *conn) +{ + conn->outgoing_packetcounter = (conn->outgoing_packetcounter + 1) % NETGRAPH_PACKETS; + conn->outgoing_netgraph[conn->outgoing_packetcounter].time = realtime; + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET; + conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes = NETGRAPH_NOPACKET; + if (realtime > conn->cleartime) + return true; + else + { + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_CHOKEDPACKET; + return false; + } +} + +int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, qboolean quakesignon_suppressreliables) +{ + int totallen = 0; + + // if this packet was supposedly choked, but we find ourselves sending one + // anyway, make sure the size counting starts at zero + // (this mostly happens on level changes and disconnects and such) + if (conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes == NETGRAPH_CHOKEDPACKET) + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET; + + if (protocol == PROTOCOL_QUAKEWORLD) + { + int packetLen; + qboolean sendreliable; + + // note that it is ok to send empty messages to the qw server, + // otherwise it won't respond to us at all + + sendreliable = false; + // if the remote side dropped the last reliable message, resend it + if (conn->qw.incoming_acknowledged > conn->qw.last_reliable_sequence && conn->qw.incoming_reliable_acknowledged != conn->qw.reliable_sequence) + sendreliable = true; + // if the reliable transmit buffer is empty, copy the current message out + if (!conn->sendMessageLength && conn->message.cursize) + { + memcpy (conn->sendMessage, conn->message.data, conn->message.cursize); + conn->sendMessageLength = conn->message.cursize; + SZ_Clear(&conn->message); // clear the message buffer + conn->qw.reliable_sequence ^= 1; + sendreliable = true; + } + // outgoing unreliable packet number, and outgoing reliable packet number (0 or 1) + StoreLittleLong(sendbuffer, (unsigned int)conn->outgoing_unreliable_sequence | ((unsigned int)sendreliable<<31)); + // last received unreliable packet number, and last received reliable packet number (0 or 1) + StoreLittleLong(sendbuffer + 4, (unsigned int)conn->qw.incoming_sequence | ((unsigned int)conn->qw.incoming_reliable_sequence<<31)); + packetLen = 8; + conn->outgoing_unreliable_sequence++; + // client sends qport in every packet + if (conn == cls.netcon) + { + *((short *)(sendbuffer + 8)) = LittleShort(cls.qw_qport); + packetLen += 2; + // also update cls.qw_outgoing_sequence + cls.qw_outgoing_sequence = conn->outgoing_unreliable_sequence; + } + if (packetLen + (sendreliable ? conn->sendMessageLength : 0) > 1400) + { + Con_Printf ("NetConn_SendUnreliableMessage: reliable message too big %u\n", data->cursize); + return -1; + } + + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28; + + // add the reliable message if there is one + if (sendreliable) + { + conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += conn->sendMessageLength + 28; + memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength); + packetLen += conn->sendMessageLength; + conn->qw.last_reliable_sequence = conn->outgoing_unreliable_sequence; + } + + // add the unreliable message if possible + if (packetLen + data->cursize <= 1400) + { + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += data->cursize + 28; + memcpy(sendbuffer + packetLen, data->data, data->cursize); + packetLen += data->cursize; + } + + NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + + packetsSent++; + unreliableMessagesSent++; + + totallen += packetLen + 28; + } + else + { + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + const void *sendme; + size_t sendmelen; + + // if a reliable message fragment has been lost, send it again + if (conn->sendMessageLength && (realtime - conn->lastSendTime) > 1.0) + { + if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + { + dataLen = conn->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_PACKETFRAGMENT; + eom = 0; + } + + packetLen = NET_HEADERSIZE + dataLen; + + StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom)); + StoreBigLong(sendbuffer + 4, conn->nq.sendSequence - 1); + memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); + + conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28; + + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if (sendme && NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress) == (int)sendmelen) + { + conn->lastSendTime = realtime; + packetsReSent++; + } + + totallen += sendmelen + 28; + } + + // if we have a new reliable message to send, do so + if (!conn->sendMessageLength && conn->message.cursize && !quakesignon_suppressreliables) + { + if (conn->message.cursize > (int)sizeof(conn->sendMessage)) + { + Con_Printf("NetConn_SendUnreliableMessage: reliable message too big (%u > %u)\n", conn->message.cursize, (int)sizeof(conn->sendMessage)); + conn->message.overflowed = true; + return -1; + } + + if (developer_networking.integer && conn == cls.netcon) + { + Con_Print("client sending reliable message to server:\n"); + SZ_HexDumpToConsole(&conn->message); + } + + memcpy(conn->sendMessage, conn->message.data, conn->message.cursize); + conn->sendMessageLength = conn->message.cursize; + SZ_Clear(&conn->message); + + if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + { + dataLen = conn->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_PACKETFRAGMENT; + eom = 0; + } + + packetLen = NET_HEADERSIZE + dataLen; + + StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom)); + StoreBigLong(sendbuffer + 4, conn->nq.sendSequence); + memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); + + conn->nq.sendSequence++; + + conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28; + + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); + + conn->lastSendTime = realtime; + packetsSent++; + reliableMessagesSent++; + + totallen += sendmelen + 28; + } + + // if we have an unreliable message to send, do so + if (data->cursize) + { + packetLen = NET_HEADERSIZE + data->cursize; + + if (packetLen > (int)sizeof(sendbuffer)) + { + Con_Printf("NetConn_SendUnreliableMessage: message too big %u\n", data->cursize); + return -1; + } + + StoreBigLong(sendbuffer, packetLen | NETFLAG_UNRELIABLE); + StoreBigLong(sendbuffer + 4, conn->outgoing_unreliable_sequence); + memcpy(sendbuffer + NET_HEADERSIZE, data->data, data->cursize); + + conn->outgoing_unreliable_sequence++; + + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28; + + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); + + packetsSent++; + unreliableMessagesSent++; + + totallen += sendmelen + 28; + } + } + + // delay later packets to obey rate limit + if (conn->cleartime < realtime - 0.1) + conn->cleartime = realtime - 0.1; + conn->cleartime = conn->cleartime + (double)totallen / (double)rate; + if (conn->cleartime < realtime) + conn->cleartime = realtime; + + return 0; +} + +qboolean NetConn_HaveClientPorts(void) +{ + return !!cl_numsockets; +} + +qboolean NetConn_HaveServerPorts(void) +{ + return !!sv_numsockets; +} + +void NetConn_CloseClientPorts(void) +{ + for (;cl_numsockets > 0;cl_numsockets--) + if (cl_sockets[cl_numsockets - 1]) + LHNET_CloseSocket(cl_sockets[cl_numsockets - 1]); +} + +void NetConn_OpenClientPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport) +{ + lhnetaddress_t address; + lhnetsocket_t *s; + int success; + char addressstring2[1024]; + if (addressstring && addressstring[0]) + success = LHNETADDRESS_FromString(&address, addressstring, defaultport); + else + success = LHNETADDRESS_FromPort(&address, addresstype, defaultport); + if (success) + { + if ((s = LHNET_OpenSocket_Connectionless(&address))) + { + cl_sockets[cl_numsockets++] = s; + LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true); + if (addresstype != LHNETADDRESSTYPE_LOOP) + Con_Printf("Client opened a socket on address %s\n", addressstring2); + } + else + { + LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true); + Con_Printf("Client failed to open a socket on address %s\n", addressstring2); + } + } + else + Con_Printf("Client unable to parse address %s\n", addressstring); +} + +void NetConn_OpenClientPorts(void) +{ + int port; + NetConn_CloseClientPorts(); + port = bound(0, cl_netport.integer, 65535); + if (cl_netport.integer != port) + Cvar_SetValueQuick(&cl_netport, port); + if(port == 0) + Con_Printf("Client using an automatically assigned port\n"); + else + Con_Printf("Client using port %i\n", port); + NetConn_OpenClientPort(NULL, LHNETADDRESSTYPE_LOOP, 2); + NetConn_OpenClientPort(net_address.string, LHNETADDRESSTYPE_INET4, port); +#ifdef SUPPORTIPV6 + NetConn_OpenClientPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port); +#endif +} + +void NetConn_CloseServerPorts(void) +{ + for (;sv_numsockets > 0;sv_numsockets--) + if (sv_sockets[sv_numsockets - 1]) + LHNET_CloseSocket(sv_sockets[sv_numsockets - 1]); +} + +qboolean NetConn_OpenServerPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport, int range) +{ + lhnetaddress_t address; + lhnetsocket_t *s; + int port; + char addressstring2[1024]; + int success; + + for (port = defaultport; port <= defaultport + range; port++) + { + if (addressstring && addressstring[0]) + success = LHNETADDRESS_FromString(&address, addressstring, port); + else + success = LHNETADDRESS_FromPort(&address, addresstype, port); + if (success) + { + if ((s = LHNET_OpenSocket_Connectionless(&address))) + { + sv_sockets[sv_numsockets++] = s; + LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true); + if (addresstype != LHNETADDRESSTYPE_LOOP) + Con_Printf("Server listening on address %s\n", addressstring2); + return true; + } + else + { + LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true); + Con_Printf("Server failed to open socket on address %s\n", addressstring2); + } + } + else + { + Con_Printf("Server unable to parse address %s\n", addressstring); + // if it cant parse one address, it wont be able to parse another for sure + return false; + } + } + return false; +} + +void NetConn_OpenServerPorts(int opennetports) +{ + int port; + NetConn_CloseServerPorts(); + NetConn_UpdateSockets(); + port = bound(0, sv_netport.integer, 65535); + if (port == 0) + port = 26000; + Con_Printf("Server using port %i\n", port); + if (sv_netport.integer != port) + Cvar_SetValueQuick(&sv_netport, port); + if (cls.state != ca_dedicated) + NetConn_OpenServerPort(NULL, LHNETADDRESSTYPE_LOOP, 1, 1); + if (opennetports) + { +#ifdef SUPPORTIPV6 + qboolean ip4success = NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100); + NetConn_OpenServerPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port, ip4success ? 1 : 100); +#else + NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100); +#endif + } + if (sv_numsockets == 0) + Host_Error("NetConn_OpenServerPorts: unable to open any ports!"); +} + +lhnetsocket_t *NetConn_ChooseClientSocketForAddress(lhnetaddress_t *address) +{ + int i, a = LHNETADDRESS_GetAddressType(address); + for (i = 0;i < cl_numsockets;i++) + if (cl_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == a) + return cl_sockets[i]; + return NULL; +} + +lhnetsocket_t *NetConn_ChooseServerSocketForAddress(lhnetaddress_t *address) +{ + int i, a = LHNETADDRESS_GetAddressType(address); + for (i = 0;i < sv_numsockets;i++) + if (sv_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(sv_sockets[i])) == a) + return sv_sockets[i]; + return NULL; +} + +netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress) +{ + netconn_t *conn; + conn = (netconn_t *)Mem_Alloc(netconn_mempool, sizeof(*conn)); + conn->mysocket = mysocket; + conn->peeraddress = *peeraddress; + conn->lastMessageTime = realtime; + conn->message.data = conn->messagedata; + conn->message.maxsize = sizeof(conn->messagedata); + conn->message.cursize = 0; + // LordHavoc: (inspired by ProQuake) use a short connect timeout to + // reduce effectiveness of connection request floods + conn->timeout = realtime + net_connecttimeout.value; + LHNETADDRESS_ToString(&conn->peeraddress, conn->address, sizeof(conn->address), true); + conn->next = netconn_list; + netconn_list = conn; + return conn; +} + +void NetConn_ClearConnectFlood(lhnetaddress_t *peeraddress); +void NetConn_Close(netconn_t *conn) +{ + netconn_t *c; + // remove connection from list + + // allow the client to reconnect immediately + NetConn_ClearConnectFlood(&(conn->peeraddress)); + + if (conn == netconn_list) + netconn_list = conn->next; + else + { + for (c = netconn_list;c;c = c->next) + { + if (c->next == conn) + { + c->next = conn->next; + break; + } + } + // not found in list, we'll avoid crashing here... + if (!c) + return; + } + // free connection + Mem_Free(conn); +} + +static int clientport = -1; +static int clientport2 = -1; +static int hostport = -1; +void NetConn_UpdateSockets(void) +{ + int i, j; + + if (cls.state != ca_dedicated) + { + if (clientport2 != cl_netport.integer) + { + clientport2 = cl_netport.integer; + if (cls.state == ca_connected) + Con_Print("Changing \"cl_port\" will not take effect until you reconnect.\n"); + } + if (cls.state == ca_disconnected && clientport != clientport2) + { + clientport = clientport2; + NetConn_CloseClientPorts(); + } + if (cl_numsockets == 0) + NetConn_OpenClientPorts(); + } + + if (hostport != sv_netport.integer) + { + hostport = sv_netport.integer; + if (sv.active) + Con_Print("Changing \"port\" will not take effect until \"map\" command is executed.\n"); + } + + for (j = 0;j < MAX_RCONS;j++) + { + i = (cls.rcon_ringpos + j + 1) % MAX_RCONS; + if(cls.rcon_commands[i][0]) + { + if(realtime > cls.rcon_timeout[i]) + { + char s[128]; + LHNETADDRESS_ToString(&cls.rcon_addresses[i], s, sizeof(s), true); + Con_Printf("rcon to %s (for command %s) failed: challenge request timed out\n", s, cls.rcon_commands[i]); + cls.rcon_commands[i][0] = 0; + --cls.rcon_trying; + break; + } + } + } +} + +static int NetConn_ReceivedMessage(netconn_t *conn, const unsigned char *data, size_t length, protocolversion_t protocol, double newtimeout) +{ + int originallength = length; + if (length < 8) + return 0; + + if (protocol == PROTOCOL_QUAKEWORLD) + { + int sequence, sequence_ack; + int reliable_ack, reliable_message; + int count; + //int qport; + + sequence = LittleLong(*((int *)(data + 0))); + sequence_ack = LittleLong(*((int *)(data + 4))); + data += 8; + length -= 8; + + if (conn != cls.netcon) + { + // server only + if (length < 2) + return 0; + // TODO: use qport to identify that this client really is who they say they are? (and elsewhere in the code to identify the connection without a port match?) + //qport = LittleShort(*((int *)(data + 8))); + data += 2; + length -= 2; + } + + packetsReceived++; + reliable_message = (sequence >> 31) & 1; + reliable_ack = (sequence_ack >> 31) & 1; + sequence &= ~(1<<31); + sequence_ack &= ~(1<<31); + if (sequence <= conn->qw.incoming_sequence) + { + //Con_DPrint("Got a stale datagram\n"); + return 0; + } + count = sequence - (conn->qw.incoming_sequence + 1); + if (count > 0) + { + droppedDatagrams += count; + //Con_DPrintf("Dropped %u datagram(s)\n", count); + while (count--) + { + conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; + conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; + conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; + } + } + conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; + conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; + conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; + if (reliable_ack == conn->qw.reliable_sequence) + { + // received, now we will be able to send another reliable message + conn->sendMessageLength = 0; + reliableMessagesReceived++; + } + conn->qw.incoming_sequence = sequence; + if (conn == cls.netcon) + cls.qw_incoming_sequence = conn->qw.incoming_sequence; + conn->qw.incoming_acknowledged = sequence_ack; + conn->qw.incoming_reliable_acknowledged = reliable_ack; + if (reliable_message) + conn->qw.incoming_reliable_sequence ^= 1; + conn->lastMessageTime = realtime; + conn->timeout = realtime + newtimeout; + unreliableMessagesReceived++; + SZ_Clear(&net_message); + SZ_Write(&net_message, data, length); + MSG_BeginReading(); + return 2; + } + else + { + unsigned int count; + unsigned int flags; + unsigned int sequence; + size_t qlength; + const void *sendme; + size_t sendmelen; + + originallength = length; + data = (const unsigned char *) Crypto_DecryptPacket(&conn->crypto, data, length, cryptoreadbuffer, &length, sizeof(cryptoreadbuffer)); + if(!data) + return 0; + if(length < 8) + return 0; + + qlength = (unsigned int)BuffBigLong(data); + flags = qlength & ~NETFLAG_LENGTH_MASK; + qlength &= NETFLAG_LENGTH_MASK; + // control packets were already handled + if (!(flags & NETFLAG_CTL) && qlength == length) + { + sequence = BuffBigLong(data + 4); + packetsReceived++; + data += 8; + length -= 8; + if (flags & NETFLAG_UNRELIABLE) + { + if (sequence >= conn->nq.unreliableReceiveSequence) + { + if (sequence > conn->nq.unreliableReceiveSequence) + { + count = sequence - conn->nq.unreliableReceiveSequence; + droppedDatagrams += count; + //Con_DPrintf("Dropped %u datagram(s)\n", count); + while (count--) + { + conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; + conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; + conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; + } + } + conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; + conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; + conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; + conn->nq.unreliableReceiveSequence = sequence + 1; + conn->lastMessageTime = realtime; + conn->timeout = realtime + newtimeout; + unreliableMessagesReceived++; + if (length > 0) + { + SZ_Clear(&net_message); + SZ_Write(&net_message, data, length); + MSG_BeginReading(); + return 2; + } + } + //else + // Con_DPrint("Got a stale datagram\n"); + return 1; + } + else if (flags & NETFLAG_ACK) + { + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes += originallength + 28; + if (sequence == (conn->nq.sendSequence - 1)) + { + if (sequence == conn->nq.ackSequence) + { + conn->nq.ackSequence++; + if (conn->nq.ackSequence != conn->nq.sendSequence) + Con_DPrint("ack sequencing error\n"); + conn->lastMessageTime = realtime; + conn->timeout = realtime + newtimeout; + if (conn->sendMessageLength > MAX_PACKETFRAGMENT) + { + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + + conn->sendMessageLength -= MAX_PACKETFRAGMENT; + memmove(conn->sendMessage, conn->sendMessage+MAX_PACKETFRAGMENT, conn->sendMessageLength); + + if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + { + dataLen = conn->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_PACKETFRAGMENT; + eom = 0; + } + + packetLen = NET_HEADERSIZE + dataLen; + + StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom)); + StoreBigLong(sendbuffer + 4, conn->nq.sendSequence); + memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); + + conn->nq.sendSequence++; + + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if (sendme && NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress) == (int)sendmelen) + { + conn->lastSendTime = realtime; + packetsSent++; + } + } + else + conn->sendMessageLength = 0; + } + //else + // Con_DPrint("Duplicate ACK received\n"); + } + //else + // Con_DPrint("Stale ACK received\n"); + return 1; + } + else if (flags & NETFLAG_DATA) + { + unsigned char temppacket[8]; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes += originallength + 28; + conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes += 8 + 28; + StoreBigLong(temppacket, 8 | NETFLAG_ACK); + StoreBigLong(temppacket + 4, sequence); + sendme = Crypto_EncryptPacket(&conn->crypto, temppacket, 8, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); + if (sequence == conn->nq.receiveSequence) + { + conn->lastMessageTime = realtime; + conn->timeout = realtime + newtimeout; + conn->nq.receiveSequence++; + if( conn->receiveMessageLength + length <= (int)sizeof( conn->receiveMessage ) ) { + memcpy(conn->receiveMessage + conn->receiveMessageLength, data, length); + conn->receiveMessageLength += length; + } else { + Con_Printf( "Reliable message (seq: %i) too big for message buffer!\n" + "Dropping the message!\n", sequence ); + conn->receiveMessageLength = 0; + return 1; + } + if (flags & NETFLAG_EOM) + { + reliableMessagesReceived++; + length = conn->receiveMessageLength; + conn->receiveMessageLength = 0; + if (length > 0) + { + SZ_Clear(&net_message); + SZ_Write(&net_message, conn->receiveMessage, length); + MSG_BeginReading(); + return 2; + } + } + } + else + receivedDuplicateCount++; + return 1; + } + } + } + return 0; +} + +void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, protocolversion_t initialprotocol) +{ + crypto_t *crypto; + cls.connect_trying = false; + M_Update_Return_Reason(""); + // the connection request succeeded, stop current connection and set up a new connection + CL_Disconnect(); + // if we're connecting to a remote server, shut down any local server + if (LHNETADDRESS_GetAddressType(peeraddress) != LHNETADDRESSTYPE_LOOP && sv.active) + Host_ShutdownServer (); + // allocate a net connection to keep track of things + cls.netcon = NetConn_Open(mysocket, peeraddress); + crypto = &cls.crypto; + if(crypto && crypto->authenticated) + { + Crypto_ServerFinishInstance(&cls.netcon->crypto, crypto); + Con_Printf("%s connection to %s has been established: server is %s@%.*s, I am %.*s@%.*s\n", + crypto->use_aes ? "Encrypted" : "Authenticated", + cls.netcon->address, + crypto->server_idfp[0] ? crypto->server_idfp : "-", + crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-", + crypto_keyfp_recommended_length, crypto->client_idfp[0] ? crypto->client_idfp : "-", + crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-" + ); + } + Con_Printf("Connection accepted to %s\n", cls.netcon->address); + key_dest = key_game; + m_state = m_none; + cls.demonum = -1; // not in the demo loop now + cls.state = ca_connected; + cls.signon = 0; // need all the signon messages before playing + cls.protocol = initialprotocol; + // reset move sequence numbering on this new connection + cls.servermovesequence = 0; + if (cls.protocol == PROTOCOL_QUAKEWORLD) + Cmd_ForwardStringToServer("new"); + if (cls.protocol == PROTOCOL_QUAKE) + { + // write a keepalive (clc_nop) as it seems to greatly improve the + // chances of connecting to a netquake server + sizebuf_t msg; + unsigned char buf[4]; + memset(&msg, 0, sizeof(msg)); + msg.data = buf; + msg.maxsize = sizeof(buf); + MSG_WriteChar(&msg, clc_nop); + NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, false); + } +} + +int NetConn_IsLocalGame(void) +{ + if (cls.state == ca_connected && sv.active && cl.maxclients == 1) + return true; + return false; +} + +static int NetConn_ClientParsePacket_ServerList_ProcessReply(const char *addressstring) +{ + int n; + int pingtime; + serverlist_entry_t *entry = NULL; + + // search the cache for this server and update it + for (n = 0;n < serverlist_cachecount;n++) { + entry = &serverlist_cache[ n ]; + if (!strcmp(addressstring, entry->info.cname)) + break; + } + + if (n == serverlist_cachecount) + { + // LAN search doesnt require an answer from the master server so we wont + // know the ping nor will it be initialized already... + + // find a slot + if (serverlist_cachecount == SERVERLIST_TOTALSIZE) + return -1; + + if (serverlist_maxcachecount <= serverlist_cachecount) + { + serverlist_maxcachecount += 64; + serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); + } + entry = &serverlist_cache[n]; + + memset(entry, 0, sizeof(*entry)); + // store the data the engine cares about (address and ping) + strlcpy(entry->info.cname, addressstring, sizeof(entry->info.cname)); + entry->info.ping = 100000; + entry->querytime = realtime; + // if not in the slist menu we should print the server to console + if (serverlist_consoleoutput) + Con_Printf("querying %s\n", addressstring); + ++serverlist_cachecount; + } + // if this is the first reply from this server, count it as having replied + pingtime = (int)((realtime - entry->querytime) * 1000.0 + 0.5); + pingtime = bound(0, pingtime, 9999); + if (entry->query == SQS_REFRESHING) { + entry->info.ping = pingtime; + entry->query = SQS_QUERIED; + } else { + // convert to unsigned to catch the -1 + // I still dont like this but its better than the old 10000 magic ping number - as in easier to type and read :( [11/8/2007 Black] + entry->info.ping = min((unsigned) entry->info.ping, (unsigned) pingtime); + serverreplycount++; + } + + // other server info is updated by the caller + return n; +} + +static void NetConn_ClientParsePacket_ServerList_UpdateCache(int n) +{ + serverlist_entry_t *entry = &serverlist_cache[n]; + serverlist_info_t *info = &entry->info; + // update description strings for engine menu and console output + dpsnprintf(entry->line1, sizeof(serverlist_cache[n].line1), "^%c%5d^7 ^%c%3u^7/%3u %-65.65s", info->ping >= 300 ? '1' : (info->ping >= 200 ? '3' : '7'), (int)info->ping, ((info->numhumans > 0 && info->numhumans < info->maxplayers) ? (info->numhumans >= 4 ? '7' : '3') : '1'), info->numplayers, info->maxplayers, info->name); + dpsnprintf(entry->line2, sizeof(serverlist_cache[n].line2), "^4%-21.21s %-19.19s ^%c%-17.17s^4 %-20.20s", info->cname, info->game, + ( + info->gameversion != gameversion.integer + && + !( + gameversion_min.integer >= 0 // min/max range set by user/mod? + && gameversion_max.integer >= 0 + && gameversion_min.integer >= info->gameversion // version of server in min/max range? + && gameversion_max.integer <= info->gameversion + ) + ) ? '1' : '4', + info->mod, info->map); + if (entry->query == SQS_QUERIED) + { + if(!serverlist_paused) + ServerList_ViewList_Remove(entry); + } + // if not in the slist menu we should print the server to console (if wanted) + else if( serverlist_consoleoutput ) + Con_Printf("%s\n%s\n", serverlist_cache[n].line1, serverlist_cache[n].line2); + // and finally, update the view set + if(!serverlist_paused) + ServerList_ViewList_Insert( entry ); + // update the entry's state + serverlist_cache[n].query = SQS_QUERIED; +} + +// returns true, if it's sensible to continue the processing +static qboolean NetConn_ClientParsePacket_ServerList_PrepareQuery( int protocol, const char *ipstring, qboolean isfavorite ) { + int n; + serverlist_entry_t *entry; + + // ignore the rest of the message if the serverlist is full + if( serverlist_cachecount == SERVERLIST_TOTALSIZE ) + return false; + // also ignore it if we have already queried it (other master server response) + for( n = 0 ; n < serverlist_cachecount ; n++ ) + if( !strcmp( ipstring, serverlist_cache[ n ].info.cname ) ) + break; + + if( n < serverlist_cachecount ) { + // the entry has already been queried once or + return true; + } + + if (serverlist_maxcachecount <= n) + { + serverlist_maxcachecount += 64; + serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); + } + + entry = &serverlist_cache[n]; + + memset(entry, 0, sizeof(entry)); + entry->protocol = protocol; + // store the data the engine cares about (address and ping) + strlcpy (entry->info.cname, ipstring, sizeof(entry->info.cname)); + + entry->info.isfavorite = isfavorite; + + // no, then reset the ping right away + entry->info.ping = -1; + // we also want to increase the serverlist_cachecount then + serverlist_cachecount++; + serverquerycount++; + + entry->query = SQS_QUERYING; + + return true; +} + +static void NetConn_ClientParsePacket_ServerList_ParseDPList(lhnetaddress_t *senderaddress, const unsigned char *data, int length, qboolean isextended) +{ + masterreplycount++; + if (serverlist_consoleoutput) + Con_Printf("received DarkPlaces %sserver list...\n", isextended ? "extended " : ""); + while (length >= 7) + { + char ipstring [128]; + + // IPv4 address + if (data[0] == '\\') + { + unsigned short port = data[5] * 256 + data[6]; + + if (port != 0 && (data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF || data[4] != 0xFF)) + dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%hu", data[1], data[2], data[3], data[4], port); + + // move on to next address in packet + data += 7; + length -= 7; + } + // IPv6 address + else if (data[0] == '/' && isextended && length >= 19) + { + unsigned short port = data[17] * 256 + data[18]; + + if (port != 0) + { +#ifdef WHY_JUST_WHY + const char *ifname; + + /// \TODO: make some basic checks of the IP address (broadcast, ...) + + ifname = LHNETADDRESS_GetInterfaceName(senderaddress); + if (ifname != NULL) + { + dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x%%%s]:%hu", + (data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8], + (data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16], + ifname, port); + } + else +#endif + { + dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x]:%hu", + (data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8], + (data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16], + port); + } + } + + // move on to next address in packet + data += 19; + length -= 19; + } + else + { + Con_Print("Error while parsing the server list\n"); + break; + } + + if (serverlist_consoleoutput && developer_networking.integer) + Con_Printf("Requesting info from DarkPlaces server %s\n", ipstring); + + if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, ipstring, false ) ) { + break; + } + + } + + // begin or resume serverlist queries + serverlist_querysleep = false; + serverlist_querywaittime = realtime + 3; +} + +static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress) +{ + qboolean fromserver; + int ret, c, control; + const char *s; + char *string, addressstring2[128], ipstring[32]; + char stringbuf[16384]; + char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + size_t sendlength; + + // quakeworld ingame packet + fromserver = cls.netcon && mysocket == cls.netcon->mysocket && !LHNETADDRESS_Compare(&cls.netcon->peeraddress, peeraddress); + + // convert the address to a string incase we need it + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + + if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255) + { + // received a command string - strip off the packaging and put it + // into our string buffer with NULL termination + data += 4; + length -= 4; + length = min(length, (int)sizeof(stringbuf) - 1); + memcpy(stringbuf, data, length); + stringbuf[length] = 0; + string = stringbuf; + + if (developer_networking.integer) + { + Con_Printf("NetConn_ClientParsePacket: %s sent us a command:\n", addressstring2); + Com_HexDumpToConsole(data, length); + } + + sendlength = sizeof(senddata) - 4; + switch(Crypto_ClientParsePacket(string, length, senddata+4, &sendlength, peeraddress)) + { + case CRYPTO_NOMATCH: + // nothing to do + break; + case CRYPTO_MATCH: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + break; + case CRYPTO_DISCARD: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + return true; + break; + case CRYPTO_REPLACE: + string = senddata+4; + length = sendlength; + break; + } + + if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.rcon_trying) + { + int i = 0, j; + for (j = 0;j < MAX_RCONS;j++) + { + // note: this value from i is used outside the loop too... + i = (cls.rcon_ringpos + j) % MAX_RCONS; + if(cls.rcon_commands[i][0]) + if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[i])) + break; + } + if (j < MAX_RCONS) + { + char buf[1500]; + char argbuf[1500]; + const char *e; + int n; + dpsnprintf(argbuf, sizeof(argbuf), "%s %s", string + 10, cls.rcon_commands[i]); + memcpy(buf, "\377\377\377\377srcon HMAC-MD4 CHALLENGE ", 29); + + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); + + if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 29), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n)) + { + int k; + buf[45] = ' '; + strlcpy(buf + 46, argbuf, sizeof(buf) - 46); + NetConn_Write(mysocket, buf, 46 + strlen(buf + 46), peeraddress); + cls.rcon_commands[i][0] = 0; + --cls.rcon_trying; + + for (k = 0;k < MAX_RCONS;k++) + if(cls.rcon_commands[k][0]) + if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[k])) + break; + if(k < MAX_RCONS) + { + int l; + NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", peeraddress); + // extend the timeout on other requests as we asked for a challenge + for (l = 0;l < MAX_RCONS;l++) + if(cls.rcon_commands[l][0]) + if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[l])) + cls.rcon_timeout[l] = realtime + rcon_secure_challengetimeout.value; + } + + return true; // we used up the challenge, so we can't use this oen for connecting now anyway + } + } + } + if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) + { + // darkplaces or quake3 + char protocolnames[1400]; + Protocol_Names(protocolnames, sizeof(protocolnames)); + Con_DPrintf("\"%s\" received, sending connect request back to %s\n", string, addressstring2); + M_Update_Return_Reason("Got challenge response"); + // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); + // TODO: add userinfo stuff here instead of using NQ commands? + NetConn_WriteString(mysocket, va("\377\377\377\377connect\\protocol\\darkplaces 3\\protocols\\%s%s\\challenge\\%s", protocolnames, cls.connect_userinfo, string + 10), peeraddress); + return true; + } + if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying) + { + // darkplaces or quake3 + M_Update_Return_Reason("Accepted"); + NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_DARKPLACES3); + return true; + } + if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying) + { + char rejectreason[128]; + cls.connect_trying = false; + string += 7; + length = min(length - 7, (int)sizeof(rejectreason) - 1); + memcpy(rejectreason, string, length); + rejectreason[length] = 0; + M_Update_Return_Reason(rejectreason); + return true; + } + if (length >= 15 && !memcmp(string, "statusResponse\x0A", 15)) + { + serverlist_info_t *info; + char *p; + int n; + + string += 15; + // search the cache for this server and update it + n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); + if (n < 0) + return true; + + info = &serverlist_cache[n].info; + info->game[0] = 0; + info->mod[0] = 0; + info->map[0] = 0; + info->name[0] = 0; + info->qcstatus[0] = 0; + info->players[0] = 0; + info->protocol = -1; + info->numplayers = 0; + info->numbots = -1; + info->maxplayers = 0; + info->gameversion = 0; + + p = strchr(string, '\n'); + if(p) + { + *p = 0; // cut off the string there + ++p; + } + else + Con_Printf("statusResponse without players block?\n"); + + if ((s = SearchInfostring(string, "gamename" )) != NULL) strlcpy(info->game, s, sizeof (info->game)); + if ((s = SearchInfostring(string, "modname" )) != NULL) strlcpy(info->mod , s, sizeof (info->mod )); + if ((s = SearchInfostring(string, "mapname" )) != NULL) strlcpy(info->map , s, sizeof (info->map )); + if ((s = SearchInfostring(string, "hostname" )) != NULL) strlcpy(info->name, s, sizeof (info->name)); + if ((s = SearchInfostring(string, "protocol" )) != NULL) info->protocol = atoi(s); + if ((s = SearchInfostring(string, "clients" )) != NULL) info->numplayers = atoi(s); + if ((s = SearchInfostring(string, "bots" )) != NULL) info->numbots = atoi(s); + if ((s = SearchInfostring(string, "sv_maxclients")) != NULL) info->maxplayers = atoi(s); + if ((s = SearchInfostring(string, "gameversion" )) != NULL) info->gameversion = atoi(s); + if ((s = SearchInfostring(string, "qcstatus" )) != NULL) strlcpy(info->qcstatus, s, sizeof(info->qcstatus)); + if (p != NULL) strlcpy(info->players, p, sizeof(info->players)); + info->numhumans = info->numplayers - max(0, info->numbots); + info->freeslots = info->maxplayers - info->numplayers; + + NetConn_ClientParsePacket_ServerList_UpdateCache(n); + + return true; + } + if (length >= 13 && !memcmp(string, "infoResponse\x0A", 13)) + { + serverlist_info_t *info; + int n; + + string += 13; + // search the cache for this server and update it + n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); + if (n < 0) + return true; + + info = &serverlist_cache[n].info; + info->game[0] = 0; + info->mod[0] = 0; + info->map[0] = 0; + info->name[0] = 0; + info->qcstatus[0] = 0; + info->players[0] = 0; + info->protocol = -1; + info->numplayers = 0; + info->numbots = -1; + info->maxplayers = 0; + info->gameversion = 0; + + if ((s = SearchInfostring(string, "gamename" )) != NULL) strlcpy(info->game, s, sizeof (info->game)); + if ((s = SearchInfostring(string, "modname" )) != NULL) strlcpy(info->mod , s, sizeof (info->mod )); + if ((s = SearchInfostring(string, "mapname" )) != NULL) strlcpy(info->map , s, sizeof (info->map )); + if ((s = SearchInfostring(string, "hostname" )) != NULL) strlcpy(info->name, s, sizeof (info->name)); + if ((s = SearchInfostring(string, "protocol" )) != NULL) info->protocol = atoi(s); + if ((s = SearchInfostring(string, "clients" )) != NULL) info->numplayers = atoi(s); + if ((s = SearchInfostring(string, "bots" )) != NULL) info->numbots = atoi(s); + if ((s = SearchInfostring(string, "sv_maxclients")) != NULL) info->maxplayers = atoi(s); + if ((s = SearchInfostring(string, "gameversion" )) != NULL) info->gameversion = atoi(s); + if ((s = SearchInfostring(string, "qcstatus" )) != NULL) strlcpy(info->qcstatus, s, sizeof(info->qcstatus)); + info->numhumans = info->numplayers - max(0, info->numbots); + info->freeslots = info->maxplayers - info->numplayers; + + NetConn_ClientParsePacket_ServerList_UpdateCache(n); + + return true; + } + if (!strncmp(string, "getserversResponse\\", 19) && serverlist_cachecount < SERVERLIST_TOTALSIZE) + { + // Extract the IP addresses + data += 18; + length -= 18; + NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, false); + return true; + } + if (!strncmp(string, "getserversExtResponse", 21) && serverlist_cachecount < SERVERLIST_TOTALSIZE) + { + // Extract the IP addresses + data += 21; + length -= 21; + NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, true); + return true; + } + if (!memcmp(string, "d\n", 2) && serverlist_cachecount < SERVERLIST_TOTALSIZE) + { + // Extract the IP addresses + data += 2; + length -= 2; + masterreplycount++; + if (serverlist_consoleoutput) + Con_Printf("received QuakeWorld server list from %s...\n", addressstring2); + while (length >= 6 && (data[0] != 0xFF || data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF) && data[4] * 256 + data[5] != 0) + { + dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%u", data[0], data[1], data[2], data[3], data[4] * 256 + data[5]); + if (serverlist_consoleoutput && developer_networking.integer) + Con_Printf("Requesting info from QuakeWorld server %s\n", ipstring); + + if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_QUAKEWORLD, ipstring, false ) ) { + break; + } + + // move on to next address in packet + data += 6; + length -= 6; + } + // begin or resume serverlist queries + serverlist_querysleep = false; + serverlist_querywaittime = realtime + 3; + return true; + } + if (!strncmp(string, "extResponse ", 12)) + { + ++cl_net_extresponse_count; + if(cl_net_extresponse_count > NET_EXTRESPONSE_MAX) + cl_net_extresponse_count = NET_EXTRESPONSE_MAX; + cl_net_extresponse_last = (cl_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX; + dpsnprintf(cl_net_extresponse[cl_net_extresponse_last], sizeof(cl_net_extresponse[cl_net_extresponse_last]), "\"%s\" %s", addressstring2, string + 12); + return true; + } + if (!strncmp(string, "ping", 4)) + { + if (developer_extra.integer) + Con_DPrintf("Received ping from %s, sending ack\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress); + return true; + } + if (!strncmp(string, "ack", 3)) + return true; + // QuakeWorld compatibility + if (length > 1 && string[0] == 'c' && (string[1] == '-' || (string[1] >= '0' && string[1] <= '9')) && cls.connect_trying) + { + // challenge message + Con_Printf("challenge %s received, sending QuakeWorld connect request back to %s\n", string + 1, addressstring2); + M_Update_Return_Reason("Got QuakeWorld challenge response"); + cls.qw_qport = qport.integer; + // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); + NetConn_WriteString(mysocket, va("\377\377\377\377connect %i %i %i \"%s%s\"\n", 28, cls.qw_qport, atoi(string + 1), cls.userinfo, cls.connect_userinfo), peeraddress); + return true; + } + if (length >= 1 && string[0] == 'j' && cls.connect_trying) + { + // accept message + M_Update_Return_Reason("QuakeWorld Accepted"); + NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_QUAKEWORLD); + return true; + } + if (length > 2 && !memcmp(string, "n\\", 2)) + { + serverlist_info_t *info; + int n; + + // qw server status + if (serverlist_consoleoutput && developer_networking.integer >= 2) + Con_Printf("QW server status from server at %s:\n%s\n", addressstring2, string + 1); + + string += 1; + // search the cache for this server and update it + n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); + if (n < 0) + return true; + + info = &serverlist_cache[n].info; + strlcpy(info->game, "QuakeWorld", sizeof(info->game)); + if ((s = SearchInfostring(string, "*gamedir" )) != NULL) strlcpy(info->mod , s, sizeof (info->mod ));else info->mod[0] = 0; + if ((s = SearchInfostring(string, "map" )) != NULL) strlcpy(info->map , s, sizeof (info->map ));else info->map[0] = 0; + if ((s = SearchInfostring(string, "hostname" )) != NULL) strlcpy(info->name, s, sizeof (info->name));else info->name[0] = 0; + info->protocol = 0; + info->numplayers = 0; // updated below + info->numhumans = 0; // updated below + if ((s = SearchInfostring(string, "maxclients" )) != NULL) info->maxplayers = atoi(s);else info->maxplayers = 0; + if ((s = SearchInfostring(string, "gameversion" )) != NULL) info->gameversion = atoi(s);else info->gameversion = 0; + + // count active players on server + // (we could gather more info, but we're just after the number) + s = strchr(string, '\n'); + if (s) + { + s++; + while (s < string + length) + { + for (;s < string + length && *s != '\n';s++) + ; + if (s >= string + length) + break; + info->numplayers++; + info->numhumans++; + s++; + } + } + + NetConn_ClientParsePacket_ServerList_UpdateCache(n); + + return true; + } + if (string[0] == 'n') + { + // qw print command + Con_Printf("QW print command from server at %s:\n%s\n", addressstring2, string + 1); + } + // we may not have liked the packet, but it was a command packet, so + // we're done processing this packet now + return true; + } + // quakeworld ingame packet + if (fromserver && cls.protocol == PROTOCOL_QUAKEWORLD && length >= 8 && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2) + { + ret = 0; + CL_ParseServerMessage(); + return ret; + } + // netquake control packets, supported for compatibility only + if (length >= 5 && (control = BuffBigLong(data)) && (control & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (control & NETFLAG_LENGTH_MASK) == length && !ENCRYPTION_REQUIRED) + { + int n; + serverlist_info_t *info; + + data += 4; + length -= 4; + SZ_Clear(&net_message); + SZ_Write(&net_message, data, length); + MSG_BeginReading(); + c = MSG_ReadByte(); + switch (c) + { + case CCREP_ACCEPT: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREP_ACCEPT from %s.\n", addressstring2); + if (cls.connect_trying) + { + lhnetaddress_t clientportaddress; + clientportaddress = *peeraddress; + LHNETADDRESS_SetPort(&clientportaddress, MSG_ReadLong()); + // extra ProQuake stuff + if (length >= 6) + cls.proquake_servermod = MSG_ReadByte(); // MOD_PROQUAKE + else + cls.proquake_servermod = 0; + if (length >= 7) + cls.proquake_serverversion = MSG_ReadByte(); // version * 10 + else + cls.proquake_serverversion = 0; + if (length >= 8) + cls.proquake_serverflags = MSG_ReadByte(); // flags (mainly PQF_CHEATFREE) + else + cls.proquake_serverflags = 0; + if (cls.proquake_servermod == 1) + Con_Printf("Connected to ProQuake %.1f server, enabling precise aim\n", cls.proquake_serverversion / 10.0f); + // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); + M_Update_Return_Reason("Accepted"); + NetConn_ConnectionEstablished(mysocket, &clientportaddress, PROTOCOL_QUAKE); + } + break; + case CCREP_REJECT: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREP_REJECT from %s.\n", addressstring2); + cls.connect_trying = false; + M_Update_Return_Reason((char *)MSG_ReadString()); + break; + case CCREP_SERVER_INFO: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREP_SERVER_INFO from %s.\n", addressstring2); + // LordHavoc: because the quake server may report weird addresses + // we just ignore it and keep the real address + MSG_ReadString(); + // search the cache for this server and update it + n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); + if (n < 0) + break; + + info = &serverlist_cache[n].info; + strlcpy(info->game, "Quake", sizeof(info->game)); + strlcpy(info->mod , "", sizeof(info->mod)); // mod name is not specified + strlcpy(info->name, MSG_ReadString(), sizeof(info->name)); + strlcpy(info->map , MSG_ReadString(), sizeof(info->map)); + info->numplayers = MSG_ReadByte(); + info->maxplayers = MSG_ReadByte(); + info->protocol = MSG_ReadByte(); + + NetConn_ClientParsePacket_ServerList_UpdateCache(n); + + break; + case CCREP_RCON: // RocketGuy: ProQuake rcon support + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREP_RCON from %s.\n", addressstring2); + + Con_Printf("%s\n", MSG_ReadString()); + break; + case CCREP_PLAYER_INFO: + // we got a CCREP_PLAYER_INFO?? + //if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: received CCREP_PLAYER_INFO from %s.\n", addressstring2); + break; + case CCREP_RULE_INFO: + // we got a CCREP_RULE_INFO?? + //if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: received CCREP_RULE_INFO from %s.\n", addressstring2); + break; + default: + break; + } + SZ_Clear(&net_message); + // we may not have liked the packet, but it was a valid control + // packet, so we're done processing this packet now + return true; + } + ret = 0; + if (fromserver && length >= (int)NET_HEADERSIZE && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2) + CL_ParseServerMessage(); + return ret; +} + +void NetConn_QueryQueueFrame(void) +{ + int index; + int queries; + int maxqueries; + double timeouttime; + static double querycounter = 0; + + if(!net_slist_pause.integer && serverlist_paused) + ServerList_RebuildViewList(); + serverlist_paused = net_slist_pause.integer != 0; + + if (serverlist_querysleep) + return; + + // apply a cool down time after master server replies, + // to avoid messing up the ping times on the servers + if (serverlist_querywaittime > realtime) + return; + + // each time querycounter reaches 1.0 issue a query + querycounter += cl.realframetime * net_slist_queriespersecond.value; + maxqueries = (int)querycounter; + maxqueries = bound(0, maxqueries, net_slist_queriesperframe.integer); + querycounter -= maxqueries; + + if( maxqueries == 0 ) { + return; + } + + // scan serverlist and issue queries as needed + serverlist_querysleep = true; + + timeouttime = realtime - net_slist_timeout.value; + for( index = 0, queries = 0 ; index < serverlist_cachecount && queries < maxqueries ; index++ ) + { + serverlist_entry_t *entry = &serverlist_cache[ index ]; + if( entry->query != SQS_QUERYING && entry->query != SQS_REFRESHING ) + { + continue; + } + + serverlist_querysleep = false; + if( entry->querycounter != 0 && entry->querytime > timeouttime ) + { + continue; + } + + if( entry->querycounter != (unsigned) net_slist_maxtries.integer ) + { + lhnetaddress_t address; + int socket; + + LHNETADDRESS_FromString(&address, entry->info.cname, 0); + if (entry->protocol == PROTOCOL_QUAKEWORLD) + { + for (socket = 0; socket < cl_numsockets ; socket++) + NetConn_WriteString(cl_sockets[socket], "\377\377\377\377status\n", &address); + } + else + { + for (socket = 0; socket < cl_numsockets ; socket++) + NetConn_WriteString(cl_sockets[socket], "\377\377\377\377getstatus", &address); + } + + // update the entry fields + entry->querytime = realtime; + entry->querycounter++; + + // if not in the slist menu we should print the server to console + if (serverlist_consoleoutput) + Con_Printf("querying %25s (%i. try)\n", entry->info.cname, entry->querycounter); + + queries++; + } + else + { + // have we tried to refresh this server? + if( entry->query == SQS_REFRESHING ) { + // yes, so update the reply count (since its not responding anymore) + serverreplycount--; + if(!serverlist_paused) + ServerList_ViewList_Remove(entry); + } + entry->query = SQS_TIMEDOUT; + } + } +} + +void NetConn_ClientFrame(void) +{ + int i, length; + lhnetaddress_t peeraddress; + NetConn_UpdateSockets(); + if (cls.connect_trying && cls.connect_nextsendtime < realtime) + { + if (cls.connect_remainingtries == 0) + M_Update_Return_Reason("Connect: Waiting 10 seconds for reply"); + cls.connect_nextsendtime = realtime + 1; + cls.connect_remainingtries--; + if (cls.connect_remainingtries <= -10) + { + cls.connect_trying = false; + M_Update_Return_Reason("Connect: Failed"); + return; + } + // try challenge first (newer DP server or QW) + NetConn_WriteString(cls.connect_mysocket, "\377\377\377\377getchallenge", &cls.connect_address); + // then try netquake as a fallback (old server, or netquake) + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_CONNECT); + MSG_WriteString(&net_message, "QUAKE"); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + // extended proquake stuff + MSG_WriteByte(&net_message, 1); // mod = MOD_PROQUAKE + // this version matches ProQuake 3.40, the first version to support + // the NAT fix, and it only supports the NAT fix for ProQuake 3.40 or + // higher clients, so we pretend we are that version... + MSG_WriteByte(&net_message, 34); // version * 10 + MSG_WriteByte(&net_message, 0); // flags + MSG_WriteLong(&net_message, 0); // password + // write the packetsize now... + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(cls.connect_mysocket, net_message.data, net_message.cursize, &cls.connect_address); + SZ_Clear(&net_message); + } + for (i = 0;i < cl_numsockets;i++) + { + while (cl_sockets[i] && (length = NetConn_Read(cl_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0) + { +// R_TimeReport("clientreadnetwork"); + NetConn_ClientParsePacket(cl_sockets[i], readbuffer, length, &peeraddress); +// R_TimeReport("clientparsepacket"); + } + } + NetConn_QueryQueueFrame(); + if (cls.netcon && realtime > cls.netcon->timeout && !sv.active) + { + Con_Print("Connection timed out\n"); + CL_Disconnect(); + Host_ShutdownServer (); + } +} + +static void NetConn_BuildChallengeString(char *buffer, int bufferlength) +{ + int i; + char c; + for (i = 0;i < bufferlength - 1;i++) + { + do + { + c = rand () % (127 - 33) + 33; + } while (c == '\\' || c == ';' || c == '"' || c == '%' || c == '/'); + buffer[i] = c; + } + buffer[i] = 0; +} + +/// (div0) build the full response only if possible; better a getinfo response than no response at all if getstatus won't fit +static qboolean NetConn_BuildStatusResponse(const char* challenge, char* out_msg, size_t out_size, qboolean fullstatus) +{ + char qcstatus[256]; + unsigned int nb_clients = 0, nb_bots = 0, i; + int length; + char teambuf[3]; + const char *crypto_idstring; + const char *str; + + SV_VM_Begin(); + + // How many clients are there? + for (i = 0;i < (unsigned int)svs.maxclients;i++) + { + if (svs.clients[i].active) + { + nb_clients++; + if (!svs.clients[i].netconnection) + nb_bots++; + } + } + + *qcstatus = 0; + str = PRVM_GetString(PRVM_serverglobalstring(worldstatus)); + if(str && *str) + { + char *p; + const char *q; + p = qcstatus; + for(q = str; *q && p - qcstatus < (ptrdiff_t)(sizeof(qcstatus)) - 1; ++q) + if(*q != '\\' && *q != '\n') + *p++ = *q; + *p = 0; + } + + /// \TODO: we should add more information for the full status string + crypto_idstring = Crypto_GetInfoResponseDataString(); + length = dpsnprintf(out_msg, out_size, + "\377\377\377\377%s\x0A" + "\\gamename\\%s\\modname\\%s\\gameversion\\%d\\sv_maxclients\\%d" + "\\clients\\%d\\bots\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d" + "%s%s" + "%s%s" + "%s%s" + "%s", + fullstatus ? "statusResponse" : "infoResponse", + gamename, com_modname, gameversion.integer, svs.maxclients, + nb_clients, nb_bots, sv.worldbasename, hostname.string, NET_PROTOCOL_VERSION, + *qcstatus ? "\\qcstatus\\" : "", qcstatus, + challenge ? "\\challenge\\" : "", challenge ? challenge : "", + crypto_idstring ? "\\d0_blind_id\\" : "", crypto_idstring ? crypto_idstring : "", + fullstatus ? "\n" : ""); + + // Make sure it fits in the buffer + if (length < 0) + goto bad; + + if (fullstatus) + { + char *ptr; + int left; + int savelength; + + savelength = length; + + ptr = out_msg + length; + left = (int)out_size - length; + + for (i = 0;i < (unsigned int)svs.maxclients;i++) + { + client_t *cl = &svs.clients[i]; + if (cl->active) + { + int nameind, cleanind, pingvalue; + char curchar; + char cleanname [sizeof(cl->name)]; + const char *str; + prvm_edict_t *ed; + + // Remove all characters '"' and '\' in the player name + nameind = 0; + cleanind = 0; + do + { + curchar = cl->name[nameind++]; + if (curchar != '"' && curchar != '\\') + { + cleanname[cleanind++] = curchar; + if (cleanind == sizeof(cleanname) - 1) + break; + } + } while (curchar != '\0'); + cleanname[cleanind] = 0; // cleanind is always a valid index even at this point + + pingvalue = (int)(cl->ping * 1000.0f); + if(cl->netconnection) + pingvalue = bound(1, pingvalue, 9999); + else + pingvalue = 0; + + *qcstatus = 0; + ed = PRVM_EDICT_NUM(i + 1); + str = PRVM_GetString(PRVM_serveredictstring(ed, clientstatus)); + if(str && *str) + { + char *p; + const char *q; + p = qcstatus; + for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q) + if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q)) + *p++ = *q; + *p = 0; + } + + if ((gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) && (teamplay.integer > 0)) + { + if(cl->frags == -666) // spectator + strlcpy(teambuf, " 0", sizeof(teambuf)); + else if(cl->colors == 0x44) // red team + strlcpy(teambuf, " 1", sizeof(teambuf)); + else if(cl->colors == 0xDD) // blue team + strlcpy(teambuf, " 2", sizeof(teambuf)); + else if(cl->colors == 0xCC) // yellow team + strlcpy(teambuf, " 3", sizeof(teambuf)); + else if(cl->colors == 0x99) // pink team + strlcpy(teambuf, " 4", sizeof(teambuf)); + else + strlcpy(teambuf, " 0", sizeof(teambuf)); + } + else + *teambuf = 0; + + // note: team number is inserted according to SoF2 protocol + if(*qcstatus) + length = dpsnprintf(ptr, left, "%s %d%s \"%s\"\n", + qcstatus, + pingvalue, + teambuf, + cleanname); + else + length = dpsnprintf(ptr, left, "%d %d%s \"%s\"\n", + cl->frags, + pingvalue, + teambuf, + cleanname); + + if(length < 0) + { + // out of space? + // turn it into an infoResponse! + out_msg[savelength] = 0; + memcpy(out_msg + 4, "infoResponse\x0A", 13); + memmove(out_msg + 17, out_msg + 19, savelength - 19); + break; + } + left -= length; + ptr += length; + } + } + } + + SV_VM_End(); + return true; + +bad: + SV_VM_End(); + return false; +} + +static qboolean NetConn_PreventConnectFlood(lhnetaddress_t *peeraddress) +{ + int floodslotnum, bestfloodslotnum; + double bestfloodtime; + lhnetaddress_t noportpeeraddress; + // see if this is a connect flood + noportpeeraddress = *peeraddress; + LHNETADDRESS_SetPort(&noportpeeraddress, 0); + bestfloodslotnum = 0; + bestfloodtime = sv.connectfloodaddresses[bestfloodslotnum].lasttime; + for (floodslotnum = 0;floodslotnum < MAX_CONNECTFLOODADDRESSES;floodslotnum++) + { + if (bestfloodtime >= sv.connectfloodaddresses[floodslotnum].lasttime) + { + bestfloodtime = sv.connectfloodaddresses[floodslotnum].lasttime; + bestfloodslotnum = floodslotnum; + } + if (sv.connectfloodaddresses[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &sv.connectfloodaddresses[floodslotnum].address) == 0) + { + // this address matches an ongoing flood address + if (realtime < sv.connectfloodaddresses[floodslotnum].lasttime + net_connectfloodblockingtimeout.value) + { + // renew the ban on this address so it does not expire + // until the flood has subsided + sv.connectfloodaddresses[floodslotnum].lasttime = realtime; + //Con_Printf("Flood detected!\n"); + return true; + } + // the flood appears to have subsided, so allow this + bestfloodslotnum = floodslotnum; // reuse the same slot + break; + } + } + // begin a new timeout on this address + sv.connectfloodaddresses[bestfloodslotnum].address = noportpeeraddress; + sv.connectfloodaddresses[bestfloodslotnum].lasttime = realtime; + //Con_Printf("Flood detection initiated!\n"); + return false; +} + +void NetConn_ClearConnectFlood(lhnetaddress_t *peeraddress) +{ + int floodslotnum; + lhnetaddress_t noportpeeraddress; + // see if this is a connect flood + noportpeeraddress = *peeraddress; + LHNETADDRESS_SetPort(&noportpeeraddress, 0); + for (floodslotnum = 0;floodslotnum < MAX_CONNECTFLOODADDRESSES;floodslotnum++) + { + if (sv.connectfloodaddresses[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &sv.connectfloodaddresses[floodslotnum].address) == 0) + { + // this address matches an ongoing flood address + // remove the ban + sv.connectfloodaddresses[floodslotnum].address.addresstype = LHNETADDRESSTYPE_NONE; + sv.connectfloodaddresses[floodslotnum].lasttime = 0; + //Con_Printf("Flood cleared!\n"); + } + } +} + +typedef qboolean (*rcon_matchfunc_t) (lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen); + +qboolean hmac_mdfour_time_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) +{ + char mdfourbuf[16]; + long t1, t2; + + t1 = (long) time(NULL); + t2 = strtol(s, NULL, 0); + if(abs(t1 - t2) > rcon_secure_maxdiff.integer) + return false; + + if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, strlen(password))) + return false; + + return !memcmp(mdfourbuf, hash, 16); +} + +qboolean hmac_mdfour_challenge_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) +{ + char mdfourbuf[16]; + int i; + + if(slen < (int)(sizeof(challenge[0].string)) - 1) + return false; + + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strncmp(challenge[i].string, s, sizeof(challenge[0].string) - 1)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) + return false; + + if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, strlen(password))) + return false; + + if(memcmp(mdfourbuf, hash, 16)) + return false; + + // unmark challenge to prevent replay attacks + challenge[i].time = 0; + + return true; +} + +qboolean plaintext_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) +{ + return !strcmp(password, hash); +} + +/// returns a string describing the user level, or NULL for auth failure +const char *RCon_Authenticate(lhnetaddress_t *peeraddress, const char *password, const char *s, const char *endpos, rcon_matchfunc_t comparator, const char *cs, int cslen) +{ + const char *text, *userpass_start, *userpass_end, *userpass_startpass; + static char buf[MAX_INPUTLINE]; + qboolean hasquotes; + qboolean restricted = false; + qboolean have_usernames = false; + + userpass_start = rcon_password.string; + while((userpass_end = strchr(userpass_start, ' '))) + { + have_usernames = true; + strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1)); + if(buf[0]) + if(comparator(peeraddress, buf, password, cs, cslen)) + goto allow; + userpass_start = userpass_end + 1; + } + if(userpass_start[0]) + { + userpass_end = userpass_start + strlen(userpass_start); + if(comparator(peeraddress, userpass_start, password, cs, cslen)) + goto allow; + } + + restricted = true; + have_usernames = false; + userpass_start = rcon_restricted_password.string; + while((userpass_end = strchr(userpass_start, ' '))) + { + have_usernames = true; + strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1)); + if(buf[0]) + if(comparator(peeraddress, buf, password, cs, cslen)) + goto check; + userpass_start = userpass_end + 1; + } + if(userpass_start[0]) + { + userpass_end = userpass_start + strlen(userpass_start); + if(comparator(peeraddress, userpass_start, password, cs, cslen)) + goto check; + } + + return NULL; // DENIED + +check: + for(text = s; text != endpos; ++text) + if((signed char) *text > 0 && ((signed char) *text < (signed char) ' ' || *text == ';')) + return NULL; // block possible exploits against the parser/alias expansion + + while(s != endpos) + { + size_t l = strlen(s); + if(l) + { + hasquotes = (strchr(s, '"') != NULL); + // sorry, we can't allow these substrings in wildcard expressions, + // as they can mess with the argument counts + text = rcon_restricted_commands.string; + while(COM_ParseToken_Console(&text)) + { + // com_token now contains a pattern to check for... + if(strchr(com_token, '*') || strchr(com_token, '?')) // wildcard expression, * can only match a SINGLE argument + { + if(!hasquotes) + if(matchpattern_with_separator(s, com_token, true, " ", true)) // note how we excluded tab, newline etc. above + goto match; + } + else if(strchr(com_token, ' ')) // multi-arg expression? must match in whole + { + if(!strcmp(com_token, s)) + goto match; + } + else // single-arg expression? must match the beginning of the command + { + if(!strcmp(com_token, s)) + goto match; + if(!memcmp(va("%s ", com_token), s, strlen(com_token) + 1)) + goto match; + } + } + // if we got here, nothing matched! + return NULL; + } +match: + s += l + 1; + } + +allow: + userpass_startpass = strchr(userpass_start, ':'); + if(have_usernames && userpass_startpass && userpass_startpass < userpass_end) + return va("%srcon (username %.*s)", restricted ? "restricted " : "", (int)(userpass_startpass-userpass_start), userpass_start); + + return va("%srcon", restricted ? "restricted " : ""); +} + +void RCon_Execute(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, const char *addressstring2, const char *userlevel, const char *s, const char *endpos, qboolean proquakeprotocol) +{ + if(userlevel) + { + // looks like a legitimate rcon command with the correct password + const char *s_ptr = s; + Con_Printf("server received %s command from %s: ", userlevel, host_client ? host_client->name : addressstring2); + while(s_ptr != endpos) + { + size_t l = strlen(s_ptr); + if(l) + Con_Printf(" %s;", s_ptr); + s_ptr += l + 1; + } + Con_Printf("\n"); + + if (!host_client || !host_client->netconnection || LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP) + Con_Rcon_Redirect_Init(mysocket, peeraddress, proquakeprotocol); + while(s != endpos) + { + size_t l = strlen(s); + if(l) + { + client_t *host_client_save = host_client; + Cmd_ExecuteString(s, src_command); + host_client = host_client_save; + // in case it is a command that changes host_client (like restart) + } + s += l + 1; + } + Con_Rcon_Redirect_End(); + } + else + { + Con_Printf("server denied rcon access to %s\n", host_client ? host_client->name : addressstring2); + } +} + +extern void SV_SendServerinfo (client_t *client); +static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress) +{ + int i, ret, clientnum, best; + double besttime; + client_t *client; + char *s, *string, response[1400], addressstring2[128]; + static char stringbuf[16384]; + qboolean islocal = (LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP); + char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + size_t sendlength, response_len; + + if (!sv.active) + return false; + + // convert the address to a string incase we need it + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + + // see if we can identify the sender as a local player + // (this is necessary for rcon to send a reliable reply if the client is + // actually on the server, not sending remotely) + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if (host_client->netconnection && host_client->netconnection->mysocket == mysocket && !LHNETADDRESS_Compare(&host_client->netconnection->peeraddress, peeraddress)) + break; + if (i == svs.maxclients) + host_client = NULL; + + if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255) + { + // received a command string - strip off the packaging and put it + // into our string buffer with NULL termination + data += 4; + length -= 4; + length = min(length, (int)sizeof(stringbuf) - 1); + memcpy(stringbuf, data, length); + stringbuf[length] = 0; + string = stringbuf; + + if (developer_extra.integer) + { + Con_Printf("NetConn_ServerParsePacket: %s sent us a command:\n", addressstring2); + Com_HexDumpToConsole(data, length); + } + + sendlength = sizeof(senddata) - 4; + switch(Crypto_ServerParsePacket(string, length, senddata+4, &sendlength, peeraddress)) + { + case CRYPTO_NOMATCH: + // nothing to do + break; + case CRYPTO_MATCH: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + break; + case CRYPTO_DISCARD: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + return true; + break; + case CRYPTO_REPLACE: + string = senddata+4; + length = sendlength; + break; + } + + if (length >= 12 && !memcmp(string, "getchallenge", 12) && (islocal || sv_public.integer > -3)) + { + for (i = 0, best = 0, besttime = realtime;i < MAX_CHALLENGES;i++) + { + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address)) + break; + if (besttime > challenge[i].time) + besttime = challenge[best = i].time; + } + // if we did not find an exact match, choose the oldest and + // update address and string + if (i == MAX_CHALLENGES) + { + i = best; + challenge[i].address = *peeraddress; + NetConn_BuildChallengeString(challenge[i].string, sizeof(challenge[i].string)); + } + challenge[i].time = realtime; + // send the challenge + dpsnprintf(response, sizeof(response), "\377\377\377\377challenge %s", challenge[i].string); + response_len = strlen(response) + 1; + Crypto_ServerAppendToChallenge(string, length, response, &response_len, sizeof(response)); + NetConn_Write(mysocket, response, response_len, peeraddress); + return true; + } + if (length > 8 && !memcmp(string, "connect\\", 8)) + { + crypto_t *crypto = Crypto_ServerGetInstance(peeraddress); + string += 7; + length -= 7; + + if(crypto && crypto->authenticated) + { + // no need to check challenge + if(crypto_developer.integer) + { + Con_Printf("%s connection to %s is being established: client is %s@%.*s, I am %.*s@%.*s\n", + crypto->use_aes ? "Encrypted" : "Authenticated", + addressstring2, + crypto->client_idfp[0] ? crypto->client_idfp : "-", + crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-", + crypto_keyfp_recommended_length, crypto->server_idfp[0] ? crypto->server_idfp : "-", + crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-" + ); + } + } + else + { + if ((s = SearchInfostring(string, "challenge"))) + { + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) + return true; + } + } + + if((s = SearchInfostring(string, "message"))) + Con_DPrintf("Connecting client %s sent us the message: %s\n", addressstring2, s); + + if(!(islocal || sv_public.integer > -2)) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject %s\" to %s.\n", sv_public_rejectreason.string, addressstring2); + NetConn_WriteString(mysocket, va("\377\377\377\377reject %s", sv_public_rejectreason.string), peeraddress); + return true; + } + + // check engine protocol + if(!(s = SearchInfostring(string, "protocol")) || strcmp(s, "darkplaces 3")) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Wrong game protocol.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Wrong game protocol.", peeraddress); + return true; + } + + // see if this is a duplicate connection request or a disconnected + // client who is rejoining to the same client slot + for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) + { + if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0) + { + // this is a known client... + if(crypto && crypto->authenticated) + { + // reject if changing key! + if(client->netconnection->crypto.authenticated) + { + if( + strcmp(client->netconnection->crypto.client_idfp, crypto->client_idfp) + || + strcmp(client->netconnection->crypto.server_idfp, crypto->server_idfp) + || + strcmp(client->netconnection->crypto.client_keyfp, crypto->client_keyfp) + || + strcmp(client->netconnection->crypto.server_keyfp, crypto->server_keyfp) + ) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to change key of crypto.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to change key of crypto.", peeraddress); + return true; + } + } + } + else + { + // reject if downgrading! + if(client->netconnection->crypto.authenticated) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to downgrade crypto.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to downgrade crypto.", peeraddress); + return true; + } + } + if (client->spawned) + { + // client crashed and is coming back, + // keep their stuff intact + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&client->netconnection->crypto, crypto); + SV_VM_Begin(); + SV_SendServerinfo(client); + SV_VM_End(); + } + else + { + // client is still trying to connect, + // so we send a duplicate reply + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending duplicate accept to %s.\n", addressstring2); + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&client->netconnection->crypto, crypto); + NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); + } + return true; + } + } + + if (NetConn_PreventConnectFlood(peeraddress)) + return true; + + // find an empty client slot for this new client + for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) + { + netconn_t *conn; + if (!client->active && (conn = NetConn_Open(mysocket, peeraddress))) + { + // allocated connection + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", conn->address); + NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); + // now set up the client + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&conn->crypto, crypto); + SV_VM_Begin(); + SV_ConnectClient(clientnum, conn); + SV_VM_End(); + NetConn_Heartbeat(1); + return true; + } + } + + // no empty slots found - server is full + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Server is full.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Server is full.", peeraddress); + + return true; + } + if (length >= 7 && !memcmp(string, "getinfo", 7) && (islocal || sv_public.integer > -1)) + { + const char *challenge = NULL; + + // If there was a challenge in the getinfo message + if (length > 8 && string[7] == ' ') + challenge = string + 8; + + if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), false)) + { + if (developer_extra.integer) + Con_DPrintf("Sending reply to master %s - %s\n", addressstring2, response); + NetConn_WriteString(mysocket, response, peeraddress); + } + return true; + } + if (length >= 9 && !memcmp(string, "getstatus", 9) && (islocal || sv_public.integer > -1)) + { + const char *challenge = NULL; + + // If there was a challenge in the getinfo message + if (length > 10 && string[9] == ' ') + challenge = string + 10; + + if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), true)) + { + if (developer_extra.integer) + Con_DPrintf("Sending reply to client %s - %s\n", addressstring2, response); + NetConn_WriteString(mysocket, response, peeraddress); + } + return true; + } + if (length >= 37 && !memcmp(string, "srcon HMAC-MD4 TIME ", 20)) + { + char *password = string + 20; + char *timeval = string + 37; + char *s = strchr(timeval, ' '); + char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it + const char *userlevel; + + if(rcon_secure.integer > 1) + return true; + + if(!s) + return true; // invalid packet + ++s; + + userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_time_matching, timeval, endpos - timeval - 1); // not including the appended \0 into the HMAC + RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); + return true; + } + if (length >= 42 && !memcmp(string, "srcon HMAC-MD4 CHALLENGE ", 25)) + { + char *password = string + 25; + char *challenge = string + 42; + char *s = strchr(challenge, ' '); + char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it + const char *userlevel; + if(!s) + return true; // invalid packet + ++s; + + userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_challenge_matching, challenge, endpos - challenge - 1); // not including the appended \0 into the HMAC + RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); + return true; + } + if (length >= 5 && !memcmp(string, "rcon ", 5)) + { + int i; + char *s = string + 5; + char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it + char password[64]; + + if(rcon_secure.integer > 0) + return true; + + for (i = 0;!ISWHITESPACE(*s);s++) + if (i < (int)sizeof(password) - 1) + password[i++] = *s; + if(ISWHITESPACE(*s) && s != endpos) // skip leading ugly space + ++s; + password[i] = 0; + if (!ISWHITESPACE(password[0])) + { + const char *userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0); + RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); + } + return true; + } + if (!strncmp(string, "extResponse ", 12)) + { + ++sv_net_extresponse_count; + if(sv_net_extresponse_count > NET_EXTRESPONSE_MAX) + sv_net_extresponse_count = NET_EXTRESPONSE_MAX; + sv_net_extresponse_last = (sv_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX; + dpsnprintf(sv_net_extresponse[sv_net_extresponse_last], sizeof(sv_net_extresponse[sv_net_extresponse_last]), "'%s' %s", addressstring2, string + 12); + return true; + } + if (!strncmp(string, "ping", 4)) + { + if (developer_extra.integer) + Con_DPrintf("Received ping from %s, sending ack\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress); + return true; + } + if (!strncmp(string, "ack", 3)) + return true; + // we may not have liked the packet, but it was a command packet, so + // we're done processing this packet now + return true; + } + // netquake control packets, supported for compatibility only, and only + // when running game protocols that are normally served via this connection + // protocol + // (this protects more modern protocols against being used for + // Quake packet flood Denial Of Service attacks) + if (length >= 5 && (i = BuffBigLong(data)) && (i & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (i & NETFLAG_LENGTH_MASK) == length && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) && !ENCRYPTION_REQUIRED) + { + int c; + int protocolnumber; + const char *protocolname; + data += 4; + length -= 4; + SZ_Clear(&net_message); + SZ_Write(&net_message, data, length); + MSG_BeginReading(); + c = MSG_ReadByte(); + switch (c) + { + case CCREQ_CONNECT: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_CONNECT from %s.\n", addressstring2); + if(!(islocal || sv_public.integer > -2)) + { + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"%s\" to %s.\n", sv_public_rejectreason.string, addressstring2); + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_REJECT); + MSG_WriteString(&net_message, va("%s\n", sv_public_rejectreason.string)); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); + SZ_Clear(&net_message); + break; + } + + protocolname = MSG_ReadString(); + protocolnumber = MSG_ReadByte(); + if (strcmp(protocolname, "QUAKE") || protocolnumber != NET_PROTOCOL_VERSION) + { + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Incompatible version.\" to %s.\n", addressstring2); + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_REJECT); + MSG_WriteString(&net_message, "Incompatible version.\n"); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); + SZ_Clear(&net_message); + break; + } + + // see if this connect request comes from a known client + for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) + { + if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0) + { + // this is either a duplicate connection request + // or coming back from a timeout + // (if so, keep their stuff intact) + + // send a reply + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending duplicate CCREP_ACCEPT to %s.\n", addressstring2); + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_ACCEPT); + MSG_WriteLong(&net_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(client->netconnection->mysocket))); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); + SZ_Clear(&net_message); + + // if client is already spawned, re-send the + // serverinfo message as they'll need it to play + if (client->spawned) + { + SV_VM_Begin(); + SV_SendServerinfo(client); + SV_VM_End(); + } + return true; + } + } + + // this is a new client, check for connection flood + if (NetConn_PreventConnectFlood(peeraddress)) + break; + + // find a slot for the new client + for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) + { + netconn_t *conn; + if (!client->active && (client->netconnection = conn = NetConn_Open(mysocket, peeraddress)) != NULL) + { + // connect to the client + // everything is allocated, just fill in the details + strlcpy (conn->address, addressstring2, sizeof (conn->address)); + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_ACCEPT to %s.\n", addressstring2); + // send back the info about the server connection + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_ACCEPT); + MSG_WriteLong(&net_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(conn->mysocket))); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); + SZ_Clear(&net_message); + // now set up the client struct + SV_VM_Begin(); + SV_ConnectClient(clientnum, conn); + SV_VM_End(); + NetConn_Heartbeat(1); + return true; + } + } + + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Server is full.\" to %s.\n", addressstring2); + // no room; try to let player know + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_REJECT); + MSG_WriteString(&net_message, "Server is full.\n"); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); + SZ_Clear(&net_message); + break; + case CCREQ_SERVER_INFO: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_SERVER_INFO from %s.\n", addressstring2); + if(!(islocal || sv_public.integer > -1)) + break; + if (sv.active && !strcmp(MSG_ReadString(), "QUAKE")) + { + int numclients; + char myaddressstring[128]; + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_SERVER_INFO to %s.\n", addressstring2); + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_SERVER_INFO); + LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), myaddressstring, sizeof(myaddressstring), true); + MSG_WriteString(&net_message, myaddressstring); + MSG_WriteString(&net_message, hostname.string); + MSG_WriteString(&net_message, sv.name); + // How many clients are there? + for (i = 0, numclients = 0;i < svs.maxclients;i++) + if (svs.clients[i].active) + numclients++; + MSG_WriteByte(&net_message, numclients); + MSG_WriteByte(&net_message, svs.maxclients); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); + SZ_Clear(&net_message); + } + break; + case CCREQ_PLAYER_INFO: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_PLAYER_INFO from %s.\n", addressstring2); + if(!(islocal || sv_public.integer > -1)) + break; + if (sv.active) + { + int playerNumber, activeNumber, clientNumber; + client_t *client; + + playerNumber = MSG_ReadByte(); + activeNumber = -1; + for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++) + if (client->active && ++activeNumber == playerNumber) + break; + if (clientNumber != svs.maxclients) + { + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_PLAYER_INFO); + MSG_WriteByte(&net_message, playerNumber); + MSG_WriteString(&net_message, client->name); + MSG_WriteLong(&net_message, client->colors); + MSG_WriteLong(&net_message, client->frags); + MSG_WriteLong(&net_message, (int)(realtime - client->connecttime)); + if(sv_status_privacy.integer) + MSG_WriteString(&net_message, client->netconnection ? "hidden" : "botclient"); + else + MSG_WriteString(&net_message, client->netconnection ? client->netconnection->address : "botclient"); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); + SZ_Clear(&net_message); + } + } + break; + case CCREQ_RULE_INFO: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RULE_INFO from %s.\n", addressstring2); + if(!(islocal || sv_public.integer > -1)) + break; + if (sv.active) + { + char *prevCvarName; + cvar_t *var; + + // find the search start location + prevCvarName = MSG_ReadString(); + var = Cvar_FindVarAfter(prevCvarName, CVAR_NOTIFY); + + // send the response + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_RULE_INFO); + if (var) + { + MSG_WriteString(&net_message, var->name); + MSG_WriteString(&net_message, var->string); + } + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, net_message.data, net_message.cursize, peeraddress); + SZ_Clear(&net_message); + } + break; + case CCREQ_RCON: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RCON from %s.\n", addressstring2); + if (sv.active && !rcon_secure.integer) + { + char password[2048]; + char cmd[2048]; + char *s; + char *endpos; + const char *userlevel; + strlcpy(password, MSG_ReadString(), sizeof(password)); + strlcpy(cmd, MSG_ReadString(), sizeof(cmd)); + s = cmd; + endpos = cmd + strlen(cmd) + 1; // one behind the NUL, so adding strlen+1 will eventually reach it + userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0); + RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, true); + return true; + } + break; + default: + break; + } + SZ_Clear(&net_message); + // we may not have liked the packet, but it was a valid control + // packet, so we're done processing this packet now + return true; + } + if (host_client) + { + if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length, sv.protocol, host_client->spawned ? net_messagetimeout.value : net_connecttimeout.value)) == 2) + { + SV_VM_Begin(); + SV_ReadClientMessage(); + SV_VM_End(); + return ret; + } + } + return 0; +} + +void NetConn_ServerFrame(void) +{ + int i, length; + lhnetaddress_t peeraddress; + for (i = 0;i < sv_numsockets;i++) + while (sv_sockets[i] && (length = NetConn_Read(sv_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0) + NetConn_ServerParsePacket(sv_sockets[i], readbuffer, length, &peeraddress); + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + // never timeout loopback connections + if (host_client->netconnection && realtime > host_client->netconnection->timeout && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP) + { + Con_Printf("Client \"%s\" connection timed out\n", host_client->name); + SV_VM_Begin(); + SV_DropClient(false); + SV_VM_End(); + } + } +} + +void NetConn_SleepMicroseconds(int microseconds) +{ + LHNET_SleepUntilPacket_Microseconds(microseconds); +} + +void NetConn_QueryMasters(qboolean querydp, qboolean queryqw) +{ + int i, j; + int masternum; + lhnetaddress_t masteraddress; + lhnetaddress_t broadcastaddress; + char request[256]; + + if (serverlist_cachecount >= SERVERLIST_TOTALSIZE) + return; + + // 26000 is the default quake server port, servers on other ports will not + // be found + // note this is IPv4-only, I doubt there are IPv6-only LANs out there + LHNETADDRESS_FromString(&broadcastaddress, "255.255.255.255", 26000); + + if (querydp) + { + for (i = 0;i < cl_numsockets;i++) + { + if (cl_sockets[i]) + { + const char *cmdname, *extraoptions; + int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])); + + if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af) + { + // search LAN for Quake servers + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_SERVER_INFO); + MSG_WriteString(&net_message, "QUAKE"); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(cl_sockets[i], net_message.data, net_message.cursize, &broadcastaddress); + SZ_Clear(&net_message); + + // search LAN for DarkPlaces servers + NetConn_WriteString(cl_sockets[i], "\377\377\377\377getstatus", &broadcastaddress); + } + + // build the getservers message to send to the dpmaster master servers + if (LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == LHNETADDRESSTYPE_INET6) + { + cmdname = "getserversExt"; + extraoptions = " ipv4 ipv6"; // ask for IPv4 and IPv6 servers + } + else + { + cmdname = "getservers"; + extraoptions = ""; + } + dpsnprintf(request, sizeof(request), "\377\377\377\377%s %s %u empty full%s", cmdname, gamename, NET_PROTOCOL_VERSION, extraoptions); + + // search internet + for (masternum = 0;sv_masters[masternum].name;masternum++) + { + if (sv_masters[masternum].string && sv_masters[masternum].string[0] && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == af) + { + masterquerycount++; + NetConn_WriteString(cl_sockets[i], request, &masteraddress); + } + } + + // search favorite servers + for(j = 0; j < nFavorites; ++j) + { + if(LHNETADDRESS_GetAddressType(&favorites[j]) == af) + { + if(LHNETADDRESS_ToString(&favorites[j], request, sizeof(request), true)) + NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, request, true ); + } + } + } + } + } + + // only query QuakeWorld servers when the user wants to + if (queryqw) + { + for (i = 0;i < cl_numsockets;i++) + { + if (cl_sockets[i]) + { + int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])); + + if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af) + { + // search LAN for QuakeWorld servers + NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &broadcastaddress); + + // build the getservers message to send to the qwmaster master servers + // note this has no -1 prefix, and the trailing nul byte is sent + dpsnprintf(request, sizeof(request), "c\n"); + } + + // search internet + for (masternum = 0;sv_qwmasters[masternum].name;masternum++) + { + if (sv_qwmasters[masternum].string && LHNETADDRESS_FromString(&masteraddress, sv_qwmasters[masternum].string, QWMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i]))) + { + if (m_state != m_slist) + { + char lookupstring[128]; + LHNETADDRESS_ToString(&masteraddress, lookupstring, sizeof(lookupstring), true); + Con_Printf("Querying master %s (resolved from %s)\n", lookupstring, sv_qwmasters[masternum].string); + } + masterquerycount++; + NetConn_Write(cl_sockets[i], request, (int)strlen(request) + 1, &masteraddress); + } + } + + // search favorite servers + for(j = 0; j < nFavorites; ++j) + { + if(LHNETADDRESS_GetAddressType(&favorites[j]) == af) + { + if(LHNETADDRESS_ToString(&favorites[j], request, sizeof(request), true)) + { + NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &favorites[j]); + NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_QUAKEWORLD, request, true ); + } + } + } + } + } + } + if (!masterquerycount) + { + Con_Print("Unable to query master servers, no suitable network sockets active.\n"); + M_Update_Return_Reason("No network"); + } +} + +void NetConn_Heartbeat(int priority) +{ + lhnetaddress_t masteraddress; + int masternum; + lhnetsocket_t *mysocket; + + // if it's a state change (client connected), limit next heartbeat to no + // more than 30 sec in the future + if (priority == 1 && nextheartbeattime > realtime + 30.0) + nextheartbeattime = realtime + 30.0; + + // limit heartbeatperiod to 30 to 270 second range, + // lower limit is to avoid abusing master servers with excess traffic, + // upper limit is to avoid timing out on the master server (which uses + // 300 sec timeout) + if (sv_heartbeatperiod.value < 30) + Cvar_SetValueQuick(&sv_heartbeatperiod, 30); + if (sv_heartbeatperiod.value > 270) + Cvar_SetValueQuick(&sv_heartbeatperiod, 270); + + // make advertising optional and don't advertise singleplayer games, and + // only send a heartbeat as often as the admin wants + if (sv.active && sv_public.integer > 0 && svs.maxclients >= 2 && (priority > 1 || realtime > nextheartbeattime)) + { + nextheartbeattime = realtime + sv_heartbeatperiod.value; + for (masternum = 0;sv_masters[masternum].name;masternum++) + if (sv_masters[masternum].string && sv_masters[masternum].string[0] && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT) && (mysocket = NetConn_ChooseServerSocketForAddress(&masteraddress))) + NetConn_WriteString(mysocket, "\377\377\377\377heartbeat DarkPlaces\x0A", &masteraddress); + } +} + +static void Net_Heartbeat_f(void) +{ + if (sv.active) + NetConn_Heartbeat(2); + else + Con_Print("No server running, can not heartbeat to master server.\n"); +} + +void PrintStats(netconn_t *conn) +{ + if ((cls.state == ca_connected && cls.protocol == PROTOCOL_QUAKEWORLD) || (sv.active && sv.protocol == PROTOCOL_QUAKEWORLD)) + Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->outgoing_unreliable_sequence, conn->qw.incoming_sequence); + else + Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->nq.sendSequence, conn->nq.receiveSequence); +} + +void Net_Stats_f(void) +{ + netconn_t *conn; + Con_Printf("unreliable messages sent = %i\n", unreliableMessagesSent); + Con_Printf("unreliable messages recv = %i\n", unreliableMessagesReceived); + Con_Printf("reliable messages sent = %i\n", reliableMessagesSent); + Con_Printf("reliable messages received = %i\n", reliableMessagesReceived); + Con_Printf("packetsSent = %i\n", packetsSent); + Con_Printf("packetsReSent = %i\n", packetsReSent); + Con_Printf("packetsReceived = %i\n", packetsReceived); + Con_Printf("receivedDuplicateCount = %i\n", receivedDuplicateCount); + Con_Printf("droppedDatagrams = %i\n", droppedDatagrams); + Con_Print("connections =\n"); + for (conn = netconn_list;conn;conn = conn->next) + PrintStats(conn); +} + +void Net_Refresh_f(void) +{ + if (m_state != m_slist) { + Con_Print("Sending new requests to master servers\n"); + ServerList_QueryList(false, true, false, true); + Con_Print("Listening for replies...\n"); + } else + ServerList_QueryList(false, true, false, false); +} + +void Net_Slist_f(void) +{ + ServerList_ResetMasks(); + serverlist_sortbyfield = SLIF_PING; + serverlist_sortflags = 0; + if (m_state != m_slist) { + Con_Print("Sending requests to master servers\n"); + ServerList_QueryList(true, true, false, true); + Con_Print("Listening for replies...\n"); + } else + ServerList_QueryList(true, true, false, false); +} + +void Net_SlistQW_f(void) +{ + ServerList_ResetMasks(); + serverlist_sortbyfield = SLIF_PING; + serverlist_sortflags = 0; + if (m_state != m_slist) { + Con_Print("Sending requests to master servers\n"); + ServerList_QueryList(true, false, true, true); + serverlist_consoleoutput = true; + Con_Print("Listening for replies...\n"); + } else + ServerList_QueryList(true, false, true, false); +} + +void NetConn_Init(void) +{ + int i; + lhnetaddress_t tempaddress; + netconn_mempool = Mem_AllocPool("network connections", 0, NULL); + Cmd_AddCommand("net_stats", Net_Stats_f, "print network statistics"); + Cmd_AddCommand("net_slist", Net_Slist_f, "query dp master servers and print all server information"); + Cmd_AddCommand("net_slistqw", Net_SlistQW_f, "query qw master servers and print all server information"); + Cmd_AddCommand("net_refresh", Net_Refresh_f, "query dp master servers and refresh all server information"); + Cmd_AddCommand("heartbeat", Net_Heartbeat_f, "send a heartbeat to the master server (updates your server information)"); + Cvar_RegisterVariable(&rcon_restricted_password); + Cvar_RegisterVariable(&rcon_restricted_commands); + Cvar_RegisterVariable(&rcon_secure_maxdiff); + Cvar_RegisterVariable(&net_slist_queriespersecond); + Cvar_RegisterVariable(&net_slist_queriesperframe); + Cvar_RegisterVariable(&net_slist_timeout); + Cvar_RegisterVariable(&net_slist_maxtries); + Cvar_RegisterVariable(&net_slist_favorites); + Cvar_RegisterVariable(&net_slist_pause); + Cvar_RegisterVariable(&net_messagetimeout); + Cvar_RegisterVariable(&net_connecttimeout); + Cvar_RegisterVariable(&net_connectfloodblockingtimeout); + Cvar_RegisterVariable(&cl_netlocalping); + Cvar_RegisterVariable(&cl_netpacketloss_send); + Cvar_RegisterVariable(&cl_netpacketloss_receive); + Cvar_RegisterVariable(&hostname); + Cvar_RegisterVariable(&developer_networking); + Cvar_RegisterVariable(&cl_netport); + Cvar_RegisterVariable(&sv_netport); + Cvar_RegisterVariable(&net_address); + Cvar_RegisterVariable(&net_address_ipv6); + Cvar_RegisterVariable(&sv_public); + Cvar_RegisterVariable(&sv_public_rejectreason); + Cvar_RegisterVariable(&sv_heartbeatperiod); + for (i = 0;sv_masters[i].name;i++) + Cvar_RegisterVariable(&sv_masters[i]); + Cvar_RegisterVariable(&gameversion); + Cvar_RegisterVariable(&gameversion_min); + Cvar_RegisterVariable(&gameversion_max); +// COMMANDLINEOPTION: Server: -ip sets the ip address of this machine for purposes of networking (default 0.0.0.0 also known as INADDR_ANY), use only if you have multiple network adapters and need to choose one specifically. + if ((i = COM_CheckParm("-ip")) && i + 1 < com_argc) + { + if (LHNETADDRESS_FromString(&tempaddress, com_argv[i + 1], 0) == 1) + { + Con_Printf("-ip option used, setting net_address to \"%s\"\n", com_argv[i + 1]); + Cvar_SetQuick(&net_address, com_argv[i + 1]); + } + else + Con_Printf("-ip option used, but unable to parse the address \"%s\"\n", com_argv[i + 1]); + } +// COMMANDLINEOPTION: Server: -port sets the port to use for a server (default 26000, the same port as QUAKE itself), useful if you host multiple servers on your machine + if (((i = COM_CheckParm("-port")) || (i = COM_CheckParm("-ipport")) || (i = COM_CheckParm("-udpport"))) && i + 1 < com_argc) + { + i = atoi(com_argv[i + 1]); + if (i >= 0 && i < 65536) + { + Con_Printf("-port option used, setting port cvar to %i\n", i); + Cvar_SetValueQuick(&sv_netport, i); + } + else + Con_Printf("-port option used, but %i is not a valid port number\n", i); + } + cl_numsockets = 0; + sv_numsockets = 0; + net_message.data = net_message_buf; + net_message.maxsize = sizeof(net_message_buf); + net_message.cursize = 0; + LHNET_Init(); +} + +void NetConn_Shutdown(void) +{ + NetConn_CloseClientPorts(); + NetConn_CloseServerPorts(); + LHNET_Shutdown(); +} + diff --git a/misc/source/darkplaces-src/netconn.h b/misc/source/darkplaces-src/netconn.h new file mode 100644 index 00000000..f93d297e --- /dev/null +++ b/misc/source/darkplaces-src/netconn.h @@ -0,0 +1,458 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2003 Forest Hale + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef NET_H +#define NET_H + +#include "lhnet.h" + +#define NET_HEADERSIZE (2 * sizeof(unsigned int)) + +// NetHeader flags +#define NETFLAG_LENGTH_MASK 0x0000ffff +#define NETFLAG_DATA 0x00010000 +#define NETFLAG_ACK 0x00020000 +#define NETFLAG_NAK 0x00040000 +#define NETFLAG_EOM 0x00080000 +#define NETFLAG_UNRELIABLE 0x00100000 +#define NETFLAG_CTL 0x80000000 +#define NETFLAG_CRYPTO 0x40000000 + + +#define NET_PROTOCOL_VERSION 3 +#define NET_EXTRESPONSE_MAX 16 + +/// \page netconn The network info/connection protocol. +/// It is used to find Quake +/// servers, get info about them, and connect to them. Once connected, the +/// Quake game protocol (documented elsewhere) is used. +/// +/// +/// General notes:\code +/// game_name is currently always "QUAKE", but is there so this same protocol +/// can be used for future games as well; can you say Quake2? +/// +/// CCREQ_CONNECT +/// string game_name "QUAKE" +/// byte net_protocol_version NET_PROTOCOL_VERSION +/// +/// CCREQ_SERVER_INFO +/// string game_name "QUAKE" +/// byte net_protocol_version NET_PROTOCOL_VERSION +/// +/// CCREQ_PLAYER_INFO +/// byte player_number +/// +/// CCREQ_RULE_INFO +/// string rule +/// +/// CCREQ_RCON +/// string password +/// string command +/// +/// +/// +/// CCREP_ACCEPT +/// long port +/// +/// CCREP_REJECT +/// string reason +/// +/// CCREP_SERVER_INFO +/// string server_address +/// string host_name +/// string level_name +/// byte current_players +/// byte max_players +/// byte protocol_version NET_PROTOCOL_VERSION +/// +/// CCREP_PLAYER_INFO +/// byte player_number +/// string name +/// long colors +/// long frags +/// long connect_time +/// string address +/// +/// CCREP_RULE_INFO +/// string rule +/// string value +/// +/// CCREP_RCON +/// string reply +/// \endcode +/// \note +/// There are two address forms used above. The short form is just a +/// port number. The address that goes along with the port is defined as +/// "whatever address you receive this reponse from". This lets us use +/// the host OS to solve the problem of multiple host addresses (possibly +/// with no routing between them); the host will use the right address +/// when we reply to the inbound connection request. The long from is +/// a full address and port in a string. It is used for returning the +/// address of a server that is not running locally. + +#define CCREQ_CONNECT 0x01 +#define CCREQ_SERVER_INFO 0x02 +#define CCREQ_PLAYER_INFO 0x03 +#define CCREQ_RULE_INFO 0x04 +#define CCREQ_RCON 0x05 // RocketGuy: ProQuake rcon support + +#define CCREP_ACCEPT 0x81 +#define CCREP_REJECT 0x82 +#define CCREP_SERVER_INFO 0x83 +#define CCREP_PLAYER_INFO 0x84 +#define CCREP_RULE_INFO 0x85 +#define CCREP_RCON 0x86 // RocketGuy: ProQuake rcon support + +typedef struct netgraphitem_s +{ + double time; + int reliablebytes; + int unreliablebytes; + int ackbytes; +} +netgraphitem_t; + +typedef struct netconn_s +{ + struct netconn_s *next; + + lhnetsocket_t *mysocket; + lhnetaddress_t peeraddress; + + // this is mostly identical to qsocket_t from quake + + /// if this time is reached, kick off peer + double connecttime; + double timeout; + double lastMessageTime; + double lastSendTime; + + /// writing buffer to send to peer as the next reliable message + /// can be added to at any time, copied into sendMessage buffer when it is + /// possible to send a reliable message and then cleared + /// @{ + sizebuf_t message; + unsigned char messagedata[NET_MAXMESSAGE]; + /// @} + + /// reliable message that is currently sending + /// (for building fragments) + int sendMessageLength; + unsigned char sendMessage[NET_MAXMESSAGE]; + + /// reliable message that is currently being received + /// (for putting together fragments) + int receiveMessageLength; + unsigned char receiveMessage[NET_MAXMESSAGE]; + + /// used by both NQ and QW protocols + unsigned int outgoing_unreliable_sequence; + + struct netconn_nq_s + { + unsigned int ackSequence; + unsigned int sendSequence; + + unsigned int receiveSequence; + unsigned int unreliableReceiveSequence; + } + nq; + struct netconn_qw_s + { + // QW protocol + qboolean fatal_error; + + float last_received; // for timeouts + + // the statistics are cleared at each client begin, because + // the server connecting process gives a bogus picture of the data + float frame_latency; // rolling average + float frame_rate; + + int drop_count; ///< dropped packets, cleared each level + int good_count; ///< cleared each level + + int qport; + + // sequencing variables + int incoming_sequence; + int incoming_acknowledged; + int incoming_reliable_acknowledged; ///< single bit + + int incoming_reliable_sequence; ///< single bit, maintained local + + int reliable_sequence; ///< single bit + int last_reliable_sequence; ///< sequence number of last send + } + qw; + + // bandwidth estimator + double cleartime; // if realtime > nc->cleartime, free to go + + // this tracks packet loss and packet sizes on the most recent packets + // used by shownetgraph feature +#define NETGRAPH_PACKETS 256 +#define NETGRAPH_NOPACKET 0 +#define NETGRAPH_LOSTPACKET -1 +#define NETGRAPH_CHOKEDPACKET -2 + int incoming_packetcounter; + netgraphitem_t incoming_netgraph[NETGRAPH_PACKETS]; + int outgoing_packetcounter; + netgraphitem_t outgoing_netgraph[NETGRAPH_PACKETS]; + + char address[128]; + crypto_t crypto; +} netconn_t; + +extern netconn_t *netconn_list; +extern mempool_t *netconn_mempool; + +extern cvar_t hostname; +extern cvar_t developer_networking; + +#define SERVERLIST_TOTALSIZE 2048 +#define SERVERLIST_VIEWLISTSIZE SERVERLIST_TOTALSIZE +#define SERVERLIST_ANDMASKCOUNT 5 +#define SERVERLIST_ORMASKCOUNT 5 + +typedef enum serverlist_maskop_e +{ + // SLMO_CONTAINS is the default for strings + // SLMO_GREATEREQUAL is the default for numbers (also used when OP == CONTAINS or NOTCONTAINS + SLMO_CONTAINS, + SLMO_NOTCONTAIN, + + SLMO_LESSEQUAL, + SLMO_LESS, + SLMO_EQUAL, + SLMO_GREATER, + SLMO_GREATEREQUAL, + SLMO_NOTEQUAL, + SLMO_STARTSWITH, + SLMO_NOTSTARTSWITH +} serverlist_maskop_t; + +/// struct with all fields that you can search for or sort by +typedef struct serverlist_info_s +{ + /// address for connecting + char cname[128]; + /// ping time for sorting servers + int ping; + /// name of the game + char game[32]; + /// name of the mod + char mod[32]; + /// name of the map + char map[32]; + /// name of the session + char name[128]; + /// qc-defined short status string + char qcstatus[128]; + /// frags/ping/name list (if they fit in the packet) + char players[1400]; + /// max client number + int maxplayers; + /// number of currently connected players (including bots) + int numplayers; + /// number of currently connected players that are bots + int numbots; + /// number of currently connected players that are not bots + int numhumans; + /// number of free slots + int freeslots; + /// protocol version + int protocol; + /// game data version + /// (an integer that is used for filtering incompatible servers, + /// not filterable by QC) + int gameversion; + /// favorite server flag + qboolean isfavorite; +} serverlist_info_t; + +typedef enum +{ + SLIF_CNAME, + SLIF_PING, + SLIF_GAME, + SLIF_MOD, + SLIF_MAP, + SLIF_NAME, + SLIF_MAXPLAYERS, + SLIF_NUMPLAYERS, + SLIF_PROTOCOL, + SLIF_NUMBOTS, + SLIF_NUMHUMANS, + SLIF_FREESLOTS, + SLIF_QCSTATUS, + SLIF_PLAYERS, + SLIF_ISFAVORITE, + SLIF_COUNT +} serverlist_infofield_t; + +typedef enum +{ + SLSF_DESCENDING = 1, + SLSF_FAVORITESFIRST = 2 +} serverlist_sortflags_t; + +typedef enum +{ + SQS_NONE = 0, + SQS_QUERYING, + SQS_QUERIED, + SQS_TIMEDOUT, + SQS_REFRESHING +} serverlist_query_state; + +typedef struct serverlist_entry_s +{ + /// used to determine whether this entry should be included into the final view + serverlist_query_state query; + /// used to count the number of times the host has tried to query this server already + unsigned querycounter; + /// used to calculate ping when update comes in + double querytime; + /// query protocol to use on this server, may be PROTOCOL_QUAKEWORLD or PROTOCOL_DARKPLACES7 + int protocol; + + serverlist_info_t info; + + // legacy stuff + char line1[128]; + char line2[128]; +} serverlist_entry_t; + +typedef struct serverlist_mask_s +{ + qboolean active; + serverlist_maskop_t tests[SLIF_COUNT]; + serverlist_info_t info; +} serverlist_mask_t; + +#define ServerList_GetCacheEntry(x) (&serverlist_cache[(x)]) +#define ServerList_GetViewEntry(x) (ServerList_GetCacheEntry(serverlist_viewlist[(x)])) + +extern serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT]; +extern serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT]; + +extern serverlist_infofield_t serverlist_sortbyfield; +extern int serverlist_sortflags; // not using the enum, as it is a bitmask + +#if SERVERLIST_TOTALSIZE > 65536 +#error too many servers, change type of index array +#endif +extern int serverlist_viewcount; +extern unsigned short serverlist_viewlist[SERVERLIST_VIEWLISTSIZE]; + +extern int serverlist_cachecount; +extern serverlist_entry_t *serverlist_cache; + +extern qboolean serverlist_consoleoutput; + +void ServerList_GetPlayerStatistics(int *numplayerspointer, int *maxplayerspointer); + +//============================================================================ +// +// public network functions +// +//============================================================================ + +extern char cl_net_extresponse[NET_EXTRESPONSE_MAX][1400]; +extern int cl_net_extresponse_count; +extern int cl_net_extresponse_last; + +extern char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400]; +extern int sv_net_extresponse_count; +extern int sv_net_extresponse_last; + +extern double masterquerytime; +extern int masterquerycount; +extern int masterreplycount; +extern int serverquerycount; +extern int serverreplycount; + +extern sizebuf_t net_message; + +extern cvar_t sv_public; + +extern cvar_t cl_netlocalping; + +extern cvar_t cl_netport; +extern cvar_t sv_netport; +extern cvar_t net_address; +extern cvar_t net_address_ipv6; + +qboolean NetConn_CanSend(netconn_t *conn); +int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, qboolean quakesignon_suppressreliables); +qboolean NetConn_HaveClientPorts(void); +qboolean NetConn_HaveServerPorts(void); +void NetConn_CloseClientPorts(void); +void NetConn_OpenClientPorts(void); +void NetConn_CloseServerPorts(void); +void NetConn_OpenServerPorts(int opennetports); +void NetConn_UpdateSockets(void); +lhnetsocket_t *NetConn_ChooseClientSocketForAddress(lhnetaddress_t *address); +lhnetsocket_t *NetConn_ChooseServerSocketForAddress(lhnetaddress_t *address); +void NetConn_Init(void); +void NetConn_Shutdown(void); +netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress); +void NetConn_Close(netconn_t *conn); +void NetConn_Listen(qboolean state); +int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress); +int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress); +int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress); +int NetConn_IsLocalGame(void); +void NetConn_ClientFrame(void); +void NetConn_ServerFrame(void); +void NetConn_SleepMicroseconds(int microseconds); +void NetConn_QueryMasters(qboolean querydp, qboolean queryqw); +void NetConn_Heartbeat(int priority); +void NetConn_QueryQueueFrame(void); +void Net_Stats_f(void); +void Net_Slist_f(void); +void Net_SlistQW_f(void); +void Net_Refresh_f(void); + +/// ServerList interface (public) +/// manually refresh the view set, do this after having changed the mask or any other flag +void ServerList_RebuildViewList(void); +void ServerList_ResetMasks(void); +void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryqw, qboolean consoleoutput); + +/// called whenever net_slist_favorites changes +void NetConn_UpdateFavorites(void); + +#define MAX_CHALLENGES 128 +typedef struct challenge_s +{ + lhnetaddress_t address; + double time; + char string[12]; +} +challenge_t; + +extern challenge_t challenge[MAX_CHALLENGES]; + +#endif + diff --git a/misc/source/darkplaces-src/nexuiz.ico b/misc/source/darkplaces-src/nexuiz.ico new file mode 100644 index 00000000..e9b76b08 Binary files /dev/null and b/misc/source/darkplaces-src/nexuiz.ico differ diff --git a/misc/source/darkplaces-src/nexuiz.rc b/misc/source/darkplaces-src/nexuiz.rc new file mode 100644 index 00000000..1de86953 --- /dev/null +++ b/misc/source/darkplaces-src/nexuiz.rc @@ -0,0 +1,25 @@ +#include // include for version info constants + +A ICON MOVEABLE PURE LOADONCALL DISCARDABLE "nexuiz.ico" + +1 VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +FILETYPE VFT_APP +{ + BLOCK "StringFileInfo" + { + BLOCK "040904E4" + { + VALUE "CompanyName", "Forest Hale Digital Services" + VALUE "FileVersion", "1.0" + VALUE "FileDescription", "Nexuiz" + VALUE "InternalName", "nexuiz.exe" + VALUE "LegalCopyright", "id Software, Forest Hale, and contributors" + VALUE "LegalTrademarks", "" + VALUE "OriginalFilename", "nexuiz.exe" + VALUE "ProductName", "Nexuiz" + VALUE "ProductVersion", "1.0" + } + } +} diff --git a/misc/source/darkplaces-src/nexuiz.xpm b/misc/source/darkplaces-src/nexuiz.xpm new file mode 100644 index 00000000..f80343d5 --- /dev/null +++ b/misc/source/darkplaces-src/nexuiz.xpm @@ -0,0 +1,142 @@ +/* XPM */ +static char * nexuiz_xpm[] = { +"48 48 91 1", +" c None", +". c #0E100D", +"+ c #131412", +"@ c #1D1E1C", +"# c #1F211E", +"$ c #212320", +"% c #232522", +"& c #252724", +"* c #272826", +"= c #2A2C29", +"- c #2D2E2C", +"; c #2F312E", +"> c #323331", +", c #343633", +"' c #363735", +") c #383937", +"! c #393B38", +"~ c #3C3D3B", +"{ c #3D3F3C", +"] c #3F403E", +"^ c #40413F", +"/ c #414340", +"( c #444643", +"_ c #454744", +": c #464845", +"< c #474946", +"[ c #484947", +"} c #494B48", +"| c #4A4C4A", +"1 c #4C4E4B", +"2 c #4D4F4C", +"3 c #4F514E", +"4 c #50524F", +"5 c #525351", +"6 c #545653", +"7 c #575956", +"8 c #5A5C59", +"9 c #5C5E5B", +"0 c #5F615E", +"a c #616360", +"b c #636562", +"c c #656764", +"d c #686A67", +"e c #6B6D6A", +"f c #6E6F6D", +"g c #70726F", +"h c #727471", +"i c #757774", +"j c #787977", +"k c #7B7D7A", +"l c #7E807D", +"m c #818380", +"n c #838582", +"o c #858784", +"p c #878986", +"q c #898B88", +"r c #8C8E8B", +"s c #8E908D", +"t c #90928F", +"u c #939592", +"v c #959794", +"w c #979996", +"x c #9A9C99", +"y c #9FA19E", +"z c #A5A7A4", +"A c #AAACA9", +"B c #B0B2AF", +"C c #B5B7B4", +"D c #B9BBB8", +"E c #BEC0BD", +"F c #C3C5C2", +"G c #C7CAC6", +"H c #CDCFCB", +"I c #D2D4D1", +"J c #D5D8D4", +"K c #D8DAD7", +"L c #DBDDDA", +"M c #DEE0DD", +"N c #E1E3E0", +"O c #E4E6E2", +"P c #E7E9E6", +"Q c #EAECE8", +"R c #EDEFEC", +"S c #F1F3EF", +"T c #F3F5F2", +"U c #F5F7F4", +"V c #F7F9F6", +"W c #F8FBF7", +"X c #FAFCF9", +"Y c #FBFDFA", +"Z c #FDFFFC", +" #%=--=&# ", +" #'3ekoqrrrokd2,# ", +" *3ke60kuxwuuvwxwtk6= ", +" &7pxs[Cxb[6juuttttuvxr0= ", +" @_nwutu2AZZLx8}quttttttuxq2# ", +" %cuusssum}QZZZWw_stsssssssuwg= ", +" =kvssssssv3yZYYZZgcussstuutssvn' ", +" >murrrrrrrucjZXXYZy4vutqkc0iptuup! ", +" -mtrrrrrrrrt0oZWWXZu}i81}8lpb11alur' ", +" %isqqqqqqqqqs2AZWWYSe(drDMWZZYOCk4 ", +" !a000000aa94({^]zWSSRi!80aa00048SSSSSPb}a000b^ ", +" /97777777788887'DWRSJ228777778:eVRRRSI[487778[ ", +"#:65555555555553(LSRSB!45555556{mWRRRSB!455555}$", +"$_1}}}}}}}}}}}1]0SQRQk)2}}}}}}2'wVQQRRp)1}}}}1[&", +"%/_(((((((((((_>pUQRJ4{(((((((_>AUQQQOa!_((((((&", +"%{^]]]]]]]]]]]]>CSPRB){]]]]]]]{'ESPPRG_~]]]]]]]&", +"$=============*/LQPPm&========*!JRPPRA;========%", +"#..............eSPQJ6+.........(MPPPPo@........@", +"$&***********&&zSOQB'&********%8PPOPL0%&*******$", +"$=-----------=(LPPMi=---------&gROOPF^=-------=$", +" =>>>>>>>>>>>*oSNPC{;>>>>>>>>>=rRNOPy;>>>>>>>>-$", +" =!)))))))))'{IPOKe;!)))))))));BQNOMg;)))))))!- ", +" &{{{{{{{{{{-oRNNv,{{{{{{{{{{~{HOMOG<){{{{{{{{= ", +" $~(///////{(JOOB(!//////////!7OMMOw,///////({$ ", +" >[::::::<;xQNE4)<:::::::::<,nQMMJ0!<::::::[' ", +" &<}}}}}})0ONE8'}}}}}}}}}}}}'COLNA!:}}}}}}}}* ", +" !2}}}2(^HOC6'11}}}}}}}}}1/3LMMJd!2}}}}}}2{# ", +" &}222}>APz}!2222222222223,rPLMA~}22222221= ", +" >322>rLn{{3222222222223_(IMLIa!22222224) ", +" #]3!iE0'(21111111112:~);xOJLv)}1111112($ ", +" &{}l/)[}}}}}}}}}}}})qrlMJLE2{}}}}}}}<* ", +" &!)^<____________:)hQMJJIi,:_____:_= ", +" =^(//////////////{!DLJJx){//////^= ", +" &){~!!!!!!!!!!!!~;hMKC},~!!!!~!* ", +" %;'',,,,,,,,,,,,>)DG0-',,,''>% ", +" #*-;;;;;;;;;;;;;=ek--;;;;-*$ ", +" $&=============;>=====*% ", +" $$%%%%%%%%%%%%%%%%%$# ", +" $##############$ ", +" $######$$ "}; diff --git a/misc/source/darkplaces-src/palette.c b/misc/source/darkplaces-src/palette.c new file mode 100644 index 00000000..af413146 --- /dev/null +++ b/misc/source/darkplaces-src/palette.c @@ -0,0 +1,336 @@ + +#include "quakedef.h" + +cvar_t r_colormap_palette = {0, "r_colormap_palette", "gfx/colormap_palette.lmp", "name of a palette lmp file to override the shirt/pants colors of player models. It consists of 16 shirt colors, 16 scoreboard shirt colors, 16 pants colors and 16 scoreboard pants colors"}; + +unsigned char palette_rgb[256][3]; +unsigned char palette_rgb_pantscolormap[16][3]; +unsigned char palette_rgb_shirtcolormap[16][3]; +unsigned char palette_rgb_pantsscoreboard[16][3]; +unsigned char palette_rgb_shirtscoreboard[16][3]; + +unsigned int palette_bgra_complete[256]; +unsigned int palette_bgra_font[256]; +unsigned int palette_bgra_alpha[256]; +unsigned int palette_bgra_nocolormap[256]; +unsigned int palette_bgra_nocolormapnofullbrights[256]; +unsigned int palette_bgra_nofullbrights[256]; +unsigned int palette_bgra_onlyfullbrights[256]; +unsigned int palette_bgra_pantsaswhite[256]; +unsigned int palette_bgra_shirtaswhite[256]; +unsigned int palette_bgra_transparent[256]; +unsigned int palette_bgra_embeddedpic[256]; +unsigned char palette_featureflags[256]; + +// John Carmack said the quake palette.lmp can be considered public domain because it is not an important asset to id, so I include it here as a fallback if no external palette file is found. +unsigned char host_quakepal[768] = +{ +// marked: colormap colors: cb = (colormap & 0xF0);cb += (cb >= 128 && cb < 224) ? 4 : 12; +// 0x0* + 0,0,0, 15,15,15, 31,31,31, 47,47,47, 63,63,63, 75,75,75, 91,91,91, 107,107,107, + 123,123,123, 139,139,139, 155,155,155, 171,171,171, 187,187,187, 203,203,203, 219,219,219, 235,235,235, +// 0x1* 0 ^ + 15,11,7, 23,15,11, 31,23,11, 39,27,15, 47,35,19, 55,43,23, 63,47,23, 75,55,27, + 83,59,27, 91,67,31, 99,75,31, 107,83,31, 115,87,31, 123,95,35, 131,103,35, 143,111,35, +// 0x2* 1 ^ + 11,11,15, 19,19,27, 27,27,39, 39,39,51, 47,47,63, 55,55,75, 63,63,87, 71,71,103, + 79,79,115, 91,91,127, 99,99,139, 107,107,151, 115,115,163, 123,123,175, 131,131,187, 139,139,203, +// 0x3* 2 ^ + 0,0,0, 7,7,0, 11,11,0, 19,19,0, 27,27,0, 35,35,0, 43,43,7, 47,47,7, + 55,55,7, 63,63,7, 71,71,7, 75,75,11, 83,83,11, 91,91,11, 99,99,11, 107,107,15, +// 0x4* 3 ^ + 7,0,0, 15,0,0, 23,0,0, 31,0,0, 39,0,0, 47,0,0, 55,0,0, 63,0,0, + 71,0,0, 79,0,0, 87,0,0, 95,0,0, 103,0,0, 111,0,0, 119,0,0, 127,0,0, +// 0x5* 4 ^ + 19,19,0, 27,27,0, 35,35,0, 47,43,0, 55,47,0, 67,55,0, 75,59,7, 87,67,7, + 95,71,7, 107,75,11, 119,83,15, 131,87,19, 139,91,19, 151,95,27, 163,99,31, 175,103,35, +// 0x6* 5 ^ + 35,19,7, 47,23,11, 59,31,15, 75,35,19, 87,43,23, 99,47,31, 115,55,35, 127,59,43, + 143,67,51, 159,79,51, 175,99,47, 191,119,47, 207,143,43, 223,171,39, 239,203,31, 255,243,27, +// 0x7* 6 ^ + 11,7,0, 27,19,0, 43,35,15, 55,43,19, 71,51,27, 83,55,35, 99,63,43, 111,71,51, + 127,83,63, 139,95,71, 155,107,83, 167,123,95, 183,135,107, 195,147,123, 211,163,139, 227,179,151, +// 0x8* 7 ^ v 8 + 171,139,163, 159,127,151, 147,115,135, 139,103,123, 127,91,111, 119,83,99, 107,75,87, 95,63,75, + 87,55,67, 75,47,55, 67,39,47, 55,31,35, 43,23,27, 35,19,19, 23,11,11, 15,7,7, +// 0x9* 9 v + 187,115,159, 175,107,143, 163,95,131, 151,87,119, 139,79,107, 127,75,95, 115,67,83, 107,59,75, + 95,51,63, 83,43,55, 71,35,43, 59,31,35, 47,23,27, 35,19,19, 23,11,11, 15,7,7, +// 0xA* 10 v + 219,195,187, 203,179,167, 191,163,155, 175,151,139, 163,135,123, 151,123,111, 135,111,95, 123,99,83, + 107,87,71, 95,75,59, 83,63,51, 67,51,39, 55,43,31, 39,31,23, 27,19,15, 15,11,7, +// 0xB* 11 v + 111,131,123, 103,123,111, 95,115,103, 87,107,95, 79,99,87, 71,91,79, 63,83,71, 55,75,63, + 47,67,55, 43,59,47, 35,51,39, 31,43,31, 23,35,23, 15,27,19, 11,19,11, 7,11,7, +// 0xC* 12 v + 255,243,27, 239,223,23, 219,203,19, 203,183,15, 187,167,15, 171,151,11, 155,131,7, 139,115,7, + 123,99,7, 107,83,0, 91,71,0, 75,55,0, 59,43,0, 43,31,0, 27,15,0, 11,7,0, +// 0xD* 13 v + 0,0,255, 11,11,239, 19,19,223, 27,27,207, 35,35,191, 43,43,175, 47,47,159, 47,47,143, + 47,47,127, 47,47,111, 47,47,95, 43,43,79, 35,35,63, 27,27,47, 19,19,31, 11,11,15, +// 0xE* + 43,0,0, 59,0,0, 75,7,0, 95,7,0, 111,15,0, 127,23,7, 147,31,7, 163,39,11, + 183,51,15, 195,75,27, 207,99,43, 219,127,59, 227,151,79, 231,171,95, 239,191,119, 247,211,139, +// 0xF* 14 ^ + 167,123,59, 183,155,55, 199,195,55, 231,227,87, 127,191,255, 171,231,255, 215,255,255, 103,0,0, + 139,0,0, 179,0,0, 215,0,0, 255,0,0, 255,243,147, 255,247,199, 255,255,255, 159,91,83 +}; // 15 ^ + +void Palette_SetupSpecialPalettes(void) +{ + int i; + int fullbright_start, fullbright_end; + int pants_start, pants_end; + int shirt_start, shirt_end; + int reversed_start, reversed_end; + int transparentcolor; + unsigned char *colormap; + fs_offset_t filesize; + union + { + int i; + unsigned char b[4]; + } + u; + + colormap = FS_LoadFile("gfx/colormap.lmp", tempmempool, true, &filesize); + if (colormap && filesize >= 16385) + fullbright_start = 256 - colormap[16384]; + else + fullbright_start = 256; + if (colormap) + Mem_Free(colormap); + fullbright_end = 256; + pants_start = 96; + pants_end = 112; + shirt_start = 16; + shirt_end = 32; + reversed_start = 128; + reversed_end = 224; + transparentcolor = 255; + + for (i = 0;i < 256;i++) + palette_featureflags[i] = PALETTEFEATURE_STANDARD; + for (i = reversed_start;i < reversed_end;i++) + palette_featureflags[i] = PALETTEFEATURE_REVERSED; + for (i = pants_start;i < pants_end;i++) + palette_featureflags[i] = PALETTEFEATURE_PANTS; + for (i = shirt_start;i < shirt_end;i++) + palette_featureflags[i] = PALETTEFEATURE_SHIRT; + for (i = fullbright_start;i < fullbright_end;i++) + palette_featureflags[i] = PALETTEFEATURE_GLOW; + palette_featureflags[0] = PALETTEFEATURE_ZERO; + palette_featureflags[transparentcolor] = PALETTEFEATURE_TRANSPARENT; + + for (i = 0;i < 256;i++) + palette_bgra_transparent[i] = palette_bgra_complete[i]; + palette_bgra_transparent[transparentcolor] = 0; + + for (i = 0;i < fullbright_start;i++) + palette_bgra_nofullbrights[i] = palette_bgra_complete[i]; + for (i = fullbright_start;i < fullbright_end;i++) + palette_bgra_nofullbrights[i] = palette_bgra_complete[0]; + + for (i = 0;i < 256;i++) + palette_bgra_onlyfullbrights[i] = 0; + for (i = fullbright_start;i < fullbright_end;i++) + palette_bgra_onlyfullbrights[i] = palette_bgra_complete[i]; + + for (i = 0;i < 256;i++) + palette_bgra_nocolormapnofullbrights[i] = palette_bgra_complete[i]; + for (i = pants_start;i < pants_end;i++) + palette_bgra_nocolormapnofullbrights[i] = 0; + for (i = shirt_start;i < shirt_end;i++) + palette_bgra_nocolormapnofullbrights[i] = 0; + for (i = fullbright_start;i < fullbright_end;i++) + palette_bgra_nocolormapnofullbrights[i] = 0; + + for (i = 0;i < 256;i++) + palette_bgra_nocolormap[i] = palette_bgra_complete[i]; + for (i = pants_start;i < pants_end;i++) + palette_bgra_nocolormap[i] = 0; + for (i = shirt_start;i < shirt_end;i++) + palette_bgra_nocolormap[i] = 0; + + for (i = 0;i < 256;i++) + palette_bgra_pantsaswhite[i] = 0; + for (i = pants_start;i < pants_end;i++) + { + if (i >= reversed_start && i < reversed_end) + palette_bgra_pantsaswhite[i] = palette_bgra_complete[15 - (i - pants_start)]; + else + palette_bgra_pantsaswhite[i] = palette_bgra_complete[i - pants_start]; + } + + for (i = 0;i < 256;i++) + palette_bgra_shirtaswhite[i] = 0; + for (i = shirt_start;i < shirt_end;i++) + { + if (i >= reversed_start && i < reversed_end) + palette_bgra_shirtaswhite[i] = palette_bgra_complete[15 - (i - shirt_start)]; + else + palette_bgra_shirtaswhite[i] = palette_bgra_complete[i - shirt_start]; + } + + for (i = 0;i < 256;i++) + palette_bgra_alpha[i] = 0xFFFFFFFF; + u.i = 0xFFFFFFFF; + u.b[3] = 0; + palette_bgra_alpha[transparentcolor] = u.i; + + for (i = 0;i < 256;i++) + palette_bgra_font[i] = palette_bgra_complete[i]; + palette_bgra_font[0] = 0; +} + +void BuildGammaTable8(float prescale, float gamma, float scale, float base, float contrastboost, unsigned char *out, int rampsize) +{ + int i, adjusted; + double invgamma; + double t, d; + + invgamma = 1.0 / gamma; + prescale /= (double) (rampsize - 1); + for (i = 0;i < rampsize;i++) + { + t = i * prescale; + d = ((contrastboost - 1) * t + 1); + if(d == 0) + t = 0; // we could just as well assume 1 here, depending on which side of the division by zero we want to be + else + t = contrastboost * t / d; + adjusted = (int) (255.0 * (pow(t, invgamma) * scale + base) + 0.5); + out[i] = bound(0, adjusted, 255); + } +} + +void BuildGammaTable16(float prescale, float gamma, float scale, float base, float contrastboost, unsigned short *out, int rampsize) +{ + int i, adjusted; + double invgamma; + double t; + + invgamma = 1.0 / gamma; + prescale /= (double) (rampsize - 1); + for (i = 0;i < rampsize;i++) + { + t = i * prescale; + t = contrastboost * t / ((contrastboost - 1) * t + 1); + adjusted = (int) (65535.0 * (pow(t, invgamma) * scale + base) + 0.5); + out[i] = bound(0, adjusted, 65535); + } +} + +void Palette_Shutdown(void) +{ +} + +void Palette_NewMap(void) +{ +} + +void Palette_Load(void) +{ + int i; + unsigned char *out; + float gamma, scale, base; + fs_offset_t filesize; + unsigned char *palfile; + unsigned char texturegammaramp[256]; + union + { + unsigned char b[4]; + unsigned int i; + } + bgra; + + gamma = 1; + scale = 1; + base = 0; +// COMMANDLINEOPTION: Client: -texgamma sets the quake palette gamma, allowing you to make quake textures brighter/darker, not recommended + i = COM_CheckParm("-texgamma"); + if (i) + gamma = atof(com_argv[i + 1]); +// COMMANDLINEOPTION: Client: -texcontrast sets the quake palette contrast, allowing you to make quake textures brighter/darker, not recommended + i = COM_CheckParm("-texcontrast"); + if (i) + scale = atof(com_argv[i + 1]); +// COMMANDLINEOPTION: Client: -texbrightness sets the quake palette brightness (brightness of black), allowing you to make quake textures brighter/darker, not recommended + i = COM_CheckParm("-texbrightness"); + if (i) + base = atof(com_argv[i + 1]); + gamma = bound(0.01, gamma, 10.0); + scale = bound(0.01, scale, 10.0); + base = bound(0, base, 0.95); + + BuildGammaTable8(1.0f, gamma, scale, base, 1, texturegammaramp, 256); + + palfile = (unsigned char *)FS_LoadFile ("gfx/palette.lmp", tempmempool, false, &filesize); + if (palfile && filesize >= 768) + memcpy(palette_rgb, palfile, 768); + else + { + Con_DPrint("Couldn't load gfx/palette.lmp, falling back on internal palette\n"); + memcpy(palette_rgb, host_quakepal, 768); + } + if (palfile) + Mem_Free(palfile); + + out = (unsigned char *) palette_bgra_complete; // palette is accessed as 32bit for speed reasons, but is created as 8bit bytes + for (i = 0;i < 256;i++) + { + out[i*4+2] = texturegammaramp[palette_rgb[i][0]]; + out[i*4+1] = texturegammaramp[palette_rgb[i][1]]; + out[i*4+0] = texturegammaramp[palette_rgb[i][2]]; + out[i*4+3] = 255; + } + + if(*r_colormap_palette.string) + palfile = (unsigned char *)FS_LoadFile (r_colormap_palette.string, tempmempool, false, &filesize); + else + palfile = NULL; + + if (palfile && filesize >= 48*2) + { + memcpy(palette_rgb_shirtcolormap[0], palfile, 48); + memcpy(palette_rgb_shirtscoreboard[0], palfile + 48, 48); + } + else + { + for(i = 0;i < 16;i++) + { + VectorCopy(palette_rgb[(i << 4) | ((i >= 8 && i <= 13) ? 0x04 : 0x0C)], palette_rgb_shirtcolormap[i]); + VectorCopy(palette_rgb[(i << 4) | 0x08], palette_rgb_shirtscoreboard[i]); + } + } + + if (palfile && filesize >= 48*4) + { + memcpy(palette_rgb_pantscolormap[0], palfile + 48*2, 48); + memcpy(palette_rgb_pantsscoreboard[0], palfile + 48*3, 48); + } + else + { + memcpy(palette_rgb_pantscolormap, palette_rgb_shirtcolormap, sizeof(palette_rgb_pantscolormap)); + memcpy(palette_rgb_pantsscoreboard, palette_rgb_shirtscoreboard, sizeof(palette_rgb_pantsscoreboard)); + } + + if(palfile) + Mem_Free(palfile); + + memset(palette_bgra_embeddedpic, 0, sizeof(palette_bgra_embeddedpic)); + for (i = '1';i <= '7';i++) + { + Vector4Set(bgra.b, 255, 255, 255, (i - '0') * 255 / 7); + palette_bgra_embeddedpic[i] = bgra.i; + } + + Palette_SetupSpecialPalettes(); +} + +void Palette_Init(void) +{ + R_RegisterModule("Palette", Palette_Load, Palette_Shutdown, Palette_NewMap, NULL, NULL); + Cvar_RegisterVariable(&r_colormap_palette); + Palette_Load(); +} diff --git a/misc/source/darkplaces-src/palette.h b/misc/source/darkplaces-src/palette.h new file mode 100644 index 00000000..f9044653 --- /dev/null +++ b/misc/source/darkplaces-src/palette.h @@ -0,0 +1,39 @@ + +#ifndef PALLETE_H +#define PALLETE_H + +#define PALETTEFEATURE_STANDARD 1 +#define PALETTEFEATURE_REVERSED 2 +#define PALETTEFEATURE_PANTS 4 +#define PALETTEFEATURE_SHIRT 8 +#define PALETTEFEATURE_GLOW 16 +#define PALETTEFEATURE_ZERO 32 +#define PALETTEFEATURE_TRANSPARENT 128 + +extern unsigned char palette_rgb[256][3]; +extern unsigned char palette_rgb_pantscolormap[16][3]; +extern unsigned char palette_rgb_shirtcolormap[16][3]; +extern unsigned char palette_rgb_pantsscoreboard[16][3]; +extern unsigned char palette_rgb_shirtscoreboard[16][3]; + +extern unsigned int palette_bgra_complete[256]; +extern unsigned int palette_bgra_font[256]; +extern unsigned int palette_bgra_alpha[256]; +extern unsigned int palette_bgra_nocolormap[256]; +extern unsigned int palette_bgra_nocolormapnofullbrights[256]; +extern unsigned int palette_bgra_nofullbrights[256]; +extern unsigned int palette_bgra_onlyfullbrights[256]; +extern unsigned int palette_bgra_pantsaswhite[256]; +extern unsigned int palette_bgra_shirtaswhite[256]; +extern unsigned int palette_bgra_transparent[256]; +extern unsigned int palette_bgra_embeddedpic[256]; +extern unsigned char palette_featureflags[256]; + +// used by hardware gamma functions in vid_* files +void BuildGammaTable8(float prescale, float gamma, float scale, float base, float contrastboost, unsigned char *out, int rampsize); +void BuildGammaTable16(float prescale, float gamma, float scale, float base, float contrastboost, unsigned short *out, int rampsize); + +void Palette_Init(void); + +#endif + diff --git a/misc/source/darkplaces-src/polygon.c b/misc/source/darkplaces-src/polygon.c new file mode 100644 index 00000000..99ceb0b9 --- /dev/null +++ b/misc/source/darkplaces-src/polygon.c @@ -0,0 +1,310 @@ + +/* +Polygon clipping routines written by Forest Hale and placed into public domain. +*/ + +#include +#include "polygon.h" + +void PolygonF_QuadForPlane(float *outpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float quadsize) +{ + float d, quadright[3], quadup[3]; + if (fabs(planenormalz) > fabs(planenormalx) && fabs(planenormalz) > fabs(planenormaly)) + { + quadup[0] = 1; + quadup[1] = 0; + quadup[2] = 0; + } + else + { + quadup[0] = 0; + quadup[1] = 0; + quadup[2] = 1; + } + // d = -DotProduct(quadup, planenormal); + d = -(quadup[0] * planenormalx + quadup[1] * planenormaly + quadup[2] * planenormalz); + // VectorMA(quadup, d, planenormal, quadup); + quadup[0] += d * planenormalx; + quadup[1] += d * planenormaly; + quadup[2] += d * planenormalz; + // VectorNormalize(quadup); + d = (float)(1.0 / sqrt(quadup[0] * quadup[0] + quadup[1] * quadup[1] + quadup[2] * quadup[2])); + quadup[0] *= d; + quadup[1] *= d; + quadup[2] *= d; + // CrossProduct(quadup,planenormal,quadright); + quadright[0] = quadup[1] * planenormalz - quadup[2] * planenormaly; + quadright[1] = quadup[2] * planenormalx - quadup[0] * planenormalz; + quadright[2] = quadup[0] * planenormaly - quadup[1] * planenormalx; + // make the points + outpoints[0] = planedist * planenormalx - quadsize * quadright[0] + quadsize * quadup[0]; + outpoints[1] = planedist * planenormaly - quadsize * quadright[1] + quadsize * quadup[1]; + outpoints[2] = planedist * planenormalz - quadsize * quadright[2] + quadsize * quadup[2]; + outpoints[3] = planedist * planenormalx + quadsize * quadright[0] + quadsize * quadup[0]; + outpoints[4] = planedist * planenormaly + quadsize * quadright[1] + quadsize * quadup[1]; + outpoints[5] = planedist * planenormalz + quadsize * quadright[2] + quadsize * quadup[2]; + outpoints[6] = planedist * planenormalx + quadsize * quadright[0] - quadsize * quadup[0]; + outpoints[7] = planedist * planenormaly + quadsize * quadright[1] - quadsize * quadup[1]; + outpoints[8] = planedist * planenormalz + quadsize * quadright[2] - quadsize * quadup[2]; + outpoints[9] = planedist * planenormalx - quadsize * quadright[0] - quadsize * quadup[0]; + outpoints[10] = planedist * planenormaly - quadsize * quadright[1] - quadsize * quadup[1]; + outpoints[11] = planedist * planenormalz - quadsize * quadright[2] - quadsize * quadup[2]; +} + +void PolygonD_QuadForPlane(double *outpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double quadsize) +{ + double d, quadright[3], quadup[3]; + if (fabs(planenormalz) > fabs(planenormalx) && fabs(planenormalz) > fabs(planenormaly)) + { + quadup[0] = 1; + quadup[1] = 0; + quadup[2] = 0; + } + else + { + quadup[0] = 0; + quadup[1] = 0; + quadup[2] = 1; + } + // d = -DotProduct(quadup, planenormal); + d = -(quadup[0] * planenormalx + quadup[1] * planenormaly + quadup[2] * planenormalz); + // VectorMA(quadup, d, planenormal, quadup); + quadup[0] += d * planenormalx; + quadup[1] += d * planenormaly; + quadup[2] += d * planenormalz; + // VectorNormalize(quadup); + d = 1.0 / sqrt(quadup[0] * quadup[0] + quadup[1] * quadup[1] + quadup[2] * quadup[2]); + quadup[0] *= d; + quadup[1] *= d; + quadup[2] *= d; + // CrossProduct(quadup,planenormal,quadright); + quadright[0] = quadup[1] * planenormalz - quadup[2] * planenormaly; + quadright[1] = quadup[2] * planenormalx - quadup[0] * planenormalz; + quadright[2] = quadup[0] * planenormaly - quadup[1] * planenormalx; + // make the points + outpoints[0] = planedist * planenormalx - quadsize * quadright[0] + quadsize * quadup[0]; + outpoints[1] = planedist * planenormaly - quadsize * quadright[1] + quadsize * quadup[1]; + outpoints[2] = planedist * planenormalz - quadsize * quadright[2] + quadsize * quadup[2]; + outpoints[3] = planedist * planenormalx + quadsize * quadright[0] + quadsize * quadup[0]; + outpoints[4] = planedist * planenormaly + quadsize * quadright[1] + quadsize * quadup[1]; + outpoints[5] = planedist * planenormalz + quadsize * quadright[2] + quadsize * quadup[2]; + outpoints[6] = planedist * planenormalx + quadsize * quadright[0] - quadsize * quadup[0]; + outpoints[7] = planedist * planenormaly + quadsize * quadright[1] - quadsize * quadup[1]; + outpoints[8] = planedist * planenormalz + quadsize * quadright[2] - quadsize * quadup[2]; + outpoints[9] = planedist * planenormalx - quadsize * quadright[0] - quadsize * quadup[0]; + outpoints[10] = planedist * planenormaly - quadsize * quadright[1] - quadsize * quadup[1]; + outpoints[11] = planedist * planenormalz - quadsize * quadright[2] - quadsize * quadup[2]; +} + +int PolygonF_Clip(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints) +{ + int i, frontcount = 0; + const float *n, *p; + float frac, pdist, ndist; + if (innumpoints < 1) + return 0; + n = inpoints; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + for(i = 0;i < innumpoints;i++) + { + p = n; + pdist = ndist; + n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + if (pdist >= -epsilon) + { + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0]; + *outfrontpoints++ = p[1]; + *outfrontpoints++ = p[2]; + } + frontcount++; + } + if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) + { + frac = pdist / (pdist - ndist); + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); + *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); + *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); + } + frontcount++; + } + } + return frontcount; +} + +int PolygonD_Clip(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints) +{ + int i, frontcount = 0; + const double *n, *p; + double frac, pdist, ndist; + if (innumpoints < 1) + return 0; + n = inpoints; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + for(i = 0;i < innumpoints;i++) + { + p = n; + pdist = ndist; + n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + if (pdist >= -epsilon) + { + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0]; + *outfrontpoints++ = p[1]; + *outfrontpoints++ = p[2]; + } + frontcount++; + } + if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) + { + frac = pdist / (pdist - ndist); + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); + *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); + *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); + } + frontcount++; + } + } + return frontcount; +} + +void PolygonF_Divide(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, float *outbackpoints, int *neededbackpoints, int *oncountpointer) +{ + int i, frontcount = 0, backcount = 0, oncount = 0; + const float *n, *p; + float frac, pdist, ndist; + if (innumpoints) + { + n = inpoints; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + for(i = 0;i < innumpoints;i++) + { + p = n; + pdist = ndist; + n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + if (pdist >= -epsilon) + { + if (pdist <= epsilon) + oncount++; + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0]; + *outfrontpoints++ = p[1]; + *outfrontpoints++ = p[2]; + } + frontcount++; + } + if (pdist <= epsilon) + { + if (backcount < outbackmaxpoints) + { + *outbackpoints++ = p[0]; + *outbackpoints++ = p[1]; + *outbackpoints++ = p[2]; + } + backcount++; + } + if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) + { + oncount++; + frac = pdist / (pdist - ndist); + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); + *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); + *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); + } + frontcount++; + if (backcount < outbackmaxpoints) + { + *outbackpoints++ = p[0] + frac * (n[0] - p[0]); + *outbackpoints++ = p[1] + frac * (n[1] - p[1]); + *outbackpoints++ = p[2] + frac * (n[2] - p[2]); + } + backcount++; + } + } + } + if (neededfrontpoints) + *neededfrontpoints = frontcount; + if (neededbackpoints) + *neededbackpoints = backcount; + if (oncountpointer) + *oncountpointer = oncount; +} + +void PolygonD_Divide(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, double *outbackpoints, int *neededbackpoints, int *oncountpointer) +{ + int i = 0, frontcount = 0, backcount = 0, oncount = 0; + const double *n, *p; + double frac, pdist, ndist; + if (innumpoints) + { + n = inpoints; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + for(i = 0;i < innumpoints;i++) + { + p = n; + pdist = ndist; + n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + if (pdist >= -epsilon) + { + if (pdist <= epsilon) + oncount++; + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0]; + *outfrontpoints++ = p[1]; + *outfrontpoints++ = p[2]; + } + frontcount++; + } + if (pdist <= epsilon) + { + if (backcount < outbackmaxpoints) + { + *outbackpoints++ = p[0]; + *outbackpoints++ = p[1]; + *outbackpoints++ = p[2]; + } + backcount++; + } + if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) + { + oncount++; + frac = pdist / (pdist - ndist); + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); + *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); + *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); + } + frontcount++; + if (backcount < outbackmaxpoints) + { + *outbackpoints++ = p[0] + frac * (n[0] - p[0]); + *outbackpoints++ = p[1] + frac * (n[1] - p[1]); + *outbackpoints++ = p[2] + frac * (n[2] - p[2]); + } + backcount++; + } + } + } + if (neededfrontpoints) + *neededfrontpoints = frontcount; + if (neededbackpoints) + *neededbackpoints = backcount; + if (oncountpointer) + *oncountpointer = oncount; +} + diff --git a/misc/source/darkplaces-src/polygon.h b/misc/source/darkplaces-src/polygon.h new file mode 100644 index 00000000..e8bf2e81 --- /dev/null +++ b/misc/source/darkplaces-src/polygon.h @@ -0,0 +1,16 @@ + +#ifndef POLYGON_H +#define POLYGON_H + +/* +Polygon clipping routines written by Forest Hale and placed into public domain. +*/ + +void PolygonF_QuadForPlane(float *outpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float quadsize); +void PolygonD_QuadForPlane(double *outpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double quadsize); +int PolygonF_Clip(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints); +int PolygonD_Clip(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints); +void PolygonF_Divide(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, float *outbackpoints, int *neededbackpoints, int *oncountpointer); +void PolygonD_Divide(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, double *outbackpoints, int *neededbackpoints, int *oncountpointer); + +#endif diff --git a/misc/source/darkplaces-src/portals.c b/misc/source/darkplaces-src/portals.c new file mode 100644 index 00000000..3b770f9e --- /dev/null +++ b/misc/source/darkplaces-src/portals.c @@ -0,0 +1,479 @@ + +#include "quakedef.h" +#include "polygon.h" + +#define MAXRECURSIVEPORTALPLANES 1024 +#define MAXRECURSIVEPORTALS 256 + +static tinyplane_t portalplanes[MAXRECURSIVEPORTALPLANES]; +static int ranoutofportalplanes; +static int ranoutofportals; +static float portaltemppoints[2][256][3]; +static float portaltemppoints2[256][3]; +static int portal_markid = 0; +static float boxpoints[4*3]; + +static int Portal_PortalThroughPortalPlanes(tinyplane_t *clipplanes, int clipnumplanes, float *targpoints, int targnumpoints, float *out, int maxpoints) +{ + int numpoints = targnumpoints, i, w; + if (numpoints < 1) + return numpoints; + if (maxpoints > 256) + maxpoints = 256; + w = 0; + memcpy(&portaltemppoints[w][0][0], targpoints, numpoints * 3 * sizeof(float)); + for (i = 0;i < clipnumplanes && numpoints > 0;i++) + { + PolygonF_Divide(numpoints, &portaltemppoints[w][0][0], clipplanes[i].normal[0], clipplanes[i].normal[1], clipplanes[i].normal[2], clipplanes[i].dist, 1.0f/32.0f, 256, &portaltemppoints[1-w][0][0], &numpoints, 0, NULL, NULL, NULL); + w = 1-w; + numpoints = min(numpoints, 256); + } + numpoints = min(numpoints, maxpoints); + if (numpoints > 0) + memcpy(out, &portaltemppoints[w][0][0], numpoints * 3 * sizeof(float)); + return numpoints; +} + +static int Portal_RecursiveFlowSearch (mleaf_t *leaf, vec3_t eye, int firstclipplane, int numclipplanes) +{ + mportal_t *p; + int newpoints, i, prev; + vec3_t center, v1, v2; + tinyplane_t *newplanes; + + if (leaf->portalmarkid == portal_markid) + return true; + + // follow portals into other leafs + for (p = leaf->portals;p;p = p->next) + { + // only flow through portals facing away from the viewer + if (PlaneDiff(eye, (&p->plane)) < 0) + { + newpoints = Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, (float *) p->points, p->numpoints, &portaltemppoints2[0][0], 256); + if (newpoints < 3) + continue; + else if (firstclipplane + numclipplanes + newpoints > MAXRECURSIVEPORTALPLANES) + ranoutofportalplanes = true; + else + { + // find the center by averaging + VectorClear(center); + for (i = 0;i < newpoints;i++) + VectorAdd(center, portaltemppoints2[i], center); + // ixtable is a 1.0f / N table + VectorScale(center, ixtable[newpoints], center); + // calculate the planes, and make sure the polygon can see it's own center + newplanes = &portalplanes[firstclipplane + numclipplanes]; + for (prev = newpoints - 1, i = 0;i < newpoints;prev = i, i++) + { + VectorSubtract(eye, portaltemppoints2[i], v1); + VectorSubtract(portaltemppoints2[prev], portaltemppoints2[i], v2); + CrossProduct(v1, v2, newplanes[i].normal); + VectorNormalize(newplanes[i].normal); + newplanes[i].dist = DotProduct(eye, newplanes[i].normal); + if (DotProduct(newplanes[i].normal, center) <= newplanes[i].dist) + { + // polygon can't see it's own center, discard and use parent portal + break; + } + } + if (i == newpoints) + { + if (Portal_RecursiveFlowSearch(p->past, eye, firstclipplane + numclipplanes, newpoints)) + return true; + } + else + { + if (Portal_RecursiveFlowSearch(p->past, eye, firstclipplane, numclipplanes)) + return true; + } + } + } + } + + return false; +} + +static void Portal_PolygonRecursiveMarkLeafs(mnode_t *node, float *polypoints, int numpoints) +{ + int i, front; + float *p; + +loc0: + if (!node->plane) + { + ((mleaf_t *)node)->portalmarkid = portal_markid; + return; + } + + front = 0; + for (i = 0, p = polypoints;i < numpoints;i++, p += 3) + { + if (DotProduct(p, node->plane->normal) > node->plane->dist) + front++; + } + if (front > 0) + { + if (front == numpoints) + { + node = node->children[0]; + goto loc0; + } + else + Portal_PolygonRecursiveMarkLeafs(node->children[0], polypoints, numpoints); + } + node = node->children[1]; + goto loc0; +} + +int Portal_CheckPolygon(dp_model_t *model, vec3_t eye, float *polypoints, int numpoints) +{ + int i, prev, returnvalue; + mleaf_t *eyeleaf; + vec3_t center, v1, v2; + + // if there is no model, it can not block visibility + if (model == NULL || !model->brush.PointInLeaf) + return true; + + portal_markid++; + + Portal_PolygonRecursiveMarkLeafs(model->brush.data_nodes, polypoints, numpoints); + + eyeleaf = model->brush.PointInLeaf(model, eye); + + // find the center by averaging + VectorClear(center); + for (i = 0;i < numpoints;i++) + VectorAdd(center, (&polypoints[i * 3]), center); + // ixtable is a 1.0f / N table + VectorScale(center, ixtable[numpoints], center); + + // calculate the planes, and make sure the polygon can see it's own center + for (prev = numpoints - 1, i = 0;i < numpoints;prev = i, i++) + { + VectorSubtract(eye, (&polypoints[i * 3]), v1); + VectorSubtract((&polypoints[prev * 3]), (&polypoints[i * 3]), v2); + CrossProduct(v1, v2, portalplanes[i].normal); + VectorNormalize(portalplanes[i].normal); + portalplanes[i].dist = DotProduct(eye, portalplanes[i].normal); + if (DotProduct(portalplanes[i].normal, center) <= portalplanes[i].dist) + { + // polygon can't see it's own center, discard + return false; + } + } + + ranoutofportalplanes = false; + ranoutofportals = false; + + returnvalue = Portal_RecursiveFlowSearch(eyeleaf, eye, 0, numpoints); + + if (ranoutofportalplanes) + Con_Printf("Portal_RecursiveFlowSearch: ran out of %d plane stack when recursing through portals\n", MAXRECURSIVEPORTALPLANES); + if (ranoutofportals) + Con_Printf("Portal_RecursiveFlowSearch: ran out of %d portal stack when recursing through portals\n", MAXRECURSIVEPORTALS); + + return returnvalue; +} + +#define Portal_MinsBoxPolygon(axis, axisvalue, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) \ +{\ + if (eye[(axis)] < ((axisvalue) - 0.5f))\ + {\ + boxpoints[ 0] = x1;boxpoints[ 1] = y1;boxpoints[ 2] = z1;\ + boxpoints[ 3] = x2;boxpoints[ 4] = y2;boxpoints[ 5] = z2;\ + boxpoints[ 6] = x3;boxpoints[ 7] = y3;boxpoints[ 8] = z3;\ + boxpoints[ 9] = x4;boxpoints[10] = y4;boxpoints[11] = z4;\ + if (Portal_CheckPolygon(model, eye, boxpoints, 4))\ + return true;\ + }\ +} + +#define Portal_MaxsBoxPolygon(axis, axisvalue, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) \ +{\ + if (eye[(axis)] > ((axisvalue) + 0.5f))\ + {\ + boxpoints[ 0] = x1;boxpoints[ 1] = y1;boxpoints[ 2] = z1;\ + boxpoints[ 3] = x2;boxpoints[ 4] = y2;boxpoints[ 5] = z2;\ + boxpoints[ 6] = x3;boxpoints[ 7] = y3;boxpoints[ 8] = z3;\ + boxpoints[ 9] = x4;boxpoints[10] = y4;boxpoints[11] = z4;\ + if (Portal_CheckPolygon(model, eye, boxpoints, 4))\ + return true;\ + }\ +} + +int Portal_CheckBox(dp_model_t *model, vec3_t eye, vec3_t a, vec3_t b) +{ + if (eye[0] >= (a[0] - 1.0f) && eye[0] < (b[0] + 1.0f) + && eye[1] >= (a[1] - 1.0f) && eye[1] < (b[1] + 1.0f) + && eye[2] >= (a[2] - 1.0f) && eye[2] < (b[2] + 1.0f)) + return true; + + Portal_MinsBoxPolygon + ( + 0, a[0], + a[0], a[1], a[2], + a[0], b[1], a[2], + a[0], b[1], b[2], + a[0], a[1], b[2] + ); + Portal_MaxsBoxPolygon + ( + 0, b[0], + b[0], b[1], a[2], + b[0], a[1], a[2], + b[0], a[1], b[2], + b[0], b[1], b[2] + ); + Portal_MinsBoxPolygon + ( + 1, a[1], + b[0], a[1], a[2], + a[0], a[1], a[2], + a[0], a[1], b[2], + b[0], a[1], b[2] + ); + Portal_MaxsBoxPolygon + ( + 1, b[1], + a[0], b[1], a[2], + b[0], b[1], a[2], + b[0], b[1], b[2], + a[0], b[1], b[2] + ); + Portal_MinsBoxPolygon + ( + 2, a[2], + a[0], a[1], a[2], + b[0], a[1], a[2], + b[0], b[1], a[2], + a[0], b[1], a[2] + ); + Portal_MaxsBoxPolygon + ( + 2, b[2], + b[0], a[1], b[2], + a[0], a[1], b[2], + a[0], b[1], b[2], + b[0], b[1], b[2] + ); + + return false; +} + +typedef struct portalrecursioninfo_s +{ + int exact; + int numfrustumplanes; + vec3_t boxmins; + vec3_t boxmaxs; + int numsurfaces; + int *surfacelist; + unsigned char *surfacepvs; + int numleafs; + unsigned char *visitingleafpvs; // used to prevent infinite loops + int *leaflist; + unsigned char *leafpvs; + unsigned char *shadowtrispvs; + unsigned char *lighttrispvs; + dp_model_t *model; + vec3_t eye; + float *updateleafsmins; + float *updateleafsmaxs; +} +portalrecursioninfo_t; + +static void Portal_RecursiveFlow (portalrecursioninfo_t *info, mleaf_t *leaf, int firstclipplane, int numclipplanes) +{ + mportal_t *p; + int newpoints, i, prev; + float dist; + vec3_t center; + tinyplane_t *newplanes; + int leafindex = leaf - info->model->brush.data_leafs; + + if (CHECKPVSBIT(info->visitingleafpvs, leafindex)) + return; // recursive loop of leafs (cmc.bsp for megatf coop) + + SETPVSBIT(info->visitingleafpvs, leafindex); + + for (i = 0;i < 3;i++) + { + if (info->updateleafsmins && info->updateleafsmins[i] > leaf->mins[i]) info->updateleafsmins[i] = leaf->mins[i]; + if (info->updateleafsmaxs && info->updateleafsmaxs[i] < leaf->maxs[i]) info->updateleafsmaxs[i] = leaf->maxs[i]; + } + + + if (info->leafpvs) + { + if (!CHECKPVSBIT(info->leafpvs, leafindex)) + { + SETPVSBIT(info->leafpvs, leafindex); + info->leaflist[info->numleafs++] = leafindex; + } + } + + // mark surfaces in leaf that can be seen through portal + if (leaf->numleafsurfaces && info->surfacepvs) + { + for (i = 0;i < leaf->numleafsurfaces;i++) + { + int surfaceindex = leaf->firstleafsurface[i]; + msurface_t *surface = info->model->data_surfaces + surfaceindex; + if (BoxesOverlap(surface->mins, surface->maxs, info->boxmins, info->boxmaxs)) + { + qboolean insidebox = BoxInsideBox(surface->mins, surface->maxs, info->boxmins, info->boxmaxs); + qboolean addedtris = false; + int t, tend; + const int *elements; + const float *vertex3f; + float v[9]; + vertex3f = info->model->surfmesh.data_vertex3f; + elements = (info->model->surfmesh.data_element3i + 3 * surface->num_firsttriangle); + for (t = surface->num_firsttriangle, tend = t + surface->num_triangles;t < tend;t++, elements += 3) + { + VectorCopy(vertex3f + elements[0] * 3, v + 0); + VectorCopy(vertex3f + elements[1] * 3, v + 3); + VectorCopy(vertex3f + elements[2] * 3, v + 6); + if (PointInfrontOfTriangle(info->eye, v + 0, v + 3, v + 6) + && (insidebox || TriangleOverlapsBox(v, v + 3, v + 6, info->boxmins, info->boxmaxs)) + && (!info->exact || Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, v, 3, &portaltemppoints2[0][0], 256) > 0)) + { + addedtris = true; + if (info->shadowtrispvs) + SETPVSBIT(info->shadowtrispvs, t); + if (info->lighttrispvs) + SETPVSBIT(info->lighttrispvs, t); + } + } + if (addedtris && !CHECKPVSBIT(info->surfacepvs, surfaceindex)) + { + SETPVSBIT(info->surfacepvs, surfaceindex); + info->surfacelist[info->numsurfaces++] = surfaceindex; + } + } + } + } + + // follow portals into other leafs + for (p = leaf->portals;p;p = p->next) + { + // only flow through portals facing the viewer + dist = PlaneDiff(info->eye, (&p->plane)); + if (dist < 0 && BoxesOverlap(p->past->mins, p->past->maxs, info->boxmins, info->boxmaxs)) + { + newpoints = Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, (float *) p->points, p->numpoints, &portaltemppoints2[0][0], 256); + if (newpoints < 3) + continue; + else if (firstclipplane + numclipplanes + newpoints > MAXRECURSIVEPORTALPLANES) + ranoutofportalplanes = true; + else + { + // find the center by averaging + VectorClear(center); + for (i = 0;i < newpoints;i++) + VectorAdd(center, portaltemppoints2[i], center); + // ixtable is a 1.0f / N table + VectorScale(center, ixtable[newpoints], center); + // calculate the planes, and make sure the polygon can see its own center + newplanes = &portalplanes[firstclipplane + numclipplanes]; + for (prev = newpoints - 1, i = 0;i < newpoints;prev = i, i++) + { + TriangleNormal(portaltemppoints2[prev], portaltemppoints2[i], info->eye, newplanes[i].normal); + VectorNormalize(newplanes[i].normal); + newplanes[i].dist = DotProduct(info->eye, newplanes[i].normal); + if (DotProduct(newplanes[i].normal, center) <= newplanes[i].dist) + { + // polygon can't see its own center, discard and use parent portal + break; + } + } + if (i == newpoints) + Portal_RecursiveFlow(info, p->past, firstclipplane + numclipplanes, newpoints); + else + Portal_RecursiveFlow(info, p->past, firstclipplane, numclipplanes); + } + } + } + + CLEARPVSBIT(info->visitingleafpvs, leafindex); +} + +static void Portal_RecursiveFindLeafForFlow(portalrecursioninfo_t *info, mnode_t *node) +{ + if (node->plane) + { + float f = DotProduct(info->eye, node->plane->normal) - node->plane->dist; + if (f > -0.1) + Portal_RecursiveFindLeafForFlow(info, node->children[0]); + if (f < 0.1) + Portal_RecursiveFindLeafForFlow(info, node->children[1]); + } + else + { + mleaf_t *leaf = (mleaf_t *)node; + if (leaf->clusterindex >= 0) + Portal_RecursiveFlow(info, leaf, 0, info->numfrustumplanes); + } +} + +void Portal_Visibility(dp_model_t *model, const vec3_t eye, int *leaflist, unsigned char *leafpvs, int *numleafspointer, int *surfacelist, unsigned char *surfacepvs, int *numsurfacespointer, const mplane_t *frustumplanes, int numfrustumplanes, int exact, const float *boxmins, const float *boxmaxs, float *updateleafsmins, float *updateleafsmaxs, unsigned char *shadowtrispvs, unsigned char *lighttrispvs, unsigned char *visitingleafpvs) +{ + int i; + portalrecursioninfo_t info; + + // if there is no model, it can not block visibility + if (model == NULL) + { + Con_Print("Portal_Visibility: NULL model\n"); + return; + } + + if (!model->brush.data_nodes) + { + Con_Print("Portal_Visibility: not a brush model\n"); + return; + } + + // put frustum planes (if any) into tinyplane format at start of buffer + for (i = 0;i < numfrustumplanes;i++) + { + VectorCopy(frustumplanes[i].normal, portalplanes[i].normal); + portalplanes[i].dist = frustumplanes[i].dist; + } + + ranoutofportalplanes = false; + ranoutofportals = false; + + VectorCopy(boxmins, info.boxmins); + VectorCopy(boxmaxs, info.boxmaxs); + info.exact = exact; + info.numsurfaces = 0; + info.surfacelist = surfacelist; + info.surfacepvs = surfacepvs; + info.numleafs = 0; + info.visitingleafpvs = visitingleafpvs; + info.leaflist = leaflist; + info.leafpvs = leafpvs; + info.model = model; + VectorCopy(eye, info.eye); + info.numfrustumplanes = numfrustumplanes; + info.updateleafsmins = updateleafsmins; + info.updateleafsmaxs = updateleafsmaxs; + info.shadowtrispvs = shadowtrispvs; + info.lighttrispvs = lighttrispvs; + + Portal_RecursiveFindLeafForFlow(&info, model->brush.data_nodes); + + if (ranoutofportalplanes) + Con_Printf("Portal_RecursiveFlow: ran out of %d plane stack when recursing through portals\n", MAXRECURSIVEPORTALPLANES); + if (ranoutofportals) + Con_Printf("Portal_RecursiveFlow: ran out of %d portal stack when recursing through portals\n", MAXRECURSIVEPORTALS); + if (numsurfacespointer) + *numsurfacespointer = info.numsurfaces; + if (numleafspointer) + *numleafspointer = info.numleafs; +} + diff --git a/misc/source/darkplaces-src/portals.h b/misc/source/darkplaces-src/portals.h new file mode 100644 index 00000000..fb613280 --- /dev/null +++ b/misc/source/darkplaces-src/portals.h @@ -0,0 +1,10 @@ + +#ifndef PORTALS_H +#define PORTALS_H + +int Portal_CheckPolygon(dp_model_t *model, vec3_t eye, float *polypoints, int numpoints); +int Portal_CheckBox(dp_model_t *model, vec3_t eye, vec3_t a, vec3_t b); +void Portal_Visibility(dp_model_t *model, const vec3_t eye, int *leaflist, unsigned char *leafpvs, int *numleafspointer, int *surfacelist, unsigned char *surfacepvs, int *numsurfacespointer, const mplane_t *frustumplanes, int numfrustumplanes, int exact, const float *boxmins, const float *boxmaxs, float *updateleafsmins, float *updateleafsmaxs, unsigned char *shadowtrispvs, unsigned char *lighttrispvs, unsigned char *visitingleafpvs); + +#endif + diff --git a/misc/source/darkplaces-src/pr_comp.h b/misc/source/darkplaces-src/pr_comp.h new file mode 100644 index 00000000..d1643b62 --- /dev/null +++ b/misc/source/darkplaces-src/pr_comp.h @@ -0,0 +1,225 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// this file is shared by quake and qcc + +#ifndef PR_COMP_H +#define PR_COMP_H + +typedef unsigned int func_t; +typedef int string_t; + +typedef enum etype_e {ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_pointer} etype_t; + + +#define OFS_NULL 0 +#define OFS_RETURN 1 +#define OFS_PARM0 4 // leave 3 ofs for each parm to hold vectors +#define OFS_PARM1 7 +#define OFS_PARM2 10 +#define OFS_PARM3 13 +#define OFS_PARM4 16 +#define OFS_PARM5 19 +#define OFS_PARM6 22 +#define OFS_PARM7 25 +#define RESERVED_OFS 28 + + +typedef enum opcode_e +{ + OP_DONE, + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_ADD_F, + OP_ADD_V, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_FNC, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_FNC, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_LOAD_F, + OP_LOAD_V, + OP_LOAD_S, + OP_LOAD_ENT, + OP_LOAD_FLD, + OP_LOAD_FNC, + + OP_ADDRESS, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_FLD, + OP_STORE_FNC, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, + OP_STOREP_FLD, + OP_STOREP_FNC, + + OP_RETURN, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + OP_NOT_FNC, + OP_IF, + OP_IFNOT, + OP_CALL0, + OP_CALL1, + OP_CALL2, + OP_CALL3, + OP_CALL4, + OP_CALL5, + OP_CALL6, + OP_CALL7, + OP_CALL8, + OP_STATE, + OP_GOTO, + OP_AND, + OP_OR, + + OP_BITAND, + OP_BITOR +} +opcode_t; + + +typedef struct statement_s +{ + unsigned short op; + signed short a,b,c; +} +dstatement_t; + +typedef struct ddef_s +{ + unsigned short type; // if DEF_SAVEGLOBGAL bit is set + // the variable needs to be saved in savegames + unsigned short ofs; + int s_name; +} +ddef_t; +#define DEF_SAVEGLOBAL (1<<15) + +#define MAX_PARMS 8 + +typedef struct dfunction_s +{ + int first_statement; // negative numbers are builtins + int parm_start; + int locals; // total ints of parms + locals + + int profile; // runtime + + int s_name; + int s_file; // source file defined in + + int numparms; + unsigned char parm_size[MAX_PARMS]; +} +dfunction_t; + +typedef struct mfunction_s +{ + int first_statement; // negative numbers are builtins + int parm_start; + int locals; // total ints of parms + locals + + // these are doubles so that they can count up to 54bits or so rather than 32bit + double tprofile; // realtime in this function + double tbprofile; // realtime in builtins called by this function (NOTE: builtins also have a tprofile!) + double profile; // runtime + double builtinsprofile; // cost of builtin functions called by this function + double callcount; // times the functions has been called since the last profile call + double totaltime; // total execution time of this function DIRECTLY FROM THE ENGINE + double tprofile_total; // runtime (NOTE: tbprofile_total makes no real sense, so not accumulating that) + double profile_total; // runtime + double builtinsprofile_total; // cost of builtin functions called by this function + int recursion; + + int s_name; + int s_file; // source file defined in + + int numparms; + unsigned char parm_size[MAX_PARMS]; +} +mfunction_t; + +typedef struct mstatement_s +{ + opcode_t op; + int operand[3]; // always a global or -1 for unused + int jumpabsolute; // only used by IF, IFNOT, GOTO +} +mstatement_t; + + +#define PROG_VERSION 6 +typedef struct dprograms_s +{ + int version; + int crc; // check of header file + + int ofs_statements; + int numstatements; // statement 0 is an error + + int ofs_globaldefs; + int numglobaldefs; + + int ofs_fielddefs; + int numfielddefs; + + int ofs_functions; + int numfunctions; // function 0 is an empty + + int ofs_strings; + int numstrings; // first string is a null string + + int ofs_globals; + int numglobals; + + int entityfields; +} +dprograms_t; + +#endif + diff --git a/misc/source/darkplaces-src/progdefs.h b/misc/source/darkplaces-src/progdefs.h new file mode 100644 index 00000000..7086dbdc --- /dev/null +++ b/misc/source/darkplaces-src/progdefs.h @@ -0,0 +1,170 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* file generated by qcc, do not modify */ + +#ifndef PROGDEFS_H +#define PROGDEFS_H + +typedef struct globalvars_s +{ + int pad[28]; + int self; + int other; + int world; + float time; + float frametime; + float force_retouch; + string_t mapname; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float total_secrets; + float total_monsters; + float found_secrets; + float killed_monsters; + float parm1; + float parm2; + float parm3; + float parm4; + float parm5; + float parm6; + float parm7; + float parm8; + float parm9; + float parm10; + float parm11; + float parm12; + float parm13; + float parm14; + float parm15; + float parm16; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + int trace_ent; + float trace_inopen; + float trace_inwater; + int msg_entity; + func_t main; + func_t StartFrame; + func_t PlayerPreThink; + func_t PlayerPostThink; + func_t ClientKill; + func_t ClientConnect; + func_t PutClientInServer; + func_t ClientDisconnect; + func_t SetNewParms; + func_t SetChangeParms; +} globalvars_t; + +typedef struct entvars_s +{ + float modelindex; + vec3_t absmin; + vec3_t absmax; + float ltime; + float movetype; + float solid; + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t angles; + vec3_t avelocity; + vec3_t punchangle; + string_t classname; + string_t model; + float frame; + float skin; + float effects; + vec3_t mins; + vec3_t maxs; + vec3_t size; + func_t touch; + func_t use; + func_t think; + func_t blocked; + float nextthink; + int groundentity; + float health; + float frags; + float weapon; + string_t weaponmodel; + float weaponframe; + float currentammo; + float ammo_shells; + float ammo_nails; + float ammo_rockets; + float ammo_cells; + float items; + float takedamage; + int chain; + float deadflag; + vec3_t view_ofs; + float button0; + float button1; + float button2; + float impulse; + float fixangle; + vec3_t v_angle; + float idealpitch; + string_t netname; + int enemy; + float flags; + float colormap; + float team; + float max_health; + float teleport_time; + float armortype; + float armorvalue; + float waterlevel; + float watertype; + float ideal_yaw; + float yaw_speed; + int aiment; + int goalentity; + float spawnflags; + string_t target; + string_t targetname; + float dmg_take; + float dmg_save; + int dmg_inflictor; + int owner; + vec3_t movedir; + string_t message; + float sounds; + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; +} entvars_t; + +#define PROGHEADER_CRC 5927 +#define PROGHEADER_CRC_TENEBRAE 32401 + +#endif + diff --git a/misc/source/darkplaces-src/progs.h b/misc/source/darkplaces-src/progs.h new file mode 100644 index 00000000..30219148 --- /dev/null +++ b/misc/source/darkplaces-src/progs.h @@ -0,0 +1,129 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef PROGS_H +#define PROGS_H +#include "pr_comp.h" // defs shared with qcc + +#define ENTITYGRIDAREAS 16 +#define MAX_ENTITYCLUSTERS 16 + +#define JOINTTYPE_POINT 1 +#define JOINTTYPE_HINGE 2 +#define JOINTTYPE_SLIDER 3 +#define JOINTTYPE_UNIVERSAL 4 +#define JOINTTYPE_HINGE2 5 +#define JOINTTYPE_FIXED -1 + +#define ODEFUNC_ENABLE 1 +#define ODEFUNC_DISABLE 2 +#define ODEFUNC_RELFORCEATPOS 3 +#define ODEFUNC_RELTORQUE 4 + +typedef struct edict_odefunc_s +{ + int type; + vec3_t v1; + vec3_t v2; + struct edict_odefunc_s *next; +}edict_odefunc_t; + +typedef struct edict_engineprivate_s +{ + // true if this edict is unused + qboolean free; + // sv.time when the object was freed (to prevent early reuse which could + // mess up client interpolation or obscure severe QuakeC bugs) + float freetime; + // mark for the leak detector + int mark; + // place in the code where it was allocated (for the leak detector) + const char *allocation_origin; + // initially false to prevent projectiles from moving on their first frame + // (even if they were spawned by an synchronous client think) + qboolean move; + + // cached cluster links for quick stationary object visibility checking + vec3_t cullmins, cullmaxs; + int pvs_numclusters; + int pvs_clusterlist[MAX_ENTITYCLUSTERS]; + + // physics grid areas this edict is linked into + link_t areagrid[ENTITYGRIDAREAS]; + // since the areagrid can have multiple references to one entity, + // we should avoid extensive checking on entities already encountered + int areagridmarknumber; + // mins/maxs passed to World_LinkEdict + vec3_t areamins, areamaxs; + + // PROTOCOL_QUAKE, PROTOCOL_QUAKEDP, PROTOCOL_NEHAHRAMOVIE, PROTOCOL_QUAKEWORLD + // baseline values + entity_state_t baseline; + + // LordHavoc: gross hack to make floating items still work + int suspendedinairflag; + + // cached position to avoid redundant SV_CheckWaterTransition calls on monsters + qboolean waterposition_forceupdate; // force an update on this entity (set by SV_PushMove code for moving water entities) + vec3_t waterposition_origin; // updates whenever this changes + + // used by PushMove to keep track of where objects were before they were + // moved, in case they need to be moved back + vec3_t moved_from; + vec3_t moved_fromangles; + + framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + skeleton_t skeleton; + + // physics parameters + qboolean ode_physics; + void *ode_body; + void *ode_geom; + void *ode_joint; + float *ode_vertex3f; + int *ode_element3i; + int ode_numvertices; + int ode_numtriangles; + edict_odefunc_t *ode_func; + vec3_t ode_mins; + vec3_t ode_maxs; + vec_t ode_mass; + vec3_t ode_origin; + vec3_t ode_velocity; + vec3_t ode_angles; + vec3_t ode_avelocity; + qboolean ode_gravity; + int ode_modelindex; + vec_t ode_movelimit; // smallest component of (maxs[]-mins[]) + matrix4x4_t ode_offsetmatrix; + matrix4x4_t ode_offsetimatrix; + int ode_joint_type; + int ode_joint_enemy; + int ode_joint_aiment; + vec3_t ode_joint_origin; // joint anchor + vec3_t ode_joint_angles; // joint axis + vec3_t ode_joint_velocity; // second joint axis + vec3_t ode_joint_movedir; // parameters + void *ode_massbuf; +} +edict_engineprivate_t; + +#endif diff --git a/misc/source/darkplaces-src/progsvm.h b/misc/source/darkplaces-src/progsvm.h new file mode 100644 index 00000000..60c3f212 --- /dev/null +++ b/misc/source/darkplaces-src/progsvm.h @@ -0,0 +1,850 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/* +This is a try to make the vm more generic, it is mainly based on the progs.h file. +For the license refer to progs.h. + +Generic means, less as possible hard-coded links with the other parts of the engine. +This means no edict_engineprivate struct usage, etc. +The code uses void pointers instead. +*/ + +#ifndef PROGSVM_H +#define PROGSVM_H + +#include "pr_comp.h" // defs shared with qcc +#include "progdefs.h" // generated by program cdefs +#include "clprogdefs.h" // generated by program cdefs + +#ifndef DP_SMALLMEMORY +#define PROFILING +#endif + +// forward declaration of clgecko_t +struct clgecko_s; + +typedef struct prvm_stack_s +{ + int s; + mfunction_t *f; + double tprofile_acc; + double profile_acc; + double builtinsprofile_acc; +} prvm_stack_t; + + +typedef union prvm_eval_s +{ + string_t string; + float _float; + float vector[3]; + func_t function; + int ivector[3]; + int _int; + int edict; +} prvm_eval_t; + +typedef struct prvm_required_field_s +{ + int type; + const char *name; +} prvm_required_field_t; + + +// AK: I dont call it engine private cause it doesnt really belongs to the engine +// it belongs to prvm. +typedef struct prvm_edict_private_s +{ + qboolean free; + float freetime; + int mark; + const char *allocation_origin; +} prvm_edict_private_t; + +typedef struct prvm_edict_s +{ + // engine-private fields (stored in dynamically resized array) + //edict_engineprivate_t *e; + union + { + prvm_edict_private_t *required; + vec_t *vp; + // FIXME: this server pointer really means world, not server + // (it is used by both server qc and client qc, but not menu qc) + edict_engineprivate_t *server; + // add other private structs as you desire + // new structs have to start with the elements of prvm_edit_private_t + // e.g. a new struct has to either look like this: + // typedef struct server_edict_private_s { + // prvm_edict_private_t base; + // vec3_t moved_from; + // vec3_t moved_fromangles; + // ... } server_edict_private_t; + // or: + // typedef struct server_edict_private_s { + // qboolean free; + // float freetime; + // vec3_t moved_from; + // vec3_t moved_fromangles; + // ... } server_edict_private_t; + // However, the first one should be preferred. + } priv; + // QuakeC fields (stored in dynamically resized array) + union + { + vec_t *vp; +// entvars_t *server; +// cl_entvars_t *client; + } fields; +} prvm_edict_t; + +extern prvm_eval_t prvm_badvalue; + +#define PRVM_alledictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_alledictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_alledictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_alledictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_alledictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_allglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_allglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_allglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_allglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_allglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_allfunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_drawedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_drawglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_drawglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_drawglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_drawglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_drawfunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_gameedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_gameglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_gameglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_gameglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_gameglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_gamefunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_serveredictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serveredictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serveredictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serveredictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serveredictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serverglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_serverglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_serverglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_serverglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_serverglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_serverfunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_clientedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_clientglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_clientglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_clientglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_clientglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_clientfunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_menuedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_menuglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_menuglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_menuglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_menuglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_menufunction(funcname) (prog->funcoffsets.funcname) + +#if 1 +#define PRVM_EDICTFIELDVALUE(ed, fieldoffset) (fieldoffset < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)((int *)ed->fields.vp + fieldoffset)) +#define PRVM_EDICTFIELDFLOAT(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->_float) +#define PRVM_EDICTFIELDVECTOR(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->vector) +#define PRVM_EDICTFIELDSTRING(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->string) +#define PRVM_EDICTFIELDEDICT(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->edict) +#define PRVM_EDICTFIELDFUNCTION(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->function) +#define PRVM_GLOBALFIELDVALUE(fieldoffset) (fieldoffset < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)((int *)prog->globals.generic + fieldoffset)) +#define PRVM_GLOBALFIELDFLOAT(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->_float) +#define PRVM_GLOBALFIELDVECTOR(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->vector) +#define PRVM_GLOBALFIELDSTRING(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->string) +#define PRVM_GLOBALFIELDEDICT(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->edict) +#define PRVM_GLOBALFIELDFUNCTION(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->function) +#else +#define PRVM_EDICTFIELDVALUE(ed, fieldoffset) ((prvm_eval_t *)((int *)ed->fields.vp + fieldoffset)) +#define PRVM_EDICTFIELDFLOAT(ed, fieldoffset) (((prvm_eval_t *)((int *)ed->fields.vp + fieldoffset))->_float) +#define PRVM_EDICTFIELDVECTOR(ed, fieldoffset) (((prvm_eval_t *)((int *)ed->fields.vp + fieldoffset))->vector) +#define PRVM_EDICTFIELDSTRING(ed, fieldoffset) (((prvm_eval_t *)((int *)ed->fields.vp + fieldoffset))->string) +#define PRVM_EDICTFIELDEDICT(ed, fieldoffset) (((prvm_eval_t *)((int *)ed->fields.vp + fieldoffset))->edict) +#define PRVM_EDICTFIELDFUNCTION(ed, fieldoffset) (((prvm_eval_t *)((int *)ed->fields.vp + fieldoffset))->function) +#define PRVM_GLOBALFIELDVALUE(fieldoffset) ((prvm_eval_t *)((int *)prog->globals.generic + fieldoffset)) +#define PRVM_GLOBALFIELDFLOAT(fieldoffset) (((prvm_eval_t *)((int *)prog->globals.generic + fieldoffset))->_float) +#define PRVM_GLOBALFIELDVECTOR(fieldoffset) (((prvm_eval_t *)((int *)prog->globals.generic + fieldoffset))->vector) +#define PRVM_GLOBALFIELDSTRING(fieldoffset) (((prvm_eval_t *)((int *)prog->globals.generic + fieldoffset))->string) +#define PRVM_GLOBALFIELDEDICT(fieldoffset) (((prvm_eval_t *)((int *)prog->globals.generic + fieldoffset))->edict) +#define PRVM_GLOBALFIELDFUNCTION(fieldoffset) (((prvm_eval_t *)((int *)prog->globals.generic + fieldoffset))->function) +#endif + +//============================================================================ +#define PRVM_OP_STATE 1 + +#ifdef DP_SMALLMEMORY +#define PRVM_MAX_STACK_DEPTH 128 +#define PRVM_LOCALSTACK_SIZE 2048 + +#define PRVM_MAX_OPENFILES 16 +#define PRVM_MAX_OPENSEARCHES 8 +#define PRVM_MAX_GECKOINSTANCES 1 +#else +#define PRVM_MAX_STACK_DEPTH 1024 +#define PRVM_LOCALSTACK_SIZE 16384 + +#define PRVM_MAX_OPENFILES 256 +#define PRVM_MAX_OPENSEARCHES 128 +#define PRVM_MAX_GECKOINSTANCES 32 +#endif + +typedef void (*prvm_builtin_t) (void); + +// NOTE: field offsets use -1 for NULL +typedef struct prvm_prog_fieldoffsets_s +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) int x; +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +} +prvm_prog_fieldoffsets_t; + +// NOTE: global offsets use -1 for NULL +typedef struct prvm_prog_globaloffsets_s +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) int x; +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +} +prvm_prog_globaloffsets_t; + +// NOTE: function offsets use 0 for NULL +typedef struct prvm_prog_funcoffsets_s +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) int x; +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +} +prvm_prog_funcoffsets_t; + +// stringbuffer flags +#define STRINGBUFFER_SAVED 1 // saved in savegames + +typedef struct prvm_stringbuffer_s +{ + int max_strings; + int num_strings; + char **strings; + const char *origin; + unsigned char flags; +} +prvm_stringbuffer_t; + +// [INIT] variables flagged with this token can be initialized by 'you' +// NOTE: external code has to create and free the mempools but everything else is done by prvm ! +typedef struct prvm_prog_s +{ + double starttime; + unsigned int id; // increasing unique id of progs instance + mfunction_t *functions; + char *strings; + int stringssize; + ddef_t *fielddefs; + ddef_t *globaldefs; + mstatement_t *statements; + int entityfields; // number of vec_t fields in progs (some variables are 3) + int entityfieldsarea; // LordHavoc: equal to max_edicts * entityfields (for bounds checking) + + // loaded values from the disk format + int progs_version; + int progs_crc; + int progs_numstatements; + int progs_numglobaldefs; + int progs_numfielddefs; + int progs_numfunctions; + int progs_numstrings; + int progs_numglobals; + int progs_entityfields; + + // real values in memory (some modified by loader) + int numstatements; + int numglobaldefs; + int numfielddefs; + int numfunctions; + int numstrings; + int numglobals; + + int *statement_linenums; // NULL if not available + + double *statement_profile; // only incremented if prvm_statementprofiling is on + + union { + vec_t *generic; +// globalvars_t *server; +// cl_globalvars_t *client; + } globals; + + int maxknownstrings; + int numknownstrings; + // this is updated whenever a string is removed or added + // (simple optimization of the free string search) + int firstfreeknownstring; + const char **knownstrings; + unsigned char *knownstrings_freeable; + const char **knownstrings_origin; + const char ***stringshash; + + memexpandablearray_t stringbuffersarray; + + // all memory allocations related to this vm_prog (code, edicts, strings) + mempool_t *progs_mempool; // [INIT] + + prvm_builtin_t *builtins; // [INIT] + int numbuiltins; // [INIT] + + int argc; + + int trace; + mfunction_t *xfunction; + int xstatement; + + // stacktrace writes into stack[MAX_STACK_DEPTH] + // thus increase the array, so depth wont be overwritten + prvm_stack_t stack[PRVM_MAX_STACK_DEPTH+1]; + int depth; + + int localstack[PRVM_LOCALSTACK_SIZE]; + int localstack_used; + + unsigned short filecrc; + + //============================================================================ + // until this point everything also exists (with the pr_ prefix) in the old vm + + qfile_t *openfiles[PRVM_MAX_OPENFILES]; + const char * openfiles_origin[PRVM_MAX_OPENFILES]; + fssearch_t *opensearches[PRVM_MAX_OPENSEARCHES]; + const char * opensearches_origin[PRVM_MAX_OPENSEARCHES]; + struct clgecko_s *opengeckoinstances[PRVM_MAX_GECKOINSTANCES]; + skeleton_t *skeletons[MAX_EDICTS]; + + // copies of some vars that were former read from sv + int num_edicts; + // number of edicts for which space has been (should be) allocated + int max_edicts; // [INIT] + // used instead of the constant MAX_EDICTS + int limit_edicts; // [INIT] + + // number of reserved edicts (allocated from 1) + int reserved_edicts; // [INIT] + + prvm_edict_t *edicts; + vec_t *edictsfields; + void *edictprivate; + + // size of the engine private struct + int edictprivate_size; // [INIT] + + prvm_prog_fieldoffsets_t fieldoffsets; + prvm_prog_globaloffsets_t globaloffsets; + prvm_prog_funcoffsets_t funcoffsets; + + // allow writing to world entity fields, this is set by server init and + // cleared before first server frame + qboolean allowworldwrites; + + // name of the prog, e.g. "Server", "Client" or "Menu" (used for text output) + const char *name; // [INIT] + + // flag - used to store general flags like PRVM_GE_SELF, etc. + int flag; + + const char *extensionstring; // [INIT] + + qboolean loadintoworld; // [INIT] + + // used to indicate whether a prog is loaded + qboolean loaded; + qboolean leaktest_active; + + // translation buffer (only needs to be freed on unloading progs, type is private to prvm_edict.c) + void *po; + + // printed together with backtraces + const char *statestring; + +// prvm_builtin_mem_t *mem_list; + +// now passed as parameter of PRVM_LoadProgs +// char **required_func; +// int numrequiredfunc; + + //============================================================================ + + ddef_t *self; // if self != 0 then there is a global self + + //============================================================================ + // function pointers + + void (*begin_increase_edicts)(void); // [INIT] used by PRVM_MEM_Increase_Edicts + void (*end_increase_edicts)(void); // [INIT] + + void (*init_edict)(prvm_edict_t *edict); // [INIT] used by PRVM_ED_ClearEdict + void (*free_edict)(prvm_edict_t *ed); // [INIT] used by PRVM_ED_Free + + void (*count_edicts)(void); // [INIT] used by PRVM_ED_Count_f + + qboolean (*load_edict)(prvm_edict_t *ent); // [INIT] used by PRVM_ED_LoadFromFile + + void (*init_cmd)(void); // [INIT] used by PRVM_InitProg + void (*reset_cmd)(void); // [INIT] used by PRVM_ResetProg + + void (*error_cmd)(const char *format, ...) DP_FUNC_PRINTF(1); // [INIT] + + void (*ExecuteProgram)(func_t fnum, const char *errormessage); // pointer to one of the *VM_ExecuteProgram functions +} prvm_prog_t; + +extern prvm_prog_t * prog; + +#define PRVM_MAXPROGS 3 +#define PRVM_SERVERPROG 0 // actually not used at the moment +#define PRVM_CLIENTPROG 1 +#define PRVM_MENUPROG 2 + +extern prvm_prog_t prvm_prog_list[PRVM_MAXPROGS]; + +//============================================================================ +// prvm_cmds part + +extern prvm_builtin_t vm_sv_builtins[]; +extern prvm_builtin_t vm_cl_builtins[]; +extern prvm_builtin_t vm_m_builtins[]; + +extern const int vm_sv_numbuiltins; +extern const int vm_cl_numbuiltins; +extern const int vm_m_numbuiltins; + +extern const char * vm_sv_extensions; // client also uses this +extern const char * vm_m_extensions; + +void VM_SV_Cmd_Init(void); +void VM_SV_Cmd_Reset(void); + +void VM_CL_Cmd_Init(void); +void VM_CL_Cmd_Reset(void); + +void VM_M_Cmd_Init(void); +void VM_M_Cmd_Reset(void); + +void VM_Cmd_Init(void); +void VM_Cmd_Reset(void); +//============================================================================ + +void PRVM_Init (void); + +#ifdef PROFILING +void MVM_ExecuteProgram (func_t fnum, const char *errormessage); +void CLVM_ExecuteProgram (func_t fnum, const char *errormessage); +void SVVM_ExecuteProgram (func_t fnum, const char *errormessage); +#else +#define MVM_ExecuteProgram SVVM_ExecuteProgram +#define CLVM_ExecuteProgram SVVM_ExecuteProgram +void SVVM_ExecuteProgram (func_t fnum, const char *errormessage); +#endif +#define PRVM_ExecuteProgram prog->ExecuteProgram + +#define PRVM_Alloc(buffersize) _PRVM_Alloc(buffersize, __FILE__, __LINE__) +#define PRVM_Free(buffer) _PRVM_Free(buffer, __FILE__, __LINE__) +#define PRVM_FreeAll() _PRVM_FreeAll(__FILE__, __LINE__) +void *_PRVM_Alloc (size_t buffersize, const char *filename, int fileline); +void _PRVM_Free (void *buffer, const char *filename, int fileline); +void _PRVM_FreeAll (const char *filename, int fileline); + +void PRVM_Profile (int maxfunctions, double mintime, int sortby); +void PRVM_Profile_f (void); +void PRVM_ChildProfile_f (void); +void PRVM_CallProfile_f (void); +void PRVM_PrintFunction_f (void); + +void PRVM_PrintState(void); +void PRVM_CrashAll (void); +void PRVM_Crash (void); +void PRVM_ShortStackTrace(char *buf, size_t bufsize); +const char *PRVM_AllocationOrigin(void); + +ddef_t *PRVM_ED_FindField(const char *name); +ddef_t *PRVM_ED_FindGlobal(const char *name); +mfunction_t *PRVM_ED_FindFunction(const char *name); + +int PRVM_ED_FindFieldOffset(const char *name); +int PRVM_ED_FindGlobalOffset(const char *name); +func_t PRVM_ED_FindFunctionOffset(const char *name); +#define PRVM_ED_FindFieldOffset_FromStruct(st, field) prog->fieldoffsets . field = ((int *)(&((st *)NULL)-> field ) - ((int *)NULL)) +#define PRVM_ED_FindGlobalOffset_FromStruct(st, field) prog->globaloffsets . field = ((int *)(&((st *)NULL)-> field ) - ((int *)NULL)) + +void PRVM_MEM_IncreaseEdicts(void); + +qboolean PRVM_ED_CanAlloc(prvm_edict_t *e); +prvm_edict_t *PRVM_ED_Alloc (void); +void PRVM_ED_Free (prvm_edict_t *ed); +void PRVM_ED_ClearEdict (prvm_edict_t *e); + +void PRVM_PrintFunctionStatements (const char *name); +void PRVM_ED_Print(prvm_edict_t *ed, const char *wildcard_fieldname); +void PRVM_ED_Write (qfile_t *f, prvm_edict_t *ed); +const char *PRVM_ED_ParseEdict (const char *data, prvm_edict_t *ent); + +void PRVM_ED_WriteGlobals (qfile_t *f); +void PRVM_ED_ParseGlobals (const char *data); + +void PRVM_ED_LoadFromFile (const char *data); + +unsigned int PRVM_EDICT_NUM_ERROR(unsigned int n, const char *filename, int fileline); +#define PRVM_EDICT(n) (((unsigned)(n) < (unsigned int)prog->max_edicts) ? (unsigned int)(n) : PRVM_EDICT_NUM_ERROR((unsigned int)(n), __FILE__, __LINE__)) +#define PRVM_EDICT_NUM(n) (prog->edicts + PRVM_EDICT(n)) + +//int NUM_FOR_EDICT_ERROR(prvm_edict_t *e); +#define PRVM_NUM_FOR_EDICT(e) ((int)((prvm_edict_t *)(e) - prog->edicts)) +//int PRVM_NUM_FOR_EDICT(prvm_edict_t *e); + +#define PRVM_NEXT_EDICT(e) ((e) + 1) + +#define PRVM_EDICT_TO_PROG(e) (PRVM_NUM_FOR_EDICT(e)) +//int PRVM_EDICT_TO_PROG(prvm_edict_t *e); +#define PRVM_PROG_TO_EDICT(n) (PRVM_EDICT_NUM(n)) +//prvm_edict_t *PRVM_PROG_TO_EDICT(int n); + +//============================================================================ + +#define PRVM_G_FLOAT(o) (prog->globals.generic[o]) +#define PRVM_G_INT(o) (*(int *)&prog->globals.generic[o]) +#define PRVM_G_EDICT(o) (PRVM_PROG_TO_EDICT(*(int *)&prog->globals.generic[o])) +#define PRVM_G_EDICTNUM(o) PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(o)) +#define PRVM_G_VECTOR(o) (&prog->globals.generic[o]) +#define PRVM_G_STRING(o) (PRVM_GetString(*(string_t *)&prog->globals.generic[o])) +//#define PRVM_G_FUNCTION(o) (*(func_t *)&prog->globals.generic[o]) + +// FIXME: make these go away? +#define PRVM_E_FLOAT(e,o) (((float*)e->fields.vp)[o]) +#define PRVM_E_INT(e,o) (((int*)e->fields.vp)[o]) +//#define PRVM_E_VECTOR(e,o) (&((float*)e->fields.vp)[o]) +#define PRVM_E_STRING(e,o) (PRVM_GetString(*(string_t *)&((float*)e->fields.vp)[o])) + +extern int prvm_type_size[8]; // for consistency : I think a goal of this sub-project is to +// make the new vm mostly independent from the old one, thus if it's necessary, I copy everything + +void PRVM_Init_Exec(void); + +void PRVM_ED_PrintEdicts_f (void); +void PRVM_ED_PrintNum (int ent, const char *wildcard_fieldname); + +const char *PRVM_GetString(int num); +int PRVM_SetEngineString(const char *s); +const char *PRVM_ChangeEngineString(int i, const char *s); +int PRVM_SetTempString(const char *s); +int PRVM_AllocString(size_t bufferlength, char **pointer); +void PRVM_FreeString(int num); + +//============================================================================ + +// used as replacement for a prog stack +//#define PRVM_DEBUGPRSTACK + +#ifdef PRVM_DEBUGPRSTACK +#define PRVM_Begin if(prog != 0) Con_Printf("prog not 0(prog = %i) in file: %s line: %i!\n", PRVM_GetProgNr(), __FILE__, __LINE__) +#define PRVM_End prog = 0 +#else +#define PRVM_Begin +#define PRVM_End prog = 0 +#endif + +//#define PRVM_SAFENAME +#ifndef PRVM_SAFENAME +# define PRVM_NAME (prog->name) +#else +# define PRVM_NAME (prog->name ? prog->name : "Unknown prog name") +#endif + +// helper macro to make function pointer calls easier +#define PRVM_GCALL(func) if(prog->func) prog->func + +#define PRVM_ERROR prog->error_cmd + +// other prog handling functions +qboolean PRVM_SetProgFromString(const char *str); +void PRVM_SetProg(int prognr); + +/* +Initializing a vm: +Call InitProg with the num +Set up the fields marked with [INIT] in the prog struct +Load a program with LoadProgs +*/ +void PRVM_InitProg(int prognr); +// LoadProgs expects to be called right after InitProg +void PRVM_LoadProgs (const char *filename, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global); +void PRVM_ResetProg(void); + +qboolean PRVM_ProgLoaded(int prognr); + +int PRVM_GetProgNr(void); + +void VM_Warning(const char *fmt, ...) DP_FUNC_PRINTF(1); + +// TODO: fill in the params +//void PRVM_Create(); + +void VM_GenerateFrameGroupBlend(framegroupblend_t *framegroupblend, const prvm_edict_t *ed); +void VM_FrameBlendFromFrameGroupBlend(frameblend_t *frameblend, const framegroupblend_t *framegroupblend, const dp_model_t *model); +void VM_UpdateEdictSkeleton(prvm_edict_t *ed, const dp_model_t *edmodel, const frameblend_t *frameblend); +void VM_RemoveEdictSkeleton(prvm_edict_t *ed); + +#endif diff --git a/misc/source/darkplaces-src/protocol.c b/misc/source/darkplaces-src/protocol.c new file mode 100644 index 00000000..3e384e16 --- /dev/null +++ b/misc/source/darkplaces-src/protocol.c @@ -0,0 +1,3328 @@ +#include "quakedef.h" + +#define ENTITYSIZEPROFILING_START(msg, num) \ + int entityprofiling_startsize = msg->cursize + +#define ENTITYSIZEPROFILING_END(msg, num) \ + if(developer_networkentities.integer >= 2) \ + { \ + prvm_edict_t *ed = prog->edicts + num; \ + Con_Printf("sent entity update of size %d for a %s\n", (msg->cursize - entityprofiling_startsize), PRVM_serveredictstring(ed, classname) ? PRVM_GetString(PRVM_serveredictstring(ed, classname)) : "(no classname)"); \ + } + +// this is 88 bytes (must match entity_state_t in protocol.h) +entity_state_t defaultstate = +{ + // ! means this is not sent to client + 0,//double time; // ! time this state was built (used on client for interpolation) + {0,0,0},//float netcenter[3]; // ! for network prioritization, this is the center of the bounding box (which may differ from the origin) + {0,0,0},//float origin[3]; + {0,0,0},//float angles[3]; + 0,//int effects; + 0,//unsigned int customizeentityforclient; // ! + 0,//unsigned short number; // entity number this state is for + 0,//unsigned short modelindex; + 0,//unsigned short frame; + 0,//unsigned short tagentity; + 0,//unsigned short specialvisibilityradius; // ! larger if it has effects/light + 0,//unsigned short viewmodelforclient; // ! + 0,//unsigned short exteriormodelforclient; // ! not shown if first person viewing from this entity, shown in all other cases + 0,//unsigned short nodrawtoclient; // ! + 0,//unsigned short drawonlytoclient; // ! + 0,//unsigned short traileffectnum; + {0,0,0,0},//unsigned short light[4]; // color*256 (0.00 to 255.996), and radius*1 + ACTIVE_NOT,//unsigned char active; // true if a valid state + 0,//unsigned char lightstyle; + 0,//unsigned char lightpflags; + 0,//unsigned char colormap; + 0,//unsigned char skin; // also chooses cubemap for rtlights if lightpflags & LIGHTPFLAGS_FULLDYNAMIC + 255,//unsigned char alpha; + 16,//unsigned char scale; + 0,//unsigned char glowsize; + 254,//unsigned char glowcolor; + 0,//unsigned char flags; + 0,//unsigned char internaleffects; // INTEF_FLAG1QW and so on + 0,//unsigned char tagindex; + {32, 32, 32},//unsigned char colormod[3]; + {32, 32, 32},//unsigned char glowmod[3]; +}; + +// LordHavoc: I own protocol ranges 96, 97, 3500-3599 + +struct protocolversioninfo_s +{ + int number; + protocolversion_t version; + const char *name; +} +protocolversioninfo[] = +{ + { 3504, PROTOCOL_DARKPLACES7 , "DP7"}, + { 3503, PROTOCOL_DARKPLACES6 , "DP6"}, + { 3502, PROTOCOL_DARKPLACES5 , "DP5"}, + { 3501, PROTOCOL_DARKPLACES4 , "DP4"}, + { 3500, PROTOCOL_DARKPLACES3 , "DP3"}, + { 97, PROTOCOL_DARKPLACES2 , "DP2"}, + { 96, PROTOCOL_DARKPLACES1 , "DP1"}, + { 15, PROTOCOL_QUAKEDP , "QUAKEDP"}, + { 15, PROTOCOL_QUAKE , "QUAKE"}, + { 28, PROTOCOL_QUAKEWORLD , "QW"}, + { 250, PROTOCOL_NEHAHRAMOVIE, "NEHAHRAMOVIE"}, + {10000, PROTOCOL_NEHAHRABJP , "NEHAHRABJP"}, + {10001, PROTOCOL_NEHAHRABJP2 , "NEHAHRABJP2"}, + {10002, PROTOCOL_NEHAHRABJP3 , "NEHAHRABJP3"}, + { 0, PROTOCOL_UNKNOWN , NULL} +}; + +protocolversion_t Protocol_EnumForName(const char *s) +{ + int i; + for (i = 0;protocolversioninfo[i].name;i++) + if (!strcasecmp(s, protocolversioninfo[i].name)) + return protocolversioninfo[i].version; + return PROTOCOL_UNKNOWN; +} + +const char *Protocol_NameForEnum(protocolversion_t p) +{ + int i; + for (i = 0;protocolversioninfo[i].name;i++) + if (protocolversioninfo[i].version == p) + return protocolversioninfo[i].name; + return "UNKNOWN"; +} + +protocolversion_t Protocol_EnumForNumber(int n) +{ + int i; + for (i = 0;protocolversioninfo[i].name;i++) + if (protocolversioninfo[i].number == n) + return protocolversioninfo[i].version; + return PROTOCOL_UNKNOWN; +} + +int Protocol_NumberForEnum(protocolversion_t p) +{ + int i; + for (i = 0;protocolversioninfo[i].name;i++) + if (protocolversioninfo[i].version == p) + return protocolversioninfo[i].number; + return 0; +} + +void Protocol_Names(char *buffer, size_t buffersize) +{ + int i; + if (buffersize < 1) + return; + buffer[0] = 0; + for (i = 0;protocolversioninfo[i].name;i++) + { + if (i > 1) + strlcat(buffer, " ", buffersize); + strlcat(buffer, protocolversioninfo[i].name, buffersize); + } +} + +void EntityFrameQuake_ReadEntity(int bits) +{ + int num; + entity_t *ent; + entity_state_t s; + + if (bits & U_MOREBITS) + bits |= (MSG_ReadByte()<<8); + if ((bits & U_EXTEND1) && cls.protocol != PROTOCOL_NEHAHRAMOVIE) + { + bits |= MSG_ReadByte() << 16; + if (bits & U_EXTEND2) + bits |= MSG_ReadByte() << 24; + } + + if (bits & U_LONGENTITY) + num = (unsigned short) MSG_ReadShort (); + else + num = MSG_ReadByte (); + + if (num >= MAX_EDICTS) + Host_Error("EntityFrameQuake_ReadEntity: entity number (%i) >= MAX_EDICTS (%i)", num, MAX_EDICTS); + if (num < 1) + Host_Error("EntityFrameQuake_ReadEntity: invalid entity number (%i)", num); + + if (cl.num_entities <= num) + { + cl.num_entities = num + 1; + if (num >= cl.max_entities) + CL_ExpandEntities(num); + } + + ent = cl.entities + num; + + // note: this inherits the 'active' state of the baseline chosen + // (state_baseline is always active, state_current may not be active if + // the entity was missing in the last frame) + if (bits & U_DELTA) + s = ent->state_current; + else + { + s = ent->state_baseline; + s.active = ACTIVE_NETWORK; + } + + cl.isquakeentity[num] = true; + if (cl.lastquakeentity < num) + cl.lastquakeentity = num; + s.number = num; + s.time = cl.mtime[0]; + s.flags = 0; + if (bits & U_MODEL) + { + if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) + s.modelindex = (unsigned short) MSG_ReadShort(); + else + s.modelindex = (s.modelindex & 0xFF00) | MSG_ReadByte(); + } + if (bits & U_FRAME) s.frame = (s.frame & 0xFF00) | MSG_ReadByte(); + if (bits & U_COLORMAP) s.colormap = MSG_ReadByte(); + if (bits & U_SKIN) s.skin = MSG_ReadByte(); + if (bits & U_EFFECTS) s.effects = (s.effects & 0xFF00) | MSG_ReadByte(); + if (bits & U_ORIGIN1) s.origin[0] = MSG_ReadCoord(cls.protocol); + if (bits & U_ANGLE1) s.angles[0] = MSG_ReadAngle(cls.protocol); + if (bits & U_ORIGIN2) s.origin[1] = MSG_ReadCoord(cls.protocol); + if (bits & U_ANGLE2) s.angles[1] = MSG_ReadAngle(cls.protocol); + if (bits & U_ORIGIN3) s.origin[2] = MSG_ReadCoord(cls.protocol); + if (bits & U_ANGLE3) s.angles[2] = MSG_ReadAngle(cls.protocol); + if (bits & U_STEP) s.flags |= RENDER_STEP; + if (bits & U_ALPHA) s.alpha = MSG_ReadByte(); + if (bits & U_SCALE) s.scale = MSG_ReadByte(); + if (bits & U_EFFECTS2) s.effects = (s.effects & 0x00FF) | (MSG_ReadByte() << 8); + if (bits & U_GLOWSIZE) s.glowsize = MSG_ReadByte(); + if (bits & U_GLOWCOLOR) s.glowcolor = MSG_ReadByte(); + if (bits & U_COLORMOD) {int c = MSG_ReadByte();s.colormod[0] = (unsigned char)(((c >> 5) & 7) * (32.0f / 7.0f));s.colormod[1] = (unsigned char)(((c >> 2) & 7) * (32.0f / 7.0f));s.colormod[2] = (unsigned char)((c & 3) * (32.0f / 3.0f));} + if (bits & U_GLOWTRAIL) s.flags |= RENDER_GLOWTRAIL; + if (bits & U_FRAME2) s.frame = (s.frame & 0x00FF) | (MSG_ReadByte() << 8); + if (bits & U_MODEL2) s.modelindex = (s.modelindex & 0x00FF) | (MSG_ReadByte() << 8); + if (bits & U_VIEWMODEL) s.flags |= RENDER_VIEWMODEL; + if (bits & U_EXTERIORMODEL) s.flags |= RENDER_EXTERIORMODEL; + + // LordHavoc: to allow playback of the Nehahra movie + if (cls.protocol == PROTOCOL_NEHAHRAMOVIE && (bits & U_EXTEND1)) + { + // LordHavoc: evil format + int i = (int)MSG_ReadFloat(); + int j = (int)(MSG_ReadFloat() * 255.0f); + if (i == 2) + { + i = (int)MSG_ReadFloat(); + if (i) + s.effects |= EF_FULLBRIGHT; + } + if (j < 0) + s.alpha = 0; + else if (j == 0 || j >= 255) + s.alpha = 255; + else + s.alpha = j; + } + + ent->state_previous = ent->state_current; + ent->state_current = s; + if (ent->state_current.active == ACTIVE_NETWORK) + { + CL_MoveLerpEntityStates(ent); + cl.entities_active[ent->state_current.number] = true; + } + + if (msg_badread) + Host_Error("EntityFrameQuake_ReadEntity: read error"); +} + +void EntityFrameQuake_ISeeDeadEntities(void) +{ + int num, lastentity; + if (cl.lastquakeentity == 0) + return; + lastentity = cl.lastquakeentity; + cl.lastquakeentity = 0; + for (num = 0;num <= lastentity;num++) + { + if (cl.isquakeentity[num]) + { + if (cl.entities_active[num] && cl.entities[num].state_current.time == cl.mtime[0]) + { + cl.isquakeentity[num] = true; + cl.lastquakeentity = num; + } + else + { + cl.isquakeentity[num] = false; + cl.entities_active[num] = ACTIVE_NOT; + cl.entities[num].state_current = defaultstate; + cl.entities[num].state_current.number = num; + } + } + } +} + +// NOTE: this only works with DP5 protocol and upwards. For lower protocols +// (including QUAKE), no packet loss handling for CSQC is done, which makes +// CSQC basically useless. +// Always use the DP5 protocol, or a higher one, when using CSQC entities. +static void EntityFrameCSQC_LostAllFrames(client_t *client) +{ + // mark ALL csqc entities as requiring a FULL resend! + // I know this is a bad workaround, but better than nothing. + int i, n; + prvm_edict_t *ed; + + n = client->csqcnumedicts; + for(i = 0; i < n; ++i) + { + if(client->csqcentityglobalhistory[i]) + { + ed = prog->edicts + i; + if (PRVM_serveredictfunction(ed, SendEntity)) + client->csqcentitysendflags[i] |= 0xFFFFFF; // FULL RESEND + else // if it was ever sent to that client as a CSQC entity + { + client->csqcentityscope[i] = 1; // REMOVE + client->csqcentitysendflags[i] |= 0xFFFFFF; + } + } + } +} +void EntityFrameCSQC_LostFrame(client_t *client, int framenum) +{ + // marks a frame as lost + int i, j; + qboolean valid; + int ringfirst, ringlast; + static int recoversendflags[MAX_EDICTS]; + csqcentityframedb_t *d; + + if(client->csqcentityframe_lastreset < 0) + return; + if(framenum < client->csqcentityframe_lastreset) + return; // no action required, as we resent that data anyway + + // is our frame out of history? + ringfirst = client->csqcentityframehistory_next; // oldest entry + ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry + + valid = false; + + for(j = 0; j < NUM_CSQCENTITYDB_FRAMES; ++j) + { + d = &client->csqcentityframehistory[(ringfirst + j) % NUM_CSQCENTITYDB_FRAMES]; + if(d->framenum < 0) + continue; + if(d->framenum == framenum) + break; + else if(d->framenum < framenum) + valid = true; + } + if(j == NUM_CSQCENTITYDB_FRAMES) + { + if(valid) // got beaten, i.e. there is a frame < framenum + { + // a non-csqc frame got lost... great + return; + } + else + { + // a too old frame got lost... sorry, cannot handle this + Con_DPrintf("CSQC entity DB: lost a frame too early to do any handling (resending ALL)...\n"); + Con_DPrintf("Lost frame = %d\n", framenum); + Con_DPrintf("Entity DB = %d to %d\n", client->csqcentityframehistory[ringfirst].framenum, client->csqcentityframehistory[ringlast].framenum); + EntityFrameCSQC_LostAllFrames(client); + client->csqcentityframe_lastreset = -1; + } + return; + } + + // so j is the frame that got lost + // ringlast is the frame that we have to go to + ringfirst = (ringfirst + j) % NUM_CSQCENTITYDB_FRAMES; + if(ringlast < ringfirst) + ringlast += NUM_CSQCENTITYDB_FRAMES; + + memset(recoversendflags, 0, sizeof(recoversendflags)); + + for(j = ringfirst; j <= ringlast; ++j) + { + d = &client->csqcentityframehistory[j % NUM_CSQCENTITYDB_FRAMES]; + if(d->framenum < 0) + { + // deleted frame + } + else if(d->framenum < framenum) + { + // a frame in the past... should never happen + Con_Printf("CSQC entity DB encountered a frame from the past when recovering from PL...?\n"); + } + else if(d->framenum == framenum) + { + // handling the actually lost frame now + for(i = 0; i < d->num; ++i) + { + int sf = d->sendflags[i]; + int ent = d->entno[i]; + if(sf < 0) // remove + recoversendflags[ent] |= -1; // all bits, including sign + else if(sf > 0) + recoversendflags[ent] |= sf; + } + } + else + { + // handling the frames that followed it now + for(i = 0; i < d->num; ++i) + { + int sf = d->sendflags[i]; + int ent = d->entno[i]; + if(sf < 0) // remove + { + recoversendflags[ent] = 0; // no need to update, we got a more recent remove (and will fix it THEN) + break; // no flags left to remove... + } + else if(sf > 0) + recoversendflags[ent] &= ~sf; // no need to update these bits, we already got them later + } + } + } + + for(i = 0; i < client->csqcnumedicts; ++i) + { + if(recoversendflags[i] < 0) + { + // a remove got lost, then either send a remove or - if it was + // recreated later - a FULL update to make totally sure + client->csqcentityscope[i] = 1; + client->csqcentitysendflags[i] = 0xFFFFFF; + } + else + client->csqcentitysendflags[i] |= recoversendflags[i]; + } +} +static int EntityFrameCSQC_AllocFrame(client_t *client, int framenum) +{ + int ringfirst = client->csqcentityframehistory_next; // oldest entry + client->csqcentityframehistory_next += 1; + client->csqcentityframehistory_next %= NUM_CSQCENTITYDB_FRAMES; + client->csqcentityframehistory[ringfirst].framenum = framenum; + client->csqcentityframehistory[ringfirst].num = 0; + return ringfirst; +} +static void EntityFrameCSQC_DeallocFrame(client_t *client, int framenum) +{ + int ringfirst = client->csqcentityframehistory_next; // oldest entry + int ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry + if(framenum == client->csqcentityframehistory[ringlast].framenum) + { + client->csqcentityframehistory[ringlast].framenum = -1; + client->csqcentityframehistory[ringlast].num = 0; + client->csqcentityframehistory_next = ringlast; + } + else + Con_Printf("Trying to dealloc the wrong entity frame\n"); +} + +//[515]: we use only one array per-client for SendEntity feature +// TODO: add some handling for entity send priorities, to better deal with huge +// amounts of csqc networked entities +qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum) +{ + int num, number, end, sendflags; + qboolean sectionstarted = false; + const unsigned short *n; + prvm_edict_t *ed; + client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber; + int dbframe = EntityFrameCSQC_AllocFrame(client, framenum); + csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe]; + + if(client->csqcentityframe_lastreset < 0) + client->csqcentityframe_lastreset = framenum; + + maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!) + + // make sure there is enough room to store the svc_csqcentities byte, + // the terminator (0x0000) and at least one entity update + if (msg->cursize + 32 >= maxsize) + return false; + + if (client->csqcnumedicts < prog->num_edicts) + client->csqcnumedicts = prog->num_edicts; + + number = 1; + for (num = 0, n = numbers;num < numnumbers;num++, n++) + { + end = *n; + for (;number < end;number++) + { + if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + } + ed = prog->edicts + number; + if (PRVM_serveredictfunction(ed, SendEntity)) + client->csqcentityscope[number] = 2; + else if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + number++; + } + end = client->csqcnumedicts; + for (;number < end;number++) + { + if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + } + + /* + // mark all scope entities as remove + for (number = 1;number < client->csqcnumedicts;number++) + if (client->csqcentityscope[number]) + client->csqcentityscope[number] = 1; + // keep visible entities + for (i = 0, n = numbers;i < numnumbers;i++, n++) + { + number = *n; + ed = prog->edicts + number; + if (PRVM_serveredictfunction(ed, SendEntity)) + client->csqcentityscope[number] = 2; + } + */ + + // now try to emit the entity updates + // (FIXME: prioritize by distance?) + end = client->csqcnumedicts; + for (number = 1;number < end;number++) + { + if (!client->csqcentityscope[number]) + continue; + sendflags = client->csqcentitysendflags[number]; + if (!sendflags) + continue; + if(db->num >= NUM_CSQCENTITIES_PER_FRAME) + break; + ed = prog->edicts + number; + // entity scope is either update (2) or remove (1) + if (client->csqcentityscope[number] == 1) + { + // write a remove message + // first write the message identifier if needed + if(!sectionstarted) + { + sectionstarted = 1; + MSG_WriteByte(msg, svc_csqcentities); + } + // write the remove message + { + ENTITYSIZEPROFILING_START(msg, number); + MSG_WriteShort(msg, (unsigned short)number | 0x8000); + client->csqcentityscope[number] = 0; + client->csqcentitysendflags[number] = 0xFFFFFF; // resend completely if it becomes active again + db->entno[db->num] = number; + db->sendflags[db->num] = -1; + db->num += 1; + client->csqcentityglobalhistory[number] = 1; + ENTITYSIZEPROFILING_END(msg, number); + } + if (msg->cursize + 17 >= maxsize) + break; + } + else + { + // write an update + // save the cursize value in case we overflow and have to rollback + int oldcursize = msg->cursize; + client->csqcentityscope[number] = 1; + if (PRVM_serveredictfunction(ed, SendEntity)) + { + if(!sectionstarted) + MSG_WriteByte(msg, svc_csqcentities); + { + ENTITYSIZEPROFILING_START(msg, number); + MSG_WriteShort(msg, number); + msg->allowoverflow = true; + PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber; + PRVM_G_FLOAT(OFS_PARM1) = sendflags; + PRVM_serverglobaledict(self) = number; + PRVM_ExecuteProgram(PRVM_serveredictfunction(ed, SendEntity), "Null SendEntity\n"); + msg->allowoverflow = false; + if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize) + { + // an update has been successfully written + client->csqcentitysendflags[number] = 0; + db->entno[db->num] = number; + db->sendflags[db->num] = sendflags; + db->num += 1; + client->csqcentityglobalhistory[number] = 1; + // and take note that we have begun the svc_csqcentities + // section of the packet + sectionstarted = 1; + ENTITYSIZEPROFILING_END(msg, number); + if (msg->cursize + 17 >= maxsize) + break; + continue; + } + } + } + // self.SendEntity returned false (or does not exist) or the + // update was too big for this packet - rollback the buffer to its + // state before the writes occurred, we'll try again next frame + msg->cursize = oldcursize; + msg->overflowed = false; + } + } + if (sectionstarted) + { + // write index 0 to end the update (0 is never used by real entities) + MSG_WriteShort(msg, 0); + } + + if(db->num == 0) + // if no single ent got added, remove the frame from the DB again, to allow + // for a larger history + EntityFrameCSQC_DeallocFrame(client, framenum); + + return sectionstarted; +} + +void Protocol_UpdateClientStats(const int *stats) +{ + int i; + // update the stats array and set deltabits for any changed stats + for (i = 0;i < MAX_CL_STATS;i++) + { + if (host_client->stats[i] != stats[i]) + { + host_client->statsdeltabits[i >> 3] |= 1 << (i & 7); + host_client->stats[i] = stats[i]; + } + } +} + +// only a few stats are within the 32 stat limit of Quake, and most of them +// are sent every frame in svc_clientdata messages, so we only send the +// remaining ones here +static const int sendquakestats[] = +{ +// quake did not send these secrets/monsters stats in this way, but doing so +// allows a mod to increase STAT_TOTALMONSTERS during the game, and ensures +// that STAT_SECRETS and STAT_MONSTERS are always correct (even if a client +// didn't receive an svc_foundsecret or svc_killedmonster), which may be most +// valuable if randomly seeking around in a demo +STAT_TOTALSECRETS, // never changes during game +STAT_TOTALMONSTERS, // changes in some mods +STAT_SECRETS, // this makes svc_foundsecret unnecessary +STAT_MONSTERS, // this makes svc_killedmonster unnecessary +STAT_VIEWHEIGHT, // sent just for FTEQW clients +STAT_VIEWZOOM, // this rarely changes +-1, +}; + +void Protocol_WriteStatsReliable(void) +{ + int i, j; + if (!host_client->netconnection) + return; + // detect changes in stats and write reliable messages + // this only deals with 32 stats because the older protocols which use + // this function can only cope with 32 stats, + // they also do not support svc_updatestatubyte which was introduced in + // DP6 protocol (except for QW) + for (j = 0;sendquakestats[j] >= 0;j++) + { + i = sendquakestats[j]; + // check if this bit is set + if (host_client->statsdeltabits[i >> 3] & (1 << (i & 7))) + { + host_client->statsdeltabits[i >> 3] -= (1 << (i & 7)); + // send the stat as a byte if possible + if (sv.protocol == PROTOCOL_QUAKEWORLD) + { + if (host_client->stats[i] >= 0 && host_client->stats[i] < 256) + { + MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatestat); + MSG_WriteByte(&host_client->netconnection->message, i); + MSG_WriteByte(&host_client->netconnection->message, host_client->stats[i]); + } + else + { + MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatestatlong); + MSG_WriteByte(&host_client->netconnection->message, i); + MSG_WriteLong(&host_client->netconnection->message, host_client->stats[i]); + } + } + else + { + // this could make use of svc_updatestatubyte in DP6 and later + // protocols but those protocols do not use this function + MSG_WriteByte(&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte(&host_client->netconnection->message, i); + MSG_WriteLong(&host_client->netconnection->message, host_client->stats[i]); + } + } + } +} + + +qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states) +{ + const entity_state_t *s; + entity_state_t baseline; + int i, bits; + sizebuf_t buf; + unsigned char data[128]; + qboolean success = false; + + // prepare the buffer + memset(&buf, 0, sizeof(buf)); + buf.data = data; + buf.maxsize = sizeof(data); + + for (i = 0;i < numstates;i++) + { + ENTITYSIZEPROFILING_START(msg, states[i]->number); + s = states[i]; + if(PRVM_serveredictfunction((&prog->edicts[s->number]), SendEntity)) + continue; + + // prepare the buffer + SZ_Clear(&buf); + +// send an update + bits = 0; + if (s->number >= 256) + bits |= U_LONGENTITY; + if (s->flags & RENDER_STEP) + bits |= U_STEP; + if (s->flags & RENDER_VIEWMODEL) + bits |= U_VIEWMODEL; + if (s->flags & RENDER_GLOWTRAIL) + bits |= U_GLOWTRAIL; + if (s->flags & RENDER_EXTERIORMODEL) + bits |= U_EXTERIORMODEL; + + // LordHavoc: old stuff, but rewritten to have more exact tolerances + baseline = prog->edicts[s->number].priv.server->baseline; + if (baseline.origin[0] != s->origin[0]) + bits |= U_ORIGIN1; + if (baseline.origin[1] != s->origin[1]) + bits |= U_ORIGIN2; + if (baseline.origin[2] != s->origin[2]) + bits |= U_ORIGIN3; + if (baseline.angles[0] != s->angles[0]) + bits |= U_ANGLE1; + if (baseline.angles[1] != s->angles[1]) + bits |= U_ANGLE2; + if (baseline.angles[2] != s->angles[2]) + bits |= U_ANGLE3; + if (baseline.colormap != s->colormap) + bits |= U_COLORMAP; + if (baseline.skin != s->skin) + bits |= U_SKIN; + if (baseline.frame != s->frame) + { + bits |= U_FRAME; + if (s->frame & 0xFF00) + bits |= U_FRAME2; + } + if (baseline.effects != s->effects) + { + bits |= U_EFFECTS; + if (s->effects & 0xFF00) + bits |= U_EFFECTS2; + } + if (baseline.modelindex != s->modelindex) + { + bits |= U_MODEL; + if ((s->modelindex & 0xFF00) && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3) + bits |= U_MODEL2; + } + if (baseline.alpha != s->alpha) + bits |= U_ALPHA; + if (baseline.scale != s->scale) + bits |= U_SCALE; + if (baseline.glowsize != s->glowsize) + bits |= U_GLOWSIZE; + if (baseline.glowcolor != s->glowcolor) + bits |= U_GLOWCOLOR; + if (!VectorCompare(baseline.colormod, s->colormod)) + bits |= U_COLORMOD; + + // if extensions are disabled, clear the relevant update flags + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_NEHAHRAMOVIE) + bits &= 0x7FFF; + if (sv.protocol == PROTOCOL_NEHAHRAMOVIE) + if (s->alpha != 255 || s->effects & EF_FULLBRIGHT) + bits |= U_EXTEND1; + + // write the message + if (bits >= 16777216) + bits |= U_EXTEND2; + if (bits >= 65536) + bits |= U_EXTEND1; + if (bits >= 256) + bits |= U_MOREBITS; + bits |= U_SIGNAL; + + MSG_WriteByte (&buf, bits); + if (bits & U_MOREBITS) MSG_WriteByte(&buf, bits>>8); + if (sv.protocol != PROTOCOL_NEHAHRAMOVIE) + { + if (bits & U_EXTEND1) MSG_WriteByte(&buf, bits>>16); + if (bits & U_EXTEND2) MSG_WriteByte(&buf, bits>>24); + } + if (bits & U_LONGENTITY) MSG_WriteShort(&buf, s->number); + else MSG_WriteByte(&buf, s->number); + + if (bits & U_MODEL) + { + if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + MSG_WriteShort(&buf, s->modelindex); + else + MSG_WriteByte(&buf, s->modelindex); + } + if (bits & U_FRAME) MSG_WriteByte(&buf, s->frame); + if (bits & U_COLORMAP) MSG_WriteByte(&buf, s->colormap); + if (bits & U_SKIN) MSG_WriteByte(&buf, s->skin); + if (bits & U_EFFECTS) MSG_WriteByte(&buf, s->effects); + if (bits & U_ORIGIN1) MSG_WriteCoord(&buf, s->origin[0], sv.protocol); + if (bits & U_ANGLE1) MSG_WriteAngle(&buf, s->angles[0], sv.protocol); + if (bits & U_ORIGIN2) MSG_WriteCoord(&buf, s->origin[1], sv.protocol); + if (bits & U_ANGLE2) MSG_WriteAngle(&buf, s->angles[1], sv.protocol); + if (bits & U_ORIGIN3) MSG_WriteCoord(&buf, s->origin[2], sv.protocol); + if (bits & U_ANGLE3) MSG_WriteAngle(&buf, s->angles[2], sv.protocol); + if (bits & U_ALPHA) MSG_WriteByte(&buf, s->alpha); + if (bits & U_SCALE) MSG_WriteByte(&buf, s->scale); + if (bits & U_EFFECTS2) MSG_WriteByte(&buf, s->effects >> 8); + if (bits & U_GLOWSIZE) MSG_WriteByte(&buf, s->glowsize); + if (bits & U_GLOWCOLOR) MSG_WriteByte(&buf, s->glowcolor); + if (bits & U_COLORMOD) {int c = ((int)bound(0, s->colormod[0] * (7.0f / 32.0f), 7) << 5) | ((int)bound(0, s->colormod[1] * (7.0f / 32.0f), 7) << 2) | ((int)bound(0, s->colormod[2] * (3.0f / 32.0f), 3) << 0);MSG_WriteByte(&buf, c);} + if (bits & U_FRAME2) MSG_WriteByte(&buf, s->frame >> 8); + if (bits & U_MODEL2) MSG_WriteByte(&buf, s->modelindex >> 8); + + // the nasty protocol + if ((bits & U_EXTEND1) && sv.protocol == PROTOCOL_NEHAHRAMOVIE) + { + if (s->effects & EF_FULLBRIGHT) + { + MSG_WriteFloat(&buf, 2); // QSG protocol version + MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha + MSG_WriteFloat(&buf, 1); // fullbright + } + else + { + MSG_WriteFloat(&buf, 1); // QSG protocol version + MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha + } + } + + // if the commit is full, we're done this frame + if (msg->cursize + buf.cursize > maxsize) + { + // next frame we will continue where we left off + break; + } + // write the message to the packet + SZ_Write(msg, buf.data, buf.cursize); + success = true; + ENTITYSIZEPROFILING_END(msg, s->number); + } + return success; +} + +int EntityState_DeltaBits(const entity_state_t *o, const entity_state_t *n) +{ + unsigned int bits; + // if o is not active, delta from default + if (o->active != ACTIVE_NETWORK) + o = &defaultstate; + bits = 0; + if (fabs(n->origin[0] - o->origin[0]) > (1.0f / 256.0f)) + bits |= E_ORIGIN1; + if (fabs(n->origin[1] - o->origin[1]) > (1.0f / 256.0f)) + bits |= E_ORIGIN2; + if (fabs(n->origin[2] - o->origin[2]) > (1.0f / 256.0f)) + bits |= E_ORIGIN3; + if ((unsigned char) (n->angles[0] * (256.0f / 360.0f)) != (unsigned char) (o->angles[0] * (256.0f / 360.0f))) + bits |= E_ANGLE1; + if ((unsigned char) (n->angles[1] * (256.0f / 360.0f)) != (unsigned char) (o->angles[1] * (256.0f / 360.0f))) + bits |= E_ANGLE2; + if ((unsigned char) (n->angles[2] * (256.0f / 360.0f)) != (unsigned char) (o->angles[2] * (256.0f / 360.0f))) + bits |= E_ANGLE3; + if ((n->modelindex ^ o->modelindex) & 0x00FF) + bits |= E_MODEL1; + if ((n->modelindex ^ o->modelindex) & 0xFF00) + bits |= E_MODEL2; + if ((n->frame ^ o->frame) & 0x00FF) + bits |= E_FRAME1; + if ((n->frame ^ o->frame) & 0xFF00) + bits |= E_FRAME2; + if ((n->effects ^ o->effects) & 0x00FF) + bits |= E_EFFECTS1; + if ((n->effects ^ o->effects) & 0xFF00) + bits |= E_EFFECTS2; + if (n->colormap != o->colormap) + bits |= E_COLORMAP; + if (n->skin != o->skin) + bits |= E_SKIN; + if (n->alpha != o->alpha) + bits |= E_ALPHA; + if (n->scale != o->scale) + bits |= E_SCALE; + if (n->glowsize != o->glowsize) + bits |= E_GLOWSIZE; + if (n->glowcolor != o->glowcolor) + bits |= E_GLOWCOLOR; + if (n->flags != o->flags) + bits |= E_FLAGS; + if (n->tagindex != o->tagindex || n->tagentity != o->tagentity) + bits |= E_TAGATTACHMENT; + if (n->light[0] != o->light[0] || n->light[1] != o->light[1] || n->light[2] != o->light[2] || n->light[3] != o->light[3]) + bits |= E_LIGHT; + if (n->lightstyle != o->lightstyle) + bits |= E_LIGHTSTYLE; + if (n->lightpflags != o->lightpflags) + bits |= E_LIGHTPFLAGS; + + if (bits) + { + if (bits & 0xFF000000) + bits |= 0x00800000; + if (bits & 0x00FF0000) + bits |= 0x00008000; + if (bits & 0x0000FF00) + bits |= 0x00000080; + } + return bits; +} + +void EntityState_WriteExtendBits(sizebuf_t *msg, unsigned int bits) +{ + MSG_WriteByte(msg, bits & 0xFF); + if (bits & 0x00000080) + { + MSG_WriteByte(msg, (bits >> 8) & 0xFF); + if (bits & 0x00008000) + { + MSG_WriteByte(msg, (bits >> 16) & 0xFF); + if (bits & 0x00800000) + MSG_WriteByte(msg, (bits >> 24) & 0xFF); + } + } +} + +void EntityState_WriteFields(const entity_state_t *ent, sizebuf_t *msg, unsigned int bits) +{ + if (sv.protocol == PROTOCOL_DARKPLACES2) + { + if (bits & E_ORIGIN1) + MSG_WriteCoord16i(msg, ent->origin[0]); + if (bits & E_ORIGIN2) + MSG_WriteCoord16i(msg, ent->origin[1]); + if (bits & E_ORIGIN3) + MSG_WriteCoord16i(msg, ent->origin[2]); + } + else + { + // LordHavoc: have to write flags first, as they can modify protocol + if (bits & E_FLAGS) + MSG_WriteByte(msg, ent->flags); + if (ent->flags & RENDER_LOWPRECISION) + { + if (bits & E_ORIGIN1) + MSG_WriteCoord16i(msg, ent->origin[0]); + if (bits & E_ORIGIN2) + MSG_WriteCoord16i(msg, ent->origin[1]); + if (bits & E_ORIGIN3) + MSG_WriteCoord16i(msg, ent->origin[2]); + } + else + { + if (bits & E_ORIGIN1) + MSG_WriteCoord32f(msg, ent->origin[0]); + if (bits & E_ORIGIN2) + MSG_WriteCoord32f(msg, ent->origin[1]); + if (bits & E_ORIGIN3) + MSG_WriteCoord32f(msg, ent->origin[2]); + } + } + if ((sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4) && (ent->flags & RENDER_LOWPRECISION)) + { + if (bits & E_ANGLE1) + MSG_WriteAngle8i(msg, ent->angles[0]); + if (bits & E_ANGLE2) + MSG_WriteAngle8i(msg, ent->angles[1]); + if (bits & E_ANGLE3) + MSG_WriteAngle8i(msg, ent->angles[2]); + } + else + { + if (bits & E_ANGLE1) + MSG_WriteAngle16i(msg, ent->angles[0]); + if (bits & E_ANGLE2) + MSG_WriteAngle16i(msg, ent->angles[1]); + if (bits & E_ANGLE3) + MSG_WriteAngle16i(msg, ent->angles[2]); + } + if (bits & E_MODEL1) + MSG_WriteByte(msg, ent->modelindex & 0xFF); + if (bits & E_MODEL2) + MSG_WriteByte(msg, (ent->modelindex >> 8) & 0xFF); + if (bits & E_FRAME1) + MSG_WriteByte(msg, ent->frame & 0xFF); + if (bits & E_FRAME2) + MSG_WriteByte(msg, (ent->frame >> 8) & 0xFF); + if (bits & E_EFFECTS1) + MSG_WriteByte(msg, ent->effects & 0xFF); + if (bits & E_EFFECTS2) + MSG_WriteByte(msg, (ent->effects >> 8) & 0xFF); + if (bits & E_COLORMAP) + MSG_WriteByte(msg, ent->colormap); + if (bits & E_SKIN) + MSG_WriteByte(msg, ent->skin); + if (bits & E_ALPHA) + MSG_WriteByte(msg, ent->alpha); + if (bits & E_SCALE) + MSG_WriteByte(msg, ent->scale); + if (bits & E_GLOWSIZE) + MSG_WriteByte(msg, ent->glowsize); + if (bits & E_GLOWCOLOR) + MSG_WriteByte(msg, ent->glowcolor); + if (sv.protocol == PROTOCOL_DARKPLACES2) + if (bits & E_FLAGS) + MSG_WriteByte(msg, ent->flags); + if (bits & E_TAGATTACHMENT) + { + MSG_WriteShort(msg, ent->tagentity); + MSG_WriteByte(msg, ent->tagindex); + } + if (bits & E_LIGHT) + { + MSG_WriteShort(msg, ent->light[0]); + MSG_WriteShort(msg, ent->light[1]); + MSG_WriteShort(msg, ent->light[2]); + MSG_WriteShort(msg, ent->light[3]); + } + if (bits & E_LIGHTSTYLE) + MSG_WriteByte(msg, ent->lightstyle); + if (bits & E_LIGHTPFLAGS) + MSG_WriteByte(msg, ent->lightpflags); +} + +void EntityState_WriteUpdate(const entity_state_t *ent, sizebuf_t *msg, const entity_state_t *delta) +{ + unsigned int bits; + ENTITYSIZEPROFILING_START(msg, ent->number); + if (ent->active == ACTIVE_NETWORK) + { + // entity is active, check for changes from the delta + if ((bits = EntityState_DeltaBits(delta, ent))) + { + // write the update number, bits, and fields + MSG_WriteShort(msg, ent->number); + EntityState_WriteExtendBits(msg, bits); + EntityState_WriteFields(ent, msg, bits); + } + } + else + { + // entity is inactive, check if the delta was active + if (delta->active == ACTIVE_NETWORK) + { + // write the remove number + MSG_WriteShort(msg, ent->number | 0x8000); + } + } + ENTITYSIZEPROFILING_END(msg, ent->number); +} + +int EntityState_ReadExtendBits(void) +{ + unsigned int bits; + bits = MSG_ReadByte(); + if (bits & 0x00000080) + { + bits |= MSG_ReadByte() << 8; + if (bits & 0x00008000) + { + bits |= MSG_ReadByte() << 16; + if (bits & 0x00800000) + bits |= MSG_ReadByte() << 24; + } + } + return bits; +} + +void EntityState_ReadFields(entity_state_t *e, unsigned int bits) +{ + if (cls.protocol == PROTOCOL_DARKPLACES2) + { + if (bits & E_ORIGIN1) + e->origin[0] = MSG_ReadCoord16i(); + if (bits & E_ORIGIN2) + e->origin[1] = MSG_ReadCoord16i(); + if (bits & E_ORIGIN3) + e->origin[2] = MSG_ReadCoord16i(); + } + else + { + if (bits & E_FLAGS) + e->flags = MSG_ReadByte(); + if (e->flags & RENDER_LOWPRECISION) + { + if (bits & E_ORIGIN1) + e->origin[0] = MSG_ReadCoord16i(); + if (bits & E_ORIGIN2) + e->origin[1] = MSG_ReadCoord16i(); + if (bits & E_ORIGIN3) + e->origin[2] = MSG_ReadCoord16i(); + } + else + { + if (bits & E_ORIGIN1) + e->origin[0] = MSG_ReadCoord32f(); + if (bits & E_ORIGIN2) + e->origin[1] = MSG_ReadCoord32f(); + if (bits & E_ORIGIN3) + e->origin[2] = MSG_ReadCoord32f(); + } + } + if ((cls.protocol == PROTOCOL_DARKPLACES5 || cls.protocol == PROTOCOL_DARKPLACES6) && !(e->flags & RENDER_LOWPRECISION)) + { + if (bits & E_ANGLE1) + e->angles[0] = MSG_ReadAngle16i(); + if (bits & E_ANGLE2) + e->angles[1] = MSG_ReadAngle16i(); + if (bits & E_ANGLE3) + e->angles[2] = MSG_ReadAngle16i(); + } + else + { + if (bits & E_ANGLE1) + e->angles[0] = MSG_ReadAngle8i(); + if (bits & E_ANGLE2) + e->angles[1] = MSG_ReadAngle8i(); + if (bits & E_ANGLE3) + e->angles[2] = MSG_ReadAngle8i(); + } + if (bits & E_MODEL1) + e->modelindex = (e->modelindex & 0xFF00) | (unsigned int) MSG_ReadByte(); + if (bits & E_MODEL2) + e->modelindex = (e->modelindex & 0x00FF) | ((unsigned int) MSG_ReadByte() << 8); + if (bits & E_FRAME1) + e->frame = (e->frame & 0xFF00) | (unsigned int) MSG_ReadByte(); + if (bits & E_FRAME2) + e->frame = (e->frame & 0x00FF) | ((unsigned int) MSG_ReadByte() << 8); + if (bits & E_EFFECTS1) + e->effects = (e->effects & 0xFF00) | (unsigned int) MSG_ReadByte(); + if (bits & E_EFFECTS2) + e->effects = (e->effects & 0x00FF) | ((unsigned int) MSG_ReadByte() << 8); + if (bits & E_COLORMAP) + e->colormap = MSG_ReadByte(); + if (bits & E_SKIN) + e->skin = MSG_ReadByte(); + if (bits & E_ALPHA) + e->alpha = MSG_ReadByte(); + if (bits & E_SCALE) + e->scale = MSG_ReadByte(); + if (bits & E_GLOWSIZE) + e->glowsize = MSG_ReadByte(); + if (bits & E_GLOWCOLOR) + e->glowcolor = MSG_ReadByte(); + if (cls.protocol == PROTOCOL_DARKPLACES2) + if (bits & E_FLAGS) + e->flags = MSG_ReadByte(); + if (bits & E_TAGATTACHMENT) + { + e->tagentity = (unsigned short) MSG_ReadShort(); + e->tagindex = MSG_ReadByte(); + } + if (bits & E_LIGHT) + { + e->light[0] = (unsigned short) MSG_ReadShort(); + e->light[1] = (unsigned short) MSG_ReadShort(); + e->light[2] = (unsigned short) MSG_ReadShort(); + e->light[3] = (unsigned short) MSG_ReadShort(); + } + if (bits & E_LIGHTSTYLE) + e->lightstyle = MSG_ReadByte(); + if (bits & E_LIGHTPFLAGS) + e->lightpflags = MSG_ReadByte(); + + if (developer_networkentities.integer >= 2) + { + Con_Printf("ReadFields e%i", e->number); + + if (bits & E_ORIGIN1) + Con_Printf(" E_ORIGIN1 %f", e->origin[0]); + if (bits & E_ORIGIN2) + Con_Printf(" E_ORIGIN2 %f", e->origin[1]); + if (bits & E_ORIGIN3) + Con_Printf(" E_ORIGIN3 %f", e->origin[2]); + if (bits & E_ANGLE1) + Con_Printf(" E_ANGLE1 %f", e->angles[0]); + if (bits & E_ANGLE2) + Con_Printf(" E_ANGLE2 %f", e->angles[1]); + if (bits & E_ANGLE3) + Con_Printf(" E_ANGLE3 %f", e->angles[2]); + if (bits & (E_MODEL1 | E_MODEL2)) + Con_Printf(" E_MODEL %i", e->modelindex); + + if (bits & (E_FRAME1 | E_FRAME2)) + Con_Printf(" E_FRAME %i", e->frame); + if (bits & (E_EFFECTS1 | E_EFFECTS2)) + Con_Printf(" E_EFFECTS %i", e->effects); + if (bits & E_ALPHA) + Con_Printf(" E_ALPHA %f", e->alpha / 255.0f); + if (bits & E_SCALE) + Con_Printf(" E_SCALE %f", e->scale / 16.0f); + if (bits & E_COLORMAP) + Con_Printf(" E_COLORMAP %i", e->colormap); + if (bits & E_SKIN) + Con_Printf(" E_SKIN %i", e->skin); + + if (bits & E_GLOWSIZE) + Con_Printf(" E_GLOWSIZE %i", e->glowsize * 4); + if (bits & E_GLOWCOLOR) + Con_Printf(" E_GLOWCOLOR %i", e->glowcolor); + + if (bits & E_LIGHT) + Con_Printf(" E_LIGHT %i:%i:%i:%i", e->light[0], e->light[1], e->light[2], e->light[3]); + if (bits & E_LIGHTPFLAGS) + Con_Printf(" E_LIGHTPFLAGS %i", e->lightpflags); + + if (bits & E_TAGATTACHMENT) + Con_Printf(" E_TAGATTACHMENT e%i:%i", e->tagentity, e->tagindex); + if (bits & E_LIGHTSTYLE) + Con_Printf(" E_LIGHTSTYLE %i", e->lightstyle); + Con_Print("\n"); + } +} + +extern void CL_NewFrameReceived(int num); + +// (client and server) allocates a new empty database +entityframe_database_t *EntityFrame_AllocDatabase(mempool_t *mempool) +{ + return (entityframe_database_t *)Mem_Alloc(mempool, sizeof(entityframe_database_t)); +} + +// (client and server) frees the database +void EntityFrame_FreeDatabase(entityframe_database_t *d) +{ + Mem_Free(d); +} + +// (server) clears the database to contain no frames (thus delta compression compresses against nothing) +void EntityFrame_ClearDatabase(entityframe_database_t *d) +{ + memset(d, 0, sizeof(*d)); +} + +// (server and client) removes frames older than 'frame' from database +void EntityFrame_AckFrame(entityframe_database_t *d, int frame) +{ + int i; + d->ackframenum = frame; + for (i = 0;i < d->numframes && d->frames[i].framenum < frame;i++); + // ignore outdated frame acks (out of order packets) + if (i == 0) + return; + d->numframes -= i; + // if some queue is left, slide it down to beginning of array + if (d->numframes) + memmove(&d->frames[0], &d->frames[i], sizeof(d->frames[0]) * d->numframes); +} + +// (server) clears frame, to prepare for adding entities +void EntityFrame_Clear(entity_frame_t *f, vec3_t eye, int framenum) +{ + f->time = 0; + f->framenum = framenum; + f->numentities = 0; + if (eye == NULL) + VectorClear(f->eye); + else + VectorCopy(eye, f->eye); +} + +// (server and client) reads a frame from the database +void EntityFrame_FetchFrame(entityframe_database_t *d, int framenum, entity_frame_t *f) +{ + int i, n; + EntityFrame_Clear(f, NULL, -1); + for (i = 0;i < d->numframes && d->frames[i].framenum < framenum;i++); + if (i < d->numframes && framenum == d->frames[i].framenum) + { + f->framenum = framenum; + f->numentities = d->frames[i].endentity - d->frames[i].firstentity; + n = MAX_ENTITY_DATABASE - (d->frames[i].firstentity % MAX_ENTITY_DATABASE); + if (n > f->numentities) + n = f->numentities; + memcpy(f->entitydata, d->entitydata + d->frames[i].firstentity % MAX_ENTITY_DATABASE, sizeof(*f->entitydata) * n); + if (f->numentities > n) + memcpy(f->entitydata + n, d->entitydata, sizeof(*f->entitydata) * (f->numentities - n)); + VectorCopy(d->eye, f->eye); + } +} + +// (client) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata) +{ + int n, e; + entity_frameinfo_t *info; + + VectorCopy(eye, d->eye); + + // figure out how many entity slots are used already + if (d->numframes) + { + n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity; + if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY) + { + // ran out of room, dump database + EntityFrame_ClearDatabase(d); + } + } + + info = &d->frames[d->numframes]; + info->framenum = framenum; + e = -1000; + // make sure we check the newly added frame as well, but we haven't incremented numframes yet + for (n = 0;n <= d->numframes;n++) + { + if (e >= d->frames[n].framenum) + { + if (e == framenum) + Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n"); + else + Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n"); + return; + } + e = d->frames[n].framenum; + } + // if database still has frames after that... + if (d->numframes) + info->firstentity = d->frames[d->numframes - 1].endentity; + else + info->firstentity = 0; + info->endentity = info->firstentity + numentities; + d->numframes++; + + n = info->firstentity % MAX_ENTITY_DATABASE; + e = MAX_ENTITY_DATABASE - n; + if (e > numentities) + e = numentities; + memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e); + if (numentities > e) + memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e)); +} + +// (server) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Server(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t **entitydata) +{ + int n, e; + entity_frameinfo_t *info; + + VectorCopy(eye, d->eye); + + // figure out how many entity slots are used already + if (d->numframes) + { + n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity; + if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY) + { + // ran out of room, dump database + EntityFrame_ClearDatabase(d); + } + } + + info = &d->frames[d->numframes]; + info->framenum = framenum; + e = -1000; + // make sure we check the newly added frame as well, but we haven't incremented numframes yet + for (n = 0;n <= d->numframes;n++) + { + if (e >= d->frames[n].framenum) + { + if (e == framenum) + Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n"); + else + Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n"); + return; + } + e = d->frames[n].framenum; + } + // if database still has frames after that... + if (d->numframes) + info->firstentity = d->frames[d->numframes - 1].endentity; + else + info->firstentity = 0; + info->endentity = info->firstentity + numentities; + d->numframes++; + + n = info->firstentity % MAX_ENTITY_DATABASE; + e = MAX_ENTITY_DATABASE - n; + if (e > numentities) + e = numentities; + memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e); + if (numentities > e) + memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e)); +} + +// (server) writes a frame to network stream +qboolean EntityFrame_WriteFrame(sizebuf_t *msg, int maxsize, entityframe_database_t *d, int numstates, const entity_state_t **states, int viewentnum) +{ + int i, onum, number; + entity_frame_t *o = &d->deltaframe; + const entity_state_t *ent, *delta; + vec3_t eye; + + d->latestframenum++; + + VectorClear(eye); + for (i = 0;i < numstates;i++) + { + ent = states[i]; + if (ent->number == viewentnum) + { + VectorSet(eye, ent->origin[0], ent->origin[1], ent->origin[2] + 22); + break; + } + } + + EntityFrame_AddFrame_Server(d, eye, d->latestframenum, numstates, states); + + EntityFrame_FetchFrame(d, d->ackframenum, o); + + MSG_WriteByte (msg, svc_entities); + MSG_WriteLong (msg, o->framenum); + MSG_WriteLong (msg, d->latestframenum); + MSG_WriteFloat (msg, eye[0]); + MSG_WriteFloat (msg, eye[1]); + MSG_WriteFloat (msg, eye[2]); + + onum = 0; + for (i = 0;i < numstates;i++) + { + ent = states[i]; + number = ent->number; + + if (PRVM_serveredictfunction((&prog->edicts[number]), SendEntity)) + continue; + for (;onum < o->numentities && o->entitydata[onum].number < number;onum++) + { + // write remove message + MSG_WriteShort(msg, o->entitydata[onum].number | 0x8000); + } + if (onum < o->numentities && (o->entitydata[onum].number == number)) + { + // delta from previous frame + delta = o->entitydata + onum; + // advance to next entity in delta frame + onum++; + } + else + { + // delta from defaults + delta = &defaultstate; + } + EntityState_WriteUpdate(ent, msg, delta); + } + for (;onum < o->numentities;onum++) + { + // write remove message + MSG_WriteShort(msg, o->entitydata[onum].number | 0x8000); + } + MSG_WriteShort(msg, 0xFFFF); + + return true; +} + +// (client) reads a frame from network stream +void EntityFrame_CL_ReadFrame(void) +{ + int i, number, removed; + entity_frame_t *f, *delta; + entity_state_t *e, *old, *oldend; + entity_t *ent; + entityframe_database_t *d; + if (!cl.entitydatabase) + cl.entitydatabase = EntityFrame_AllocDatabase(cls.levelmempool); + d = cl.entitydatabase; + f = &d->framedata; + delta = &d->deltaframe; + + EntityFrame_Clear(f, NULL, -1); + + // read the frame header info + f->time = cl.mtime[0]; + number = MSG_ReadLong(); + f->framenum = MSG_ReadLong(); + CL_NewFrameReceived(f->framenum); + f->eye[0] = MSG_ReadFloat(); + f->eye[1] = MSG_ReadFloat(); + f->eye[2] = MSG_ReadFloat(); + EntityFrame_AckFrame(d, number); + EntityFrame_FetchFrame(d, number, delta); + old = delta->entitydata; + oldend = old + delta->numentities; + // read entities until we hit the magic 0xFFFF end tag + while ((number = (unsigned short) MSG_ReadShort()) != 0xFFFF && !msg_badread) + { + if (msg_badread) + Host_Error("EntityFrame_Read: read error"); + removed = number & 0x8000; + number &= 0x7FFF; + if (number >= MAX_EDICTS) + Host_Error("EntityFrame_Read: number (%i) >= MAX_EDICTS (%i)", number, MAX_EDICTS); + + // seek to entity, while copying any skipped entities (assume unchanged) + while (old < oldend && old->number < number) + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + f->entitydata[f->numentities] = *old++; + f->entitydata[f->numentities++].time = cl.mtime[0]; + } + if (removed) + { + if (old < oldend && old->number == number) + old++; + else + Con_Printf("EntityFrame_Read: REMOVE on unused entity %i\n", number); + } + else + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + + // reserve this slot + e = f->entitydata + f->numentities++; + + if (old < oldend && old->number == number) + { + // delta from old entity + *e = *old++; + } + else + { + // delta from defaults + *e = defaultstate; + } + + if (cl.num_entities <= number) + { + cl.num_entities = number + 1; + if (number >= cl.max_entities) + CL_ExpandEntities(number); + } + cl.entities_active[number] = true; + e->active = ACTIVE_NETWORK; + e->time = cl.mtime[0]; + e->number = number; + EntityState_ReadFields(e, EntityState_ReadExtendBits()); + } + } + while (old < oldend) + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + f->entitydata[f->numentities] = *old++; + f->entitydata[f->numentities++].time = cl.mtime[0]; + } + EntityFrame_AddFrame_Client(d, f->eye, f->framenum, f->numentities, f->entitydata); + + memset(cl.entities_active, 0, cl.num_entities * sizeof(unsigned char)); + number = 1; + for (i = 0;i < f->numentities;i++) + { + for (;number < f->entitydata[i].number && number < cl.num_entities;number++) + { + if (cl.entities_active[number]) + { + cl.entities_active[number] = false; + cl.entities[number].state_current.active = ACTIVE_NOT; + } + } + if (number >= cl.num_entities) + break; + // update the entity + ent = &cl.entities[number]; + ent->state_previous = ent->state_current; + ent->state_current = f->entitydata[i]; + CL_MoveLerpEntityStates(ent); + // the entity lives again... + cl.entities_active[number] = true; + number++; + } + for (;number < cl.num_entities;number++) + { + if (cl.entities_active[number]) + { + cl.entities_active[number] = false; + cl.entities[number].state_current.active = ACTIVE_NOT; + } + } +} + + +// (client) returns the frame number of the most recent frame recieved +int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d) +{ + if (d->numframes) + return d->frames[d->numframes - 1].framenum; + else + return -1; +} + + + + + + +entity_state_t *EntityFrame4_GetReferenceEntity(entityframe4_database_t *d, int number) +{ + if (d->maxreferenceentities <= number) + { + int oldmax = d->maxreferenceentities; + entity_state_t *oldentity = d->referenceentity; + d->maxreferenceentities = (number + 15) & ~7; + d->referenceentity = (entity_state_t *)Mem_Alloc(d->mempool, d->maxreferenceentities * sizeof(*d->referenceentity)); + if (oldentity) + { + memcpy(d->referenceentity, oldentity, oldmax * sizeof(*d->referenceentity)); + Mem_Free(oldentity); + } + // clear the newly created entities + for (;oldmax < d->maxreferenceentities;oldmax++) + { + d->referenceentity[oldmax] = defaultstate; + d->referenceentity[oldmax].number = oldmax; + } + } + return d->referenceentity + number; +} + +void EntityFrame4_AddCommitEntity(entityframe4_database_t *d, const entity_state_t *s) +{ + // resize commit's entity list if full + if (d->currentcommit->maxentities <= d->currentcommit->numentities) + { + entity_state_t *oldentity = d->currentcommit->entity; + d->currentcommit->maxentities += 8; + d->currentcommit->entity = (entity_state_t *)Mem_Alloc(d->mempool, d->currentcommit->maxentities * sizeof(*d->currentcommit->entity)); + if (oldentity) + { + memcpy(d->currentcommit->entity, oldentity, d->currentcommit->numentities * sizeof(*d->currentcommit->entity)); + Mem_Free(oldentity); + } + } + d->currentcommit->entity[d->currentcommit->numentities++] = *s; +} + +entityframe4_database_t *EntityFrame4_AllocDatabase(mempool_t *pool) +{ + entityframe4_database_t *d; + d = (entityframe4_database_t *)Mem_Alloc(pool, sizeof(*d)); + d->mempool = pool; + EntityFrame4_ResetDatabase(d); + return d; +} + +void EntityFrame4_FreeDatabase(entityframe4_database_t *d) +{ + int i; + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (d->commit[i].entity) + Mem_Free(d->commit[i].entity); + if (d->referenceentity) + Mem_Free(d->referenceentity); + Mem_Free(d); +} + +void EntityFrame4_ResetDatabase(entityframe4_database_t *d) +{ + int i; + d->referenceframenum = -1; + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + d->commit[i].numentities = 0; + for (i = 0;i < d->maxreferenceentities;i++) + d->referenceentity[i] = defaultstate; +} + +int EntityFrame4_AckFrame(entityframe4_database_t *d, int framenum, int servermode) +{ + int i, j, found; + entity_database4_commit_t *commit; + if (framenum == -1) + { + // reset reference, but leave commits alone + d->referenceframenum = -1; + for (i = 0;i < d->maxreferenceentities;i++) + d->referenceentity[i] = defaultstate; + // if this is the server, remove commits + for (i = 0, commit = d->commit;i < MAX_ENTITY_HISTORY;i++, commit++) + commit->numentities = 0; + found = true; + } + else if (d->referenceframenum == framenum) + found = true; + else + { + found = false; + for (i = 0, commit = d->commit;i < MAX_ENTITY_HISTORY;i++, commit++) + { + if (commit->numentities && commit->framenum <= framenum) + { + if (commit->framenum == framenum) + { + found = true; + d->referenceframenum = framenum; + if (developer_networkentities.integer >= 3) + { + for (j = 0;j < commit->numentities;j++) + { + entity_state_t *s = EntityFrame4_GetReferenceEntity(d, commit->entity[j].number); + if (commit->entity[j].active != s->active) + { + if (commit->entity[j].active == ACTIVE_NETWORK) + Con_Printf("commit entity %i has become active (modelindex %i)\n", commit->entity[j].number, commit->entity[j].modelindex); + else + Con_Printf("commit entity %i has become inactive (modelindex %i)\n", commit->entity[j].number, commit->entity[j].modelindex); + } + *s = commit->entity[j]; + } + } + else + for (j = 0;j < commit->numentities;j++) + *EntityFrame4_GetReferenceEntity(d, commit->entity[j].number) = commit->entity[j]; + } + commit->numentities = 0; + } + } + } + if (developer_networkentities.integer >= 1) + { + Con_Printf("ack ref:%i database updated to: ref:%i commits:", framenum, d->referenceframenum); + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (d->commit[i].numentities) + Con_Printf(" %i", d->commit[i].framenum); + Con_Print("\n"); + } + return found; +} + +void EntityFrame4_CL_ReadFrame(void) +{ + int i, n, cnumber, referenceframenum, framenum, enumber, done, stopnumber, skip = false; + entity_state_t *s; + entityframe4_database_t *d; + if (!cl.entitydatabase4) + cl.entitydatabase4 = EntityFrame4_AllocDatabase(cls.levelmempool); + d = cl.entitydatabase4; + // read the number of the frame this refers to + referenceframenum = MSG_ReadLong(); + // read the number of this frame + framenum = MSG_ReadLong(); + CL_NewFrameReceived(framenum); + // read the start number + enumber = (unsigned short) MSG_ReadShort(); + if (developer_networkentities.integer >= 10) + { + Con_Printf("recv svc_entities num:%i ref:%i database: ref:%i commits:", framenum, referenceframenum, d->referenceframenum); + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (d->commit[i].numentities) + Con_Printf(" %i", d->commit[i].framenum); + Con_Print("\n"); + } + if (!EntityFrame4_AckFrame(d, referenceframenum, false)) + { + Con_Print("EntityFrame4_CL_ReadFrame: reference frame invalid (VERY BAD ERROR), this update will be skipped\n"); + skip = true; + } + d->currentcommit = NULL; + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + { + if (!d->commit[i].numentities) + { + d->currentcommit = d->commit + i; + d->currentcommit->framenum = framenum; + d->currentcommit->numentities = 0; + } + } + if (d->currentcommit == NULL) + { + Con_Printf("EntityFrame4_CL_ReadFrame: error while decoding frame %i: database full, reading but not storing this update\n", framenum); + skip = true; + } + done = false; + while (!done && !msg_badread) + { + // read the number of the modified entity + // (gaps will be copied unmodified) + n = (unsigned short)MSG_ReadShort(); + if (n == 0x8000) + { + // no more entities in this update, but we still need to copy the + // rest of the reference entities (final gap) + done = true; + // read end of range number, then process normally + n = (unsigned short)MSG_ReadShort(); + } + // high bit means it's a remove message + cnumber = n & 0x7FFF; + // if this is a live entity we may need to expand the array + if (cl.num_entities <= cnumber && !(n & 0x8000)) + { + cl.num_entities = cnumber + 1; + if (cnumber >= cl.max_entities) + CL_ExpandEntities(cnumber); + } + // add one (the changed one) if not done + stopnumber = cnumber + !done; + // process entities in range from the last one to the changed one + for (;enumber < stopnumber;enumber++) + { + if (skip || enumber >= cl.num_entities) + { + if (enumber == cnumber && (n & 0x8000) == 0) + { + entity_state_t tempstate; + EntityState_ReadFields(&tempstate, EntityState_ReadExtendBits()); + } + continue; + } + // slide the current into the previous slot + cl.entities[enumber].state_previous = cl.entities[enumber].state_current; + // copy a new current from reference database + cl.entities[enumber].state_current = *EntityFrame4_GetReferenceEntity(d, enumber); + s = &cl.entities[enumber].state_current; + // if this is the one to modify, read more data... + if (enumber == cnumber) + { + if (n & 0x8000) + { + // simply removed + if (developer_networkentities.integer >= 2) + Con_Printf("entity %i: remove\n", enumber); + *s = defaultstate; + } + else + { + // read the changes + if (developer_networkentities.integer >= 2) + Con_Printf("entity %i: update\n", enumber); + s->active = ACTIVE_NETWORK; + EntityState_ReadFields(s, EntityState_ReadExtendBits()); + } + } + else if (developer_networkentities.integer >= 4) + Con_Printf("entity %i: copy\n", enumber); + // set the cl.entities_active flag + cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); + // set the update time + s->time = cl.mtime[0]; + // fix the number (it gets wiped occasionally by copying from defaultstate) + s->number = enumber; + // check if we need to update the lerp stuff + if (s->active == ACTIVE_NETWORK) + CL_MoveLerpEntityStates(&cl.entities[enumber]); + // add this to the commit entry whether it is modified or not + if (d->currentcommit) + EntityFrame4_AddCommitEntity(d, &cl.entities[enumber].state_current); + // print extra messages if desired + if (developer_networkentities.integer >= 2 && cl.entities[enumber].state_current.active != cl.entities[enumber].state_previous.active) + { + if (cl.entities[enumber].state_current.active == ACTIVE_NETWORK) + Con_Printf("entity #%i has become active\n", enumber); + else if (cl.entities[enumber].state_previous.active) + Con_Printf("entity #%i has become inactive\n", enumber); + } + } + } + d->currentcommit = NULL; + if (skip) + EntityFrame4_ResetDatabase(d); +} + +qboolean EntityFrame4_WriteFrame(sizebuf_t *msg, int maxsize, entityframe4_database_t *d, int numstates, const entity_state_t **states) +{ + const entity_state_t *e, *s; + entity_state_t inactiveentitystate; + int i, n, startnumber; + sizebuf_t buf; + unsigned char data[128]; + + // if there isn't enough space to accomplish anything, skip it + if (msg->cursize + 24 > maxsize) + return false; + + // prepare the buffer + memset(&buf, 0, sizeof(buf)); + buf.data = data; + buf.maxsize = sizeof(data); + + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (!d->commit[i].numentities) + break; + // if commit buffer full, just don't bother writing an update this frame + if (i == MAX_ENTITY_HISTORY) + return false; + d->currentcommit = d->commit + i; + + // this state's number gets played around with later + inactiveentitystate = defaultstate; + + d->currentcommit->numentities = 0; + d->currentcommit->framenum = ++d->latestframenumber; + MSG_WriteByte(msg, svc_entities); + MSG_WriteLong(msg, d->referenceframenum); + MSG_WriteLong(msg, d->currentcommit->framenum); + if (developer_networkentities.integer >= 10) + { + Con_Printf("send svc_entities num:%i ref:%i (database: ref:%i commits:", d->currentcommit->framenum, d->referenceframenum, d->referenceframenum); + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (d->commit[i].numentities) + Con_Printf(" %i", d->commit[i].framenum); + Con_Print(")\n"); + } + if (d->currententitynumber >= prog->max_edicts) + startnumber = 1; + else + startnumber = bound(1, d->currententitynumber, prog->max_edicts - 1); + MSG_WriteShort(msg, startnumber); + // reset currententitynumber so if the loop does not break it we will + // start at beginning next frame (if it does break, it will set it) + d->currententitynumber = 1; + for (i = 0, n = startnumber;n < prog->max_edicts;n++) + { + if (PRVM_serveredictfunction((&prog->edicts[n]), SendEntity)) + continue; + // find the old state to delta from + e = EntityFrame4_GetReferenceEntity(d, n); + // prepare the buffer + SZ_Clear(&buf); + // entity exists, build an update (if empty there is no change) + // find the state in the list + for (;i < numstates && states[i]->number < n;i++); + // make the message + s = states[i]; + if (s->number == n) + { + // build the update + EntityState_WriteUpdate(s, &buf, e); + } + else + { + inactiveentitystate.number = n; + s = &inactiveentitystate; + if (e->active == ACTIVE_NETWORK) + { + // entity used to exist but doesn't anymore, send remove + MSG_WriteShort(&buf, n | 0x8000); + } + } + // if the commit is full, we're done this frame + if (msg->cursize + buf.cursize > maxsize - 4) + { + // next frame we will continue where we left off + break; + } + // add the entity to the commit + EntityFrame4_AddCommitEntity(d, s); + // if the message is empty, skip out now + if (buf.cursize) + { + // write the message to the packet + SZ_Write(msg, buf.data, buf.cursize); + } + } + d->currententitynumber = n; + + // remove world message (invalid, and thus a good terminator) + MSG_WriteShort(msg, 0x8000); + // write the number of the end entity + MSG_WriteShort(msg, d->currententitynumber); + // just to be sure + d->currentcommit = NULL; + + return true; +} + + + + +entityframe5_database_t *EntityFrame5_AllocDatabase(mempool_t *pool) +{ + int i; + entityframe5_database_t *d; + d = (entityframe5_database_t *)Mem_Alloc(pool, sizeof(*d)); + d->latestframenum = 0; + for (i = 0;i < d->maxedicts;i++) + d->states[i] = defaultstate; + return d; +} + +void EntityFrame5_FreeDatabase(entityframe5_database_t *d) +{ + // all the [maxedicts] memory is allocated at once, so there's only one + // thing to free + if (d->maxedicts) + Mem_Free(d->deltabits); + Mem_Free(d); +} + +static void EntityFrame5_ExpandEdicts(entityframe5_database_t *d, int newmax) +{ + if (d->maxedicts < newmax) + { + unsigned char *data; + int oldmaxedicts = d->maxedicts; + int *olddeltabits = d->deltabits; + unsigned char *oldpriorities = d->priorities; + int *oldupdateframenum = d->updateframenum; + entity_state_t *oldstates = d->states; + unsigned char *oldvisiblebits = d->visiblebits; + d->maxedicts = newmax; + data = (unsigned char *)Mem_Alloc(sv_mempool, d->maxedicts * sizeof(int) + d->maxedicts * sizeof(unsigned char) + d->maxedicts * sizeof(int) + d->maxedicts * sizeof(entity_state_t) + (d->maxedicts+7)/8 * sizeof(unsigned char)); + d->deltabits = (int *)data;data += d->maxedicts * sizeof(int); + d->priorities = (unsigned char *)data;data += d->maxedicts * sizeof(unsigned char); + d->updateframenum = (int *)data;data += d->maxedicts * sizeof(int); + d->states = (entity_state_t *)data;data += d->maxedicts * sizeof(entity_state_t); + d->visiblebits = (unsigned char *)data;data += (d->maxedicts+7)/8 * sizeof(unsigned char); + if (oldmaxedicts) + { + memcpy(d->deltabits, olddeltabits, oldmaxedicts * sizeof(int)); + memcpy(d->priorities, oldpriorities, oldmaxedicts * sizeof(unsigned char)); + memcpy(d->updateframenum, oldupdateframenum, oldmaxedicts * sizeof(int)); + memcpy(d->states, oldstates, oldmaxedicts * sizeof(entity_state_t)); + memcpy(d->visiblebits, oldvisiblebits, (oldmaxedicts+7)/8 * sizeof(unsigned char)); + // the previous buffers were a single allocation, so just one free + Mem_Free(olddeltabits); + } + } +} + +static int EntityState5_Priority(entityframe5_database_t *d, int stateindex) +{ + int limit, priority; + entity_state_t *s = NULL; // hush compiler warning by initializing this + // if it is the player, update urgently + if (stateindex == d->viewentnum) + return ENTITYFRAME5_PRIORITYLEVELS - 1; + // priority increases each frame no matter what happens + priority = d->priorities[stateindex] + 1; + // players get an extra priority boost + if (stateindex <= svs.maxclients) + priority++; + // remove dead entities very quickly because they are just 2 bytes + if (d->states[stateindex].active != ACTIVE_NETWORK) + { + priority++; + return bound(1, priority, ENTITYFRAME5_PRIORITYLEVELS - 1); + } + // certain changes are more noticable than others + if (d->deltabits[stateindex] & (E5_FULLUPDATE | E5_ATTACHMENT | E5_MODEL | E5_FLAGS | E5_COLORMAP)) + priority++; + // find the root entity this one is attached to, and judge relevance by it + for (limit = 0;limit < 256;limit++) + { + s = d->states + stateindex; + if (s->flags & RENDER_VIEWMODEL) + stateindex = d->viewentnum; + else if (s->tagentity) + stateindex = s->tagentity; + else + break; + if (d->maxedicts < stateindex) + EntityFrame5_ExpandEdicts(d, (stateindex+256)&~255); + } + if (limit >= 256) + Con_DPrintf("Protocol: Runaway loop recursing tagentity links on entity %i\n", stateindex); + // now that we have the parent entity we can make some decisions based on + // distance from the player + if (VectorDistance(d->states[d->viewentnum].netcenter, s->netcenter) < 1024.0f) + priority++; + return bound(1, priority, ENTITYFRAME5_PRIORITYLEVELS - 1); +} + +void EntityState5_WriteUpdate(int number, const entity_state_t *s, int changedbits, sizebuf_t *msg) +{ + unsigned int bits = 0; + //dp_model_t *model; + ENTITYSIZEPROFILING_START(msg, s->number); + + if (s->active != ACTIVE_NETWORK) + MSG_WriteShort(msg, number | 0x8000); + else + { + if (PRVM_serveredictfunction((&prog->edicts[s->number]), SendEntity)) + return; + + bits = changedbits; + if ((bits & E5_ORIGIN) && (!(s->flags & RENDER_LOWPRECISION) || s->exteriormodelforclient || s->tagentity || s->viewmodelforclient || (s->number >= 1 && s->number <= svs.maxclients) || s->origin[0] <= -4096.0625 || s->origin[0] >= 4095.9375 || s->origin[1] <= -4096.0625 || s->origin[1] >= 4095.9375 || s->origin[2] <= -4096.0625 || s->origin[2] >= 4095.9375)) + // maybe also add: ((model = SV_GetModelByIndex(s->modelindex)) != NULL && model->name[0] == '*') + bits |= E5_ORIGIN32; + // possible values: + // negative origin: + // (int)(f * 8 - 0.5) >= -32768 + // (f * 8 - 0.5) > -32769 + // f > -4096.0625 + // positive origin: + // (int)(f * 8 + 0.5) <= 32767 + // (f * 8 + 0.5) < 32768 + // f * 8 + 0.5) < 4095.9375 + if ((bits & E5_ANGLES) && !(s->flags & RENDER_LOWPRECISION)) + bits |= E5_ANGLES16; + if ((bits & E5_MODEL) && s->modelindex >= 256) + bits |= E5_MODEL16; + if ((bits & E5_FRAME) && s->frame >= 256) + bits |= E5_FRAME16; + if (bits & E5_EFFECTS) + { + if (s->effects & 0xFFFF0000) + bits |= E5_EFFECTS32; + else if (s->effects & 0xFFFFFF00) + bits |= E5_EFFECTS16; + } + if (bits >= 256) + bits |= E5_EXTEND1; + if (bits >= 65536) + bits |= E5_EXTEND2; + if (bits >= 16777216) + bits |= E5_EXTEND3; + MSG_WriteShort(msg, number); + MSG_WriteByte(msg, bits & 0xFF); + if (bits & E5_EXTEND1) + MSG_WriteByte(msg, (bits >> 8) & 0xFF); + if (bits & E5_EXTEND2) + MSG_WriteByte(msg, (bits >> 16) & 0xFF); + if (bits & E5_EXTEND3) + MSG_WriteByte(msg, (bits >> 24) & 0xFF); + if (bits & E5_FLAGS) + MSG_WriteByte(msg, s->flags); + if (bits & E5_ORIGIN) + { + if (bits & E5_ORIGIN32) + { + MSG_WriteCoord32f(msg, s->origin[0]); + MSG_WriteCoord32f(msg, s->origin[1]); + MSG_WriteCoord32f(msg, s->origin[2]); + } + else + { + MSG_WriteCoord13i(msg, s->origin[0]); + MSG_WriteCoord13i(msg, s->origin[1]); + MSG_WriteCoord13i(msg, s->origin[2]); + } + } + if (bits & E5_ANGLES) + { + if (bits & E5_ANGLES16) + { + MSG_WriteAngle16i(msg, s->angles[0]); + MSG_WriteAngle16i(msg, s->angles[1]); + MSG_WriteAngle16i(msg, s->angles[2]); + } + else + { + MSG_WriteAngle8i(msg, s->angles[0]); + MSG_WriteAngle8i(msg, s->angles[1]); + MSG_WriteAngle8i(msg, s->angles[2]); + } + } + if (bits & E5_MODEL) + { + if (bits & E5_MODEL16) + MSG_WriteShort(msg, s->modelindex); + else + MSG_WriteByte(msg, s->modelindex); + } + if (bits & E5_FRAME) + { + if (bits & E5_FRAME16) + MSG_WriteShort(msg, s->frame); + else + MSG_WriteByte(msg, s->frame); + } + if (bits & E5_SKIN) + MSG_WriteByte(msg, s->skin); + if (bits & E5_EFFECTS) + { + if (bits & E5_EFFECTS32) + MSG_WriteLong(msg, s->effects); + else if (bits & E5_EFFECTS16) + MSG_WriteShort(msg, s->effects); + else + MSG_WriteByte(msg, s->effects); + } + if (bits & E5_ALPHA) + MSG_WriteByte(msg, s->alpha); + if (bits & E5_SCALE) + MSG_WriteByte(msg, s->scale); + if (bits & E5_COLORMAP) + MSG_WriteByte(msg, s->colormap); + if (bits & E5_ATTACHMENT) + { + MSG_WriteShort(msg, s->tagentity); + MSG_WriteByte(msg, s->tagindex); + } + if (bits & E5_LIGHT) + { + MSG_WriteShort(msg, s->light[0]); + MSG_WriteShort(msg, s->light[1]); + MSG_WriteShort(msg, s->light[2]); + MSG_WriteShort(msg, s->light[3]); + MSG_WriteByte(msg, s->lightstyle); + MSG_WriteByte(msg, s->lightpflags); + } + if (bits & E5_GLOW) + { + MSG_WriteByte(msg, s->glowsize); + MSG_WriteByte(msg, s->glowcolor); + } + if (bits & E5_COLORMOD) + { + MSG_WriteByte(msg, s->colormod[0]); + MSG_WriteByte(msg, s->colormod[1]); + MSG_WriteByte(msg, s->colormod[2]); + } + if (bits & E5_GLOWMOD) + { + MSG_WriteByte(msg, s->glowmod[0]); + MSG_WriteByte(msg, s->glowmod[1]); + MSG_WriteByte(msg, s->glowmod[2]); + } + if (bits & E5_COMPLEXANIMATION) + { + if (s->skeletonobject.model && s->skeletonobject.relativetransforms) + { + int numbones = s->skeletonobject.model->num_bones; + int bonenum; + short pose6s[6]; + MSG_WriteByte(msg, 4); + MSG_WriteShort(msg, s->modelindex); + MSG_WriteByte(msg, numbones); + for (bonenum = 0;bonenum < numbones;bonenum++) + { + Matrix4x4_ToBonePose6s(s->skeletonobject.relativetransforms + bonenum, 64, pose6s); + MSG_WriteShort(msg, pose6s[0]); + MSG_WriteShort(msg, pose6s[1]); + MSG_WriteShort(msg, pose6s[2]); + MSG_WriteShort(msg, pose6s[3]); + MSG_WriteShort(msg, pose6s[4]); + MSG_WriteShort(msg, pose6s[5]); + } + } + else if (s->framegroupblend[3].lerp > 0) + { + MSG_WriteByte(msg, 3); + MSG_WriteShort(msg, s->framegroupblend[0].frame); + MSG_WriteShort(msg, s->framegroupblend[1].frame); + MSG_WriteShort(msg, s->framegroupblend[2].frame); + MSG_WriteShort(msg, s->framegroupblend[3].frame); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[0].start) * 1000.0)); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[1].start) * 1000.0)); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[2].start) * 1000.0)); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[3].start) * 1000.0)); + MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[2].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[3].lerp * 255.0f); + } + else if (s->framegroupblend[2].lerp > 0) + { + MSG_WriteByte(msg, 2); + MSG_WriteShort(msg, s->framegroupblend[0].frame); + MSG_WriteShort(msg, s->framegroupblend[1].frame); + MSG_WriteShort(msg, s->framegroupblend[2].frame); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[0].start) * 1000.0)); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[1].start) * 1000.0)); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[2].start) * 1000.0)); + MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[2].lerp * 255.0f); + } + else if (s->framegroupblend[1].lerp > 0) + { + MSG_WriteByte(msg, 1); + MSG_WriteShort(msg, s->framegroupblend[0].frame); + MSG_WriteShort(msg, s->framegroupblend[1].frame); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[0].start) * 1000.0)); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[1].start) * 1000.0)); + MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); + } + else + { + MSG_WriteByte(msg, 0); + MSG_WriteShort(msg, s->framegroupblend[0].frame); + MSG_WriteShort(msg, (int)((sv.time - s->framegroupblend[0].start) * 1000.0)); + } + } + if (bits & E5_TRAILEFFECTNUM) + MSG_WriteShort(msg, s->traileffectnum); + } + + ENTITYSIZEPROFILING_END(msg, s->number); +} + +extern dp_model_t *CL_GetModelByIndex(int modelindex); + +static void EntityState5_ReadUpdate(entity_state_t *s, int number) +{ + int bits; + bits = MSG_ReadByte(); + if (bits & E5_EXTEND1) + { + bits |= MSG_ReadByte() << 8; + if (bits & E5_EXTEND2) + { + bits |= MSG_ReadByte() << 16; + if (bits & E5_EXTEND3) + bits |= MSG_ReadByte() << 24; + } + } + if (bits & E5_FULLUPDATE) + { + *s = defaultstate; + s->active = ACTIVE_NETWORK; + } + if (bits & E5_FLAGS) + s->flags = MSG_ReadByte(); + if (bits & E5_ORIGIN) + { + if (bits & E5_ORIGIN32) + { + s->origin[0] = MSG_ReadCoord32f(); + s->origin[1] = MSG_ReadCoord32f(); + s->origin[2] = MSG_ReadCoord32f(); + } + else + { + s->origin[0] = MSG_ReadCoord13i(); + s->origin[1] = MSG_ReadCoord13i(); + s->origin[2] = MSG_ReadCoord13i(); + } + } + if (bits & E5_ANGLES) + { + if (bits & E5_ANGLES16) + { + s->angles[0] = MSG_ReadAngle16i(); + s->angles[1] = MSG_ReadAngle16i(); + s->angles[2] = MSG_ReadAngle16i(); + } + else + { + s->angles[0] = MSG_ReadAngle8i(); + s->angles[1] = MSG_ReadAngle8i(); + s->angles[2] = MSG_ReadAngle8i(); + } + } + if (bits & E5_MODEL) + { + if (bits & E5_MODEL16) + s->modelindex = (unsigned short) MSG_ReadShort(); + else + s->modelindex = MSG_ReadByte(); + } + if (bits & E5_FRAME) + { + if (bits & E5_FRAME16) + s->frame = (unsigned short) MSG_ReadShort(); + else + s->frame = MSG_ReadByte(); + } + if (bits & E5_SKIN) + s->skin = MSG_ReadByte(); + if (bits & E5_EFFECTS) + { + if (bits & E5_EFFECTS32) + s->effects = (unsigned int) MSG_ReadLong(); + else if (bits & E5_EFFECTS16) + s->effects = (unsigned short) MSG_ReadShort(); + else + s->effects = MSG_ReadByte(); + } + if (bits & E5_ALPHA) + s->alpha = MSG_ReadByte(); + if (bits & E5_SCALE) + s->scale = MSG_ReadByte(); + if (bits & E5_COLORMAP) + s->colormap = MSG_ReadByte(); + if (bits & E5_ATTACHMENT) + { + s->tagentity = (unsigned short) MSG_ReadShort(); + s->tagindex = MSG_ReadByte(); + } + if (bits & E5_LIGHT) + { + s->light[0] = (unsigned short) MSG_ReadShort(); + s->light[1] = (unsigned short) MSG_ReadShort(); + s->light[2] = (unsigned short) MSG_ReadShort(); + s->light[3] = (unsigned short) MSG_ReadShort(); + s->lightstyle = MSG_ReadByte(); + s->lightpflags = MSG_ReadByte(); + } + if (bits & E5_GLOW) + { + s->glowsize = MSG_ReadByte(); + s->glowcolor = MSG_ReadByte(); + } + if (bits & E5_COLORMOD) + { + s->colormod[0] = MSG_ReadByte(); + s->colormod[1] = MSG_ReadByte(); + s->colormod[2] = MSG_ReadByte(); + } + if (bits & E5_GLOWMOD) + { + s->glowmod[0] = MSG_ReadByte(); + s->glowmod[1] = MSG_ReadByte(); + s->glowmod[2] = MSG_ReadByte(); + } + if (bits & E5_COMPLEXANIMATION) + { + skeleton_t *skeleton; + const dp_model_t *model; + int modelindex; + int type; + int bonenum; + int numbones; + short pose6s[6]; + type = MSG_ReadByte(); + switch(type) + { + case 0: + s->framegroupblend[0].frame = MSG_ReadShort(); + s->framegroupblend[1].frame = 0; + s->framegroupblend[2].frame = 0; + s->framegroupblend[3].frame = 0; + s->framegroupblend[0].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[1].start = 0; + s->framegroupblend[2].start = 0; + s->framegroupblend[3].start = 0; + s->framegroupblend[0].lerp = 1; + s->framegroupblend[1].lerp = 0; + s->framegroupblend[2].lerp = 0; + s->framegroupblend[3].lerp = 0; + break; + case 1: + s->framegroupblend[0].frame = MSG_ReadShort(); + s->framegroupblend[1].frame = MSG_ReadShort(); + s->framegroupblend[2].frame = 0; + s->framegroupblend[3].frame = 0; + s->framegroupblend[0].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[1].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[2].start = 0; + s->framegroupblend[3].start = 0; + s->framegroupblend[0].lerp = MSG_ReadByte() * (1.0f / 255.0f); + s->framegroupblend[1].lerp = MSG_ReadByte() * (1.0f / 255.0f); + s->framegroupblend[2].lerp = 0; + s->framegroupblend[3].lerp = 0; + break; + case 2: + s->framegroupblend[0].frame = MSG_ReadShort(); + s->framegroupblend[1].frame = MSG_ReadShort(); + s->framegroupblend[2].frame = MSG_ReadShort(); + s->framegroupblend[3].frame = 0; + s->framegroupblend[0].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[1].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[2].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[3].start = 0; + s->framegroupblend[0].lerp = MSG_ReadByte() * (1.0f / 255.0f); + s->framegroupblend[1].lerp = MSG_ReadByte() * (1.0f / 255.0f); + s->framegroupblend[2].lerp = MSG_ReadByte() * (1.0f / 255.0f); + s->framegroupblend[3].lerp = 0; + break; + case 3: + s->framegroupblend[0].frame = MSG_ReadShort(); + s->framegroupblend[1].frame = MSG_ReadShort(); + s->framegroupblend[2].frame = MSG_ReadShort(); + s->framegroupblend[3].frame = MSG_ReadShort(); + s->framegroupblend[0].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[1].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[2].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[3].start = cl.time - (short)MSG_ReadShort() * (1.0f / 1000.0f); + s->framegroupblend[0].lerp = MSG_ReadByte() * (1.0f / 255.0f); + s->framegroupblend[1].lerp = MSG_ReadByte() * (1.0f / 255.0f); + s->framegroupblend[2].lerp = MSG_ReadByte() * (1.0f / 255.0f); + s->framegroupblend[3].lerp = MSG_ReadByte() * (1.0f / 255.0f); + break; + case 4: + if (!cl.engineskeletonobjects) + cl.engineskeletonobjects = (skeleton_t *) Mem_Alloc(cls.levelmempool, sizeof(*cl.engineskeletonobjects) * MAX_EDICTS); + skeleton = &cl.engineskeletonobjects[number]; + modelindex = MSG_ReadShort(); + model = CL_GetModelByIndex(modelindex); + numbones = MSG_ReadByte(); + if (model && numbones != model->num_bones) + Host_Error("E5_COMPLEXANIMATION: model has different number of bones than network packet describes\n"); + if (!skeleton->relativetransforms || skeleton->model != model) + { + skeleton->model = model; + skeleton->relativetransforms = (matrix4x4_t *) Mem_Realloc(cls.levelmempool, skeleton->relativetransforms, sizeof(*skeleton->relativetransforms) * skeleton->model->num_bones); + for (bonenum = 0;bonenum < model->num_bones;bonenum++) + skeleton->relativetransforms[bonenum] = identitymatrix; + } + for (bonenum = 0;bonenum < numbones;bonenum++) + { + pose6s[0] = (short)MSG_ReadShort(); + pose6s[1] = (short)MSG_ReadShort(); + pose6s[2] = (short)MSG_ReadShort(); + pose6s[3] = (short)MSG_ReadShort(); + pose6s[4] = (short)MSG_ReadShort(); + pose6s[5] = (short)MSG_ReadShort(); + Matrix4x4_FromBonePose6s(skeleton->relativetransforms + bonenum, 1.0f / 64.0f, pose6s); + } + s->skeletonobject = *skeleton; + break; + default: + Host_Error("E5_COMPLEXANIMATION: Parse error - unknown type %i\n", type); + break; + } + } + if (bits & E5_TRAILEFFECTNUM) + s->traileffectnum = (unsigned short) MSG_ReadShort(); + + + if (developer_networkentities.integer >= 2) + { + Con_Printf("ReadFields e%i", number); + + if (bits & E5_ORIGIN) + Con_Printf(" E5_ORIGIN %f %f %f", s->origin[0], s->origin[1], s->origin[2]); + if (bits & E5_ANGLES) + Con_Printf(" E5_ANGLES %f %f %f", s->angles[0], s->angles[1], s->angles[2]); + if (bits & E5_MODEL) + Con_Printf(" E5_MODEL %i", s->modelindex); + if (bits & E5_FRAME) + Con_Printf(" E5_FRAME %i", s->frame); + if (bits & E5_SKIN) + Con_Printf(" E5_SKIN %i", s->skin); + if (bits & E5_EFFECTS) + Con_Printf(" E5_EFFECTS %i", s->effects); + if (bits & E5_FLAGS) + { + Con_Printf(" E5_FLAGS %i (", s->flags); + if (s->flags & RENDER_STEP) + Con_Print(" STEP"); + if (s->flags & RENDER_GLOWTRAIL) + Con_Print(" GLOWTRAIL"); + if (s->flags & RENDER_VIEWMODEL) + Con_Print(" VIEWMODEL"); + if (s->flags & RENDER_EXTERIORMODEL) + Con_Print(" EXTERIORMODEL"); + if (s->flags & RENDER_LOWPRECISION) + Con_Print(" LOWPRECISION"); + if (s->flags & RENDER_COLORMAPPED) + Con_Print(" COLORMAPPED"); + if (s->flags & RENDER_SHADOW) + Con_Print(" SHADOW"); + if (s->flags & RENDER_LIGHT) + Con_Print(" LIGHT"); + if (s->flags & RENDER_NOSELFSHADOW) + Con_Print(" NOSELFSHADOW"); + Con_Print(")"); + } + if (bits & E5_ALPHA) + Con_Printf(" E5_ALPHA %f", s->alpha / 255.0f); + if (bits & E5_SCALE) + Con_Printf(" E5_SCALE %f", s->scale / 16.0f); + if (bits & E5_COLORMAP) + Con_Printf(" E5_COLORMAP %i", s->colormap); + if (bits & E5_ATTACHMENT) + Con_Printf(" E5_ATTACHMENT e%i:%i", s->tagentity, s->tagindex); + if (bits & E5_LIGHT) + Con_Printf(" E5_LIGHT %i:%i:%i:%i %i:%i", s->light[0], s->light[1], s->light[2], s->light[3], s->lightstyle, s->lightpflags); + if (bits & E5_GLOW) + Con_Printf(" E5_GLOW %i:%i", s->glowsize * 4, s->glowcolor); + if (bits & E5_COLORMOD) + Con_Printf(" E5_COLORMOD %f:%f:%f", s->colormod[0] / 32.0f, s->colormod[1] / 32.0f, s->colormod[2] / 32.0f); + if (bits & E5_GLOWMOD) + Con_Printf(" E5_GLOWMOD %f:%f:%f", s->glowmod[0] / 32.0f, s->glowmod[1] / 32.0f, s->glowmod[2] / 32.0f); + Con_Print("\n"); + } +} + +static int EntityState5_DeltaBits(const entity_state_t *o, const entity_state_t *n) +{ + unsigned int bits = 0; + if (n->active == ACTIVE_NETWORK) + { + if (o->active != ACTIVE_NETWORK) + bits |= E5_FULLUPDATE; + if (!VectorCompare(o->origin, n->origin)) + bits |= E5_ORIGIN; + if (!VectorCompare(o->angles, n->angles)) + bits |= E5_ANGLES; + if (o->modelindex != n->modelindex) + bits |= E5_MODEL; + if (o->frame != n->frame) + bits |= E5_FRAME; + if (o->skin != n->skin) + bits |= E5_SKIN; + if (o->effects != n->effects) + bits |= E5_EFFECTS; + if (o->flags != n->flags) + bits |= E5_FLAGS; + if (o->alpha != n->alpha) + bits |= E5_ALPHA; + if (o->scale != n->scale) + bits |= E5_SCALE; + if (o->colormap != n->colormap) + bits |= E5_COLORMAP; + if (o->tagentity != n->tagentity || o->tagindex != n->tagindex) + bits |= E5_ATTACHMENT; + if (o->light[0] != n->light[0] || o->light[1] != n->light[1] || o->light[2] != n->light[2] || o->light[3] != n->light[3] || o->lightstyle != n->lightstyle || o->lightpflags != n->lightpflags) + bits |= E5_LIGHT; + if (o->glowsize != n->glowsize || o->glowcolor != n->glowcolor) + bits |= E5_GLOW; + if (o->colormod[0] != n->colormod[0] || o->colormod[1] != n->colormod[1] || o->colormod[2] != n->colormod[2]) + bits |= E5_COLORMOD; + if (o->glowmod[0] != n->glowmod[0] || o->glowmod[1] != n->glowmod[1] || o->glowmod[2] != n->glowmod[2]) + bits |= E5_GLOWMOD; + if (n->flags & RENDER_COMPLEXANIMATION) + bits |= E5_COMPLEXANIMATION; + if (o->traileffectnum != n->traileffectnum) + bits |= E5_TRAILEFFECTNUM; + } + else + if (o->active == ACTIVE_NETWORK) + bits |= E5_FULLUPDATE; + return bits; +} + +void EntityFrame5_CL_ReadFrame(void) +{ + int n, enumber, framenum; + entity_t *ent; + entity_state_t *s; + // read the number of this frame to echo back in next input packet + framenum = MSG_ReadLong(); + CL_NewFrameReceived(framenum); + if (cls.protocol != PROTOCOL_QUAKE && cls.protocol != PROTOCOL_QUAKEDP && cls.protocol != PROTOCOL_NEHAHRAMOVIE && cls.protocol != PROTOCOL_DARKPLACES1 && cls.protocol != PROTOCOL_DARKPLACES2 && cls.protocol != PROTOCOL_DARKPLACES3 && cls.protocol != PROTOCOL_DARKPLACES4 && cls.protocol != PROTOCOL_DARKPLACES5 && cls.protocol != PROTOCOL_DARKPLACES6) + cls.servermovesequence = MSG_ReadLong(); + // read entity numbers until we find a 0x8000 + // (which would be remove world entity, but is actually a terminator) + while ((n = (unsigned short)MSG_ReadShort()) != 0x8000 && !msg_badread) + { + // get the entity number + enumber = n & 0x7FFF; + // we may need to expand the array + if (cl.num_entities <= enumber) + { + cl.num_entities = enumber + 1; + if (enumber >= cl.max_entities) + CL_ExpandEntities(enumber); + } + // look up the entity + ent = cl.entities + enumber; + // slide the current into the previous slot + ent->state_previous = ent->state_current; + // read the update + s = &ent->state_current; + if (n & 0x8000) + { + // remove entity + *s = defaultstate; + } + else + { + // update entity + EntityState5_ReadUpdate(s, enumber); + } + // set the cl.entities_active flag + cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); + // set the update time + s->time = cl.mtime[0]; + // fix the number (it gets wiped occasionally by copying from defaultstate) + s->number = enumber; + // check if we need to update the lerp stuff + if (s->active == ACTIVE_NETWORK) + CL_MoveLerpEntityStates(&cl.entities[enumber]); + // print extra messages if desired + if (developer_networkentities.integer >= 2 && cl.entities[enumber].state_current.active != cl.entities[enumber].state_previous.active) + { + if (cl.entities[enumber].state_current.active == ACTIVE_NETWORK) + Con_Printf("entity #%i has become active\n", enumber); + else if (cl.entities[enumber].state_previous.active) + Con_Printf("entity #%i has become inactive\n", enumber); + } + } +} + +static int packetlog5cmp(const void *a_, const void *b_) +{ + const entityframe5_packetlog_t *a = (const entityframe5_packetlog_t *) a_; + const entityframe5_packetlog_t *b = (const entityframe5_packetlog_t *) b_; + return a->packetnumber - b->packetnumber; +} + +void EntityFrame5_LostFrame(entityframe5_database_t *d, int framenum) +{ + int i, j, l, bits; + entityframe5_changestate_t *s; + entityframe5_packetlog_t *p; + static unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; + static int deltabits[MAX_EDICTS]; + entityframe5_packetlog_t *packetlogs[ENTITYFRAME5_MAXPACKETLOGS]; + + for (i = 0, p = d->packetlog;i < ENTITYFRAME5_MAXPACKETLOGS;i++, p++) + packetlogs[i] = p; + qsort(packetlogs, sizeof(*packetlogs), ENTITYFRAME5_MAXPACKETLOGS, packetlog5cmp); + + memset(deltabits, 0, sizeof(deltabits)); + memset(statsdeltabits, 0, sizeof(statsdeltabits)); + for (i = 0; i < ENTITYFRAME5_MAXPACKETLOGS; i++) + { + p = packetlogs[i]; + + if (!p->packetnumber) + continue; + + if (p->packetnumber <= framenum) + { + for (j = 0, s = p->states;j < p->numstates;j++, s++) + deltabits[s->number] |= s->bits; + + for (l = 0;l < (MAX_CL_STATS+7)/8;l++) + statsdeltabits[l] |= p->statsdeltabits[l]; + + p->packetnumber = 0; + } + else + { + for (j = 0, s = p->states;j < p->numstates;j++, s++) + deltabits[s->number] &= ~s->bits; + for (l = 0;l < (MAX_CL_STATS+7)/8;l++) + statsdeltabits[l] &= ~p->statsdeltabits[l]; + } + } + + for(i = 0; i < d->maxedicts; ++i) + { + bits = deltabits[i] & ~d->deltabits[i]; + if(bits) + { + d->deltabits[i] |= bits; + // if it was a very important update, set priority higher + if (bits & (E5_FULLUPDATE | E5_ATTACHMENT | E5_MODEL | E5_COLORMAP)) + d->priorities[i] = max(d->priorities[i], 4); + else + d->priorities[i] = max(d->priorities[i], 1); + } + } + + for (l = 0;l < (MAX_CL_STATS+7)/8;l++) + host_client->statsdeltabits[l] |= statsdeltabits[l]; + // no need to mask out the already-set bits here, as we do not + // do that priorities stuff +} + +void EntityFrame5_AckFrame(entityframe5_database_t *d, int framenum) +{ + int i; + // scan for packets made obsolete by this ack and delete them + for (i = 0;i < ENTITYFRAME5_MAXPACKETLOGS;i++) + if (d->packetlog[i].packetnumber <= framenum) + d->packetlog[i].packetnumber = 0; +} + +qboolean EntityFrame5_WriteFrame(sizebuf_t *msg, int maxsize, entityframe5_database_t *d, int numstates, const entity_state_t **states, int viewentnum, int movesequence, qboolean need_empty) +{ + const entity_state_t *n; + int i, num, l, framenum, packetlognumber, priority; + sizebuf_t buf; + unsigned char data[128]; + entityframe5_packetlog_t *packetlog; + + if (prog->max_edicts > d->maxedicts) + EntityFrame5_ExpandEdicts(d, prog->max_edicts); + + framenum = d->latestframenum + 1; + d->viewentnum = viewentnum; + + // if packet log is full, mark all frames as lost, this will cause + // it to send the lost data again + for (packetlognumber = 0;packetlognumber < ENTITYFRAME5_MAXPACKETLOGS;packetlognumber++) + if (d->packetlog[packetlognumber].packetnumber == 0) + break; + if (packetlognumber == ENTITYFRAME5_MAXPACKETLOGS) + { + Con_DPrintf("EntityFrame5_WriteFrame: packetlog overflow for a client, resetting\n"); + EntityFrame5_LostFrame(d, framenum); + packetlognumber = 0; + } + + // prepare the buffer + memset(&buf, 0, sizeof(buf)); + buf.data = data; + buf.maxsize = sizeof(data); + + // detect changes in states + num = 1; + for (i = 0;i < numstates;i++) + { + n = states[i]; + // mark gaps in entity numbering as removed entities + for (;num < n->number;num++) + { + // if the entity used to exist, clear it + if (CHECKPVSBIT(d->visiblebits, num)) + { + CLEARPVSBIT(d->visiblebits, num); + d->deltabits[num] = E5_FULLUPDATE; + d->priorities[num] = max(d->priorities[num], 8); // removal is cheap + d->states[num] = defaultstate; + d->states[num].number = num; + } + } + // update the entity state data + if (!CHECKPVSBIT(d->visiblebits, num)) + { + // entity just spawned in, don't let it completely hog priority + // because of being ancient on the first frame + d->updateframenum[num] = framenum; + // initial priority is a bit high to make projectiles send on the + // first frame, among other things + d->priorities[num] = max(d->priorities[num], 4); + } + SETPVSBIT(d->visiblebits, num); + d->deltabits[num] |= EntityState5_DeltaBits(d->states + num, n); + d->priorities[num] = max(d->priorities[num], 1); + d->states[num] = *n; + d->states[num].number = num; + // advance to next entity so the next iteration doesn't immediately remove it + num++; + } + // all remaining entities are dead + for (;num < d->maxedicts;num++) + { + if (CHECKPVSBIT(d->visiblebits, num)) + { + CLEARPVSBIT(d->visiblebits, num); + d->deltabits[num] = E5_FULLUPDATE; + d->priorities[num] = max(d->priorities[num], 8); // removal is cheap + d->states[num] = defaultstate; + d->states[num].number = num; + } + } + + // if there isn't at least enough room for an empty svc_entities, + // don't bother trying... + if (buf.cursize + 11 > buf.maxsize) + return false; + + // build lists of entities by priority level + memset(d->prioritychaincounts, 0, sizeof(d->prioritychaincounts)); + l = 0; + for (num = 0;num < d->maxedicts;num++) + { + if (d->priorities[num]) + { + if (d->deltabits[num]) + { + if (d->priorities[num] < (ENTITYFRAME5_PRIORITYLEVELS - 1)) + d->priorities[num] = EntityState5_Priority(d, num); + l = num; + priority = d->priorities[num]; + if (d->prioritychaincounts[priority] < ENTITYFRAME5_MAXSTATES) + d->prioritychains[priority][d->prioritychaincounts[priority]++] = num; + } + else + d->priorities[num] = 0; + } + } + + packetlog = NULL; + // write stat updates + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5) + { + for (i = 0;i < MAX_CL_STATS && msg->cursize + 6 + 11 <= maxsize;i++) + { + if (host_client->statsdeltabits[i>>3] & (1<<(i&7))) + { + host_client->statsdeltabits[i>>3] &= ~(1<<(i&7)); + // add packetlog entry now that we have something for it + if (!packetlog) + { + packetlog = d->packetlog + packetlognumber; + packetlog->packetnumber = framenum; + packetlog->numstates = 0; + memset(packetlog->statsdeltabits, 0, sizeof(packetlog->statsdeltabits)); + } + packetlog->statsdeltabits[i>>3] |= (1<<(i&7)); + if (host_client->stats[i] >= 0 && host_client->stats[i] < 256) + { + MSG_WriteByte(msg, svc_updatestatubyte); + MSG_WriteByte(msg, i); + MSG_WriteByte(msg, host_client->stats[i]); + l = 1; + } + else + { + MSG_WriteByte(msg, svc_updatestat); + MSG_WriteByte(msg, i); + MSG_WriteLong(msg, host_client->stats[i]); + l = 1; + } + } + } + } + + // only send empty svc_entities frame if needed + if(!l && !need_empty) + return false; + + // add packetlog entry now that we have something for it + if (!packetlog) + { + packetlog = d->packetlog + packetlognumber; + packetlog->packetnumber = framenum; + packetlog->numstates = 0; + memset(packetlog->statsdeltabits, 0, sizeof(packetlog->statsdeltabits)); + } + + // write state updates + if (developer_networkentities.integer >= 10) + Con_Printf("send: svc_entities %i\n", framenum); + d->latestframenum = framenum; + MSG_WriteByte(msg, svc_entities); + MSG_WriteLong(msg, framenum); + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5 && sv.protocol != PROTOCOL_DARKPLACES6) + MSG_WriteLong(msg, movesequence); + for (priority = ENTITYFRAME5_PRIORITYLEVELS - 1;priority >= 0 && packetlog->numstates < ENTITYFRAME5_MAXSTATES;priority--) + { + for (i = 0;i < d->prioritychaincounts[priority] && packetlog->numstates < ENTITYFRAME5_MAXSTATES;i++) + { + num = d->prioritychains[priority][i]; + n = d->states + num; + if (d->deltabits[num] & E5_FULLUPDATE) + d->deltabits[num] = E5_FULLUPDATE | EntityState5_DeltaBits(&defaultstate, n); + buf.cursize = 0; + EntityState5_WriteUpdate(num, n, d->deltabits[num], &buf); + // if the entity won't fit, try the next one + if (msg->cursize + buf.cursize + 2 > maxsize) + continue; + // write entity to the packet + SZ_Write(msg, buf.data, buf.cursize); + // mark age on entity for prioritization + d->updateframenum[num] = framenum; + // log entity so deltabits can be restored later if lost + packetlog->states[packetlog->numstates].number = num; + packetlog->states[packetlog->numstates].bits = d->deltabits[num]; + packetlog->numstates++; + // clear deltabits and priority so it won't be sent again + d->deltabits[num] = 0; + d->priorities[num] = 0; + } + } + MSG_WriteShort(msg, 0x8000); + + return true; +} + + +static void QW_TranslateEffects(entity_state_t *s, int qweffects) +{ + s->effects = 0; + s->internaleffects = 0; + if (qweffects & QW_EF_BRIGHTFIELD) + s->effects |= EF_BRIGHTFIELD; + if (qweffects & QW_EF_MUZZLEFLASH) + s->effects |= EF_MUZZLEFLASH; + if (qweffects & QW_EF_FLAG1) + { + // mimic FTEQW's interpretation of EF_FLAG1 as EF_NODRAW on non-player entities + if (s->number > cl.maxclients) + s->effects |= EF_NODRAW; + else + s->internaleffects |= INTEF_FLAG1QW; + } + if (qweffects & QW_EF_FLAG2) + { + // mimic FTEQW's interpretation of EF_FLAG2 as EF_ADDITIVE on non-player entities + if (s->number > cl.maxclients) + s->effects |= EF_ADDITIVE; + else + s->internaleffects |= INTEF_FLAG2QW; + } + if (qweffects & QW_EF_RED) + { + if (qweffects & QW_EF_BLUE) + s->effects |= EF_RED | EF_BLUE; + else + s->effects |= EF_RED; + } + else if (qweffects & QW_EF_BLUE) + s->effects |= EF_BLUE; + else if (qweffects & QW_EF_BRIGHTLIGHT) + s->effects |= EF_BRIGHTLIGHT; + else if (qweffects & QW_EF_DIMLIGHT) + s->effects |= EF_DIMLIGHT; +} + +void EntityStateQW_ReadPlayerUpdate(void) +{ + int slot = MSG_ReadByte(); + int enumber = slot + 1; + int weaponframe; + int msec; + int playerflags; + int bits; + entity_state_t *s; + // look up the entity + entity_t *ent = cl.entities + enumber; + vec3_t viewangles; + vec3_t velocity; + + // slide the current state into the previous + ent->state_previous = ent->state_current; + + // read the update + s = &ent->state_current; + *s = defaultstate; + s->active = ACTIVE_NETWORK; + s->number = enumber; + s->colormap = enumber; + playerflags = MSG_ReadShort(); + MSG_ReadVector(s->origin, cls.protocol); + s->frame = MSG_ReadByte(); + + VectorClear(viewangles); + VectorClear(velocity); + + if (playerflags & QW_PF_MSEC) + { + // time difference between last update this player sent to the server, + // and last input we sent to the server (this packet is in response to + // our input, so msec is how long ago the last update of this player + // entity occurred, compared to our input being received) + msec = MSG_ReadByte(); + } + else + msec = 0; + if (playerflags & QW_PF_COMMAND) + { + bits = MSG_ReadByte(); + if (bits & QW_CM_ANGLE1) + viewangles[0] = MSG_ReadAngle16i(); // cmd->angles[0] + if (bits & QW_CM_ANGLE2) + viewangles[1] = MSG_ReadAngle16i(); // cmd->angles[1] + if (bits & QW_CM_ANGLE3) + viewangles[2] = MSG_ReadAngle16i(); // cmd->angles[2] + if (bits & QW_CM_FORWARD) + MSG_ReadShort(); // cmd->forwardmove + if (bits & QW_CM_SIDE) + MSG_ReadShort(); // cmd->sidemove + if (bits & QW_CM_UP) + MSG_ReadShort(); // cmd->upmove + if (bits & QW_CM_BUTTONS) + (void) MSG_ReadByte(); // cmd->buttons + if (bits & QW_CM_IMPULSE) + (void) MSG_ReadByte(); // cmd->impulse + (void) MSG_ReadByte(); // cmd->msec + } + if (playerflags & QW_PF_VELOCITY1) + velocity[0] = MSG_ReadShort(); + if (playerflags & QW_PF_VELOCITY2) + velocity[1] = MSG_ReadShort(); + if (playerflags & QW_PF_VELOCITY3) + velocity[2] = MSG_ReadShort(); + if (playerflags & QW_PF_MODEL) + s->modelindex = MSG_ReadByte(); + else + s->modelindex = cl.qw_modelindex_player; + if (playerflags & QW_PF_SKINNUM) + s->skin = MSG_ReadByte(); + if (playerflags & QW_PF_EFFECTS) + QW_TranslateEffects(s, MSG_ReadByte()); + if (playerflags & QW_PF_WEAPONFRAME) + weaponframe = MSG_ReadByte(); + else + weaponframe = 0; + + if (enumber == cl.playerentity) + { + // if this is an update on our player, update the angles + VectorCopy(cl.viewangles, viewangles); + } + + // calculate the entity angles from the viewangles + s->angles[0] = viewangles[0] * -0.0333; + s->angles[1] = viewangles[1]; + s->angles[2] = 0; + s->angles[2] = V_CalcRoll(s->angles, velocity)*4; + + // if this is an update on our player, update interpolation state + if (enumber == cl.playerentity) + { + VectorCopy (cl.mpunchangle[0], cl.mpunchangle[1]); + VectorCopy (cl.mpunchvector[0], cl.mpunchvector[1]); + VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); + cl.mviewzoom[1] = cl.mviewzoom[0]; + + cl.idealpitch = 0; + cl.mpunchangle[0][0] = 0; + cl.mpunchangle[0][1] = 0; + cl.mpunchangle[0][2] = 0; + cl.mpunchvector[0][0] = 0; + cl.mpunchvector[0][1] = 0; + cl.mpunchvector[0][2] = 0; + cl.mvelocity[0][0] = 0; + cl.mvelocity[0][1] = 0; + cl.mvelocity[0][2] = 0; + cl.mviewzoom[0] = 1; + + VectorCopy(velocity, cl.mvelocity[0]); + cl.stats[STAT_WEAPONFRAME] = weaponframe; + if (playerflags & QW_PF_GIB) + cl.stats[STAT_VIEWHEIGHT] = 8; + else if (playerflags & QW_PF_DEAD) + cl.stats[STAT_VIEWHEIGHT] = -16; + else + cl.stats[STAT_VIEWHEIGHT] = 22; + } + + // set the cl.entities_active flag + cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); + // set the update time + s->time = cl.mtime[0] - msec * 0.001; // qw has no clock + // check if we need to update the lerp stuff + if (s->active == ACTIVE_NETWORK) + CL_MoveLerpEntityStates(&cl.entities[enumber]); +} + +static void EntityStateQW_ReadEntityUpdate(entity_state_t *s, int bits) +{ + int qweffects = 0; + s->active = ACTIVE_NETWORK; + s->number = bits & 511; + bits &= ~511; + if (bits & QW_U_MOREBITS) + bits |= MSG_ReadByte(); + + // store the QW_U_SOLID bit here? + + if (bits & QW_U_MODEL) + s->modelindex = MSG_ReadByte(); + if (bits & QW_U_FRAME) + s->frame = MSG_ReadByte(); + if (bits & QW_U_COLORMAP) + s->colormap = MSG_ReadByte(); + if (bits & QW_U_SKIN) + s->skin = MSG_ReadByte(); + if (bits & QW_U_EFFECTS) + QW_TranslateEffects(s, qweffects = MSG_ReadByte()); + if (bits & QW_U_ORIGIN1) + s->origin[0] = MSG_ReadCoord13i(); + if (bits & QW_U_ANGLE1) + s->angles[0] = MSG_ReadAngle8i(); + if (bits & QW_U_ORIGIN2) + s->origin[1] = MSG_ReadCoord13i(); + if (bits & QW_U_ANGLE2) + s->angles[1] = MSG_ReadAngle8i(); + if (bits & QW_U_ORIGIN3) + s->origin[2] = MSG_ReadCoord13i(); + if (bits & QW_U_ANGLE3) + s->angles[2] = MSG_ReadAngle8i(); + + if (developer_networkentities.integer >= 2) + { + Con_Printf("ReadFields e%i", s->number); + if (bits & QW_U_MODEL) + Con_Printf(" U_MODEL %i", s->modelindex); + if (bits & QW_U_FRAME) + Con_Printf(" U_FRAME %i", s->frame); + if (bits & QW_U_COLORMAP) + Con_Printf(" U_COLORMAP %i", s->colormap); + if (bits & QW_U_SKIN) + Con_Printf(" U_SKIN %i", s->skin); + if (bits & QW_U_EFFECTS) + Con_Printf(" U_EFFECTS %i", qweffects); + if (bits & QW_U_ORIGIN1) + Con_Printf(" U_ORIGIN1 %f", s->origin[0]); + if (bits & QW_U_ANGLE1) + Con_Printf(" U_ANGLE1 %f", s->angles[0]); + if (bits & QW_U_ORIGIN2) + Con_Printf(" U_ORIGIN2 %f", s->origin[1]); + if (bits & QW_U_ANGLE2) + Con_Printf(" U_ANGLE2 %f", s->angles[1]); + if (bits & QW_U_ORIGIN3) + Con_Printf(" U_ORIGIN3 %f", s->origin[2]); + if (bits & QW_U_ANGLE3) + Con_Printf(" U_ANGLE3 %f", s->angles[2]); + if (bits & QW_U_SOLID) + Con_Printf(" U_SOLID"); + Con_Print("\n"); + } +} + +entityframeqw_database_t *EntityFrameQW_AllocDatabase(mempool_t *pool) +{ + entityframeqw_database_t *d; + d = (entityframeqw_database_t *)Mem_Alloc(pool, sizeof(*d)); + return d; +} + +void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d) +{ + Mem_Free(d); +} + +void EntityFrameQW_CL_ReadFrame(qboolean delta) +{ + qboolean invalid = false; + int number, oldsnapindex, newsnapindex, oldindex, newindex, oldnum, newnum; + entity_t *ent; + entityframeqw_database_t *d; + entityframeqw_snapshot_t *oldsnap, *newsnap; + + if (!cl.entitydatabaseqw) + cl.entitydatabaseqw = EntityFrameQW_AllocDatabase(cls.levelmempool); + d = cl.entitydatabaseqw; + + // there is no cls.netcon in demos, so this reading code can't access + // cls.netcon-> at all... so cls.qw_incoming_sequence and + // cls.qw_outgoing_sequence are updated every time the corresponding + // cls.netcon->qw. variables are updated + // read the number of this frame to echo back in next input packet + cl.qw_validsequence = cls.qw_incoming_sequence; + newsnapindex = cl.qw_validsequence & QW_UPDATE_MASK; + newsnap = d->snapshot + newsnapindex; + memset(newsnap, 0, sizeof(*newsnap)); + oldsnapindex = -1; + oldsnap = NULL; + if (delta) + { + number = MSG_ReadByte(); + oldsnapindex = cl.qw_deltasequence[newsnapindex]; + if ((number & QW_UPDATE_MASK) != (oldsnapindex & QW_UPDATE_MASK)) + Con_DPrintf("WARNING: from mismatch\n"); + if (oldsnapindex != -1) + { + if (cls.qw_outgoing_sequence - oldsnapindex >= QW_UPDATE_BACKUP-1) + { + Con_DPrintf("delta update too old\n"); + newsnap->invalid = invalid = true; // too old + delta = false; + } + oldsnap = d->snapshot + (oldsnapindex & QW_UPDATE_MASK); + } + else + delta = false; + } + + // if we can't decode this frame properly, report that to the server + if (invalid) + cl.qw_validsequence = 0; + + // read entity numbers until we find a 0x0000 + // (which would be an empty update on world entity, but is actually a terminator) + newsnap->num_entities = 0; + oldindex = 0; + for (;;) + { + int word = (unsigned short)MSG_ReadShort(); + if (msg_badread) + return; // just return, the main parser will print an error + newnum = word == 0 ? 512 : (word & 511); + oldnum = delta ? (oldindex >= oldsnap->num_entities ? 9999 : oldsnap->entities[oldindex].number) : 9999; + + // copy unmodified oldsnap entities + while (newnum > oldnum) // delta only + { + if (developer_networkentities.integer >= 2) + Con_Printf("copy %i\n", oldnum); + // copy one of the old entities + if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES) + Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES"); + newsnap->entities[newsnap->num_entities] = oldsnap->entities[oldindex++]; + newsnap->num_entities++; + oldnum = oldindex >= oldsnap->num_entities ? 9999 : oldsnap->entities[oldindex].number; + } + + if (word == 0) + break; + + if (developer_networkentities.integer >= 2) + { + if (word & QW_U_REMOVE) + Con_Printf("remove %i\n", newnum); + else if (newnum == oldnum) + Con_Printf("delta %i\n", newnum); + else + Con_Printf("baseline %i\n", newnum); + } + + if (word & QW_U_REMOVE) + { + if (newnum != oldnum && !delta && !invalid) + { + cl.qw_validsequence = 0; + Con_Printf("WARNING: U_REMOVE %i on full update\n", newnum); + } + } + else + { + if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES) + Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES"); + newsnap->entities[newsnap->num_entities] = (newnum == oldnum) ? oldsnap->entities[oldindex] : cl.entities[newnum].state_baseline; + EntityStateQW_ReadEntityUpdate(newsnap->entities + newsnap->num_entities, word); + newsnap->num_entities++; + } + + if (newnum == oldnum) + oldindex++; + } + + // expand cl.num_entities to include every entity we've seen this game + newnum = newsnap->num_entities ? newsnap->entities[newsnap->num_entities - 1].number : 1; + if (cl.num_entities <= newnum) + { + cl.num_entities = newnum + 1; + if (cl.max_entities < newnum + 1) + CL_ExpandEntities(newnum); + } + + // now update the non-player entities from the snapshot states + number = cl.maxclients + 1; + for (newindex = 0;;newindex++) + { + newnum = newindex >= newsnap->num_entities ? cl.num_entities : newsnap->entities[newindex].number; + // kill any missing entities + for (;number < newnum;number++) + { + if (cl.entities_active[number]) + { + cl.entities_active[number] = false; + cl.entities[number].state_current.active = ACTIVE_NOT; + } + } + if (number >= cl.num_entities) + break; + // update the entity + ent = &cl.entities[number]; + ent->state_previous = ent->state_current; + ent->state_current = newsnap->entities[newindex]; + ent->state_current.time = cl.mtime[0]; + CL_MoveLerpEntityStates(ent); + // the entity lives again... + cl.entities_active[number] = true; + number++; + } +} diff --git a/misc/source/darkplaces-src/protocol.h b/misc/source/darkplaces-src/protocol.h new file mode 100644 index 00000000..c101665d --- /dev/null +++ b/misc/source/darkplaces-src/protocol.h @@ -0,0 +1,1021 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// protocol.h -- communications protocols + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +// protocolversion_t is defined in common.h + +protocolversion_t Protocol_EnumForName(const char *s); +const char *Protocol_NameForEnum(protocolversion_t p); +protocolversion_t Protocol_EnumForNumber(int n); +int Protocol_NumberForEnum(protocolversion_t p); +void Protocol_Names(char *buffer, size_t buffersize); + +// model effects +#define MF_ROCKET 1 // leave a trail +#define MF_GRENADE 2 // leave a trail +#define MF_GIB 4 // leave a trail +#define MF_ROTATE 8 // rotate (bonus items) +#define MF_TRACER 16 // green split trail +#define MF_ZOMGIB 32 // small blood trail +#define MF_TRACER2 64 // orange split trail + rotate +#define MF_TRACER3 128 // purple trail + +// entity effects +#define EF_BRIGHTFIELD 1 +#define EF_MUZZLEFLASH 2 +#define EF_BRIGHTLIGHT 4 +#define EF_DIMLIGHT 8 +#define EF_NODRAW 16 +#define EF_ADDITIVE 32 +#define EF_BLUE 64 +#define EF_RED 128 +#define EF_NOGUNBOB 256 // LordHavoc: when used with .viewmodelforclient this makes the entity attach to the view without gun bobbing and such effects, it also works on the player entity to disable gun bobbing of the engine-managed .viewmodel (without affecting any .viewmodelforclient entities attached to the player) +#define EF_FULLBRIGHT 512 // LordHavoc: fullbright +#define EF_FLAME 1024 // LordHavoc: on fire +#define EF_STARDUST 2048 // LordHavoc: showering sparks +#define EF_NOSHADOW 4096 // LordHavoc: does not cast a shadow +#define EF_NODEPTHTEST 8192 // LordHavoc: shows through walls +#define EF_SELECTABLE 16384 // LordHavoc: highlights when PRYDON_CLIENTCURSOR mouse is over it +#define EF_DOUBLESIDED 32768 //[515]: disable cull face for this entity +#define EF_NOSELFSHADOW 65536 // LordHavoc: does not cast a shadow on itself (or any other EF_NOSELFSHADOW entities) +#define EF_UNUSED17 131072 +#define EF_UNUSED18 262144 +#define EF_UNUSED19 524288 +#define EF_RESTARTANIM_BIT 1048576 // div0: restart animation bit (like teleport bit, but lerps between end and start of the anim, and doesn't stop player lerping) +#define EF_TELEPORT_BIT 2097152 // div0: teleport bit (toggled when teleporting, prevents lerping when the bit has changed) +#define EF_LOWPRECISION 4194304 // LordHavoc: entity is low precision (integer coordinates) to save network bandwidth (serverside only) +#define EF_NOMODELFLAGS 8388608 // indicates the model's .effects should be ignored (allows overriding them) +#define EF_ROCKET 16777216 // leave a trail +#define EF_GRENADE 33554432 // leave a trail +#define EF_GIB 67108864 // leave a trail +#define EF_ROTATE 134217728 // rotate (bonus items) +#define EF_TRACER 268435456 // green split trail +#define EF_ZOMGIB 536870912 // small blood trail +#define EF_TRACER2 1073741824 // orange split trail + rotate +#define EF_TRACER3 0x80000000 // purple trail + +// internaleffects bits (no overlap with EF_ bits): +#define INTEF_FLAG1QW 1 +#define INTEF_FLAG2QW 2 + +// flags for the pflags field of entities +#define PFLAGS_NOSHADOW 1 +#define PFLAGS_CORONA 2 +#define PFLAGS_FULLDYNAMIC 128 // must be set or the light fields are ignored + +// if the high bit of the servercmd is set, the low bits are fast update flags: +#define U_MOREBITS (1<<0) +#define U_ORIGIN1 (1<<1) +#define U_ORIGIN2 (1<<2) +#define U_ORIGIN3 (1<<3) +#define U_ANGLE2 (1<<4) +// LordHavoc: U_NOLERP was only ever used for monsters, so I renamed it U_STEP +#define U_STEP (1<<5) +#define U_FRAME (1<<6) +// just differentiates from other updates +#define U_SIGNAL (1<<7) + +#define U_ANGLE1 (1<<8) +#define U_ANGLE3 (1<<9) +#define U_MODEL (1<<10) +#define U_COLORMAP (1<<11) +#define U_SKIN (1<<12) +#define U_EFFECTS (1<<13) +#define U_LONGENTITY (1<<14) + +// LordHavoc: protocol extension +#define U_EXTEND1 (1<<15) +// LordHavoc: first extend byte +#define U_DELTA (1<<16) // no data, while this is set the entity is delta compressed (uses previous frame as a baseline, meaning only things that have changed from the previous frame are sent, except for the forced full update every half second) +#define U_ALPHA (1<<17) // 1 byte, 0.0-1.0 maps to 0-255, not sent if exactly 1, and the entity is not sent if <=0 unless it has effects (model effects are checked as well) +#define U_SCALE (1<<18) // 1 byte, scale / 16 positive, not sent if 1.0 +#define U_EFFECTS2 (1<<19) // 1 byte, this is .effects & 0xFF00 (second byte) +#define U_GLOWSIZE (1<<20) // 1 byte, encoding is float/4.0, unsigned, not sent if 0 +#define U_GLOWCOLOR (1<<21) // 1 byte, palette index, default is 254 (white), this IS used for darklight (allowing colored darklight), however the particles from a darklight are always black, not sent if default value (even if glowsize or glowtrail is set) +#define U_COLORMOD (1<<22) // 1 byte, 3 bit red, 3 bit green, 2 bit blue, this lets you tint an object artifically, so you could make a red rocket, or a blue fiend... +#define U_EXTEND2 (1<<23) // another byte to follow +// LordHavoc: second extend byte +#define U_GLOWTRAIL (1<<24) // leaves a trail of particles (of color .glowcolor, or black if it is a negative glowsize) +#define U_VIEWMODEL (1<<25) // attachs the model to the view (origin and angles become relative to it), only shown to owner, a more powerful alternative to .weaponmodel and such +#define U_FRAME2 (1<<26) // 1 byte, this is .frame & 0xFF00 (second byte) +#define U_MODEL2 (1<<27) // 1 byte, this is .modelindex & 0xFF00 (second byte) +#define U_EXTERIORMODEL (1<<28) // causes this model to not be drawn when using a first person view (third person will draw it, first person will not) +#define U_UNUSED29 (1<<29) // future expansion +#define U_UNUSED30 (1<<30) // future expansion +#define U_EXTEND3 (1<<31) // another byte to follow, future expansion + +#define SU_VIEWHEIGHT (1<<0) +#define SU_IDEALPITCH (1<<1) +#define SU_PUNCH1 (1<<2) +#define SU_PUNCH2 (1<<3) +#define SU_PUNCH3 (1<<4) +#define SU_VELOCITY1 (1<<5) +#define SU_VELOCITY2 (1<<6) +#define SU_VELOCITY3 (1<<7) +//define SU_AIMENT (1<<8) AVAILABLE BIT +#define SU_ITEMS (1<<9) +#define SU_ONGROUND (1<<10) // no data follows, the bit is it +#define SU_INWATER (1<<11) // no data follows, the bit is it +#define SU_WEAPONFRAME (1<<12) +#define SU_ARMOR (1<<13) +#define SU_WEAPON (1<<14) +#define SU_EXTEND1 (1<<15) +// first extend byte +#define SU_PUNCHVEC1 (1<<16) +#define SU_PUNCHVEC2 (1<<17) +#define SU_PUNCHVEC3 (1<<18) +#define SU_VIEWZOOM (1<<19) // byte factor (0 = 0.0 (not valid), 255 = 1.0) +#define SU_UNUSED20 (1<<20) +#define SU_UNUSED21 (1<<21) +#define SU_UNUSED22 (1<<22) +#define SU_EXTEND2 (1<<23) // another byte to follow, future expansion +// second extend byte +#define SU_UNUSED24 (1<<24) +#define SU_UNUSED25 (1<<25) +#define SU_UNUSED26 (1<<26) +#define SU_UNUSED27 (1<<27) +#define SU_UNUSED28 (1<<28) +#define SU_UNUSED29 (1<<29) +#define SU_UNUSED30 (1<<30) +#define SU_EXTEND3 (1<<31) // another byte to follow, future expansion + +// a sound with no channel is a local only sound +#define SND_VOLUME (1<<0) // a byte +#define SND_ATTENUATION (1<<1) // a byte +#define SND_LOOPING (1<<2) // a long +#define SND_LARGEENTITY (1<<3) // a short and a byte (instead of a short) +#define SND_LARGESOUND (1<<4) // a short (instead of a byte) + + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 22 + + +// game types sent by serverinfo +// these determine which intermission screen plays +#define GAME_COOP 0 +#define GAME_DEATHMATCH 1 + +//================== +// note that there are some defs.qc that mirror to these numbers +// also related to svc_strings[] in cl_parse +//================== + +// +// server to client +// +#define svc_bad 0 +#define svc_nop 1 +#define svc_disconnect 2 +#define svc_updatestat 3 // [byte] [long] +#define svc_version 4 // [long] server version +#define svc_setview 5 // [short] entity number +#define svc_sound 6 // +#define svc_time 7 // [float] server time +#define svc_print 8 // [string] null terminated string +#define svc_stufftext 9 // [string] stuffed into client's console buffer + // the string should be \n terminated +#define svc_setangle 10 // [angle3] set the view angle to this absolute value + +#define svc_serverinfo 11 // [long] version + // [string] signon string + // [string]..[0]model cache + // [string]...[0]sounds cache +#define svc_lightstyle 12 // [byte] [string] +#define svc_updatename 13 // [byte] [string] +#define svc_updatefrags 14 // [byte] [short] +#define svc_clientdata 15 // +#define svc_stopsound 16 // +#define svc_updatecolors 17 // [byte] [byte] +#define svc_particle 18 // [vec3] +#define svc_damage 19 + +#define svc_spawnstatic 20 +// svc_spawnbinary 21 +#define svc_spawnbaseline 22 + +#define svc_temp_entity 23 + +#define svc_setpause 24 // [byte] on / off +#define svc_signonnum 25 // [byte] used for the signon sequence + +#define svc_centerprint 26 // [string] to put in center of the screen + +#define svc_killedmonster 27 +#define svc_foundsecret 28 + +#define svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten + +#define svc_intermission 30 // [string] music +#define svc_finale 31 // [string] music [string] text + +#define svc_cdtrack 32 // [byte] track [byte] looptrack +#define svc_sellscreen 33 + +#define svc_cutscene 34 + +#define svc_showlmp 35 // [string] slotname [string] lmpfilename [short] x [short] y +#define svc_hidelmp 36 // [string] slotname +#define svc_skybox 37 // [string] skyname + +// LordHavoc: my svc_ range, 50-69 +#define svc_downloaddata 50 // [int] start [short] size +#define svc_updatestatubyte 51 // [byte] stat [byte] value +#define svc_effect 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate +#define svc_effect2 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate +#define svc_sound2 54 // (obsolete in DP6 and later) short soundindex instead of byte +#define svc_precache 54 // [short] precacheindex [string] filename, precacheindex is + 0 for modelindex and +32768 for soundindex +#define svc_spawnbaseline2 55 // short modelindex instead of byte +#define svc_spawnstatic2 56 // short modelindex instead of byte +#define svc_entities 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata +#define svc_csqcentities 58 // [short] entnum [variable length] entitydata ... [short] 0x0000 +#define svc_spawnstaticsound2 59 // [coord3] [short] samp [byte] vol [byte] aten +#define svc_trailparticles 60 // [short] entnum [short] effectnum [vector] start [vector] end +#define svc_pointparticles 61 // [short] effectnum [vector] start [vector] velocity [short] count +#define svc_pointparticles1 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 + +// +// client to server +// +#define clc_bad 0 +#define clc_nop 1 +#define clc_disconnect 2 +#define clc_move 3 // [usercmd_t] +#define clc_stringcmd 4 // [string] message + +// LordHavoc: my clc_ range, 50-59 +#define clc_ackframe 50 // [int] framenumber +#define clc_ackdownloaddata 51 // [int] start [short] size (note: exact echo of latest values received in svc_downloaddata, packet-loss handling is in the server) +#define clc_unusedlh2 52 +#define clc_unusedlh3 53 +#define clc_unusedlh4 54 +#define clc_unusedlh5 55 +#define clc_unusedlh6 56 +#define clc_unusedlh7 57 +#define clc_unusedlh8 58 +#define clc_unusedlh9 59 + +// +// temp entity events +// +#define TE_SPIKE 0 // [vector] origin +#define TE_SUPERSPIKE 1 // [vector] origin +#define TE_GUNSHOT 2 // [vector] origin +#define TE_EXPLOSION 3 // [vector] origin +#define TE_TAREXPLOSION 4 // [vector] origin +#define TE_LIGHTNING1 5 // [entity] entity [vector] start [vector] end +#define TE_LIGHTNING2 6 // [entity] entity [vector] start [vector] end +#define TE_WIZSPIKE 7 // [vector] origin +#define TE_KNIGHTSPIKE 8 // [vector] origin +#define TE_LIGHTNING3 9 // [entity] entity [vector] start [vector] end +#define TE_LAVASPLASH 10 // [vector] origin +#define TE_TELEPORT 11 // [vector] origin +#define TE_EXPLOSION2 12 // [vector] origin [byte] startcolor [byte] colorcount + +// PGM 01/21/97 +#define TE_BEAM 13 // [entity] entity [vector] start [vector] end +// PGM 01/21/97 + +// Nehahra effects used in the movie (TE_EXPLOSION3 also got written up in a QSG tutorial, hence it's not marked NEH) +#define TE_EXPLOSION3 16 // [vector] origin [coord] red [coord] green [coord] blue +#define TE_LIGHTNING4NEH 17 // [string] model [entity] entity [vector] start [vector] end + +// LordHavoc: added some TE_ codes (block1 - 50-60) +#define TE_BLOOD 50 // [vector] origin [byte] xvel [byte] yvel [byte] zvel [byte] count +#define TE_SPARK 51 // [vector] origin [byte] xvel [byte] yvel [byte] zvel [byte] count +#define TE_BLOODSHOWER 52 // [vector] min [vector] max [coord] explosionspeed [short] count +#define TE_EXPLOSIONRGB 53 // [vector] origin [byte] red [byte] green [byte] blue +#define TE_PARTICLECUBE 54 // [vector] min [vector] max [vector] dir [short] count [byte] color [byte] gravity [coord] randomvel +#define TE_PARTICLERAIN 55 // [vector] min [vector] max [vector] dir [short] count [byte] color +#define TE_PARTICLESNOW 56 // [vector] min [vector] max [vector] dir [short] count [byte] color +#define TE_GUNSHOTQUAD 57 // [vector] origin +#define TE_SPIKEQUAD 58 // [vector] origin +#define TE_SUPERSPIKEQUAD 59 // [vector] origin +// LordHavoc: block2 - 70-80 +#define TE_EXPLOSIONQUAD 70 // [vector] origin +#define TE_UNUSED1 71 // unused +#define TE_SMALLFLASH 72 // [vector] origin +#define TE_CUSTOMFLASH 73 // [vector] origin [byte] radius / 8 - 1 [byte] lifetime / 256 - 1 [byte] red [byte] green [byte] blue +#define TE_FLAMEJET 74 // [vector] origin [vector] velocity [byte] count +#define TE_PLASMABURN 75 // [vector] origin +// LordHavoc: Tei grabbed these codes +#define TE_TEI_G3 76 // [vector] start [vector] end [vector] angles +#define TE_TEI_SMOKE 77 // [vector] origin [vector] dir [byte] count +#define TE_TEI_BIGEXPLOSION 78 // [vector] origin +#define TE_TEI_PLASMAHIT 79 // [vector} origin [vector] dir [byte] count + + +// these are bits for the 'flags' field of the entity_state_t +#define RENDER_STEP 1 +#define RENDER_GLOWTRAIL 2 +#define RENDER_VIEWMODEL 4 +#define RENDER_EXTERIORMODEL 8 +#define RENDER_LOWPRECISION 16 // send as low precision coordinates to save bandwidth +#define RENDER_COLORMAPPED 32 +#define RENDER_NOCULL 64 // do not cull this entity with r_cullentities +#define RENDER_COMPLEXANIMATION 128 + +#define RENDER_SHADOW 65536 // cast shadow +#define RENDER_LIGHT 131072 // receive light +#define RENDER_NOSELFSHADOW 262144 // render lighting on this entity before its own shadow is added to the scene +// (note: all RENDER_NOSELFSHADOW entities are grouped together and rendered in a batch before their shadows are rendered, so they can not shadow eachother either) +#define RENDER_EQUALIZE 524288 // (subflag of RENDER_LIGHT) equalize the light from the light grid hitting this ent (less invasive EF_FULLBRIGHT implementation) +#define RENDER_NODEPTHTEST 1048576 +#define RENDER_ADDITIVE 2097152 +#define RENDER_DOUBLESIDED 4194304 + +#define MAX_FRAMEGROUPBLENDS 4 +typedef struct framegroupblend_s +{ + // animation number and blend factor + // (for most models this is the frame number) + int frame; + float lerp; + // time frame began playing (for framegroup animations) + double start; +} +framegroupblend_t; + +struct matrix4x4_s; +struct model_s; + +typedef struct skeleton_s +{ + const struct model_s *model; + struct matrix4x4_s *relativetransforms; +} +skeleton_t; + +typedef enum entity_state_active_e +{ + ACTIVE_NOT = 0, + ACTIVE_NETWORK = 1, + ACTIVE_SHARED = 2 +} +entity_state_active_t; + +// this was 96 bytes, now 168 bytes (32bit) or 176 bytes (64bit) +typedef struct entity_state_s +{ + // ! means this is not sent to client + double time; // ! time this state was built (used on client for interpolation) + float netcenter[3]; // ! for network prioritization, this is the center of the bounding box (which may differ from the origin) + float origin[3]; + float angles[3]; + int effects; + unsigned int customizeentityforclient; // ! + unsigned short number; // entity number this state is for + unsigned short modelindex; + unsigned short frame; + unsigned short tagentity; + unsigned short specialvisibilityradius; // ! larger if it has effects/light + unsigned short viewmodelforclient; // ! + unsigned short exteriormodelforclient; // ! not shown if first person viewing from this entity, shown in all other cases + unsigned short nodrawtoclient; // ! + unsigned short drawonlytoclient; // ! + unsigned short traileffectnum; + unsigned short light[4]; // color*256 (0.00 to 255.996), and radius*1 + unsigned char active; // true if a valid state + unsigned char lightstyle; + unsigned char lightpflags; + unsigned char colormap; + unsigned char skin; // also chooses cubemap for rtlights if lightpflags & LIGHTPFLAGS_FULLDYNAMIC + unsigned char alpha; + unsigned char scale; + unsigned char glowsize; + unsigned char glowcolor; + unsigned char flags; + unsigned char internaleffects; // INTEF_FLAG1QW and so on + unsigned char tagindex; + unsigned char colormod[3]; + unsigned char glowmod[3]; + // LordHavoc: very big data here :( + framegroupblend_t framegroupblend[4]; + skeleton_t skeletonobject; +} +entity_state_t; + +// baseline state values +extern entity_state_t defaultstate; +// reads a quake entity from the network stream +void EntityFrameQuake_ReadEntity(int bits); +// checks for stats changes and sets corresponding host_client->statsdeltabits +// (also updates host_client->stats array) +void Protocol_UpdateClientStats(const int *stats); +// writes reliable messages updating stats (not used by DP6 and later +// protocols which send updates in their WriteFrame function using a different +// method of reliable messaging) +void Protocol_WriteStatsReliable(void); +// writes a list of quake entities to the network stream +// (or as many will fit) +qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states); +// cleans up dead entities each frame after ReadEntity (which doesn't clear unused entities) +void EntityFrameQuake_ISeeDeadEntities(void); + +/* +PROTOCOL_DARKPLACES3 +server updates entities according to some (unmentioned) scheme. + +a frame consists of all visible entities, some of which are up to date, +often some are not up to date. + +these entities are stored in a range (firstentity/endentity) of structs in the +entitydata[] buffer. + +to make a commit the server performs these steps: +1. duplicate oldest frame in database (this is the baseline) as new frame, and + write frame numbers (oldest frame's number, new frame's number) and eye + location to network packet (eye location is obsolete and will be removed in + future revisions) +2. write an entity change to packet and modify new frame accordingly + (this repeats until packet is sufficiently full or new frame is complete) +3. write terminator (0xFFFF) to network packet + (FIXME: this terminator value conflicts with MAX_EDICTS 32768...) + +to read a commit the client performs these steps: +1. reads frame numbers from packet and duplicates baseline frame as new frame, + also reads eye location but does nothing with it (obsolete). +2. delete frames older than the baseline which was used +3. read entity changes from packet until terminator (0xFFFF) is encountered, + each change is applied to entity frame. +4. sends ack framenumber to server as part of input packet + +if server receives ack message in put packet it performs these steps: +1. remove all older frames from database. +*/ + +/* +PROTOCOL_DARKPLACES4 +a frame consists of some visible entities in a range (this is stored as start and end, note that end may be less than start if it wrapped). + +these entities are stored in a range (firstentity/endentity) of structs in the entitydata[] buffer. + +to make a commit the server performs these steps: +1. build an entity_frame_t using appropriate functions, containing (some of) the visible entities, this is passed to the Write function to send it. + +This documention is unfinished! +the Write function performs these steps: +1. check if entity frame is larger than MAX_ENTITYFRAME or is larger than available space in database, if so the baseline is defaults, otherwise it is the current baseline of the database. +2. write differences of an entity compared to selected baseline. +3. add entity to entity update in database. +4. if there are more entities to write and packet is not full, go back to step 2. +5. write terminator (0xFFFF) as entity number. +6. return. + + + + + +server updates entities in looping ranges, a frame consists of a range of visible entities (not always all visible entities), +*/ + +#define MAX_ENTITY_HISTORY 64 +#define MAX_ENTITY_DATABASE (MAX_EDICTS * 2) + +// build entity data in this, to pass to entity read/write functions +typedef struct entity_frame_s +{ + double time; + int framenum; + int numentities; + int firstentitynum; + int lastentitynum; + vec3_t eye; + entity_state_t entitydata[MAX_ENTITY_DATABASE]; +} +entity_frame_t; + +typedef struct entity_frameinfo_s +{ + double time; + int framenum; + int firstentity; // index into entitydata, modulo MAX_ENTITY_DATABASE + int endentity; // index into entitydata, firstentity + numentities +} +entity_frameinfo_t; + +typedef struct entityframe_database_s +{ + // note: these can be far out of range, modulo with MAX_ENTITY_DATABASE to get a valid range (which may wrap) + // start and end of used area, when adding a new update to database, store at endpos, and increment endpos + // when removing updates from database, nudge down frames array to only contain useful frames + // this logic should explain better: + // if (numframes >= MAX_ENTITY_HISTORY || (frames[numframes - 1].endentity - frames[0].firstentity) + entitiestoadd > MAX_ENTITY_DATABASE) + // flushdatabase(); + // note: if numframes == 0, insert at start (0 in entitydata) + // the only reason this system is used is to avoid copying memory when frames are removed + int numframes; + // server only: last sent frame + int latestframenum; + // server only: last acknowledged frame + int ackframenum; + // the current state in the database + vec3_t eye; + // table of entities in the entityhistorydata + entity_frameinfo_t frames[MAX_ENTITY_HISTORY]; + // entities + entity_state_t entitydata[MAX_ENTITY_DATABASE]; + + // structs for building new frames and reading them + entity_frame_t deltaframe; + entity_frame_t framedata; +} +entityframe_database_t; + +// LordHavoc: these are in approximately sorted order, according to cost and +// likelyhood of being used for numerous objects in a frame + +// note that the bytes are not written/read in this order, this is only the +// order of the bits to minimize overhead from extend bytes + +// enough to describe a nail, gib, shell casing, bullet hole, or rocket +#define E_ORIGIN1 (1<<0) +#define E_ORIGIN2 (1<<1) +#define E_ORIGIN3 (1<<2) +#define E_ANGLE1 (1<<3) +#define E_ANGLE2 (1<<4) +#define E_ANGLE3 (1<<5) +#define E_MODEL1 (1<<6) +#define E_EXTEND1 (1<<7) + +// enough to describe almost anything +#define E_FRAME1 (1<<8) +#define E_EFFECTS1 (1<<9) +#define E_ALPHA (1<<10) +#define E_SCALE (1<<11) +#define E_COLORMAP (1<<12) +#define E_SKIN (1<<13) +#define E_FLAGS (1<<14) +#define E_EXTEND2 (1<<15) + +// players, custom color glows, high model numbers +#define E_FRAME2 (1<<16) +#define E_MODEL2 (1<<17) +#define E_EFFECTS2 (1<<18) +#define E_GLOWSIZE (1<<19) +#define E_GLOWCOLOR (1<<20) +#define E_LIGHT (1<<21) +#define E_LIGHTPFLAGS (1<<22) +#define E_EXTEND3 (1<<23) + +#define E_SOUND1 (1<<24) +#define E_SOUNDVOL (1<<25) +#define E_SOUNDATTEN (1<<26) +#define E_TAGATTACHMENT (1<<27) +#define E_LIGHTSTYLE (1<<28) +#define E_UNUSED6 (1<<29) +#define E_UNUSED7 (1<<30) +#define E_EXTEND4 (1<<31) + +// returns difference between two states as E_ flags +int EntityState_DeltaBits(const entity_state_t *o, const entity_state_t *n); +// write E_ flags to a msg +void EntityState_WriteExtendBits(sizebuf_t *msg, unsigned int bits); +// write values for the E_ flagged fields to a msg +void EntityState_WriteFields(const entity_state_t *ent, sizebuf_t *msg, unsigned int bits); +// write entity number and E_ flags and their values, or a remove number, describing the change from delta to ent +void EntityState_WriteUpdate(const entity_state_t *ent, sizebuf_t *msg, const entity_state_t *delta); +// read E_ flags +int EntityState_ReadExtendBits(void); +// read values for E_ flagged fields and apply them to a state +void EntityState_ReadFields(entity_state_t *e, unsigned int bits); + +// (client and server) allocates a new empty database +entityframe_database_t *EntityFrame_AllocDatabase(mempool_t *mempool); +// (client and server) frees the database +void EntityFrame_FreeDatabase(entityframe_database_t *d); +// (server) clears the database to contain no frames (thus delta compression +// compresses against nothing) +void EntityFrame_ClearDatabase(entityframe_database_t *d); +// (server and client) removes frames older than 'frame' from database +void EntityFrame_AckFrame(entityframe_database_t *d, int frame); +// (server) clears frame, to prepare for adding entities +void EntityFrame_Clear(entity_frame_t *f, vec3_t eye, int framenum); +// (server and client) reads a frame from the database +void EntityFrame_FetchFrame(entityframe_database_t *d, int framenum, entity_frame_t *f); +// (client) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata); +// (server) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Server(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t **entitydata); +// (server) writes a frame to network stream +qboolean EntityFrame_WriteFrame(sizebuf_t *msg, int maxsize, entityframe_database_t *d, int numstates, const entity_state_t **states, int viewentnum); +// (client) reads a frame from network stream +void EntityFrame_CL_ReadFrame(void); +// (client) returns the frame number of the most recent frame recieved +int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d); + +typedef struct entity_database4_commit_s +{ + // frame number this commit represents + int framenum; + // number of entities in entity[] array + int numentities; + // maximum number of entities in entity[] array (dynamic resizing) + int maxentities; + entity_state_t *entity; +} +entity_database4_commit_t; + +typedef struct entity_database4_s +{ + // what mempool to use for allocations + mempool_t *mempool; + // reference frame + int referenceframenum; + // reference entities array is resized according to demand + int maxreferenceentities; + // array of states for entities, these are indexable by their entity number (yes there are gaps) + entity_state_t *referenceentity; + // commits waiting to be applied to the reference database when confirmed + // (commit[i]->numentities == 0 means it is empty) + entity_database4_commit_t commit[MAX_ENTITY_HISTORY]; + // (server only) the current commit being worked on + entity_database4_commit_t *currentcommit; + // (server only) if a commit won't fit entirely, continue where it left + // off next frame + int currententitynumber; + // (server only) + int latestframenumber; +} +entityframe4_database_t; + +// should-be-private functions that aren't +entity_state_t *EntityFrame4_GetReferenceEntity(entityframe4_database_t *d, int number); +void EntityFrame4_AddCommitEntity(entityframe4_database_t *d, const entity_state_t *s); + +// allocate a database +entityframe4_database_t *EntityFrame4_AllocDatabase(mempool_t *pool); +// free a database +void EntityFrame4_FreeDatabase(entityframe4_database_t *d); +// reset a database (resets compression but does not reallocate anything) +void EntityFrame4_ResetDatabase(entityframe4_database_t *d); +// updates database to account for a frame-received acknowledgment +int EntityFrame4_AckFrame(entityframe4_database_t *d, int framenum, int servermode); +// writes a frame to the network stream +qboolean EntityFrame4_WriteFrame(sizebuf_t *msg, int maxsize, entityframe4_database_t *d, int numstates, const entity_state_t **states); +// reads a frame from the network stream +void EntityFrame4_CL_ReadFrame(void); + +// reset all entity fields (typically used if status changed) +#define E5_FULLUPDATE (1<<0) +// E5_ORIGIN32=0: short[3] = s->origin[0] * 8, s->origin[1] * 8, s->origin[2] * 8 +// E5_ORIGIN32=1: float[3] = s->origin[0], s->origin[1], s->origin[2] +#define E5_ORIGIN (1<<1) +// E5_ANGLES16=0: byte[3] = s->angle[0] * 256 / 360, s->angle[1] * 256 / 360, s->angle[2] * 256 / 360 +// E5_ANGLES16=1: short[3] = s->angle[0] * 65536 / 360, s->angle[1] * 65536 / 360, s->angle[2] * 65536 / 360 +#define E5_ANGLES (1<<2) +// E5_MODEL16=0: byte = s->modelindex +// E5_MODEL16=1: short = s->modelindex +#define E5_MODEL (1<<3) +// E5_FRAME16=0: byte = s->frame +// E5_FRAME16=1: short = s->frame +#define E5_FRAME (1<<4) +// byte = s->skin +#define E5_SKIN (1<<5) +// E5_EFFECTS16=0 && E5_EFFECTS32=0: byte = s->effects +// E5_EFFECTS16=1 && E5_EFFECTS32=0: short = s->effects +// E5_EFFECTS16=0 && E5_EFFECTS32=1: int = s->effects +// E5_EFFECTS16=1 && E5_EFFECTS32=1: int = s->effects +#define E5_EFFECTS (1<<6) +// bits >= (1<<8) +#define E5_EXTEND1 (1<<7) + +// byte = s->renderflags +#define E5_FLAGS (1<<8) +// byte = bound(0, s->alpha * 255, 255) +#define E5_ALPHA (1<<9) +// byte = bound(0, s->scale * 16, 255) +#define E5_SCALE (1<<10) +// flag +#define E5_ORIGIN32 (1<<11) +// flag +#define E5_ANGLES16 (1<<12) +// flag +#define E5_MODEL16 (1<<13) +// byte = s->colormap +#define E5_COLORMAP (1<<14) +// bits >= (1<<16) +#define E5_EXTEND2 (1<<15) + +// short = s->tagentity +// byte = s->tagindex +#define E5_ATTACHMENT (1<<16) +// short[4] = s->light[0], s->light[1], s->light[2], s->light[3] +// byte = s->lightstyle +// byte = s->lightpflags +#define E5_LIGHT (1<<17) +// byte = s->glowsize +// byte = s->glowcolor +#define E5_GLOW (1<<18) +// short = s->effects +#define E5_EFFECTS16 (1<<19) +// int = s->effects +#define E5_EFFECTS32 (1<<20) +// flag +#define E5_FRAME16 (1<<21) +// byte[3] = s->colormod[0], s->colormod[1], s->colormod[2] +#define E5_COLORMOD (1<<22) +// bits >= (1<<24) +#define E5_EXTEND3 (1<<23) + +// byte[3] = s->glowmod[0], s->glowmod[1], s->glowmod[2] +#define E5_GLOWMOD (1<<24) +// byte type=0 short frames[1] short times[1] +// byte type=1 short frames[2] short times[2] byte lerps[2] +// byte type=2 short frames[3] short times[3] byte lerps[3] +// byte type=3 short frames[4] short times[4] byte lerps[4] +// byte type=4 short modelindex byte numbones {short pose6s[6]} +// see also RENDER_COMPLEXANIMATION +#define E5_COMPLEXANIMATION (1<<25) +// ushort traileffectnum +#define E5_TRAILEFFECTNUM (1<<26) +// unused +#define E5_UNUSED27 (1<<27) +// unused +#define E5_UNUSED28 (1<<28) +// unused +#define E5_UNUSED29 (1<<29) +// unused +#define E5_UNUSED30 (1<<30) +// bits2 > 0 +#define E5_EXTEND4 (1<<31) + +#define ENTITYFRAME5_MAXPACKETLOGS 64 +#define ENTITYFRAME5_MAXSTATES 1024 +#define ENTITYFRAME5_PRIORITYLEVELS 32 + +typedef struct entityframe5_changestate_s +{ + unsigned int number; + unsigned int bits; +} +entityframe5_changestate_t; + +typedef struct entityframe5_packetlog_s +{ + int packetnumber; + int numstates; + entityframe5_changestate_t states[ENTITYFRAME5_MAXSTATES]; + unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; +} +entityframe5_packetlog_t; + +typedef struct entityframe5_database_s +{ + // number of the latest message sent to client + int latestframenum; + // updated by WriteFrame for internal use + int viewentnum; + + // logs of all recently sent messages (between acked and latest) + entityframe5_packetlog_t packetlog[ENTITYFRAME5_MAXPACKETLOGS]; + + // this goes up as needed and causes all the arrays to be reallocated + int maxedicts; + + // which properties of each entity have changed since last send + int *deltabits; // [maxedicts] + // priorities of entities (updated whenever deltabits change) + // (derived from deltabits) + unsigned char *priorities; // [maxedicts] + // last frame this entity was sent on, for prioritzation + int *updateframenum; // [maxedicts] + + // database of current status of all entities + entity_state_t *states; // [maxedicts] + // which entities are currently active + // (duplicate of the active bit of every state in states[]) + // (derived from states) + unsigned char *visiblebits; // [(maxedicts+7)/8] + + // old notes + + // this is used to decide which changestates to set each frame + //int numvisiblestates; + //entity_state_t visiblestates[MAX_EDICTS]; + + // sorted changing states that need to be sent to the client + // kept sorted in lowest to highest priority order, because this allows + // the numchangestates to simply be decremented whenever an state is sent, + // rather than a memmove to remove them from the start. + //int numchangestates; + //entityframe5_changestate_t changestates[MAX_EDICTS]; + + // buffers for building priority info + int prioritychaincounts[ENTITYFRAME5_PRIORITYLEVELS]; + unsigned short prioritychains[ENTITYFRAME5_PRIORITYLEVELS][ENTITYFRAME5_MAXSTATES]; +} +entityframe5_database_t; + +entityframe5_database_t *EntityFrame5_AllocDatabase(mempool_t *pool); +void EntityFrame5_FreeDatabase(entityframe5_database_t *d); +void EntityState5_WriteUpdate(int number, const entity_state_t *s, int changedbits, sizebuf_t *msg); +int EntityState5_DeltaBitsForState(entity_state_t *o, entity_state_t *n); +void EntityFrame5_CL_ReadFrame(void); +void EntityFrame5_LostFrame(entityframe5_database_t *d, int framenum); +void EntityFrame5_AckFrame(entityframe5_database_t *d, int framenum); +qboolean EntityFrame5_WriteFrame(sizebuf_t *msg, int maxsize, entityframe5_database_t *d, int numstates, const entity_state_t **states, int viewentnum, int movesequence, qboolean need_empty); + +extern cvar_t developer_networkentities; + +// QUAKEWORLD +// server to client +#define qw_svc_bad 0 +#define qw_svc_nop 1 +#define qw_svc_disconnect 2 +#define qw_svc_updatestat 3 // [byte] [byte] +#define qw_svc_setview 5 // [short] entity number +#define qw_svc_sound 6 // +#define qw_svc_print 8 // [byte] id [string] null terminated string +#define qw_svc_stufftext 9 // [string] stuffed into client's console buffer +#define qw_svc_setangle 10 // [angle3] set the view angle to this absolute value +#define qw_svc_serverdata 11 // [long] protocol ... +#define qw_svc_lightstyle 12 // [byte] [string] +#define qw_svc_updatefrags 14 // [byte] [short] +#define qw_svc_stopsound 16 // +#define qw_svc_damage 19 +#define qw_svc_spawnstatic 20 +#define qw_svc_spawnbaseline 22 +#define qw_svc_temp_entity 23 // variable +#define qw_svc_setpause 24 // [byte] on / off +#define qw_svc_centerprint 26 // [string] to put in center of the screen +#define qw_svc_killedmonster 27 +#define qw_svc_foundsecret 28 +#define qw_svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten +#define qw_svc_intermission 30 // [vec3_t] origin [vec3_t] angle +#define qw_svc_finale 31 // [string] text +#define qw_svc_cdtrack 32 // [byte] track +#define qw_svc_sellscreen 33 +#define qw_svc_smallkick 34 // set client punchangle to 2 +#define qw_svc_bigkick 35 // set client punchangle to 4 +#define qw_svc_updateping 36 // [byte] [short] +#define qw_svc_updateentertime 37 // [byte] [float] +#define qw_svc_updatestatlong 38 // [byte] [long] +#define qw_svc_muzzleflash 39 // [short] entity +#define qw_svc_updateuserinfo 40 // [byte] slot [long] uid +#define qw_svc_download 41 // [short] size [size bytes] +#define qw_svc_playerinfo 42 // variable +#define qw_svc_nails 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 +#define qw_svc_chokecount 44 // [byte] packets choked +#define qw_svc_modellist 45 // [strings] +#define qw_svc_soundlist 46 // [strings] +#define qw_svc_packetentities 47 // [...] +#define qw_svc_deltapacketentities 48 // [...] +#define qw_svc_maxspeed 49 // maxspeed change, for prediction +#define qw_svc_entgravity 50 // gravity change, for prediction +#define qw_svc_setinfo 51 // setinfo on a client +#define qw_svc_serverinfo 52 // serverinfo +#define qw_svc_updatepl 53 // [byte] [byte] +// QUAKEWORLD +// client to server +#define qw_clc_bad 0 +#define qw_clc_nop 1 +#define qw_clc_move 3 // [[usercmd_t] +#define qw_clc_stringcmd 4 // [string] message +#define qw_clc_delta 5 // [byte] sequence number, requests delta compression of message +#define qw_clc_tmove 6 // teleport request, spectator only +#define qw_clc_upload 7 // teleport request, spectator only +// QUAKEWORLD +// playerinfo flags from server +// playerinfo always sends: playernum, flags, origin[] and framenumber +#define QW_PF_MSEC (1<<0) +#define QW_PF_COMMAND (1<<1) +#define QW_PF_VELOCITY1 (1<<2) +#define QW_PF_VELOCITY2 (1<<3) +#define QW_PF_VELOCITY3 (1<<4) +#define QW_PF_MODEL (1<<5) +#define QW_PF_SKINNUM (1<<6) +#define QW_PF_EFFECTS (1<<7) +#define QW_PF_WEAPONFRAME (1<<8) // only sent for view player +#define QW_PF_DEAD (1<<9) // don't block movement any more +#define QW_PF_GIB (1<<10) // offset the view height differently +#define QW_PF_NOGRAV (1<<11) // don't apply gravity for prediction +// QUAKEWORLD +// if the high bit of the client to server byte is set, the low bits are +// client move cmd bits +// ms and angle2 are allways sent, the others are optional +#define QW_CM_ANGLE1 (1<<0) +#define QW_CM_ANGLE3 (1<<1) +#define QW_CM_FORWARD (1<<2) +#define QW_CM_SIDE (1<<3) +#define QW_CM_UP (1<<4) +#define QW_CM_BUTTONS (1<<5) +#define QW_CM_IMPULSE (1<<6) +#define QW_CM_ANGLE2 (1<<7) +// QUAKEWORLD +// the first 16 bits of a packetentities update holds 9 bits +// of entity number and 7 bits of flags +#define QW_U_ORIGIN1 (1<<9) +#define QW_U_ORIGIN2 (1<<10) +#define QW_U_ORIGIN3 (1<<11) +#define QW_U_ANGLE2 (1<<12) +#define QW_U_FRAME (1<<13) +#define QW_U_REMOVE (1<<14) // REMOVE this entity, don't add it +#define QW_U_MOREBITS (1<<15) +// if MOREBITS is set, these additional flags are read in next +#define QW_U_ANGLE1 (1<<0) +#define QW_U_ANGLE3 (1<<1) +#define QW_U_MODEL (1<<2) +#define QW_U_COLORMAP (1<<3) +#define QW_U_SKIN (1<<4) +#define QW_U_EFFECTS (1<<5) +#define QW_U_SOLID (1<<6) // the entity should be solid for prediction +// QUAKEWORLD +// temp entity events +#define QW_TE_SPIKE 0 +#define QW_TE_SUPERSPIKE 1 +#define QW_TE_GUNSHOT 2 +#define QW_TE_EXPLOSION 3 +#define QW_TE_TAREXPLOSION 4 +#define QW_TE_LIGHTNING1 5 +#define QW_TE_LIGHTNING2 6 +#define QW_TE_WIZSPIKE 7 +#define QW_TE_KNIGHTSPIKE 8 +#define QW_TE_LIGHTNING3 9 +#define QW_TE_LAVASPLASH 10 +#define QW_TE_TELEPORT 11 +#define QW_TE_BLOOD 12 +#define QW_TE_LIGHTNINGBLOOD 13 +// QUAKEWORLD +// effect flags +#define QW_EF_BRIGHTFIELD 1 +#define QW_EF_MUZZLEFLASH 2 +#define QW_EF_BRIGHTLIGHT 4 +#define QW_EF_DIMLIGHT 8 +#define QW_EF_FLAG1 16 +#define QW_EF_FLAG2 32 +#define QW_EF_BLUE 64 +#define QW_EF_RED 128 + +#define QW_UPDATE_BACKUP 64 +#define QW_UPDATE_MASK (QW_UPDATE_BACKUP - 1) +#define QW_MAX_PACKET_ENTITIES 64 + +// note: QW stats are directly compatible with NQ +// (but FRAGS, WEAPONFRAME, and VIEWHEIGHT are unused) +// so these defines are not actually used by darkplaces, but kept for reference +#define QW_STAT_HEALTH 0 +//#define QW_STAT_FRAGS 1 +#define QW_STAT_WEAPON 2 +#define QW_STAT_AMMO 3 +#define QW_STAT_ARMOR 4 +//#define QW_STAT_WEAPONFRAME 5 +#define QW_STAT_SHELLS 6 +#define QW_STAT_NAILS 7 +#define QW_STAT_ROCKETS 8 +#define QW_STAT_CELLS 9 +#define QW_STAT_ACTIVEWEAPON 10 +#define QW_STAT_TOTALSECRETS 11 +#define QW_STAT_TOTALMONSTERS 12 +#define QW_STAT_SECRETS 13 // bumped on client side by svc_foundsecret +#define QW_STAT_MONSTERS 14 // bumped by svc_killedmonster +#define QW_STAT_ITEMS 15 +//#define QW_STAT_VIEWHEIGHT 16 + +// build entity data in this, to pass to entity read/write functions +typedef struct entityframeqw_snapshot_s +{ + double time; + qboolean invalid; + int num_entities; + entity_state_t entities[QW_MAX_PACKET_ENTITIES]; +} +entityframeqw_snapshot_t; + +typedef struct entityframeqw_database_s +{ + entityframeqw_snapshot_t snapshot[QW_UPDATE_BACKUP]; +} +entityframeqw_database_t; + +entityframeqw_database_t *EntityFrameQW_AllocDatabase(mempool_t *pool); +void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d); +void EntityStateQW_ReadPlayerUpdate(void); +void EntityFrameQW_CL_ReadFrame(qboolean delta); + +struct client_s; +void EntityFrameCSQC_LostFrame(struct client_s *client, int framenum); +qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum); + +#endif + diff --git a/misc/source/darkplaces-src/prvm_cmds.c b/misc/source/darkplaces-src/prvm_cmds.c new file mode 100644 index 00000000..c0b34d4a --- /dev/null +++ b/misc/source/darkplaces-src/prvm_cmds.c @@ -0,0 +1,6986 @@ +// AK +// Basically every vm builtin cmd should be in here. +// All 3 builtin and extension lists can be found here +// cause large (I think they will) parts are from pr_cmds the same copyright like in pr_cmds +// also applies here + +#include "quakedef.h" + +#include "prvm_cmds.h" +#include "libcurl.h" +#include + +#include "cl_collision.h" +#include "clvm_cmds.h" +#include "ft2.h" + +extern cvar_t prvm_backtraceforwarnings; + +// LordHavoc: changed this to NOT use a return statement, so that it can be used in functions that must return a value +void VM_Warning(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + static double recursive = -1; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Con_DPrint(msg); + + // TODO: either add a cvar/cmd to control the state dumping or replace some of the calls with Con_Printf [9/13/2006 Black] + if(prvm_backtraceforwarnings.integer && recursive != realtime) // NOTE: this compares to the time, just in case if PRVM_PrintState causes a Host_Error and keeps recursive set + { + recursive = realtime; + PRVM_PrintState(); + recursive = -1; + } +} + + +//============================================================================ +// Common + +// TODO DONE: move vm_files and vm_fssearchlist to prvm_prog_t struct +// TODO: move vm_files and vm_fssearchlist back [9/13/2006 Black] +// TODO: (move vm_files and vm_fssearchlist to prvm_prog_t struct again) [2007-01-23 LordHavoc] +// TODO: will this war ever end? [2007-01-23 LordHavoc] + +void VM_CheckEmptyString (const char *s) +{ + if (ISWHITESPACE(s[0])) + PRVM_ERROR ("%s: Bad string", PRVM_NAME); +} + +void VM_GenerateFrameGroupBlend(framegroupblend_t *framegroupblend, const prvm_edict_t *ed) +{ + // self.frame is the interpolation target (new frame) + // self.frame1time is the animation base time for the interpolation target + // self.frame2 is the interpolation start (previous frame) + // self.frame2time is the animation base time for the interpolation start + // self.lerpfrac is the interpolation strength for self.frame2 + // self.lerpfrac3 is the interpolation strength for self.frame3 + // self.lerpfrac4 is the interpolation strength for self.frame4 + // pitch angle on a player model where the animator set up 5 sets of + // animations and the csqc simply lerps between sets) + framegroupblend[0].frame = (int) PRVM_gameedictfloat(ed, frame ); + framegroupblend[1].frame = (int) PRVM_gameedictfloat(ed, frame2 ); + framegroupblend[2].frame = (int) PRVM_gameedictfloat(ed, frame3 ); + framegroupblend[3].frame = (int) PRVM_gameedictfloat(ed, frame4 ); + framegroupblend[0].start = PRVM_gameedictfloat(ed, frame1time); + framegroupblend[1].start = PRVM_gameedictfloat(ed, frame2time); + framegroupblend[2].start = PRVM_gameedictfloat(ed, frame3time); + framegroupblend[3].start = PRVM_gameedictfloat(ed, frame4time); + framegroupblend[1].lerp = PRVM_gameedictfloat(ed, lerpfrac ); + framegroupblend[2].lerp = PRVM_gameedictfloat(ed, lerpfrac3 ); + framegroupblend[3].lerp = PRVM_gameedictfloat(ed, lerpfrac4 ); + // assume that the (missing) lerpfrac1 is whatever remains after lerpfrac2+lerpfrac3+lerpfrac4 are summed + framegroupblend[0].lerp = 1 - framegroupblend[1].lerp - framegroupblend[2].lerp - framegroupblend[3].lerp; +} + +// LordHavoc: quite tempting to break apart this function to reuse the +// duplicated code, but I suspect it is better for performance +// this way +void VM_FrameBlendFromFrameGroupBlend(frameblend_t *frameblend, const framegroupblend_t *framegroupblend, const dp_model_t *model) +{ + int sub2, numframes, f, i, k; + int isfirstframegroup = true; + int nolerp; + double sublerp, lerp, d; + const animscene_t *scene; + const framegroupblend_t *g; + frameblend_t *blend = frameblend; + + memset(blend, 0, MAX_FRAMEBLENDS * sizeof(*blend)); + + if (!model || !model->surfmesh.isanimated) + { + blend[0].lerp = 1; + return; + } + + nolerp = (model->type == mod_sprite) ? !r_lerpsprites.integer : !r_lerpmodels.integer; + numframes = model->numframes; + for (k = 0, g = framegroupblend;k < MAX_FRAMEGROUPBLENDS;k++, g++) + { + f = g->frame; + if ((unsigned int)f >= (unsigned int)numframes) + { + Con_DPrintf("VM_FrameBlendFromFrameGroupBlend: no such frame %d in model %s\n", f, model->name); + f = 0; + } + d = lerp = g->lerp; + if (lerp <= 0) + continue; + if (nolerp) + { + if (isfirstframegroup) + { + d = lerp = 1; + isfirstframegroup = false; + } + else + continue; + } + if (model->animscenes) + { + scene = model->animscenes + f; + f = scene->firstframe; + if (scene->framecount > 1) + { + // this code path is only used on .zym models and torches + sublerp = scene->framerate * (cl.time - g->start); + f = (int) floor(sublerp); + sublerp -= f; + sub2 = f + 1; + if (sublerp < (1.0 / 65536.0f)) + sublerp = 0; + if (sublerp > (65535.0f / 65536.0f)) + sublerp = 1; + if (nolerp) + sublerp = 0; + if (scene->loop) + { + f = (f % scene->framecount); + sub2 = (sub2 % scene->framecount); + } + f = bound(0, f, (scene->framecount - 1)) + scene->firstframe; + sub2 = bound(0, sub2, (scene->framecount - 1)) + scene->firstframe; + d = sublerp * lerp; + // two framelerps produced from one animation + if (d > 0) + { + for (i = 0;i < MAX_FRAMEBLENDS;i++) + { + if (blend[i].lerp <= 0 || blend[i].subframe == sub2) + { + blend[i].subframe = sub2; + blend[i].lerp += d; + break; + } + } + } + d = (1 - sublerp) * lerp; + } + } + if (d > 0) + { + for (i = 0;i < MAX_FRAMEBLENDS;i++) + { + if (blend[i].lerp <= 0 || blend[i].subframe == f) + { + blend[i].subframe = f; + blend[i].lerp += d; + break; + } + } + } + } +} + +void VM_UpdateEdictSkeleton(prvm_edict_t *ed, const dp_model_t *edmodel, const frameblend_t *frameblend) +{ + if (ed->priv.server->skeleton.model != edmodel) + { + VM_RemoveEdictSkeleton(ed); + ed->priv.server->skeleton.model = edmodel; + } + if (!ed->priv.server->skeleton.model || !ed->priv.server->skeleton.model->num_bones) + { + if(ed->priv.server->skeleton.relativetransforms) + Mem_Free(ed->priv.server->skeleton.relativetransforms); + ed->priv.server->skeleton.relativetransforms = NULL; + return; + } + + { + int skeletonindex = -1; + skeleton_t *skeleton; + skeletonindex = (int)PRVM_gameedictfloat(ed, skeletonindex) - 1; + if (skeletonindex >= 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones) + { + // custom skeleton controlled by the game (FTE_CSQC_SKELETONOBJECTS) + if (!ed->priv.server->skeleton.relativetransforms) + ed->priv.server->skeleton.relativetransforms = (matrix4x4_t *)Mem_Alloc(prog->progs_mempool, ed->priv.server->skeleton.model->num_bones * sizeof(matrix4x4_t)); + memcpy(ed->priv.server->skeleton.relativetransforms, skeleton->relativetransforms, ed->priv.server->skeleton.model->num_bones * sizeof(matrix4x4_t)); + } + else + { + if(ed->priv.server->skeleton.relativetransforms) + Mem_Free(ed->priv.server->skeleton.relativetransforms); + ed->priv.server->skeleton.relativetransforms = NULL; + } + } +} + +void VM_RemoveEdictSkeleton(prvm_edict_t *ed) +{ + if (ed->priv.server->skeleton.relativetransforms) + Mem_Free(ed->priv.server->skeleton.relativetransforms); + memset(&ed->priv.server->skeleton, 0, sizeof(ed->priv.server->skeleton)); +} + + + + +//============================================================================ +//BUILT-IN FUNCTIONS + +void VM_VarString(int first, char *out, int outlength) +{ + int i; + const char *s; + char *outend; + + outend = out + outlength - 1; + for (i = first;i < prog->argc && out < outend;i++) + { + s = PRVM_G_STRING((OFS_PARM0+i*3)); + while (out < outend && *s) + *out++ = *s++; + } + *out++ = 0; +} + +/* +================= +VM_checkextension + +returns true if the extension is supported by the server + +checkextension(extensionname) +================= +*/ + +// kind of helper function +static qboolean checkextension(const char *name) +{ + int len; + const char *e, *start; + len = (int)strlen(name); + + for (e = prog->extensionstring;*e;e++) + { + while (*e == ' ') + e++; + if (!*e) + break; + start = e; + while (*e && *e != ' ') + e++; + if ((e - start) == len && !strncasecmp(start, name, len)) + { + // special sheck for ODE + if (!strncasecmp("DP_PHYSICS_ODE", name, 14)) + { +#ifdef USEODE + return ode_dll ? true : false; +#else + return false; +#endif + } + return true; + } + } + return false; +} + +void VM_checkextension (void) +{ + VM_SAFEPARMCOUNT(1,VM_checkextension); + + PRVM_G_FLOAT(OFS_RETURN) = checkextension(PRVM_G_STRING(OFS_PARM0)); +} + +/* +================= +VM_error + +This is a TERMINAL error, which will kill off the entire prog. +Dumps self. + +error(value) +================= +*/ +void VM_error (void) +{ + prvm_edict_t *ed; + char string[VM_STRINGTEMP_LENGTH]; + + VM_VarString(0, string, sizeof(string)); + Con_Printf("======%s ERROR in %s:\n%s\n", PRVM_NAME, PRVM_GetString(prog->xfunction->s_name), string); + ed = PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)); + PRVM_ED_Print(ed, NULL); + + PRVM_ERROR ("%s: Program error in function %s:\n%s\nTip: read above for entity information\n", PRVM_NAME, PRVM_GetString(prog->xfunction->s_name), string); +} + +/* +================= +VM_objerror + +Dumps out self, then an error message. The program is aborted and self is +removed, but the level can continue. + +objerror(value) +================= +*/ +void VM_objerror (void) +{ + prvm_edict_t *ed; + char string[VM_STRINGTEMP_LENGTH]; + + VM_VarString(0, string, sizeof(string)); + Con_Printf("======OBJECT ERROR======\n"); // , PRVM_NAME, PRVM_GetString(prog->xfunction->s_name), string); // or include them? FIXME + ed = PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)); + PRVM_ED_Print(ed, NULL); + PRVM_ED_Free (ed); + Con_Printf("%s OBJECT ERROR in %s:\n%s\nTip: read above for entity information\n", PRVM_NAME, PRVM_GetString(prog->xfunction->s_name), string); +} + +/* +================= +VM_print + +print to console + +print(...[string]) +================= +*/ +void VM_print (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + + VM_VarString(0, string, sizeof(string)); + Con_Print(string); +} + +/* +================= +VM_bprint + +broadcast print to everyone on server + +bprint(...[string]) +================= +*/ +void VM_bprint (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + + if(!sv.active) + { + VM_Warning("VM_bprint: game is not server(%s) !\n", PRVM_NAME); + return; + } + + VM_VarString(0, string, sizeof(string)); + SV_BroadcastPrint(string); +} + +/* +================= +VM_sprint (menu & client but only if server.active == true) + +single print to a specific client + +sprint(float clientnum,...[string]) +================= +*/ +void VM_sprint (void) +{ + client_t *client; + int clientnum; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_sprint); + + //find client for this entity + clientnum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (!sv.active || clientnum < 0 || clientnum >= svs.maxclients || !svs.clients[clientnum].active) + { + VM_Warning("VM_sprint: %s: invalid client or server is not active !\n", PRVM_NAME); + return; + } + + client = svs.clients + clientnum; + if (!client->netconnection) + return; + + VM_VarString(1, string, sizeof(string)); + MSG_WriteChar(&client->netconnection->message,svc_print); + MSG_WriteString(&client->netconnection->message, string); +} + +/* +================= +VM_centerprint + +single print to the screen + +centerprint(value) +================= +*/ +void VM_centerprint (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_centerprint); + VM_VarString(0, string, sizeof(string)); + SCR_CenterPrint(string); +} + +/* +================= +VM_normalize + +vector normalize(vector) +================= +*/ +void VM_normalize (void) +{ + float *value1; + vec3_t newvalue; + double f; + + VM_SAFEPARMCOUNT(1,VM_normalize); + + value1 = PRVM_G_VECTOR(OFS_PARM0); + + f = VectorLength2(value1); + if (f) + { + f = 1.0 / sqrt(f); + VectorScale(value1, f, newvalue); + } + else + VectorClear(newvalue); + + VectorCopy (newvalue, PRVM_G_VECTOR(OFS_RETURN)); +} + +/* +================= +VM_vlen + +scalar vlen(vector) +================= +*/ +void VM_vlen (void) +{ + VM_SAFEPARMCOUNT(1,VM_vlen); + PRVM_G_FLOAT(OFS_RETURN) = VectorLength(PRVM_G_VECTOR(OFS_PARM0)); +} + +/* +================= +VM_vectoyaw + +float vectoyaw(vector) +================= +*/ +void VM_vectoyaw (void) +{ + float *value1; + float yaw; + + VM_SAFEPARMCOUNT(1,VM_vectoyaw); + + value1 = PRVM_G_VECTOR(OFS_PARM0); + + if (value1[1] == 0 && value1[0] == 0) + yaw = 0; + else + { + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + PRVM_G_FLOAT(OFS_RETURN) = yaw; +} + + +/* +================= +VM_vectoangles + +vector vectoangles(vector[, vector]) +================= +*/ +void VM_vectoangles (void) +{ + VM_SAFEPARMCOUNTRANGE(1, 2,VM_vectoangles); + + AnglesFromVectors(PRVM_G_VECTOR(OFS_RETURN), PRVM_G_VECTOR(OFS_PARM0), prog->argc >= 2 ? PRVM_G_VECTOR(OFS_PARM1) : NULL, true); +} + +/* +================= +VM_random + +Returns a number from 0<= num < 1 + +float random() +================= +*/ +void VM_random (void) +{ + VM_SAFEPARMCOUNT(0,VM_random); + + PRVM_G_FLOAT(OFS_RETURN) = lhrandom(0, 1); +} + +/* +========= +VM_localsound + +localsound(string sample) +========= +*/ +void VM_localsound(void) +{ + const char *s; + + VM_SAFEPARMCOUNT(1,VM_localsound); + + s = PRVM_G_STRING(OFS_PARM0); + + if(!S_LocalSound (s)) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning("VM_localsound: Failed to play %s for %s !\n", s, PRVM_NAME); + return; + } + + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +================= +VM_break + +break() +================= +*/ +void VM_break (void) +{ + PRVM_ERROR ("%s: break statement", PRVM_NAME); +} + +//============================================================================ + +/* +================= +VM_localcmd + +Sends text over to the client's execution buffer + +[localcmd (string, ...) or] +cmd (string, ...) +================= +*/ +void VM_localcmd (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_localcmd); + VM_VarString(0, string, sizeof(string)); + Cbuf_AddText(string); +} + +static qboolean PRVM_Cvar_ReadOk(const char *string) +{ + cvar_t *cvar; + cvar = Cvar_FindVar(string); + return ((cvar) && ((cvar->flags & CVAR_PRIVATE) == 0)); +} + +/* +================= +VM_cvar + +float cvar (string) +================= +*/ +void VM_cvar (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar); + VM_VarString(0, string, sizeof(string)); + VM_CheckEmptyString(string); + PRVM_G_FLOAT(OFS_RETURN) = PRVM_Cvar_ReadOk(string) ? Cvar_VariableValue(string) : 0; +} + +/* +================= +VM_cvar + +float cvar_type (string) +float CVAR_TYPEFLAG_EXISTS = 1; +float CVAR_TYPEFLAG_SAVED = 2; +float CVAR_TYPEFLAG_PRIVATE = 4; +float CVAR_TYPEFLAG_ENGINE = 8; +float CVAR_TYPEFLAG_HASDESCRIPTION = 16; +float CVAR_TYPEFLAG_READONLY = 32; +================= +*/ +void VM_cvar_type (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + cvar_t *cvar; + int ret; + + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar); + VM_VarString(0, string, sizeof(string)); + VM_CheckEmptyString(string); + cvar = Cvar_FindVar(string); + + + if(!cvar) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; // CVAR_TYPE_NONE + } + + ret = 1; // CVAR_EXISTS + if(cvar->flags & CVAR_SAVE) + ret |= 2; // CVAR_TYPE_SAVED + if(cvar->flags & CVAR_PRIVATE) + ret |= 4; // CVAR_TYPE_PRIVATE + if(!(cvar->flags & CVAR_ALLOCATED)) + ret |= 8; // CVAR_TYPE_ENGINE + if(cvar->description != cvar_dummy_description) + ret |= 16; // CVAR_TYPE_HASDESCRIPTION + if(cvar->flags & CVAR_READONLY) + ret |= 32; // CVAR_TYPE_READONLY + + PRVM_G_FLOAT(OFS_RETURN) = ret; +} + +/* +================= +VM_cvar_string + +const string VM_cvar_string (string, ...) +================= +*/ +void VM_cvar_string(void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_string); + VM_VarString(0, string, sizeof(string)); + VM_CheckEmptyString(string); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(PRVM_Cvar_ReadOk(string) ? Cvar_VariableString(string) : ""); +} + + +/* +======================== +VM_cvar_defstring + +const string VM_cvar_defstring (string, ...) +======================== +*/ +void VM_cvar_defstring (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_defstring); + VM_VarString(0, string, sizeof(string)); + VM_CheckEmptyString(string); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(Cvar_VariableDefString(string)); +} + +/* +======================== +VM_cvar_defstring + +const string VM_cvar_description (string, ...) +======================== +*/ +void VM_cvar_description (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_description); + VM_VarString(0, string, sizeof(string)); + VM_CheckEmptyString(string); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(Cvar_VariableDescription(string)); +} +/* +================= +VM_cvar_set + +void cvar_set (string,string, ...) +================= +*/ +void VM_cvar_set (void) +{ + const char *name; + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(2,8,VM_cvar_set); + VM_VarString(1, string, sizeof(string)); + name = PRVM_G_STRING(OFS_PARM0); + VM_CheckEmptyString(name); + Cvar_Set(name, string); +} + +/* +========= +VM_dprint + +dprint(...[string]) +========= +*/ +void VM_dprint (void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_dprint); + VM_VarString(0, string, sizeof(string)); +#if 1 + Con_DPrintf("%s", string); +#else + Con_DPrintf("%s: %s", PRVM_NAME, string); +#endif +} + +/* +========= +VM_ftos + +string ftos(float) +========= +*/ + +void VM_ftos (void) +{ + float v; + char s[128]; + + VM_SAFEPARMCOUNT(1, VM_ftos); + + v = PRVM_G_FLOAT(OFS_PARM0); + + if ((float)((int)v) == v) + dpsnprintf(s, sizeof(s), "%i", (int)v); + else + dpsnprintf(s, sizeof(s), "%f", v); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(s); +} + +/* +========= +VM_fabs + +float fabs(float) +========= +*/ + +void VM_fabs (void) +{ + float v; + + VM_SAFEPARMCOUNT(1,VM_fabs); + + v = PRVM_G_FLOAT(OFS_PARM0); + PRVM_G_FLOAT(OFS_RETURN) = fabs(v); +} + +/* +========= +VM_vtos + +string vtos(vector) +========= +*/ + +void VM_vtos (void) +{ + char s[512]; + + VM_SAFEPARMCOUNT(1,VM_vtos); + + dpsnprintf (s, sizeof(s), "'%5.1f %5.1f %5.1f'", PRVM_G_VECTOR(OFS_PARM0)[0], PRVM_G_VECTOR(OFS_PARM0)[1], PRVM_G_VECTOR(OFS_PARM0)[2]); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(s); +} + +/* +========= +VM_etos + +string etos(entity) +========= +*/ + +void VM_etos (void) +{ + char s[128]; + + VM_SAFEPARMCOUNT(1, VM_etos); + + dpsnprintf (s, sizeof(s), "entity %i", PRVM_G_EDICTNUM(OFS_PARM0)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(s); +} + +/* +========= +VM_stof + +float stof(...[string]) +========= +*/ +void VM_stof(void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_stof); + VM_VarString(0, string, sizeof(string)); + PRVM_G_FLOAT(OFS_RETURN) = atof(string); +} + +/* +======================== +VM_itof + +float itof(intt ent) +======================== +*/ +void VM_itof(void) +{ + VM_SAFEPARMCOUNT(1, VM_itof); + PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); +} + +/* +======================== +VM_ftoe + +entity ftoe(float num) +======================== +*/ +void VM_ftoe(void) +{ + int ent; + VM_SAFEPARMCOUNT(1, VM_ftoe); + + ent = (int)PRVM_G_FLOAT(OFS_PARM0); + if (ent < 0 || ent >= prog->max_edicts || PRVM_PROG_TO_EDICT(ent)->priv.required->free) + ent = 0; // return world instead of a free or invalid entity + + PRVM_G_INT(OFS_RETURN) = ent; +} + +/* +======================== +VM_etof + +float etof(entity ent) +======================== +*/ +void VM_etof(void) +{ + VM_SAFEPARMCOUNT(1, VM_etof); + PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_EDICTNUM(OFS_PARM0); +} + +/* +========= +VM_strftime + +string strftime(float uselocaltime, string[, string ...]) +========= +*/ +void VM_strftime(void) +{ + time_t t; +#if _MSC_VER >= 1400 + struct tm tm; + int tmresult; +#else + struct tm *tm; +#endif + char fmt[VM_STRINGTEMP_LENGTH]; + char result[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(2, 8, VM_strftime); + VM_VarString(1, fmt, sizeof(fmt)); + t = time(NULL); +#if _MSC_VER >= 1400 + if (PRVM_G_FLOAT(OFS_PARM0)) + tmresult = localtime_s(&tm, &t); + else + tmresult = gmtime_s(&tm, &t); + if (!tmresult) +#else + if (PRVM_G_FLOAT(OFS_PARM0)) + tm = localtime(&t); + else + tm = gmtime(&t); + if (!tm) +#endif + { + PRVM_G_INT(OFS_RETURN) = 0; + return; + } +#if _MSC_VER >= 1400 + strftime(result, sizeof(result), fmt, &tm); +#else + strftime(result, sizeof(result), fmt, tm); +#endif + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(result); +} + +/* +========= +VM_spawn + +entity spawn() +========= +*/ + +void VM_spawn (void) +{ + prvm_edict_t *ed; + VM_SAFEPARMCOUNT(0, VM_spawn); + prog->xfunction->builtinsprofile += 20; + ed = PRVM_ED_Alloc(); + VM_RETURN_EDICT(ed); +} + +/* +========= +VM_remove + +remove(entity e) +========= +*/ + +void VM_remove (void) +{ + prvm_edict_t *ed; + prog->xfunction->builtinsprofile += 20; + + VM_SAFEPARMCOUNT(1, VM_remove); + + ed = PRVM_G_EDICT(OFS_PARM0); + if( PRVM_NUM_FOR_EDICT(ed) <= prog->reserved_edicts ) + { + if (developer.integer > 0) + VM_Warning( "VM_remove: tried to remove the null entity or a reserved entity!\n" ); + } + else if( ed->priv.required->free ) + { + if (developer.integer > 0) + VM_Warning( "VM_remove: tried to remove an already freed entity!\n" ); + } + else + PRVM_ED_Free (ed); +} + +/* +========= +VM_find + +entity find(entity start, .string field, string match) +========= +*/ + +void VM_find (void) +{ + int e; + int f; + const char *s, *t; + prvm_edict_t *ed; + + VM_SAFEPARMCOUNT(3,VM_find); + + e = PRVM_G_EDICTNUM(OFS_PARM0); + f = PRVM_G_INT(OFS_PARM1); + s = PRVM_G_STRING(OFS_PARM2); + + // LordHavoc: apparently BloodMage does a find(world, weaponmodel, "") and + // expects it to find all the monsters, so we must be careful to support + // searching for "" + + for (e++ ; e < prog->num_edicts ; e++) + { + prog->xfunction->builtinsprofile++; + ed = PRVM_EDICT_NUM(e); + if (ed->priv.required->free) + continue; + t = PRVM_E_STRING(ed,f); + if (!t) + t = ""; + if (!strcmp(t,s)) + { + VM_RETURN_EDICT(ed); + return; + } + } + + VM_RETURN_EDICT(prog->edicts); +} + +/* +========= +VM_findfloat + + entity findfloat(entity start, .float field, float match) + entity findentity(entity start, .entity field, entity match) +========= +*/ +// LordHavoc: added this for searching float, int, and entity reference fields +void VM_findfloat (void) +{ + int e; + int f; + float s; + prvm_edict_t *ed; + + VM_SAFEPARMCOUNT(3,VM_findfloat); + + e = PRVM_G_EDICTNUM(OFS_PARM0); + f = PRVM_G_INT(OFS_PARM1); + s = PRVM_G_FLOAT(OFS_PARM2); + + for (e++ ; e < prog->num_edicts ; e++) + { + prog->xfunction->builtinsprofile++; + ed = PRVM_EDICT_NUM(e); + if (ed->priv.required->free) + continue; + if (PRVM_E_FLOAT(ed,f) == s) + { + VM_RETURN_EDICT(ed); + return; + } + } + + VM_RETURN_EDICT(prog->edicts); +} + +/* +========= +VM_findchain + +entity findchain(.string field, string match) +========= +*/ +// chained search for strings in entity fields +// entity(.string field, string match) findchain = #402; +void VM_findchain (void) +{ + int i; + int f; + const char *s, *t; + prvm_edict_t *ent, *chain; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2,3,VM_findchain); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if (chainfield < 0) + PRVM_ERROR("VM_findchain: %s doesnt have the specified chain field !", PRVM_NAME); + + chain = prog->edicts; + + f = PRVM_G_INT(OFS_PARM0); + s = PRVM_G_STRING(OFS_PARM1); + + // LordHavoc: apparently BloodMage does a find(world, weaponmodel, "") and + // expects it to find all the monsters, so we must be careful to support + // searching for "" + + ent = PRVM_NEXT_EDICT(prog->edicts); + for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + { + prog->xfunction->builtinsprofile++; + if (ent->priv.required->free) + continue; + t = PRVM_E_STRING(ent,f); + if (!t) + t = ""; + if (strcmp(t,s)) + continue; + + PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_NUM_FOR_EDICT(chain); + chain = ent; + } + + VM_RETURN_EDICT(chain); +} + +/* +========= +VM_findchainfloat + +entity findchainfloat(.string field, float match) +entity findchainentity(.string field, entity match) +========= +*/ +// LordHavoc: chained search for float, int, and entity reference fields +// entity(.string field, float match) findchainfloat = #403; +void VM_findchainfloat (void) +{ + int i; + int f; + float s; + prvm_edict_t *ent, *chain; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_findchainfloat); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if (chainfield < 0) + PRVM_ERROR("VM_findchain: %s doesnt have the specified chain field !", PRVM_NAME); + + chain = (prvm_edict_t *)prog->edicts; + + f = PRVM_G_INT(OFS_PARM0); + s = PRVM_G_FLOAT(OFS_PARM1); + + ent = PRVM_NEXT_EDICT(prog->edicts); + for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + { + prog->xfunction->builtinsprofile++; + if (ent->priv.required->free) + continue; + if (PRVM_E_FLOAT(ent,f) != s) + continue; + + PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); + chain = ent; + } + + VM_RETURN_EDICT(chain); +} + +/* +======================== +VM_findflags + +entity findflags(entity start, .float field, float match) +======================== +*/ +// LordHavoc: search for flags in float fields +void VM_findflags (void) +{ + int e; + int f; + int s; + prvm_edict_t *ed; + + VM_SAFEPARMCOUNT(3, VM_findflags); + + + e = PRVM_G_EDICTNUM(OFS_PARM0); + f = PRVM_G_INT(OFS_PARM1); + s = (int)PRVM_G_FLOAT(OFS_PARM2); + + for (e++ ; e < prog->num_edicts ; e++) + { + prog->xfunction->builtinsprofile++; + ed = PRVM_EDICT_NUM(e); + if (ed->priv.required->free) + continue; + if (!PRVM_E_FLOAT(ed,f)) + continue; + if ((int)PRVM_E_FLOAT(ed,f) & s) + { + VM_RETURN_EDICT(ed); + return; + } + } + + VM_RETURN_EDICT(prog->edicts); +} + +/* +======================== +VM_findchainflags + +entity findchainflags(.float field, float match) +======================== +*/ +// LordHavoc: chained search for flags in float fields +void VM_findchainflags (void) +{ + int i; + int f; + int s; + prvm_edict_t *ent, *chain; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_findchainflags); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if (chainfield < 0) + PRVM_ERROR("VM_findchain: %s doesnt have the specified chain field !", PRVM_NAME); + + chain = (prvm_edict_t *)prog->edicts; + + f = PRVM_G_INT(OFS_PARM0); + s = (int)PRVM_G_FLOAT(OFS_PARM1); + + ent = PRVM_NEXT_EDICT(prog->edicts); + for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + { + prog->xfunction->builtinsprofile++; + if (ent->priv.required->free) + continue; + if (!PRVM_E_FLOAT(ent,f)) + continue; + if (!((int)PRVM_E_FLOAT(ent,f) & s)) + continue; + + PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); + chain = ent; + } + + VM_RETURN_EDICT(chain); +} + +/* +========= +VM_precache_sound + +string precache_sound (string sample) +========= +*/ +void VM_precache_sound (void) +{ + const char *s; + + VM_SAFEPARMCOUNT(1, VM_precache_sound); + + s = PRVM_G_STRING(OFS_PARM0); + PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); + //VM_CheckEmptyString(s); + + if(snd_initialized.integer && !S_PrecacheSound(s, true, true)) + { + VM_Warning("VM_precache_sound: Failed to load %s for %s\n", s, PRVM_NAME); + return; + } +} + +/* +================= +VM_precache_file + +returns the same string as output + +does nothing, only used by qcc to build .pak archives +================= +*/ +void VM_precache_file (void) +{ + VM_SAFEPARMCOUNT(1,VM_precache_file); + // precache_file is only used to copy files with qcc, it does nothing + PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); +} + +/* +========= +VM_coredump + +coredump() +========= +*/ +void VM_coredump (void) +{ + VM_SAFEPARMCOUNT(0,VM_coredump); + + Cbuf_AddText("prvm_edicts "); + Cbuf_AddText(PRVM_NAME); + Cbuf_AddText("\n"); +} + +/* +========= +VM_stackdump + +stackdump() +========= +*/ +void PRVM_StackTrace(void); +void VM_stackdump (void) +{ + VM_SAFEPARMCOUNT(0, VM_stackdump); + + PRVM_StackTrace(); +} + +/* +========= +VM_crash + +crash() +========= +*/ + +void VM_crash(void) +{ + VM_SAFEPARMCOUNT(0, VM_crash); + + PRVM_ERROR("Crash called by %s",PRVM_NAME); +} + +/* +========= +VM_traceon + +traceon() +========= +*/ +void VM_traceon (void) +{ + VM_SAFEPARMCOUNT(0,VM_traceon); + + prog->trace = true; +} + +/* +========= +VM_traceoff + +traceoff() +========= +*/ +void VM_traceoff (void) +{ + VM_SAFEPARMCOUNT(0,VM_traceoff); + + prog->trace = false; +} + +/* +========= +VM_eprint + +eprint(entity e) +========= +*/ +void VM_eprint (void) +{ + VM_SAFEPARMCOUNT(1,VM_eprint); + + PRVM_ED_PrintNum (PRVM_G_EDICTNUM(OFS_PARM0), NULL); +} + +/* +========= +VM_rint + +float rint(float) +========= +*/ +void VM_rint (void) +{ + float f; + VM_SAFEPARMCOUNT(1,VM_rint); + + f = PRVM_G_FLOAT(OFS_PARM0); + if (f > 0) + PRVM_G_FLOAT(OFS_RETURN) = floor(f + 0.5); + else + PRVM_G_FLOAT(OFS_RETURN) = ceil(f - 0.5); +} + +/* +========= +VM_floor + +float floor(float) +========= +*/ +void VM_floor (void) +{ + VM_SAFEPARMCOUNT(1,VM_floor); + + PRVM_G_FLOAT(OFS_RETURN) = floor(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_ceil + +float ceil(float) +========= +*/ +void VM_ceil (void) +{ + VM_SAFEPARMCOUNT(1,VM_ceil); + + PRVM_G_FLOAT(OFS_RETURN) = ceil(PRVM_G_FLOAT(OFS_PARM0)); +} + + +/* +============= +VM_nextent + +entity nextent(entity) +============= +*/ +void VM_nextent (void) +{ + int i; + prvm_edict_t *ent; + + VM_SAFEPARMCOUNT(1, VM_nextent); + + i = PRVM_G_EDICTNUM(OFS_PARM0); + while (1) + { + prog->xfunction->builtinsprofile++; + i++; + if (i == prog->num_edicts) + { + VM_RETURN_EDICT(prog->edicts); + return; + } + ent = PRVM_EDICT_NUM(i); + if (!ent->priv.required->free) + { + VM_RETURN_EDICT(ent); + return; + } + } +} + +//============================================================================= + +/* +============== +VM_changelevel +server and menu + +changelevel(string map) +============== +*/ +void VM_changelevel (void) +{ + VM_SAFEPARMCOUNT(1, VM_changelevel); + + if(!sv.active) + { + VM_Warning("VM_changelevel: game is not server (%s)\n", PRVM_NAME); + return; + } + +// make sure we don't issue two changelevels + if (svs.changelevel_issued) + return; + svs.changelevel_issued = true; + + Cbuf_AddText (va("changelevel %s\n",PRVM_G_STRING(OFS_PARM0))); +} + +/* +========= +VM_sin + +float sin(float) +========= +*/ +void VM_sin (void) +{ + VM_SAFEPARMCOUNT(1,VM_sin); + PRVM_G_FLOAT(OFS_RETURN) = sin(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_cos +float cos(float) +========= +*/ +void VM_cos (void) +{ + VM_SAFEPARMCOUNT(1,VM_cos); + PRVM_G_FLOAT(OFS_RETURN) = cos(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_sqrt + +float sqrt(float) +========= +*/ +void VM_sqrt (void) +{ + VM_SAFEPARMCOUNT(1,VM_sqrt); + PRVM_G_FLOAT(OFS_RETURN) = sqrt(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_asin + +float asin(float) +========= +*/ +void VM_asin (void) +{ + VM_SAFEPARMCOUNT(1,VM_asin); + PRVM_G_FLOAT(OFS_RETURN) = asin(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_acos +float acos(float) +========= +*/ +void VM_acos (void) +{ + VM_SAFEPARMCOUNT(1,VM_acos); + PRVM_G_FLOAT(OFS_RETURN) = acos(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_atan +float atan(float) +========= +*/ +void VM_atan (void) +{ + VM_SAFEPARMCOUNT(1,VM_atan); + PRVM_G_FLOAT(OFS_RETURN) = atan(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_atan2 +float atan2(float,float) +========= +*/ +void VM_atan2 (void) +{ + VM_SAFEPARMCOUNT(2,VM_atan2); + PRVM_G_FLOAT(OFS_RETURN) = atan2(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); +} + +/* +========= +VM_tan +float tan(float) +========= +*/ +void VM_tan (void) +{ + VM_SAFEPARMCOUNT(1,VM_tan); + PRVM_G_FLOAT(OFS_RETURN) = tan(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +================= +VM_randomvec + +Returns a vector of length < 1 and > 0 + +vector randomvec() +================= +*/ +void VM_randomvec (void) +{ + vec3_t temp; + //float length; + + VM_SAFEPARMCOUNT(0, VM_randomvec); + + //// WTF ?? + do + { + temp[0] = (rand()&32767) * (2.0 / 32767.0) - 1.0; + temp[1] = (rand()&32767) * (2.0 / 32767.0) - 1.0; + temp[2] = (rand()&32767) * (2.0 / 32767.0) - 1.0; + } + while (DotProduct(temp, temp) >= 1); + VectorCopy (temp, PRVM_G_VECTOR(OFS_RETURN)); + + /* + temp[0] = (rand()&32767) * (2.0 / 32767.0) - 1.0; + temp[1] = (rand()&32767) * (2.0 / 32767.0) - 1.0; + temp[2] = (rand()&32767) * (2.0 / 32767.0) - 1.0; + // length returned always > 0 + length = (rand()&32766 + 1) * (1.0 / 32767.0) / VectorLength(temp); + VectorScale(temp,length, temp);*/ + //VectorCopy(temp, PRVM_G_VECTOR(OFS_RETURN)); +} + +//============================================================================= + +/* +========= +VM_registercvar + +float registercvar (string name, string value[, float flags]) +========= +*/ +void VM_registercvar (void) +{ + const char *name, *value; + int flags; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_registercvar); + + name = PRVM_G_STRING(OFS_PARM0); + value = PRVM_G_STRING(OFS_PARM1); + flags = prog->argc >= 3 ? (int)PRVM_G_FLOAT(OFS_PARM2) : 0; + PRVM_G_FLOAT(OFS_RETURN) = 0; + + if(flags > CVAR_MAXFLAGSVAL) + return; + +// first check to see if it has already been defined + if (Cvar_FindVar (name)) + return; + +// check for overlap with a command + if (Cmd_Exists (name)) + { + VM_Warning("VM_registercvar: %s is a command\n", name); + return; + } + + Cvar_Get(name, value, flags, NULL); + + PRVM_G_FLOAT(OFS_RETURN) = 1; // success +} + + +/* +================= +VM_min + +returns the minimum of two supplied floats + +float min(float a, float b, ...[float]) +================= +*/ +void VM_min (void) +{ + VM_SAFEPARMCOUNTRANGE(2, 8, VM_min); + // LordHavoc: 3+ argument enhancement suggested by FrikaC + if (prog->argc >= 3) + { + int i; + float f = PRVM_G_FLOAT(OFS_PARM0); + for (i = 1;i < prog->argc;i++) + if (f > PRVM_G_FLOAT((OFS_PARM0+i*3))) + f = PRVM_G_FLOAT((OFS_PARM0+i*3)); + PRVM_G_FLOAT(OFS_RETURN) = f; + } + else + PRVM_G_FLOAT(OFS_RETURN) = min(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); +} + +/* +================= +VM_max + +returns the maximum of two supplied floats + +float max(float a, float b, ...[float]) +================= +*/ +void VM_max (void) +{ + VM_SAFEPARMCOUNTRANGE(2, 8, VM_max); + // LordHavoc: 3+ argument enhancement suggested by FrikaC + if (prog->argc >= 3) + { + int i; + float f = PRVM_G_FLOAT(OFS_PARM0); + for (i = 1;i < prog->argc;i++) + if (f < PRVM_G_FLOAT((OFS_PARM0+i*3))) + f = PRVM_G_FLOAT((OFS_PARM0+i*3)); + PRVM_G_FLOAT(OFS_RETURN) = f; + } + else + PRVM_G_FLOAT(OFS_RETURN) = max(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); +} + +/* +================= +VM_bound + +returns number bounded by supplied range + +float bound(float min, float value, float max) +================= +*/ +void VM_bound (void) +{ + VM_SAFEPARMCOUNT(3,VM_bound); + PRVM_G_FLOAT(OFS_RETURN) = bound(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1), PRVM_G_FLOAT(OFS_PARM2)); +} + +/* +================= +VM_pow + +returns a raised to power b + +float pow(float a, float b) +================= +*/ +void VM_pow (void) +{ + VM_SAFEPARMCOUNT(2,VM_pow); + PRVM_G_FLOAT(OFS_RETURN) = pow(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); +} + +void VM_log (void) +{ + VM_SAFEPARMCOUNT(1,VM_log); + PRVM_G_FLOAT(OFS_RETURN) = log(PRVM_G_FLOAT(OFS_PARM0)); +} + +void VM_Files_Init(void) +{ + int i; + for (i = 0;i < PRVM_MAX_OPENFILES;i++) + prog->openfiles[i] = NULL; +} + +void VM_Files_CloseAll(void) +{ + int i; + for (i = 0;i < PRVM_MAX_OPENFILES;i++) + { + if (prog->openfiles[i]) + FS_Close(prog->openfiles[i]); + prog->openfiles[i] = NULL; + } +} + +static qfile_t *VM_GetFileHandle( int index ) +{ + if (index < 0 || index >= PRVM_MAX_OPENFILES) + { + Con_Printf("VM_GetFileHandle: invalid file handle %i used in %s\n", index, PRVM_NAME); + return NULL; + } + if (prog->openfiles[index] == NULL) + { + Con_Printf("VM_GetFileHandle: no such file handle %i (or file has been closed) in %s\n", index, PRVM_NAME); + return NULL; + } + return prog->openfiles[index]; +} + +/* +========= +VM_fopen + +float fopen(string filename, float mode) +========= +*/ +// float(string filename, float mode) fopen = #110; +// opens a file inside quake/gamedir/data/ (mode is FILE_READ, FILE_APPEND, or FILE_WRITE), +// returns fhandle >= 0 if successful, or fhandle < 0 if unable to open file for any reason +void VM_fopen(void) +{ + int filenum, mode; + const char *modestring, *filename; + + VM_SAFEPARMCOUNT(2,VM_fopen); + + for (filenum = 0;filenum < PRVM_MAX_OPENFILES;filenum++) + if (prog->openfiles[filenum] == NULL) + break; + if (filenum >= PRVM_MAX_OPENFILES) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_fopen: %s ran out of file handles (%i)\n", PRVM_NAME, PRVM_MAX_OPENFILES); + return; + } + filename = PRVM_G_STRING(OFS_PARM0); + mode = (int)PRVM_G_FLOAT(OFS_PARM1); + switch(mode) + { + case 0: // FILE_READ + modestring = "rb"; + prog->openfiles[filenum] = FS_OpenVirtualFile(va("data/%s", filename), false); + if (prog->openfiles[filenum] == NULL) + prog->openfiles[filenum] = FS_OpenVirtualFile(va("%s", filename), false); + break; + case 1: // FILE_APPEND + modestring = "a"; + prog->openfiles[filenum] = FS_OpenRealFile(va("data/%s", filename), modestring, false); + break; + case 2: // FILE_WRITE + modestring = "w"; + prog->openfiles[filenum] = FS_OpenRealFile(va("data/%s", filename), modestring, false); + break; + default: + PRVM_G_FLOAT(OFS_RETURN) = -3; + VM_Warning("VM_fopen: %s: no such mode %i (valid: 0 = read, 1 = append, 2 = write)\n", PRVM_NAME, mode); + return; + } + + if (prog->openfiles[filenum] == NULL) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + if (developer_extra.integer) + VM_Warning("VM_fopen: %s: %s mode %s failed\n", PRVM_NAME, filename, modestring); + } + else + { + PRVM_G_FLOAT(OFS_RETURN) = filenum; + if (developer_extra.integer) + Con_DPrintf("VM_fopen: %s: %s mode %s opened as #%i\n", PRVM_NAME, filename, modestring, filenum); + prog->openfiles_origin[filenum] = PRVM_AllocationOrigin(); + } +} + +/* +========= +VM_fclose + +fclose(float fhandle) +========= +*/ +//void(float fhandle) fclose = #111; // closes a file +void VM_fclose(void) +{ + int filenum; + + VM_SAFEPARMCOUNT(1,VM_fclose); + + filenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) + { + VM_Warning("VM_fclose: invalid file handle %i used in %s\n", filenum, PRVM_NAME); + return; + } + if (prog->openfiles[filenum] == NULL) + { + VM_Warning("VM_fclose: no such file handle %i (or file has been closed) in %s\n", filenum, PRVM_NAME); + return; + } + FS_Close(prog->openfiles[filenum]); + prog->openfiles[filenum] = NULL; + if(prog->openfiles_origin[filenum]) + PRVM_Free((char *)prog->openfiles_origin[filenum]); + if (developer_extra.integer) + Con_DPrintf("VM_fclose: %s: #%i closed\n", PRVM_NAME, filenum); +} + +/* +========= +VM_fgets + +string fgets(float fhandle) +========= +*/ +//string(float fhandle) fgets = #112; // reads a line of text from the file and returns as a tempstring +void VM_fgets(void) +{ + int c, end; + char string[VM_STRINGTEMP_LENGTH]; + int filenum; + + VM_SAFEPARMCOUNT(1,VM_fgets); + + // set the return value regardless of any possible errors + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + + filenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) + { + VM_Warning("VM_fgets: invalid file handle %i used in %s\n", filenum, PRVM_NAME); + return; + } + if (prog->openfiles[filenum] == NULL) + { + VM_Warning("VM_fgets: no such file handle %i (or file has been closed) in %s\n", filenum, PRVM_NAME); + return; + } + end = 0; + for (;;) + { + c = FS_Getc(prog->openfiles[filenum]); + if (c == '\r' || c == '\n' || c < 0) + break; + if (end < VM_STRINGTEMP_LENGTH - 1) + string[end++] = c; + } + string[end] = 0; + // remove \n following \r + if (c == '\r') + { + c = FS_Getc(prog->openfiles[filenum]); + if (c != '\n') + FS_UnGetc(prog->openfiles[filenum], (unsigned char)c); + } + if (developer_extra.integer) + Con_DPrintf("fgets: %s: %s\n", PRVM_NAME, string); + if (c >= 0 || end) + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); +} + +/* +========= +VM_fputs + +fputs(float fhandle, string s) +========= +*/ +//void(float fhandle, string s) fputs = #113; // writes a line of text to the end of the file +void VM_fputs(void) +{ + int stringlength; + char string[VM_STRINGTEMP_LENGTH]; + int filenum; + + VM_SAFEPARMCOUNT(2,VM_fputs); + + filenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) + { + VM_Warning("VM_fputs: invalid file handle %i used in %s\n", filenum, PRVM_NAME); + return; + } + if (prog->openfiles[filenum] == NULL) + { + VM_Warning("VM_fputs: no such file handle %i (or file has been closed) in %s\n", filenum, PRVM_NAME); + return; + } + VM_VarString(1, string, sizeof(string)); + if ((stringlength = (int)strlen(string))) + FS_Write(prog->openfiles[filenum], string, stringlength); + if (developer_extra.integer) + Con_DPrintf("fputs: %s: %s\n", PRVM_NAME, string); +} + +/* +========= +VM_writetofile + + writetofile(float fhandle, entity ent) +========= +*/ +void VM_writetofile(void) +{ + prvm_edict_t * ent; + qfile_t *file; + + VM_SAFEPARMCOUNT(2, VM_writetofile); + + file = VM_GetFileHandle( (int)PRVM_G_FLOAT(OFS_PARM0) ); + if( !file ) + { + VM_Warning("VM_writetofile: invalid or closed file handle\n"); + return; + } + + ent = PRVM_G_EDICT(OFS_PARM1); + if(ent->priv.required->free) + { + VM_Warning("VM_writetofile: %s: entity %i is free !\n", PRVM_NAME, PRVM_NUM_FOR_EDICT(ent)); + return; + } + + PRVM_ED_Write (file, ent); +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_numentityfields + +float() numentityfields +Return the number of entity fields - NOT offsets +========= +*/ +void VM_numentityfields(void) +{ + PRVM_G_FLOAT(OFS_RETURN) = prog->numfielddefs; +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_entityfieldname + +string(float fieldnum) entityfieldname +Return name of the specified field as a string, or empty if the field is invalid (warning) +========= +*/ +void VM_entityfieldname(void) +{ + ddef_t *d; + int i = (int)PRVM_G_FLOAT(OFS_PARM0); + + if (i < 0 || i >= prog->numfielddefs) + { + VM_Warning("VM_entityfieldname: %s: field index out of bounds\n", PRVM_NAME); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(""); + return; + } + + d = &prog->fielddefs[i]; + PRVM_G_INT(OFS_RETURN) = d->s_name; // presuming that s_name points to a string already +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_entityfieldtype + +float(float fieldnum) entityfieldtype +========= +*/ +void VM_entityfieldtype(void) +{ + ddef_t *d; + int i = (int)PRVM_G_FLOAT(OFS_PARM0); + + if (i < 0 || i >= prog->numfielddefs) + { + VM_Warning("VM_entityfieldtype: %s: field index out of bounds\n", PRVM_NAME); + PRVM_G_FLOAT(OFS_RETURN) = -1.0; + return; + } + + d = &prog->fielddefs[i]; + PRVM_G_FLOAT(OFS_RETURN) = (float)d->type; +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_getentityfieldstring + +string(float fieldnum, entity ent) getentityfieldstring +========= +*/ +void VM_getentityfieldstring(void) +{ + // put the data into a string + ddef_t *d; + int type, j; + int *v; + prvm_edict_t * ent; + int i = (int)PRVM_G_FLOAT(OFS_PARM0); + + if (i < 0 || i >= prog->numfielddefs) + { + VM_Warning("VM_entityfielddata: %s: field index out of bounds\n", PRVM_NAME); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(""); + return; + } + + d = &prog->fielddefs[i]; + + // get the entity + ent = PRVM_G_EDICT(OFS_PARM1); + if(ent->priv.required->free) + { + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(""); + VM_Warning("VM_entityfielddata: %s: entity %i is free !\n", PRVM_NAME, PRVM_NUM_FOR_EDICT(ent)); + return; + } + v = (int *)((char *)ent->fields.vp + d->ofs*4); + + // if it's 0 or blank, return an empty string + type = d->type & ~DEF_SAVEGLOBAL; + for (j=0 ; jtype, (prvm_eval_t *)v)); +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_putentityfieldstring + +float(float fieldnum, entity ent, string s) putentityfieldstring +========= +*/ +void VM_putentityfieldstring(void) +{ + ddef_t *d; + prvm_edict_t * ent; + int i = (int)PRVM_G_FLOAT(OFS_PARM0); + + if (i < 0 || i >= prog->numfielddefs) + { + VM_Warning("VM_entityfielddata: %s: field index out of bounds\n", PRVM_NAME); + PRVM_G_FLOAT(OFS_RETURN) = 0.0f; + return; + } + + d = &prog->fielddefs[i]; + + // get the entity + ent = PRVM_G_EDICT(OFS_PARM1); + if(ent->priv.required->free) + { + VM_Warning("VM_entityfielddata: %s: entity %i is free !\n", PRVM_NAME, PRVM_NUM_FOR_EDICT(ent)); + PRVM_G_FLOAT(OFS_RETURN) = 0.0f; + return; + } + + // parse the string into the value + PRVM_G_FLOAT(OFS_RETURN) = ( PRVM_ED_ParseEpair(ent, d, PRVM_G_STRING(OFS_PARM2), false) ) ? 1.0f : 0.0f; +} + +/* +========= +VM_strlen + +float strlen(string s) +========= +*/ +//float(string s) strlen = #114; // returns how many characters are in a string +void VM_strlen(void) +{ + VM_SAFEPARMCOUNT(1,VM_strlen); + + //PRVM_G_FLOAT(OFS_RETURN) = strlen(PRVM_G_STRING(OFS_PARM0)); + PRVM_G_FLOAT(OFS_RETURN) = u8_strlen(PRVM_G_STRING(OFS_PARM0)); +} + +// DRESK - Decolorized String +/* +========= +VM_strdecolorize + +string strdecolorize(string s) +========= +*/ +// string (string s) strdecolorize = #472; // returns the passed in string with color codes stripped +void VM_strdecolorize(void) +{ + char szNewString[VM_STRINGTEMP_LENGTH]; + const char *szString; + + // Prepare Strings + VM_SAFEPARMCOUNT(1,VM_strdecolorize); + szString = PRVM_G_STRING(OFS_PARM0); + COM_StringDecolorize(szString, 0, szNewString, sizeof(szNewString), TRUE); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(szNewString); +} + +// DRESK - String Length (not counting color codes) +/* +========= +VM_strlennocol + +float strlennocol(string s) +========= +*/ +// float(string s) strlennocol = #471; // returns how many characters are in a string not including color codes +// For example, ^2Dresk returns a length of 5 +void VM_strlennocol(void) +{ + const char *szString; + int nCnt; + + VM_SAFEPARMCOUNT(1,VM_strlennocol); + + szString = PRVM_G_STRING(OFS_PARM0); + + //nCnt = COM_StringLengthNoColors(szString, 0, NULL); + nCnt = u8_COM_StringLengthNoColors(szString, 0, NULL); + + PRVM_G_FLOAT(OFS_RETURN) = nCnt; +} + +// DRESK - String to Uppercase and Lowercase +/* +========= +VM_strtolower + +string strtolower(string s) +========= +*/ +// string (string s) strtolower = #480; // returns passed in string in lowercase form +void VM_strtolower(void) +{ + char szNewString[VM_STRINGTEMP_LENGTH]; + const char *szString; + + // Prepare Strings + VM_SAFEPARMCOUNT(1,VM_strtolower); + szString = PRVM_G_STRING(OFS_PARM0); + + COM_ToLowerString(szString, szNewString, sizeof(szNewString) ); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(szNewString); +} + +/* +========= +VM_strtoupper + +string strtoupper(string s) +========= +*/ +// string (string s) strtoupper = #481; // returns passed in string in uppercase form +void VM_strtoupper(void) +{ + char szNewString[VM_STRINGTEMP_LENGTH]; + const char *szString; + + // Prepare Strings + VM_SAFEPARMCOUNT(1,VM_strtoupper); + szString = PRVM_G_STRING(OFS_PARM0); + + COM_ToUpperString(szString, szNewString, sizeof(szNewString) ); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(szNewString); +} + +/* +========= +VM_strcat + +string strcat(string,string,...[string]) +========= +*/ +//string(string s1, string s2) strcat = #115; +// concatenates two strings (for example "abc", "def" would return "abcdef") +// and returns as a tempstring +void VM_strcat(void) +{ + char s[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_strcat); + + VM_VarString(0, s, sizeof(s)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(s); +} + +/* +========= +VM_substring + +string substring(string s, float start, float length) +========= +*/ +// string(string s, float start, float length) substring = #116; +// returns a section of a string as a tempstring +void VM_substring(void) +{ + int start, length; + int u_slength = 0, u_start; + size_t u_length; + const char *s; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT(3,VM_substring); + + /* + s = PRVM_G_STRING(OFS_PARM0); + start = (int)PRVM_G_FLOAT(OFS_PARM1); + length = (int)PRVM_G_FLOAT(OFS_PARM2); + slength = strlen(s); + + if (start < 0) // FTE_STRINGS feature + start += slength; + start = bound(0, start, slength); + + if (length < 0) // FTE_STRINGS feature + length += slength - start + 1; + maxlen = min((int)sizeof(string) - 1, slength - start); + length = bound(0, length, maxlen); + + memcpy(string, s + start, length); + string[length] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); + */ + + s = PRVM_G_STRING(OFS_PARM0); + start = (int)PRVM_G_FLOAT(OFS_PARM1); + length = (int)PRVM_G_FLOAT(OFS_PARM2); + + if (start < 0) // FTE_STRINGS feature + { + u_slength = u8_strlen(s); + start += u_slength; + start = bound(0, start, u_slength); + } + + if (length < 0) // FTE_STRINGS feature + { + if (!u_slength) // it's not calculated when it's not needed above + u_slength = u8_strlen(s); + length += u_slength - start + 1; + } + + // positive start, positive length + u_start = u8_byteofs(s, start, NULL); + if (u_start < 0) + { + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(""); + return; + } + u_length = u8_bytelen(s + u_start, length); + if (u_length >= sizeof(string)-1) + u_length = sizeof(string)-1; + + memcpy(string, s + u_start, u_length); + string[u_length] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); +} + +/* +========= +VM_strreplace + +string(string search, string replace, string subject) strreplace = #484; +========= +*/ +// replaces all occurrences of search with replace in the string subject, and returns the result +void VM_strreplace(void) +{ + int i, j, si; + const char *search, *replace, *subject; + char string[VM_STRINGTEMP_LENGTH]; + int search_len, replace_len, subject_len; + + VM_SAFEPARMCOUNT(3,VM_strreplace); + + search = PRVM_G_STRING(OFS_PARM0); + replace = PRVM_G_STRING(OFS_PARM1); + subject = PRVM_G_STRING(OFS_PARM2); + + search_len = (int)strlen(search); + replace_len = (int)strlen(replace); + subject_len = (int)strlen(subject); + + si = 0; + for (i = 0; i <= subject_len - search_len; i++) + { + for (j = 0; j < search_len; j++) // thus, i+j < subject_len + if (subject[i+j] != search[j]) + break; + if (j == search_len) + { + // NOTE: if search_len == 0, we always hit THIS case, and never the other + // found it at offset 'i' + for (j = 0; j < replace_len && si < (int)sizeof(string) - 1; j++) + string[si++] = replace[j]; + if(search_len > 0) + { + i += search_len - 1; + } + else + { + // the above would subtract 1 from i... so we + // don't do that, but instead output the next + // char + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + } + } + else + { + // in THIS case, we know search_len > 0, thus i < subject_len + // not found + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + } + } + // remaining chars (these cannot match) + for (; i < subject_len; i++) + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + string[si] = '\0'; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); +} + +/* +========= +VM_strireplace + +string(string search, string replace, string subject) strireplace = #485; +========= +*/ +// case-insensitive version of strreplace +void VM_strireplace(void) +{ + int i, j, si; + const char *search, *replace, *subject; + char string[VM_STRINGTEMP_LENGTH]; + int search_len, replace_len, subject_len; + + VM_SAFEPARMCOUNT(3,VM_strreplace); + + search = PRVM_G_STRING(OFS_PARM0); + replace = PRVM_G_STRING(OFS_PARM1); + subject = PRVM_G_STRING(OFS_PARM2); + + search_len = (int)strlen(search); + replace_len = (int)strlen(replace); + subject_len = (int)strlen(subject); + + si = 0; + for (i = 0; i <= subject_len - search_len; i++) + { + for (j = 0; j < search_len; j++) // thus, i+j < subject_len + if (tolower(subject[i+j]) != tolower(search[j])) + break; + if (j == search_len) + { + // NOTE: if search_len == 0, we always hit THIS case, and never the other + // found it at offset 'i' + for (j = 0; j < replace_len && si < (int)sizeof(string) - 1; j++) + string[si++] = replace[j]; + if(search_len > 0) + { + i += search_len - 1; + } + else + { + // the above would subtract 1 from i... so we + // don't do that, but instead output the next + // char + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + } + } + else + { + // in THIS case, we know search_len > 0, thus i < subject_len + // not found + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + } + } + // remaining chars (these cannot match) + for (; i < subject_len; i++) + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + string[si] = '\0'; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); +} + +/* +========= +VM_stov + +vector stov(string s) +========= +*/ +//vector(string s) stov = #117; // returns vector value from a string +void VM_stov(void) +{ + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT(1,VM_stov); + + VM_VarString(0, string, sizeof(string)); + Math_atov(string, PRVM_G_VECTOR(OFS_RETURN)); +} + +/* +========= +VM_strzone + +string strzone(string s) +========= +*/ +//string(string s, ...) strzone = #118; // makes a copy of a string into the string zone and returns it, this is often used to keep around a tempstring for longer periods of time (tempstrings are replaced often) +void VM_strzone(void) +{ + char *out; + char string[VM_STRINGTEMP_LENGTH]; + size_t alloclen; + + VM_SAFEPARMCOUNT(1,VM_strzone); + + VM_VarString(0, string, sizeof(string)); + alloclen = strlen(string) + 1; + PRVM_G_INT(OFS_RETURN) = PRVM_AllocString(alloclen, &out); + memcpy(out, string, alloclen); +} + +/* +========= +VM_strunzone + +strunzone(string s) +========= +*/ +//void(string s) strunzone = #119; // removes a copy of a string from the string zone (you can not use that string again or it may crash!!!) +void VM_strunzone(void) +{ + VM_SAFEPARMCOUNT(1,VM_strunzone); + PRVM_FreeString(PRVM_G_INT(OFS_PARM0)); +} + +/* +========= +VM_command (used by client and menu) + +clientcommand(float client, string s) (for client and menu) +========= +*/ +//void(entity e, string s) clientcommand = #440; // executes a command string as if it came from the specified client +//this function originally written by KrimZon, made shorter by LordHavoc +void VM_clcommand (void) +{ + client_t *temp_client; + int i; + + VM_SAFEPARMCOUNT(2,VM_clcommand); + + i = (int)PRVM_G_FLOAT(OFS_PARM0); + if (!sv.active || i < 0 || i >= svs.maxclients || !svs.clients[i].active) + { + VM_Warning("VM_clientcommand: %s: invalid client/server is not active !\n", PRVM_NAME); + return; + } + + temp_client = host_client; + host_client = svs.clients + i; + Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client); + host_client = temp_client; +} + + +/* +========= +VM_tokenize + +float tokenize(string s) +========= +*/ +//float(string s) tokenize = #441; // takes apart a string into individal words (access them with argv), returns how many +//this function originally written by KrimZon, made shorter by LordHavoc +//20040203: rewritten by LordHavoc (no longer uses allocations) +static int num_tokens = 0; +static int tokens[VM_STRINGTEMP_LENGTH / 2]; +static int tokens_startpos[VM_STRINGTEMP_LENGTH / 2]; +static int tokens_endpos[VM_STRINGTEMP_LENGTH / 2]; +static char tokenize_string[VM_STRINGTEMP_LENGTH]; +void VM_tokenize (void) +{ + const char *p; + + VM_SAFEPARMCOUNT(1,VM_tokenize); + + strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); + p = tokenize_string; + + num_tokens = 0; + for(;;) + { + if (num_tokens >= (int)(sizeof(tokens)/sizeof(tokens[0]))) + break; + + // skip whitespace here to find token start pos + while(*p && ISWHITESPACE(*p)) + ++p; + + tokens_startpos[num_tokens] = p - tokenize_string; + if(!COM_ParseToken_VM_Tokenize(&p, false)) + break; + tokens_endpos[num_tokens] = p - tokenize_string; + tokens[num_tokens] = PRVM_SetTempString(com_token); + ++num_tokens; + } + + PRVM_G_FLOAT(OFS_RETURN) = num_tokens; +} + +//float(string s) tokenize = #514; // takes apart a string into individal words (access them with argv), returns how many +void VM_tokenize_console (void) +{ + const char *p; + + VM_SAFEPARMCOUNT(1,VM_tokenize); + + strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); + p = tokenize_string; + + num_tokens = 0; + for(;;) + { + if (num_tokens >= (int)(sizeof(tokens)/sizeof(tokens[0]))) + break; + + // skip whitespace here to find token start pos + while(*p && ISWHITESPACE(*p)) + ++p; + + tokens_startpos[num_tokens] = p - tokenize_string; + if(!COM_ParseToken_Console(&p)) + break; + tokens_endpos[num_tokens] = p - tokenize_string; + tokens[num_tokens] = PRVM_SetTempString(com_token); + ++num_tokens; + } + + PRVM_G_FLOAT(OFS_RETURN) = num_tokens; +} + +/* +========= +VM_tokenizebyseparator + +float tokenizebyseparator(string s, string separator1, ...) +========= +*/ +//float(string s, string separator1, ...) tokenizebyseparator = #479; // takes apart a string into individal words (access them with argv), returns how many +//this function returns the token preceding each instance of a separator (of +//which there can be multiple), and the text following the last separator +//useful for parsing certain kinds of data like IP addresses +//example: +//numnumbers = tokenizebyseparator("10.1.2.3", "."); +//returns 4 and the tokens "10" "1" "2" "3". +void VM_tokenizebyseparator (void) +{ + int j, k; + int numseparators; + int separatorlen[7]; + const char *separators[7]; + const char *p, *p0; + const char *token; + char tokentext[MAX_INPUTLINE]; + + VM_SAFEPARMCOUNTRANGE(2, 8,VM_tokenizebyseparator); + + strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); + p = tokenize_string; + + numseparators = 0; + for (j = 1;j < prog->argc;j++) + { + // skip any blank separator strings + const char *s = PRVM_G_STRING(OFS_PARM0+j*3); + if (!s[0]) + continue; + separators[numseparators] = s; + separatorlen[numseparators] = strlen(s); + numseparators++; + } + + num_tokens = 0; + j = 0; + + while (num_tokens < (int)(sizeof(tokens)/sizeof(tokens[0]))) + { + token = tokentext + j; + tokens_startpos[num_tokens] = p - tokenize_string; + p0 = p; + while (*p) + { + for (k = 0;k < numseparators;k++) + { + if (!strncmp(p, separators[k], separatorlen[k])) + { + p += separatorlen[k]; + break; + } + } + if (k < numseparators) + break; + if (j < (int)sizeof(tokentext)-1) + tokentext[j++] = *p; + p++; + p0 = p; + } + tokens_endpos[num_tokens] = p0 - tokenize_string; + if (j >= (int)sizeof(tokentext)) + break; + tokentext[j++] = 0; + tokens[num_tokens++] = PRVM_SetTempString(token); + if (!*p) + break; + } + + PRVM_G_FLOAT(OFS_RETURN) = num_tokens; +} + +//string(float n) argv = #442; // returns a word from the tokenized string (returns nothing for an invalid index) +//this function originally written by KrimZon, made shorter by LordHavoc +void VM_argv (void) +{ + int token_num; + + VM_SAFEPARMCOUNT(1,VM_argv); + + token_num = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(token_num < 0) + token_num += num_tokens; + + if (token_num >= 0 && token_num < num_tokens) + PRVM_G_INT(OFS_RETURN) = tokens[token_num]; + else + PRVM_G_INT(OFS_RETURN) = OFS_NULL; +} + +//float(float n) argv_start_index = #515; // returns the start index of a token +void VM_argv_start_index (void) +{ + int token_num; + + VM_SAFEPARMCOUNT(1,VM_argv); + + token_num = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(token_num < 0) + token_num += num_tokens; + + if (token_num >= 0 && token_num < num_tokens) + PRVM_G_FLOAT(OFS_RETURN) = tokens_startpos[token_num]; + else + PRVM_G_FLOAT(OFS_RETURN) = -1; +} + +//float(float n) argv_end_index = #516; // returns the end index of a token +void VM_argv_end_index (void) +{ + int token_num; + + VM_SAFEPARMCOUNT(1,VM_argv); + + token_num = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(token_num < 0) + token_num += num_tokens; + + if (token_num >= 0 && token_num < num_tokens) + PRVM_G_FLOAT(OFS_RETURN) = tokens_endpos[token_num]; + else + PRVM_G_FLOAT(OFS_RETURN) = -1; +} + +/* +========= +VM_isserver + +float isserver() +========= +*/ +void VM_isserver(void) +{ + VM_SAFEPARMCOUNT(0,VM_serverstate); + + PRVM_G_FLOAT(OFS_RETURN) = sv.active && (svs.maxclients > 1 || cls.state == ca_dedicated); +} + +/* +========= +VM_clientcount + +float clientcount() +========= +*/ +void VM_clientcount(void) +{ + VM_SAFEPARMCOUNT(0,VM_clientcount); + + PRVM_G_FLOAT(OFS_RETURN) = svs.maxclients; +} + +/* +========= +VM_clientstate + +float clientstate() +========= +*/ +void VM_clientstate(void) +{ + VM_SAFEPARMCOUNT(0,VM_clientstate); + + + switch( cls.state ) { + case ca_uninitialized: + case ca_dedicated: + PRVM_G_FLOAT(OFS_RETURN) = 0; + break; + case ca_disconnected: + PRVM_G_FLOAT(OFS_RETURN) = 1; + break; + case ca_connected: + PRVM_G_FLOAT(OFS_RETURN) = 2; + break; + default: + // should never be reached! + break; + } +} + +/* +========= +VM_getostype + +float getostype(void) +========= +*/ // not used at the moment -> not included in the common list +void VM_getostype(void) +{ + VM_SAFEPARMCOUNT(0,VM_getostype); + + /* + OS_WINDOWS + OS_LINUX + OS_MAC - not supported + */ + +#ifdef WIN32 + PRVM_G_FLOAT(OFS_RETURN) = 0; +#elif defined(MACOSX) + PRVM_G_FLOAT(OFS_RETURN) = 2; +#else + PRVM_G_FLOAT(OFS_RETURN) = 1; +#endif +} + +/* +========= +VM_gettime + +float gettime(void) +========= +*/ +extern double host_starttime; +float CDAudio_GetPosition(void); +void VM_gettime(void) +{ + int timer_index; + + VM_SAFEPARMCOUNTRANGE(0,1,VM_gettime); + + if(prog->argc == 0) + { + PRVM_G_FLOAT(OFS_RETURN) = (float) realtime; + } + else + { + timer_index = (int) PRVM_G_FLOAT(OFS_PARM0); + switch(timer_index) + { + case 0: // GETTIME_FRAMESTART + PRVM_G_FLOAT(OFS_RETURN) = (float) realtime; + break; + case 1: // GETTIME_REALTIME + PRVM_G_FLOAT(OFS_RETURN) = (float) Sys_DoubleTime(); + break; + case 2: // GETTIME_HIRES + PRVM_G_FLOAT(OFS_RETURN) = (float) (Sys_DoubleTime() - realtime); + break; + case 3: // GETTIME_UPTIME + PRVM_G_FLOAT(OFS_RETURN) = (float) (Sys_DoubleTime() - host_starttime); + break; + case 4: // GETTIME_CDTRACK + PRVM_G_FLOAT(OFS_RETURN) = (float) CDAudio_GetPosition(); + break; + default: + VM_Warning("VM_gettime: %s: unsupported timer specified, returning realtime\n", PRVM_NAME); + PRVM_G_FLOAT(OFS_RETURN) = (float) realtime; + break; + } + } +} + +/* +========= +VM_getsoundtime + +float getsoundtime(void) +========= +*/ + +void VM_getsoundtime (void) +{ + int entnum, entchannel, pnum; + VM_SAFEPARMCOUNT(2,VM_getsoundtime); + + pnum = PRVM_GetProgNr(); + if (pnum == PRVM_MENUPROG) + { + VM_Warning("VM_getsoundtime: %s: not supported on this progs\n", PRVM_NAME); + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; + } + entnum = ((pnum == PRVM_CLIENTPROG) ? MAX_EDICTS : 0) + PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0)); + entchannel = (int)PRVM_G_FLOAT(OFS_PARM1); + entchannel = CHAN_USER2ENGINE(entchannel); + if (!IS_CHAN(entchannel)) + VM_Warning("VM_getsoundtime: %s: bad channel %i\n", PRVM_NAME, entchannel); + PRVM_G_FLOAT(OFS_RETURN) = (float)S_GetEntChannelPosition(entnum, entchannel); +} + +/* +========= +VM_GetSoundLen + +string soundlength (string sample) +========= +*/ +void VM_soundlength (void) +{ + const char *s; + + VM_SAFEPARMCOUNT(1, VM_soundlength); + + s = PRVM_G_STRING(OFS_PARM0); + PRVM_G_FLOAT(OFS_RETURN) = S_SoundLength(s); +} + +/* +========= +VM_loadfromdata + +loadfromdata(string data) +========= +*/ +void VM_loadfromdata(void) +{ + VM_SAFEPARMCOUNT(1,VM_loadentsfromfile); + + PRVM_ED_LoadFromFile(PRVM_G_STRING(OFS_PARM0)); +} + +/* +======================== +VM_parseentitydata + +parseentitydata(entity ent, string data) +======================== +*/ +void VM_parseentitydata(void) +{ + prvm_edict_t *ent; + const char *data; + + VM_SAFEPARMCOUNT(2, VM_parseentitydata); + + // get edict and test it + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent->priv.required->free) + PRVM_ERROR ("VM_parseentitydata: %s: Can only set already spawned entities (entity %i is free)!", PRVM_NAME, PRVM_NUM_FOR_EDICT(ent)); + + data = PRVM_G_STRING(OFS_PARM1); + + // parse the opening brace + if (!COM_ParseToken_Simple(&data, false, false) || com_token[0] != '{' ) + PRVM_ERROR ("VM_parseentitydata: %s: Couldn't parse entity data:\n%s", PRVM_NAME, data ); + + PRVM_ED_ParseEdict (data, ent); +} + +/* +========= +VM_loadfromfile + +loadfromfile(string file) +========= +*/ +void VM_loadfromfile(void) +{ + const char *filename; + char *data; + + VM_SAFEPARMCOUNT(1,VM_loadfromfile); + + filename = PRVM_G_STRING(OFS_PARM0); + if (FS_CheckNastyPath(filename, false)) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning("VM_loadfromfile: %s dangerous or non-portable filename \"%s\" not allowed. (contains : or \\ or begins with .. or /)\n", PRVM_NAME, filename); + return; + } + + // not conform with VM_fopen + data = (char *)FS_LoadFile(filename, tempmempool, false, NULL); + if (data == NULL) + PRVM_G_FLOAT(OFS_RETURN) = -1; + + PRVM_ED_LoadFromFile(data); + + if(data) + Mem_Free(data); +} + + +/* +========= +VM_modulo + +float mod(float val, float m) +========= +*/ +void VM_modulo(void) +{ + int val, m; + VM_SAFEPARMCOUNT(2,VM_module); + + val = (int) PRVM_G_FLOAT(OFS_PARM0); + m = (int) PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_FLOAT(OFS_RETURN) = (float) (val % m); +} + +void VM_Search_Init(void) +{ + int i; + for (i = 0;i < PRVM_MAX_OPENSEARCHES;i++) + prog->opensearches[i] = NULL; +} + +void VM_Search_Reset(void) +{ + int i; + // reset the fssearch list + for(i = 0; i < PRVM_MAX_OPENSEARCHES; i++) + { + if(prog->opensearches[i]) + FS_FreeSearch(prog->opensearches[i]); + prog->opensearches[i] = NULL; + } +} + +/* +========= +VM_search_begin + +float search_begin(string pattern, float caseinsensitive, float quiet) +========= +*/ +void VM_search_begin(void) +{ + int handle; + const char *pattern; + int caseinsens, quiet; + + VM_SAFEPARMCOUNT(3, VM_search_begin); + + pattern = PRVM_G_STRING(OFS_PARM0); + + VM_CheckEmptyString(pattern); + + caseinsens = (int)PRVM_G_FLOAT(OFS_PARM1); + quiet = (int)PRVM_G_FLOAT(OFS_PARM2); + + for(handle = 0; handle < PRVM_MAX_OPENSEARCHES; handle++) + if(!prog->opensearches[handle]) + break; + + if(handle >= PRVM_MAX_OPENSEARCHES) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_search_begin: %s ran out of search handles (%i)\n", PRVM_NAME, PRVM_MAX_OPENSEARCHES); + return; + } + + if(!(prog->opensearches[handle] = FS_Search(pattern,caseinsens, quiet))) + PRVM_G_FLOAT(OFS_RETURN) = -1; + else + { + prog->opensearches_origin[handle] = PRVM_AllocationOrigin(); + PRVM_G_FLOAT(OFS_RETURN) = handle; + } +} + +/* +========= +VM_search_end + +void search_end(float handle) +========= +*/ +void VM_search_end(void) +{ + int handle; + VM_SAFEPARMCOUNT(1, VM_search_end); + + handle = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) + { + VM_Warning("VM_search_end: invalid handle %i used in %s\n", handle, PRVM_NAME); + return; + } + if(prog->opensearches[handle] == NULL) + { + VM_Warning("VM_search_end: no such handle %i in %s\n", handle, PRVM_NAME); + return; + } + + FS_FreeSearch(prog->opensearches[handle]); + prog->opensearches[handle] = NULL; + if(prog->opensearches_origin[handle]) + PRVM_Free((char *)prog->opensearches_origin[handle]); +} + +/* +========= +VM_search_getsize + +float search_getsize(float handle) +========= +*/ +void VM_search_getsize(void) +{ + int handle; + VM_SAFEPARMCOUNT(1, VM_M_search_getsize); + + handle = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) + { + VM_Warning("VM_search_getsize: invalid handle %i used in %s\n", handle, PRVM_NAME); + return; + } + if(prog->opensearches[handle] == NULL) + { + VM_Warning("VM_search_getsize: no such handle %i in %s\n", handle, PRVM_NAME); + return; + } + + PRVM_G_FLOAT(OFS_RETURN) = prog->opensearches[handle]->numfilenames; +} + +/* +========= +VM_search_getfilename + +string search_getfilename(float handle, float num) +========= +*/ +void VM_search_getfilename(void) +{ + int handle, filenum; + VM_SAFEPARMCOUNT(2, VM_search_getfilename); + + handle = (int)PRVM_G_FLOAT(OFS_PARM0); + filenum = (int)PRVM_G_FLOAT(OFS_PARM1); + + if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) + { + VM_Warning("VM_search_getfilename: invalid handle %i used in %s\n", handle, PRVM_NAME); + return; + } + if(prog->opensearches[handle] == NULL) + { + VM_Warning("VM_search_getfilename: no such handle %i in %s\n", handle, PRVM_NAME); + return; + } + if(filenum < 0 || filenum >= prog->opensearches[handle]->numfilenames) + { + VM_Warning("VM_search_getfilename: invalid filenum %i in %s\n", filenum, PRVM_NAME); + return; + } + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog->opensearches[handle]->filenames[filenum]); +} + +/* +========= +VM_chr + +string chr(float ascii) +========= +*/ +void VM_chr(void) +{ + /* + char tmp[2]; + VM_SAFEPARMCOUNT(1, VM_chr); + + tmp[0] = (unsigned char) PRVM_G_FLOAT(OFS_PARM0); + tmp[1] = 0; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(tmp); + */ + + char tmp[8]; + int len; + VM_SAFEPARMCOUNT(1, VM_chr); + + len = u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0), tmp, sizeof(tmp)); + tmp[len] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(tmp); +} + +//============================================================================= +// Draw builtins (client & menu) + +/* +========= +VM_iscachedpic + +float iscachedpic(string pic) +========= +*/ +void VM_iscachedpic(void) +{ + VM_SAFEPARMCOUNT(1,VM_iscachedpic); + + // drawq hasnt such a function, thus always return true + PRVM_G_FLOAT(OFS_RETURN) = false; +} + +/* +========= +VM_precache_pic + +string precache_pic(string pic) +========= +*/ +void VM_precache_pic(void) +{ + const char *s; + + VM_SAFEPARMCOUNT(1, VM_precache_pic); + + s = PRVM_G_STRING(OFS_PARM0); + PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); + VM_CheckEmptyString (s); + + // AK Draw_CachePic is supposed to always return a valid pointer + if( Draw_CachePic_Flags(s, 0)->tex == r_texture_notexture ) + PRVM_G_INT(OFS_RETURN) = OFS_NULL; +} + +/* +========= +VM_freepic + +freepic(string s) +========= +*/ +void VM_freepic(void) +{ + const char *s; + + VM_SAFEPARMCOUNT(1,VM_freepic); + + s = PRVM_G_STRING(OFS_PARM0); + VM_CheckEmptyString (s); + + Draw_FreePic(s); +} + +void getdrawfontscale(float *sx, float *sy) +{ + vec3_t v; + *sx = *sy = 1; + VectorCopy(PRVM_drawglobalvector(drawfontscale), v); + if(VectorLength2(v) > 0) + { + *sx = v[0]; + *sy = v[1]; + } +} + +dp_font_t *getdrawfont(void) +{ + int f = (int) PRVM_drawglobalfloat(drawfont); + if(f < 0 || f >= dp_fonts.maxsize) + return FONT_DEFAULT; + return &dp_fonts.f[f]; +} + +/* +========= +VM_drawcharacter + +float drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawcharacter(void) +{ + float *pos,*scale,*rgb; + char character; + int flag; + float sx, sy; + VM_SAFEPARMCOUNT(6,VM_drawcharacter); + + character = (char) PRVM_G_FLOAT(OFS_PARM1); + if(character == 0) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + VM_Warning("VM_drawcharacter: %s passed null character !\n",PRVM_NAME); + return; + } + + pos = PRVM_G_VECTOR(OFS_PARM0); + scale = PRVM_G_VECTOR(OFS_PARM2); + rgb = PRVM_G_VECTOR(OFS_PARM3); + flag = (int)PRVM_G_FLOAT(OFS_PARM5); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_drawcharacter: %s: wrong DRAWFLAG %i !\n",PRVM_NAME,flag); + return; + } + + if(pos[2] || scale[2]) + Con_Printf("VM_drawcharacter: z value%c from %s discarded\n",(pos[2] && scale[2]) ? 's' : 0,((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); + + if(!scale[0] || !scale[1]) + { + PRVM_G_FLOAT(OFS_RETURN) = -3; + VM_Warning("VM_drawcharacter: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); + return; + } + + getdrawfontscale(&sx, &sy); + DrawQ_String_Scale(pos[0], pos[1], &character, 1, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont()); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_drawstring + +float drawstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawstring(void) +{ + float *pos,*scale,*rgb; + const char *string; + int flag; + float sx, sy; + VM_SAFEPARMCOUNT(6,VM_drawstring); + + string = PRVM_G_STRING(OFS_PARM1); + pos = PRVM_G_VECTOR(OFS_PARM0); + scale = PRVM_G_VECTOR(OFS_PARM2); + rgb = PRVM_G_VECTOR(OFS_PARM3); + flag = (int)PRVM_G_FLOAT(OFS_PARM5); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_drawstring: %s: wrong DRAWFLAG %i !\n",PRVM_NAME,flag); + return; + } + + if(!scale[0] || !scale[1]) + { + PRVM_G_FLOAT(OFS_RETURN) = -3; + VM_Warning("VM_drawstring: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); + return; + } + + if(pos[2] || scale[2]) + Con_Printf("VM_drawstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); + + getdrawfontscale(&sx, &sy); + DrawQ_String_Scale(pos[0], pos[1], string, 0, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont()); + //Font_DrawString(pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_drawcolorcodedstring + +float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) +/ +float drawcolorcodedstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawcolorcodedstring(void) +{ + float *pos, *scale; + const char *string; + int flag; + vec3_t rgb; + float sx, sy, alpha; + + VM_SAFEPARMCOUNTRANGE(5,6,VM_drawcolorcodedstring); + + if (prog->argc == 6) // full 6 parms, like normal drawstring + { + pos = PRVM_G_VECTOR(OFS_PARM0); + string = PRVM_G_STRING(OFS_PARM1); + scale = PRVM_G_VECTOR(OFS_PARM2); + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), rgb); + alpha = PRVM_G_FLOAT(OFS_PARM4); + flag = (int)PRVM_G_FLOAT(OFS_PARM5); + } + else + { + pos = PRVM_G_VECTOR(OFS_PARM0); + string = PRVM_G_STRING(OFS_PARM1); + scale = PRVM_G_VECTOR(OFS_PARM2); + rgb[0] = 1.0; + rgb[1] = 1.0; + rgb[2] = 1.0; + alpha = PRVM_G_FLOAT(OFS_PARM3); + flag = (int)PRVM_G_FLOAT(OFS_PARM4); + } + + if(flag < DRAWFLAG_NORMAL || flag >= DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_drawcolorcodedstring: %s: wrong DRAWFLAG %i !\n",PRVM_NAME,flag); + return; + } + + if(!scale[0] || !scale[1]) + { + PRVM_G_FLOAT(OFS_RETURN) = -3; + VM_Warning("VM_drawcolorcodedstring: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); + return; + } + + if(pos[2] || scale[2]) + Con_Printf("VM_drawcolorcodedstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); + + getdrawfontscale(&sx, &sy); + DrawQ_String_Scale(pos[0], pos[1], string, 0, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], alpha, flag, NULL, false, getdrawfont()); + if (prog->argc == 6) // also return vector of last color + VectorCopy(DrawQ_Color, PRVM_G_VECTOR(OFS_RETURN)); + else + PRVM_G_FLOAT(OFS_RETURN) = 1; +} +/* +========= +VM_stringwidth + +float stringwidth(string text, float allowColorCodes, float size) +========= +*/ +void VM_stringwidth(void) +{ + const char *string; + float *szv; + float mult; // sz is intended font size so we can later add freetype support, mult is font size multiplier in pixels per character cell + int colors; + float sx, sy; + size_t maxlen = 0; + VM_SAFEPARMCOUNTRANGE(2,3,VM_drawstring); + + getdrawfontscale(&sx, &sy); + if(prog->argc == 3) + { + szv = PRVM_G_VECTOR(OFS_PARM2); + mult = 1; + } + else + { + // we want the width for 8x8 font size, divided by 8 + static float defsize[] = {8, 8}; + szv = defsize; + mult = 0.125; + // to make sure snapping is turned off, ALWAYS use a nontrivial scale in this case + if(sx >= 0.9 && sx <= 1.1) + { + mult *= 2; + sx /= 2; + sy /= 2; + } + } + + string = PRVM_G_STRING(OFS_PARM0); + colors = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth_UntilWidth_TrackColors_Scale(string, &maxlen, szv[0], szv[1], sx, sy, NULL, !colors, getdrawfont(), 1000000000) * mult; +/* + if(prog->argc == 3) + { + mult = sz = PRVM_G_FLOAT(OFS_PARM2); + } + else + { + sz = 8; + mult = 1; + } + + string = PRVM_G_STRING(OFS_PARM0); + colors = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth(string, 0, !colors, getdrawfont()) * mult; // 1x1 characters, don't actually draw +*/ +} + +/* +========= +VM_findfont + +float findfont(string s) +========= +*/ + +float getdrawfontnum(const char *fontname) +{ + int i; + + for(i = 0; i < dp_fonts.maxsize; ++i) + if(!strcmp(dp_fonts.f[i].title, fontname)) + return i; + return -1; +} + +void VM_findfont(void) +{ + VM_SAFEPARMCOUNT(1,VM_findfont); + PRVM_G_FLOAT(OFS_RETURN) = getdrawfontnum(PRVM_G_STRING(OFS_PARM0)); +} + +/* +========= +VM_loadfont + +float loadfont(string fontname, string fontmaps, string sizes, float slot) +========= +*/ + +dp_font_t *FindFont(const char *title, qboolean allocate_new); +void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset); +void VM_loadfont(void) +{ + const char *fontname, *filelist, *sizes, *c, *cm; + char mainfont[MAX_QPATH]; + int i, numsizes; + float sz, scale, voffset; + dp_font_t *f; + + VM_SAFEPARMCOUNTRANGE(3,6,VM_loadfont); + + fontname = PRVM_G_STRING(OFS_PARM0); + if (!fontname[0]) + fontname = "default"; + + filelist = PRVM_G_STRING(OFS_PARM1); + if (!filelist[0]) + filelist = "gfx/conchars"; + + sizes = PRVM_G_STRING(OFS_PARM2); + if (!sizes[0]) + sizes = "10"; + + // find a font + f = NULL; + if (prog->argc >= 4) + { + i = PRVM_G_FLOAT(OFS_PARM3); + if (i >= 0 && i < dp_fonts.maxsize) + { + f = &dp_fonts.f[i]; + strlcpy(f->title, fontname, sizeof(f->title)); // replace name + } + } + if (!f) + f = FindFont(fontname, true); + if (!f) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; // something go wrong + } + + memset(f->fallbacks, 0, sizeof(f->fallbacks)); + memset(f->fallback_faces, 0, sizeof(f->fallback_faces)); + + // first font is handled "normally" + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->req_face = atoi(c+1); + else + { + f->req_face = 0; + c = cm; + } + if(!c || (c - filelist) > MAX_QPATH) + strlcpy(mainfont, filelist, sizeof(mainfont)); + else + { + memcpy(mainfont, filelist, c - filelist); + mainfont[c - filelist] = 0; + } + + // handle fallbacks + for(i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + c = strchr(filelist, ','); + if(!c) + break; + filelist = c + 1; + if(!*filelist) + break; + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->fallback_faces[i] = atoi(c+1); + else + { + f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index + c = cm; + } + if(!c || (c-filelist) > MAX_QPATH) + { + strlcpy(f->fallbacks[i], filelist, sizeof(mainfont)); + } + else + { + memcpy(f->fallbacks[i], filelist, c - filelist); + f->fallbacks[i][c - filelist] = 0; + } + } + + // handle sizes + for(i = 0; i < MAX_FONT_SIZES; ++i) + f->req_sizes[i] = -1; + for (numsizes = 0,c = sizes;;) + { + if (!COM_ParseToken_VM_Tokenize(&c, 0)) + break; + sz = atof(com_token); + // detect crap size + if (sz < 0.001f || sz > 1000.0f) + { + VM_Warning("VM_loadfont: crap size %s", com_token); + continue; + } + // check overflow + if (numsizes == MAX_FONT_SIZES) + { + VM_Warning("VM_loadfont: MAX_FONT_SIZES = %i exceeded", MAX_FONT_SIZES); + break; + } + f->req_sizes[numsizes] = sz; + numsizes++; + } + + // additional scale/hoffset parms + scale = 1; + voffset = 0; + if (prog->argc >= 5) + { + scale = PRVM_G_FLOAT(OFS_PARM4); + if (scale <= 0) + scale = 1; + } + if (prog->argc >= 6) + voffset = PRVM_G_FLOAT(OFS_PARM5); + + // load + LoadFont(true, mainfont, f, scale, voffset); + + // return index of loaded font + PRVM_G_FLOAT(OFS_RETURN) = (f - dp_fonts.f); +} + +/* +========= +VM_drawpic + +float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawpic(void) +{ + const char *picname; + float *size, *pos, *rgb; + int flag; + + VM_SAFEPARMCOUNT(6,VM_drawpic); + + picname = PRVM_G_STRING(OFS_PARM1); + VM_CheckEmptyString (picname); + + // is pic cached ? no function yet for that + if(!1) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning("VM_drawpic: %s: %s not cached !\n", PRVM_NAME, picname); + return; + } + + pos = PRVM_G_VECTOR(OFS_PARM0); + size = PRVM_G_VECTOR(OFS_PARM2); + rgb = PRVM_G_VECTOR(OFS_PARM3); + flag = (int) PRVM_G_FLOAT(OFS_PARM5); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_drawpic: %s: wrong DRAWFLAG %i !\n",PRVM_NAME,flag); + return; + } + + if(pos[2] || size[2]) + Con_Printf("VM_drawpic: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); + + DrawQ_Pic(pos[0], pos[1], Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT), size[0], size[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} +/* +========= +VM_drawrotpic + +float drawrotpic(vector position, string pic, vector size, vector org, float angle, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawrotpic(void) +{ + const char *picname; + float *size, *pos, *org, *rgb; + int flag; + + VM_SAFEPARMCOUNT(8,VM_drawrotpic); + + picname = PRVM_G_STRING(OFS_PARM1); + VM_CheckEmptyString (picname); + + // is pic cached ? no function yet for that + if(!1) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning("VM_drawrotpic: %s: %s not cached !\n", PRVM_NAME, picname); + return; + } + + pos = PRVM_G_VECTOR(OFS_PARM0); + size = PRVM_G_VECTOR(OFS_PARM2); + org = PRVM_G_VECTOR(OFS_PARM3); + rgb = PRVM_G_VECTOR(OFS_PARM5); + flag = (int) PRVM_G_FLOAT(OFS_PARM7); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_drawrotpic: %s: wrong DRAWFLAG %i !\n",PRVM_NAME,flag); + return; + } + + if(pos[2] || size[2] || org[2]) + Con_Printf("VM_drawrotpic: z value from pos/size/org discarded\n"); + + DrawQ_RotPic(pos[0], pos[1], Draw_CachePic_Flags(picname, CACHEPICFLAG_NOTPERSISTENT), size[0], size[1], org[0], org[1], PRVM_G_FLOAT(OFS_PARM4), rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM6), flag); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} +/* +========= +VM_drawsubpic + +float drawsubpic(vector position, vector size, string pic, vector srcPos, vector srcSize, vector rgb, float alpha, float flag) + +========= +*/ +void VM_drawsubpic(void) +{ + const char *picname; + float *size, *pos, *rgb, *srcPos, *srcSize, alpha; + int flag; + + VM_SAFEPARMCOUNT(8,VM_drawsubpic); + + picname = PRVM_G_STRING(OFS_PARM2); + VM_CheckEmptyString (picname); + + // is pic cached ? no function yet for that + if(!1) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning("VM_drawsubpic: %s: %s not cached !\n", PRVM_NAME, picname); + return; + } + + pos = PRVM_G_VECTOR(OFS_PARM0); + size = PRVM_G_VECTOR(OFS_PARM1); + srcPos = PRVM_G_VECTOR(OFS_PARM3); + srcSize = PRVM_G_VECTOR(OFS_PARM4); + rgb = PRVM_G_VECTOR(OFS_PARM5); + alpha = PRVM_G_FLOAT(OFS_PARM6); + flag = (int) PRVM_G_FLOAT(OFS_PARM7); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_drawsubpic: %s: wrong DRAWFLAG %i !\n",PRVM_NAME,flag); + return; + } + + if(pos[2] || size[2]) + Con_Printf("VM_drawsubpic: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); + + DrawQ_SuperPic(pos[0], pos[1], Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT), + size[0], size[1], + srcPos[0], srcPos[1], rgb[0], rgb[1], rgb[2], alpha, + srcPos[0] + srcSize[0], srcPos[1], rgb[0], rgb[1], rgb[2], alpha, + srcPos[0], srcPos[1] + srcSize[1], rgb[0], rgb[1], rgb[2], alpha, + srcPos[0] + srcSize[0], srcPos[1] + srcSize[1], rgb[0], rgb[1], rgb[2], alpha, + flag); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_drawfill + +float drawfill(vector position, vector size, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawfill(void) +{ + float *size, *pos, *rgb; + int flag; + + VM_SAFEPARMCOUNT(5,VM_drawfill); + + + pos = PRVM_G_VECTOR(OFS_PARM0); + size = PRVM_G_VECTOR(OFS_PARM1); + rgb = PRVM_G_VECTOR(OFS_PARM2); + flag = (int) PRVM_G_FLOAT(OFS_PARM4); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning("VM_drawfill: %s: wrong DRAWFLAG %i !\n",PRVM_NAME,flag); + return; + } + + if(pos[2] || size[2]) + Con_Printf("VM_drawfill: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); + + DrawQ_Fill(pos[0], pos[1], size[0], size[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM3), flag); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_drawsetcliparea + +drawsetcliparea(float x, float y, float width, float height) +========= +*/ +void VM_drawsetcliparea(void) +{ + float x,y,w,h; + VM_SAFEPARMCOUNT(4,VM_drawsetcliparea); + + x = bound(0, PRVM_G_FLOAT(OFS_PARM0), vid_conwidth.integer); + y = bound(0, PRVM_G_FLOAT(OFS_PARM1), vid_conheight.integer); + w = bound(0, PRVM_G_FLOAT(OFS_PARM2) + PRVM_G_FLOAT(OFS_PARM0) - x, (vid_conwidth.integer - x)); + h = bound(0, PRVM_G_FLOAT(OFS_PARM3) + PRVM_G_FLOAT(OFS_PARM1) - y, (vid_conheight.integer - y)); + + DrawQ_SetClipArea(x, y, w, h); +} + +/* +========= +VM_drawresetcliparea + +drawresetcliparea() +========= +*/ +void VM_drawresetcliparea(void) +{ + VM_SAFEPARMCOUNT(0,VM_drawresetcliparea); + + DrawQ_ResetClipArea(); +} + +/* +========= +VM_getimagesize + +vector getimagesize(string pic) +========= +*/ +void VM_getimagesize(void) +{ + const char *p; + cachepic_t *pic; + + VM_SAFEPARMCOUNT(1,VM_getimagesize); + + p = PRVM_G_STRING(OFS_PARM0); + VM_CheckEmptyString (p); + + pic = Draw_CachePic_Flags (p, CACHEPICFLAG_NOTPERSISTENT); + + PRVM_G_VECTOR(OFS_RETURN)[0] = pic->width; + PRVM_G_VECTOR(OFS_RETURN)[1] = pic->height; + PRVM_G_VECTOR(OFS_RETURN)[2] = 0; +} + +/* +========= +VM_keynumtostring + +string keynumtostring(float keynum) +========= +*/ +void VM_keynumtostring (void) +{ + VM_SAFEPARMCOUNT(1, VM_keynumtostring); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(Key_KeynumToString((int)PRVM_G_FLOAT(OFS_PARM0))); +} + +/* +========= +VM_findkeysforcommand + +string findkeysforcommand(string command, float bindmap) + +the returned string is an altstring +========= +*/ +#define FKFC_NUMKEYS 5 +void M_FindKeysForCommand(const char *command, int *keys); +void VM_findkeysforcommand(void) +{ + const char *cmd; + char ret[VM_STRINGTEMP_LENGTH]; + int keys[FKFC_NUMKEYS]; + int i; + int bindmap; + + VM_SAFEPARMCOUNTRANGE(1, 2, VM_findkeysforcommand); + + cmd = PRVM_G_STRING(OFS_PARM0); + if(prog->argc == 2) + bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM1), MAX_BINDMAPS-1); + else + bindmap = 0; // consistent to "bind" + + VM_CheckEmptyString(cmd); + + Key_FindKeysForCommand(cmd, keys, FKFC_NUMKEYS, bindmap); + + ret[0] = 0; + for(i = 0; i < FKFC_NUMKEYS; i++) + strlcat(ret, va(" \'%i\'", keys[i]), sizeof(ret)); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(ret); +} + +/* +========= +VM_stringtokeynum + +float stringtokeynum(string key) +========= +*/ +void VM_stringtokeynum (void) +{ + VM_SAFEPARMCOUNT( 1, VM_keynumtostring ); + + PRVM_G_FLOAT(OFS_RETURN) = Key_StringToKeynum(PRVM_G_STRING(OFS_PARM0)); +} + +/* +========= +VM_getkeybind + +string getkeybind(float key, float bindmap) +========= +*/ +void VM_getkeybind (void) +{ + int bindmap; + VM_SAFEPARMCOUNTRANGE(1, 2, VM_CL_getkeybind); + if(prog->argc == 2) + bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM1), MAX_BINDMAPS-1); + else + bindmap = 0; // consistent to "bind" + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(Key_GetBind((int)PRVM_G_FLOAT(OFS_PARM0), bindmap)); +} + +/* +========= +VM_setkeybind + +float setkeybind(float key, string cmd, float bindmap) +========= +*/ +void VM_setkeybind (void) +{ + int bindmap; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_setkeybind); + if(prog->argc == 3) + bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM2), MAX_BINDMAPS-1); + else + bindmap = 0; // consistent to "bind" + + PRVM_G_FLOAT(OFS_RETURN) = 0; + if(Key_SetBinding((int)PRVM_G_FLOAT(OFS_PARM0), bindmap, PRVM_G_STRING(OFS_PARM1))) + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_getbindmap + +vector getbindmaps() +========= +*/ +void VM_getbindmaps (void) +{ + int fg, bg; + VM_SAFEPARMCOUNT(0, VM_CL_getbindmap); + Key_GetBindMap(&fg, &bg); + PRVM_G_VECTOR(OFS_RETURN)[0] = fg; + PRVM_G_VECTOR(OFS_RETURN)[1] = bg; + PRVM_G_VECTOR(OFS_RETURN)[2] = 0; +} + +/* +========= +VM_setbindmap + +float setbindmaps(vector bindmap) +========= +*/ +void VM_setbindmaps (void) +{ + VM_SAFEPARMCOUNT(1, VM_CL_setbindmap); + PRVM_G_FLOAT(OFS_RETURN) = 0; + if(PRVM_G_VECTOR(OFS_PARM0)[2] == 0) + if(Key_SetBindMap((int)PRVM_G_VECTOR(OFS_PARM0)[0], (int)PRVM_G_VECTOR(OFS_PARM0)[1])) + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +// CL_Video interface functions + +/* +======================== +VM_cin_open + +float cin_open(string file, string name) +======================== +*/ +void VM_cin_open( void ) +{ + const char *file; + const char *name; + + VM_SAFEPARMCOUNT( 2, VM_cin_open ); + + file = PRVM_G_STRING( OFS_PARM0 ); + name = PRVM_G_STRING( OFS_PARM1 ); + + VM_CheckEmptyString( file ); + VM_CheckEmptyString( name ); + + if( CL_OpenVideo( file, name, MENUOWNER, "" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = 1; + else + PRVM_G_FLOAT( OFS_RETURN ) = 0; +} + +/* +======================== +VM_cin_close + +void cin_close(string name) +======================== +*/ +void VM_cin_close( void ) +{ + const char *name; + + VM_SAFEPARMCOUNT( 1, VM_cin_close ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + + CL_CloseVideo( CL_GetVideoByName( name ) ); +} + +/* +======================== +VM_cin_setstate +void cin_setstate(string name, float type) +======================== +*/ +void VM_cin_setstate( void ) +{ + const char *name; + clvideostate_t state; + clvideo_t *video; + + VM_SAFEPARMCOUNT( 2, VM_cin_netstate ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + + state = (clvideostate_t)((int)PRVM_G_FLOAT( OFS_PARM1 )); + + video = CL_GetVideoByName( name ); + if( video && state > CLVIDEO_UNUSED && state < CLVIDEO_STATECOUNT ) + CL_SetVideoState( video, state ); +} + +/* +======================== +VM_cin_getstate + +float cin_getstate(string name) +======================== +*/ +void VM_cin_getstate( void ) +{ + const char *name; + clvideo_t *video; + + VM_SAFEPARMCOUNT( 1, VM_cin_getstate ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + + video = CL_GetVideoByName( name ); + if( video ) + PRVM_G_FLOAT( OFS_RETURN ) = (int)video->state; + else + PRVM_G_FLOAT( OFS_RETURN ) = 0; +} + +/* +======================== +VM_cin_restart + +void cin_restart(string name) +======================== +*/ +void VM_cin_restart( void ) +{ + const char *name; + clvideo_t *video; + + VM_SAFEPARMCOUNT( 1, VM_cin_restart ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + + video = CL_GetVideoByName( name ); + if( video ) + CL_RestartVideo( video ); +} + +/* +======================== +VM_Gecko_Init +======================== +*/ +void VM_Gecko_Init( void ) { + // the prog struct is memset to 0 by Initprog? [12/6/2007 Black] + // FIXME: remove the other _Init functions then, too? [12/6/2007 Black] +} + +/* +======================== +VM_Gecko_Destroy +======================== +*/ +void VM_Gecko_Destroy( void ) { + int i; + for( i = 0 ; i < PRVM_MAX_GECKOINSTANCES ; i++ ) { + clgecko_t **instance = &prog->opengeckoinstances[ i ]; + if( *instance ) { + CL_Gecko_DestroyBrowser( *instance ); + } + *instance = NULL; + } +} + +/* +======================== +VM_gecko_create + +float[bool] gecko_create( string name ) +======================== +*/ +void VM_gecko_create( void ) { + const char *name; + int i; + clgecko_t *instance; + + VM_SAFEPARMCOUNT( 1, VM_gecko_create ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + + // find an empty slot for this gecko browser.. + for( i = 0 ; i < PRVM_MAX_GECKOINSTANCES ; i++ ) { + if( prog->opengeckoinstances[ i ] == NULL ) { + break; + } + } + if( i == PRVM_MAX_GECKOINSTANCES ) { + VM_Warning("VM_gecko_create: %s ran out of gecko handles (%i)\n", PRVM_NAME, PRVM_MAX_GECKOINSTANCES); + PRVM_G_FLOAT( OFS_RETURN ) = 0; + return; + } + + instance = prog->opengeckoinstances[ i ] = CL_Gecko_CreateBrowser( name, PRVM_GetProgNr() ); + if( !instance ) { + // TODO: error handling [12/3/2007 Black] + PRVM_G_FLOAT( OFS_RETURN ) = 0; + return; + } + PRVM_G_FLOAT( OFS_RETURN ) = 1; +} + +/* +======================== +VM_gecko_destroy + +void gecko_destroy( string name ) +======================== +*/ +void VM_gecko_destroy( void ) { + const char *name; + clgecko_t *instance; + + VM_SAFEPARMCOUNT( 1, VM_gecko_destroy ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + instance = CL_Gecko_FindBrowser( name ); + if( !instance ) { + return; + } + CL_Gecko_DestroyBrowser( instance ); +} + +/* +======================== +VM_gecko_navigate + +void gecko_navigate( string name, string URI ) +======================== +*/ +void VM_gecko_navigate( void ) { + const char *name; + const char *URI; + clgecko_t *instance; + + VM_SAFEPARMCOUNT( 2, VM_gecko_navigate ); + + name = PRVM_G_STRING( OFS_PARM0 ); + URI = PRVM_G_STRING( OFS_PARM1 ); + VM_CheckEmptyString( name ); + VM_CheckEmptyString( URI ); + + instance = CL_Gecko_FindBrowser( name ); + if( !instance ) { + return; + } + CL_Gecko_NavigateToURI( instance, URI ); +} + +/* +======================== +VM_gecko_keyevent + +float[bool] gecko_keyevent( string name, float key, float eventtype ) +======================== +*/ +void VM_gecko_keyevent( void ) { + const char *name; + unsigned int key; + clgecko_buttoneventtype_t eventtype; + clgecko_t *instance; + + VM_SAFEPARMCOUNT( 3, VM_gecko_keyevent ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + key = (unsigned int) PRVM_G_FLOAT( OFS_PARM1 ); + switch( (unsigned int) PRVM_G_FLOAT( OFS_PARM2 ) ) { + case 0: + eventtype = CLG_BET_DOWN; + break; + case 1: + eventtype = CLG_BET_UP; + break; + case 2: + eventtype = CLG_BET_PRESS; + break; + case 3: + eventtype = CLG_BET_DOUBLECLICK; + break; + default: + // TODO: console printf? [12/3/2007 Black] + PRVM_G_FLOAT( OFS_RETURN ) = 0; + return; + } + + instance = CL_Gecko_FindBrowser( name ); + if( !instance ) { + PRVM_G_FLOAT( OFS_RETURN ) = 0; + return; + } + + PRVM_G_FLOAT( OFS_RETURN ) = (CL_Gecko_Event_Key( instance, (keynum_t) key, eventtype ) == true); +} + +/* +======================== +VM_gecko_movemouse + +void gecko_mousemove( string name, float x, float y ) +======================== +*/ +void VM_gecko_movemouse( void ) { + const char *name; + float x, y; + clgecko_t *instance; + + VM_SAFEPARMCOUNT( 3, VM_gecko_movemouse ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + x = PRVM_G_FLOAT( OFS_PARM1 ); + y = PRVM_G_FLOAT( OFS_PARM2 ); + + instance = CL_Gecko_FindBrowser( name ); + if( !instance ) { + return; + } + CL_Gecko_Event_CursorMove( instance, x, y ); +} + + +/* +======================== +VM_gecko_resize + +void gecko_resize( string name, float w, float h ) +======================== +*/ +void VM_gecko_resize( void ) { + const char *name; + float w, h; + clgecko_t *instance; + + VM_SAFEPARMCOUNT( 3, VM_gecko_movemouse ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + w = PRVM_G_FLOAT( OFS_PARM1 ); + h = PRVM_G_FLOAT( OFS_PARM2 ); + + instance = CL_Gecko_FindBrowser( name ); + if( !instance ) { + return; + } + CL_Gecko_Resize( instance, (int) w, (int) h ); +} + + +/* +======================== +VM_gecko_get_texture_extent + +vector gecko_get_texture_extent( string name ) +======================== +*/ +void VM_gecko_get_texture_extent( void ) { + const char *name; + clgecko_t *instance; + + VM_SAFEPARMCOUNT( 1, VM_gecko_movemouse ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( name ); + + PRVM_G_VECTOR(OFS_RETURN)[2] = 0; + instance = CL_Gecko_FindBrowser( name ); + if( !instance ) { + PRVM_G_VECTOR(OFS_RETURN)[0] = 0; + PRVM_G_VECTOR(OFS_RETURN)[1] = 0; + return; + } + CL_Gecko_GetTextureExtent( instance, + PRVM_G_VECTOR(OFS_RETURN), PRVM_G_VECTOR(OFS_RETURN)+1 ); +} + + + +/* +============== +VM_makevectors + +Writes new values for v_forward, v_up, and v_right based on angles +void makevectors(vector angle) +============== +*/ +void VM_makevectors (void) +{ + VM_SAFEPARMCOUNT(1, VM_makevectors); + AngleVectors(PRVM_G_VECTOR(OFS_PARM0), PRVM_gameglobalvector(v_forward), PRVM_gameglobalvector(v_right), PRVM_gameglobalvector(v_up)); +} + +/* +============== +VM_vectorvectors + +Writes new values for v_forward, v_up, and v_right based on the given forward vector +vectorvectors(vector) +============== +*/ +void VM_vectorvectors (void) +{ + VM_SAFEPARMCOUNT(1, VM_vectorvectors); + VectorNormalize2(PRVM_G_VECTOR(OFS_PARM0), PRVM_gameglobalvector(v_forward)); + VectorVectors(PRVM_gameglobalvector(v_forward), PRVM_gameglobalvector(v_right), PRVM_gameglobalvector(v_up)); +} + +/* +======================== +VM_drawline + +void drawline(float width, vector pos1, vector pos2, vector rgb, float alpha, float flags) +======================== +*/ +void VM_drawline (void) +{ + float *c1, *c2, *rgb; + float alpha, width; + unsigned char flags; + + VM_SAFEPARMCOUNT(6, VM_drawline); + width = PRVM_G_FLOAT(OFS_PARM0); + c1 = PRVM_G_VECTOR(OFS_PARM1); + c2 = PRVM_G_VECTOR(OFS_PARM2); + rgb = PRVM_G_VECTOR(OFS_PARM3); + alpha = PRVM_G_FLOAT(OFS_PARM4); + flags = (int)PRVM_G_FLOAT(OFS_PARM5); + DrawQ_Line(width, c1[0], c1[1], c2[0], c2[1], rgb[0], rgb[1], rgb[2], alpha, flags); +} + +// float(float number, float quantity) bitshift (EXT_BITSHIFT) +void VM_bitshift (void) +{ + int n1, n2; + VM_SAFEPARMCOUNT(2, VM_bitshift); + + n1 = (int)fabs((float)((int)PRVM_G_FLOAT(OFS_PARM0))); + n2 = (int)PRVM_G_FLOAT(OFS_PARM1); + if(!n1) + PRVM_G_FLOAT(OFS_RETURN) = n1; + else + if(n2 < 0) + PRVM_G_FLOAT(OFS_RETURN) = (n1 >> -n2); + else + PRVM_G_FLOAT(OFS_RETURN) = (n1 << n2); +} + +//////////////////////////////////////// +// AltString functions +//////////////////////////////////////// + +/* +======================== +VM_altstr_count + +float altstr_count(string) +======================== +*/ +void VM_altstr_count( void ) +{ + const char *altstr, *pos; + int count; + + VM_SAFEPARMCOUNT( 1, VM_altstr_count ); + + altstr = PRVM_G_STRING( OFS_PARM0 ); + //VM_CheckEmptyString( altstr ); + + for( count = 0, pos = altstr ; *pos ; pos++ ) { + if( *pos == '\\' ) { + if( !*++pos ) { + break; + } + } else if( *pos == '\'' ) { + count++; + } + } + + PRVM_G_FLOAT( OFS_RETURN ) = (float) (count / 2); +} + +/* +======================== +VM_altstr_prepare + +string altstr_prepare(string) +======================== +*/ +void VM_altstr_prepare( void ) +{ + char *out; + const char *instr, *in; + int size; + char outstr[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT( 1, VM_altstr_prepare ); + + instr = PRVM_G_STRING( OFS_PARM0 ); + + for( out = outstr, in = instr, size = sizeof(outstr) - 1 ; size && *in ; size--, in++, out++ ) + if( *in == '\'' ) { + *out++ = '\\'; + *out = '\''; + size--; + } else + *out = *in; + *out = 0; + + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( outstr ); +} + +/* +======================== +VM_altstr_get + +string altstr_get(string, float) +======================== +*/ +void VM_altstr_get( void ) +{ + const char *altstr, *pos; + char *out; + int count, size; + char outstr[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT( 2, VM_altstr_get ); + + altstr = PRVM_G_STRING( OFS_PARM0 ); + + count = (int)PRVM_G_FLOAT( OFS_PARM1 ); + count = count * 2 + 1; + + for( pos = altstr ; *pos && count ; pos++ ) + if( *pos == '\\' ) { + if( !*++pos ) + break; + } else if( *pos == '\'' ) + count--; + + if( !*pos ) { + PRVM_G_INT( OFS_RETURN ) = 0; + return; + } + + for( out = outstr, size = sizeof(outstr) - 1 ; size && *pos ; size--, pos++, out++ ) + if( *pos == '\\' ) { + if( !*++pos ) + break; + *out = *pos; + size--; + } else if( *pos == '\'' ) + break; + else + *out = *pos; + + *out = 0; + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( outstr ); +} + +/* +======================== +VM_altstr_set + +string altstr_set(string altstr, float num, string set) +======================== +*/ +void VM_altstr_set( void ) +{ + int num; + const char *altstr, *str; + const char *in; + char *out; + char outstr[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT( 3, VM_altstr_set ); + + altstr = PRVM_G_STRING( OFS_PARM0 ); + + num = (int)PRVM_G_FLOAT( OFS_PARM1 ); + + str = PRVM_G_STRING( OFS_PARM2 ); + + out = outstr; + for( num = num * 2 + 1, in = altstr; *in && num; *out++ = *in++ ) + if( *in == '\\' ) { + if( !*++in ) { + break; + } + } else if( *in == '\'' ) { + num--; + } + + // copy set in + for( ; *str; *out++ = *str++ ); + // now jump over the old content + for( ; *in ; in++ ) + if( *in == '\'' || (*in == '\\' && !*++in) ) + break; + + strlcpy(out, in, outstr + sizeof(outstr) - out); + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( outstr ); +} + +/* +======================== +VM_altstr_ins +insert after num +string altstr_ins(string altstr, float num, string set) +======================== +*/ +void VM_altstr_ins(void) +{ + int num; + const char *set; + const char *in; + char *out; + char outstr[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT(3, VM_altstr_ins); + + in = PRVM_G_STRING( OFS_PARM0 ); + num = (int)PRVM_G_FLOAT( OFS_PARM1 ); + set = PRVM_G_STRING( OFS_PARM2 ); + + out = outstr; + for( num = num * 2 + 2 ; *in && num > 0 ; *out++ = *in++ ) + if( *in == '\\' ) { + if( !*++in ) { + break; + } + } else if( *in == '\'' ) { + num--; + } + + *out++ = '\''; + for( ; *set ; *out++ = *set++ ); + *out++ = '\''; + + strlcpy(out, in, outstr + sizeof(outstr) - out); + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( outstr ); +} + + +//////////////////////////////////////// +// BufString functions +//////////////////////////////////////// +//[515]: string buffers support + +static size_t stringbuffers_sortlength; + +static void BufStr_Expand(prvm_stringbuffer_t *stringbuffer, int strindex) +{ + if (stringbuffer->max_strings <= strindex) + { + char **oldstrings = stringbuffer->strings; + stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128); + while (stringbuffer->max_strings <= strindex) + stringbuffer->max_strings *= 2; + stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0])); + if (stringbuffer->num_strings > 0) + memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0])); + if (oldstrings) + Mem_Free(oldstrings); + } +} + +static void BufStr_Shrink(prvm_stringbuffer_t *stringbuffer) +{ + // reduce num_strings if there are empty string slots at the end + while (stringbuffer->num_strings > 0 && stringbuffer->strings[stringbuffer->num_strings - 1] == NULL) + stringbuffer->num_strings--; + + // if empty, free the string pointer array + if (stringbuffer->num_strings == 0) + { + stringbuffer->max_strings = 0; + if (stringbuffer->strings) + Mem_Free(stringbuffer->strings); + stringbuffer->strings = NULL; + } +} + +static int BufStr_SortStringsUP (const void *in1, const void *in2) +{ + const char *a, *b; + a = *((const char **) in1); + b = *((const char **) in2); + if(!a || !a[0]) return 1; + if(!b || !b[0]) return -1; + return strncmp(a, b, stringbuffers_sortlength); +} + +static int BufStr_SortStringsDOWN (const void *in1, const void *in2) +{ + const char *a, *b; + a = *((const char **) in1); + b = *((const char **) in2); + if(!a || !a[0]) return 1; + if(!b || !b[0]) return -1; + return strncmp(b, a, stringbuffers_sortlength); +} + +/* +======================== +VM_buf_create +creates new buffer, and returns it's index, returns -1 if failed +float buf_create(void) = #460; +float newbuf(string format, float flags) = #460; +======================== +*/ + +void VM_buf_create (void) +{ + prvm_stringbuffer_t *stringbuffer; + int i; + + VM_SAFEPARMCOUNTRANGE(0, 2, VM_buf_create); + + // VorteX: optional parm1 (buffer format) is unfinished, to keep intact with future databuffers extension must be set to "string" + if(prog->argc >= 1 && strcmp(PRVM_G_STRING(OFS_PARM0), "string")) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; + } + stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecord(&prog->stringbuffersarray); + for (i = 0;stringbuffer != Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);i++); + stringbuffer->origin = PRVM_AllocationOrigin(); + // optional flags parm + if (prog->argc >= 2) + stringbuffer->flags = (int)PRVM_G_FLOAT(OFS_PARM1) & 0xFF; + PRVM_G_FLOAT(OFS_RETURN) = i; +} + + + +/* +======================== +VM_buf_del +deletes buffer and all strings in it +void buf_del(float bufhandle) = #461; +======================== +*/ +void VM_buf_del (void) +{ + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNT(1, VM_buf_del); + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if (stringbuffer) + { + int i; + for (i = 0;i < stringbuffer->num_strings;i++) + if (stringbuffer->strings[i]) + Mem_Free(stringbuffer->strings[i]); + if (stringbuffer->strings) + Mem_Free(stringbuffer->strings); + if(stringbuffer->origin) + PRVM_Free((char *)stringbuffer->origin); + Mem_ExpandableArray_FreeRecord(&prog->stringbuffersarray, stringbuffer); + } + else + { + VM_Warning("VM_buf_del: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } +} + +/* +======================== +VM_buf_getsize +how many strings are stored in buffer +float buf_getsize(float bufhandle) = #462; +======================== +*/ +void VM_buf_getsize (void) +{ + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNT(1, VM_buf_getsize); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + VM_Warning("VM_buf_getsize: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + else + PRVM_G_FLOAT(OFS_RETURN) = stringbuffer->num_strings; +} + +/* +======================== +VM_buf_copy +copy all content from one buffer to another, make sure it exists +void buf_copy(float bufhandle_from, float bufhandle_to) = #463; +======================== +*/ +void VM_buf_copy (void) +{ + prvm_stringbuffer_t *srcstringbuffer, *dststringbuffer; + int i; + VM_SAFEPARMCOUNT(2, VM_buf_copy); + + srcstringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!srcstringbuffer) + { + VM_Warning("VM_buf_copy: invalid source buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + i = (int)PRVM_G_FLOAT(OFS_PARM1); + if(i == (int)PRVM_G_FLOAT(OFS_PARM0)) + { + VM_Warning("VM_buf_copy: source == destination (%i) in %s\n", i, PRVM_NAME); + return; + } + dststringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!dststringbuffer) + { + VM_Warning("VM_buf_copy: invalid destination buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), PRVM_NAME); + return; + } + + for (i = 0;i < dststringbuffer->num_strings;i++) + if (dststringbuffer->strings[i]) + Mem_Free(dststringbuffer->strings[i]); + if (dststringbuffer->strings) + Mem_Free(dststringbuffer->strings); + *dststringbuffer = *srcstringbuffer; + if (dststringbuffer->max_strings) + dststringbuffer->strings = (char **)Mem_Alloc(prog->progs_mempool, sizeof(dststringbuffer->strings[0]) * dststringbuffer->max_strings); + + for (i = 0;i < dststringbuffer->num_strings;i++) + { + if (srcstringbuffer->strings[i]) + { + size_t stringlen; + stringlen = strlen(srcstringbuffer->strings[i]) + 1; + dststringbuffer->strings[i] = (char *)Mem_Alloc(prog->progs_mempool, stringlen); + memcpy(dststringbuffer->strings[i], srcstringbuffer->strings[i], stringlen); + } + } +} + +/* +======================== +VM_buf_sort +sort buffer by beginnings of strings (cmplength defaults it's length) +"backward == TRUE" means that sorting goes upside-down +void buf_sort(float bufhandle, float cmplength, float backward) = #464; +======================== +*/ +void VM_buf_sort (void) +{ + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNT(3, VM_buf_sort); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning("VM_buf_sort: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + if(stringbuffer->num_strings <= 0) + { + VM_Warning("VM_buf_sort: tried to sort empty buffer %i in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + stringbuffers_sortlength = (int)PRVM_G_FLOAT(OFS_PARM1); + if(stringbuffers_sortlength <= 0) + stringbuffers_sortlength = 0x7FFFFFFF; + + if(!PRVM_G_FLOAT(OFS_PARM2)) + qsort(stringbuffer->strings, stringbuffer->num_strings, sizeof(char*), BufStr_SortStringsUP); + else + qsort(stringbuffer->strings, stringbuffer->num_strings, sizeof(char*), BufStr_SortStringsDOWN); + + BufStr_Shrink(stringbuffer); +} + +/* +======================== +VM_buf_implode +concantenates all buffer string into one with "glue" separator and returns it as tempstring +string buf_implode(float bufhandle, string glue) = #465; +======================== +*/ +void VM_buf_implode (void) +{ + prvm_stringbuffer_t *stringbuffer; + char k[VM_STRINGTEMP_LENGTH]; + const char *sep; + int i; + size_t l; + VM_SAFEPARMCOUNT(2, VM_buf_implode); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + if(!stringbuffer) + { + VM_Warning("VM_buf_implode: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + if(!stringbuffer->num_strings) + return; + sep = PRVM_G_STRING(OFS_PARM1); + k[0] = 0; + for(l = i = 0;i < stringbuffer->num_strings;i++) + { + if(stringbuffer->strings[i]) + { + l += (i > 0 ? strlen(sep) : 0) + strlen(stringbuffer->strings[i]); + if (l >= sizeof(k) - 1) + break; + strlcat(k, sep, sizeof(k)); + strlcat(k, stringbuffer->strings[i], sizeof(k)); + } + } + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(k); +} + +/* +======================== +VM_bufstr_get +get a string from buffer, returns tempstring, dont str_unzone it! +string bufstr_get(float bufhandle, float string_index) = #465; +======================== +*/ +void VM_bufstr_get (void) +{ + prvm_stringbuffer_t *stringbuffer; + int strindex; + VM_SAFEPARMCOUNT(2, VM_bufstr_get); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning("VM_bufstr_get: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + strindex = (int)PRVM_G_FLOAT(OFS_PARM1); + if (strindex < 0) + { + // VM_Warning("VM_bufstr_get: invalid string index %i used in %s\n", strindex, PRVM_NAME); + return; + } + if (strindex < stringbuffer->num_strings && stringbuffer->strings[strindex]) + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(stringbuffer->strings[strindex]); +} + +/* +======================== +VM_bufstr_set +copies a string into selected slot of buffer +void bufstr_set(float bufhandle, float string_index, string str) = #466; +======================== +*/ +void VM_bufstr_set (void) +{ + size_t alloclen; + int strindex; + prvm_stringbuffer_t *stringbuffer; + const char *news; + + VM_SAFEPARMCOUNT(3, VM_bufstr_set); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning("VM_bufstr_set: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + strindex = (int)PRVM_G_FLOAT(OFS_PARM1); + if(strindex < 0 || strindex >= 1000000) // huge number of strings + { + VM_Warning("VM_bufstr_set: invalid string index %i used in %s\n", strindex, PRVM_NAME); + return; + } + + BufStr_Expand(stringbuffer, strindex); + stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1); + + if(stringbuffer->strings[strindex]) + Mem_Free(stringbuffer->strings[strindex]); + stringbuffer->strings[strindex] = NULL; + + if(PRVM_G_INT(OFS_PARM2)) + { + // not the NULL string! + news = PRVM_G_STRING(OFS_PARM2); + alloclen = strlen(news) + 1; + stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); + memcpy(stringbuffer->strings[strindex], news, alloclen); + } + + BufStr_Shrink(stringbuffer); +} + +/* +======================== +VM_bufstr_add +adds string to buffer in first free slot and returns its index +"order == TRUE" means that string will be added after last "full" slot +float bufstr_add(float bufhandle, string str, float order) = #467; +======================== +*/ +void VM_bufstr_add (void) +{ + int order, strindex; + prvm_stringbuffer_t *stringbuffer; + const char *string; + size_t alloclen; + + VM_SAFEPARMCOUNT(3, VM_bufstr_add); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + PRVM_G_FLOAT(OFS_RETURN) = -1; + if(!stringbuffer) + { + VM_Warning("VM_bufstr_add: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + if(!PRVM_G_INT(OFS_PARM1)) // NULL string + { + VM_Warning("VM_bufstr_add: can not add an empty string to buffer %i in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + string = PRVM_G_STRING(OFS_PARM1); + order = (int)PRVM_G_FLOAT(OFS_PARM2); + if(order) + strindex = stringbuffer->num_strings; + else + for (strindex = 0;strindex < stringbuffer->num_strings;strindex++) + if (stringbuffer->strings[strindex] == NULL) + break; + + BufStr_Expand(stringbuffer, strindex); + + stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1); + alloclen = strlen(string) + 1; + stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); + memcpy(stringbuffer->strings[strindex], string, alloclen); + + PRVM_G_FLOAT(OFS_RETURN) = strindex; +} + +/* +======================== +VM_bufstr_free +delete string from buffer +void bufstr_free(float bufhandle, float string_index) = #468; +======================== +*/ +void VM_bufstr_free (void) +{ + int i; + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNT(2, VM_bufstr_free); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning("VM_bufstr_free: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + i = (int)PRVM_G_FLOAT(OFS_PARM1); + if(i < 0) + { + VM_Warning("VM_bufstr_free: invalid string index %i used in %s\n", i, PRVM_NAME); + return; + } + + if (i < stringbuffer->num_strings) + { + if(stringbuffer->strings[i]) + Mem_Free(stringbuffer->strings[i]); + stringbuffer->strings[i] = NULL; + } + + BufStr_Shrink(stringbuffer); +} + + + + + + + +void VM_buf_cvarlist(void) +{ + cvar_t *cvar; + const char *partial, *antipartial; + size_t len, antilen; + size_t alloclen; + qboolean ispattern, antiispattern; + int n; + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_buf_cvarlist); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning("VM_bufstr_free: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + + partial = PRVM_G_STRING(OFS_PARM1); + if(!partial) + len = 0; + else + len = strlen(partial); + + if(prog->argc == 3) + antipartial = PRVM_G_STRING(OFS_PARM2); + else + antipartial = NULL; + if(!antipartial) + antilen = 0; + else + antilen = strlen(antipartial); + + for (n = 0;n < stringbuffer->num_strings;n++) + if (stringbuffer->strings[n]) + Mem_Free(stringbuffer->strings[n]); + if (stringbuffer->strings) + Mem_Free(stringbuffer->strings); + stringbuffer->strings = NULL; + + ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); + antiispattern = antipartial && (strchr(antipartial, '*') || strchr(antipartial, '?')); + + n = 0; + for(cvar = cvar_vars; cvar; cvar = cvar->next) + { + if(len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp(partial, cvar->name, len))) + continue; + + if(antilen && (antiispattern ? matchpattern_with_separator(cvar->name, antipartial, false, "", false) : !strncmp(antipartial, cvar->name, antilen))) + continue; + + ++n; + } + + stringbuffer->max_strings = stringbuffer->num_strings = n; + if (stringbuffer->max_strings) + stringbuffer->strings = (char **)Mem_Alloc(prog->progs_mempool, sizeof(stringbuffer->strings[0]) * stringbuffer->max_strings); + + n = 0; + for(cvar = cvar_vars; cvar; cvar = cvar->next) + { + if(len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp(partial, cvar->name, len))) + continue; + + if(antilen && (antiispattern ? matchpattern_with_separator(cvar->name, antipartial, false, "", false) : !strncmp(antipartial, cvar->name, antilen))) + continue; + + alloclen = strlen(cvar->name) + 1; + stringbuffer->strings[n] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); + memcpy(stringbuffer->strings[n], cvar->name, alloclen); + + ++n; + } +} + + + + +//============= + +/* +============== +VM_changeyaw + +This was a major timewaster in progs, so it was converted to C +============== +*/ +void VM_changeyaw (void) +{ + prvm_edict_t *ent; + float ideal, current, move, speed; + + // this is called (VERY HACKISHLY) by SV_MoveToGoal, so it can not use any + // parameters because they are the parameters to SV_MoveToGoal, not this + //VM_SAFEPARMCOUNT(0, VM_changeyaw); + + ent = PRVM_PROG_TO_EDICT(PRVM_gameglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning("changeyaw: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("changeyaw: can not modify free entity\n"); + return; + } + current = PRVM_gameedictvector(ent, angles)[1]; + current = ANGLEMOD(current); + ideal = PRVM_gameedictfloat(ent, ideal_yaw); + speed = PRVM_gameedictfloat(ent, yaw_speed); + + if (current == ideal) + return; + move = ideal - current; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + current += move; + PRVM_gameedictvector(ent, angles)[1] = ANGLEMOD(current); +} + +/* +============== +VM_changepitch +============== +*/ +void VM_changepitch (void) +{ + prvm_edict_t *ent; + float ideal, current, move, speed; + + VM_SAFEPARMCOUNT(1, VM_changepitch); + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning("changepitch: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("changepitch: can not modify free entity\n"); + return; + } + current = PRVM_gameedictvector(ent, angles)[0]; + current = ANGLEMOD(current); + ideal = PRVM_gameedictfloat(ent, idealpitch); + speed = PRVM_gameedictfloat(ent, pitch_speed); + + if (current == ideal) + return; + move = ideal - current; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + current += move; + PRVM_gameedictvector(ent, angles)[0] = ANGLEMOD(current); +} + + +void VM_uncolorstring (void) +{ + char szNewString[VM_STRINGTEMP_LENGTH]; + const char *szString; + + // Prepare Strings + VM_SAFEPARMCOUNT(1, VM_uncolorstring); + szString = PRVM_G_STRING(OFS_PARM0); + COM_StringDecolorize(szString, 0, szNewString, sizeof(szNewString), TRUE); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(szNewString); + +} + +// #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) +//strstr, without generating a new string. Use in conjunction with FRIK_FILE's substring for more similar strstr. +void VM_strstrofs (void) +{ + const char *instr, *match; + int firstofs; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_strstrofs); + instr = PRVM_G_STRING(OFS_PARM0); + match = PRVM_G_STRING(OFS_PARM1); + firstofs = (prog->argc > 2)?(int)PRVM_G_FLOAT(OFS_PARM2):0; + firstofs = u8_bytelen(instr, firstofs); + + if (firstofs && (firstofs < 0 || firstofs > (int)strlen(instr))) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; + } + + match = strstr(instr+firstofs, match); + if (!match) + PRVM_G_FLOAT(OFS_RETURN) = -1; + else + PRVM_G_FLOAT(OFS_RETURN) = u8_strnlen(instr, match-instr); +} + +//#222 string(string s, float index) str2chr (FTE_STRINGS) +void VM_str2chr (void) +{ + const char *s; + Uchar ch; + int index; + VM_SAFEPARMCOUNT(2, VM_str2chr); + s = PRVM_G_STRING(OFS_PARM0); + index = u8_bytelen(s, (int)PRVM_G_FLOAT(OFS_PARM1)); + + if((unsigned)index < strlen(s)) + { + if (utf8_enable.integer) + ch = u8_getchar_noendptr(s + index); + else + ch = (unsigned char)s[index]; + PRVM_G_FLOAT(OFS_RETURN) = ch; + } + else + PRVM_G_FLOAT(OFS_RETURN) = 0; +} + +//#223 string(float c, ...) chr2str (FTE_STRINGS) +void VM_chr2str (void) +{ + /* + char t[9]; + int i; + VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str); + for(i = 0;i < prog->argc && i < (int)sizeof(t) - 1;i++) + t[i] = (unsigned char)PRVM_G_FLOAT(OFS_PARM0+i*3); + t[i] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t); + */ + char t[9 * 4 + 1]; + int i; + size_t len = 0; + VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str); + for(i = 0; i < prog->argc && len < sizeof(t)-1; ++i) + len += u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0+i*3), t + len, sizeof(t)-1); + t[len] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t); +} + +static int chrconv_number(int i, int base, int conv) +{ + i -= base; + switch (conv) + { + default: + case 5: + case 6: + case 0: + break; + case 1: + base = '0'; + break; + case 2: + base = '0'+128; + break; + case 3: + base = '0'-30; + break; + case 4: + base = '0'+128-30; + break; + } + return i + base; +} +static int chrconv_punct(int i, int base, int conv) +{ + i -= base; + switch (conv) + { + default: + case 0: + break; + case 1: + base = 0; + break; + case 2: + base = 128; + break; + } + return i + base; +} + +static int chrchar_alpha(int i, int basec, int baset, int convc, int convt, int charnum) +{ + //convert case and colour seperatly... + + i -= baset + basec; + switch (convt) + { + default: + case 0: + break; + case 1: + baset = 0; + break; + case 2: + baset = 128; + break; + + case 5: + case 6: + baset = 128*((charnum&1) == (convt-5)); + break; + } + + switch (convc) + { + default: + case 0: + break; + case 1: + basec = 'a'; + break; + case 2: + basec = 'A'; + break; + } + return i + basec + baset; +} +// #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) +//bulk convert a string. change case or colouring. +void VM_strconv (void) +{ + int ccase, redalpha, rednum, len, i; + unsigned char resbuf[VM_STRINGTEMP_LENGTH]; + unsigned char *result = resbuf; + + VM_SAFEPARMCOUNTRANGE(3, 8, VM_strconv); + + ccase = (int) PRVM_G_FLOAT(OFS_PARM0); //0 same, 1 lower, 2 upper + redalpha = (int) PRVM_G_FLOAT(OFS_PARM1); //0 same, 1 white, 2 red, 5 alternate, 6 alternate-alternate + rednum = (int) PRVM_G_FLOAT(OFS_PARM2); //0 same, 1 white, 2 red, 3 redspecial, 4 whitespecial, 5 alternate, 6 alternate-alternate + VM_VarString(3, (char *) resbuf, sizeof(resbuf)); + len = strlen((char *) resbuf); + + for (i = 0; i < len; i++, result++) //should this be done backwards? + { + if (*result >= '0' && *result <= '9') //normal numbers... + *result = chrconv_number(*result, '0', rednum); + else if (*result >= '0'+128 && *result <= '9'+128) + *result = chrconv_number(*result, '0'+128, rednum); + else if (*result >= '0'+128-30 && *result <= '9'+128-30) + *result = chrconv_number(*result, '0'+128-30, rednum); + else if (*result >= '0'-30 && *result <= '9'-30) + *result = chrconv_number(*result, '0'-30, rednum); + + else if (*result >= 'a' && *result <= 'z') //normal numbers... + *result = chrchar_alpha(*result, 'a', 0, ccase, redalpha, i); + else if (*result >= 'A' && *result <= 'Z') //normal numbers... + *result = chrchar_alpha(*result, 'A', 0, ccase, redalpha, i); + else if (*result >= 'a'+128 && *result <= 'z'+128) //normal numbers... + *result = chrchar_alpha(*result, 'a', 128, ccase, redalpha, i); + else if (*result >= 'A'+128 && *result <= 'Z'+128) //normal numbers... + *result = chrchar_alpha(*result, 'A', 128, ccase, redalpha, i); + + else if ((*result & 127) < 16 || !redalpha) //special chars.. + *result = *result; + else if (*result < 128) + *result = chrconv_punct(*result, 0, redalpha); + else + *result = chrconv_punct(*result, 128, redalpha); + } + *result = '\0'; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString((char *) resbuf); +} + +// #225 string(float chars, string s, ...) strpad (FTE_STRINGS) +void VM_strpad (void) +{ + char src[VM_STRINGTEMP_LENGTH]; + char destbuf[VM_STRINGTEMP_LENGTH]; + int pad; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_strpad); + pad = (int) PRVM_G_FLOAT(OFS_PARM0); + VM_VarString(1, src, sizeof(src)); + + // note: < 0 = left padding, > 0 = right padding, + // this is reverse logic of printf! + dpsnprintf(destbuf, sizeof(destbuf), "%*s", -pad, src); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(destbuf); +} + +// #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) +//uses qw style \key\value strings +void VM_infoadd (void) +{ + const char *info, *key; + char value[VM_STRINGTEMP_LENGTH]; + char temp[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_infoadd); + info = PRVM_G_STRING(OFS_PARM0); + key = PRVM_G_STRING(OFS_PARM1); + VM_VarString(2, value, sizeof(value)); + + strlcpy(temp, info, VM_STRINGTEMP_LENGTH); + + InfoString_SetValue(temp, VM_STRINGTEMP_LENGTH, key, value); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(temp); +} + +// #227 string(string info, string key) infoget (FTE_STRINGS) +//uses qw style \key\value strings +void VM_infoget (void) +{ + const char *info; + const char *key; + char value[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT(2, VM_infoget); + info = PRVM_G_STRING(OFS_PARM0); + key = PRVM_G_STRING(OFS_PARM1); + + InfoString_GetValue(info, key, value, VM_STRINGTEMP_LENGTH); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(value); +} + +//#228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) +// also float(string s1, string s2) strcmp (FRIK_FILE) +void VM_strncmp (void) +{ + const char *s1, *s2; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_strncmp); + s1 = PRVM_G_STRING(OFS_PARM0); + s2 = PRVM_G_STRING(OFS_PARM1); + if (prog->argc > 2) + { + PRVM_G_FLOAT(OFS_RETURN) = strncmp(s1, s2, (size_t)PRVM_G_FLOAT(OFS_PARM2)); + } + else + { + PRVM_G_FLOAT(OFS_RETURN) = strcmp(s1, s2); + } +} + +// #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) +// #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) +void VM_strncasecmp (void) +{ + const char *s1, *s2; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_strncasecmp); + s1 = PRVM_G_STRING(OFS_PARM0); + s2 = PRVM_G_STRING(OFS_PARM1); + if (prog->argc > 2) + { + PRVM_G_FLOAT(OFS_RETURN) = strncasecmp(s1, s2, (size_t)PRVM_G_FLOAT(OFS_PARM2)); + } + else + { + PRVM_G_FLOAT(OFS_RETURN) = strcasecmp(s1, s2); + } +} + +// #494 float(float caseinsensitive, string s, ...) crc16 +void VM_crc16(void) +{ + float insensitive; + static char s[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(2, 8, VM_hash); + insensitive = PRVM_G_FLOAT(OFS_PARM0); + VM_VarString(1, s, sizeof(s)); + PRVM_G_FLOAT(OFS_RETURN) = (unsigned short) ((insensitive ? CRC_Block_CaseInsensitive : CRC_Block) ((unsigned char *) s, strlen(s))); +} + +void VM_wasfreed (void) +{ + VM_SAFEPARMCOUNT(1, VM_wasfreed); + PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_EDICT(OFS_PARM0)->priv.required->free; +} + +void VM_SetTraceGlobals(const trace_t *trace) +{ + PRVM_gameglobalfloat(trace_allsolid) = trace->allsolid; + PRVM_gameglobalfloat(trace_startsolid) = trace->startsolid; + PRVM_gameglobalfloat(trace_fraction) = trace->fraction; + PRVM_gameglobalfloat(trace_inwater) = trace->inwater; + PRVM_gameglobalfloat(trace_inopen) = trace->inopen; + VectorCopy(trace->endpos, PRVM_gameglobalvector(trace_endpos)); + VectorCopy(trace->plane.normal, PRVM_gameglobalvector(trace_plane_normal)); + PRVM_gameglobalfloat(trace_plane_dist) = trace->plane.dist; + PRVM_gameglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(trace->ent ? trace->ent : prog->edicts); + PRVM_gameglobalfloat(trace_dpstartcontents) = trace->startsupercontents; + PRVM_gameglobalfloat(trace_dphitcontents) = trace->hitsupercontents; + PRVM_gameglobalfloat(trace_dphitq3surfaceflags) = trace->hitq3surfaceflags; + PRVM_gameglobalstring(trace_dphittexturename) = trace->hittexture ? PRVM_SetTempString(trace->hittexture->name) : 0; +} + +void VM_ClearTraceGlobals(void) +{ + // clean up all trace globals when leaving the VM (anti-triggerbot safeguard) + PRVM_gameglobalfloat(trace_allsolid) = 0; + PRVM_gameglobalfloat(trace_startsolid) = 0; + PRVM_gameglobalfloat(trace_fraction) = 0; + PRVM_gameglobalfloat(trace_inwater) = 0; + PRVM_gameglobalfloat(trace_inopen) = 0; + VectorClear(PRVM_gameglobalvector(trace_endpos)); + VectorClear(PRVM_gameglobalvector(trace_plane_normal)); + PRVM_gameglobalfloat(trace_plane_dist) = 0; + PRVM_gameglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_gameglobalfloat(trace_dpstartcontents) = 0; + PRVM_gameglobalfloat(trace_dphitcontents) = 0; + PRVM_gameglobalfloat(trace_dphitq3surfaceflags) = 0; + PRVM_gameglobalstring(trace_dphittexturename) = 0; +} + +//============= + +void VM_Cmd_Init(void) +{ + // only init the stuff for the current prog + VM_Files_Init(); + VM_Search_Init(); + VM_Gecko_Init(); +// VM_BufStr_Init(); +} + +void VM_Cmd_Reset(void) +{ + CL_PurgeOwner( MENUOWNER ); + VM_Search_Reset(); + VM_Files_CloseAll(); + VM_Gecko_Destroy(); +// VM_BufStr_ShutDown(); +} + +// #510 string(string input, ...) uri_escape (DP_QC_URI_ESCAPE) +// does URI escaping on a string (replace evil stuff by %AB escapes) +void VM_uri_escape (void) +{ + char src[VM_STRINGTEMP_LENGTH]; + char dest[VM_STRINGTEMP_LENGTH]; + char *p, *q; + static const char *hex = "0123456789ABCDEF"; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_escape); + VM_VarString(0, src, sizeof(src)); + + for(p = src, q = dest; *p && q < dest + sizeof(dest) - 3; ++p) + { + if((*p >= 'A' && *p <= 'Z') + || (*p >= 'a' && *p <= 'z') + || (*p >= '0' && *p <= '9') + || (*p == '-') || (*p == '_') || (*p == '.') + || (*p == '!') || (*p == '~') + || (*p == '\'') || (*p == '(') || (*p == ')')) + *q++ = *p; + else + { + *q++ = '%'; + *q++ = hex[(*(unsigned char *)p >> 4) & 0xF]; + *q++ = hex[ *(unsigned char *)p & 0xF]; + } + } + *q++ = 0; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(dest); +} + +// #510 string(string input, ...) uri_unescape (DP_QC_URI_ESCAPE) +// does URI unescaping on a string (get back the evil stuff) +void VM_uri_unescape (void) +{ + char src[VM_STRINGTEMP_LENGTH]; + char dest[VM_STRINGTEMP_LENGTH]; + char *p, *q; + int hi, lo; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_unescape); + VM_VarString(0, src, sizeof(src)); + + for(p = src, q = dest; *p; ) // no need to check size, because unescape can't expand + { + if(*p == '%') + { + if(p[1] >= '0' && p[1] <= '9') + hi = p[1] - '0'; + else if(p[1] >= 'a' && p[1] <= 'f') + hi = p[1] - 'a' + 10; + else if(p[1] >= 'A' && p[1] <= 'F') + hi = p[1] - 'A' + 10; + else + goto nohex; + if(p[2] >= '0' && p[2] <= '9') + lo = p[2] - '0'; + else if(p[2] >= 'a' && p[2] <= 'f') + lo = p[2] - 'a' + 10; + else if(p[2] >= 'A' && p[2] <= 'F') + lo = p[2] - 'A' + 10; + else + goto nohex; + if(hi != 0 || lo != 0) // don't unescape NUL bytes + *q++ = (char) (hi * 0x10 + lo); + p += 3; + continue; + } + +nohex: + // otherwise: + *q++ = *p++; + } + *q++ = 0; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(dest); +} + +// #502 string(string filename) whichpack (DP_QC_WHICHPACK) +// returns the name of the pack containing a file, or "" if it is not in any pack (but local or non-existant) +void VM_whichpack (void) +{ + const char *fn, *pack; + + VM_SAFEPARMCOUNT(1, VM_whichpack); + fn = PRVM_G_STRING(OFS_PARM0); + pack = FS_WhichPack(fn); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(pack ? pack : ""); +} + +typedef struct +{ + int prognr; + double starttime; + float id; + char buffer[MAX_INPUTLINE]; + unsigned char *postdata; // free when uri_to_prog_t is freed + size_t postlen; + char *sigdata; // free when uri_to_prog_t is freed + size_t siglen; +} +uri_to_prog_t; + +static void uri_to_string_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) +{ + uri_to_prog_t *handle = (uri_to_prog_t *) cbdata; + + if(!PRVM_ProgLoaded(handle->prognr)) + { + // curl reply came too late... so just drop it + if(handle->postdata) + Z_Free(handle->postdata); + if(handle->sigdata) + Z_Free(handle->sigdata); + Z_Free(handle); + return; + } + + PRVM_SetProg(handle->prognr); + PRVM_Begin; + if((prog->starttime == handle->starttime) && (PRVM_allfunction(URI_Get_Callback))) + { + if(length_received >= sizeof(handle->buffer)) + length_received = sizeof(handle->buffer) - 1; + handle->buffer[length_received] = 0; + + PRVM_G_FLOAT(OFS_PARM0) = handle->id; + PRVM_G_FLOAT(OFS_PARM1) = status; + PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(handle->buffer); + PRVM_ExecuteProgram(PRVM_allfunction(URI_Get_Callback), "QC function URI_Get_Callback is missing"); + } + PRVM_End; + + if(handle->postdata) + Z_Free(handle->postdata); + if(handle->sigdata) + Z_Free(handle->sigdata); + Z_Free(handle); +} + +// uri_get() gets content from an URL and calls a callback "uri_get_callback" with it set as string; an unique ID of the transfer is returned +// returns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string +void VM_uri_get (void) +{ + const char *url; + float id; + qboolean ret; + uri_to_prog_t *handle; + const char *posttype = NULL; + const char *postseparator = NULL; + int poststringbuffer = -1; + int postkeyid = -1; + const char *query_string = NULL; + size_t lq; + + if(!PRVM_allfunction(URI_Get_Callback)) + PRVM_ERROR("uri_get called by %s without URI_Get_Callback defined", PRVM_NAME); + + VM_SAFEPARMCOUNTRANGE(2, 6, VM_uri_get); + + url = PRVM_G_STRING(OFS_PARM0); + id = PRVM_G_FLOAT(OFS_PARM1); + if(prog->argc >= 3) + posttype = PRVM_G_STRING(OFS_PARM2); + if(prog->argc >= 4) + postseparator = PRVM_G_STRING(OFS_PARM3); + if(prog->argc >= 5) + poststringbuffer = PRVM_G_FLOAT(OFS_PARM4); + if(prog->argc >= 6) + postkeyid = PRVM_G_FLOAT(OFS_PARM5); + handle = (uri_to_prog_t *) Z_Malloc(sizeof(*handle)); // this can't be the prog's mem pool, as curl may call the callback later! + + query_string = strchr(url, '?'); + if(query_string) + ++query_string; + lq = query_string ? strlen(query_string) : 0; + + handle->prognr = PRVM_GetProgNr(); + handle->starttime = prog->starttime; + handle->id = id; + if(postseparator) + { + size_t l = strlen(postseparator); + if(poststringbuffer >= 0) + { + size_t ltotal; + int i; + // "implode" + prvm_stringbuffer_t *stringbuffer; + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, poststringbuffer); + if(!stringbuffer) + { + VM_Warning("uri_get: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME); + return; + } + ltotal = 0; + for(i = 0;i < stringbuffer->num_strings;i++) + { + if(i > 0) + ltotal += l; + if(stringbuffer->strings[i]) + ltotal += strlen(stringbuffer->strings[i]); + } + handle->postdata = (unsigned char *)Z_Malloc(ltotal + 1 + lq); + handle->postlen = ltotal; + ltotal = 0; + for(i = 0;i < stringbuffer->num_strings;i++) + { + if(i > 0) + { + memcpy(handle->postdata + ltotal, postseparator, l); + ltotal += l; + } + if(stringbuffer->strings[i]) + { + memcpy(handle->postdata + ltotal, stringbuffer->strings[i], strlen(stringbuffer->strings[i])); + ltotal += strlen(stringbuffer->strings[i]); + } + } + if(ltotal != handle->postlen) + PRVM_ERROR ("%s: string buffer content size mismatch, possible overrun", PRVM_NAME); + } + else + { + handle->postdata = (unsigned char *)Z_Malloc(l + 1 + lq); + handle->postlen = l; + memcpy(handle->postdata, postseparator, l); + } + handle->postdata[handle->postlen] = 0; + if(query_string) + memcpy(handle->postdata + handle->postlen + 1, query_string, lq); + if(postkeyid >= 0) + { + // POST: we sign postdata \0 query string + size_t ll; + handle->sigdata = (char *)Z_Malloc(8192); + strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192); + l = strlen(handle->sigdata); + handle->siglen = Crypto_SignDataDetached(handle->postdata, handle->postlen + 1 + lq, postkeyid, handle->sigdata + l, 8192 - l); + if(!handle->siglen) + { + Z_Free(handle->sigdata); + handle->sigdata = NULL; + goto out1; + } + ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1); + if(!ll) + { + Z_Free(handle->sigdata); + handle->sigdata = NULL; + goto out1; + } + handle->siglen = l + ll; + handle->sigdata[handle->siglen] = 0; + } +out1: + ret = Curl_Begin_ToMemory_POST(url, handle->sigdata, 0, posttype, handle->postdata, handle->postlen, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle); + } + else + { + if(postkeyid >= 0 && query_string) + { + // GET: we sign JUST the query string + size_t l, ll; + handle->sigdata = (char *)Z_Malloc(8192); + strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192); + l = strlen(handle->sigdata); + handle->siglen = Crypto_SignDataDetached(query_string, lq, postkeyid, handle->sigdata + l, 8192 - l); + if(!handle->siglen) + { + Z_Free(handle->sigdata); + handle->sigdata = NULL; + goto out2; + } + ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1); + if(!ll) + { + Z_Free(handle->sigdata); + handle->sigdata = NULL; + goto out2; + } + handle->siglen = l + ll; + handle->sigdata[handle->siglen] = 0; + } +out2: + handle->postdata = NULL; + handle->postlen = 0; + ret = Curl_Begin_ToMemory(url, 0, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle); + } + if(ret) + { + PRVM_G_INT(OFS_RETURN) = 1; + } + else + { + if(handle->postdata) + Z_Free(handle->postdata); + if(handle->sigdata) + Z_Free(handle->sigdata); + Z_Free(handle); + PRVM_G_INT(OFS_RETURN) = 0; + } +} + +void VM_netaddress_resolve (void) +{ + const char *ip; + char normalized[128]; + int port; + lhnetaddress_t addr; + + VM_SAFEPARMCOUNTRANGE(1, 2, VM_netaddress_resolve); + + ip = PRVM_G_STRING(OFS_PARM0); + port = 0; + if(prog->argc > 1) + port = (int) PRVM_G_FLOAT(OFS_PARM1); + + if(LHNETADDRESS_FromString(&addr, ip, port) && LHNETADDRESS_ToString(&addr, normalized, sizeof(normalized), prog->argc > 1)) + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(normalized); + else + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(""); +} + +//string(void) getextresponse = #624; // returns the next extResponse packet that was sent to this client +void VM_CL_getextresponse (void) +{ + VM_SAFEPARMCOUNT(0,VM_argv); + + if (cl_net_extresponse_count <= 0) + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + else + { + int first; + --cl_net_extresponse_count; + first = (cl_net_extresponse_last + NET_EXTRESPONSE_MAX - cl_net_extresponse_count) % NET_EXTRESPONSE_MAX; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(cl_net_extresponse[first]); + } +} + +void VM_SV_getextresponse (void) +{ + VM_SAFEPARMCOUNT(0,VM_argv); + + if (sv_net_extresponse_count <= 0) + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + else + { + int first; + --sv_net_extresponse_count; + first = (sv_net_extresponse_last + NET_EXTRESPONSE_MAX - sv_net_extresponse_count) % NET_EXTRESPONSE_MAX; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(sv_net_extresponse[first]); + } +} + +/* +========= +Common functions between menu.dat and clsprogs +========= +*/ + +//#349 float() isdemo +void VM_CL_isdemo (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_isdemo); + PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback; +} + +//#355 float() videoplaying +void VM_CL_videoplaying (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_videoplaying); + PRVM_G_FLOAT(OFS_RETURN) = cl_videoplaying; +} + +/* +========= +VM_M_callfunction + + callfunction(...,string function_name) +Extension: pass +========= +*/ +mfunction_t *PRVM_ED_FindFunction (const char *name); +void VM_callfunction(void) +{ + mfunction_t *func; + const char *s; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_callfunction); + + s = PRVM_G_STRING(OFS_PARM0+(prog->argc - 1)*3); + + VM_CheckEmptyString(s); + + func = PRVM_ED_FindFunction(s); + + if(!func) + PRVM_ERROR("VM_callfunciton: function %s not found !", s); + else if (func->first_statement < 0) + { + // negative statements are built in functions + int builtinnumber = -func->first_statement; + prog->xfunction->builtinsprofile++; + if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber]) + prog->builtins[builtinnumber](); + else + PRVM_ERROR("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, PRVM_NAME); + } + else if(func - prog->functions > 0) + { + prog->argc--; + PRVM_ExecuteProgram(func - prog->functions,""); + prog->argc++; + } +} + +/* +========= +VM_isfunction + +float isfunction(string function_name) +========= +*/ +mfunction_t *PRVM_ED_FindFunction (const char *name); +void VM_isfunction(void) +{ + mfunction_t *func; + const char *s; + + VM_SAFEPARMCOUNT(1, VM_isfunction); + + s = PRVM_G_STRING(OFS_PARM0); + + VM_CheckEmptyString(s); + + func = PRVM_ED_FindFunction(s); + + if(!func) + PRVM_G_FLOAT(OFS_RETURN) = false; + else + PRVM_G_FLOAT(OFS_RETURN) = true; +} + +/* +========= +VM_sprintf + +string sprintf(string format, ...) +========= +*/ + +void VM_sprintf(void) +{ + const char *s, *s0; + char outbuf[MAX_INPUTLINE]; + char *o = outbuf, *end = outbuf + sizeof(outbuf), *err; + int argpos = 1; + int width, precision, thisarg, flags; + char formatbuf[16]; + char *f; + int isfloat; + static int dummyivec[3] = {0, 0, 0}; + static float dummyvec[3] = {0, 0, 0}; + +#define PRINTF_ALTERNATE 1 +#define PRINTF_ZEROPAD 2 +#define PRINTF_LEFT 4 +#define PRINTF_SPACEPOSITIVE 8 +#define PRINTF_SIGNPOSITIVE 16 + + formatbuf[0] = '%'; + + s = PRVM_G_STRING(OFS_PARM0); + +#define GETARG_FLOAT(a) (((a)>=1 && (a)argc) ? (PRVM_G_FLOAT(OFS_PARM0 + 3 * (a))) : 0) +#define GETARG_VECTOR(a) (((a)>=1 && (a)argc) ? (PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec) +#define GETARG_INT(a) (((a)>=1 && (a)argc) ? (PRVM_G_INT(OFS_PARM0 + 3 * (a))) : 0) +#define GETARG_INTVECTOR(a) (((a)>=1 && (a)argc) ? ((int*) PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec) +#define GETARG_STRING(a) (((a)>=1 && (a)argc) ? (PRVM_G_STRING(OFS_PARM0 + 3 * (a))) : "") + + for(;;) + { + s0 = s; + switch(*s) + { + case 0: + goto finished; + case '%': + ++s; + + if(*s == '%') + goto verbatim; + + // complete directive format: + // %3$*1$.*2$ld + + width = -1; + precision = -1; + thisarg = -1; + flags = 0; + isfloat = -1; + + // is number following? + if(*s >= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err) + { + VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0); + goto finished; + } + if(*err == '$') + { + thisarg = width; + width = -1; + s = err + 1; + } + else + { + if(*s == '0') + { + flags |= PRINTF_ZEROPAD; + if(width == 0) + width = -1; // it was just a flag + } + s = err; + } + } + + if(width < 0) + { + for(;;) + { + switch(*s) + { + case '#': flags |= PRINTF_ALTERNATE; break; + case '0': flags |= PRINTF_ZEROPAD; break; + case '-': flags |= PRINTF_LEFT; break; + case ' ': flags |= PRINTF_SPACEPOSITIVE; break; + case '+': flags |= PRINTF_SIGNPOSITIVE; break; + default: + goto noflags; + } + ++s; + } +noflags: + if(*s == '*') + { + ++s; + if(*s >= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err || *err != '$') + { + VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0); + goto finished; + } + s = err + 1; + } + else + width = argpos++; + width = GETARG_FLOAT(width); + if(width < 0) + { + flags |= PRINTF_LEFT; + width = -width; + } + } + else if(*s >= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err) + { + VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0); + goto finished; + } + s = err; + if(width < 0) + { + flags |= PRINTF_LEFT; + width = -width; + } + } + // otherwise width stays -1 + } + + if(*s == '.') + { + ++s; + if(*s == '*') + { + ++s; + if(*s >= '0' && *s <= '9') + { + precision = strtol(s, &err, 10); + if(!err || *err != '$') + { + VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0); + goto finished; + } + s = err + 1; + } + else + precision = argpos++; + precision = GETARG_FLOAT(precision); + } + else if(*s >= '0' && *s <= '9') + { + precision = strtol(s, &err, 10); + if(!err) + { + VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0); + goto finished; + } + s = err; + } + else + { + VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0); + goto finished; + } + } + + for(;;) + { + switch(*s) + { + case 'h': isfloat = 1; break; + case 'l': isfloat = 0; break; + case 'L': isfloat = 0; break; + case 'j': break; + case 'z': break; + case 't': break; + default: + goto nolength; + } + ++s; + } +nolength: + + // now s points to the final directive char and is no longer changed + if(isfloat < 0) + { + if(*s == 'i') + isfloat = 0; + else + isfloat = 1; + } + + if(thisarg < 0) + thisarg = argpos++; + + if(o < end - 1) + { + f = &formatbuf[1]; + if(*s != 's' && *s != 'c') + if(flags & PRINTF_ALTERNATE) *f++ = '#'; + if(flags & PRINTF_ZEROPAD) *f++ = '0'; + if(flags & PRINTF_LEFT) *f++ = '-'; + if(flags & PRINTF_SPACEPOSITIVE) *f++ = ' '; + if(flags & PRINTF_SIGNPOSITIVE) *f++ = '+'; + *f++ = '*'; + if(precision >= 0) + { + *f++ = '.'; + *f++ = '*'; + } + *f++ = *s; + *f++ = 0; + + if(width < 0) // not set + width = 0; + + switch(*s) + { + case 'd': case 'i': + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg))); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg))); + break; + case 'o': case 'u': case 'x': case 'X': + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + break; + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); + break; + case 'v': case 'V': + f[-2] += 'g' - 'v'; + if(precision < 0) // not set + o += dpsnprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) + ); + else + o += dpsnprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) + ); + break; + case 'c': + if(flags & PRINTF_ALTERNATE) + { + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + } + else + { + unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)); + const char *buf = u8_encodech(c, NULL); + if(!buf) + buf = ""; + if(precision < 0) // not set + precision = end - o - 1; + o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision); + } + break; + case 's': + if(flags & PRINTF_ALTERNATE) + { + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, GETARG_STRING(thisarg)); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg)); + } + else + { + if(precision < 0) // not set + precision = end - o - 1; + o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision); + } + break; + default: + VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0); + goto finished; + } + } + ++s; + break; + default: +verbatim: + if(o < end - 1) + *o++ = *s++; + break; + } + } +finished: + *o = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(outbuf); +} + + +// surface querying + +static dp_model_t *getmodel(prvm_edict_t *ed) +{ + switch(PRVM_GetProgNr()) + { + case PRVM_SERVERPROG: + return SV_GetModelFromEdict(ed); + case PRVM_CLIENTPROG: + return CL_GetModelFromEdict(ed); + default: + return NULL; + } +} + +typedef struct +{ + unsigned int progid; + dp_model_t *model; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + skeleton_t *skeleton_p; + skeleton_t skeleton; + float *data_vertex3f; + float *data_svector3f; + float *data_tvector3f; + float *data_normal3f; + int max_vertices; + float *buf_vertex3f; + float *buf_svector3f; + float *buf_tvector3f; + float *buf_normal3f; +} +animatemodel_cache_t; +static animatemodel_cache_t animatemodel_cache; + +void animatemodel(dp_model_t *model, prvm_edict_t *ed) +{ + skeleton_t *skeleton; + int skeletonindex = -1; + qboolean need = false; + if(!(model->surfmesh.isanimated && model->AnimateVertices)) + { + animatemodel_cache.data_vertex3f = model->surfmesh.data_vertex3f; + animatemodel_cache.data_svector3f = model->surfmesh.data_svector3f; + animatemodel_cache.data_tvector3f = model->surfmesh.data_tvector3f; + animatemodel_cache.data_normal3f = model->surfmesh.data_normal3f; + return; + } + if(animatemodel_cache.progid != prog->id) + memset(&animatemodel_cache, 0, sizeof(animatemodel_cache)); + need |= (animatemodel_cache.model != model); + VM_GenerateFrameGroupBlend(ed->priv.server->framegroupblend, ed); + VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model); + need |= (memcmp(&animatemodel_cache.frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend))) != 0; + skeletonindex = (int)PRVM_gameedictfloat(ed, skeletonindex) - 1; + if (!(skeletonindex >= 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones)) + skeleton = NULL; + need |= (animatemodel_cache.skeleton_p != skeleton); + if(skeleton) + need |= (memcmp(&animatemodel_cache.skeleton, skeleton, sizeof(ed->priv.server->skeleton))) != 0; + if(!need) + return; + if(model->surfmesh.num_vertices > animatemodel_cache.max_vertices) + { + animatemodel_cache.max_vertices = model->surfmesh.num_vertices * 2; + if(animatemodel_cache.buf_vertex3f) Mem_Free(animatemodel_cache.buf_vertex3f); + if(animatemodel_cache.buf_svector3f) Mem_Free(animatemodel_cache.buf_svector3f); + if(animatemodel_cache.buf_tvector3f) Mem_Free(animatemodel_cache.buf_tvector3f); + if(animatemodel_cache.buf_normal3f) Mem_Free(animatemodel_cache.buf_normal3f); + animatemodel_cache.buf_vertex3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices); + animatemodel_cache.buf_svector3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices); + animatemodel_cache.buf_tvector3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices); + animatemodel_cache.buf_normal3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices); + } + animatemodel_cache.data_vertex3f = animatemodel_cache.buf_vertex3f; + animatemodel_cache.data_svector3f = animatemodel_cache.buf_svector3f; + animatemodel_cache.data_tvector3f = animatemodel_cache.buf_tvector3f; + animatemodel_cache.data_normal3f = animatemodel_cache.buf_normal3f; + VM_UpdateEdictSkeleton(ed, model, ed->priv.server->frameblend); + model->AnimateVertices(model, ed->priv.server->frameblend, &ed->priv.server->skeleton, animatemodel_cache.data_vertex3f, animatemodel_cache.data_normal3f, animatemodel_cache.data_svector3f, animatemodel_cache.data_tvector3f); + animatemodel_cache.progid = prog->id; + animatemodel_cache.model = model; + memcpy(&animatemodel_cache.frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend)); + animatemodel_cache.skeleton_p = skeleton; + if(skeleton) + memcpy(&animatemodel_cache.skeleton, skeleton, sizeof(ed->priv.server->skeleton)); +} + +static void getmatrix(prvm_edict_t *ed, matrix4x4_t *out) +{ + switch(PRVM_GetProgNr()) + { + case PRVM_SERVERPROG: + SV_GetEntityMatrix(ed, out, false); + break; + case PRVM_CLIENTPROG: + CL_GetEntityMatrix(ed, out, false); + break; + default: + *out = identitymatrix; + break; + } +} + +static void applytransform_forward(const vec3_t in, prvm_edict_t *ed, vec3_t out) +{ + matrix4x4_t m; + getmatrix(ed, &m); + Matrix4x4_Transform(&m, in, out); +} + +static void applytransform_forward_direction(const vec3_t in, prvm_edict_t *ed, vec3_t out) +{ + matrix4x4_t m; + getmatrix(ed, &m); + Matrix4x4_Transform3x3(&m, in, out); +} + +static void applytransform_inverted(const vec3_t in, prvm_edict_t *ed, vec3_t out) +{ + matrix4x4_t m, n; + getmatrix(ed, &m); + Matrix4x4_Invert_Full(&n, &m); + Matrix4x4_Transform3x3(&n, in, out); +} + +static void applytransform_forward_normal(const vec3_t in, prvm_edict_t *ed, vec3_t out) +{ + matrix4x4_t m; + float p[4]; + getmatrix(ed, &m); + Matrix4x4_TransformPositivePlane(&m, in[0], in[1], in[2], 0, p); + VectorCopy(p, out); +} + +static void clippointtosurface(prvm_edict_t *ed, dp_model_t *model, msurface_t *surface, vec3_t p, vec3_t out) +{ + int i, j, k; + float *v[3], facenormal[3], edgenormal[3], sidenormal[3], temp[3], offsetdist, dist, bestdist; + const int *e; + animatemodel(model, ed); + bestdist = 1000000000; + VectorCopy(p, out); + for (i = 0, e = (model->surfmesh.data_element3i + 3 * surface->num_firsttriangle);i < surface->num_triangles;i++, e += 3) + { + // clip original point to each triangle of the surface and find the + // triangle that is closest + v[0] = animatemodel_cache.data_vertex3f + e[0] * 3; + v[1] = animatemodel_cache.data_vertex3f + e[1] * 3; + v[2] = animatemodel_cache.data_vertex3f + e[2] * 3; + TriangleNormal(v[0], v[1], v[2], facenormal); + VectorNormalize(facenormal); + offsetdist = DotProduct(v[0], facenormal) - DotProduct(p, facenormal); + VectorMA(p, offsetdist, facenormal, temp); + for (j = 0, k = 2;j < 3;k = j, j++) + { + VectorSubtract(v[k], v[j], edgenormal); + CrossProduct(edgenormal, facenormal, sidenormal); + VectorNormalize(sidenormal); + offsetdist = DotProduct(v[k], sidenormal) - DotProduct(temp, sidenormal); + if (offsetdist < 0) + VectorMA(temp, offsetdist, sidenormal, temp); + } + dist = VectorDistance2(temp, p); + if (bestdist > dist) + { + bestdist = dist; + VectorCopy(temp, out); + } + } +} + +static msurface_t *getsurface(dp_model_t *model, int surfacenum) +{ + if (surfacenum < 0 || surfacenum >= model->nummodelsurfaces) + return NULL; + return model->data_surfaces + surfacenum + model->firstmodelsurface; +} + + +//PF_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints = #434; +void VM_getsurfacenumpoints(void) +{ + dp_model_t *model; + msurface_t *surface; + VM_SAFEPARMCOUNT(2, VM_getsurfacenumpoints); + // return 0 if no such surface + if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + + // note: this (incorrectly) assumes it is a simple polygon + PRVM_G_FLOAT(OFS_RETURN) = surface->num_vertices; +} +//PF_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint = #435; +void VM_getsurfacepoint(void) +{ + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + int pointnum; + VM_SAFEPARMCOUNT(3, VM_getsurfacepoint); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + // note: this (incorrectly) assumes it is a simple polygon + pointnum = (int)PRVM_G_FLOAT(OFS_PARM2); + if (pointnum < 0 || pointnum >= surface->num_vertices) + return; + animatemodel(model, ed); + applytransform_forward(&(animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN)); +} +//PF_getsurfacepointattribute, // #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; +// float SPA_POSITION = 0; +// float SPA_S_AXIS = 1; +// float SPA_T_AXIS = 2; +// float SPA_R_AXIS = 3; // same as SPA_NORMAL +// float SPA_TEXCOORDS0 = 4; +// float SPA_LIGHTMAP0_TEXCOORDS = 5; +// float SPA_LIGHTMAP0_COLOR = 6; +void VM_getsurfacepointattribute(void) +{ + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + int pointnum; + int attributetype; + + VM_SAFEPARMCOUNT(4, VM_getsurfacepoint); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + pointnum = (int)PRVM_G_FLOAT(OFS_PARM2); + if (pointnum < 0 || pointnum >= surface->num_vertices) + return; + attributetype = (int) PRVM_G_FLOAT(OFS_PARM3); + + animatemodel(model, ed); + + switch( attributetype ) { + // float SPA_POSITION = 0; + case 0: + applytransform_forward(&(animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN)); + break; + // float SPA_S_AXIS = 1; + case 1: + applytransform_forward_direction(&(animatemodel_cache.data_svector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN)); + break; + // float SPA_T_AXIS = 2; + case 2: + applytransform_forward_direction(&(animatemodel_cache.data_tvector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN)); + break; + // float SPA_R_AXIS = 3; // same as SPA_NORMAL + case 3: + applytransform_forward_direction(&(animatemodel_cache.data_normal3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN)); + break; + // float SPA_TEXCOORDS0 = 4; + case 4: { + float *ret = PRVM_G_VECTOR(OFS_RETURN); + float *texcoord = &(model->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[pointnum * 2]; + ret[0] = texcoord[0]; + ret[1] = texcoord[1]; + ret[2] = 0.0f; + break; + } + // float SPA_LIGHTMAP0_TEXCOORDS = 5; + case 5: { + float *ret = PRVM_G_VECTOR(OFS_RETURN); + float *texcoord = &(model->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[pointnum * 2]; + ret[0] = texcoord[0]; + ret[1] = texcoord[1]; + ret[2] = 0.0f; + break; + } + // float SPA_LIGHTMAP0_COLOR = 6; + case 6: + // ignore alpha for now.. + VectorCopy( &(model->surfmesh.data_lightmapcolor4f + 4 * surface->num_firstvertex)[pointnum * 4], PRVM_G_VECTOR(OFS_RETURN)); + break; + default: + VectorSet( PRVM_G_VECTOR(OFS_RETURN), 0.0f, 0.0f, 0.0f ); + break; + } +} +//PF_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal = #436; +void VM_getsurfacenormal(void) +{ + dp_model_t *model; + msurface_t *surface; + vec3_t normal; + VM_SAFEPARMCOUNT(2, VM_getsurfacenormal); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + // note: this only returns the first triangle, so it doesn't work very + // well for curved surfaces or arbitrary meshes + animatemodel(model, PRVM_G_EDICT(OFS_PARM0)); + TriangleNormal((animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex), (animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex) + 3, (animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex) + 6, normal); + applytransform_forward_normal(normal, PRVM_G_EDICT(OFS_PARM0), PRVM_G_VECTOR(OFS_RETURN)); + VectorNormalize(PRVM_G_VECTOR(OFS_RETURN)); +} +//PF_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture = #437; +void VM_getsurfacetexture(void) +{ + dp_model_t *model; + msurface_t *surface; + VM_SAFEPARMCOUNT(2, VM_getsurfacetexture); + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(surface->texture->name); +} +//PF_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint = #438; +void VM_getsurfacenearpoint(void) +{ + int surfacenum, best; + vec3_t clipped, p; + vec_t dist, bestdist; + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + vec_t *point; + VM_SAFEPARMCOUNT(2, VM_getsurfacenearpoint); + PRVM_G_FLOAT(OFS_RETURN) = -1; + ed = PRVM_G_EDICT(OFS_PARM0); + point = PRVM_G_VECTOR(OFS_PARM1); + + if (!ed || ed->priv.server->free) + return; + model = getmodel(ed); + if (!model || !model->num_surfaces) + return; + + animatemodel(model, ed); + + applytransform_inverted(point, ed, p); + best = -1; + bestdist = 1000000000; + for (surfacenum = 0;surfacenum < model->nummodelsurfaces;surfacenum++) + { + surface = model->data_surfaces + surfacenum + model->firstmodelsurface; + // first see if the nearest point on the surface's box is closer than the previous match + clipped[0] = bound(surface->mins[0], p[0], surface->maxs[0]) - p[0]; + clipped[1] = bound(surface->mins[1], p[1], surface->maxs[1]) - p[1]; + clipped[2] = bound(surface->mins[2], p[2], surface->maxs[2]) - p[2]; + dist = VectorLength2(clipped); + if (dist < bestdist) + { + // it is, check the nearest point on the actual geometry + clippointtosurface(ed, model, surface, p, clipped); + VectorSubtract(clipped, p, clipped); + dist += VectorLength2(clipped); + if (dist < bestdist) + { + // that's closer too, store it as the best match + best = surfacenum; + bestdist = dist; + } + } + } + PRVM_G_FLOAT(OFS_RETURN) = best; +} +//PF_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; +void VM_getsurfaceclippedpoint(void) +{ + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + vec3_t p, out; + VM_SAFEPARMCOUNT(3, VM_te_getsurfaceclippedpoint); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + animatemodel(model, ed); + applytransform_inverted(PRVM_G_VECTOR(OFS_PARM2), ed, p); + clippointtosurface(ed, model, surface, p, out); + VectorAdd(out, PRVM_serveredictvector(ed, origin), PRVM_G_VECTOR(OFS_RETURN)); +} + +//PF_getsurfacenumtriangles, // #??? float(entity e, float s) getsurfacenumtriangles = #???; +void VM_getsurfacenumtriangles(void) +{ + dp_model_t *model; + msurface_t *surface; + VM_SAFEPARMCOUNT(2, VM_SV_getsurfacenumtriangles); + // return 0 if no such surface + if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + + // note: this (incorrectly) assumes it is a simple polygon + PRVM_G_FLOAT(OFS_RETURN) = surface->num_triangles; +} +//PF_getsurfacetriangle, // #??? vector(entity e, float s, float n) getsurfacetriangle = #???; +void VM_getsurfacetriangle(void) +{ + const vec3_t d = {-1, -1, -1}; + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + int trinum; + VM_SAFEPARMCOUNT(3, VM_SV_getsurfacetriangle); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + trinum = (int)PRVM_G_FLOAT(OFS_PARM2); + if (trinum < 0 || trinum >= surface->num_triangles) + return; + // FIXME: implement rotation/scaling + VectorMA(&(model->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[trinum * 3], surface->num_firstvertex, d, PRVM_G_VECTOR(OFS_RETURN)); +} + +// +// physics builtins +// + +void World_Physics_ApplyCmd(prvm_edict_t *ed, edict_odefunc_t *f); + +#define VM_physics_ApplyCmd(ed,f) if (!ed->priv.server->ode_body) VM_physics_newstackfunction(ed, f); else World_Physics_ApplyCmd(ed, f) + +edict_odefunc_t *VM_physics_newstackfunction(prvm_edict_t *ed, edict_odefunc_t *f) +{ + edict_odefunc_t *newfunc, *func; + + newfunc = (edict_odefunc_t *)Mem_Alloc(prog->progs_mempool, sizeof(edict_odefunc_t)); + memcpy(newfunc, f, sizeof(edict_odefunc_t)); + newfunc->next = NULL; + if (!ed->priv.server->ode_func) + ed->priv.server->ode_func = newfunc; + else + { + for (func = ed->priv.server->ode_func; func->next; func = func->next); + func->next = newfunc; + } + return newfunc; +} + +// void(entity e, float physics_enabled) physics_enable = #; +void VM_physics_enable(void) +{ + prvm_edict_t *ed; + edict_odefunc_t f; + + VM_SAFEPARMCOUNT(2, VM_physics_enable); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!ed) + { + if (developer.integer > 0) + VM_Warning("VM_physics_enable: null entity!\n"); + return; + } + // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer + if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) + { + VM_Warning("VM_physics_enable: entity is not MOVETYPE_PHYSICS!\n"); + return; + } + f.type = PRVM_G_FLOAT(OFS_PARM1) == 0 ? ODEFUNC_DISABLE : ODEFUNC_ENABLE; + VM_physics_ApplyCmd(ed, &f); +} + +// void(entity e, vector force, vector relative_ofs) physics_addforce = #; +void VM_physics_addforce(void) +{ + prvm_edict_t *ed; + edict_odefunc_t f; + + VM_SAFEPARMCOUNT(3, VM_physics_addforce); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!ed) + { + if (developer.integer > 0) + VM_Warning("VM_physics_addforce: null entity!\n"); + return; + } + // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer + if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) + { + VM_Warning("VM_physics_addforce: entity is not MOVETYPE_PHYSICS!\n"); + return; + } + f.type = ODEFUNC_RELFORCEATPOS; + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f.v1); + VectorSubtract(PRVM_serveredictvector(ed, origin), PRVM_G_VECTOR(OFS_PARM2), f.v2); + VM_physics_ApplyCmd(ed, &f); +} + +// void(entity e, vector torque) physics_addtorque = #; +void VM_physics_addtorque(void) +{ + prvm_edict_t *ed; + edict_odefunc_t f; + + VM_SAFEPARMCOUNT(2, VM_physics_addtorque); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!ed) + { + if (developer.integer > 0) + VM_Warning("VM_physics_addtorque: null entity!\n"); + return; + } + // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer + if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) + { + VM_Warning("VM_physics_addtorque: entity is not MOVETYPE_PHYSICS!\n"); + return; + } + f.type = ODEFUNC_RELTORQUE; + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f.v1); + VM_physics_ApplyCmd(ed, &f); +} diff --git a/misc/source/darkplaces-src/prvm_cmds.h b/misc/source/darkplaces-src/prvm_cmds.h new file mode 100644 index 00000000..e8ed3cbc --- /dev/null +++ b/misc/source/darkplaces-src/prvm_cmds.h @@ -0,0 +1,480 @@ +// AK +// Basically every vm builtin cmd should be in here. +// All 3 builtin and extension lists can be found here +// cause large (I think they will) parts are from pr_cmds the same copyright like in pr_cmds +// also applies here + + +/* +============================================================================ +common cmd list: +================= + + checkextension(string) + error(...[string]) + objerror(...[string) + print(...[strings]) + bprint(...[string]) + sprint(float clientnum,...[string]) + centerprint(...[string]) +vector normalize(vector) +float vlen(vector) +float vectoyaw(vector) +vector vectoangles(vector) +float random() + cmd(string) + float cvar (string) + cvar_set (string,string) + dprint(...[string]) +string ftos(float) +float fabs(float) +string vtos(vector) +string etos(entity) +float stof(...[string]) +entity spawn() + remove(entity e) +entity find(entity start, .string field, string match) + +entity findfloat(entity start, .float field, float match) +entity findentity(entity start, .entity field, entity match) + +entity findchain(.string field, string match) + +entity findchainfloat(.string field, float match) +entity findchainentity(.string field, entity match) + +string precache_file(string) +string precache_sound (string sample) + coredump() + traceon() + traceoff() + eprint(entity e) +float rint(float) +float floor(float) +float ceil(float) +entity nextent(entity) +float sin(float) +float cos(float) +float sqrt(float) +vector randomvec() +float registercvar (string name, string value, float flags) +float min(float a, float b, ...[float]) +float max(float a, float b, ...[float]) +float bound(float min, float value, float max) +float pow(float a, float b) + copyentity(entity src, entity dst) +float fopen(string filename, float mode) + fclose(float fhandle) +string fgets(float fhandle) + fputs(float fhandle, string s) +float strlen(string s) +string strcat(string,string,...[string]) +string substring(string s, float start, float length) +vector stov(string s) +string strzone(string s) + strunzone(string s) +float tokenize(string s) +string argv(float n) +float isserver() +float clientcount() +float clientstate() + clientcommand(float client, string s) (for client and menu) + changelevel(string map) + localsound(string sample) +vector getmousepos() +float gettime() + loadfromdata(string data) + loadfromfile(string file) + parseentitydata(entity ent, string data) +float mod(float val, float m) +const string cvar_string (string) +float cvar_type (string) + crash() + stackdump() + +float search_begin(string pattern, float caseinsensitive, float quiet) +void search_end(float handle) +float search_getsize(float handle) +string search_getfilename(float handle, float num) + +string chr(float ascii) + +float itof(intt ent) +entity ftoe(float num) + +-------will be removed soon---------- +float altstr_count(string) +string altstr_prepare(string) +string altstr_get(string,float) +string altstr_set(string altstr, float num, string set) +string altstr_ins(string altstr, float num, string set) +-------------------------------------- + +entity findflags(entity start, .float field, float match) +entity findchainflags(.float field, float match) + +const string VM_cvar_defstring (string) + +perhaps only : Menu : WriteMsg +=============================== + + WriteByte(float data, float dest, float desto) + WriteChar(float data, float dest, float desto) + WriteShort(float data, float dest, float desto) + WriteLong(float data, float dest, float desto) + WriteAngle(float data, float dest, float desto) + WriteCoord(float data, float dest, float desto) + WriteString(string data, float dest, float desto) + WriteEntity(entity data, float dest, float desto) + +Client & Menu : draw functions & video functions (& gecko functions) +=================================================== + +float iscachedpic(string pic) +string precache_pic(string pic) + freepic(string s) +float drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) +float drawstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) +float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) +float stringwidth(string text, float handleColors) +float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) +float drawsubpic(vector position, vector size, string pic, vector srcPos, vector srcSize, vector rgb, float alpha, float flag) +float drawfill(vector position, vector size, vector rgb, float alpha, float flag) + drawsetcliparea(float x, float y, float width, float height) + drawresetcliparea() +vector getimagesize(string pic) + +float cin_open(string file, string name) +void cin_close(string name) +void cin_setstate(string name, float type) +float cin_getstate(string name) +void cin_restart(string name) + +float[bool] gecko_create( string name ) +void gecko_destroy( string name ) +void gecko_navigate( string name, string URI ) +float[bool] gecko_keyevent( string name, float key, float eventtype ) +void gecko_mousemove( string name, float x, float y ) + +============================================================================== +menu cmd list: +=============== + + setkeydest(float dest) +float getkeydest() + setmousetarget(float target) +float getmousetarget() + + callfunction(...,string function_name) + writetofile(float fhandle, entity ent) +float isfunction(string function_name) +vector getresolution(float number) +string keynumtostring(float keynum) +string findkeysforcommand(string command) +float getserverliststat(float type) +string getserverliststring(float fld, float hostnr) + +float stringtokeynum(string key) + + resetserverlistmasks() + setserverlistmaskstring(float mask, float fld, string str) + setserverlistmasknumber(float mask, float fld, float num, float op) + resortserverlist() + setserverlistsort(float field, float descending) + refreshserverlist() +float getserverlistnumber(float fld, float hostnr) +float getserverlistindexforkey(string key) + addwantedserverlistkey(string key) +*/ + +#include "quakedef.h" +#include "progdefs.h" +#include "progsvm.h" +#include "clprogdefs.h" +#include "mprogdefs.h" + +#include "cl_video.h" +#include "cl_gecko.h" + +//============================================================================ +// nice helper macros + +#ifndef VM_NOPARMCHECK +#define VM_SAFEPARMCOUNTRANGE(p1,p2,f) if(prog->argc < p1 || prog->argc > p2) PRVM_ERROR(#f " wrong parameter count %i (" #p1 " to " #p2 " expected ) !", prog->argc) +#define VM_SAFEPARMCOUNT(p,f) if(prog->argc != p) PRVM_ERROR(#f " wrong parameter count %i (" #p " expected ) !", prog->argc) +#else +#define VM_SAFEPARMCOUNTRANGE(p1,p2,f) +#define VM_SAFEPARMCOUNT(p,f) +#endif + +#define VM_RETURN_EDICT(e) (((int *)prog->globals.generic)[OFS_RETURN] = PRVM_EDICT_TO_PROG(e)) + +#define VM_STRINGTEMP_LENGTH MAX_INPUTLINE + +// builtins and other general functions + +void VM_CheckEmptyString (const char *s); +void VM_VarString(int first, char *out, int outlength); + +void VM_checkextension (void); +void VM_error (void); +void VM_objerror (void); +void VM_print (void); +void VM_bprint (void); +void VM_sprint (void); +void VM_centerprint (void); +void VM_normalize (void); +void VM_vlen (void); +void VM_vectoyaw (void); +void VM_vectoangles (void); +void VM_random (void); +void VM_localsound(void); +void VM_break (void); +void VM_localcmd (void); +void VM_cvar (void); +void VM_cvar_string(void); +void VM_cvar_type (void); +void VM_cvar_defstring (void); +void VM_cvar_set (void); +void VM_dprint (void); +void VM_ftos (void); +void VM_fabs (void); +void VM_vtos (void); +void VM_etos (void); +void VM_stof(void); +void VM_itof(void); +void VM_ftoe(void); +void VM_strftime(void); +void VM_spawn (void); +void VM_remove (void); +void VM_find (void); +void VM_findfloat (void); +void VM_findchain (void); +void VM_findchainfloat (void); +void VM_findflags (void); +void VM_findchainflags (void); +void VM_precache_file (void); +void VM_precache_sound (void); +void VM_coredump (void); + +void VM_stackdump (void); +void VM_crash(void); // REMOVE IT +void VM_traceon (void); +void VM_traceoff (void); +void VM_eprint (void); +void VM_rint (void); +void VM_floor (void); +void VM_ceil (void); +void VM_nextent (void); + +void VM_changelevel (void); +void VM_sin (void); +void VM_cos (void); +void VM_sqrt (void); +void VM_randomvec (void); +void VM_registercvar (void); +void VM_min (void); +void VM_max (void); +void VM_bound (void); +void VM_pow (void); +void VM_log (void); +void VM_asin (void); +void VM_acos (void); +void VM_atan (void); +void VM_atan2 (void); +void VM_tan (void); + +void VM_Files_Init(void); +void VM_Files_CloseAll(void); + +void VM_fopen(void); +void VM_fclose(void); +void VM_fgets(void); +void VM_fputs(void); +void VM_writetofile(void); // only used by menu + +void VM_strlen(void); +void VM_strcat(void); +void VM_substring(void); +void VM_stov(void); +void VM_strzone(void); +void VM_strunzone(void); + +// KrimZon - DP_QC_ENTITYDATA +void VM_numentityfields(void); +void VM_entityfieldname(void); +void VM_entityfieldtype(void); +void VM_getentityfieldstring(void); +void VM_putentityfieldstring(void); +// And declared these ones for VM_getentityfieldstring and VM_putentityfieldstring in prvm_cmds.c +// the function is from prvm_edict.c +char *PRVM_UglyValueString (etype_t type, prvm_eval_t *val); +qboolean PRVM_ED_ParseEpair(prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash); + +// DRESK - String Length (not counting color codes) +void VM_strlennocol(void); +// DRESK - Decolorized String +void VM_strdecolorize(void); +// DRESK - String Uppercase and Lowercase Support +void VM_strtolower(void); +void VM_strtoupper(void); + +void VM_clcommand (void); + +void VM_tokenize (void); +void VM_tokenizebyseparator (void); +void VM_argv (void); + +void VM_isserver(void); +void VM_clientcount(void); +void VM_clientstate(void); +// not used at the moment -> not included in the common list +void VM_getostype(void); +void VM_getmousepos(void); +void VM_gettime(void); +void VM_getsoundtime(void); +void VM_soundlength(void); +void VM_loadfromdata(void); +void VM_parseentitydata(void); +void VM_loadfromfile(void); +void VM_modulo(void); + +void VM_search_begin(void); +void VM_search_end(void); +void VM_search_getsize(void); +void VM_search_getfilename(void); +void VM_chr(void); +void VM_iscachedpic(void); +void VM_precache_pic(void); +void VM_freepic(void); +void VM_drawcharacter(void); +void VM_drawstring(void); +void VM_drawcolorcodedstring(void); +void VM_stringwidth(void); +void VM_drawpic(void); +void VM_drawrotpic(void); +void VM_drawsubpic(void); +void VM_drawfill(void); +void VM_drawsetcliparea(void); +void VM_drawresetcliparea(void); +void VM_getimagesize(void); + +void VM_findfont(void); +void VM_loadfont(void); + +void VM_makevectors (void); +void VM_vectorvectors (void); + +void VM_keynumtostring (void); +void VM_getkeybind (void); +void VM_findkeysforcommand (void); +void VM_stringtokeynum (void); +void VM_setkeybind (void); +void VM_getbindmaps (void); +void VM_setbindmaps (void); + +void VM_cin_open( void ); +void VM_cin_close( void ); +void VM_cin_setstate( void ); +void VM_cin_getstate( void ); +void VM_cin_restart( void ); + +void VM_gecko_create( void ); +void VM_gecko_destroy( void ); +void VM_gecko_navigate( void ); +void VM_gecko_keyevent( void ); +void VM_gecko_movemouse( void ); +void VM_gecko_resize( void ); +void VM_gecko_get_texture_extent( void ); + +void VM_drawline (void); + +void VM_bitshift (void); + +void VM_altstr_count( void ); +void VM_altstr_prepare( void ); +void VM_altstr_get( void ); +void VM_altstr_set( void ); +void VM_altstr_ins(void); + +void VM_buf_create(void); +void VM_buf_del (void); +void VM_buf_getsize (void); +void VM_buf_copy (void); +void VM_buf_sort (void); +void VM_buf_implode (void); +void VM_bufstr_get (void); +void VM_bufstr_set (void); +void VM_bufstr_add (void); +void VM_bufstr_free (void); + +void VM_changeyaw (void); +void VM_changepitch (void); + +void VM_uncolorstring (void); + +void VM_strstrofs (void); +void VM_str2chr (void); +void VM_chr2str (void); +void VM_strconv (void); +void VM_strpad (void); +void VM_infoadd (void); +void VM_infoget (void); +void VM_strncmp (void); +void VM_strncmp (void); +void VM_strncasecmp (void); +void VM_registercvar (void); +void VM_wasfreed (void); + +void VM_strreplace (void); +void VM_strireplace (void); + +void VM_crc16(void); + +void VM_SetTraceGlobals(const trace_t *trace); +void VM_ClearTraceGlobals(void); + +void VM_Cmd_Init(void); +void VM_Cmd_Reset(void); + +void VM_uri_escape (void); +void VM_uri_unescape (void); +void VM_whichpack (void); + +void VM_etof (void); +void VM_uri_get (void); +void VM_netaddress_resolve (void); + +void VM_tokenize_console (void); +void VM_argv_start_index (void); +void VM_argv_end_index (void); + +void VM_buf_cvarlist(void); +void VM_cvar_description(void); + +void VM_CL_getextresponse (void); +void VM_SV_getextresponse (void); + +// Common functions between menu.dat and clsprogs +void VM_CL_isdemo (void); +void VM_CL_videoplaying (void); + +void VM_isfunction(void); +void VM_callfunction(void); + +void VM_sprintf(void); + +void VM_getsurfacenumpoints(void); +void VM_getsurfacepoint(void); +void VM_getsurfacepointattribute(void); +void VM_getsurfacenormal(void); +void VM_getsurfacetexture(void); +void VM_getsurfacenearpoint(void); +void VM_getsurfaceclippedpoint(void); +void VM_getsurfacenumtriangles(void); +void VM_getsurfacetriangle(void); + +// physics builtins +void VM_physics_enable(void); +void VM_physics_addforce(void); +void VM_physics_addtorque(void); diff --git a/misc/source/darkplaces-src/prvm_edict.c b/misc/source/darkplaces-src/prvm_edict.c new file mode 100644 index 00000000..ffe917a8 --- /dev/null +++ b/misc/source/darkplaces-src/prvm_edict.c @@ -0,0 +1,3224 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// AK new vm + +#include "quakedef.h" +#include "progsvm.h" +#include "csprogs.h" + +prvm_prog_t *prog; + +static prvm_prog_t prog_list[PRVM_MAXPROGS]; + +int prvm_type_size[8] = {1,sizeof(string_t)/4,1,3,1,1,sizeof(func_t)/4,sizeof(void *)/4}; + +prvm_eval_t prvm_badvalue; // used only for error returns + +ddef_t *PRVM_ED_FieldAtOfs(int ofs); +qboolean PRVM_ED_ParseEpair(prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash); + +cvar_t prvm_language = {CVAR_SAVE, "prvm_language", "", "when set, loads progs.dat.LANGUAGENAME.po for string translations; when set to dump, progs.dat.pot is written from the strings in the progs"}; +// LordHavoc: prints every opcode as it executes - warning: this is significant spew +cvar_t prvm_traceqc = {0, "prvm_traceqc", "0", "prints every QuakeC statement as it is executed (only for really thorough debugging!)"}; +// LordHavoc: counts usage of each QuakeC statement +cvar_t prvm_statementprofiling = {0, "prvm_statementprofiling", "0", "counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled)"}; +cvar_t prvm_timeprofiling = {0, "prvm_timeprofiling", "0", "counts how long each function has been executed, these counts are displayed in prvm_profile output (if enabled)"}; +cvar_t prvm_backtraceforwarnings = {0, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"}; +cvar_t prvm_leaktest = {0, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"}; +cvar_t prvm_leaktest_ignore_classnames = {0, "prvm_leaktest_ignore_classnames", "", "classnames of entities to NOT leak check because they are found by find(world, classname, ...) but are actually spawned by QC code (NOT map entities)"}; +cvar_t prvm_errordump = {0, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"}; +cvar_t prvm_reuseedicts_startuptime = {0, "prvm_reuseedicts_startuptime", "2", "allows immediate re-use of freed entity slots during start of new level (value in seconds)"}; +cvar_t prvm_reuseedicts_neverinsameframe = {0, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"}; + +static double prvm_reuseedicts_always_allow = 0; +qboolean prvm_runawaycheck = true; + +extern sizebuf_t vm_tempstringsbuf; + +//============================================================================ +// mempool handling + +/* +=============== +PRVM_MEM_Alloc +=============== +*/ +void PRVM_MEM_Alloc(void) +{ + int i; + + // reserve space for the null entity aka world + // check bound of max_edicts + prog->max_edicts = bound(1 + prog->reserved_edicts, prog->max_edicts, prog->limit_edicts); + prog->num_edicts = bound(1 + prog->reserved_edicts, prog->num_edicts, prog->max_edicts); + + // edictprivate_size has to be min as big prvm_edict_private_t + prog->edictprivate_size = max(prog->edictprivate_size,(int)sizeof(prvm_edict_private_t)); + + // alloc edicts + prog->edicts = (prvm_edict_t *)Mem_Alloc(prog->progs_mempool,prog->limit_edicts * sizeof(prvm_edict_t)); + + // alloc edict private space + prog->edictprivate = Mem_Alloc(prog->progs_mempool, prog->max_edicts * prog->edictprivate_size); + + // alloc edict fields + prog->entityfieldsarea = prog->entityfields * prog->max_edicts; + prog->edictsfields = (vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(vec_t)); + + // set edict pointers + for(i = 0; i < prog->max_edicts; i++) + { + prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size); + prog->edicts[i].fields.vp = prog->edictsfields + i * prog->entityfields; + } +} + +/* +=============== +PRVM_MEM_IncreaseEdicts +=============== +*/ +void PRVM_MEM_IncreaseEdicts(void) +{ + int i; + + if(prog->max_edicts >= prog->limit_edicts) + return; + + PRVM_GCALL(begin_increase_edicts)(); + + // increase edicts + prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts); + + prog->entityfieldsarea = prog->entityfields * prog->max_edicts; + prog->edictsfields = (vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields, prog->entityfieldsarea * sizeof(vec_t)); + prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size); + + //set e and v pointers + for(i = 0; i < prog->max_edicts; i++) + { + prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size); + prog->edicts[i].fields.vp = prog->edictsfields + i * prog->entityfields; + } + + PRVM_GCALL(end_increase_edicts)(); +} + +//============================================================================ +// normal prvm + +int PRVM_ED_FindFieldOffset(const char *field) +{ + ddef_t *d; + d = PRVM_ED_FindField(field); + if (!d) + return -1; + return d->ofs; +} + +int PRVM_ED_FindGlobalOffset(const char *global) +{ + ddef_t *d; + d = PRVM_ED_FindGlobal(global); + if (!d) + return -1; + return d->ofs; +} + +func_t PRVM_ED_FindFunctionOffset(const char *function) +{ + mfunction_t *f; + f = PRVM_ED_FindFunction(function); + if (!f) + return 0; + return (func_t)(f - prog->functions); +} + +qboolean PRVM_ProgLoaded(int prognr) +{ + if(prognr < 0 || prognr >= PRVM_MAXPROGS) + return FALSE; + + return (prog_list[prognr].loaded ? TRUE : FALSE); +} + +/* +================= +PRVM_SetProgFromString +================= +*/ +// perhaps add a return value when the str doesnt exist +qboolean PRVM_SetProgFromString(const char *str) +{ + int i = 0; + for(; i < PRVM_MAXPROGS ; i++) + if(prog_list[i].name && !strcmp(prog_list[i].name,str)) + { + if(prog_list[i].loaded) + { + prog = &prog_list[i]; + return TRUE; + } + else + { + Con_Printf("%s not loaded !\n",PRVM_NAME); + return FALSE; + } + } + + Con_Printf("Invalid program name %s !\n", str); + return FALSE; +} + +/* +================= +PRVM_SetProg +================= +*/ +void PRVM_SetProg(int prognr) +{ + if(0 <= prognr && prognr < PRVM_MAXPROGS) + { + if(prog_list[prognr].loaded) + prog = &prog_list[prognr]; + else + PRVM_ERROR("%i not loaded !", prognr); + return; + } + PRVM_ERROR("Invalid program number %i", prognr); +} + +/* +================= +PRVM_ED_ClearEdict + +Sets everything to NULL +================= +*/ +void PRVM_ED_ClearEdict (prvm_edict_t *e) +{ + memset (e->fields.vp, 0, prog->entityfields * 4); + e->priv.required->free = false; + + // AK: Let the init_edict function determine if something needs to be initialized + PRVM_GCALL(init_edict)(e); +} + +const char *PRVM_AllocationOrigin(void) +{ + char *buf = NULL; + if(prog->leaktest_active) + if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame + { + buf = (char *)PRVM_Alloc(128); + PRVM_ShortStackTrace(buf, 128); + } + return buf; +} + +/* +================= +PRVM_ED_CanAlloc + +Returns if this particular edict could get allocated by PRVM_ED_Alloc +================= +*/ +qboolean PRVM_ED_CanAlloc(prvm_edict_t *e) +{ + if(!e->priv.required->free) + return false; + if(prvm_reuseedicts_always_allow == realtime) + return true; + if(realtime <= e->priv.required->freetime && prvm_reuseedicts_neverinsameframe.integer) + return false; // never allow reuse in same frame (causes networking trouble) + if(e->priv.required->freetime < prog->starttime + prvm_reuseedicts_startuptime.value) + return true; + if(realtime > e->priv.required->freetime + 1) + return true; + return false; // entity slot still blocked because the entity was freed less than one second ago +} + +/* +================= +PRVM_ED_Alloc + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +prvm_edict_t *PRVM_ED_Alloc (void) +{ + int i; + prvm_edict_t *e; + + // the client qc dont need maxclients + // thus it doesnt need to use svs.maxclients + // AK: changed i=svs.maxclients+1 + // AK: changed so the edict 0 wont spawn -> used as reserved/world entity + // although the menu/client has no world + for (i = prog->reserved_edicts + 1;i < prog->num_edicts;i++) + { + e = PRVM_EDICT_NUM(i); + if(PRVM_ED_CanAlloc(e)) + { + PRVM_ED_ClearEdict (e); + e->priv.required->allocation_origin = PRVM_AllocationOrigin(); + return e; + } + } + + if (i == prog->limit_edicts) + PRVM_ERROR ("%s: PRVM_ED_Alloc: no free edicts",PRVM_NAME); + + prog->num_edicts++; + if (prog->num_edicts >= prog->max_edicts) + PRVM_MEM_IncreaseEdicts(); + + e = PRVM_EDICT_NUM(i); + PRVM_ED_ClearEdict (e); + + e->priv.required->allocation_origin = PRVM_AllocationOrigin(); + + return e; +} + +/* +================= +PRVM_ED_Free + +Marks the edict as free +FIXME: walk all entities and NULL out references to this entity +================= +*/ +void PRVM_ED_Free (prvm_edict_t *ed) +{ + // dont delete the null entity (world) or reserved edicts + if(PRVM_NUM_FOR_EDICT(ed) <= prog->reserved_edicts ) + return; + + PRVM_GCALL(free_edict)(ed); + + ed->priv.required->free = true; + ed->priv.required->freetime = realtime; + if(ed->priv.required->allocation_origin) + { + PRVM_Free((char *)ed->priv.required->allocation_origin); + ed->priv.required->allocation_origin = NULL; + } +} + +//=========================================================================== + +/* +============ +PRVM_ED_GlobalAtOfs +============ +*/ +ddef_t *PRVM_ED_GlobalAtOfs (int ofs) +{ + ddef_t *def; + int i; + + for (i = 0;i < prog->numglobaldefs;i++) + { + def = &prog->globaldefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FieldAtOfs +============ +*/ +ddef_t *PRVM_ED_FieldAtOfs (int ofs) +{ + ddef_t *def; + int i; + + for (i = 0;i < prog->numfielddefs;i++) + { + def = &prog->fielddefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FindField +============ +*/ +ddef_t *PRVM_ED_FindField (const char *name) +{ + ddef_t *def; + int i; + + for (i = 0;i < prog->numfielddefs;i++) + { + def = &prog->fielddefs[i]; + if (!strcmp(PRVM_GetString(def->s_name), name)) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FindGlobal +============ +*/ +ddef_t *PRVM_ED_FindGlobal (const char *name) +{ + ddef_t *def; + int i; + + for (i = 0;i < prog->numglobaldefs;i++) + { + def = &prog->globaldefs[i]; + if (!strcmp(PRVM_GetString(def->s_name), name)) + return def; + } + return NULL; +} + + +/* +============ +PRVM_ED_FindFunction +============ +*/ +mfunction_t *PRVM_ED_FindFunction (const char *name) +{ + mfunction_t *func; + int i; + + for (i = 0;i < prog->numfunctions;i++) + { + func = &prog->functions[i]; + if (!strcmp(PRVM_GetString(func->s_name), name)) + return func; + } + return NULL; +} + + +/* +============ +PRVM_ValueString + +Returns a string describing *data in a type specific manner +============= +*/ +char *PRVM_ValueString (etype_t type, prvm_eval_t *val) +{ + static char line[MAX_INPUTLINE]; + ddef_t *def; + mfunction_t *f; + int n; + + type = (etype_t)((int) type & ~DEF_SAVEGLOBAL); + + switch (type) + { + case ev_string: + strlcpy (line, PRVM_GetString (val->string), sizeof (line)); + break; + case ev_entity: + n = val->edict; + if (n < 0 || n >= prog->max_edicts) + dpsnprintf (line, sizeof(line), "entity %i (invalid!)", n); + else + dpsnprintf (line, sizeof(line), "entity %i", n); + break; + case ev_function: + f = prog->functions + val->function; + dpsnprintf (line, sizeof(line), "%s()", PRVM_GetString(f->s_name)); + break; + case ev_field: + def = PRVM_ED_FieldAtOfs ( val->_int ); + dpsnprintf (line, sizeof(line), ".%s", PRVM_GetString(def->s_name)); + break; + case ev_void: + dpsnprintf (line, sizeof(line), "void"); + break; + case ev_float: + // LordHavoc: changed from %5.1f to %10.4f + dpsnprintf (line, sizeof(line), "%10.4f", val->_float); + break; + case ev_vector: + // LordHavoc: changed from %5.1f to %10.4f + dpsnprintf (line, sizeof(line), "'%10.4f %10.4f %10.4f'", val->vector[0], val->vector[1], val->vector[2]); + break; + case ev_pointer: + dpsnprintf (line, sizeof(line), "pointer"); + break; + default: + dpsnprintf (line, sizeof(line), "bad type %i", (int) type); + break; + } + + return line; +} + +/* +============ +PRVM_UglyValueString + +Returns a string describing *data in a type specific manner +Easier to parse than PR_ValueString +============= +*/ +char *PRVM_UglyValueString (etype_t type, prvm_eval_t *val) +{ + static char line[MAX_INPUTLINE]; + int i; + const char *s; + ddef_t *def; + mfunction_t *f; + + type = (etype_t)((int)type & ~DEF_SAVEGLOBAL); + + switch (type) + { + case ev_string: + // Parse the string a bit to turn special characters + // (like newline, specifically) into escape codes, + // this fixes saving games from various mods + s = PRVM_GetString (val->string); + for (i = 0;i < (int)sizeof(line) - 2 && *s;) + { + if (*s == '\n') + { + line[i++] = '\\'; + line[i++] = 'n'; + } + else if (*s == '\r') + { + line[i++] = '\\'; + line[i++] = 'r'; + } + else if (*s == '\\') + { + line[i++] = '\\'; + line[i++] = '\\'; + } + else if (*s == '"') + { + line[i++] = '\\'; + line[i++] = '"'; + } + else + line[i++] = *s; + s++; + } + line[i] = '\0'; + break; + case ev_entity: + dpsnprintf (line, sizeof (line), "%i", PRVM_NUM_FOR_EDICT(PRVM_PROG_TO_EDICT(val->edict))); + break; + case ev_function: + f = prog->functions + val->function; + strlcpy (line, PRVM_GetString (f->s_name), sizeof (line)); + break; + case ev_field: + def = PRVM_ED_FieldAtOfs ( val->_int ); + dpsnprintf (line, sizeof (line), ".%s", PRVM_GetString(def->s_name)); + break; + case ev_void: + dpsnprintf (line, sizeof (line), "void"); + break; + case ev_float: + dpsnprintf (line, sizeof (line), "%.9g", val->_float); + break; + case ev_vector: + dpsnprintf (line, sizeof (line), "%.9g %.9g %.9g", val->vector[0], val->vector[1], val->vector[2]); + break; + default: + dpsnprintf (line, sizeof (line), "bad type %i", type); + break; + } + + return line; +} + +/* +============ +PRVM_GlobalString + +Returns a string with a description and the contents of a global, +padded to 20 field width +============ +*/ +char *PRVM_GlobalString (int ofs) +{ + char *s; + //size_t i; + ddef_t *def; + void *val; + static char line[128]; + + val = (void *)&prog->globals.generic[ofs]; + def = PRVM_ED_GlobalAtOfs(ofs); + if (!def) + dpsnprintf (line, sizeof(line), "GLOBAL%i", ofs); + else + { + s = PRVM_ValueString ((etype_t)def->type, (prvm_eval_t *)val); + dpsnprintf (line, sizeof(line), "%s (=%s)", PRVM_GetString(def->s_name), s); + } + + //i = strlen(line); + //for ( ; i<20 ; i++) + // strcat (line," "); + //strcat (line," "); + + return line; +} + +char *PRVM_GlobalStringNoContents (int ofs) +{ + //size_t i; + ddef_t *def; + static char line[128]; + + def = PRVM_ED_GlobalAtOfs(ofs); + if (!def) + dpsnprintf (line, sizeof(line), "GLOBAL%i", ofs); + else + dpsnprintf (line, sizeof(line), "%s", PRVM_GetString(def->s_name)); + + //i = strlen(line); + //for ( ; i<20 ; i++) + // strcat (line," "); + //strcat (line," "); + + return line; +} + + +/* +============= +PRVM_ED_Print + +For debugging +============= +*/ +// LordHavoc: optimized this to print out much more quickly (tempstring) +// LordHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print) +void PRVM_ED_Print(prvm_edict_t *ed, const char *wildcard_fieldname) +{ + size_t l; + ddef_t *d; + int *v; + int i, j; + const char *name; + int type; + char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers + + if (ed->priv.required->free) + { + Con_Printf("%s: FREE\n",PRVM_NAME); + return; + } + + tempstring[0] = 0; + dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", PRVM_NAME, PRVM_NUM_FOR_EDICT(ed)); + for (i = 1;i < prog->numfielddefs;i++) + { + d = &prog->fielddefs[i]; + name = PRVM_GetString(d->s_name); + if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) + continue; // skip _x, _y, _z vars + + // Check Field Name Wildcard + if(wildcard_fieldname) + if( !matchpattern(name, wildcard_fieldname, 1) ) + // Didn't match; skip + continue; + + v = (int *)(ed->fields.vp + d->ofs); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + + for (j=0 ; j sizeof(tempstring2)-4) + { + memcpy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + strlcat(tempstring, name, sizeof(tempstring)); + for (l = strlen(name);l < 14;l++) + strlcat(tempstring, " ", sizeof(tempstring)); + strlcat(tempstring, " ", sizeof(tempstring)); + + name = PRVM_ValueString((etype_t)d->type, (prvm_eval_t *)v); + if (strlen(name) > sizeof(tempstring2)-4) + { + memcpy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + strlcat(tempstring, name, sizeof(tempstring)); + strlcat(tempstring, "\n", sizeof(tempstring)); + if (strlen(tempstring) >= sizeof(tempstring)/2) + { + Con_Print(tempstring); + tempstring[0] = 0; + } + } + if (tempstring[0]) + Con_Print(tempstring); +} + +/* +============= +PRVM_ED_Write + +For savegames +============= +*/ +extern cvar_t developer_entityparsing; +void PRVM_ED_Write (qfile_t *f, prvm_edict_t *ed) +{ + ddef_t *d; + int *v; + int i, j; + const char *name; + int type; + + FS_Print(f, "{\n"); + + if (ed->priv.required->free) + { + FS_Print(f, "}\n"); + return; + } + + for (i = 1;i < prog->numfielddefs;i++) + { + d = &prog->fielddefs[i]; + name = PRVM_GetString(d->s_name); + + if(developer_entityparsing.integer) + Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name); + + //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) + if(strlen(name) > 1 && name[strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars, and ALSO other _? vars as some mods expect them to be never saved (TODO: a gameplayfix for using the "more precise" condition above?) + + v = (int *)(ed->fields.vp + d->ofs); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + for (j=0 ; jstatestring = va("PRVM_ED_Write, ent=%d, name=%s", i, name); + FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString((etype_t)d->type, (prvm_eval_t *)v)); + prog->statestring = NULL; + } + + FS_Print(f, "}\n"); +} + +void PRVM_ED_PrintNum (int ent, const char *wildcard_fieldname) +{ + PRVM_ED_Print(PRVM_EDICT_NUM(ent), wildcard_fieldname); +} + +/* +============= +PRVM_ED_PrintEdicts_f + +For debugging, prints all the entities in the current server +============= +*/ +void PRVM_ED_PrintEdicts_f (void) +{ + int i; + const char *wildcard_fieldname; + + if(Cmd_Argc() < 2 || Cmd_Argc() > 3) + { + Con_Print("prvm_edicts \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + if( Cmd_Argc() == 3) + wildcard_fieldname = Cmd_Argv(2); + else + wildcard_fieldname = NULL; + + Con_Printf("%s: %i entities\n", PRVM_NAME, prog->num_edicts); + for (i=0 ; inum_edicts ; i++) + PRVM_ED_PrintNum (i, wildcard_fieldname); + + PRVM_End; +} + +/* +============= +PRVM_ED_PrintEdict_f + +For debugging, prints a single edict +============= +*/ +void PRVM_ED_PrintEdict_f (void) +{ + int i; + const char *wildcard_fieldname; + + if(Cmd_Argc() < 3 || Cmd_Argc() > 4) + { + Con_Print("prvm_edict \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + i = atoi (Cmd_Argv(2)); + if (i >= prog->num_edicts) + { + Con_Print("Bad edict number\n"); + PRVM_End; + return; + } + if( Cmd_Argc() == 4) + // Optional Wildcard Provided + wildcard_fieldname = Cmd_Argv(3); + else + // Use All + wildcard_fieldname = NULL; + PRVM_ED_PrintNum (i, wildcard_fieldname); + + PRVM_End; +} + +/* +============= +PRVM_ED_Count + +For debugging +============= +*/ +// 2 possibilities : 1. just displaying the active edict count +// 2. making a function pointer [x] +void PRVM_ED_Count_f (void) +{ + int i; + prvm_edict_t *ent; + int active; + + if(Cmd_Argc() != 2) + { + Con_Print("prvm_count \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + if(prog->count_edicts) + prog->count_edicts(); + else + { + active = 0; + for (i=0 ; inum_edicts ; i++) + { + ent = PRVM_EDICT_NUM(i); + if (ent->priv.required->free) + continue; + active++; + } + + Con_Printf("num_edicts:%3i\n", prog->num_edicts); + Con_Printf("active :%3i\n", active); + } + + PRVM_End; +} + +/* +============================================================================== + + ARCHIVING GLOBALS + +FIXME: need to tag constants, doesn't really work +============================================================================== +*/ + +/* +============= +PRVM_ED_WriteGlobals +============= +*/ +void PRVM_ED_WriteGlobals (qfile_t *f) +{ + ddef_t *def; + int i; + const char *name; + int type; + + FS_Print(f,"{\n"); + for (i = 0;i < prog->numglobaldefs;i++) + { + def = &prog->globaldefs[i]; + type = def->type; + if ( !(def->type & DEF_SAVEGLOBAL) ) + continue; + type &= ~DEF_SAVEGLOBAL; + + if (type != ev_string && type != ev_float && type != ev_entity) + continue; + + name = PRVM_GetString(def->s_name); + + if(developer_entityparsing.integer) + Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name); + + prog->statestring = va("PRVM_ED_WriteGlobals, name=%s", name); + FS_Printf(f,"\"%s\" ", name); + FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString((etype_t)type, (prvm_eval_t *)&prog->globals.generic[def->ofs])); + prog->statestring = NULL; + } + FS_Print(f,"}\n"); +} + +/* +============= +PRVM_ED_ParseGlobals +============= +*/ +void PRVM_ED_ParseGlobals (const char *data) +{ + char keyname[MAX_INPUTLINE]; + ddef_t *key; + + while (1) + { + // parse key + if (!COM_ParseToken_Simple(&data, false, false)) + PRVM_ERROR ("PRVM_ED_ParseGlobals: EOF without closing brace"); + if (com_token[0] == '}') + break; + + if (developer_entityparsing.integer) + Con_Printf("Key: \"%s\"", com_token); + + strlcpy (keyname, com_token, sizeof(keyname)); + + // parse value + if (!COM_ParseToken_Simple(&data, false, true)) + PRVM_ERROR ("PRVM_ED_ParseGlobals: EOF without closing brace"); + + if (developer_entityparsing.integer) + Con_Printf(" \"%s\"\n", com_token); + + if (com_token[0] == '}') + PRVM_ERROR ("PRVM_ED_ParseGlobals: closing brace without data"); + + key = PRVM_ED_FindGlobal (keyname); + if (!key) + { + Con_DPrintf("'%s' is not a global on %s\n", keyname, PRVM_NAME); + continue; + } + + if (!PRVM_ED_ParseEpair(NULL, key, com_token, true)) + PRVM_ERROR ("PRVM_ED_ParseGlobals: parse error"); + } +} + +//============================================================================ + + +/* +============= +PRVM_ED_ParseEval + +Can parse either fields or globals +returns false if error +============= +*/ +qboolean PRVM_ED_ParseEpair(prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash) +{ + int i, l; + char *new_p; + ddef_t *def; + prvm_eval_t *val; + mfunction_t *func; + + if (ent) + val = (prvm_eval_t *)(ent->fields.vp + key->ofs); + else + val = (prvm_eval_t *)(prog->globals.generic + key->ofs); + switch (key->type & ~DEF_SAVEGLOBAL) + { + case ev_string: + l = (int)strlen(s) + 1; + val->string = PRVM_AllocString(l, &new_p); + for (i = 0;i < l;i++) + { + if (s[i] == '\\' && s[i+1] && parsebackslash) + { + i++; + if (s[i] == 'n') + *new_p++ = '\n'; + else if (s[i] == 'r') + *new_p++ = '\r'; + else + *new_p++ = s[i]; + } + else + *new_p++ = s[i]; + } + break; + + case ev_float: + while (*s && ISWHITESPACE(*s)) + s++; + val->_float = atof(s); + break; + + case ev_vector: + for (i = 0;i < 3;i++) + { + while (*s && ISWHITESPACE(*s)) + s++; + if (!*s) + break; + val->vector[i] = atof(s); + while (!ISWHITESPACE(*s)) + s++; + if (!*s) + break; + } + break; + + case ev_entity: + while (*s && ISWHITESPACE(*s)) + s++; + i = atoi(s); + if (i >= prog->limit_edicts) + Con_Printf("PRVM_ED_ParseEpair: ev_entity reference too large (edict %u >= MAX_EDICTS %u) on %s\n", (unsigned int)i, prog->limit_edicts, PRVM_NAME); + while (i >= prog->max_edicts) + PRVM_MEM_IncreaseEdicts(); + // if IncreaseEdicts was called the base pointer needs to be updated + if (ent) + val = (prvm_eval_t *)(ent->fields.vp + key->ofs); + val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i)); + break; + + case ev_field: + if (*s != '.') + { + Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, PRVM_NAME); + return false; + } + def = PRVM_ED_FindField(s + 1); + if (!def) + { + Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, PRVM_NAME); + return false; + } + val->_int = def->ofs; + break; + + case ev_function: + func = PRVM_ED_FindFunction(s); + if (!func) + { + Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, PRVM_NAME); + return false; + } + val->function = func - prog->functions; + break; + + default: + Con_Printf("PRVM_ED_ParseEpair: Unknown key->type %i for key \"%s\" on %s\n", key->type, PRVM_GetString(key->s_name), PRVM_NAME); + return false; + } + return true; +} + +/* +============= +PRVM_GameCommand_f + +Console command to send a string to QC function GameCommand of the +indicated progs + +Usage: + sv_cmd adminmsg 3 "do not teamkill" + cl_cmd someclientcommand + menu_cmd somemenucommand + +All progs can support this extension; sg calls it in server QC, cg in client +QC, mg in menu QC. +============= +*/ +void PRVM_GameCommand(const char *whichprogs, const char *whichcmd) +{ + if(Cmd_Argc() < 1) + { + Con_Printf("%s text...\n", whichcmd); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(whichprogs)) + // note: this is not PRVM_SetProg because that one aborts "hard" using PRVM_Error + // also, it makes printing error messages easier! + { + Con_Printf("%s program not loaded.\n", whichprogs); + return; + } + + if(!PRVM_allfunction(GameCommand)) + { + Con_Printf("%s program do not support GameCommand!\n", whichprogs); + } + else + { + int restorevm_tempstringsbuf_cursize; + const char *s; + + s = Cmd_Args(); + + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(s ? s : ""); + PRVM_ExecuteProgram (PRVM_allfunction(GameCommand), "QC function GameCommand is missing"); + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + } + + PRVM_End; +} +void PRVM_GameCommand_Server_f(void) +{ + PRVM_GameCommand("server", "sv_cmd"); +} +void PRVM_GameCommand_Client_f(void) +{ + PRVM_GameCommand("client", "cl_cmd"); +} +void PRVM_GameCommand_Menu_f(void) +{ + PRVM_GameCommand("menu", "menu_cmd"); +} + +/* +============= +PRVM_ED_EdictGet_f + +Console command to load a field of a specified edict +============= +*/ +void PRVM_ED_EdictGet_f(void) +{ + prvm_edict_t *ed; + ddef_t *key; + const char *s; + prvm_eval_t *v; + + if(Cmd_Argc() != 4 && Cmd_Argc() != 5) + { + Con_Print("prvm_edictget []\n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + { + Con_Printf("Wrong program name %s !\n", Cmd_Argv(1)); + return; + } + + ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(2))); + + if((key = PRVM_ED_FindField(Cmd_Argv(3))) == 0) + { + Con_Printf("Key %s not found !\n", Cmd_Argv(3)); + goto fail; + } + + v = (prvm_eval_t *)(ed->fields.vp + key->ofs); + s = PRVM_UglyValueString((etype_t)key->type, v); + if(Cmd_Argc() == 5) + { + cvar_t *cvar = Cvar_FindVar(Cmd_Argv(4)); + if (cvar && cvar->flags & CVAR_READONLY) + { + Con_Printf("prvm_edictget: %s is read-only\n", cvar->name); + goto fail; + } + Cvar_Get(Cmd_Argv(4), s, 0, NULL); + } + else + Con_Printf("%s\n", s); + +fail: + PRVM_End; +} + +void PRVM_ED_GlobalGet_f(void) +{ + ddef_t *key; + const char *s; + prvm_eval_t *v; + + if(Cmd_Argc() != 3 && Cmd_Argc() != 4) + { + Con_Print("prvm_globalget []\n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + { + Con_Printf("Wrong program name %s !\n", Cmd_Argv(1)); + return; + } + + key = PRVM_ED_FindGlobal(Cmd_Argv(2)); + if(!key) + { + Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); + goto fail; + } + + v = (prvm_eval_t *) &prog->globals.generic[key->ofs]; + s = PRVM_UglyValueString((etype_t)key->type, v); + if(Cmd_Argc() == 4) + { + cvar_t *cvar = Cvar_FindVar(Cmd_Argv(3)); + if (cvar && cvar->flags & CVAR_READONLY) + { + Con_Printf("prvm_globalget: %s is read-only\n", cvar->name); + goto fail; + } + Cvar_Get(Cmd_Argv(3), s, 0, NULL); + } + else + Con_Printf("%s\n", s); + +fail: + PRVM_End; +} + +/* +============= +PRVM_ED_EdictSet_f + +Console command to set a field of a specified edict +============= +*/ +void PRVM_ED_EdictSet_f(void) +{ + prvm_edict_t *ed; + ddef_t *key; + + if(Cmd_Argc() != 5) + { + Con_Print("prvm_edictset \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + { + Con_Printf("Wrong program name %s !\n", Cmd_Argv(1)); + return; + } + + ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(2))); + + if((key = PRVM_ED_FindField(Cmd_Argv(3))) == 0) + Con_Printf("Key %s not found !\n", Cmd_Argv(3)); + else + PRVM_ED_ParseEpair(ed, key, Cmd_Argv(4), true); + + PRVM_End; +} + +/* +==================== +PRVM_ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +Used for initial level load and for savegames. +==================== +*/ +const char *PRVM_ED_ParseEdict (const char *data, prvm_edict_t *ent) +{ + ddef_t *key; + qboolean anglehack; + qboolean init; + char keyname[256]; + size_t n; + + init = false; + +// go through all the dictionary pairs + while (1) + { + // parse key + if (!COM_ParseToken_Simple(&data, false, false)) + PRVM_ERROR ("PRVM_ED_ParseEdict: EOF without closing brace"); + if (developer_entityparsing.integer) + Con_Printf("Key: \"%s\"", com_token); + if (com_token[0] == '}') + break; + + // anglehack is to allow QuakeEd to write single scalar angles + // and allow them to be turned into vectors. (FIXME...) + if (!strcmp(com_token, "angle")) + { + strlcpy (com_token, "angles", sizeof(com_token)); + anglehack = true; + } + else + anglehack = false; + + // FIXME: change light to _light to get rid of this hack + if (!strcmp(com_token, "light")) + strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def + + strlcpy (keyname, com_token, sizeof(keyname)); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + if (!COM_ParseToken_Simple(&data, false, false)) + PRVM_ERROR ("PRVM_ED_ParseEdict: EOF without closing brace"); + if (developer_entityparsing.integer) + Con_Printf(" \"%s\"\n", com_token); + + if (com_token[0] == '}') + PRVM_ERROR ("PRVM_ED_ParseEdict: closing brace without data"); + + init = true; + + // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp) + if (!keyname[0]) + continue; + +// keynames with a leading underscore are used for utility comments, +// and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + key = PRVM_ED_FindField (keyname); + if (!key) + { + Con_DPrintf("%s: '%s' is not a field\n", PRVM_NAME, keyname); + continue; + } + + if (anglehack) + { + char temp[32]; + strlcpy (temp, com_token, sizeof(temp)); + dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp); + } + + if (!PRVM_ED_ParseEpair(ent, key, com_token, strcmp(keyname, "wad") != 0)) + PRVM_ERROR ("PRVM_ED_ParseEdict: parse error"); + } + + if (!init) + ent->priv.required->free = true; + + return data; +} + + +/* +================ +PRVM_ED_LoadFromFile + +The entities are directly placed in the array, rather than allocated with +PRVM_ED_Alloc, because otherwise an error loading the map would have entity +number references out of order. + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. + +Used for both fresh maps and savegame loads. A fresh map would also need +to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves. +================ +*/ +void PRVM_ED_LoadFromFile (const char *data) +{ + prvm_edict_t *ent; + int parsed, inhibited, spawned, died; + const char *funcname; + mfunction_t *func; + + parsed = 0; + inhibited = 0; + spawned = 0; + died = 0; + + prvm_reuseedicts_always_allow = realtime; + +// parse ents + while (1) + { +// parse the opening brace + if (!COM_ParseToken_Simple(&data, false, false)) + break; + if (com_token[0] != '{') + PRVM_ERROR ("PRVM_ED_LoadFromFile: %s: found %s when expecting {", PRVM_NAME, com_token); + + // CHANGED: this is not conform to PR_LoadFromFile + if(prog->loadintoworld) + { + prog->loadintoworld = false; + ent = PRVM_EDICT_NUM(0); + } + else + ent = PRVM_ED_Alloc(); + + // clear it + if (ent != prog->edicts) // hack + memset (ent->fields.vp, 0, prog->entityfields * 4); + + data = PRVM_ED_ParseEdict (data, ent); + parsed++; + + // remove the entity ? + if(prog->load_edict && !prog->load_edict(ent)) + { + PRVM_ED_Free(ent); + inhibited++; + continue; + } + + if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction)) + { + // self = ent + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram (PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing"); + } + + if(ent->priv.required->free) + { + inhibited++; + continue; + } + +// +// immediately call spawn function, but only if there is a self global and a classname +// + if(!ent->priv.required->free) + { + if (!PRVM_alledictstring(ent, classname)) + { + Con_Print("No classname for:\n"); + PRVM_ED_Print(ent, NULL); + PRVM_ED_Free (ent); + continue; + } + + // look for the spawn function + funcname = PRVM_GetString(PRVM_alledictstring(ent, classname)); + func = PRVM_ED_FindFunction (va("spawnfunc_%s", funcname)); + if(!func) + if(!PRVM_allglobalfloat(require_spawnfunc_prefix)) + func = PRVM_ED_FindFunction (funcname); + + if (!func) + { + // check for OnEntityNoSpawnFunction + if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction)) + { + // self = ent + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram (PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing"); + } + else + { + if (developer.integer > 0) // don't confuse non-developers with errors + { + Con_Print("No spawn function for:\n"); + PRVM_ED_Print(ent, NULL); + } + PRVM_ED_Free (ent); + continue; // not included in "inhibited" count + } + } + else + { + // self = ent + PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram (func - prog->functions, ""); + } + } + + if(!ent->priv.required->free) + if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction)) + { + // self = ent + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram (PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing"); + } + + spawned++; + if (ent->priv.required->free) + died++; + } + + Con_DPrintf("%s: %i new entities parsed, %i new inhibited, %i (%i new) spawned (whereas %i removed self, %i stayed)\n", PRVM_NAME, parsed, inhibited, prog->num_edicts, spawned, died, spawned - died); + + prvm_reuseedicts_always_allow = 0; +} + +void PRVM_FindOffsets(void) +{ + // field and global searches use -1 for NULL + memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets)); + memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets)); + // function searches use 0 for NULL + memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets)); +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(#x); +#define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(#x); +#define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(#x); +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +} + +// not used +/* +typedef struct dpfield_s +{ + int type; + char *string; +} +dpfield_t; + +#define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t)) + +dpfield_t dpfields[] = +{ +}; +*/ + +/* +=============== +PRVM_ResetProg +=============== +*/ + +#define PO_HASHSIZE 16384 +typedef struct po_string_s +{ + char *key, *value; + struct po_string_s *nextonhashchain; +} +po_string_t; +typedef struct po_s +{ + po_string_t *hashtable[PO_HASHSIZE]; +} +po_t; +void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize) +{ + for(;;) + { + switch(*in) + { + case 0: + *out++ = 0; + return; + case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break; + case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break; + case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break; + case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break; + case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break; + case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break; + case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break; + default: + if(*in >= 0 && *in <= 0x1F) + { + if(outsize >= 4) + { + *out++ = '\\'; + *out++ = '0' + ((*in & 0700) >> 6); + *out++ = '0' + ((*in & 0070) >> 3); + *out++ = '0' + ((*in & 0007)); + outsize -= 4; + } + } + else + { + if(outsize >= 1) + { + *out++ = *in; + outsize -= 1; + } + } + break; + } + ++in; + } +} +void PRVM_PO_ParseString(char *out, const char *in, size_t outsize) +{ + for(;;) + { + switch(*in) + { + case 0: + *out++ = 0; + return; + case '\\': + ++in; + switch(*in) + { + case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break; + case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break; + case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break; + case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break; + case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break; + case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break; + case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break; + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': + if(outsize > 0) + *out = *in - '0'; + ++in; + if(*in >= '0' && *in <= '7') + { + if(outsize > 0) + *out = (*out << 3) | (*in - '0'); + ++in; + } + if(*in >= '0' && *in <= '7') + { + if(outsize > 0) + *out = (*out << 3) | (*in - '0'); + ++in; + } + --in; + if(outsize > 0) + { + ++out; + --outsize; + } + break; + default: + if(outsize > 0) { *out++ = *in; --outsize; } + break; + } + break; + default: + if(outsize > 0) + { + *out++ = *in; + --outsize; + } + break; + } + ++in; + } +} +po_t *PRVM_PO_Load(const char *filename, mempool_t *pool) +{ + po_t *po; + const char *p, *q; + int mode; + char inbuf[MAX_INPUTLINE]; + char decodedbuf[MAX_INPUTLINE]; + size_t decodedpos; + int hashindex; + po_string_t thisstr; + const char *buf = (const char *) FS_LoadFile(filename, pool, true, NULL); + + if(!buf) + return NULL; + + memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning + + po = (po_t *)Mem_Alloc(pool, sizeof(*po)); + memset(po, 0, sizeof(*po)); + + p = buf; + while(*p) + { + if(*p == '#') + { + // skip to newline + p = strchr(p, '\n'); + if(!p) + break; + ++p; + continue; + } + if(*p == '\r' || *p == '\n') + { + ++p; + continue; + } + if(!strncmp(p, "msgid \"", 7)) + { + mode = 0; + p += 6; + } + else if(!strncmp(p, "msgstr \"", 8)) + { + mode = 1; + p += 7; + } + else + { + p = strchr(p, '\n'); + if(!p) + break; + ++p; + continue; + } + decodedpos = 0; + while(*p == '"') + { + ++p; + q = strchr(p, '\n'); + if(!q) + break; + if(*(q-1) == '\r') + --q; + if(*(q-1) != '"') + break; + if((size_t)(q - p) >= (size_t) sizeof(inbuf)) + break; + strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL + PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos); + decodedpos += strlen(decodedbuf + decodedpos); + if(*q == '\r') + ++q; + if(*q == '\n') + ++q; + p = q; + } + if(mode == 0) + { + if(thisstr.key) + Mem_Free(thisstr.key); + thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1); + memcpy(thisstr.key, decodedbuf, decodedpos + 1); + } + else if(decodedpos > 0 && thisstr.key) // skip empty translation results + { + thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1); + memcpy(thisstr.value, decodedbuf, decodedpos + 1); + hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE; + thisstr.nextonhashchain = po->hashtable[hashindex]; + po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr)); + memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr)); + memset(&thisstr, 0, sizeof(thisstr)); + } + } + + Mem_Free((char *) buf); + return po; +} +const char *PRVM_PO_Lookup(po_t *po, const char *str) +{ + int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE; + po_string_t *p = po->hashtable[hashindex]; + while(p) + { + if(!strcmp(str, p->key)) + return p->value; + p = p->nextonhashchain; + } + return NULL; +} +void PRVM_PO_Destroy(po_t *po) +{ + int i; + for(i = 0; i < PO_HASHSIZE; ++i) + { + po_string_t *p = po->hashtable[i]; + while(p) + { + po_string_t *q = p; + p = p->nextonhashchain; + Mem_Free(q->key); + Mem_Free(q->value); + Mem_Free(q); + } + } + Mem_Free(po); +} + +void PRVM_LeakTest(void); +void PRVM_ResetProg(void) +{ + PRVM_LeakTest(); + PRVM_GCALL(reset_cmd)(); + Mem_FreePool(&prog->progs_mempool); + if(prog->po) + PRVM_PO_Destroy((po_t *) prog->po); + memset(prog,0,sizeof(prvm_prog_t)); + prog->starttime = Sys_DoubleTime(); +} + +/* +=============== +PRVM_LoadLNO +=============== +*/ +void PRVM_LoadLNO( const char *progname ) { + fs_offset_t filesize; + unsigned char *lno; + unsigned int *header; + char filename[512]; + + FS_StripExtension( progname, filename, sizeof( filename ) ); + strlcat( filename, ".lno", sizeof( filename ) ); + + lno = FS_LoadFile( filename, tempmempool, false, &filesize ); + if( !lno ) { + return; + } + +/* + SafeWrite (h, &lnotype, sizeof(int)); + SafeWrite (h, &version, sizeof(int)); + SafeWrite (h, &numglobaldefs, sizeof(int)); + SafeWrite (h, &numpr_globals, sizeof(int)); + SafeWrite (h, &numfielddefs, sizeof(int)); + SafeWrite (h, &numstatements, sizeof(int)); + SafeWrite (h, statement_linenums, numstatements*sizeof(int)); +*/ + if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int)) + { + Mem_Free(lno); + return; + } + + header = (unsigned int *) lno; + if( header[ 0 ] == *(unsigned int *) "LNOF" && + LittleLong( header[ 1 ] ) == 1 && + (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs && + (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals && + (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs && + (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements ) + { + prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) ); + memcpy( prog->statement_linenums, (int *) lno + 6, prog->progs_numstatements * sizeof( int ) ); + } + Mem_Free( lno ); +} + +/* +=============== +PRVM_LoadProgs +=============== +*/ +void PRVM_LoadProgs (const char * filename, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global) +{ + int i; + dprograms_t *dprograms; + dstatement_t *instatements; + ddef_t *infielddefs; + ddef_t *inglobaldefs; + float *inglobals; + dfunction_t *infunctions; + char *instrings; + fs_offset_t filesize; + int requiredglobalspace; + opcode_t op; + int a; + int b; + int c; + + if (prog->loaded) + PRVM_ERROR ("PRVM_LoadProgs: there is already a %s program loaded!", PRVM_NAME ); + + dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize); + if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t)) + PRVM_ERROR ("PRVM_LoadProgs: couldn't load %s for %s", filename, PRVM_NAME); + // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file + + Con_DPrintf("%s programs occupy %iK.\n", PRVM_NAME, (int)(filesize/1024)); + + requiredglobalspace = 0; + for (i = 0;i < numrequiredglobals;i++) + requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1; + + prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize); + +// byte swap the header + prog->progs_version = LittleLong(dprograms->version); + prog->progs_crc = LittleLong(dprograms->crc); + if (prog->progs_version != PROG_VERSION) + PRVM_ERROR ("%s: %s has wrong version number (%i should be %i)", PRVM_NAME, filename, prog->progs_version, PROG_VERSION); + instatements = (dstatement_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements)); + prog->progs_numstatements = LittleLong(dprograms->numstatements); + inglobaldefs = (ddef_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs)); + prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs); + infielddefs = (ddef_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs)); + prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs); + infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions)); + prog->progs_numfunctions = LittleLong(dprograms->numfunctions); + instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings)); + prog->progs_numstrings = LittleLong(dprograms->numstrings); + inglobals = (float *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals)); + prog->progs_numglobals = LittleLong(dprograms->numglobals); + prog->progs_entityfields = LittleLong(dprograms->entityfields); + + prog->numstatements = prog->progs_numstatements; + prog->numglobaldefs = prog->progs_numglobaldefs; + prog->numfielddefs = prog->progs_numfielddefs; + prog->numfunctions = prog->progs_numfunctions; + prog->numstrings = prog->progs_numstrings; + prog->numglobals = prog->progs_numglobals; + prog->entityfields = prog->progs_entityfields; + + if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings >= (int)filesize) + PRVM_ERROR ("%s: %s strings go past end of file", PRVM_NAME, filename); + prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings); + memcpy(prog->strings, instrings, prog->progs_numstrings); + prog->stringssize = prog->progs_numstrings; + + prog->numknownstrings = 0; + prog->maxknownstrings = 0; + prog->knownstrings = NULL; + prog->knownstrings_freeable = NULL; + + Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64); + + // we need to expand the globaldefs and fielddefs to include engine defs + prog->globaldefs = (ddef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(ddef_t)); + prog->globals.generic = (float *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace) * sizeof(float)); + prog->fielddefs = (ddef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(ddef_t)); + // we need to convert the statements to our memory format + prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t)); + // allocate space for profiling statement usage + prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile)); + // functions need to be converted to the memory format + prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions); + + for (i = 0;i < prog->progs_numfunctions;i++) + { + prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement); + prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start); + prog->functions[i].s_name = LittleLong(infunctions[i].s_name); + prog->functions[i].s_file = LittleLong(infunctions[i].s_file); + prog->functions[i].numparms = LittleLong(infunctions[i].numparms); + prog->functions[i].locals = LittleLong(infunctions[i].locals); + memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size)); + if(prog->functions[i].first_statement >= prog->numstatements) + PRVM_ERROR("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, PRVM_NAME); + // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size + } + + // copy the globaldefs to the new globaldefs list + for (i=0 ; inumglobaldefs ; i++) + { + prog->globaldefs[i].type = LittleShort(inglobaldefs[i].type); + prog->globaldefs[i].ofs = LittleShort(inglobaldefs[i].ofs); + prog->globaldefs[i].s_name = LittleLong(inglobaldefs[i].s_name); + // TODO bounds check ofs, s_name + } + + // append the required globals + for (i = 0;i < numrequiredglobals;i++) + { + prog->globaldefs[prog->numglobaldefs].type = required_global[i].type; + prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals; + prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(required_global[i].name); + if (prog->globaldefs[prog->numglobaldefs].type == ev_vector) + prog->numglobals += 3; + else + prog->numglobals++; + prog->numglobaldefs++; + } + + // copy the progs fields to the new fields list + for (i = 0;i < prog->numfielddefs;i++) + { + prog->fielddefs[i].type = LittleShort(infielddefs[i].type); + if (prog->fielddefs[i].type & DEF_SAVEGLOBAL) + PRVM_ERROR ("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", PRVM_NAME); + prog->fielddefs[i].ofs = LittleShort(infielddefs[i].ofs); + prog->fielddefs[i].s_name = LittleLong(infielddefs[i].s_name); + // TODO bounds check ofs, s_name + } + + // append the required fields + for (i = 0;i < numrequiredfields;i++) + { + prog->fielddefs[prog->numfielddefs].type = required_field[i].type; + prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields; + prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(required_field[i].name); + if (prog->fielddefs[prog->numfielddefs].type == ev_vector) + prog->entityfields += 3; + else + prog->entityfields++; + prog->numfielddefs++; + } + + // LordHavoc: TODO: reorder globals to match engine struct + // LordHavoc: TODO: reorder fields to match engine struct +#define remapglobal(index) (index) +#define remapfield(index) (index) + + // copy globals + for (i = 0;i < prog->progs_numglobals;i++) + ((int *)prog->globals.generic)[remapglobal(i)] = LittleLong(((int *)inglobals)[i]); + + // LordHavoc: TODO: support 32bit progs statement formats + // copy, remap globals in statements, bounds check + for (i = 0;i < prog->progs_numstatements;i++) + { + op = (opcode_t)LittleShort(instatements[i].op); + a = (unsigned short)LittleShort(instatements[i].a); + b = (unsigned short)LittleShort(instatements[i].b); + c = (unsigned short)LittleShort(instatements[i].c); + switch (op) + { + case OP_IF: + case OP_IFNOT: + b = (short)b; + if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements) + PRVM_ERROR("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, PRVM_NAME); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = -1; + prog->statements[i].operand[2] = -1; + prog->statements[i].jumpabsolute = i + b; + break; + case OP_GOTO: + a = (short)a; + if (a + i < 0 || a + i >= prog->progs_numstatements) + PRVM_ERROR("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, PRVM_NAME); + prog->statements[i].op = op; + prog->statements[i].operand[0] = -1; + prog->statements[i].operand[1] = -1; + prog->statements[i].operand[2] = -1; + prog->statements[i].jumpabsolute = i + a; + break; + default: + Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, PRVM_NAME); + // global global global + case OP_ADD_F: + case OP_ADD_V: + case OP_SUB_F: + case OP_SUB_V: + case OP_MUL_F: + case OP_MUL_V: + case OP_MUL_FV: + case OP_MUL_VF: + case OP_DIV_F: + case OP_BITAND: + case OP_BITOR: + case OP_GE: + case OP_LE: + case OP_GT: + case OP_LT: + case OP_AND: + case OP_OR: + case OP_EQ_F: + case OP_EQ_V: + case OP_EQ_S: + case OP_EQ_E: + case OP_EQ_FNC: + case OP_NE_F: + case OP_NE_V: + case OP_NE_S: + case OP_NE_E: + case OP_NE_FNC: + case OP_ADDRESS: + case OP_LOAD_F: + case OP_LOAD_FLD: + case OP_LOAD_ENT: + case OP_LOAD_S: + case OP_LOAD_FNC: + case OP_LOAD_V: + if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals) + PRVM_ERROR("PRVM_LoadProgs: out of bounds global index (statement %d)", i); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = remapglobal(b); + prog->statements[i].operand[2] = remapglobal(c); + prog->statements[i].jumpabsolute = -1; + break; + // global none global + case OP_NOT_F: + case OP_NOT_V: + case OP_NOT_S: + case OP_NOT_FNC: + case OP_NOT_ENT: + if (a >= prog->progs_numglobals || c >= prog->progs_numglobals) + PRVM_ERROR("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = -1; + prog->statements[i].operand[2] = remapglobal(c); + prog->statements[i].jumpabsolute = -1; + break; + // 2 globals + case OP_STOREP_F: + case OP_STOREP_ENT: + case OP_STOREP_FLD: + case OP_STOREP_S: + case OP_STOREP_FNC: + case OP_STORE_F: + case OP_STORE_ENT: + case OP_STORE_FLD: + case OP_STORE_S: + case OP_STORE_FNC: + case OP_STATE: + case OP_STOREP_V: + case OP_STORE_V: + if (a >= prog->progs_numglobals || b >= prog->progs_numglobals) + PRVM_ERROR("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = remapglobal(b); + prog->statements[i].operand[2] = -1; + prog->statements[i].jumpabsolute = -1; + break; + // 1 global + case OP_CALL0: + case OP_CALL1: + case OP_CALL2: + case OP_CALL3: + case OP_CALL4: + case OP_CALL5: + case OP_CALL6: + case OP_CALL7: + case OP_CALL8: + case OP_DONE: + case OP_RETURN: + if ( a >= prog->progs_numglobals) + PRVM_ERROR("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = -1; + prog->statements[i].operand[2] = -1; + prog->statements[i].jumpabsolute = -1; + break; + } + } + if(prog->numstatements < 1) + { + PRVM_ERROR("PRVM_LoadProgs: empty program in %s", PRVM_NAME); + } + else switch(prog->statements[prog->numstatements - 1].op) + { + case OP_RETURN: + case OP_GOTO: + case OP_DONE: + break; + default: + PRVM_ERROR("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", PRVM_NAME); + break; + } + + // we're done with the file now + Mem_Free(dprograms); + dprograms = NULL; + + // check required functions + for(i=0 ; i < numrequiredfunc ; i++) + if(PRVM_ED_FindFunction(required_func[i]) == 0) + PRVM_ERROR("%s: %s not found in %s",PRVM_NAME, required_func[i], filename); + + PRVM_LoadLNO(filename); + + PRVM_Init_Exec(); + + if(*prvm_language.string) + // in CSQC we really shouldn't be able to change how stuff works... sorry for now + // later idea: include a list of authorized .po file checksums with the csprogs + { + qboolean deftrans = !!strcmp(PRVM_NAME, "client"); + const char *realfilename = (strcmp(PRVM_NAME, "client") ? filename : csqc_progname.string); + if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method! + { + for (i=0 ; inumglobaldefs ; i++) + { + const char *name; + name = PRVM_GetString(prog->globaldefs[i].s_name); + if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) + if(name && !strncmp(name, "dotranslate_", 12)) + { + deftrans = false; + break; + } + } + } + if(!strcmp(prvm_language.string, "dump")) + { + qfile_t *f = FS_OpenRealFile(va("%s.pot", realfilename), "w", false); + Con_Printf("Dumping to %s.pot\n", realfilename); + if(f) + { + for (i=0 ; inumglobaldefs ; i++) + { + const char *name; + name = PRVM_GetString(prog->globaldefs[i].s_name); + if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12))) + if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) + { + prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); + const char *value = PRVM_GetString(val->string); + if(*value) + { + char buf[MAX_INPUTLINE]; + PRVM_PO_UnparseString(buf, value, sizeof(buf)); + FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf); + } + } + } + FS_Close(f); + } + } + else + { + po_t *po = PRVM_PO_Load(va("%s.%s.po", realfilename, prvm_language.string), prog->progs_mempool); + if(po) + { + for (i=0 ; inumglobaldefs ; i++) + { + const char *name; + name = PRVM_GetString(prog->globaldefs[i].s_name); + if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12))) + if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) + { + prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); + const char *value = PRVM_GetString(val->string); + if(*value) + { + value = PRVM_PO_Lookup(po, value); + if(value) + val->string = PRVM_SetEngineString(value); + } + } + } + } + } + } + + for (i=0 ; inumglobaldefs ; i++) + { + const char *name; + name = PRVM_GetString(prog->globaldefs[i].s_name); + //Con_Printf("found var %s\n", name); + if(name + && !strncmp(name, "autocvar_", 9) + && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) + ) + { + prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); + cvar_t *cvar = Cvar_FindVar(name + 9); + //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, PRVM_NAME); + if(!cvar) + { + const char *value; + char buf[64]; + Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, PRVM_NAME); + switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) + { + case ev_float: + if((float)((int)(val->_float)) == val->_float) + dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float)); + else + dpsnprintf(buf, sizeof(buf), "%.9g", val->_float); + value = buf; + break; + case ev_vector: + dpsnprintf(buf, sizeof(buf), "%.9g %.9g %.9g", val->vector[0], val->vector[1], val->vector[2]); value = buf; + break; + case ev_string: + value = PRVM_GetString(val->string); + break; + default: + Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, PRVM_NAME); + goto fail; + } + cvar = Cvar_Get(name + 9, value, 0, NULL); + if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) + { + val->string = PRVM_SetEngineString(cvar->string); + cvar->globaldefindex_stringno[prog - prog_list] = val->string; + } + if(!cvar) + PRVM_ERROR("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, PRVM_NAME); + cvar->globaldefindex_progid[prog - prog_list] = prog->id; + cvar->globaldefindex[prog - prog_list] = i; + } + else if((cvar->flags & CVAR_PRIVATE) == 0) + { + // MUST BE SYNCED WITH cvar.c Cvar_Set + int j; + const char *s; + switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) + { + case ev_float: + val->_float = cvar->value; + break; + case ev_vector: + s = cvar->string; + VectorClear(val->vector); + for (j = 0;j < 3;j++) + { + while (*s && ISWHITESPACE(*s)) + s++; + if (!*s) + break; + val->vector[j] = atof(s); + while (!ISWHITESPACE(*s)) + s++; + if (!*s) + break; + } + break; + case ev_string: + val->string = PRVM_SetEngineString(cvar->string); + cvar->globaldefindex_stringno[prog - prog_list] = val->string; + break; + default: + Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, PRVM_NAME); + goto fail; + } + cvar->globaldefindex_progid[prog - prog_list] = prog->id; + cvar->globaldefindex[prog - prog_list] = i; + } + else + Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, PRVM_NAME); + } +fail: + ; + } + + prog->loaded = TRUE; + + // set flags & ddef_ts in prog + + prog->flag = 0; + + PRVM_FindOffsets(); + + PRVM_GCALL(init_cmd)(); + + // init mempools + PRVM_MEM_Alloc(); +} + + +void PRVM_Fields_f (void) +{ + int i, j, ednum, used, usedamount; + int *counts; + char tempstring[MAX_INPUTLINE], tempstring2[260]; + const char *name; + prvm_edict_t *ed; + ddef_t *d; + int *v; + + // TODO + /* + if (!sv.active) + { + Con_Print("no progs loaded\n"); + return; + } + */ + + if(Cmd_Argc() != 2) + { + Con_Print("prvm_fields \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int)); + for (ednum = 0;ednum < prog->max_edicts;ednum++) + { + ed = PRVM_EDICT_NUM(ednum); + if (ed->priv.required->free) + continue; + for (i = 1;i < prog->numfielddefs;i++) + { + d = &prog->fielddefs[i]; + name = PRVM_GetString(d->s_name); + if (name[strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars + v = (int *)(ed->fields.vp + d->ofs); + // if the value is still all 0, skip the field + for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++) + { + if (v[j]) + { + counts[i]++; + break; + } + } + } + } + used = 0; + usedamount = 0; + tempstring[0] = 0; + for (i = 0;i < prog->numfielddefs;i++) + { + d = &prog->fielddefs[i]; + name = PRVM_GetString(d->s_name); + if (name[strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars + switch(d->type & ~DEF_SAVEGLOBAL) + { + case ev_string: + strlcat(tempstring, "string ", sizeof(tempstring)); + break; + case ev_entity: + strlcat(tempstring, "entity ", sizeof(tempstring)); + break; + case ev_function: + strlcat(tempstring, "function ", sizeof(tempstring)); + break; + case ev_field: + strlcat(tempstring, "field ", sizeof(tempstring)); + break; + case ev_void: + strlcat(tempstring, "void ", sizeof(tempstring)); + break; + case ev_float: + strlcat(tempstring, "float ", sizeof(tempstring)); + break; + case ev_vector: + strlcat(tempstring, "vector ", sizeof(tempstring)); + break; + case ev_pointer: + strlcat(tempstring, "pointer ", sizeof(tempstring)); + break; + default: + dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL); + strlcat(tempstring, tempstring2, sizeof(tempstring)); + break; + } + if (strlen(name) > sizeof(tempstring2)-4) + { + memcpy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + strlcat(tempstring, name, sizeof(tempstring)); + for (j = (int)strlen(name);j < 25;j++) + strlcat(tempstring, " ", sizeof(tempstring)); + dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]); + strlcat(tempstring, tempstring2, sizeof(tempstring)); + strlcat(tempstring, "\n", sizeof(tempstring)); + if (strlen(tempstring) >= sizeof(tempstring)/2) + { + Con_Print(tempstring); + tempstring[0] = 0; + } + if (counts[i]) + { + used++; + usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL]; + } + } + Mem_Free(counts); + Con_Printf("%s: %i entity fields (%i in use), totalling %i bytes per edict (%i in use), %i edicts allocated, %i bytes total spent on edict fields (%i needed)\n", PRVM_NAME, prog->entityfields, used, prog->entityfields * 4, usedamount * 4, prog->max_edicts, prog->entityfields * 4 * prog->max_edicts, usedamount * 4 * prog->max_edicts); + + PRVM_End; +} + +void PRVM_Globals_f (void) +{ + int i; + const char *wildcard; + int numculled; + numculled = 0; + // TODO + /*if (!sv.active) + { + Con_Print("no progs loaded\n"); + return; + }*/ + if(Cmd_Argc () < 2 || Cmd_Argc() > 3) + { + Con_Print("prvm_globals \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString (Cmd_Argv (1))) + return; + + if( Cmd_Argc() == 3) + wildcard = Cmd_Argv(2); + else + wildcard = NULL; + + Con_Printf("%s :", PRVM_NAME); + + for (i = 0;i < prog->numglobaldefs;i++) + { + if(wildcard) + if( !matchpattern( PRVM_GetString(prog->globaldefs[i].s_name), wildcard, 1) ) + { + numculled++; + continue; + } + Con_Printf("%s\n", PRVM_GetString(prog->globaldefs[i].s_name)); + } + Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4); + + PRVM_End; +} + +/* +=============== +PRVM_Global +=============== +*/ +void PRVM_Global_f(void) +{ + ddef_t *global; + if( Cmd_Argc() != 3 ) { + Con_Printf( "prvm_global \n" ); + return; + } + + PRVM_Begin; + if( !PRVM_SetProgFromString( Cmd_Argv(1) ) ) + return; + + global = PRVM_ED_FindGlobal( Cmd_Argv(2) ); + if( !global ) + Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); + else + Con_Printf( "%s: %s\n", Cmd_Argv(2), PRVM_ValueString( (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs) ) ); + PRVM_End; +} + +/* +=============== +PRVM_GlobalSet +=============== +*/ +void PRVM_GlobalSet_f(void) +{ + ddef_t *global; + if( Cmd_Argc() != 4 ) { + Con_Printf( "prvm_globalset \n" ); + return; + } + + PRVM_Begin; + if( !PRVM_SetProgFromString( Cmd_Argv(1) ) ) + return; + + global = PRVM_ED_FindGlobal( Cmd_Argv(2) ); + if( !global ) + Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); + else + PRVM_ED_ParseEpair( NULL, global, Cmd_Argv(3), true ); + PRVM_End; +} + +/* +=============== +PRVM_Init +=============== +*/ +void PRVM_Init (void) +{ + Cmd_AddCommand ("prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_profile", PRVM_Profile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_childprofile", PRVM_ChildProfile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu), sorted by time taken in function with child calls"); + Cmd_AddCommand ("prvm_callprofile", PRVM_CallProfile_f, "prints execution statistics about the most time consuming QuakeC calls from the engine in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_fields", PRVM_Fields_f, "prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_edictset", PRVM_ED_EdictSet_f, "changes value of a specified property of a specified entity in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_edictget", PRVM_ED_EdictGet_f, "retrieves the value of a specified property of a specified entity in the selected VM (server, client menu) into a cvar or to the console"); + Cmd_AddCommand ("prvm_globalget", PRVM_ED_GlobalGet_f, "retrieves the value of a specified global variable in the selected VM (server, client menu) into a cvar or to the console"); + Cmd_AddCommand ("prvm_printfunction", PRVM_PrintFunction_f, "prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, menu)"); + Cmd_AddCommand ("cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument"); + Cmd_AddCommand ("menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument"); + Cmd_AddCommand ("sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument"); + + Cvar_RegisterVariable (&prvm_language); + Cvar_RegisterVariable (&prvm_traceqc); + Cvar_RegisterVariable (&prvm_statementprofiling); + Cvar_RegisterVariable (&prvm_timeprofiling); + Cvar_RegisterVariable (&prvm_backtraceforwarnings); + Cvar_RegisterVariable (&prvm_leaktest); + Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames); + Cvar_RegisterVariable (&prvm_errordump); + Cvar_RegisterVariable (&prvm_reuseedicts_startuptime); + Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe); + + // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!) + prvm_runawaycheck = !COM_CheckParm("-norunaway"); + + //VM_Cmd_Init(); +} + +/* +=============== +PRVM_InitProg +=============== +*/ +void PRVM_InitProg(int prognr) +{ + static unsigned int progid = 0; + + if(prognr < 0 || prognr >= PRVM_MAXPROGS) + Sys_Error("PRVM_InitProg: Invalid program number %i",prognr); + + prog = &prog_list[prognr]; + + if(prog->loaded) + PRVM_ResetProg(); + + memset(prog, 0, sizeof(prvm_prog_t)); + prog->starttime = Sys_DoubleTime(); + prog->id = ++progid; + + prog->error_cmd = Host_Error; + prog->leaktest_active = prvm_leaktest.integer != 0; +} + +int PRVM_GetProgNr(void) +{ + return prog - prog_list; +} + +void *_PRVM_Alloc(size_t buffersize, const char *filename, int fileline) +{ + return _Mem_Alloc(prog->progs_mempool, NULL, buffersize, 16, filename, fileline); +} + +void _PRVM_Free(void *buffer, const char *filename, int fileline) +{ + _Mem_Free(buffer, filename, fileline); +} + +void _PRVM_FreeAll(const char *filename, int fileline) +{ + prog->functions = NULL; + prog->strings = NULL; + prog->fielddefs = NULL; + prog->globaldefs = NULL; + prog->statements = NULL; + // FIXME: what about knownstrings? + _Mem_EmptyPool(prog->progs_mempool, filename, fileline); +} + +// LordHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons +unsigned int PRVM_EDICT_NUM_ERROR(unsigned int n, const char *filename, int fileline) +{ + PRVM_ERROR ("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", PRVM_NAME, n, filename, fileline); + return 0; +} + +sizebuf_t vm_tempstringsbuf; +#define PRVM_KNOWNSTRINGBASE 0x40000000 + +const char *PRVM_GetString(int num) +{ + if (num < 0) + { + // invalid + VM_Warning("PRVM_GetString: Invalid string offset (%i < 0)\n", num); + return ""; + } + else if (num < prog->stringssize) + { + // constant string from progs.dat + return prog->strings + num; + } + else if (num <= prog->stringssize + vm_tempstringsbuf.maxsize) + { + // tempstring returned by engine to QC (becomes invalid after returning to engine) + num -= prog->stringssize; + if (num < vm_tempstringsbuf.cursize) + return (char *)vm_tempstringsbuf.data + num; + else + { + VM_Warning("PRVM_GetString: Invalid temp-string offset (%i >= %i vm_tempstringsbuf.cursize)\n", num, vm_tempstringsbuf.cursize); + return ""; + } + } + else if (num & PRVM_KNOWNSTRINGBASE) + { + // allocated string + num = num - PRVM_KNOWNSTRINGBASE; + if (num >= 0 && num < prog->numknownstrings) + { + if (!prog->knownstrings[num]) + { + VM_Warning("PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num); + return ""; + } + return prog->knownstrings[num]; + } + else + { + VM_Warning("PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings); + return ""; + } + } + else + { + // invalid string offset + VM_Warning("PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize); + return ""; + } +} + +const char *PRVM_ChangeEngineString(int i, const char *s) +{ + const char *old; + i = i - PRVM_KNOWNSTRINGBASE; + if(i < 0 || i >= prog->numknownstrings) + PRVM_ERROR("PRVM_ChangeEngineString: s is not an engine string"); + old = prog->knownstrings[i]; + prog->knownstrings[i] = s; + return old; +} + +int PRVM_SetEngineString(const char *s) +{ + int i; + if (!s) + return 0; + if (s >= prog->strings && s <= prog->strings + prog->stringssize) + PRVM_ERROR("PRVM_SetEngineString: s in prog->strings area"); + // if it's in the tempstrings area, use a reserved range + // (otherwise we'd get millions of useless string offsets cluttering the database) + if (s >= (char *)vm_tempstringsbuf.data && s < (char *)vm_tempstringsbuf.data + vm_tempstringsbuf.maxsize) +#if 1 + return prog->stringssize + (s - (char *)vm_tempstringsbuf.data); +#endif + // see if it's a known string address + for (i = 0;i < prog->numknownstrings;i++) + if (prog->knownstrings[i] == s) + return PRVM_KNOWNSTRINGBASE + i; + // new unknown engine string + if (developer_insane.integer) + Con_DPrintf("new engine string %p = \"%s\"\n", s, s); + for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) + if (!prog->knownstrings[i]) + break; + if (i >= prog->numknownstrings) + { + if (i >= prog->maxknownstrings) + { + const char **oldstrings = prog->knownstrings; + const unsigned char *oldstrings_freeable = prog->knownstrings_freeable; + const char **oldstrings_origin = prog->knownstrings_origin; + prog->maxknownstrings += 128; + prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); + prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); + if(prog->leaktest_active) + prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); + if (prog->numknownstrings) + { + memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *)); + memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char)); + if(prog->leaktest_active) + memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *)); + } + } + prog->numknownstrings++; + } + prog->firstfreeknownstring = i + 1; + prog->knownstrings[i] = s; + prog->knownstrings_freeable[i] = false; + if(prog->leaktest_active) + prog->knownstrings_origin[i] = NULL; + return PRVM_KNOWNSTRINGBASE + i; +} + +// temp string handling + +// all tempstrings go into this buffer consecutively, and it is reset +// whenever PRVM_ExecuteProgram returns to the engine +// (technically each PRVM_ExecuteProgram call saves the cursize value and +// restores it on return, so multiple recursive calls can share the same +// buffer) +// the buffer size is automatically grown as needed + +int PRVM_SetTempString(const char *s) +{ + int size; + char *t; + if (!s) + return 0; + size = (int)strlen(s) + 1; + if (developer_insane.integer) + Con_DPrintf("PRVM_SetTempString: cursize %i, size %i\n", vm_tempstringsbuf.cursize, size); + if (vm_tempstringsbuf.maxsize < vm_tempstringsbuf.cursize + size) + { + sizebuf_t old = vm_tempstringsbuf; + if (vm_tempstringsbuf.cursize + size >= 1<<28) + PRVM_ERROR("PRVM_SetTempString: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", vm_tempstringsbuf.cursize, size); + vm_tempstringsbuf.maxsize = max(vm_tempstringsbuf.maxsize, 65536); + while (vm_tempstringsbuf.maxsize < vm_tempstringsbuf.cursize + size) + vm_tempstringsbuf.maxsize *= 2; + if (vm_tempstringsbuf.maxsize != old.maxsize || vm_tempstringsbuf.data == NULL) + { + Con_DPrintf("PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old.maxsize/1024, vm_tempstringsbuf.maxsize/1024); + vm_tempstringsbuf.data = (unsigned char *) Mem_Alloc(sv_mempool, vm_tempstringsbuf.maxsize); + if (old.cursize) + memcpy(vm_tempstringsbuf.data, old.data, old.cursize); + if (old.data) + Mem_Free(old.data); + } + } + t = (char *)vm_tempstringsbuf.data + vm_tempstringsbuf.cursize; + memcpy(t, s, size); + vm_tempstringsbuf.cursize += size; + return PRVM_SetEngineString(t); +} + +int PRVM_AllocString(size_t bufferlength, char **pointer) +{ + int i; + if (!bufferlength) + return 0; + for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) + if (!prog->knownstrings[i]) + break; + if (i >= prog->numknownstrings) + { + if (i >= prog->maxknownstrings) + { + const char **oldstrings = prog->knownstrings; + const unsigned char *oldstrings_freeable = prog->knownstrings_freeable; + const char **oldstrings_origin = prog->knownstrings_origin; + prog->maxknownstrings += 128; + prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); + prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); + if(prog->leaktest_active) + prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); + if (prog->numknownstrings) + { + memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *)); + memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char)); + if(prog->leaktest_active) + memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *)); + } + if (oldstrings) + Mem_Free((char **)oldstrings); + if (oldstrings_freeable) + Mem_Free((unsigned char *)oldstrings_freeable); + if (oldstrings_origin) + Mem_Free((char **)oldstrings_origin); + } + prog->numknownstrings++; + } + prog->firstfreeknownstring = i + 1; + prog->knownstrings[i] = (char *)PRVM_Alloc(bufferlength); + prog->knownstrings_freeable[i] = true; + if(prog->leaktest_active) + prog->knownstrings_origin[i] = PRVM_AllocationOrigin(); + if (pointer) + *pointer = (char *)(prog->knownstrings[i]); + return PRVM_KNOWNSTRINGBASE + i; +} + +void PRVM_FreeString(int num) +{ + if (num == 0) + PRVM_ERROR("PRVM_FreeString: attempt to free a NULL string"); + else if (num >= 0 && num < prog->stringssize) + PRVM_ERROR("PRVM_FreeString: attempt to free a constant string"); + else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings) + { + num = num - PRVM_KNOWNSTRINGBASE; + if (!prog->knownstrings[num]) + PRVM_ERROR("PRVM_FreeString: attempt to free a non-existent or already freed string"); + if (!prog->knownstrings_freeable[num]) + PRVM_ERROR("PRVM_FreeString: attempt to free a string owned by the engine"); + PRVM_Free((char *)prog->knownstrings[num]); + if(prog->leaktest_active) + if(prog->knownstrings_origin[num]) + PRVM_Free((char *)prog->knownstrings_origin[num]); + prog->knownstrings[num] = NULL; + prog->knownstrings_freeable[num] = false; + prog->firstfreeknownstring = min(prog->firstfreeknownstring, num); + } + else + PRVM_ERROR("PRVM_FreeString: invalid string offset %i", num); +} + +static qboolean PRVM_IsStringReferenced(string_t string) +{ + int i, j; + + for (i = 0;i < prog->numglobaldefs;i++) + { + ddef_t *d = &prog->globaldefs[i]; + if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string) + continue; + if(string == PRVM_GLOBALFIELDSTRING(d->ofs)) + return true; + } + + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if (ed->priv.required->free) + continue; + for (i=0; inumfielddefs; ++i) + { + ddef_t *d = &prog->fielddefs[i]; + if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string) + continue; + if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs)) + return true; + } + } + + return false; +} + +static qboolean PRVM_IsEdictRelevant(prvm_edict_t *edict) +{ + if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts) + return true; // world or clients + switch(prog - prog_list) + { + case PRVM_SERVERPROG: + { + if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger? + return true; + if(PRVM_serveredictfloat(edict, modelindex)) // visible ent? + return true; + if(PRVM_serveredictfloat(edict, effects)) // particle effect? + return true; + if(PRVM_serveredictfunction(edict, think)) // has a think function? + if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run? + return true; + if(PRVM_serveredictfloat(edict, takedamage)) + return true; + if(*prvm_leaktest_ignore_classnames.string) + { + if(strstr(va(" %s ", prvm_leaktest_ignore_classnames.string), va(" %s ", PRVM_GetString(PRVM_serveredictstring(edict, classname))))) + return true; + } + } + break; + case PRVM_CLIENTPROG: + { + // TODO someone add more stuff here + if(PRVM_clientedictfloat(edict, entnum)) // csqc networked + return true; + if(PRVM_clientedictfloat(edict, modelindex)) // visible ent? + return true; + if(PRVM_clientedictfloat(edict, effects)) // particle effect? + return true; + if(PRVM_clientedictfunction(edict, think)) // has a think function? + if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run? + return true; + if(*prvm_leaktest_ignore_classnames.string) + { + if(strstr(va(" %s ", prvm_leaktest_ignore_classnames.string), va(" %s ", PRVM_GetString(PRVM_clientedictstring(edict, classname))))) + return true; + } + } + break; + case PRVM_MENUPROG: + // menu prog does not have classnames + break; + } + return false; +} + +static qboolean PRVM_IsEdictReferenced(prvm_edict_t *edict, int mark) +{ + int i, j; + int edictnum = PRVM_NUM_FOR_EDICT(edict); + const char *targetname = NULL; + + switch(prog - prog_list) + { + case PRVM_SERVERPROG: + targetname = PRVM_GetString(PRVM_serveredictstring(edict, targetname)); + break; + } + + if(targetname) + if(!*targetname) // "" + targetname = NULL; + + for (i = 0;i < prog->numglobaldefs;i++) + { + ddef_t *d = &prog->globaldefs[i]; + if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity) + continue; + if(edictnum == PRVM_GLOBALFIELDEDICT(d->ofs)) + return true; + } + + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if (ed->priv.required->mark < mark) + continue; + if(ed == edict) + continue; + if(targetname) + { + const char *target = PRVM_GetString(PRVM_serveredictstring(ed, target)); + if(target) + if(!strcmp(target, targetname)) + return true; + } + for (i=0; inumfielddefs; ++i) + { + ddef_t *d = &prog->fielddefs[i]; + if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity) + continue; + if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs)) + return true; + } + } + + return false; +} + +static void PRVM_MarkReferencedEdicts(void) +{ + int j; + qboolean found_new; + int stage; + + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if(ed->priv.required->free) + continue; + ed->priv.required->mark = PRVM_IsEdictRelevant(ed) ? 1 : 0; + } + + stage = 1; + do + { + found_new = false; + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if(ed->priv.required->free) + continue; + if(ed->priv.required->mark) + continue; + if(PRVM_IsEdictReferenced(ed, stage)) + { + ed->priv.required->mark = stage + 1; + found_new = true; + } + } + ++stage; + } + while(found_new); + Con_DPrintf("leak check used %d stages to find all references\n", stage); +} + +void PRVM_LeakTest(void) +{ + int i, j; + qboolean leaked = false; + + if(!prog->leaktest_active) + return; + + // 1. Strings + for (i = 0; i < prog->numknownstrings; ++i) + { + if(prog->knownstrings[i]) + if(prog->knownstrings_freeable[i]) + if(prog->knownstrings_origin[i]) + if(!PRVM_IsStringReferenced(PRVM_KNOWNSTRINGBASE + i)) + { + Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]); + leaked = true; + } + } + + // 2. Edicts + PRVM_MarkReferencedEdicts(); + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if(ed->priv.required->free) + continue; + if(!ed->priv.required->mark) + if(ed->priv.required->allocation_origin) + { + Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin); + PRVM_ED_Print(ed, NULL); + Con_Print("\n"); + leaked = true; + } + } + + for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i) + { + prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); + if(stringbuffer) + if(stringbuffer->origin) + { + Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin); + leaked = true; + } + } + + for(i = 0; i < PRVM_MAX_OPENFILES; ++i) + { + if(prog->openfiles[i]) + if(prog->openfiles_origin[i]) + { + Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]); + leaked = true; + } + } + + for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i) + { + if(prog->opensearches[i]) + if(prog->opensearches_origin[i]) + { + Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]); + leaked = true; + } + } + + if(!leaked) + Con_Printf("Congratulations. No leaks found.\n"); +} diff --git a/misc/source/darkplaces-src/prvm_exec.c b/misc/source/darkplaces-src/prvm_exec.c new file mode 100644 index 00000000..8fb0d692 --- /dev/null +++ b/misc/source/darkplaces-src/prvm_exec.c @@ -0,0 +1,943 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "progsvm.h" + +const char *prvm_opnames[] = +{ +"^5DONE", + +"MUL_F", +"MUL_V", +"MUL_FV", +"MUL_VF", + +"DIV", + +"ADD_F", +"ADD_V", + +"SUB_F", +"SUB_V", + +"^2EQ_F", +"^2EQ_V", +"^2EQ_S", +"^2EQ_E", +"^2EQ_FNC", + +"^2NE_F", +"^2NE_V", +"^2NE_S", +"^2NE_E", +"^2NE_FNC", + +"^2LE", +"^2GE", +"^2LT", +"^2GT", + +"^6FIELD_F", +"^6FIELD_V", +"^6FIELD_S", +"^6FIELD_ENT", +"^6FIELD_FLD", +"^6FIELD_FNC", + +"^1ADDRESS", + +"STORE_F", +"STORE_V", +"STORE_S", +"STORE_ENT", +"STORE_FLD", +"STORE_FNC", + +"^1STOREP_F", +"^1STOREP_V", +"^1STOREP_S", +"^1STOREP_ENT", +"^1STOREP_FLD", +"^1STOREP_FNC", + +"^5RETURN", + +"^2NOT_F", +"^2NOT_V", +"^2NOT_S", +"^2NOT_ENT", +"^2NOT_FNC", + +"^5IF", +"^5IFNOT", + +"^3CALL0", +"^3CALL1", +"^3CALL2", +"^3CALL3", +"^3CALL4", +"^3CALL5", +"^3CALL6", +"^3CALL7", +"^3CALL8", + +"^1STATE", + +"^5GOTO", + +"^2AND", +"^2OR", + +"BITAND", +"BITOR" +}; + +char *PRVM_GlobalString (int ofs); +char *PRVM_GlobalStringNoContents (int ofs); +extern ddef_t *PRVM_ED_FieldAtOfs(int ofs); + + +//============================================================================= + +/* +================= +PRVM_PrintStatement +================= +*/ +extern cvar_t prvm_statementprofiling; +extern cvar_t prvm_timeprofiling; +void PRVM_PrintStatement(mstatement_t *s) +{ + size_t i; + int opnum = (int)(s - prog->statements); + + Con_Printf("s%i: ", opnum); + if( prog->statement_linenums ) + Con_Printf( "%s:%i: ", PRVM_GetString( prog->xfunction->s_file ), prog->statement_linenums[ opnum ] ); + + if (prvm_statementprofiling.integer) + Con_Printf("%7.0f ", prog->statement_profile[s - prog->statements]); + + if ( (unsigned)s->op < sizeof(prvm_opnames)/sizeof(prvm_opnames[0])) + { + Con_Printf("%s ", prvm_opnames[s->op]); + i = strlen(prvm_opnames[s->op]); + // don't count a preceding color tag when padding the name + if (prvm_opnames[s->op][0] == STRING_COLOR_TAG) + i -= 2; + for ( ; i<10 ; i++) + Con_Print(" "); + } + if (s->operand[0] >= 0) Con_Printf( "%s", PRVM_GlobalString(s->operand[0])); + if (s->operand[1] >= 0) Con_Printf(", %s", PRVM_GlobalString(s->operand[1])); + if (s->operand[2] >= 0) Con_Printf(", %s", PRVM_GlobalString(s->operand[2])); + if (s->jumpabsolute >= 0) Con_Printf(", statement %i", s->jumpabsolute); + Con_Print("\n"); +} + +void PRVM_PrintFunctionStatements (const char *name) +{ + int i, firststatement, endstatement; + mfunction_t *func; + func = PRVM_ED_FindFunction (name); + if (!func) + { + Con_Printf("%s progs: no function named %s\n", PRVM_NAME, name); + return; + } + firststatement = func->first_statement; + if (firststatement < 0) + { + Con_Printf("%s progs: function %s is builtin #%i\n", PRVM_NAME, name, -firststatement); + return; + } + + // find the end statement + endstatement = prog->numstatements; + for (i = 0;i < prog->numfunctions;i++) + if (endstatement > prog->functions[i].first_statement && firststatement < prog->functions[i].first_statement) + endstatement = prog->functions[i].first_statement; + + // now print the range of statements + Con_Printf("%s progs: disassembly of function %s (statements %i-%i, locals %i-%i):\n", PRVM_NAME, name, firststatement, endstatement, func->parm_start, func->parm_start + func->locals - 1); + for (i = firststatement;i < endstatement;i++) + { + PRVM_PrintStatement(prog->statements + i); + prog->statement_profile[i] = 0; + } +} + +/* +============ +PRVM_PrintFunction_f + +============ +*/ +void PRVM_PrintFunction_f (void) +{ + if (Cmd_Argc() != 3) + { + Con_Printf("usage: prvm_printfunction \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + PRVM_PrintFunctionStatements(Cmd_Argv(2)); + + PRVM_End; +} + +/* +============ +PRVM_StackTrace +============ +*/ +void PRVM_StackTrace (void) +{ + mfunction_t *f; + int i; + + prog->stack[prog->depth].s = prog->xstatement; + prog->stack[prog->depth].f = prog->xfunction; + for (i = prog->depth;i > 0;i--) + { + f = prog->stack[i].f; + + if (!f) + Con_Print("\n"); + else + Con_Printf("%12s : %s : statement %i\n", PRVM_GetString(f->s_file), PRVM_GetString(f->s_name), prog->stack[i].s - f->first_statement); + } +} + +void PRVM_ShortStackTrace(char *buf, size_t bufsize) +{ + mfunction_t *f; + int i; + + if(prog) + { + dpsnprintf(buf, bufsize, "(%s) ", prog->name); + } + else + { + strlcpy(buf, "", bufsize); + return; + } + + prog->stack[prog->depth].s = prog->xstatement; + prog->stack[prog->depth].f = prog->xfunction; + for (i = prog->depth;i > 0;i--) + { + f = prog->stack[i].f; + + if(strlcat(buf, + f + ? va("%s:%s(%i) ", PRVM_GetString(f->s_file), PRVM_GetString(f->s_name), prog->stack[i].s - f->first_statement) + : " ", + bufsize + ) >= bufsize) + break; + } +} + + +void PRVM_CallProfile (void) +{ + mfunction_t *f, *best; + int i; + double max; + double sum; + + Con_Printf( "%s Call Profile:\n", PRVM_NAME ); + + sum = 0; + do + { + max = 0; + best = NULL; + for (i=0 ; inumfunctions ; i++) + { + f = &prog->functions[i]; + if (max < f->totaltime) + { + max = f->totaltime; + best = f; + } + } + if (best) + { + sum += best->totaltime; + Con_Printf("%9.4f %s\n", best->totaltime, PRVM_GetString(best->s_name)); + best->totaltime = 0; + } + } while (best); + + Con_Printf("Total time since last profile reset: %9.4f\n", Sys_DoubleTime() - prog->starttime); + Con_Printf(" - used by QC code of this VM: %9.4f\n", sum); + + prog->starttime = Sys_DoubleTime(); +} + +void PRVM_Profile (int maxfunctions, double mintime, int sortby) +{ + mfunction_t *f, *best; + int i, num; + double max; + + if(!prvm_timeprofiling.integer) + mintime *= 10000000; // count each statement as about 0.1µs + + if(prvm_timeprofiling.integer) + Con_Printf( "%s Profile:\n[CallCount] [Time] [BuiltinTm] [Statement] [BuiltinCt] [TimeTotal] [StmtTotal] [BltnTotal] [self]\n", PRVM_NAME ); + // 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 123.45% + else + Con_Printf( "%s Profile:\n[CallCount] [Statement] [BuiltinCt] [StmtTotal] [BltnTotal] [self]\n", PRVM_NAME ); + // 12345678901 12345678901 12345678901 12345678901 12345678901 123.45% + + num = 0; + do + { + max = 0; + best = NULL; + for (i=0 ; inumfunctions ; i++) + { + f = &prog->functions[i]; + if(prvm_timeprofiling.integer) + { + if(sortby) + { + if(f->first_statement < 0) + { + if (max < f->tprofile) + { + max = f->tprofile; + best = f; + } + } + else + { + if (max < f->tprofile_total) + { + max = f->tprofile_total; + best = f; + } + } + } + else + { + if (max < f->tprofile + f->tbprofile) + { + max = f->tprofile + f->tbprofile; + best = f; + } + } + } + else + { + if(sortby) + { + if (max < f->profile_total + f->builtinsprofile_total + f->callcount) + { + max = f->profile_total + f->builtinsprofile_total + f->callcount; + best = f; + } + } + else + { + if (max < f->profile + f->builtinsprofile + f->callcount) + { + max = f->profile + f->builtinsprofile + f->callcount; + best = f; + } + } + } + } + if (best) + { + if (num < maxfunctions && max > mintime) + { + if(prvm_timeprofiling.integer) + { + if (best->first_statement < 0) + Con_Printf("%11.0f %11.6f ------------- builtin ------------- %11.6f ----------- builtin ----------- %s\n", best->callcount, best->tprofile, best->tprofile, PRVM_GetString(best->s_name)); + // %11.6f 12345678901 12345678901 12345678901 %11.6f 12345678901 12345678901 123.45% + else + Con_Printf("%11.0f %11.6f %11.6f %11.0f %11.0f %11.6f %11.0f %11.0f %6.2f%% %s\n", best->callcount, best->tprofile, best->tbprofile, best->profile, best->builtinsprofile, best->tprofile_total, best->profile_total, best->builtinsprofile_total, (best->tprofile_total > 0) ? ((best->tprofile) * 100.0 / (best->tprofile_total)) : -99.99, PRVM_GetString(best->s_name)); + } + else + { + if (best->first_statement < 0) + Con_Printf("%11.0f ----------------------- builtin ----------------------- %s\n", best->callcount, PRVM_GetString(best->s_name)); + // 12345678901 12345678901 12345678901 12345678901 123.45% + else + Con_Printf("%11.0f %11.0f %11.0f %11.0f %11.0f %6.2f%% %s\n", best->callcount, best->profile, best->builtinsprofile, best->profile_total, best->builtinsprofile_total, (best->profile + best->builtinsprofile) * 100.0 / (best->profile_total + best->builtinsprofile_total), PRVM_GetString(best->s_name)); + } + } + num++; + best->profile = 0; + best->tprofile = 0; + best->tbprofile = 0; + best->builtinsprofile = 0; + best->profile_total = 0; + best->tprofile_total = 0; + best->builtinsprofile_total = 0; + best->callcount = 0; + } + } while (best); +} + +/* +============ +PRVM_CallProfile_f + +============ +*/ +void PRVM_CallProfile_f (void) +{ + if (Cmd_Argc() != 2) + { + Con_Print("prvm_callprofile \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + PRVM_CallProfile(); + + PRVM_End; +} + +/* +============ +PRVM_Profile_f + +============ +*/ +void PRVM_Profile_f (void) +{ + int howmany; + + howmany = 1<<30; + if (Cmd_Argc() == 3) + howmany = atoi(Cmd_Argv(2)); + else if (Cmd_Argc() != 2) + { + Con_Print("prvm_profile \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + PRVM_Profile(howmany, 0, 0); + + PRVM_End; +} + +void PRVM_ChildProfile_f (void) +{ + int howmany; + + howmany = 1<<30; + if (Cmd_Argc() == 3) + howmany = atoi(Cmd_Argv(2)); + else if (Cmd_Argc() != 2) + { + Con_Print("prvm_childprofile \n"); + return; + } + + PRVM_Begin; + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + PRVM_Profile(howmany, 0, 1); + + PRVM_End; +} + +void PRVM_CrashAll(void) +{ + int i; + prvm_prog_t *oldprog = prog; + + for(i = 0; i < PRVM_MAXPROGS; i++) + { + if(!PRVM_ProgLoaded(i)) + continue; + PRVM_SetProg(i); + PRVM_Crash(); + } + + prog = oldprog; +} + +void PRVM_PrintState(void) +{ + int i; + if(prog->statestring) + { + Con_Printf("Caller-provided information: %s\n", prog->statestring); + } + if (prog->xfunction) + { + for (i = -7; i <= 0;i++) + if (prog->xstatement + i >= prog->xfunction->first_statement) + PRVM_PrintStatement (prog->statements + prog->xstatement + i); + } + else + Con_Print("null function executing??\n"); + PRVM_StackTrace (); +} + +extern sizebuf_t vm_tempstringsbuf; +extern cvar_t prvm_errordump; +void Host_Savegame_to (const char *name); +void PRVM_Crash(void) +{ + if (prog == NULL) + return; + + PRVM_serverfunction(SV_Shutdown) = 0; // don't call SV_Shutdown on crash + + if( prog->depth > 0 ) + { + Con_Printf("QuakeC crash report for %s:\n", PRVM_NAME); + PRVM_PrintState(); + } + + if(prvm_errordump.integer) + { + // make a savegame + Host_Savegame_to(va("crash-%s.dmp", PRVM_NAME)); + } + + // dump the stack so host_error can shutdown functions + prog->depth = 0; + prog->localstack_used = 0; + + // delete all tempstrings (FIXME: is this safe in VM->engine->VM recursion?) + vm_tempstringsbuf.cursize = 0; + + // reset the prog pointer + prog = NULL; +} + +/* +============================================================================ +PRVM_ExecuteProgram + +The interpretation main loop +============================================================================ +*/ + +/* +==================== +PRVM_EnterFunction + +Returns the new program statement counter +==================== +*/ +int PRVM_EnterFunction (mfunction_t *f) +{ + int i, j, c, o; + + if (!f) + PRVM_ERROR ("PRVM_EnterFunction: NULL function in %s", PRVM_NAME); + + prog->stack[prog->depth].s = prog->xstatement; + prog->stack[prog->depth].f = prog->xfunction; + prog->stack[prog->depth].profile_acc = -f->profile; + prog->stack[prog->depth].tprofile_acc = -f->tprofile + -f->tbprofile; + prog->stack[prog->depth].builtinsprofile_acc = -f->builtinsprofile; + prog->depth++; + if (prog->depth >=PRVM_MAX_STACK_DEPTH) + PRVM_ERROR ("stack overflow"); + +// save off any locals that the new function steps on + c = f->locals; + if (prog->localstack_used + c > PRVM_LOCALSTACK_SIZE) + PRVM_ERROR ("PRVM_ExecuteProgram: locals stack overflow in %s", PRVM_NAME); + + for (i=0 ; i < c ; i++) + prog->localstack[prog->localstack_used+i] = ((int *)prog->globals.generic)[f->parm_start + i]; + prog->localstack_used += c; + +// copy parameters + o = f->parm_start; + for (i=0 ; inumparms ; i++) + { + for (j=0 ; jparm_size[i] ; j++) + { + ((int *)prog->globals.generic)[o] = ((int *)prog->globals.generic)[OFS_PARM0+i*3+j]; + o++; + } + } + + ++f->recursion; + prog->xfunction = f; + return f->first_statement - 1; // offset the s++ +} + +/* +==================== +PRVM_LeaveFunction +==================== +*/ +int PRVM_LeaveFunction (void) +{ + int i, c; + mfunction_t *f; + + if (prog->depth <= 0) + PRVM_ERROR ("prog stack underflow in %s", PRVM_NAME); + + if (!prog->xfunction) + PRVM_ERROR ("PR_LeaveFunction: NULL function in %s", PRVM_NAME); +// restore locals from the stack + c = prog->xfunction->locals; + prog->localstack_used -= c; + if (prog->localstack_used < 0) + PRVM_ERROR ("PRVM_ExecuteProgram: locals stack underflow in %s", PRVM_NAME); + + for (i=0 ; i < c ; i++) + ((int *)prog->globals.generic)[prog->xfunction->parm_start + i] = prog->localstack[prog->localstack_used+i]; + +// up stack + prog->depth--; + f = prog->xfunction; + --f->recursion; + prog->xfunction = prog->stack[prog->depth].f; + prog->stack[prog->depth].profile_acc += f->profile; + prog->stack[prog->depth].tprofile_acc += f->tprofile + f->tbprofile; + prog->stack[prog->depth].builtinsprofile_acc += f->builtinsprofile; + if(prog->depth > 0) + { + prog->stack[prog->depth-1].profile_acc += prog->stack[prog->depth].profile_acc; + prog->stack[prog->depth-1].tprofile_acc += prog->stack[prog->depth].tprofile_acc; + prog->stack[prog->depth-1].builtinsprofile_acc += prog->stack[prog->depth].builtinsprofile_acc; + } + if(!f->recursion) + { + // if f is already on the call stack... + // we cannot add this profile data to it now + // or we would add it more than once + // so, let's only add to the function's profile if it is the outermost call + f->profile_total += prog->stack[prog->depth].profile_acc; + f->tprofile_total += prog->stack[prog->depth].tprofile_acc; + f->builtinsprofile_total += prog->stack[prog->depth].builtinsprofile_acc; + } + + return prog->stack[prog->depth].s; +} + +void PRVM_Init_Exec(void) +{ + // dump the stack + prog->depth = 0; + prog->localstack_used = 0; + // reset the string table + // nothing here yet +} + +#define OPA ((prvm_eval_t *)&prog->globals.generic[st->operand[0]]) +#define OPB ((prvm_eval_t *)&prog->globals.generic[st->operand[1]]) +#define OPC ((prvm_eval_t *)&prog->globals.generic[st->operand[2]]) +extern cvar_t prvm_traceqc; +extern cvar_t prvm_statementprofiling; +extern sizebuf_t vm_tempstringsbuf; +extern qboolean prvm_runawaycheck; + +#ifdef PROFILING +/* +==================== +MVM_ExecuteProgram +==================== +*/ +void MVM_ExecuteProgram (func_t fnum, const char *errormessage) +{ + mstatement_t *st, *startst; + mfunction_t *f, *newf; + prvm_edict_t *ed; + prvm_eval_t *ptr; + int jumpcount, cachedpr_trace, exitdepth; + int restorevm_tempstringsbuf_cursize; + double calltime; + double tm, starttm; + + calltime = Sys_DoubleTime(); + + if (!fnum || fnum >= (unsigned int)prog->numfunctions) + { + if (PRVM_allglobaledict(self)) + PRVM_ED_Print(PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); + PRVM_ERROR ("MVM_ExecuteProgram: %s", errormessage); + } + + f = &prog->functions[fnum]; + + // after executing this function, delete all tempstrings it created + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + + prog->trace = prvm_traceqc.integer; + + // we know we're done when pr_depth drops to this + exitdepth = prog->depth; + +// make a stack frame + st = &prog->statements[PRVM_EnterFunction (f)]; + // save the starting statement pointer for profiling + // (when the function exits or jumps, the (st - startst) integer value is + // added to the function's profile counter) + startst = st; + starttm = calltime; + // instead of counting instructions, we count jumps + jumpcount = 0; + // add one to the callcount of this function because otherwise engine-called functions aren't counted + prog->xfunction->callcount++; + +chooseexecprogram: + cachedpr_trace = prog->trace; + if (prvm_statementprofiling.integer || prog->trace) + { +#define PRVMSLOWINTERPRETER 1 + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } +#undef PRVMSLOWINTERPRETER + } + else + { + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } + } + +cleanup: + if (developer_insane.integer && vm_tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) + Con_DPrintf("MVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog->functions[fnum].s_name), vm_tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); + // delete tempstrings created by this function + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + + f->totaltime += (Sys_DoubleTime() - calltime); + + SV_FlushBroadcastMessages(); +} + +/* +==================== +CLVM_ExecuteProgram +==================== +*/ +void CLVM_ExecuteProgram (func_t fnum, const char *errormessage) +{ + mstatement_t *st, *startst; + mfunction_t *f, *newf; + prvm_edict_t *ed; + prvm_eval_t *ptr; + int jumpcount, cachedpr_trace, exitdepth; + int restorevm_tempstringsbuf_cursize; + double calltime; + double tm, starttm; + + calltime = Sys_DoubleTime(); + + if (!fnum || fnum >= (unsigned int)prog->numfunctions) + { + if (PRVM_allglobaledict(self)) + PRVM_ED_Print(PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); + PRVM_ERROR ("CLVM_ExecuteProgram: %s", errormessage); + } + + f = &prog->functions[fnum]; + + // after executing this function, delete all tempstrings it created + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + + prog->trace = prvm_traceqc.integer; + + // we know we're done when pr_depth drops to this + exitdepth = prog->depth; + +// make a stack frame + st = &prog->statements[PRVM_EnterFunction (f)]; + // save the starting statement pointer for profiling + // (when the function exits or jumps, the (st - startst) integer value is + // added to the function's profile counter) + startst = st; + starttm = calltime; + // instead of counting instructions, we count jumps + jumpcount = 0; + // add one to the callcount of this function because otherwise engine-called functions aren't counted + prog->xfunction->callcount++; + +chooseexecprogram: + cachedpr_trace = prog->trace; + if (prvm_statementprofiling.integer || prog->trace) + { +#define PRVMSLOWINTERPRETER 1 + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } +#undef PRVMSLOWINTERPRETER + } + else + { + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } + } + +cleanup: + if (developer_insane.integer && vm_tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) + Con_DPrintf("CLVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog->functions[fnum].s_name), vm_tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); + // delete tempstrings created by this function + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + + f->totaltime += (Sys_DoubleTime() - calltime); + + SV_FlushBroadcastMessages(); +} +#endif + +/* +==================== +SVVM_ExecuteProgram +==================== +*/ +void SVVM_ExecuteProgram (func_t fnum, const char *errormessage) +{ + mstatement_t *st, *startst; + mfunction_t *f, *newf; + prvm_edict_t *ed; + prvm_eval_t *ptr; + int jumpcount, cachedpr_trace, exitdepth; + int restorevm_tempstringsbuf_cursize; + double calltime; + double tm, starttm; + + calltime = Sys_DoubleTime(); + + if (!fnum || fnum >= (unsigned int)prog->numfunctions) + { + if (PRVM_allglobaledict(self)) + PRVM_ED_Print(PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); + PRVM_ERROR ("SVVM_ExecuteProgram: %s", errormessage); + } + + f = &prog->functions[fnum]; + + // after executing this function, delete all tempstrings it created + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + + prog->trace = prvm_traceqc.integer; + + // we know we're done when pr_depth drops to this + exitdepth = prog->depth; + +// make a stack frame + st = &prog->statements[PRVM_EnterFunction (f)]; + // save the starting statement pointer for profiling + // (when the function exits or jumps, the (st - startst) integer value is + // added to the function's profile counter) + startst = st; + starttm = calltime; + // instead of counting instructions, we count jumps + jumpcount = 0; + // add one to the callcount of this function because otherwise engine-called functions aren't counted + prog->xfunction->callcount++; + +chooseexecprogram: + cachedpr_trace = prog->trace; + if (prvm_statementprofiling.integer || prog->trace) + { +#define PRVMSLOWINTERPRETER 1 + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } +#undef PRVMSLOWINTERPRETER + } + else + { + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } + } + +cleanup: + if (developer_insane.integer && vm_tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) + Con_DPrintf("SVVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog->functions[fnum].s_name), vm_tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); + // delete tempstrings created by this function + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + + f->totaltime += (Sys_DoubleTime() - calltime); + + SV_FlushBroadcastMessages(); +} diff --git a/misc/source/darkplaces-src/prvm_execprogram.h b/misc/source/darkplaces-src/prvm_execprogram.h new file mode 100644 index 00000000..4d872f64 --- /dev/null +++ b/misc/source/darkplaces-src/prvm_execprogram.h @@ -0,0 +1,683 @@ +#ifdef PRVMTIMEPROFILING +#define PreError() \ + prog->xstatement = st - prog->statements; \ + tm = Sys_DoubleTime(); \ + prog->xfunction->profile += (st - startst); \ + prog->xfunction->tprofile += (tm - starttm); +#else +#define PreError() \ + prog->xstatement = st - prog->statements; \ + prog->xfunction->profile += (st - startst); +#endif + +// This code isn't #ifdef/#define protectable, don't try. + + while (1) + { + st++; + +#if PRVMSLOWINTERPRETER + if (prog->trace) + PRVM_PrintStatement(st); + prog->statement_profile[st - prog->statements]++; +#endif + + switch (st->op) + { + case OP_ADD_F: + OPC->_float = OPA->_float + OPB->_float; + break; + case OP_ADD_V: + OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; + break; + case OP_SUB_F: + OPC->_float = OPA->_float - OPB->_float; + break; + case OP_SUB_V: + OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; + break; + case OP_MUL_F: + OPC->_float = OPA->_float * OPB->_float; + break; + case OP_MUL_V: + OPC->_float = OPA->vector[0]*OPB->vector[0] + OPA->vector[1]*OPB->vector[1] + OPA->vector[2]*OPB->vector[2]; + break; + case OP_MUL_FV: + OPC->vector[0] = OPA->_float * OPB->vector[0]; + OPC->vector[1] = OPA->_float * OPB->vector[1]; + OPC->vector[2] = OPA->_float * OPB->vector[2]; + break; + case OP_MUL_VF: + OPC->vector[0] = OPB->_float * OPA->vector[0]; + OPC->vector[1] = OPB->_float * OPA->vector[1]; + OPC->vector[2] = OPB->_float * OPA->vector[2]; + break; + case OP_DIV_F: + if( OPB->_float != 0.0f ) + { + OPC->_float = OPA->_float / OPB->_float; + } + else + { + if (developer.integer) + { + prog->xfunction->profile += (st - startst); + startst = st; + prog->xstatement = st - prog->statements; + VM_Warning( "Attempted division by zero in %s\n", PRVM_NAME ); + } + OPC->_float = 0.0f; + } + break; + case OP_BITAND: + OPC->_float = (int)OPA->_float & (int)OPB->_float; + break; + case OP_BITOR: + OPC->_float = (int)OPA->_float | (int)OPB->_float; + break; + case OP_GE: + OPC->_float = OPA->_float >= OPB->_float; + break; + case OP_LE: + OPC->_float = OPA->_float <= OPB->_float; + break; + case OP_GT: + OPC->_float = OPA->_float > OPB->_float; + break; + case OP_LT: + OPC->_float = OPA->_float < OPB->_float; + break; + case OP_AND: + OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) && FLOAT_IS_TRUE_FOR_INT(OPB->_int); // TODO change this back to float, and add AND_I to be used by fteqcc for anything not a float + break; + case OP_OR: + OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) || FLOAT_IS_TRUE_FOR_INT(OPB->_int); // TODO change this back to float, and add OR_I to be used by fteqcc for anything not a float + break; + case OP_NOT_F: + OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int); + break; + case OP_NOT_V: + OPC->_float = !OPA->vector[0] && !OPA->vector[1] && !OPA->vector[2]; + break; + case OP_NOT_S: + OPC->_float = !OPA->string || !*PRVM_GetString(OPA->string); + break; + case OP_NOT_FNC: + OPC->_float = !OPA->function; + break; + case OP_NOT_ENT: + OPC->_float = (OPA->edict == 0); + break; + case OP_EQ_F: + OPC->_float = OPA->_float == OPB->_float; + break; + case OP_EQ_V: + OPC->_float = (OPA->vector[0] == OPB->vector[0]) && (OPA->vector[1] == OPB->vector[1]) && (OPA->vector[2] == OPB->vector[2]); + break; + case OP_EQ_S: + OPC->_float = !strcmp(PRVM_GetString(OPA->string),PRVM_GetString(OPB->string)); + break; + case OP_EQ_E: + OPC->_float = OPA->_int == OPB->_int; + break; + case OP_EQ_FNC: + OPC->_float = OPA->function == OPB->function; + break; + case OP_NE_F: + OPC->_float = OPA->_float != OPB->_float; + break; + case OP_NE_V: + OPC->_float = (OPA->vector[0] != OPB->vector[0]) || (OPA->vector[1] != OPB->vector[1]) || (OPA->vector[2] != OPB->vector[2]); + break; + case OP_NE_S: + OPC->_float = strcmp(PRVM_GetString(OPA->string),PRVM_GetString(OPB->string)); + break; + case OP_NE_E: + OPC->_float = OPA->_int != OPB->_int; + break; + case OP_NE_FNC: + OPC->_float = OPA->function != OPB->function; + break; + + //================== + case OP_STORE_F: + case OP_STORE_ENT: + case OP_STORE_FLD: // integers + case OP_STORE_S: + case OP_STORE_FNC: // pointers + OPB->_int = OPA->_int; + break; + case OP_STORE_V: + OPB->ivector[0] = OPA->ivector[0]; + OPB->ivector[1] = OPA->ivector[1]; + OPB->ivector[2] = OPA->ivector[2]; + break; + + case OP_STOREP_F: + case OP_STOREP_ENT: + case OP_STOREP_FLD: // integers + case OP_STOREP_S: + case OP_STOREP_FNC: // pointers + if (OPB->_int < 0 || OPB->_int + 1 > prog->entityfieldsarea) + { + PreError(); + PRVM_ERROR("%s attempted to write to an out of bounds edict (%i)", PRVM_NAME, OPB->_int); + goto cleanup; + } + if (OPB->_int < prog->entityfields && !prog->allowworldwrites) + { + prog->xstatement = st - prog->statements; + VM_Warning("assignment to world.%s (field %i) in %s\n", PRVM_GetString(PRVM_ED_FieldAtOfs(OPB->_int)->s_name), OPB->_int, PRVM_NAME); + } + ptr = (prvm_eval_t *)(prog->edictsfields + OPB->_int); + ptr->_int = OPA->_int; + break; + case OP_STOREP_V: + if (OPB->_int < 0 || OPB->_int + 3 > prog->entityfieldsarea) + { + PreError(); + PRVM_ERROR("%s attempted to write to an out of bounds edict (%i)", PRVM_NAME, OPB->_int); + goto cleanup; + } + if (OPB->_int < prog->entityfields && !prog->allowworldwrites) + { + prog->xstatement = st - prog->statements; + VM_Warning("assignment to world.%s (field %i) in %s\n", PRVM_GetString(PRVM_ED_FieldAtOfs(OPB->_int)->s_name), OPB->_int, PRVM_NAME); + } + ptr = (prvm_eval_t *)(prog->edictsfields + OPB->_int); + ptr->ivector[0] = OPA->ivector[0]; + ptr->ivector[1] = OPA->ivector[1]; + ptr->ivector[2] = OPA->ivector[2]; + break; + + case OP_ADDRESS: + if (OPA->edict < 0 || OPA->edict >= prog->max_edicts) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to address an out of bounds edict number", PRVM_NAME); + goto cleanup; + } + if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) + { + PreError(); + PRVM_ERROR("%s attempted to address an invalid field (%i) in an edict", PRVM_NAME, OPB->_int); + goto cleanup; + } +#if 0 + if (OPA->edict == 0 && !prog->allowworldwrites) + { + PreError(); + PRVM_ERROR("forbidden assignment to null/world entity in %s", PRVM_NAME); + goto cleanup; + } +#endif + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = ed->fields.vp - prog->edictsfields + OPB->_int; + break; + + case OP_LOAD_F: + case OP_LOAD_FLD: + case OP_LOAD_ENT: + case OP_LOAD_S: + case OP_LOAD_FNC: + if (OPA->edict < 0 || OPA->edict >= prog->max_edicts) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to read an out of bounds edict number", PRVM_NAME); + goto cleanup; + } + if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) + { + PreError(); + PRVM_ERROR("%s attempted to read an invalid field in an edict (%i)", PRVM_NAME, OPB->_int); + goto cleanup; + } + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = ((prvm_eval_t *)((int *)ed->fields.vp + OPB->_int))->_int; + break; + + case OP_LOAD_V: + if (OPA->edict < 0 || OPA->edict >= prog->max_edicts) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to read an out of bounds edict number", PRVM_NAME); + goto cleanup; + } + if (OPB->_int < 0 || OPB->_int + 2 >= prog->entityfields) + { + PreError(); + PRVM_ERROR("%s attempted to read an invalid field in an edict (%i)", PRVM_NAME, OPB->_int); + goto cleanup; + } + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->ivector[0] = ((prvm_eval_t *)((int *)ed->fields.vp + OPB->_int))->ivector[0]; + OPC->ivector[1] = ((prvm_eval_t *)((int *)ed->fields.vp + OPB->_int))->ivector[1]; + OPC->ivector[2] = ((prvm_eval_t *)((int *)ed->fields.vp + OPB->_int))->ivector[2]; + break; + + //================== + + case OP_IFNOT: + if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int)) + // TODO add an "int-if", and change this one to OPA->_float + // although mostly unneeded, thanks to the only float being false being 0x0 and 0x80000000 (negative zero) + // and entity, string, field values can never have that value + { + prog->xfunction->profile += (st - startst); + st = prog->statements + st->jumpabsolute - 1; // offset the st++ + startst = st; + // no bounds check needed, it is done when loading progs + if (++jumpcount == 10000000 && prvm_runawaycheck) + { + prog->xstatement = st - prog->statements; + PRVM_Profile(1<<30, 1000000, 0); + PRVM_ERROR("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", PRVM_NAME, jumpcount); + } + } + break; + + case OP_IF: + if(FLOAT_IS_TRUE_FOR_INT(OPA->_int)) + // TODO add an "int-if", and change this one, as well as the FLOAT_IS_TRUE_FOR_INT usages, to OPA->_float + // although mostly unneeded, thanks to the only float being false being 0x0 and 0x80000000 (negative zero) + // and entity, string, field values can never have that value + { + prog->xfunction->profile += (st - startst); + st = prog->statements + st->jumpabsolute - 1; // offset the st++ + startst = st; + // no bounds check needed, it is done when loading progs + if (++jumpcount == 10000000 && prvm_runawaycheck) + { + prog->xstatement = st - prog->statements; + PRVM_Profile(1<<30, 0.01, 0); + PRVM_ERROR("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", PRVM_NAME, jumpcount); + } + } + break; + + case OP_GOTO: + prog->xfunction->profile += (st - startst); + st = prog->statements + st->jumpabsolute - 1; // offset the st++ + startst = st; + // no bounds check needed, it is done when loading progs + if (++jumpcount == 10000000 && prvm_runawaycheck) + { + prog->xstatement = st - prog->statements; + PRVM_Profile(1<<30, 0.01, 0); + PRVM_ERROR("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", PRVM_NAME, jumpcount); + } + break; + + case OP_CALL0: + case OP_CALL1: + case OP_CALL2: + case OP_CALL3: + case OP_CALL4: + case OP_CALL5: + case OP_CALL6: + case OP_CALL7: + case OP_CALL8: +#ifdef PRVMTIMEPROFILING + tm = Sys_DoubleTime(); + prog->xfunction->tprofile += (tm - starttm); + starttm = tm; +#endif + prog->xfunction->profile += (st - startst); + startst = st; + prog->xstatement = st - prog->statements; + prog->argc = st->op - OP_CALL0; + if (!OPA->function) + PRVM_ERROR("NULL function in %s", PRVM_NAME); + + if(!OPA->function || OPA->function >= (unsigned int)prog->numfunctions) + { + PreError(); + PRVM_ERROR("%s CALL outside the program", PRVM_NAME); + goto cleanup; + } + + newf = &prog->functions[OPA->function]; + newf->callcount++; + + if (newf->first_statement < 0) + { + // negative statements are built in functions + int builtinnumber = -newf->first_statement; + prog->xfunction->builtinsprofile++; + if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber]) + { + prog->builtins[builtinnumber](); +#ifdef PRVMTIMEPROFILING + tm = Sys_DoubleTime(); + newf->tprofile += (tm - starttm); + prog->xfunction->tbprofile += (tm - starttm); + starttm = tm; +#endif + } + else + PRVM_ERROR("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, PRVM_NAME); + } + else + st = prog->statements + PRVM_EnterFunction(newf); + startst = st; + break; + + case OP_DONE: + case OP_RETURN: +#ifdef PRVMTIMEPROFILING + tm = Sys_DoubleTime(); + prog->xfunction->tprofile += (tm - starttm); + starttm = tm; +#endif + prog->xfunction->profile += (st - startst); + prog->xstatement = st - prog->statements; + + prog->globals.generic[OFS_RETURN] = prog->globals.generic[st->operand[0]]; + prog->globals.generic[OFS_RETURN+1] = prog->globals.generic[st->operand[0]+1]; + prog->globals.generic[OFS_RETURN+2] = prog->globals.generic[st->operand[0]+2]; + + st = prog->statements + PRVM_LeaveFunction(); + startst = st; + if (prog->depth <= exitdepth) + goto cleanup; // all done + if (prog->trace != cachedpr_trace) + goto chooseexecprogram; + break; + + case OP_STATE: + if(prog->flag & PRVM_OP_STATE) + { + ed = PRVM_PROG_TO_EDICT(PRVM_gameglobaledict(self)); + PRVM_gameedictfloat(ed,nextthink) = PRVM_gameglobalfloat(time) + 0.1; + PRVM_gameedictfloat(ed,frame) = OPA->_float; + PRVM_gameedictfunction(ed,think) = OPB->function; + } + else + { + PreError(); + prog->xstatement = st - prog->statements; + PRVM_ERROR("OP_STATE not supported by %s", PRVM_NAME); + } + break; + +// LordHavoc: to be enabled when Progs version 7 (or whatever it will be numbered) is finalized +/* + case OP_ADD_I: + OPC->_int = OPA->_int + OPB->_int; + break; + case OP_ADD_IF: + OPC->_int = OPA->_int + (int) OPB->_float; + break; + case OP_ADD_FI: + OPC->_float = OPA->_float + (float) OPB->_int; + break; + case OP_SUB_I: + OPC->_int = OPA->_int - OPB->_int; + break; + case OP_SUB_IF: + OPC->_int = OPA->_int - (int) OPB->_float; + break; + case OP_SUB_FI: + OPC->_float = OPA->_float - (float) OPB->_int; + break; + case OP_MUL_I: + OPC->_int = OPA->_int * OPB->_int; + break; + case OP_MUL_IF: + OPC->_int = OPA->_int * (int) OPB->_float; + break; + case OP_MUL_FI: + OPC->_float = OPA->_float * (float) OPB->_int; + break; + case OP_MUL_VI: + OPC->vector[0] = (float) OPB->_int * OPA->vector[0]; + OPC->vector[1] = (float) OPB->_int * OPA->vector[1]; + OPC->vector[2] = (float) OPB->_int * OPA->vector[2]; + break; + case OP_DIV_VF: + { + float temp = 1.0f / OPB->_float; + OPC->vector[0] = temp * OPA->vector[0]; + OPC->vector[1] = temp * OPA->vector[1]; + OPC->vector[2] = temp * OPA->vector[2]; + } + break; + case OP_DIV_I: + OPC->_int = OPA->_int / OPB->_int; + break; + case OP_DIV_IF: + OPC->_int = OPA->_int / (int) OPB->_float; + break; + case OP_DIV_FI: + OPC->_float = OPA->_float / (float) OPB->_int; + break; + case OP_CONV_IF: + OPC->_float = OPA->_int; + break; + case OP_CONV_FI: + OPC->_int = OPA->_float; + break; + case OP_BITAND_I: + OPC->_int = OPA->_int & OPB->_int; + break; + case OP_BITOR_I: + OPC->_int = OPA->_int | OPB->_int; + break; + case OP_BITAND_IF: + OPC->_int = OPA->_int & (int)OPB->_float; + break; + case OP_BITOR_IF: + OPC->_int = OPA->_int | (int)OPB->_float; + break; + case OP_BITAND_FI: + OPC->_float = (int)OPA->_float & OPB->_int; + break; + case OP_BITOR_FI: + OPC->_float = (int)OPA->_float | OPB->_int; + break; + case OP_GE_I: + OPC->_float = OPA->_int >= OPB->_int; + break; + case OP_LE_I: + OPC->_float = OPA->_int <= OPB->_int; + break; + case OP_GT_I: + OPC->_float = OPA->_int > OPB->_int; + break; + case OP_LT_I: + OPC->_float = OPA->_int < OPB->_int; + break; + case OP_AND_I: + OPC->_float = OPA->_int && OPB->_int; + break; + case OP_OR_I: + OPC->_float = OPA->_int || OPB->_int; + break; + case OP_GE_IF: + OPC->_float = (float)OPA->_int >= OPB->_float; + break; + case OP_LE_IF: + OPC->_float = (float)OPA->_int <= OPB->_float; + break; + case OP_GT_IF: + OPC->_float = (float)OPA->_int > OPB->_float; + break; + case OP_LT_IF: + OPC->_float = (float)OPA->_int < OPB->_float; + break; + case OP_AND_IF: + OPC->_float = (float)OPA->_int && OPB->_float; + break; + case OP_OR_IF: + OPC->_float = (float)OPA->_int || OPB->_float; + break; + case OP_GE_FI: + OPC->_float = OPA->_float >= (float)OPB->_int; + break; + case OP_LE_FI: + OPC->_float = OPA->_float <= (float)OPB->_int; + break; + case OP_GT_FI: + OPC->_float = OPA->_float > (float)OPB->_int; + break; + case OP_LT_FI: + OPC->_float = OPA->_float < (float)OPB->_int; + break; + case OP_AND_FI: + OPC->_float = OPA->_float && (float)OPB->_int; + break; + case OP_OR_FI: + OPC->_float = OPA->_float || (float)OPB->_int; + break; + case OP_NOT_I: + OPC->_float = !OPA->_int; + break; + case OP_EQ_I: + OPC->_float = OPA->_int == OPB->_int; + break; + case OP_EQ_IF: + OPC->_float = (float)OPA->_int == OPB->_float; + break; + case OP_EQ_FI: + OPC->_float = OPA->_float == (float)OPB->_int; + break; + case OP_NE_I: + OPC->_float = OPA->_int != OPB->_int; + break; + case OP_NE_IF: + OPC->_float = (float)OPA->_int != OPB->_float; + break; + case OP_NE_FI: + OPC->_float = OPA->_float != (float)OPB->_int; + break; + case OP_STORE_I: + OPB->_int = OPA->_int; + break; + case OP_STOREP_I: +#if PRBOUNDSCHECK + if (OPB->_int < 0 || OPB->_int + 4 > pr_edictareasize) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to write to an out of bounds edict", PRVM_NAME); + goto cleanup; + } +#endif + ptr = (prvm_eval_t *)(prog->edictsfields + OPB->_int); + ptr->_int = OPA->_int; + break; + case OP_LOAD_I: +#if PRBOUNDSCHECK + if (OPA->edict < 0 || OPA->edict >= prog->max_edicts) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to read an out of bounds edict number", PRVM_NAME); + goto cleanup; + } + if (OPB->_int < 0 || OPB->_int >= progs->entityfields) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to read an invalid field in an edict", PRVM_NAME); + goto cleanup; + } +#endif + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = ((prvm_eval_t *)((int *)ed->v + OPB->_int))->_int; + break; + + case OP_GSTOREP_I: + case OP_GSTOREP_F: + case OP_GSTOREP_ENT: + case OP_GSTOREP_FLD: // integers + case OP_GSTOREP_S: + case OP_GSTOREP_FNC: // pointers +#if PRBOUNDSCHECK + if (OPB->_int < 0 || OPB->_int >= pr_globaldefs) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to write to an invalid indexed global", PRVM_NAME); + goto cleanup; + } +#endif + pr_iglobals[OPB->_int] = OPA->_int; + break; + case OP_GSTOREP_V: +#if PRBOUNDSCHECK + if (OPB->_int < 0 || OPB->_int + 2 >= pr_globaldefs) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to write to an invalid indexed global", PRVM_NAME); + goto cleanup; + } +#endif + pr_iglobals[OPB->_int ] = OPA->ivector[0]; + pr_iglobals[OPB->_int+1] = OPA->ivector[1]; + pr_iglobals[OPB->_int+2] = OPA->ivector[2]; + break; + + case OP_GADDRESS: + i = OPA->_int + (int) OPB->_float; +#if PRBOUNDSCHECK + if (i < 0 || i >= pr_globaldefs) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to address an out of bounds global", PRVM_NAME); + goto cleanup; + } +#endif + OPC->_int = pr_iglobals[i]; + break; + + case OP_GLOAD_I: + case OP_GLOAD_F: + case OP_GLOAD_FLD: + case OP_GLOAD_ENT: + case OP_GLOAD_S: + case OP_GLOAD_FNC: +#if PRBOUNDSCHECK + if (OPA->_int < 0 || OPA->_int >= pr_globaldefs) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to read an invalid indexed global", PRVM_NAME); + goto cleanup; + } +#endif + OPC->_int = pr_iglobals[OPA->_int]; + break; + + case OP_GLOAD_V: +#if PRBOUNDSCHECK + if (OPA->_int < 0 || OPA->_int + 2 >= pr_globaldefs) + { + PreError(); + PRVM_ERROR ("%s Progs attempted to read an invalid indexed global", PRVM_NAME); + goto cleanup; + } +#endif + OPC->ivector[0] = pr_iglobals[OPA->_int ]; + OPC->ivector[1] = pr_iglobals[OPA->_int+1]; + OPC->ivector[2] = pr_iglobals[OPA->_int+2]; + break; + + case OP_BOUNDCHECK: + if (OPA->_int < 0 || OPA->_int >= st->b) + { + PreError(); + PRVM_ERROR ("%s Progs boundcheck failed at line number %d, value is < 0 or >= %d", PRVM_NAME, st->b, st->c); + goto cleanup; + } + break; + +*/ + + default: + PreError(); + PRVM_ERROR ("Bad opcode %i in %s", st->op, PRVM_NAME); + goto cleanup; + } + } + +#undef PreError diff --git a/misc/source/darkplaces-src/prvm_offsets.h b/misc/source/darkplaces-src/prvm_offsets.h new file mode 100644 index 00000000..1bd6c5e1 --- /dev/null +++ b/misc/source/darkplaces-src/prvm_offsets.h @@ -0,0 +1,812 @@ +PRVM_DECLARE_clientfieldedict(aiment) +PRVM_DECLARE_clientfieldedict(chain) +PRVM_DECLARE_clientfieldedict(enemy) +PRVM_DECLARE_clientfieldedict(groundentity) +PRVM_DECLARE_clientfieldedict(owner) +PRVM_DECLARE_clientfieldedict(tag_entity) +PRVM_DECLARE_clientfieldfloat(alpha) +PRVM_DECLARE_clientfieldfloat(bouncefactor) +PRVM_DECLARE_clientfieldfloat(bouncestop) +PRVM_DECLARE_clientfieldfloat(colormap) +PRVM_DECLARE_clientfieldfloat(dphitcontentsmask) +PRVM_DECLARE_clientfieldfloat(drawmask) +PRVM_DECLARE_clientfieldfloat(effects) +PRVM_DECLARE_clientfieldfloat(entnum) +PRVM_DECLARE_clientfieldfloat(flags) +PRVM_DECLARE_clientfieldfloat(frame) +PRVM_DECLARE_clientfieldfloat(frame1time) +PRVM_DECLARE_clientfieldfloat(frame2) +PRVM_DECLARE_clientfieldfloat(frame2time) +PRVM_DECLARE_clientfieldfloat(frame3) +PRVM_DECLARE_clientfieldfloat(frame3time) +PRVM_DECLARE_clientfieldfloat(frame4) +PRVM_DECLARE_clientfieldfloat(frame4time) +PRVM_DECLARE_clientfieldfloat(gravity) +PRVM_DECLARE_clientfieldfloat(ideal_yaw) +PRVM_DECLARE_clientfieldfloat(idealpitch) +PRVM_DECLARE_clientfieldfloat(jointtype) +PRVM_DECLARE_clientfieldfloat(lerpfrac) +PRVM_DECLARE_clientfieldfloat(lerpfrac3) +PRVM_DECLARE_clientfieldfloat(lerpfrac4) +PRVM_DECLARE_clientfieldfloat(mass) +PRVM_DECLARE_clientfieldfloat(modelindex) +PRVM_DECLARE_clientfieldfloat(movetype) +PRVM_DECLARE_clientfieldfloat(nextthink) +PRVM_DECLARE_clientfieldfloat(pitch_speed) +PRVM_DECLARE_clientfieldfloat(renderflags) +PRVM_DECLARE_clientfieldfloat(scale) +PRVM_DECLARE_clientfieldfloat(shadertime) +PRVM_DECLARE_clientfieldfloat(skeletonindex) +PRVM_DECLARE_clientfieldfloat(skin) +PRVM_DECLARE_clientfieldfloat(solid) +PRVM_DECLARE_clientfieldfloat(tag_index) +PRVM_DECLARE_clientfieldfloat(userwavefunc_param0) +PRVM_DECLARE_clientfieldfloat(userwavefunc_param1) +PRVM_DECLARE_clientfieldfloat(userwavefunc_param2) +PRVM_DECLARE_clientfieldfloat(userwavefunc_param3) +PRVM_DECLARE_clientfieldfloat(yaw_speed) +PRVM_DECLARE_clientfieldfunction(blocked) +PRVM_DECLARE_clientfieldfunction(camera_transform) +PRVM_DECLARE_clientfieldfunction(predraw) +PRVM_DECLARE_clientfieldfunction(think) +PRVM_DECLARE_clientfieldfunction(touch) +PRVM_DECLARE_clientfieldfunction(use) +PRVM_DECLARE_clientfieldstring(classname) +PRVM_DECLARE_clientfieldstring(message) +PRVM_DECLARE_clientfieldstring(model) +PRVM_DECLARE_clientfieldstring(netname) +PRVM_DECLARE_clientfieldvector(absmax) +PRVM_DECLARE_clientfieldvector(absmin) +PRVM_DECLARE_clientfieldvector(angles) +PRVM_DECLARE_clientfieldvector(avelocity) +PRVM_DECLARE_clientfieldvector(colormod) +PRVM_DECLARE_clientfieldvector(glowmod) +PRVM_DECLARE_clientfieldvector(maxs) +PRVM_DECLARE_clientfieldvector(mins) +PRVM_DECLARE_clientfieldvector(movedir) +PRVM_DECLARE_clientfieldvector(oldorigin) +PRVM_DECLARE_clientfieldvector(origin) +PRVM_DECLARE_clientfieldvector(size) +PRVM_DECLARE_clientfieldvector(velocity) +PRVM_DECLARE_clientfunction(CSQC_ConsoleCommand) +PRVM_DECLARE_clientfunction(CSQC_Ent_Remove) +PRVM_DECLARE_clientfunction(CSQC_Ent_Spawn) +PRVM_DECLARE_clientfunction(CSQC_Ent_Update) +PRVM_DECLARE_clientfunction(CSQC_Event) +PRVM_DECLARE_clientfunction(CSQC_Event_Sound) +PRVM_DECLARE_clientfunction(CSQC_Init) +PRVM_DECLARE_clientfunction(CSQC_InputEvent) +PRVM_DECLARE_clientfunction(CSQC_Parse_CenterPrint) +PRVM_DECLARE_clientfunction(CSQC_Parse_Print) +PRVM_DECLARE_clientfunction(CSQC_Parse_StuffCmd) +PRVM_DECLARE_clientfunction(CSQC_Parse_TempEntity) +PRVM_DECLARE_clientfunction(CSQC_Shutdown) +PRVM_DECLARE_clientfunction(CSQC_UpdateView) +PRVM_DECLARE_clientfunction(GameCommand) +PRVM_DECLARE_clientfunction(Gecko_Query) +PRVM_DECLARE_clientfunction(URI_Get_Callback) +PRVM_DECLARE_clientglobaledict(other) +PRVM_DECLARE_clientglobaledict(self) +PRVM_DECLARE_clientglobaledict(trace_ent) +PRVM_DECLARE_clientglobaledict(world) +PRVM_DECLARE_clientglobalfloat(clientcommandframe) +PRVM_DECLARE_clientglobalfloat(coop) +PRVM_DECLARE_clientglobalfloat(deathmatch) +PRVM_DECLARE_clientglobalfloat(dmg_save) +PRVM_DECLARE_clientglobalfloat(dmg_take) +PRVM_DECLARE_clientglobalfloat(drawfont) +PRVM_DECLARE_clientglobalfloat(frametime) +PRVM_DECLARE_clientglobalfloat(gettaginfo_parent) +PRVM_DECLARE_clientglobalfloat(input_buttons) +PRVM_DECLARE_clientglobalfloat(input_timelength) +PRVM_DECLARE_clientglobalfloat(intermission) +PRVM_DECLARE_clientglobalfloat(maxclients) +PRVM_DECLARE_clientglobalfloat(movevar_accelerate) +PRVM_DECLARE_clientglobalfloat(movevar_airaccelerate) +PRVM_DECLARE_clientglobalfloat(movevar_entgravity) +PRVM_DECLARE_clientglobalfloat(movevar_friction) +PRVM_DECLARE_clientglobalfloat(movevar_gravity) +PRVM_DECLARE_clientglobalfloat(movevar_maxspeed) +PRVM_DECLARE_clientglobalfloat(movevar_spectatormaxspeed) +PRVM_DECLARE_clientglobalfloat(movevar_stopspeed) +PRVM_DECLARE_clientglobalfloat(movevar_wateraccelerate) +PRVM_DECLARE_clientglobalfloat(movevar_waterfriction) +PRVM_DECLARE_clientglobalfloat(particle_airfriction) +PRVM_DECLARE_clientglobalfloat(particle_alpha) +PRVM_DECLARE_clientglobalfloat(particle_alphafade) +PRVM_DECLARE_clientglobalfloat(particle_angle) +PRVM_DECLARE_clientglobalfloat(particle_blendmode) +PRVM_DECLARE_clientglobalfloat(particle_bounce) +PRVM_DECLARE_clientglobalfloat(particle_delaycollision) +PRVM_DECLARE_clientglobalfloat(particle_delayspawn) +PRVM_DECLARE_clientglobalfloat(particle_gravity) +PRVM_DECLARE_clientglobalfloat(particle_liquidfriction) +PRVM_DECLARE_clientglobalfloat(particle_orientation) +PRVM_DECLARE_clientglobalfloat(particle_originjitter) +PRVM_DECLARE_clientglobalfloat(particle_qualityreduction) +PRVM_DECLARE_clientglobalfloat(particle_size) +PRVM_DECLARE_clientglobalfloat(particle_sizeincrease) +PRVM_DECLARE_clientglobalfloat(particle_spin) +PRVM_DECLARE_clientglobalfloat(particle_stainalpha) +PRVM_DECLARE_clientglobalfloat(particle_stainsize) +PRVM_DECLARE_clientglobalfloat(particle_staintex) +PRVM_DECLARE_clientglobalfloat(particle_stretch) +PRVM_DECLARE_clientglobalfloat(particle_tex) +PRVM_DECLARE_clientglobalfloat(particle_time) +PRVM_DECLARE_clientglobalfloat(particle_type) +PRVM_DECLARE_clientglobalfloat(particle_velocityjitter) +PRVM_DECLARE_clientglobalfloat(particles_alphamax) +PRVM_DECLARE_clientglobalfloat(particles_alphamin) +PRVM_DECLARE_clientglobalfloat(player_localentnum) +PRVM_DECLARE_clientglobalfloat(player_localnum) +PRVM_DECLARE_clientglobalfloat(require_spawnfunc_prefix) +PRVM_DECLARE_clientglobalfloat(sb_showscores) +PRVM_DECLARE_clientglobalfloat(servercommandframe) +PRVM_DECLARE_clientglobalfloat(serverdeltatime) +PRVM_DECLARE_clientglobalfloat(serverprevtime) +PRVM_DECLARE_clientglobalfloat(servertime) +PRVM_DECLARE_clientglobalfloat(time) +PRVM_DECLARE_clientglobalfloat(trace_allsolid) +PRVM_DECLARE_clientglobalfloat(trace_dphitcontents) +PRVM_DECLARE_clientglobalfloat(trace_dphitq3surfaceflags) +PRVM_DECLARE_clientglobalfloat(trace_dpstartcontents) +PRVM_DECLARE_clientglobalfloat(trace_fraction) +PRVM_DECLARE_clientglobalfloat(trace_inopen) +PRVM_DECLARE_clientglobalfloat(trace_inwater) +PRVM_DECLARE_clientglobalfloat(trace_networkentity) +PRVM_DECLARE_clientglobalfloat(trace_plane_dist) +PRVM_DECLARE_clientglobalfloat(trace_startsolid) +PRVM_DECLARE_clientglobalfloat(transparent_offset) +PRVM_DECLARE_clientglobalstring(gettaginfo_name) +PRVM_DECLARE_clientglobalstring(mapname) +PRVM_DECLARE_clientglobalstring(trace_dphittexturename) +PRVM_DECLARE_clientglobalvector(dmg_origin) +PRVM_DECLARE_clientglobalvector(drawfontscale) +PRVM_DECLARE_clientglobalvector(gettaginfo_forward) +PRVM_DECLARE_clientglobalvector(gettaginfo_offset) +PRVM_DECLARE_clientglobalvector(gettaginfo_right) +PRVM_DECLARE_clientglobalvector(gettaginfo_up) +PRVM_DECLARE_clientglobalvector(input_angles) +PRVM_DECLARE_clientglobalvector(input_movevalues) +PRVM_DECLARE_clientglobalvector(particle_color1) +PRVM_DECLARE_clientglobalvector(particle_color2) +PRVM_DECLARE_clientglobalvector(particle_staincolor1) +PRVM_DECLARE_clientglobalvector(particle_staincolor2) +PRVM_DECLARE_clientglobalvector(particles_colormax) +PRVM_DECLARE_clientglobalvector(particles_colormin) +PRVM_DECLARE_clientglobalvector(pmove_inwater) +PRVM_DECLARE_clientglobalvector(pmove_maxs) +PRVM_DECLARE_clientglobalvector(pmove_mins) +PRVM_DECLARE_clientglobalvector(pmove_onground) +PRVM_DECLARE_clientglobalvector(pmove_org) +PRVM_DECLARE_clientglobalvector(pmove_vel) +PRVM_DECLARE_clientglobalvector(trace_endpos) +PRVM_DECLARE_clientglobalvector(trace_plane_normal) +PRVM_DECLARE_clientglobalvector(v_forward) +PRVM_DECLARE_clientglobalvector(v_right) +PRVM_DECLARE_clientglobalvector(v_up) +PRVM_DECLARE_clientglobalvector(view_angles) +PRVM_DECLARE_clientglobalvector(view_punchangle) +PRVM_DECLARE_clientglobalvector(view_punchvector) +PRVM_DECLARE_field(SendEntity) +PRVM_DECLARE_field(SendFlags) +PRVM_DECLARE_field(Version) +PRVM_DECLARE_field(absmax) +PRVM_DECLARE_field(absmin) +PRVM_DECLARE_field(aiment) +PRVM_DECLARE_field(alpha) +PRVM_DECLARE_field(ammo_cells) +PRVM_DECLARE_field(ammo_cells1) +PRVM_DECLARE_field(ammo_lava_nails) +PRVM_DECLARE_field(ammo_multi_rockets) +PRVM_DECLARE_field(ammo_nails) +PRVM_DECLARE_field(ammo_nails1) +PRVM_DECLARE_field(ammo_plasma) +PRVM_DECLARE_field(ammo_rockets) +PRVM_DECLARE_field(ammo_rockets1) +PRVM_DECLARE_field(ammo_shells) +PRVM_DECLARE_field(ammo_shells1) +PRVM_DECLARE_field(angles) +PRVM_DECLARE_field(armortype) +PRVM_DECLARE_field(armorvalue) +PRVM_DECLARE_field(avelocity) +PRVM_DECLARE_field(blocked) +PRVM_DECLARE_field(bouncefactor) +PRVM_DECLARE_field(bouncestop) +PRVM_DECLARE_field(button0) +PRVM_DECLARE_field(button1) +PRVM_DECLARE_field(button2) +PRVM_DECLARE_field(button3) +PRVM_DECLARE_field(button4) +PRVM_DECLARE_field(button5) +PRVM_DECLARE_field(button6) +PRVM_DECLARE_field(button7) +PRVM_DECLARE_field(button8) +PRVM_DECLARE_field(button9) +PRVM_DECLARE_field(button10) +PRVM_DECLARE_field(button11) +PRVM_DECLARE_field(button12) +PRVM_DECLARE_field(button13) +PRVM_DECLARE_field(button14) +PRVM_DECLARE_field(button15) +PRVM_DECLARE_field(button16) +PRVM_DECLARE_field(buttonchat) +PRVM_DECLARE_field(buttonuse) +PRVM_DECLARE_field(camera_transform) +PRVM_DECLARE_field(chain) +PRVM_DECLARE_field(classname) +PRVM_DECLARE_field(clientcamera) +PRVM_DECLARE_field(clientcolors) +PRVM_DECLARE_field(clientstatus) +PRVM_DECLARE_field(color) +PRVM_DECLARE_field(colormap) +PRVM_DECLARE_field(colormod) +PRVM_DECLARE_field(contentstransition) +PRVM_DECLARE_field(crypto_encryptmethod) +PRVM_DECLARE_field(crypto_idfp) +PRVM_DECLARE_field(crypto_keyfp) +PRVM_DECLARE_field(crypto_mykeyfp) +PRVM_DECLARE_field(crypto_signmethod) +PRVM_DECLARE_field(currentammo) +PRVM_DECLARE_field(cursor_active) +PRVM_DECLARE_field(cursor_screen) +PRVM_DECLARE_field(cursor_trace_endpos) +PRVM_DECLARE_field(cursor_trace_ent) +PRVM_DECLARE_field(cursor_trace_start) +PRVM_DECLARE_field(customizeentityforclient) +PRVM_DECLARE_field(deadflag) +PRVM_DECLARE_field(disableclientprediction) +PRVM_DECLARE_field(discardabledemo) +PRVM_DECLARE_field(dmg_inflictor) +PRVM_DECLARE_field(dmg_save) +PRVM_DECLARE_field(dmg_take) +PRVM_DECLARE_field(dphitcontentsmask) +PRVM_DECLARE_field(drawmask) +PRVM_DECLARE_field(drawonlytoclient) +PRVM_DECLARE_field(effects) +PRVM_DECLARE_field(enemy) +PRVM_DECLARE_field(entnum) +PRVM_DECLARE_field(exteriormodeltoclient) +PRVM_DECLARE_field(fixangle) +PRVM_DECLARE_field(flags) +PRVM_DECLARE_field(frags) +PRVM_DECLARE_field(frame) +PRVM_DECLARE_field(frame1time) +PRVM_DECLARE_field(frame2) +PRVM_DECLARE_field(frame2time) +PRVM_DECLARE_field(frame3) +PRVM_DECLARE_field(frame3time) +PRVM_DECLARE_field(frame4) +PRVM_DECLARE_field(frame4time) +PRVM_DECLARE_field(fullbright) +PRVM_DECLARE_field(glow_color) +PRVM_DECLARE_field(glow_size) +PRVM_DECLARE_field(glow_trail) +PRVM_DECLARE_field(glowmod) +PRVM_DECLARE_field(goalentity) +PRVM_DECLARE_field(gravity) +PRVM_DECLARE_field(groundentity) +PRVM_DECLARE_field(health) +PRVM_DECLARE_field(ideal_yaw) +PRVM_DECLARE_field(idealpitch) +PRVM_DECLARE_field(impulse) +PRVM_DECLARE_field(items) +PRVM_DECLARE_field(items2) +PRVM_DECLARE_field(jointtype) +PRVM_DECLARE_field(lerpfrac) +PRVM_DECLARE_field(lerpfrac3) +PRVM_DECLARE_field(lerpfrac4) +PRVM_DECLARE_field(light_lev) +PRVM_DECLARE_field(ltime) +PRVM_DECLARE_field(mass) +PRVM_DECLARE_field(max_health) +PRVM_DECLARE_field(maxs) +PRVM_DECLARE_field(message) +PRVM_DECLARE_field(mins) +PRVM_DECLARE_field(model) +PRVM_DECLARE_field(modelflags) +PRVM_DECLARE_field(modelindex) +PRVM_DECLARE_field(movedir) +PRVM_DECLARE_field(movement) +PRVM_DECLARE_field(movetype) +PRVM_DECLARE_field(movetypesteplandevent) +PRVM_DECLARE_field(netaddress) +PRVM_DECLARE_field(netname) +PRVM_DECLARE_field(nextthink) +PRVM_DECLARE_field(nodrawtoclient) +PRVM_DECLARE_field(noise) +PRVM_DECLARE_field(noise1) +PRVM_DECLARE_field(noise2) +PRVM_DECLARE_field(noise3) +PRVM_DECLARE_field(oldorigin) +PRVM_DECLARE_field(origin) +PRVM_DECLARE_field(owner) +PRVM_DECLARE_field(pflags) +PRVM_DECLARE_field(ping) +PRVM_DECLARE_field(ping_movementloss) +PRVM_DECLARE_field(ping_packetloss) +PRVM_DECLARE_field(pitch_speed) +PRVM_DECLARE_field(playermodel) +PRVM_DECLARE_field(playerskin) +PRVM_DECLARE_field(pmodel) +PRVM_DECLARE_field(predraw) +PRVM_DECLARE_field(punchangle) +PRVM_DECLARE_field(punchvector) +PRVM_DECLARE_field(renderamt) +PRVM_DECLARE_field(renderflags) +PRVM_DECLARE_field(scale) +PRVM_DECLARE_field(sendcomplexanimation) +PRVM_DECLARE_field(shadertime) +PRVM_DECLARE_field(size) +PRVM_DECLARE_field(skeletonindex) +PRVM_DECLARE_field(skin) +PRVM_DECLARE_field(solid) +PRVM_DECLARE_field(sounds) +PRVM_DECLARE_field(spawnflags) +PRVM_DECLARE_field(style) +PRVM_DECLARE_field(tag_entity) +PRVM_DECLARE_field(tag_index) +PRVM_DECLARE_field(takedamage) +PRVM_DECLARE_field(target) +PRVM_DECLARE_field(targetname) +PRVM_DECLARE_field(team) +PRVM_DECLARE_field(teleport_time) +PRVM_DECLARE_field(think) +PRVM_DECLARE_field(touch) +PRVM_DECLARE_field(traileffectnum) +PRVM_DECLARE_field(use) +PRVM_DECLARE_field(userwavefunc_param0) +PRVM_DECLARE_field(userwavefunc_param1) +PRVM_DECLARE_field(userwavefunc_param2) +PRVM_DECLARE_field(userwavefunc_param3) +PRVM_DECLARE_field(v_angle) +PRVM_DECLARE_field(velocity) +PRVM_DECLARE_field(view_ofs) +PRVM_DECLARE_field(viewmodelforclient) +PRVM_DECLARE_field(viewzoom) +PRVM_DECLARE_field(waterlevel) +PRVM_DECLARE_field(watertype) +PRVM_DECLARE_field(weapon) +PRVM_DECLARE_field(weaponframe) +PRVM_DECLARE_field(weaponmodel) +PRVM_DECLARE_field(yaw_speed) +PRVM_DECLARE_function(CSQC_ConsoleCommand) +PRVM_DECLARE_function(CSQC_Ent_Remove) +PRVM_DECLARE_function(CSQC_Ent_Spawn) +PRVM_DECLARE_function(CSQC_Ent_Update) +PRVM_DECLARE_function(CSQC_Event) +PRVM_DECLARE_function(CSQC_Event_Sound) +PRVM_DECLARE_function(CSQC_Init) +PRVM_DECLARE_function(CSQC_InputEvent) +PRVM_DECLARE_function(CSQC_Parse_CenterPrint) +PRVM_DECLARE_function(CSQC_Parse_Print) +PRVM_DECLARE_function(CSQC_Parse_StuffCmd) +PRVM_DECLARE_function(CSQC_Parse_TempEntity) +PRVM_DECLARE_function(CSQC_Shutdown) +PRVM_DECLARE_function(CSQC_UpdateView) +PRVM_DECLARE_function(ClientConnect) +PRVM_DECLARE_function(ClientDisconnect) +PRVM_DECLARE_function(ClientKill) +PRVM_DECLARE_function(EndFrame) +PRVM_DECLARE_function(GameCommand) +PRVM_DECLARE_function(Gecko_Query) +PRVM_DECLARE_function(PlayerPostThink) +PRVM_DECLARE_function(PlayerPreThink) +PRVM_DECLARE_function(PutClientInServer) +PRVM_DECLARE_function(RestoreGame) +PRVM_DECLARE_function(SV_ChangeTeam) +PRVM_DECLARE_function(SV_OnEntityNoSpawnFunction) +PRVM_DECLARE_function(SV_OnEntityPostSpawnFunction) +PRVM_DECLARE_function(SV_OnEntityPreSpawnFunction) +PRVM_DECLARE_function(SV_ParseClientCommand) +PRVM_DECLARE_function(SV_PausedTic) +PRVM_DECLARE_function(SV_PlayerPhysics) +PRVM_DECLARE_function(SV_Shutdown) +PRVM_DECLARE_function(SetChangeParms) +PRVM_DECLARE_function(SetNewParms) +PRVM_DECLARE_function(StartFrame) +PRVM_DECLARE_function(URI_Get_Callback) +PRVM_DECLARE_function(m_draw) +PRVM_DECLARE_function(m_init) +PRVM_DECLARE_function(m_keydown) +PRVM_DECLARE_function(m_keyup) +PRVM_DECLARE_function(m_newmap) +PRVM_DECLARE_function(m_shutdown) +PRVM_DECLARE_function(m_toggle) +PRVM_DECLARE_function(main) +PRVM_DECLARE_global(SV_InitCmd) +PRVM_DECLARE_global(clientcommandframe) +PRVM_DECLARE_global(coop) +PRVM_DECLARE_global(deathmatch) +PRVM_DECLARE_global(dmg_origin) +PRVM_DECLARE_global(dmg_save) +PRVM_DECLARE_global(dmg_take) +PRVM_DECLARE_global(drawfont) +PRVM_DECLARE_global(drawfontscale) +PRVM_DECLARE_global(force_retouch) +PRVM_DECLARE_global(found_secrets) +PRVM_DECLARE_global(frametime) +PRVM_DECLARE_global(gettaginfo_forward) +PRVM_DECLARE_global(gettaginfo_name) +PRVM_DECLARE_global(gettaginfo_offset) +PRVM_DECLARE_global(gettaginfo_parent) +PRVM_DECLARE_global(gettaginfo_right) +PRVM_DECLARE_global(gettaginfo_up) +PRVM_DECLARE_global(input_angles) +PRVM_DECLARE_global(input_buttons) +PRVM_DECLARE_global(input_movevalues) +PRVM_DECLARE_global(input_timelength) +PRVM_DECLARE_global(intermission) +PRVM_DECLARE_global(killed_monsters) +PRVM_DECLARE_global(mapname) +PRVM_DECLARE_global(maxclients) +PRVM_DECLARE_global(movevar_accelerate) +PRVM_DECLARE_global(movevar_airaccelerate) +PRVM_DECLARE_global(movevar_entgravity) +PRVM_DECLARE_global(movevar_friction) +PRVM_DECLARE_global(movevar_gravity) +PRVM_DECLARE_global(movevar_maxspeed) +PRVM_DECLARE_global(movevar_spectatormaxspeed) +PRVM_DECLARE_global(movevar_stopspeed) +PRVM_DECLARE_global(movevar_wateraccelerate) +PRVM_DECLARE_global(movevar_waterfriction) +PRVM_DECLARE_global(msg_entity) +PRVM_DECLARE_global(other) +PRVM_DECLARE_global(parm1) +PRVM_DECLARE_global(parm2) +PRVM_DECLARE_global(parm3) +PRVM_DECLARE_global(parm4) +PRVM_DECLARE_global(parm5) +PRVM_DECLARE_global(parm6) +PRVM_DECLARE_global(parm7) +PRVM_DECLARE_global(parm8) +PRVM_DECLARE_global(parm9) +PRVM_DECLARE_global(parm10) +PRVM_DECLARE_global(parm11) +PRVM_DECLARE_global(parm12) +PRVM_DECLARE_global(parm13) +PRVM_DECLARE_global(parm14) +PRVM_DECLARE_global(parm15) +PRVM_DECLARE_global(parm16) +PRVM_DECLARE_global(particle_airfriction) +PRVM_DECLARE_global(particle_alpha) +PRVM_DECLARE_global(particle_alphafade) +PRVM_DECLARE_global(particle_angle) +PRVM_DECLARE_global(particle_blendmode) +PRVM_DECLARE_global(particle_bounce) +PRVM_DECLARE_global(particle_color1) +PRVM_DECLARE_global(particle_color2) +PRVM_DECLARE_global(particle_delaycollision) +PRVM_DECLARE_global(particle_delayspawn) +PRVM_DECLARE_global(particle_gravity) +PRVM_DECLARE_global(particle_liquidfriction) +PRVM_DECLARE_global(particle_orientation) +PRVM_DECLARE_global(particle_originjitter) +PRVM_DECLARE_global(particle_qualityreduction) +PRVM_DECLARE_global(particle_size) +PRVM_DECLARE_global(particle_sizeincrease) +PRVM_DECLARE_global(particle_spin) +PRVM_DECLARE_global(particle_stainalpha) +PRVM_DECLARE_global(particle_staincolor1) +PRVM_DECLARE_global(particle_staincolor2) +PRVM_DECLARE_global(particle_stainsize) +PRVM_DECLARE_global(particle_staintex) +PRVM_DECLARE_global(particle_stretch) +PRVM_DECLARE_global(particle_tex) +PRVM_DECLARE_global(particle_time) +PRVM_DECLARE_global(particle_type) +PRVM_DECLARE_global(particle_velocityjitter) +PRVM_DECLARE_global(particles_alphamax) +PRVM_DECLARE_global(particles_alphamin) +PRVM_DECLARE_global(particles_colormax) +PRVM_DECLARE_global(particles_colormin) +PRVM_DECLARE_global(player_localentnum) +PRVM_DECLARE_global(player_localnum) +PRVM_DECLARE_global(pmove_inwater) +PRVM_DECLARE_global(pmove_maxs) +PRVM_DECLARE_global(pmove_mins) +PRVM_DECLARE_global(pmove_onground) +PRVM_DECLARE_global(pmove_org) +PRVM_DECLARE_global(pmove_vel) +PRVM_DECLARE_global(require_spawnfunc_prefix) +PRVM_DECLARE_global(sb_showscores) +PRVM_DECLARE_global(self) +PRVM_DECLARE_global(servercommandframe) +PRVM_DECLARE_global(serverdeltatime) +PRVM_DECLARE_global(serverflags) +PRVM_DECLARE_global(serverprevtime) +PRVM_DECLARE_global(servertime) +PRVM_DECLARE_global(teamplay) +PRVM_DECLARE_global(time) +PRVM_DECLARE_global(total_monsters) +PRVM_DECLARE_global(total_secrets) +PRVM_DECLARE_global(trace_allsolid) +PRVM_DECLARE_global(trace_dphitcontents) +PRVM_DECLARE_global(trace_dphitq3surfaceflags) +PRVM_DECLARE_global(trace_dphittexturename) +PRVM_DECLARE_global(trace_dpstartcontents) +PRVM_DECLARE_global(trace_endpos) +PRVM_DECLARE_global(trace_ent) +PRVM_DECLARE_global(trace_fraction) +PRVM_DECLARE_global(trace_inopen) +PRVM_DECLARE_global(trace_inwater) +PRVM_DECLARE_global(trace_networkentity) +PRVM_DECLARE_global(trace_plane_dist) +PRVM_DECLARE_global(trace_plane_normal) +PRVM_DECLARE_global(trace_startsolid) +PRVM_DECLARE_global(transparent_offset) +PRVM_DECLARE_global(v_forward) +PRVM_DECLARE_global(v_right) +PRVM_DECLARE_global(v_up) +PRVM_DECLARE_global(view_angles) +PRVM_DECLARE_global(view_punchangle) +PRVM_DECLARE_global(view_punchvector) +PRVM_DECLARE_global(world) +PRVM_DECLARE_global(worldstatus) +PRVM_DECLARE_menufieldstring(classname) +PRVM_DECLARE_menufunction(GameCommand) +PRVM_DECLARE_menufunction(Gecko_Query) +PRVM_DECLARE_menufunction(URI_Get_Callback) +PRVM_DECLARE_menufunction(m_draw) +PRVM_DECLARE_menufunction(m_init) +PRVM_DECLARE_menufunction(m_keydown) +PRVM_DECLARE_menufunction(m_keyup) +PRVM_DECLARE_menufunction(m_newmap) +PRVM_DECLARE_menufunction(m_shutdown) +PRVM_DECLARE_menufunction(m_toggle) +PRVM_DECLARE_menuglobaledict(self) +PRVM_DECLARE_menuglobalfloat(drawfont) +PRVM_DECLARE_menuglobalfloat(require_spawnfunc_prefix) +PRVM_DECLARE_menuglobalvector(drawfontscale) +PRVM_DECLARE_serverfieldedict(aiment) +PRVM_DECLARE_serverfieldedict(chain) +PRVM_DECLARE_serverfieldedict(clientcamera) +PRVM_DECLARE_serverfieldedict(cursor_trace_ent) +PRVM_DECLARE_serverfieldedict(dmg_inflictor) +PRVM_DECLARE_serverfieldedict(drawonlytoclient) +PRVM_DECLARE_serverfieldedict(enemy) +PRVM_DECLARE_serverfieldedict(exteriormodeltoclient) +PRVM_DECLARE_serverfieldedict(goalentity) +PRVM_DECLARE_serverfieldedict(groundentity) +PRVM_DECLARE_serverfieldedict(nodrawtoclient) +PRVM_DECLARE_serverfieldedict(owner) +PRVM_DECLARE_serverfieldedict(tag_entity) +PRVM_DECLARE_serverfieldedict(viewmodelforclient) +PRVM_DECLARE_serverfieldfloat(SendFlags) +PRVM_DECLARE_serverfieldfloat(Version) +PRVM_DECLARE_serverfieldfloat(alpha) +PRVM_DECLARE_serverfieldfloat(ammo_cells) +PRVM_DECLARE_serverfieldfloat(ammo_cells1) +PRVM_DECLARE_serverfieldfloat(ammo_lava_nails) +PRVM_DECLARE_serverfieldfloat(ammo_multi_rockets) +PRVM_DECLARE_serverfieldfloat(ammo_nails) +PRVM_DECLARE_serverfieldfloat(ammo_nails1) +PRVM_DECLARE_serverfieldfloat(ammo_plasma) +PRVM_DECLARE_serverfieldfloat(ammo_rockets) +PRVM_DECLARE_serverfieldfloat(ammo_rockets1) +PRVM_DECLARE_serverfieldfloat(ammo_shells) +PRVM_DECLARE_serverfieldfloat(ammo_shells1) +PRVM_DECLARE_serverfieldfloat(armortype) +PRVM_DECLARE_serverfieldfloat(armorvalue) +PRVM_DECLARE_serverfieldfloat(bouncefactor) +PRVM_DECLARE_serverfieldfloat(bouncestop) +PRVM_DECLARE_serverfieldfloat(button0) +PRVM_DECLARE_serverfieldfloat(button1) +PRVM_DECLARE_serverfieldfloat(button2) +PRVM_DECLARE_serverfieldfloat(button3) +PRVM_DECLARE_serverfieldfloat(button4) +PRVM_DECLARE_serverfieldfloat(button5) +PRVM_DECLARE_serverfieldfloat(button6) +PRVM_DECLARE_serverfieldfloat(button7) +PRVM_DECLARE_serverfieldfloat(button8) +PRVM_DECLARE_serverfieldfloat(button9) +PRVM_DECLARE_serverfieldfloat(button10) +PRVM_DECLARE_serverfieldfloat(button11) +PRVM_DECLARE_serverfieldfloat(button12) +PRVM_DECLARE_serverfieldfloat(button13) +PRVM_DECLARE_serverfieldfloat(button14) +PRVM_DECLARE_serverfieldfloat(button15) +PRVM_DECLARE_serverfieldfloat(button16) +PRVM_DECLARE_serverfieldfloat(buttonchat) +PRVM_DECLARE_serverfieldfloat(buttonuse) +PRVM_DECLARE_serverfieldfloat(clientcolors) +PRVM_DECLARE_serverfieldfloat(colormap) +PRVM_DECLARE_serverfieldfloat(currentammo) +PRVM_DECLARE_serverfieldfloat(cursor_active) +PRVM_DECLARE_serverfieldfloat(deadflag) +PRVM_DECLARE_serverfieldfloat(disableclientprediction) +PRVM_DECLARE_serverfieldfloat(discardabledemo) +PRVM_DECLARE_serverfieldfloat(dmg_save) +PRVM_DECLARE_serverfieldfloat(dmg_take) +PRVM_DECLARE_serverfieldfloat(dphitcontentsmask) +PRVM_DECLARE_serverfieldfloat(effects) +PRVM_DECLARE_serverfieldfloat(fixangle) +PRVM_DECLARE_serverfieldfloat(flags) +PRVM_DECLARE_serverfieldfloat(frags) +PRVM_DECLARE_serverfieldfloat(frame) +PRVM_DECLARE_serverfieldfloat(frame1time) +PRVM_DECLARE_serverfieldfloat(frame2) +PRVM_DECLARE_serverfieldfloat(frame2time) +PRVM_DECLARE_serverfieldfloat(frame3) +PRVM_DECLARE_serverfieldfloat(frame3time) +PRVM_DECLARE_serverfieldfloat(frame4) +PRVM_DECLARE_serverfieldfloat(frame4time) +PRVM_DECLARE_serverfieldfloat(fullbright) +PRVM_DECLARE_serverfieldfloat(glow_color) +PRVM_DECLARE_serverfieldfloat(glow_size) +PRVM_DECLARE_serverfieldfloat(glow_trail) +PRVM_DECLARE_serverfieldfloat(gravity) +PRVM_DECLARE_serverfieldfloat(health) +PRVM_DECLARE_serverfieldfloat(ideal_yaw) +PRVM_DECLARE_serverfieldfloat(idealpitch) +PRVM_DECLARE_serverfieldfloat(impulse) +PRVM_DECLARE_serverfieldfloat(items) +PRVM_DECLARE_serverfieldfloat(items2) +PRVM_DECLARE_serverfieldfloat(jointtype) +PRVM_DECLARE_serverfieldfloat(lerpfrac) +PRVM_DECLARE_serverfieldfloat(lerpfrac3) +PRVM_DECLARE_serverfieldfloat(lerpfrac4) +PRVM_DECLARE_serverfieldfloat(light_lev) +PRVM_DECLARE_serverfieldfloat(ltime) +PRVM_DECLARE_serverfieldfloat(mass) +PRVM_DECLARE_serverfieldfloat(max_health) +PRVM_DECLARE_serverfieldfloat(modelflags) +PRVM_DECLARE_serverfieldfloat(modelindex) +PRVM_DECLARE_serverfieldfloat(movetype) +PRVM_DECLARE_serverfieldfloat(nextthink) +PRVM_DECLARE_serverfieldfloat(pflags) +PRVM_DECLARE_serverfieldfloat(ping) +PRVM_DECLARE_serverfieldfloat(ping_movementloss) +PRVM_DECLARE_serverfieldfloat(ping_packetloss) +PRVM_DECLARE_serverfieldfloat(pitch_speed) +PRVM_DECLARE_serverfieldfloat(pmodel) +PRVM_DECLARE_serverfieldfloat(renderamt) +PRVM_DECLARE_serverfieldfloat(scale) +PRVM_DECLARE_serverfieldfloat(sendcomplexanimation) +PRVM_DECLARE_serverfieldfloat(skeletonindex) +PRVM_DECLARE_serverfieldfloat(skin) +PRVM_DECLARE_serverfieldfloat(solid) +PRVM_DECLARE_serverfieldfloat(sounds) +PRVM_DECLARE_serverfieldfloat(spawnflags) +PRVM_DECLARE_serverfieldfloat(style) +PRVM_DECLARE_serverfieldfloat(tag_index) +PRVM_DECLARE_serverfieldfloat(takedamage) +PRVM_DECLARE_serverfieldfloat(team) +PRVM_DECLARE_serverfieldfloat(teleport_time) +PRVM_DECLARE_serverfieldfloat(traileffectnum) +PRVM_DECLARE_serverfieldfloat(viewzoom) +PRVM_DECLARE_serverfieldfloat(waterlevel) +PRVM_DECLARE_serverfieldfloat(watertype) +PRVM_DECLARE_serverfieldfloat(weapon) +PRVM_DECLARE_serverfieldfloat(weaponframe) +PRVM_DECLARE_serverfieldfloat(yaw_speed) +PRVM_DECLARE_serverfieldfunction(SendEntity) +PRVM_DECLARE_serverfieldfunction(blocked) +PRVM_DECLARE_serverfieldfunction(camera_transform) +PRVM_DECLARE_serverfieldfunction(contentstransition) +PRVM_DECLARE_serverfieldfunction(customizeentityforclient) +PRVM_DECLARE_serverfieldfunction(movetypesteplandevent) +PRVM_DECLARE_serverfieldfunction(think) +PRVM_DECLARE_serverfieldfunction(touch) +PRVM_DECLARE_serverfieldfunction(use) +PRVM_DECLARE_serverfieldstring(classname) +PRVM_DECLARE_serverfieldstring(clientstatus) +PRVM_DECLARE_serverfieldstring(crypto_encryptmethod) +PRVM_DECLARE_serverfieldstring(crypto_idfp) +PRVM_DECLARE_serverfieldstring(crypto_keyfp) +PRVM_DECLARE_serverfieldstring(crypto_mykeyfp) +PRVM_DECLARE_serverfieldstring(crypto_signmethod) +PRVM_DECLARE_serverfieldstring(message) +PRVM_DECLARE_serverfieldstring(model) +PRVM_DECLARE_serverfieldstring(netaddress) +PRVM_DECLARE_serverfieldstring(netname) +PRVM_DECLARE_serverfieldstring(noise) +PRVM_DECLARE_serverfieldstring(noise1) +PRVM_DECLARE_serverfieldstring(noise2) +PRVM_DECLARE_serverfieldstring(noise3) +PRVM_DECLARE_serverfieldstring(playermodel) +PRVM_DECLARE_serverfieldstring(playerskin) +PRVM_DECLARE_serverfieldstring(target) +PRVM_DECLARE_serverfieldstring(targetname) +PRVM_DECLARE_serverfieldstring(weaponmodel) +PRVM_DECLARE_serverfieldvector(absmax) +PRVM_DECLARE_serverfieldvector(absmin) +PRVM_DECLARE_serverfieldvector(angles) +PRVM_DECLARE_serverfieldvector(avelocity) +PRVM_DECLARE_serverfieldvector(color) +PRVM_DECLARE_serverfieldvector(colormod) +PRVM_DECLARE_serverfieldvector(cursor_screen) +PRVM_DECLARE_serverfieldvector(cursor_trace_endpos) +PRVM_DECLARE_serverfieldvector(cursor_trace_start) +PRVM_DECLARE_serverfieldvector(glowmod) +PRVM_DECLARE_serverfieldvector(maxs) +PRVM_DECLARE_serverfieldvector(mins) +PRVM_DECLARE_serverfieldvector(movedir) +PRVM_DECLARE_serverfieldvector(movement) +PRVM_DECLARE_serverfieldvector(oldorigin) +PRVM_DECLARE_serverfieldvector(origin) +PRVM_DECLARE_serverfieldvector(punchangle) +PRVM_DECLARE_serverfieldvector(punchvector) +PRVM_DECLARE_serverfieldvector(size) +PRVM_DECLARE_serverfieldvector(v_angle) +PRVM_DECLARE_serverfieldvector(velocity) +PRVM_DECLARE_serverfieldvector(view_ofs) +PRVM_DECLARE_serverfunction(ClientConnect) +PRVM_DECLARE_serverfunction(ClientDisconnect) +PRVM_DECLARE_serverfunction(ClientKill) +PRVM_DECLARE_serverfunction(EndFrame) +PRVM_DECLARE_serverfunction(GameCommand) +PRVM_DECLARE_serverfunction(PlayerPostThink) +PRVM_DECLARE_serverfunction(PlayerPreThink) +PRVM_DECLARE_serverfunction(PutClientInServer) +PRVM_DECLARE_serverfunction(RestoreGame) +PRVM_DECLARE_serverfunction(SV_ChangeTeam) +PRVM_DECLARE_serverfunction(SV_OnEntityNoSpawnFunction) +PRVM_DECLARE_serverfunction(SV_OnEntityPostSpawnFunction) +PRVM_DECLARE_serverfunction(SV_OnEntityPreSpawnFunction) +PRVM_DECLARE_serverfunction(SV_ParseClientCommand) +PRVM_DECLARE_serverfunction(SV_PausedTic) +PRVM_DECLARE_serverfunction(SV_PlayerPhysics) +PRVM_DECLARE_serverfunction(SV_Shutdown) +PRVM_DECLARE_serverfunction(SetChangeParms) +PRVM_DECLARE_serverfunction(SetNewParms) +PRVM_DECLARE_serverfunction(StartFrame) +PRVM_DECLARE_serverfunction(URI_Get_Callback) +PRVM_DECLARE_serverfunction(main) +PRVM_DECLARE_serverglobaledict(msg_entity) +PRVM_DECLARE_serverglobaledict(other) +PRVM_DECLARE_serverglobaledict(self) +PRVM_DECLARE_serverglobaledict(trace_ent) +PRVM_DECLARE_serverglobaledict(world) +PRVM_DECLARE_serverglobalfloat(coop) +PRVM_DECLARE_serverglobalfloat(deathmatch) +PRVM_DECLARE_serverglobalfloat(force_retouch) +PRVM_DECLARE_serverglobalfloat(found_secrets) +PRVM_DECLARE_serverglobalfloat(frametime) +PRVM_DECLARE_serverglobalfloat(gettaginfo_parent) +PRVM_DECLARE_serverglobalfloat(killed_monsters) +PRVM_DECLARE_serverglobalfloat(parm1) +PRVM_DECLARE_serverglobalfloat(parm2) +PRVM_DECLARE_serverglobalfloat(parm3) +PRVM_DECLARE_serverglobalfloat(parm4) +PRVM_DECLARE_serverglobalfloat(parm5) +PRVM_DECLARE_serverglobalfloat(parm6) +PRVM_DECLARE_serverglobalfloat(parm7) +PRVM_DECLARE_serverglobalfloat(parm8) +PRVM_DECLARE_serverglobalfloat(parm9) +PRVM_DECLARE_serverglobalfloat(parm10) +PRVM_DECLARE_serverglobalfloat(parm11) +PRVM_DECLARE_serverglobalfloat(parm12) +PRVM_DECLARE_serverglobalfloat(parm13) +PRVM_DECLARE_serverglobalfloat(parm14) +PRVM_DECLARE_serverglobalfloat(parm15) +PRVM_DECLARE_serverglobalfloat(parm16) +PRVM_DECLARE_serverglobalfloat(require_spawnfunc_prefix) +PRVM_DECLARE_serverglobalfloat(serverflags) +PRVM_DECLARE_serverglobalfloat(teamplay) +PRVM_DECLARE_serverglobalfloat(time) +PRVM_DECLARE_serverglobalfloat(total_monsters) +PRVM_DECLARE_serverglobalfloat(total_secrets) +PRVM_DECLARE_serverglobalfloat(trace_allsolid) +PRVM_DECLARE_serverglobalfloat(trace_dphitcontents) +PRVM_DECLARE_serverglobalfloat(trace_dphitq3surfaceflags) +PRVM_DECLARE_serverglobalfloat(trace_dpstartcontents) +PRVM_DECLARE_serverglobalfloat(trace_fraction) +PRVM_DECLARE_serverglobalfloat(trace_inopen) +PRVM_DECLARE_serverglobalfloat(trace_inwater) +PRVM_DECLARE_serverglobalfloat(trace_plane_dist) +PRVM_DECLARE_serverglobalfloat(trace_startsolid) +PRVM_DECLARE_serverglobalstring(SV_InitCmd) +PRVM_DECLARE_serverglobalstring(gettaginfo_name) +PRVM_DECLARE_serverglobalstring(mapname) +PRVM_DECLARE_serverglobalstring(trace_dphittexturename) +PRVM_DECLARE_serverglobalstring(worldstatus) +PRVM_DECLARE_serverglobalvector(gettaginfo_forward) +PRVM_DECLARE_serverglobalvector(gettaginfo_offset) +PRVM_DECLARE_serverglobalvector(gettaginfo_right) +PRVM_DECLARE_serverglobalvector(gettaginfo_up) +PRVM_DECLARE_serverglobalvector(trace_endpos) +PRVM_DECLARE_serverglobalvector(trace_plane_normal) +PRVM_DECLARE_serverglobalvector(v_forward) +PRVM_DECLARE_serverglobalvector(v_right) +PRVM_DECLARE_serverglobalvector(v_up) \ No newline at end of file diff --git a/misc/source/darkplaces-src/qtypes.h b/misc/source/darkplaces-src/qtypes.h new file mode 100644 index 00000000..250beaee --- /dev/null +++ b/misc/source/darkplaces-src/qtypes.h @@ -0,0 +1,38 @@ + +#ifndef QTYPES_H +#define QTYPES_H + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum qboolean_e {false, true} qboolean; +#else +typedef bool qboolean; +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef FALSE +#define FALSE false +#define TRUE true +#endif + +// up / down +#define PITCH 0 + +// left / right +#define YAW 1 + +// fall over +#define ROLL 2 + +#if defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1400) +#define RESTRICT __restrict +#else +#define RESTRICT +#endif + +#endif diff --git a/misc/source/darkplaces-src/quakedef.h b/misc/source/darkplaces-src/quakedef.h new file mode 100644 index 00000000..2310bc20 --- /dev/null +++ b/misc/source/darkplaces-src/quakedef.h @@ -0,0 +1,549 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// quakedef.h -- primary header for client + +#ifndef QUAKEDEF_H +#define QUAKEDEF_H + +#if defined(__GNUC__) && (__GNUC__ > 2) +#define DP_FUNC_PRINTF(n) __attribute__ ((format (printf, n, n+1))) +#define DP_FUNC_PURE __attribute__ ((pure)) +#else +#define DP_FUNC_PRINTF(n) +#define DP_FUNC_PURE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qtypes.h" + +extern const char *buildstring; +extern char engineversion[128]; + +#define GAMENAME "id1" + +#define MAX_NUM_ARGVS 50 + +#ifdef DP_SMALLMEMORY +#define MAX_INPUTLINE 1024 +#define CON_TEXTSIZE 16384 +#define CON_MAXLINES 256 +#define HIST_TEXTSIZE 2048 +#define HIST_MAXLINES 16 +#define MAX_ALIAS_NAME 32 +#define CMDBUFSIZE 131072 +#define MAX_ARGS 80 + +#define NET_MAXMESSAGE 65536 +#define MAX_PACKETFRAGMENT 1024 +#define MAX_EDICTS 4096 +#define MAX_MODELS 1024 +#define MAX_SOUNDS 1024 +#define MAX_LIGHTSTYLES 64 +#define MAX_STYLESTRING 16 +#define MAX_SCOREBOARD 32 +#define MAX_SCOREBOARDNAME 128 +#define MAX_USERINFO_STRING 196 +#define MAX_SERVERINFO_STRING 512 +#define MAX_LOCALINFO_STRING 1 // not actually used by DP servers +#define CL_MAX_USERCMDS 32 +#define CVAR_HASHSIZE 1024 +#define M_MAX_EDICTS 4096 +#define MAX_DEMOS 8 +#define MAX_DEMONAME 16 +#define MAX_SAVEGAMES 12 +#define SAVEGAME_COMMENT_LENGTH 39 +#define MAX_CLIENTNETWORKEYES 2 +#define MAX_LEVELNETWORKEYES 0 // no portal support +#define MAX_OCCLUSION_QUERIES 256 + +#define CRYPTO_HOSTKEY_HASHSIZE 256 +#define MAX_NETWM_ICON 1026 // one 32x32 + +#define MAX_WATERPLANES 2 +#define MAX_CUBEMAPS 1024 +#define MAX_EXPLOSIONS 8 +#define MAX_DLIGHTS 16 +#define MAX_CACHED_PICS 1024 // this is 144 bytes each (or 152 on 64bit) +#define CACHEPICHASHSIZE 256 +#define MAX_PARTICLEEFFECTNAME 256 +#define MAX_PARTICLEEFFECTINFO 1024 +#define MAX_PARTICLETEXTURES 256 +#define MAXCLVIDEOS 1 +#define MAX_GECKO_INSTANCES 1 +#define MAX_DYNAMIC_TEXTURE_COUNT 2 +#define MAX_MAP_LEAFS 8192 + +#define MAXTRACKS 256 +#define MAX_DYNAMIC_CHANNELS 64 +#define MAX_CHANNELS 260 +#define MODLIST_TOTALSIZE 32 +#define MAX_FAVORITESERVERS 32 +#define MAX_DECALSYSTEM_QUEUE 64 +#define PAINTBUFFER_SIZE 512 +#define MAX_BINDMAPS 8 +#define MAX_PARTICLES_INITIAL 8192 +#define MAX_PARTICLES 8192 +#define MAX_DECALS_INITIAL 1024 +#define MAX_DECALS 1024 +#define MAX_ENITIES_INITIAL 256 +#define MAX_STATICENTITIES 256 +#define MAX_EFFECTS 16 +#define MAX_BEAMS 16 +#define MAX_TEMPENTITIES 256 +#else +#define MAX_INPUTLINE 16384 ///< maximum length of console commandline, QuakeC strings, and many other text processing buffers +#define CON_TEXTSIZE 1048576 ///< max scrollback buffer characters in console +#define CON_MAXLINES 16384 ///< max scrollback buffer lines in console +#define HIST_TEXTSIZE 262144 ///< max command history buffer characters in console +#define HIST_MAXLINES 4096 ///< max command history buffer lines in console +#define MAX_ALIAS_NAME 32 +#define CMDBUFSIZE 655360 ///< maximum script size that can be loaded by the exec command (8192 in Quake) +#define MAX_ARGS 80 ///< maximum number of parameters to a console command or alias + +#define NET_MAXMESSAGE 65536 ///< max reliable packet size (sent as multiple fragments of MAX_PACKETFRAGMENT) +#define MAX_PACKETFRAGMENT 1024 ///< max length of packet fragment +#define MAX_EDICTS 32768 ///< max number of objects in game world at once (32768 protocol limit) +#define MAX_MODELS 8192 ///< max number of models loaded at once (including during level transitions) +#define MAX_SOUNDS 4096 ///< max number of sounds loaded at once +#define MAX_LIGHTSTYLES 256 ///< max flickering light styles in level (note: affects savegame format) +#define MAX_STYLESTRING 64 ///< max length of flicker pattern for light style +#define MAX_SCOREBOARD 255 ///< max number of players in game at once (255 protocol limit) +#define MAX_SCOREBOARDNAME 128 ///< max length of player name in game +#define MAX_USERINFO_STRING 1280 ///< max length of infostring for PROTOCOL_QUAKEWORLD (196 in QuakeWorld) +#define MAX_SERVERINFO_STRING 1280 ///< max length of server infostring for PROTOCOL_QUAKEWORLD (512 in QuakeWorld) +#define MAX_LOCALINFO_STRING 32768 ///< max length of server-local infostring for PROTOCOL_QUAKEWORLD (32768 in QuakeWorld) +#define CL_MAX_USERCMDS 128 ///< max number of predicted input packets in queue +#define CVAR_HASHSIZE 65536 ///< number of hash buckets for accelerating cvar name lookups +#define M_MAX_EDICTS 32768 ///< max objects in menu vm +#define MAX_DEMOS 8 ///< max demos provided to demos command +#define MAX_DEMONAME 16 ///< max demo name length for demos command +#define MAX_SAVEGAMES 12 ///< max savegames listed in savegame menu +#define SAVEGAME_COMMENT_LENGTH 39 ///< max comment length of savegame in menu +#define MAX_CLIENTNETWORKEYES 16 ///< max number of locations that can be added to pvs when culling network entities (must be at least 2 for prediction) +#define MAX_LEVELNETWORKEYES 512 ///< max number of locations that can be added to pvs when culling network entities (must be at least 2 for prediction) +#define MAX_OCCLUSION_QUERIES 4096 ///< max number of GL_ARB_occlusion_query objects that can be used in one frame + +#define CRYPTO_HOSTKEY_HASHSIZE 8192 ///< number of hash buckets for accelerating host key lookups +#define MAX_NETWM_ICON 352822 // 16x16, 22x22, 24x24, 32x32, 48x48, 64x64, 128x128, 256x256, 512x512 + +#define MAX_WATERPLANES 16 ///< max number of water planes visible (each one causes additional view renders) +#define MAX_CUBEMAPS 1024 ///< max number of cubemap textures loaded for light filters +#define MAX_EXPLOSIONS 64 ///< max number of explosion shell effects active at once (not particle related) +#define MAX_DLIGHTS 256 ///< max number of dynamic lights (rocket flashes, etc) in scene at once +#define MAX_CACHED_PICS 1024 ///< max number of 2D pics loaded at once +#define CACHEPICHASHSIZE 256 ///< number of hash buckets for accelerating 2D pic name lookups +#define MAX_PARTICLEEFFECTNAME 256 ///< maximum number of unique names of particle effects (for particleeffectnum) +#define MAX_PARTICLEEFFECTINFO 4096 ///< maximum number of unique particle effects (each name may associate with several of these) +#define MAX_PARTICLETEXTURES 256 ///< maximum number of unique particle textures in the particle font +#define MAXCLVIDEOS 65 ///< maximum number of video streams being played back at once (1 is reserved for the playvideo command) +#define MAX_GECKO_INSTANCES 16 ///< maximum number of web browser textures active at once +#define MAX_DYNAMIC_TEXTURE_COUNT 64 ///< maximum number of dynamic textures (web browsers, playvideo, etc) +#define MAX_MAP_LEAFS 65536 ///< maximum number of BSP leafs in world (8192 in Quake) + +#define MAXTRACKS 256 ///< max CD track index +// 0 to NUM_AMBIENTS - 1 = water, etc +// NUM_AMBIENTS to NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS - 1 = normal entity sounds +// NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS to total_channels = static sounds +#define MAX_DYNAMIC_CHANNELS 512 +#define MAX_CHANNELS 1028 +#define MODLIST_TOTALSIZE 256 +#define MAX_FAVORITESERVERS 256 +#define MAX_DECALSYSTEM_QUEUE 1024 +#define PAINTBUFFER_SIZE 2048 +#define MAX_BINDMAPS 8 +#define MAX_PARTICLES_INITIAL 8192 ///< initial allocation for cl.particles +#define MAX_PARTICLES 1048576 ///< upper limit on cl.particles size +#define MAX_DECALS_INITIAL 8192 ///< initial allocation for cl.decals +#define MAX_DECALS 1048576 ///< upper limit on cl.decals size +#define MAX_ENITIES_INITIAL 256 ///< initial size of cl.entities +#define MAX_STATICENTITIES 1024 ///< limit on size of cl.static_entities +#define MAX_EFFECTS 256 ///< limit on size of cl.effects +#define MAX_BEAMS 256 ///< limit on size of cl.beams +#define MAX_TEMPENTITIES 4096 ///< max number of temporary models visible per frame (certain sprite effects, certain types of CSQC entities also use this) +#endif + + +#define CMD_TOKENIZELENGTH (MAX_INPUTLINE + MAX_ARGS) ///< maximum tokenizable commandline length (counting trailing 0) + + +#define MAX_QPATH 128 ///< max length of a quake game pathname +#ifdef PATH_MAX +#define MAX_OSPATH PATH_MAX +#elif MAX_PATH +#define MAX_OSPATH MAX_PATH +#else +#define MAX_OSPATH 1024 ///< max length of a filesystem pathname +#endif + +#define ON_EPSILON 0.1 ///< point on plane side epsilon + +#define NET_MINRATE 1000 ///< limits "rate" and "sv_maxrate" cvars + +// +// stats are integers communicated to the client by the server +// +#define MAX_CL_STATS 256 +#define STAT_HEALTH 0 +//#define STAT_FRAGS 1 +#define STAT_WEAPON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR 4 +#define STAT_WEAPONFRAME 5 +#define STAT_SHELLS 6 +#define STAT_NAILS 7 +#define STAT_ROCKETS 8 +#define STAT_CELLS 9 +#define STAT_ACTIVEWEAPON 10 +#define STAT_TOTALSECRETS 11 +#define STAT_TOTALMONSTERS 12 +#define STAT_SECRETS 13 ///< bumped on client side by svc_foundsecret +#define STAT_MONSTERS 14 ///< bumped by svc_killedmonster +#define STAT_ITEMS 15 ///< FTE, DP +#define STAT_VIEWHEIGHT 16 ///< FTE, DP +//#define STAT_TIME 17 ///< FTE +//#define STAT_VIEW2 20 ///< FTE +#define STAT_VIEWZOOM 21 ///< DP +#define STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR 220 ///< DP +#define STAT_MOVEVARS_AIRCONTROL_PENALTY 221 ///< DP +#define STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW 222 ///< DP +#define STAT_MOVEVARS_AIRSTRAFEACCEL_QW 223 ///< DP +#define STAT_MOVEVARS_AIRCONTROL_POWER 224 ///< DP +#define STAT_MOVEFLAGS 225 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL 226 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_ACCEL 227 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED 228 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL 229 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO 230 ///< DP +#define STAT_MOVEVARS_AIRSTOPACCELERATE 231 ///< DP +#define STAT_MOVEVARS_AIRSTRAFEACCELERATE 232 ///< DP +#define STAT_MOVEVARS_MAXAIRSTRAFESPEED 233 ///< DP +#define STAT_MOVEVARS_AIRCONTROL 234 ///< DP +#define STAT_FRAGLIMIT 235 ///< DP +#define STAT_TIMELIMIT 236 ///< DP +#define STAT_MOVEVARS_WALLFRICTION 237 ///< DP +#define STAT_MOVEVARS_FRICTION 238 ///< DP +#define STAT_MOVEVARS_WATERFRICTION 239 ///< DP +#define STAT_MOVEVARS_TICRATE 240 ///< DP +#define STAT_MOVEVARS_TIMESCALE 241 ///< DP +#define STAT_MOVEVARS_GRAVITY 242 ///< DP +#define STAT_MOVEVARS_STOPSPEED 243 ///< DP +#define STAT_MOVEVARS_MAXSPEED 244 ///< DP +#define STAT_MOVEVARS_SPECTATORMAXSPEED 245 ///< DP +#define STAT_MOVEVARS_ACCELERATE 246 ///< DP +#define STAT_MOVEVARS_AIRACCELERATE 247 ///< DP +#define STAT_MOVEVARS_WATERACCELERATE 248 ///< DP +#define STAT_MOVEVARS_ENTGRAVITY 249 ///< DP +#define STAT_MOVEVARS_JUMPVELOCITY 250 ///< DP +#define STAT_MOVEVARS_EDGEFRICTION 251 ///< DP +#define STAT_MOVEVARS_MAXAIRSPEED 252 ///< DP +#define STAT_MOVEVARS_STEPHEIGHT 253 ///< DP +#define STAT_MOVEVARS_AIRACCEL_QW 254 ///< DP +#define STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION 255 ///< DP + +// moveflags values +#define MOVEFLAG_VALID 0x80000000 +#define MOVEFLAG_Q2AIRACCELERATE 0x00000001 +#define MOVEFLAG_NOGRAVITYONGROUND 0x00000002 +#define MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE 0x00000004 + +// stock defines + +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_SUPER_LIGHTNING 128 +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 +#define IT_AXE 4096 +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 +#define IT_KEY1 131072 +#define IT_KEY2 262144 +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_SIGIL1 (1<<28) +#define IT_SIGIL2 (1<<29) +#define IT_SIGIL3 (1<<30) +#define IT_SIGIL4 (1<<31) + +//=========================================== +// AK nexuiz changed and added defines + +#define NEX_IT_UZI 1 +#define NEX_IT_SHOTGUN 2 +#define NEX_IT_GRENADE_LAUNCHER 4 +#define NEX_IT_ELECTRO 8 +#define NEX_IT_CRYLINK 16 +#define NEX_IT_NEX 32 +#define NEX_IT_HAGAR 64 +#define NEX_IT_ROCKET_LAUNCHER 128 +#define NEX_IT_SHELLS 256 +#define NEX_IT_BULLETS 512 +#define NEX_IT_ROCKETS 1024 +#define NEX_IT_CELLS 2048 +#define NEX_IT_LASER 4094 +#define NEX_IT_STRENGTH 8192 +#define NEX_IT_INVINCIBLE 16384 +#define NEX_IT_SPEED 32768 +#define NEX_IT_SLOWMO 65536 + +//=========================================== +//rogue changed and added defines + +#define RIT_SHELLS 128 +#define RIT_NAILS 256 +#define RIT_ROCKETS 512 +#define RIT_CELLS 1024 +#define RIT_AXE 2048 +#define RIT_LAVA_NAILGUN 4096 +#define RIT_LAVA_SUPER_NAILGUN 8192 +#define RIT_MULTI_GRENADE 16384 +#define RIT_MULTI_ROCKET 32768 +#define RIT_PLASMA_GUN 65536 +#define RIT_ARMOR1 8388608 +#define RIT_ARMOR2 16777216 +#define RIT_ARMOR3 33554432 +#define RIT_LAVA_NAILS 67108864 +#define RIT_PLASMA_AMMO 134217728 +#define RIT_MULTI_ROCKETS 268435456 +#define RIT_SHIELD 536870912 +#define RIT_ANTIGRAV 1073741824 +#define RIT_SUPERHEALTH 2147483648 + +//MED 01/04/97 added hipnotic defines +//=========================================== +//hipnotic added defines +#define HIT_PROXIMITY_GUN_BIT 16 +#define HIT_MJOLNIR_BIT 7 +#define HIT_LASER_CANNON_BIT 23 +#define HIT_PROXIMITY_GUN (1<server packet. Demo +// playback will ignore this, but it may be useful to make DP sniff packets to +// debug protocol exploits. +#define DEMOMSG_CLIENT_TO_SERVER 0x80000000 + +// In Quake, any char in 0..32 counts as whitespace +//#define ISWHITESPACE(ch) ((unsigned char) ch <= (unsigned char) ' ') +#define ISWHITESPACE(ch) (!(ch) || (ch) == ' ' || (ch) == '\t' || (ch) == '\r' || (ch) == '\n') + +// This also includes extended characters, and ALL control chars +#define ISWHITESPACEORCONTROL(ch) ((signed char) (ch) <= (signed char) ' ') + + +#define FLOAT_IS_TRUE_FOR_INT(x) ((x) & 0x7FFFFFFF) // also match "negative zero" floats of value 0x80000000 + +#endif + diff --git a/misc/source/darkplaces-src/r_explosion.c b/misc/source/darkplaces-src/r_explosion.c new file mode 100644 index 00000000..3789d8d8 --- /dev/null +++ b/misc/source/darkplaces-src/r_explosion.c @@ -0,0 +1,285 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "cl_collision.h" + +#ifdef MAX_EXPLOSIONS +#define EXPLOSIONGRID 8 +#define EXPLOSIONVERTS ((EXPLOSIONGRID+1)*(EXPLOSIONGRID+1)) +#define EXPLOSIONTRIS (EXPLOSIONGRID*EXPLOSIONGRID*2) + +static int numexplosions = 0; + +static float explosiontexcoord2f[EXPLOSIONVERTS][2]; +static unsigned short explosiontris[EXPLOSIONTRIS][3]; +static int explosionnoiseindex[EXPLOSIONVERTS]; +static vec3_t explosionpoint[EXPLOSIONVERTS]; + +typedef struct explosion_s +{ + float starttime; + float endtime; + float time; + float alpha; + float fade; + vec3_t origin; + vec3_t vert[EXPLOSIONVERTS]; + vec3_t vertvel[EXPLOSIONVERTS]; + qboolean clipping; +} +explosion_t; + +static explosion_t explosion[MAX_EXPLOSIONS]; + +static rtexture_t *explosiontexture; +//static rtexture_t *explosiontexturefog; + +static rtexturepool_t *explosiontexturepool; +#endif + +cvar_t r_explosionclip = {CVAR_SAVE, "r_explosionclip", "1", "enables collision detection for explosion shell (so that it flattens against walls and floors)"}; +#ifdef MAX_EXPLOSIONS +static cvar_t r_drawexplosions = {0, "r_drawexplosions", "1", "enables rendering of explosion shells (see also cl_particles_explosions_shell)"}; + +//extern qboolean r_loadfog; +static void r_explosion_start(void) +{ + int x, y; + static unsigned char noise1[128][128], noise2[128][128], noise3[128][128], data[128][128][4]; + explosiontexturepool = R_AllocTexturePool(); + explosiontexture = NULL; + //explosiontexturefog = NULL; + fractalnoise(&noise1[0][0], 128, 32); + fractalnoise(&noise2[0][0], 128, 4); + fractalnoise(&noise3[0][0], 128, 4); + for (y = 0;y < 128;y++) + { + for (x = 0;x < 128;x++) + { + int j, r, g, b, a; + j = (noise1[y][x] * noise2[y][x]) * 3 / 256 - 128; + r = (j * 512) / 256; + g = (j * 256) / 256; + b = (j * 128) / 256; + a = noise3[y][x] * 3 - 128; + data[y][x][2] = bound(0, r, 255); + data[y][x][1] = bound(0, g, 255); + data[y][x][0] = bound(0, b, 255); + data[y][x][3] = bound(0, a, 255); + } + } + explosiontexture = R_LoadTexture2D(explosiontexturepool, "explosiontexture", 128, 128, &data[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); +// if (r_loadfog) +// { +// for (y = 0;y < 128;y++) +// for (x = 0;x < 128;x++) +// data[y][x][0] = data[y][x][1] = data[y][x][2] = 255; +// explosiontexturefog = R_LoadTexture2D(explosiontexturepool, "explosiontexture_fog", 128, 128, &data[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_ALPHA | TEXF_FORCELINEAR, NULL); +// } + // note that explosions survive the restart +} + +static void r_explosion_shutdown(void) +{ + R_FreeTexturePool(&explosiontexturepool); +} + +static void r_explosion_newmap(void) +{ + numexplosions = 0; + memset(explosion, 0, sizeof(explosion)); +} + +static int R_ExplosionVert(int column, int row) +{ + int i; + float yaw, pitch; + // top and bottom rows are all one position... + if (row == 0 || row == EXPLOSIONGRID) + column = 0; + i = row * (EXPLOSIONGRID + 1) + column; + yaw = ((double) column / EXPLOSIONGRID) * M_PI * 2; + pitch = (((double) row / EXPLOSIONGRID) - 0.5) * M_PI; + explosionpoint[i][0] = cos(yaw) * cos(pitch); + explosionpoint[i][1] = sin(yaw) * cos(pitch); + explosionpoint[i][2] = 1 * -sin(pitch); + explosiontexcoord2f[i][0] = (float) column / (float) EXPLOSIONGRID; + explosiontexcoord2f[i][1] = (float) row / (float) EXPLOSIONGRID; + explosionnoiseindex[i] = (row % EXPLOSIONGRID) * EXPLOSIONGRID + (column % EXPLOSIONGRID); + return i; +} +#endif + +void R_Explosion_Init(void) +{ +#ifdef MAX_EXPLOSIONS + int i, x, y; + i = 0; + for (y = 0;y < EXPLOSIONGRID;y++) + { + for (x = 0;x < EXPLOSIONGRID;x++) + { + explosiontris[i][0] = R_ExplosionVert(x , y ); + explosiontris[i][1] = R_ExplosionVert(x + 1, y ); + explosiontris[i][2] = R_ExplosionVert(x , y + 1); + i++; + explosiontris[i][0] = R_ExplosionVert(x + 1, y ); + explosiontris[i][1] = R_ExplosionVert(x + 1, y + 1); + explosiontris[i][2] = R_ExplosionVert(x , y + 1); + i++; + } + } + +#endif + Cvar_RegisterVariable(&r_explosionclip); +#ifdef MAX_EXPLOSIONS + Cvar_RegisterVariable(&r_drawexplosions); + + R_RegisterModule("R_Explosions", r_explosion_start, r_explosion_shutdown, r_explosion_newmap, NULL, NULL); +#endif +} + +void R_NewExplosion(const vec3_t org) +{ +#ifdef MAX_EXPLOSIONS + int i, j; + float dist, n; + explosion_t *e; + trace_t trace; + unsigned char noise[EXPLOSIONGRID*EXPLOSIONGRID]; + fractalnoisequick(noise, EXPLOSIONGRID, 4); // adjust noise grid size according to explosion + for (i = 0, e = explosion;i < MAX_EXPLOSIONS;i++, e++) + { + if (!e->alpha) + { + numexplosions = max(numexplosions, i + 1); + e->starttime = cl.time; + e->endtime = cl.time + cl_explosions_lifetime.value; + e->time = e->starttime; + e->alpha = cl_explosions_alpha_start.value; + e->fade = (cl_explosions_alpha_start.value - cl_explosions_alpha_end.value) / cl_explosions_lifetime.value; + e->clipping = r_explosionclip.integer != 0; + VectorCopy(org, e->origin); + for (j = 0;j < EXPLOSIONVERTS;j++) + { + // calculate start origin and velocity + n = noise[explosionnoiseindex[j]] * (1.0f / 255.0f) + 0.5; + dist = n * cl_explosions_size_start.value; + VectorMA(e->origin, dist, explosionpoint[j], e->vert[j]); + dist = n * (cl_explosions_size_end.value - cl_explosions_size_start.value) / cl_explosions_lifetime.value; + VectorScale(explosionpoint[j], dist, e->vertvel[j]); + // clip start origin + if (e->clipping) + { + trace = CL_TraceLine(e->origin, e->vert[j], MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false); + VectorCopy(trace.endpos, e->vert[i]); + } + } + break; + } + } +#endif +} + +#ifdef MAX_EXPLOSIONS +static void R_DrawExplosion_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex = 0; + const int numtriangles = EXPLOSIONTRIS, numverts = EXPLOSIONVERTS; + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + GL_DepthTest(true); + GL_CullFace(r_refdef.view.cullface_back); + R_EntityMatrix(&identitymatrix); + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(explosiontexture, NULL, GL_MODULATE, 1, false); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + const explosion_t *e = explosion + surfacelist[surfacelistindex]; + // FIXME: this can't properly handle r_refdef.view.colorscale > 1 + GL_Color(e->alpha * r_refdef.view.colorscale, e->alpha * r_refdef.view.colorscale, e->alpha * r_refdef.view.colorscale, 1); + R_Mesh_PrepareVertices_Generic_Arrays(numverts, e->vert[0], NULL, explosiontexcoord2f[0]); + R_Mesh_Draw(0, numverts, 0, numtriangles, NULL, NULL, 0, explosiontris[0], NULL, 0); + } +} + +static void R_MoveExplosion(explosion_t *e) +{ + int i; + float dot, end[3], frametime; + trace_t trace; + + frametime = cl.time - e->time; + e->time = cl.time; + e->alpha = e->alpha - (e->fade * frametime); + if (e->alpha < 0 || cl.time > e->endtime) + { + e->alpha = 0; + return; + } + for (i = 0;i < EXPLOSIONVERTS;i++) + { + if (e->vertvel[i][0] || e->vertvel[i][1] || e->vertvel[i][2]) + { + VectorMA(e->vert[i], frametime, e->vertvel[i], end); + if (e->clipping) + { + trace = CL_TraceLine(e->vert[i], end, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false); + if (trace.fraction < 1) + { + // clip velocity against the wall + dot = -DotProduct(e->vertvel[i], trace.plane.normal); + VectorMA(e->vertvel[i], dot, trace.plane.normal, e->vertvel[i]); + } + VectorCopy(trace.endpos, e->vert[i]); + } + else + VectorCopy(end, e->vert[i]); + } + } +} +#endif + +void R_DrawExplosions(void) +{ +#ifdef MAX_EXPLOSIONS + int i; + + if (!r_drawexplosions.integer) + return; + + for (i = 0;i < numexplosions;i++) + { + if (explosion[i].alpha) + { + R_MoveExplosion(&explosion[i]); + if (explosion[i].alpha) + R_MeshQueue_AddTransparent(explosion[i].origin, R_DrawExplosion_TransparentCallback, NULL, i, NULL); + } + } + while (numexplosions > 0 && explosion[i-1].alpha <= 0) + numexplosions--; +#endif +} + diff --git a/misc/source/darkplaces-src/r_lerpanim.c b/misc/source/darkplaces-src/r_lerpanim.c new file mode 100644 index 00000000..e69de29b diff --git a/misc/source/darkplaces-src/r_lerpanim.h b/misc/source/darkplaces-src/r_lerpanim.h new file mode 100644 index 00000000..e69de29b diff --git a/misc/source/darkplaces-src/r_lightning.c b/misc/source/darkplaces-src/r_lightning.c new file mode 100644 index 00000000..67d32ae8 --- /dev/null +++ b/misc/source/darkplaces-src/r_lightning.c @@ -0,0 +1,327 @@ + +#include "quakedef.h" +#include "image.h" + +cvar_t r_lightningbeam_thickness = {CVAR_SAVE, "r_lightningbeam_thickness", "4", "thickness of the lightning beam effect"}; +cvar_t r_lightningbeam_scroll = {CVAR_SAVE, "r_lightningbeam_scroll", "5", "speed of texture scrolling on the lightning beam effect"}; +cvar_t r_lightningbeam_repeatdistance = {CVAR_SAVE, "r_lightningbeam_repeatdistance", "128", "how far to stretch the texture along the lightning beam effect"}; +cvar_t r_lightningbeam_color_red = {CVAR_SAVE, "r_lightningbeam_color_red", "1", "color of the lightning beam effect"}; +cvar_t r_lightningbeam_color_green = {CVAR_SAVE, "r_lightningbeam_color_green", "1", "color of the lightning beam effect"}; +cvar_t r_lightningbeam_color_blue = {CVAR_SAVE, "r_lightningbeam_color_blue", "1", "color of the lightning beam effect"}; +cvar_t r_lightningbeam_qmbtexture = {CVAR_SAVE, "r_lightningbeam_qmbtexture", "0", "load the qmb textures/particles/lightning.pcx texture instead of generating one, can look better"}; + +skinframe_t *r_lightningbeamtexture; +skinframe_t *r_lightningbeamqmbtexture; + +int r_lightningbeamelement3i[18] = {0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11}; +unsigned short r_lightningbeamelement3s[18] = {0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11}; + +void r_lightningbeams_start(void) +{ + r_lightningbeamtexture = NULL; + r_lightningbeamqmbtexture = NULL; +} + +void r_lightningbeams_setupqmbtexture(void) +{ + r_lightningbeamqmbtexture = R_SkinFrame_LoadExternal("textures/particles/lightning.pcx", TEXF_ALPHA | TEXF_FORCELINEAR, false); + if (r_lightningbeamqmbtexture == NULL) + Cvar_SetValueQuick(&r_lightningbeam_qmbtexture, false); +} + +void r_lightningbeams_setuptexture(void) +{ +#if 0 +#define BEAMWIDTH 128 +#define BEAMHEIGHT 64 +#define PATHPOINTS 8 + int i, j, px, py, nearestpathindex, imagenumber; + float particlex, particley, particlexv, particleyv, dx, dy, s, maxpathstrength; + unsigned char *pixels; + int *image; + struct lightningpathnode_s + { + float x, y, strength; + } + path[PATHPOINTS], temppath; + + image = Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * sizeof(int)); + pixels = Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * sizeof(unsigned char[4])); + + for (imagenumber = 0, maxpathstrength = 0.0339476;maxpathstrength < 0.5;imagenumber++, maxpathstrength += 0.01) + { + for (i = 0;i < PATHPOINTS;i++) + { + path[i].x = lhrandom(0, 1); + path[i].y = lhrandom(0.2, 0.8); + path[i].strength = lhrandom(0, 1); + } + for (i = 0;i < PATHPOINTS;i++) + { + for (j = i + 1;j < PATHPOINTS;j++) + { + if (path[j].x < path[i].x) + { + temppath = path[j]; + path[j] = path[i]; + path[i] = temppath; + } + } + } + particlex = path[0].x; + particley = path[0].y; + particlexv = lhrandom(0, 0.02); + particlexv = lhrandom(-0.02, 0.02); + memset(image, 0, BEAMWIDTH * BEAMHEIGHT * sizeof(int)); + for (i = 0;i < 65536;i++) + { + for (nearestpathindex = 0;nearestpathindex < PATHPOINTS;nearestpathindex++) + if (path[nearestpathindex].x > particlex) + break; + nearestpathindex %= PATHPOINTS; + dx = path[nearestpathindex].x + lhrandom(-0.01, 0.01);dx = bound(0, dx, 1) - particlex;if (dx < 0) dx += 1; + dy = path[nearestpathindex].y + lhrandom(-0.01, 0.01);dy = bound(0, dy, 1) - particley; + s = path[nearestpathindex].strength / sqrt(dx*dx+dy*dy); + particlexv = particlexv /* (1 - lhrandom(0.08, 0.12))*/ + dx * s; + particleyv = particleyv /* (1 - lhrandom(0.08, 0.12))*/ + dy * s; + particlex += particlexv * maxpathstrength;particlex -= (int) particlex; + particley += particleyv * maxpathstrength;particley = bound(0, particley, 1); + px = particlex * BEAMWIDTH; + py = particley * BEAMHEIGHT; + if (px >= 0 && py >= 0 && px < BEAMWIDTH && py < BEAMHEIGHT) + image[py*BEAMWIDTH+px] += 16; + } + + for (py = 0;py < BEAMHEIGHT;py++) + { + for (px = 0;px < BEAMWIDTH;px++) + { + pixels[(py*BEAMWIDTH+px)*4+2] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); + pixels[(py*BEAMWIDTH+px)*4+1] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); + pixels[(py*BEAMWIDTH+px)*4+0] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); + pixels[(py*BEAMWIDTH+px)*4+3] = 255; + } + } + + Image_WriteTGABGRA(va("lightningbeam%i.tga", imagenumber), BEAMWIDTH, BEAMHEIGHT, pixels); + } + + r_lightningbeamtexture = R_LoadTexture2D(r_lightningbeamtexturepool, "lightningbeam", BEAMWIDTH, BEAMHEIGHT, pixels, TEXTYPE_BGRA, TEXF_FORCELINEAR, NULL); + + Mem_Free(pixels); + Mem_Free(image); +#else +#define BEAMWIDTH 64 +#define BEAMHEIGHT 128 + float r, g, b, intensity, fx, width, center; + int x, y; + unsigned char *data, *noise1, *noise2; + + data = (unsigned char *)Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * 4); + noise1 = (unsigned char *)Mem_Alloc(tempmempool, BEAMHEIGHT * BEAMHEIGHT); + noise2 = (unsigned char *)Mem_Alloc(tempmempool, BEAMHEIGHT * BEAMHEIGHT); + fractalnoise(noise1, BEAMHEIGHT, BEAMHEIGHT / 8); + fractalnoise(noise2, BEAMHEIGHT, BEAMHEIGHT / 16); + + for (y = 0;y < BEAMHEIGHT;y++) + { + width = 0.15;//((noise1[y * BEAMHEIGHT] * (1.0f / 256.0f)) * 0.1f + 0.1f); + center = (noise1[y * BEAMHEIGHT + (BEAMHEIGHT / 2)] / 256.0f) * (1.0f - width * 2.0f) + width; + for (x = 0;x < BEAMWIDTH;x++, fx++) + { + fx = (((float) x / BEAMWIDTH) - center) / width; + intensity = 1.0f - sqrt(fx * fx); + if (intensity > 0) + intensity = pow(intensity, 2) * ((noise2[y * BEAMHEIGHT + x] * (1.0f / 256.0f)) * 0.33f + 0.66f); + intensity = bound(0, intensity, 1); + r = intensity * 1.0f; + g = intensity * 1.0f; + b = intensity * 1.0f; + data[(y * BEAMWIDTH + x) * 4 + 2] = (unsigned char)(bound(0, r, 1) * 255.0f); + data[(y * BEAMWIDTH + x) * 4 + 1] = (unsigned char)(bound(0, g, 1) * 255.0f); + data[(y * BEAMWIDTH + x) * 4 + 0] = (unsigned char)(bound(0, b, 1) * 255.0f); + data[(y * BEAMWIDTH + x) * 4 + 3] = (unsigned char)255; + } + } + + r_lightningbeamtexture = R_SkinFrame_LoadInternalBGRA("lightningbeam", TEXF_FORCELINEAR, data, BEAMWIDTH, BEAMHEIGHT, false); + Mem_Free(noise1); + Mem_Free(noise2); + Mem_Free(data); +#endif +} + +void r_lightningbeams_shutdown(void) +{ + r_lightningbeamtexture = NULL; + r_lightningbeamqmbtexture = NULL; +} + +void r_lightningbeams_newmap(void) +{ + if (r_lightningbeamtexture) + R_SkinFrame_MarkUsed(r_lightningbeamtexture); + if (r_lightningbeamqmbtexture) + R_SkinFrame_MarkUsed(r_lightningbeamqmbtexture); +} + +void R_LightningBeams_Init(void) +{ + Cvar_RegisterVariable(&r_lightningbeam_thickness); + Cvar_RegisterVariable(&r_lightningbeam_scroll); + Cvar_RegisterVariable(&r_lightningbeam_repeatdistance); + Cvar_RegisterVariable(&r_lightningbeam_color_red); + Cvar_RegisterVariable(&r_lightningbeam_color_green); + Cvar_RegisterVariable(&r_lightningbeam_color_blue); + Cvar_RegisterVariable(&r_lightningbeam_qmbtexture); + R_RegisterModule("R_LightningBeams", r_lightningbeams_start, r_lightningbeams_shutdown, r_lightningbeams_newmap, NULL, NULL); +} + +void R_CalcLightningBeamPolygonVertex3f(float *v, const float *start, const float *end, const float *offset) +{ + // near right corner + VectorAdd (start, offset, (v + 0)); + // near left corner + VectorSubtract(start, offset, (v + 3)); + // far left corner + VectorSubtract(end , offset, (v + 6)); + // far right corner + VectorAdd (end , offset, (v + 9)); +} + +void R_CalcLightningBeamPolygonTexCoord2f(float *tc, float t1, float t2) +{ + if (r_lightningbeam_qmbtexture.integer) + { + // near right corner + tc[0] = t1;tc[1] = 0; + // near left corner + tc[2] = t1;tc[3] = 1; + // far left corner + tc[4] = t2;tc[5] = 1; + // far right corner + tc[6] = t2;tc[7] = 0; + } + else + { + // near right corner + tc[0] = 0;tc[1] = t1; + // near left corner + tc[2] = 1;tc[3] = t1; + // far left corner + tc[4] = 1;tc[5] = t2; + // far right corner + tc[6] = 0;tc[7] = t2; + } +} + +float beamrepeatscale; + +void R_DrawLightningBeam_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex; + float vertex3f[12*3]; + float texcoord2f[12*2]; + + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, r_lightningbeam_color_red.value, r_lightningbeam_color_green.value, r_lightningbeam_color_blue.value, 1, 12, vertex3f, texcoord2f, NULL, NULL, NULL, NULL, 6, r_lightningbeamelement3i, r_lightningbeamelement3s, false, false); + + if (r_lightningbeam_qmbtexture.integer && r_lightningbeamqmbtexture == NULL) + r_lightningbeams_setupqmbtexture(); + if (!r_lightningbeam_qmbtexture.integer && r_lightningbeamtexture == NULL) + r_lightningbeams_setuptexture(); + + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + const beam_t *b = cl.beams + surfacelist[surfacelistindex]; + vec3_t beamdir, right, up, offset, start, end; + float length, t1, t2; + + CL_Beam_CalculatePositions(b, start, end); + + // calculate beam direction (beamdir) vector and beam length + // get difference vector + VectorSubtract(end, start, beamdir); + // find length of difference vector + length = sqrt(DotProduct(beamdir, beamdir)); + // calculate scale to make beamdir a unit vector (normalized) + t1 = 1.0f / length; + // scale beamdir so it is now normalized + VectorScale(beamdir, t1, beamdir); + + // calculate up vector such that it points toward viewer, and rotates around the beamdir + // get direction from start of beam to viewer + VectorSubtract(r_refdef.view.origin, start, up); + // remove the portion of the vector that moves along the beam + // (this leaves only a vector pointing directly away from the beam) + t1 = -DotProduct(up, beamdir); + VectorMA(up, t1, beamdir, up); + // generate right vector from forward and up, the result is unnormalized + CrossProduct(beamdir, up, right); + // now normalize the right vector and up vector + VectorNormalize(right); + VectorNormalize(up); + + // calculate T coordinate scrolling (start and end texcoord along the beam) + t1 = r_refdef.scene.time * -r_lightningbeam_scroll.value;// + beamrepeatscale * DotProduct(start, beamdir); + t1 = t1 - (int) t1; + t2 = t1 + beamrepeatscale * length; + + // the beam is 3 polygons in this configuration: + // * 2 + // * * + // 1****** + // * * + // * 3 + // they are showing different portions of the beam texture, creating an + // illusion of a beam that appears to curl around in 3D space + // (and realize that the whole polygon assembly orients itself to face + // the viewer) + + // polygon 1, verts 0-3 + VectorScale(right, r_lightningbeam_thickness.value, offset); + R_CalcLightningBeamPolygonVertex3f(vertex3f + 0, start, end, offset); + // polygon 2, verts 4-7 + VectorAdd(right, up, offset); + VectorScale(offset, r_lightningbeam_thickness.value * 0.70710681f, offset); + R_CalcLightningBeamPolygonVertex3f(vertex3f + 12, start, end, offset); + // polygon 3, verts 8-11 + VectorSubtract(right, up, offset); + VectorScale(offset, r_lightningbeam_thickness.value * 0.70710681f, offset); + R_CalcLightningBeamPolygonVertex3f(vertex3f + 24, start, end, offset); + R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 0, t1, t2); + R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 8, t1 + 0.33, t2 + 0.33); + R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 16, t1 + 0.66, t2 + 0.66); + + // draw the 3 polygons as one batch of 6 triangles using the 12 vertices + R_DrawCustomSurface(r_lightningbeam_qmbtexture.integer ? r_lightningbeamqmbtexture : r_lightningbeamtexture, &identitymatrix, MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 12, 0, 6, false, false); + } +} + +extern cvar_t cl_beams_polygons; +void R_DrawLightningBeams(void) +{ + int i; + beam_t *b; + + if (!cl_beams_polygons.integer) + return; + + beamrepeatscale = 1.0f / r_lightningbeam_repeatdistance.value; + for (i = 0, b = cl.beams;i < cl.num_beams;i++, b++) + { + if (b->model && b->lightning) + { + vec3_t org, start, end, dir; + vec_t dist; + CL_Beam_CalculatePositions(b, start, end); + // calculate the nearest point on the line (beam) for depth sorting + VectorSubtract(end, start, dir); + dist = (DotProduct(r_refdef.view.origin, dir) - DotProduct(start, dir)) / (DotProduct(end, dir) - DotProduct(start, dir)); + dist = bound(0, dist, 1); + VectorLerp(start, dist, end, org); + // now we have the nearest point on the line, so sort with it + R_MeshQueue_AddTransparent(org, R_DrawLightningBeam_TransparentCallback, NULL, i, NULL); + } + } +} + diff --git a/misc/source/darkplaces-src/r_modules.c b/misc/source/darkplaces-src/r_modules.c new file mode 100644 index 00000000..818637e8 --- /dev/null +++ b/misc/source/darkplaces-src/r_modules.c @@ -0,0 +1,134 @@ + +#include "quakedef.h" + +#define MAXRENDERMODULES 20 + +typedef struct rendermodule_s +{ + int active; // set by start, cleared by shutdown + const char *name; + void(*start)(void); + void(*shutdown)(void); + void(*newmap)(void); + void(*devicelost)(void); + void(*devicerestored)(void); +} +rendermodule_t; + +rendermodule_t rendermodule[MAXRENDERMODULES]; + +void R_Modules_Init(void) +{ + Cmd_AddCommand("r_restart", R_Modules_Restart, "restarts renderer"); +} + +void R_RegisterModule(const char *name, void(*start)(void), void(*shutdown)(void), void(*newmap)(void), void(*devicelost)(void), void(*devicerestored)(void)) +{ + int i; + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + break; + if (!strcmp(name, rendermodule[i].name)) + { + Con_Printf("R_RegisterModule: module \"%s\" registered twice\n", name); + return; + } + } + if (i >= MAXRENDERMODULES) + Sys_Error("R_RegisterModule: ran out of renderer module slots (%i)", MAXRENDERMODULES); + rendermodule[i].active = 0; + rendermodule[i].name = name; + rendermodule[i].start = start; + rendermodule[i].shutdown = shutdown; + rendermodule[i].newmap = newmap; + rendermodule[i].devicelost = devicelost; + rendermodule[i].devicerestored = devicerestored; +} + +void R_Modules_Start(void) +{ + int i; + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + continue; + if (rendermodule[i].active) + { + Con_Printf ("R_StartModules: module \"%s\" already active\n", rendermodule[i].name); + continue; + } + rendermodule[i].active = 1; + rendermodule[i].start(); + } +} + +void R_Modules_Shutdown(void) +{ + int i; + // shutdown in reverse + for (i = MAXRENDERMODULES - 1;i >= 0;i--) + { + if (rendermodule[i].name == NULL) + continue; + if (!rendermodule[i].active) + continue; + rendermodule[i].active = 0; + rendermodule[i].shutdown(); + } +} + +void R_Modules_Restart(void) +{ + Host_StartVideo(); + Con_Print("restarting renderer\n"); + R_Modules_Shutdown(); + R_Modules_Start(); +} + +void R_Modules_NewMap(void) +{ + int i; + R_SkinFrame_PrepareForPurge(); + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + continue; + if (!rendermodule[i].active) + continue; + rendermodule[i].newmap(); + } + R_SkinFrame_Purge(); +} + +void R_Modules_DeviceLost(void) +{ + int i; + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + continue; + if (!rendermodule[i].active) + continue; + if (!rendermodule[i].devicelost) + continue; + rendermodule[i].devicelost(); + } +} + + +void R_Modules_DeviceRestored(void) +{ + int i; + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + continue; + if (!rendermodule[i].active) + continue; + if (!rendermodule[i].devicerestored) + continue; + rendermodule[i].devicerestored(); + } +} + diff --git a/misc/source/darkplaces-src/r_modules.h b/misc/source/darkplaces-src/r_modules.h new file mode 100644 index 00000000..ad838d17 --- /dev/null +++ b/misc/source/darkplaces-src/r_modules.h @@ -0,0 +1,15 @@ + +#ifndef R_MODULES_H +#define R_MODULES_H + +void R_Modules_Init(void); +void R_RegisterModule(const char *name, void(*start)(void), void(*shutdown)(void), void(*newmap)(void), void(*devicelost)(void), void(*devicerestored)(void)); +void R_Modules_Start(void); +void R_Modules_Shutdown(void); +void R_Modules_NewMap(void); +void R_Modules_Restart(void); +void R_Modules_DeviceLost(void); +void R_Modules_DeviceRestored(void); + +#endif + diff --git a/misc/source/darkplaces-src/r_shadow.c b/misc/source/darkplaces-src/r_shadow.c new file mode 100644 index 00000000..c11668d4 --- /dev/null +++ b/misc/source/darkplaces-src/r_shadow.c @@ -0,0 +1,6877 @@ + +/* +Terminology: Stencil Shadow Volume (sometimes called Stencil Shadows) +An extrusion of the lit faces, beginning at the original geometry and ending +further from the light source than the original geometry (presumably at least +as far as the light's radius, if the light has a radius at all), capped at +both front and back to avoid any problems (extrusion from dark faces also +works but has a different set of problems) + +This is normally rendered using Carmack's Reverse technique, in which +backfaces behind zbuffer (zfail) increment the stencil, and frontfaces behind +zbuffer (zfail) decrement the stencil, the result is a stencil value of zero +where shadows did not intersect the visible geometry, suitable as a stencil +mask for rendering lighting everywhere but shadow. + +In our case to hopefully avoid the Creative Labs patent, we draw the backfaces +as decrement and the frontfaces as increment, and we redefine the DepthFunc to +GL_LESS (the patent uses GL_GEQUAL) which causes zfail when behind surfaces +and zpass when infront (the patent draws where zpass with a GL_GEQUAL test), +additionally we clear stencil to 128 to avoid the need for the unclamped +incr/decr extension (not related to patent). + +Patent warning: +This algorithm may be covered by Creative's patent (US Patent #6384822), +however that patent is quite specific about increment on backfaces and +decrement on frontfaces where zpass with GL_GEQUAL depth test, which is +opposite this implementation and partially opposite Carmack's Reverse paper +(which uses GL_LESS, but increments on backfaces and decrements on frontfaces). + + + +Terminology: Stencil Light Volume (sometimes called Light Volumes) +Similar to a Stencil Shadow Volume, but inverted; rather than containing the +areas in shadow it contains the areas in light, this can only be built +quickly for certain limited cases (such as portal visibility from a point), +but is quite useful for some effects (sunlight coming from sky polygons is +one possible example, translucent occluders is another example). + + + +Terminology: Optimized Stencil Shadow Volume +A Stencil Shadow Volume that has been processed sufficiently to ensure it has +no duplicate coverage of areas (no need to shadow an area twice), often this +greatly improves performance but is an operation too costly to use on moving +lights (however completely optimal Stencil Light Volumes can be constructed +in some ideal cases). + + + +Terminology: Per Pixel Lighting (sometimes abbreviated PPL) +Per pixel evaluation of lighting equations, at a bare minimum this involves +DOT3 shading of diffuse lighting (per pixel dotproduct of negated incidence +vector and surface normal, using a texture of the surface bumps, called a +NormalMap) if supported by hardware; in our case there is support for cards +which are incapable of DOT3, the quality is quite poor however. Additionally +it is desirable to have specular evaluation per pixel, per vertex +normalization of specular halfangle vectors causes noticable distortion but +is unavoidable on hardware without GL_ARB_fragment_program or +GL_ARB_fragment_shader. + + + +Terminology: Normalization CubeMap +A cubemap containing normalized dot3-encoded (vectors of length 1 or less +encoded as RGB colors) for any possible direction, this technique allows per +pixel calculation of incidence vector for per pixel lighting purposes, which +would not otherwise be possible per pixel without GL_ARB_fragment_program or +GL_ARB_fragment_shader. + + + +Terminology: 2D+1D Attenuation Texturing +A very crude approximation of light attenuation with distance which results +in cylindrical light shapes which fade vertically as a streak (some games +such as Doom3 allow this to be rotated to be less noticable in specific +cases), the technique is simply modulating lighting by two 2D textures (which +can be the same) on different axes of projection (XY and Z, typically), this +is the second best technique available without 3D Attenuation Texturing, +GL_ARB_fragment_program or GL_ARB_fragment_shader technology. + + + +Terminology: 2D+1D Inverse Attenuation Texturing +A clever method described in papers on the Abducted engine, this has a squared +distance texture (bright on the outside, black in the middle), which is used +twice using GL_ADD blending, the result of this is used in an inverse modulate +(GL_ONE_MINUS_DST_ALPHA, GL_ZERO) to implement the equation +lighting*=(1-((X*X+Y*Y)+(Z*Z))) which is spherical (unlike 2D+1D attenuation +texturing). + + + +Terminology: 3D Attenuation Texturing +A slightly crude approximation of light attenuation with distance, its flaws +are limited radius and resolution (performance tradeoffs). + + + +Terminology: 3D Attenuation-Normalization Texturing +A 3D Attenuation Texture merged with a Normalization CubeMap, by making the +vectors shorter the lighting becomes darker, a very effective optimization of +diffuse lighting if 3D Attenuation Textures are already used. + + + +Terminology: Light Cubemap Filtering +A technique for modeling non-uniform light distribution according to +direction, for example a lantern may use a cubemap to describe the light +emission pattern of the cage around the lantern (as well as soot buildup +discoloring the light in certain areas), often also used for softened grate +shadows and light shining through a stained glass window (done crudely by +texturing the lighting with a cubemap), another good example would be a disco +light. This technique is used heavily in many games (Doom3 does not support +this however). + + + +Terminology: Light Projection Filtering +A technique for modeling shadowing of light passing through translucent +surfaces, allowing stained glass windows and other effects to be done more +elegantly than possible with Light Cubemap Filtering by applying an occluder +texture to the lighting combined with a stencil light volume to limit the lit +area, this technique is used by Doom3 for spotlights and flashlights, among +other things, this can also be used more generally to render light passing +through multiple translucent occluders in a scene (using a light volume to +describe the area beyond the occluder, and thus mask off rendering of all +other areas). + + + +Terminology: Doom3 Lighting +A combination of Stencil Shadow Volume, Per Pixel Lighting, Normalization +CubeMap, 2D+1D Attenuation Texturing, and Light Projection Filtering, as +demonstrated by the game Doom3. +*/ + +#include "quakedef.h" +#include "r_shadow.h" +#include "cl_collision.h" +#include "portals.h" +#include "image.h" +#include "dpsoftrast.h" + +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +#endif + +extern void R_Shadow_EditLights_Init(void); + +typedef enum r_shadow_rendermode_e +{ + R_SHADOW_RENDERMODE_NONE, + R_SHADOW_RENDERMODE_ZPASS_STENCIL, + R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL, + R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE, + R_SHADOW_RENDERMODE_ZFAIL_STENCIL, + R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL, + R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE, + R_SHADOW_RENDERMODE_LIGHT_VERTEX, + R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN, + R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN, + R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN, + R_SHADOW_RENDERMODE_LIGHT_GLSL, + R_SHADOW_RENDERMODE_VISIBLEVOLUMES, + R_SHADOW_RENDERMODE_VISIBLELIGHTING, + R_SHADOW_RENDERMODE_SHADOWMAP2D +} +r_shadow_rendermode_t; + +typedef enum r_shadow_shadowmode_e +{ + R_SHADOW_SHADOWMODE_STENCIL, + R_SHADOW_SHADOWMODE_SHADOWMAP2D +} +r_shadow_shadowmode_t; + +r_shadow_rendermode_t r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; +r_shadow_rendermode_t r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_NONE; +r_shadow_rendermode_t r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_NONE; +r_shadow_rendermode_t r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_NONE; +qboolean r_shadow_usingshadowmap2d; +qboolean r_shadow_usingshadowmaportho; +int r_shadow_shadowmapside; +float r_shadow_shadowmap_texturescale[2]; +float r_shadow_shadowmap_parameters[4]; +#if 0 +int r_shadow_drawbuffer; +int r_shadow_readbuffer; +#endif +int r_shadow_cullface_front, r_shadow_cullface_back; +GLuint r_shadow_fbo2d; +r_shadow_shadowmode_t r_shadow_shadowmode; +int r_shadow_shadowmapfilterquality; +int r_shadow_shadowmapdepthbits; +int r_shadow_shadowmapmaxsize; +qboolean r_shadow_shadowmapvsdct; +qboolean r_shadow_shadowmapsampler; +int r_shadow_shadowmappcf; +int r_shadow_shadowmapborder; +matrix4x4_t r_shadow_shadowmapmatrix; +int r_shadow_lightscissor[4]; +qboolean r_shadow_usingdeferredprepass; + +int maxshadowtriangles; +int *shadowelements; + +int maxshadowvertices; +float *shadowvertex3f; + +int maxshadowmark; +int numshadowmark; +int *shadowmark; +int *shadowmarklist; +int shadowmarkcount; + +int maxshadowsides; +int numshadowsides; +unsigned char *shadowsides; +int *shadowsideslist; + +int maxvertexupdate; +int *vertexupdate; +int *vertexremap; +int vertexupdatenum; + +int r_shadow_buffer_numleafpvsbytes; +unsigned char *r_shadow_buffer_visitingleafpvs; +unsigned char *r_shadow_buffer_leafpvs; +int *r_shadow_buffer_leaflist; + +int r_shadow_buffer_numsurfacepvsbytes; +unsigned char *r_shadow_buffer_surfacepvs; +int *r_shadow_buffer_surfacelist; +unsigned char *r_shadow_buffer_surfacesides; + +int r_shadow_buffer_numshadowtrispvsbytes; +unsigned char *r_shadow_buffer_shadowtrispvs; +int r_shadow_buffer_numlighttrispvsbytes; +unsigned char *r_shadow_buffer_lighttrispvs; + +rtexturepool_t *r_shadow_texturepool; +rtexture_t *r_shadow_attenuationgradienttexture; +rtexture_t *r_shadow_attenuation2dtexture; +rtexture_t *r_shadow_attenuation3dtexture; +skinframe_t *r_shadow_lightcorona; +rtexture_t *r_shadow_shadowmap2dtexture; +rtexture_t *r_shadow_shadowmap2dcolortexture; +rtexture_t *r_shadow_shadowmapvsdcttexture; +int r_shadow_shadowmapsize; // changes for each light based on distance +int r_shadow_shadowmaplod; // changes for each light based on distance + +GLuint r_shadow_prepassgeometryfbo; +GLuint r_shadow_prepasslightingdiffusespecularfbo; +GLuint r_shadow_prepasslightingdiffusefbo; +int r_shadow_prepass_width; +int r_shadow_prepass_height; +rtexture_t *r_shadow_prepassgeometrydepthtexture; +rtexture_t *r_shadow_prepassgeometrydepthcolortexture; +rtexture_t *r_shadow_prepassgeometrynormalmaptexture; +rtexture_t *r_shadow_prepasslightingdiffusetexture; +rtexture_t *r_shadow_prepasslightingspeculartexture; + +// lights are reloaded when this changes +char r_shadow_mapname[MAX_QPATH]; + +// used only for light filters (cubemaps) +rtexturepool_t *r_shadow_filters_texturepool; + +static const GLenum r_shadow_prepasslightingdrawbuffers[2] = {GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT}; + +cvar_t r_shadow_bumpscale_basetexture = {0, "r_shadow_bumpscale_basetexture", "0", "generate fake bumpmaps from diffuse textures at this bumpyness, try 4 to match tenebrae, higher values increase depth, requires r_restart to take effect"}; +cvar_t r_shadow_bumpscale_bumpmap = {0, "r_shadow_bumpscale_bumpmap", "4", "what magnitude to interpret _bump.tga textures as, higher values increase depth, requires r_restart to take effect"}; +cvar_t r_shadow_debuglight = {0, "r_shadow_debuglight", "-1", "renders only one light, for level design purposes or debugging"}; +cvar_t r_shadow_deferred = {CVAR_SAVE, "r_shadow_deferred", "0", "uses image-based lighting instead of geometry-based lighting, the method used renders a depth image and a normalmap image, renders lights into separate diffuse and specular images, and then combines this into the normal rendering, requires r_shadow_shadowmapping"}; +cvar_t r_shadow_deferred_8bitrange = {CVAR_SAVE, "r_shadow_deferred_8bitrange", "4", "dynamic range of image-based lighting when using 32bit color (does not apply to fp)"}; +//cvar_t r_shadow_deferred_fp = {CVAR_SAVE, "r_shadow_deferred_fp", "0", "use 16bit (1) or 32bit (2) floating point for accumulation of image-based lighting"}; +cvar_t r_shadow_usebihculling = {0, "r_shadow_usebihculling", "1", "use BIH (Bounding Interval Hierarchy) for culling lit surfaces instead of BSP (Binary Space Partitioning)"}; +cvar_t r_shadow_usenormalmap = {CVAR_SAVE, "r_shadow_usenormalmap", "1", "enables use of directional shading on lights"}; +cvar_t r_shadow_gloss = {CVAR_SAVE, "r_shadow_gloss", "1", "0 disables gloss (specularity) rendering, 1 uses gloss if textures are found, 2 forces a flat metallic specular effect on everything without textures (similar to tenebrae)"}; +cvar_t r_shadow_gloss2intensity = {0, "r_shadow_gloss2intensity", "0.125", "how bright the forced flat gloss should look if r_shadow_gloss is 2"}; +cvar_t r_shadow_glossintensity = {0, "r_shadow_glossintensity", "1", "how bright textured glossmaps should look if r_shadow_gloss is 1 or 2"}; +cvar_t r_shadow_glossexponent = {0, "r_shadow_glossexponent", "32", "how 'sharp' the gloss should appear (specular power)"}; +cvar_t r_shadow_gloss2exponent = {0, "r_shadow_gloss2exponent", "32", "same as r_shadow_glossexponent but for forced gloss (gloss 2) surfaces"}; +cvar_t r_shadow_glossexact = {0, "r_shadow_glossexact", "0", "use exact reflection math for gloss (slightly slower, but should look a tad better)"}; +cvar_t r_shadow_lightattenuationdividebias = {0, "r_shadow_lightattenuationdividebias", "1", "changes attenuation texture generation"}; +cvar_t r_shadow_lightattenuationlinearscale = {0, "r_shadow_lightattenuationlinearscale", "2", "changes attenuation texture generation"}; +cvar_t r_shadow_lightintensityscale = {0, "r_shadow_lightintensityscale", "1", "renders all world lights brighter or darker"}; +cvar_t r_shadow_lightradiusscale = {0, "r_shadow_lightradiusscale", "1", "renders all world lights larger or smaller"}; +cvar_t r_shadow_projectdistance = {0, "r_shadow_projectdistance", "0", "how far to cast shadows"}; +cvar_t r_shadow_frontsidecasting = {0, "r_shadow_frontsidecasting", "1", "whether to cast shadows from illuminated triangles (front side of model) or unlit triangles (back side of model)"}; +cvar_t r_shadow_realtime_dlight = {CVAR_SAVE, "r_shadow_realtime_dlight", "1", "enables rendering of dynamic lights such as explosions and rocket light"}; +cvar_t r_shadow_realtime_dlight_shadows = {CVAR_SAVE, "r_shadow_realtime_dlight_shadows", "1", "enables rendering of shadows from dynamic lights"}; +cvar_t r_shadow_realtime_dlight_svbspculling = {0, "r_shadow_realtime_dlight_svbspculling", "0", "enables svbsp optimization on dynamic lights (very slow!)"}; +cvar_t r_shadow_realtime_dlight_portalculling = {0, "r_shadow_realtime_dlight_portalculling", "0", "enables portal optimization on dynamic lights (slow!)"}; +cvar_t r_shadow_realtime_world = {CVAR_SAVE, "r_shadow_realtime_world", "0", "enables rendering of full world lighting (whether loaded from the map, or a .rtlights file, or a .ent file, or a .lights file produced by hlight)"}; +cvar_t r_shadow_realtime_world_lightmaps = {CVAR_SAVE, "r_shadow_realtime_world_lightmaps", "0", "brightness to render lightmaps when using full world lighting, try 0.5 for a tenebrae-like appearance"}; +cvar_t r_shadow_realtime_world_shadows = {CVAR_SAVE, "r_shadow_realtime_world_shadows", "1", "enables rendering of shadows from world lights"}; +cvar_t r_shadow_realtime_world_compile = {0, "r_shadow_realtime_world_compile", "1", "enables compilation of world lights for higher performance rendering"}; +cvar_t r_shadow_realtime_world_compileshadow = {0, "r_shadow_realtime_world_compileshadow", "1", "enables compilation of shadows from world lights for higher performance rendering"}; +cvar_t r_shadow_realtime_world_compilesvbsp = {0, "r_shadow_realtime_world_compilesvbsp", "1", "enables svbsp optimization during compilation (slower than compileportalculling but more exact)"}; +cvar_t r_shadow_realtime_world_compileportalculling = {0, "r_shadow_realtime_world_compileportalculling", "1", "enables portal-based culling optimization during compilation (overrides compilesvbsp)"}; +cvar_t r_shadow_scissor = {0, "r_shadow_scissor", "1", "use scissor optimization of light rendering (restricts rendering to the portion of the screen affected by the light)"}; +cvar_t r_shadow_shadowmapping = {CVAR_SAVE, "r_shadow_shadowmapping", "1", "enables use of shadowmapping (depth texture sampling) instead of stencil shadow volumes, requires gl_fbo 1"}; +cvar_t r_shadow_shadowmapping_filterquality = {CVAR_SAVE, "r_shadow_shadowmapping_filterquality", "-1", "shadowmap filter modes: -1 = auto-select, 0 = no filtering, 1 = bilinear, 2 = bilinear 2x2 blur (fast), 3 = 3x3 blur (moderate), 4 = 4x4 blur (slow)"}; +cvar_t r_shadow_shadowmapping_depthbits = {CVAR_SAVE, "r_shadow_shadowmapping_depthbits", "24", "requested minimum shadowmap texture depth bits"}; +cvar_t r_shadow_shadowmapping_vsdct = {CVAR_SAVE, "r_shadow_shadowmapping_vsdct", "1", "enables use of virtual shadow depth cube texture"}; +cvar_t r_shadow_shadowmapping_minsize = {CVAR_SAVE, "r_shadow_shadowmapping_minsize", "32", "shadowmap size limit"}; +cvar_t r_shadow_shadowmapping_maxsize = {CVAR_SAVE, "r_shadow_shadowmapping_maxsize", "512", "shadowmap size limit"}; +cvar_t r_shadow_shadowmapping_precision = {CVAR_SAVE, "r_shadow_shadowmapping_precision", "1", "makes shadowmaps have a maximum resolution of this number of pixels per light source radius unit such that, for example, at precision 0.5 a light with radius 200 will have a maximum resolution of 100 pixels"}; +//cvar_t r_shadow_shadowmapping_lod_bias = {CVAR_SAVE, "r_shadow_shadowmapping_lod_bias", "16", "shadowmap size bias"}; +//cvar_t r_shadow_shadowmapping_lod_scale = {CVAR_SAVE, "r_shadow_shadowmapping_lod_scale", "128", "shadowmap size scaling parameter"}; +cvar_t r_shadow_shadowmapping_bordersize = {CVAR_SAVE, "r_shadow_shadowmapping_bordersize", "4", "shadowmap size bias for filtering"}; +cvar_t r_shadow_shadowmapping_nearclip = {CVAR_SAVE, "r_shadow_shadowmapping_nearclip", "1", "shadowmap nearclip in world units"}; +cvar_t r_shadow_shadowmapping_bias = {CVAR_SAVE, "r_shadow_shadowmapping_bias", "0.03", "shadowmap bias parameter (this is multiplied by nearclip * 1024 / lodsize)"}; +cvar_t r_shadow_shadowmapping_polygonfactor = {CVAR_SAVE, "r_shadow_shadowmapping_polygonfactor", "2", "slope-dependent shadowmapping bias"}; +cvar_t r_shadow_shadowmapping_polygonoffset = {CVAR_SAVE, "r_shadow_shadowmapping_polygonoffset", "0", "constant shadowmapping bias"}; +cvar_t r_shadow_sortsurfaces = {0, "r_shadow_sortsurfaces", "1", "improve performance by sorting illuminated surfaces by texture"}; +cvar_t r_shadow_polygonfactor = {0, "r_shadow_polygonfactor", "0", "how much to enlarge shadow volume polygons when rendering (should be 0!)"}; +cvar_t r_shadow_polygonoffset = {0, "r_shadow_polygonoffset", "1", "how much to push shadow volumes into the distance when rendering, to reduce chances of zfighting artifacts (should not be less than 0)"}; +cvar_t r_shadow_texture3d = {0, "r_shadow_texture3d", "1", "use 3D voxel textures for spherical attenuation rather than cylindrical (does not affect OpenGL 2.0 render path)"}; +cvar_t r_shadow_bouncegrid = {CVAR_SAVE, "r_shadow_bouncegrid", "0", "perform particle tracing for indirect lighting (Global Illumination / radiosity) using a 3D texture covering the scene, only active on levels with realtime lights active (r_shadow_realtime_world is usually required for these)"}; +cvar_t r_shadow_bouncegrid_bounceanglediffuse = {CVAR_SAVE, "r_shadow_bouncegrid_bounceanglediffuse", "0", "use random bounce direction rather than true reflection, makes some corner areas dark"}; +cvar_t r_shadow_bouncegrid_directionalshading = {CVAR_SAVE, "r_shadow_bouncegrid_directionalshading", "0", "use diffuse shading rather than ambient, 3D texture becomes 8x as many pixels to hold the additional data"}; +cvar_t r_shadow_bouncegrid_dlightparticlemultiplier = {CVAR_SAVE, "r_shadow_bouncegrid_dlightparticlemultiplier", "0", "if set to a high value like 16 this can make dlights look great, but 0 is recommended for performance reasons"}; +cvar_t r_shadow_bouncegrid_hitmodels = {CVAR_SAVE, "r_shadow_bouncegrid_hitmodels", "0", "enables hitting character model geometry (SLOW)"}; +cvar_t r_shadow_bouncegrid_includedirectlighting = {CVAR_SAVE, "r_shadow_bouncegrid_includedirectlighting", "0", "allows direct lighting to be recorded, not just indirect (gives an effect somewhat like r_shadow_realtime_world_lightmaps)"}; +cvar_t r_shadow_bouncegrid_intensity = {CVAR_SAVE, "r_shadow_bouncegrid_intensity", "4", "overall brightness of bouncegrid texture"}; +cvar_t r_shadow_bouncegrid_lightradiusscale = {CVAR_SAVE, "r_shadow_bouncegrid_lightradiusscale", "4", "particles stop at this fraction of light radius (can be more than 1)"}; +cvar_t r_shadow_bouncegrid_maxbounce = {CVAR_SAVE, "r_shadow_bouncegrid_maxbounce", "2", "maximum number of bounces for a particle (minimum is 0)"}; +cvar_t r_shadow_bouncegrid_particlebounceintensity = {CVAR_SAVE, "r_shadow_bouncegrid_particlebounceintensity", "1", "amount of energy carried over after each bounce, this is a multiplier of texture color and the result is clamped to 1 or less, to prevent adding energy on each bounce"}; +cvar_t r_shadow_bouncegrid_particleintensity = {CVAR_SAVE, "r_shadow_bouncegrid_particleintensity", "1", "brightness of particles contributing to bouncegrid texture"}; +cvar_t r_shadow_bouncegrid_photons = {CVAR_SAVE, "r_shadow_bouncegrid_photons", "2000", "total photons to shoot per update, divided proportionately between lights"}; +cvar_t r_shadow_bouncegrid_spacing = {CVAR_SAVE, "r_shadow_bouncegrid_spacing", "64", "unit size of bouncegrid pixel"}; +cvar_t r_shadow_bouncegrid_stablerandom = {CVAR_SAVE, "r_shadow_bouncegrid_stablerandom", "1", "make particle distribution consistent from frame to frame"}; +cvar_t r_shadow_bouncegrid_static = {CVAR_SAVE, "r_shadow_bouncegrid_static", "1", "use static radiosity solution (high quality) rather than dynamic (splotchy)"}; +cvar_t r_shadow_bouncegrid_static_directionalshading = {CVAR_SAVE, "r_shadow_bouncegrid_static_directionalshading", "1", "whether to use directionalshading when in static mode"}; +cvar_t r_shadow_bouncegrid_static_lightradiusscale = {CVAR_SAVE, "r_shadow_bouncegrid_static_lightradiusscale", "10", "particles stop at this fraction of light radius (can be more than 1) when in static mode"}; +cvar_t r_shadow_bouncegrid_static_maxbounce = {CVAR_SAVE, "r_shadow_bouncegrid_static_maxbounce", "5", "maximum number of bounces for a particle (minimum is 0) in static mode"}; +cvar_t r_shadow_bouncegrid_static_photons = {CVAR_SAVE, "r_shadow_bouncegrid_static_photons", "25000", "photons value to use when in static mode"}; +cvar_t r_shadow_bouncegrid_updateinterval = {CVAR_SAVE, "r_shadow_bouncegrid_updateinterval", "0", "update bouncegrid texture once per this many seconds, useful values are 0, 0.05, or 1000000"}; +cvar_t r_shadow_bouncegrid_x = {CVAR_SAVE, "r_shadow_bouncegrid_x", "64", "maximum texture size of bouncegrid on X axis"}; +cvar_t r_shadow_bouncegrid_y = {CVAR_SAVE, "r_shadow_bouncegrid_y", "64", "maximum texture size of bouncegrid on Y axis"}; +cvar_t r_shadow_bouncegrid_z = {CVAR_SAVE, "r_shadow_bouncegrid_z", "32", "maximum texture size of bouncegrid on Z axis"}; +cvar_t r_coronas = {CVAR_SAVE, "r_coronas", "1", "brightness of corona flare effects around certain lights, 0 disables corona effects"}; +cvar_t r_coronas_occlusionsizescale = {CVAR_SAVE, "r_coronas_occlusionsizescale", "0.1", "size of light source for corona occlusion checksum the proportion of hidden pixels controls corona intensity"}; +cvar_t r_coronas_occlusionquery = {CVAR_SAVE, "r_coronas_occlusionquery", "1", "use GL_ARB_occlusion_query extension if supported (fades coronas according to visibility)"}; +cvar_t gl_flashblend = {CVAR_SAVE, "gl_flashblend", "0", "render bright coronas for dynamic lights instead of actual lighting, fast but ugly"}; +cvar_t gl_ext_separatestencil = {0, "gl_ext_separatestencil", "1", "make use of OpenGL 2.0 glStencilOpSeparate or GL_ATI_separate_stencil extension"}; +cvar_t gl_ext_stenciltwoside = {0, "gl_ext_stenciltwoside", "1", "make use of GL_EXT_stenciltwoside extension (NVIDIA only)"}; +cvar_t r_editlights = {0, "r_editlights", "0", "enables .rtlights file editing mode"}; +cvar_t r_editlights_cursordistance = {0, "r_editlights_cursordistance", "1024", "maximum distance of cursor from eye"}; +cvar_t r_editlights_cursorpushback = {0, "r_editlights_cursorpushback", "0", "how far to pull the cursor back toward the eye"}; +cvar_t r_editlights_cursorpushoff = {0, "r_editlights_cursorpushoff", "4", "how far to push the cursor off the impacted surface"}; +cvar_t r_editlights_cursorgrid = {0, "r_editlights_cursorgrid", "4", "snaps cursor to this grid size"}; +cvar_t r_editlights_quakelightsizescale = {CVAR_SAVE, "r_editlights_quakelightsizescale", "1", "changes size of light entities loaded from a map"}; + +typedef struct r_shadow_bouncegrid_settings_s +{ + qboolean staticmode; + qboolean bounceanglediffuse; + qboolean directionalshading; + qboolean includedirectlighting; + float dlightparticlemultiplier; + qboolean hitmodels; + float lightradiusscale; + int maxbounce; + float particlebounceintensity; + float particleintensity; + int photons; + float spacing[3]; + int stablerandom; +} +r_shadow_bouncegrid_settings_t; + +r_shadow_bouncegrid_settings_t r_shadow_bouncegridsettings; +rtexture_t *r_shadow_bouncegridtexture; +matrix4x4_t r_shadow_bouncegridmatrix; +vec_t r_shadow_bouncegridintensity; +qboolean r_shadow_bouncegriddirectional; +static double r_shadow_bouncegridtime; +static int r_shadow_bouncegridresolution[3]; +static int r_shadow_bouncegridnumpixels; +static unsigned char *r_shadow_bouncegridpixels; +static float *r_shadow_bouncegridhighpixels; + +// note the table actually includes one more value, just to avoid the need to clamp the distance index due to minor math error +#define ATTENTABLESIZE 256 +// 1D gradient, 2D circle and 3D sphere attenuation textures +#define ATTEN1DSIZE 32 +#define ATTEN2DSIZE 64 +#define ATTEN3DSIZE 32 + +static float r_shadow_attendividebias; // r_shadow_lightattenuationdividebias +static float r_shadow_attenlinearscale; // r_shadow_lightattenuationlinearscale +static float r_shadow_attentable[ATTENTABLESIZE+1]; + +rtlight_t *r_shadow_compilingrtlight; +static memexpandablearray_t r_shadow_worldlightsarray; +dlight_t *r_shadow_selectedlight; +dlight_t r_shadow_bufferlight; +vec3_t r_editlights_cursorlocation; +qboolean r_editlights_lockcursor; + +extern int con_vislines; + +void R_Shadow_UncompileWorldLights(void); +void R_Shadow_ClearWorldLights(void); +void R_Shadow_SaveWorldLights(void); +void R_Shadow_LoadWorldLights(void); +void R_Shadow_LoadLightsFile(void); +void R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(void); +void R_Shadow_EditLights_Reload_f(void); +void R_Shadow_ValidateCvars(void); +static void R_Shadow_MakeTextures(void); + +#define EDLIGHTSPRSIZE 8 +skinframe_t *r_editlights_sprcursor; +skinframe_t *r_editlights_sprlight; +skinframe_t *r_editlights_sprnoshadowlight; +skinframe_t *r_editlights_sprcubemaplight; +skinframe_t *r_editlights_sprcubemapnoshadowlight; +skinframe_t *r_editlights_sprselection; + +void R_Shadow_SetShadowMode(void) +{ + r_shadow_shadowmapmaxsize = bound(1, r_shadow_shadowmapping_maxsize.integer, (int)vid.maxtexturesize_2d / 4); + r_shadow_shadowmapvsdct = r_shadow_shadowmapping_vsdct.integer != 0 && vid.renderpath == RENDERPATH_GL20; + r_shadow_shadowmapfilterquality = r_shadow_shadowmapping_filterquality.integer; + r_shadow_shadowmapdepthbits = r_shadow_shadowmapping_depthbits.integer; + r_shadow_shadowmapborder = bound(0, r_shadow_shadowmapping_bordersize.integer, 16); + r_shadow_shadowmaplod = -1; + r_shadow_shadowmapsize = 0; + r_shadow_shadowmapsampler = false; + r_shadow_shadowmappcf = 0; + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_STENCIL; + if ((r_shadow_shadowmapping.integer || r_shadow_deferred.integer) && vid.support.ext_framebuffer_object) + { + switch(vid.renderpath) + { + case RENDERPATH_GL20: + if(r_shadow_shadowmapfilterquality < 0) + { + if(vid.support.amd_texture_texture4 || vid.support.arb_texture_gather) + r_shadow_shadowmappcf = 1; + else if(strstr(gl_vendor, "NVIDIA") || strstr(gl_renderer, "Radeon HD")) + { + r_shadow_shadowmapsampler = vid.support.arb_shadow; + r_shadow_shadowmappcf = 1; + } + else if(strstr(gl_vendor, "ATI")) + r_shadow_shadowmappcf = 1; + else + r_shadow_shadowmapsampler = vid.support.arb_shadow; + } + else + { + switch (r_shadow_shadowmapfilterquality) + { + case 1: + r_shadow_shadowmapsampler = vid.support.arb_shadow; + break; + case 2: + r_shadow_shadowmapsampler = vid.support.arb_shadow; + r_shadow_shadowmappcf = 1; + break; + case 3: + r_shadow_shadowmappcf = 1; + break; + case 4: + r_shadow_shadowmappcf = 2; + break; + } + } + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_SHADOWMAP2D; + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + r_shadow_shadowmapsampler = false; + r_shadow_shadowmappcf = 1; + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_SHADOWMAP2D; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + } + } +} + +qboolean R_Shadow_ShadowMappingEnabled(void) +{ + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + return true; + default: + return false; + } +} + +void R_Shadow_FreeShadowMaps(void) +{ + R_Shadow_SetShadowMode(); + + R_Mesh_DestroyFramebufferObject(r_shadow_fbo2d); + + r_shadow_fbo2d = 0; + + if (r_shadow_shadowmap2dtexture) + R_FreeTexture(r_shadow_shadowmap2dtexture); + r_shadow_shadowmap2dtexture = NULL; + + if (r_shadow_shadowmap2dcolortexture) + R_FreeTexture(r_shadow_shadowmap2dcolortexture); + r_shadow_shadowmap2dcolortexture = NULL; + + if (r_shadow_shadowmapvsdcttexture) + R_FreeTexture(r_shadow_shadowmapvsdcttexture); + r_shadow_shadowmapvsdcttexture = NULL; +} + +void r_shadow_start(void) +{ + // allocate vertex processing arrays + r_shadow_bouncegridpixels = NULL; + r_shadow_bouncegridhighpixels = NULL; + r_shadow_bouncegridnumpixels = 0; + r_shadow_bouncegridtexture = NULL; + r_shadow_bouncegriddirectional = false; + r_shadow_attenuationgradienttexture = NULL; + r_shadow_attenuation2dtexture = NULL; + r_shadow_attenuation3dtexture = NULL; + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_STENCIL; + r_shadow_shadowmap2dtexture = NULL; + r_shadow_shadowmap2dcolortexture = NULL; + r_shadow_shadowmapvsdcttexture = NULL; + r_shadow_shadowmapmaxsize = 0; + r_shadow_shadowmapsize = 0; + r_shadow_shadowmaplod = 0; + r_shadow_shadowmapfilterquality = -1; + r_shadow_shadowmapdepthbits = 0; + r_shadow_shadowmapvsdct = false; + r_shadow_shadowmapsampler = false; + r_shadow_shadowmappcf = 0; + r_shadow_fbo2d = 0; + + R_Shadow_FreeShadowMaps(); + + r_shadow_texturepool = NULL; + r_shadow_filters_texturepool = NULL; + R_Shadow_ValidateCvars(); + R_Shadow_MakeTextures(); + maxshadowtriangles = 0; + shadowelements = NULL; + maxshadowvertices = 0; + shadowvertex3f = NULL; + maxvertexupdate = 0; + vertexupdate = NULL; + vertexremap = NULL; + vertexupdatenum = 0; + maxshadowmark = 0; + numshadowmark = 0; + shadowmark = NULL; + shadowmarklist = NULL; + shadowmarkcount = 0; + maxshadowsides = 0; + numshadowsides = 0; + shadowsides = NULL; + shadowsideslist = NULL; + r_shadow_buffer_numleafpvsbytes = 0; + r_shadow_buffer_visitingleafpvs = NULL; + r_shadow_buffer_leafpvs = NULL; + r_shadow_buffer_leaflist = NULL; + r_shadow_buffer_numsurfacepvsbytes = 0; + r_shadow_buffer_surfacepvs = NULL; + r_shadow_buffer_surfacelist = NULL; + r_shadow_buffer_surfacesides = NULL; + r_shadow_buffer_numshadowtrispvsbytes = 0; + r_shadow_buffer_shadowtrispvs = NULL; + r_shadow_buffer_numlighttrispvsbytes = 0; + r_shadow_buffer_lighttrispvs = NULL; + + r_shadow_usingdeferredprepass = false; + r_shadow_prepass_width = r_shadow_prepass_height = 0; +} + +static void R_Shadow_FreeDeferred(void); +void r_shadow_shutdown(void) +{ + CHECKGLERROR + R_Shadow_UncompileWorldLights(); + + R_Shadow_FreeShadowMaps(); + + r_shadow_usingdeferredprepass = false; + if (r_shadow_prepass_width) + R_Shadow_FreeDeferred(); + r_shadow_prepass_width = r_shadow_prepass_height = 0; + + CHECKGLERROR + r_shadow_bouncegridtexture = NULL; + r_shadow_bouncegridpixels = NULL; + r_shadow_bouncegridhighpixels = NULL; + r_shadow_bouncegridnumpixels = 0; + r_shadow_bouncegriddirectional = false; + r_shadow_attenuationgradienttexture = NULL; + r_shadow_attenuation2dtexture = NULL; + r_shadow_attenuation3dtexture = NULL; + R_FreeTexturePool(&r_shadow_texturepool); + R_FreeTexturePool(&r_shadow_filters_texturepool); + maxshadowtriangles = 0; + if (shadowelements) + Mem_Free(shadowelements); + shadowelements = NULL; + if (shadowvertex3f) + Mem_Free(shadowvertex3f); + shadowvertex3f = NULL; + maxvertexupdate = 0; + if (vertexupdate) + Mem_Free(vertexupdate); + vertexupdate = NULL; + if (vertexremap) + Mem_Free(vertexremap); + vertexremap = NULL; + vertexupdatenum = 0; + maxshadowmark = 0; + numshadowmark = 0; + if (shadowmark) + Mem_Free(shadowmark); + shadowmark = NULL; + if (shadowmarklist) + Mem_Free(shadowmarklist); + shadowmarklist = NULL; + shadowmarkcount = 0; + maxshadowsides = 0; + numshadowsides = 0; + if (shadowsides) + Mem_Free(shadowsides); + shadowsides = NULL; + if (shadowsideslist) + Mem_Free(shadowsideslist); + shadowsideslist = NULL; + r_shadow_buffer_numleafpvsbytes = 0; + if (r_shadow_buffer_visitingleafpvs) + Mem_Free(r_shadow_buffer_visitingleafpvs); + r_shadow_buffer_visitingleafpvs = NULL; + if (r_shadow_buffer_leafpvs) + Mem_Free(r_shadow_buffer_leafpvs); + r_shadow_buffer_leafpvs = NULL; + if (r_shadow_buffer_leaflist) + Mem_Free(r_shadow_buffer_leaflist); + r_shadow_buffer_leaflist = NULL; + r_shadow_buffer_numsurfacepvsbytes = 0; + if (r_shadow_buffer_surfacepvs) + Mem_Free(r_shadow_buffer_surfacepvs); + r_shadow_buffer_surfacepvs = NULL; + if (r_shadow_buffer_surfacelist) + Mem_Free(r_shadow_buffer_surfacelist); + r_shadow_buffer_surfacelist = NULL; + if (r_shadow_buffer_surfacesides) + Mem_Free(r_shadow_buffer_surfacesides); + r_shadow_buffer_surfacesides = NULL; + r_shadow_buffer_numshadowtrispvsbytes = 0; + if (r_shadow_buffer_shadowtrispvs) + Mem_Free(r_shadow_buffer_shadowtrispvs); + r_shadow_buffer_numlighttrispvsbytes = 0; + if (r_shadow_buffer_lighttrispvs) + Mem_Free(r_shadow_buffer_lighttrispvs); +} + +void r_shadow_newmap(void) +{ + if (r_shadow_bouncegridtexture) R_FreeTexture(r_shadow_bouncegridtexture);r_shadow_bouncegridtexture = NULL; + if (r_shadow_lightcorona) R_SkinFrame_MarkUsed(r_shadow_lightcorona); + if (r_editlights_sprcursor) R_SkinFrame_MarkUsed(r_editlights_sprcursor); + if (r_editlights_sprlight) R_SkinFrame_MarkUsed(r_editlights_sprlight); + if (r_editlights_sprnoshadowlight) R_SkinFrame_MarkUsed(r_editlights_sprnoshadowlight); + if (r_editlights_sprcubemaplight) R_SkinFrame_MarkUsed(r_editlights_sprcubemaplight); + if (r_editlights_sprcubemapnoshadowlight) R_SkinFrame_MarkUsed(r_editlights_sprcubemapnoshadowlight); + if (r_editlights_sprselection) R_SkinFrame_MarkUsed(r_editlights_sprselection); + if (strncmp(cl.worldname, r_shadow_mapname, sizeof(r_shadow_mapname))) + R_Shadow_EditLights_Reload_f(); +} + +void R_Shadow_Init(void) +{ + Cvar_RegisterVariable(&r_shadow_bumpscale_basetexture); + Cvar_RegisterVariable(&r_shadow_bumpscale_bumpmap); + Cvar_RegisterVariable(&r_shadow_usebihculling); + Cvar_RegisterVariable(&r_shadow_usenormalmap); + Cvar_RegisterVariable(&r_shadow_debuglight); + Cvar_RegisterVariable(&r_shadow_deferred); + Cvar_RegisterVariable(&r_shadow_deferred_8bitrange); +// Cvar_RegisterVariable(&r_shadow_deferred_fp); + Cvar_RegisterVariable(&r_shadow_gloss); + Cvar_RegisterVariable(&r_shadow_gloss2intensity); + Cvar_RegisterVariable(&r_shadow_glossintensity); + Cvar_RegisterVariable(&r_shadow_glossexponent); + Cvar_RegisterVariable(&r_shadow_gloss2exponent); + Cvar_RegisterVariable(&r_shadow_glossexact); + Cvar_RegisterVariable(&r_shadow_lightattenuationdividebias); + Cvar_RegisterVariable(&r_shadow_lightattenuationlinearscale); + Cvar_RegisterVariable(&r_shadow_lightintensityscale); + Cvar_RegisterVariable(&r_shadow_lightradiusscale); + Cvar_RegisterVariable(&r_shadow_projectdistance); + Cvar_RegisterVariable(&r_shadow_frontsidecasting); + Cvar_RegisterVariable(&r_shadow_realtime_dlight); + Cvar_RegisterVariable(&r_shadow_realtime_dlight_shadows); + Cvar_RegisterVariable(&r_shadow_realtime_dlight_svbspculling); + Cvar_RegisterVariable(&r_shadow_realtime_dlight_portalculling); + Cvar_RegisterVariable(&r_shadow_realtime_world); + Cvar_RegisterVariable(&r_shadow_realtime_world_lightmaps); + Cvar_RegisterVariable(&r_shadow_realtime_world_shadows); + Cvar_RegisterVariable(&r_shadow_realtime_world_compile); + Cvar_RegisterVariable(&r_shadow_realtime_world_compileshadow); + Cvar_RegisterVariable(&r_shadow_realtime_world_compilesvbsp); + Cvar_RegisterVariable(&r_shadow_realtime_world_compileportalculling); + Cvar_RegisterVariable(&r_shadow_scissor); + Cvar_RegisterVariable(&r_shadow_shadowmapping); + Cvar_RegisterVariable(&r_shadow_shadowmapping_vsdct); + Cvar_RegisterVariable(&r_shadow_shadowmapping_filterquality); + Cvar_RegisterVariable(&r_shadow_shadowmapping_depthbits); + Cvar_RegisterVariable(&r_shadow_shadowmapping_precision); + Cvar_RegisterVariable(&r_shadow_shadowmapping_maxsize); + Cvar_RegisterVariable(&r_shadow_shadowmapping_minsize); +// Cvar_RegisterVariable(&r_shadow_shadowmapping_lod_bias); +// Cvar_RegisterVariable(&r_shadow_shadowmapping_lod_scale); + Cvar_RegisterVariable(&r_shadow_shadowmapping_bordersize); + Cvar_RegisterVariable(&r_shadow_shadowmapping_nearclip); + Cvar_RegisterVariable(&r_shadow_shadowmapping_bias); + Cvar_RegisterVariable(&r_shadow_shadowmapping_polygonfactor); + Cvar_RegisterVariable(&r_shadow_shadowmapping_polygonoffset); + Cvar_RegisterVariable(&r_shadow_sortsurfaces); + Cvar_RegisterVariable(&r_shadow_polygonfactor); + Cvar_RegisterVariable(&r_shadow_polygonoffset); + Cvar_RegisterVariable(&r_shadow_texture3d); + Cvar_RegisterVariable(&r_shadow_bouncegrid); + Cvar_RegisterVariable(&r_shadow_bouncegrid_bounceanglediffuse); + Cvar_RegisterVariable(&r_shadow_bouncegrid_directionalshading); + Cvar_RegisterVariable(&r_shadow_bouncegrid_dlightparticlemultiplier); + Cvar_RegisterVariable(&r_shadow_bouncegrid_hitmodels); + Cvar_RegisterVariable(&r_shadow_bouncegrid_includedirectlighting); + Cvar_RegisterVariable(&r_shadow_bouncegrid_intensity); + Cvar_RegisterVariable(&r_shadow_bouncegrid_lightradiusscale); + Cvar_RegisterVariable(&r_shadow_bouncegrid_maxbounce); + Cvar_RegisterVariable(&r_shadow_bouncegrid_particlebounceintensity); + Cvar_RegisterVariable(&r_shadow_bouncegrid_particleintensity); + Cvar_RegisterVariable(&r_shadow_bouncegrid_photons); + Cvar_RegisterVariable(&r_shadow_bouncegrid_spacing); + Cvar_RegisterVariable(&r_shadow_bouncegrid_stablerandom); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static_directionalshading); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static_lightradiusscale); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static_maxbounce); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static_photons); + Cvar_RegisterVariable(&r_shadow_bouncegrid_updateinterval); + Cvar_RegisterVariable(&r_shadow_bouncegrid_x); + Cvar_RegisterVariable(&r_shadow_bouncegrid_y); + Cvar_RegisterVariable(&r_shadow_bouncegrid_z); + Cvar_RegisterVariable(&r_coronas); + Cvar_RegisterVariable(&r_coronas_occlusionsizescale); + Cvar_RegisterVariable(&r_coronas_occlusionquery); + Cvar_RegisterVariable(&gl_flashblend); + Cvar_RegisterVariable(&gl_ext_separatestencil); + Cvar_RegisterVariable(&gl_ext_stenciltwoside); + R_Shadow_EditLights_Init(); + Mem_ExpandableArray_NewArray(&r_shadow_worldlightsarray, r_main_mempool, sizeof(dlight_t), 128); + maxshadowtriangles = 0; + shadowelements = NULL; + maxshadowvertices = 0; + shadowvertex3f = NULL; + maxvertexupdate = 0; + vertexupdate = NULL; + vertexremap = NULL; + vertexupdatenum = 0; + maxshadowmark = 0; + numshadowmark = 0; + shadowmark = NULL; + shadowmarklist = NULL; + shadowmarkcount = 0; + maxshadowsides = 0; + numshadowsides = 0; + shadowsides = NULL; + shadowsideslist = NULL; + r_shadow_buffer_numleafpvsbytes = 0; + r_shadow_buffer_visitingleafpvs = NULL; + r_shadow_buffer_leafpvs = NULL; + r_shadow_buffer_leaflist = NULL; + r_shadow_buffer_numsurfacepvsbytes = 0; + r_shadow_buffer_surfacepvs = NULL; + r_shadow_buffer_surfacelist = NULL; + r_shadow_buffer_surfacesides = NULL; + r_shadow_buffer_shadowtrispvs = NULL; + r_shadow_buffer_lighttrispvs = NULL; + R_RegisterModule("R_Shadow", r_shadow_start, r_shadow_shutdown, r_shadow_newmap, NULL, NULL); +} + +matrix4x4_t matrix_attenuationxyz = +{ + { + {0.5, 0.0, 0.0, 0.5}, + {0.0, 0.5, 0.0, 0.5}, + {0.0, 0.0, 0.5, 0.5}, + {0.0, 0.0, 0.0, 1.0} + } +}; + +matrix4x4_t matrix_attenuationz = +{ + { + {0.0, 0.0, 0.5, 0.5}, + {0.0, 0.0, 0.0, 0.5}, + {0.0, 0.0, 0.0, 0.5}, + {0.0, 0.0, 0.0, 1.0} + } +}; + +void R_Shadow_ResizeShadowArrays(int numvertices, int numtriangles, int vertscale, int triscale) +{ + numvertices = ((numvertices + 255) & ~255) * vertscale; + numtriangles = ((numtriangles + 255) & ~255) * triscale; + // make sure shadowelements is big enough for this volume + if (maxshadowtriangles < numtriangles) + { + maxshadowtriangles = numtriangles; + if (shadowelements) + Mem_Free(shadowelements); + shadowelements = (int *)Mem_Alloc(r_main_mempool, maxshadowtriangles * sizeof(int[3])); + } + // make sure shadowvertex3f is big enough for this volume + if (maxshadowvertices < numvertices) + { + maxshadowvertices = numvertices; + if (shadowvertex3f) + Mem_Free(shadowvertex3f); + shadowvertex3f = (float *)Mem_Alloc(r_main_mempool, maxshadowvertices * sizeof(float[3])); + } +} + +static void R_Shadow_EnlargeLeafSurfaceTrisBuffer(int numleafs, int numsurfaces, int numshadowtriangles, int numlighttriangles) +{ + int numleafpvsbytes = (((numleafs + 7) >> 3) + 255) & ~255; + int numsurfacepvsbytes = (((numsurfaces + 7) >> 3) + 255) & ~255; + int numshadowtrispvsbytes = (((numshadowtriangles + 7) >> 3) + 255) & ~255; + int numlighttrispvsbytes = (((numlighttriangles + 7) >> 3) + 255) & ~255; + if (r_shadow_buffer_numleafpvsbytes < numleafpvsbytes) + { + if (r_shadow_buffer_visitingleafpvs) + Mem_Free(r_shadow_buffer_visitingleafpvs); + if (r_shadow_buffer_leafpvs) + Mem_Free(r_shadow_buffer_leafpvs); + if (r_shadow_buffer_leaflist) + Mem_Free(r_shadow_buffer_leaflist); + r_shadow_buffer_numleafpvsbytes = numleafpvsbytes; + r_shadow_buffer_visitingleafpvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes); + r_shadow_buffer_leafpvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes); + r_shadow_buffer_leaflist = (int *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes * 8 * sizeof(*r_shadow_buffer_leaflist)); + } + if (r_shadow_buffer_numsurfacepvsbytes < numsurfacepvsbytes) + { + if (r_shadow_buffer_surfacepvs) + Mem_Free(r_shadow_buffer_surfacepvs); + if (r_shadow_buffer_surfacelist) + Mem_Free(r_shadow_buffer_surfacelist); + if (r_shadow_buffer_surfacesides) + Mem_Free(r_shadow_buffer_surfacesides); + r_shadow_buffer_numsurfacepvsbytes = numsurfacepvsbytes; + r_shadow_buffer_surfacepvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes); + r_shadow_buffer_surfacelist = (int *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes * 8 * sizeof(*r_shadow_buffer_surfacelist)); + r_shadow_buffer_surfacesides = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes * 8 * sizeof(*r_shadow_buffer_surfacelist)); + } + if (r_shadow_buffer_numshadowtrispvsbytes < numshadowtrispvsbytes) + { + if (r_shadow_buffer_shadowtrispvs) + Mem_Free(r_shadow_buffer_shadowtrispvs); + r_shadow_buffer_numshadowtrispvsbytes = numshadowtrispvsbytes; + r_shadow_buffer_shadowtrispvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numshadowtrispvsbytes); + } + if (r_shadow_buffer_numlighttrispvsbytes < numlighttrispvsbytes) + { + if (r_shadow_buffer_lighttrispvs) + Mem_Free(r_shadow_buffer_lighttrispvs); + r_shadow_buffer_numlighttrispvsbytes = numlighttrispvsbytes; + r_shadow_buffer_lighttrispvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numlighttrispvsbytes); + } +} + +void R_Shadow_PrepareShadowMark(int numtris) +{ + // make sure shadowmark is big enough for this volume + if (maxshadowmark < numtris) + { + maxshadowmark = numtris; + if (shadowmark) + Mem_Free(shadowmark); + if (shadowmarklist) + Mem_Free(shadowmarklist); + shadowmark = (int *)Mem_Alloc(r_main_mempool, maxshadowmark * sizeof(*shadowmark)); + shadowmarklist = (int *)Mem_Alloc(r_main_mempool, maxshadowmark * sizeof(*shadowmarklist)); + shadowmarkcount = 0; + } + shadowmarkcount++; + // if shadowmarkcount wrapped we clear the array and adjust accordingly + if (shadowmarkcount == 0) + { + shadowmarkcount = 1; + memset(shadowmark, 0, maxshadowmark * sizeof(*shadowmark)); + } + numshadowmark = 0; +} + +void R_Shadow_PrepareShadowSides(int numtris) +{ + if (maxshadowsides < numtris) + { + maxshadowsides = numtris; + if (shadowsides) + Mem_Free(shadowsides); + if (shadowsideslist) + Mem_Free(shadowsideslist); + shadowsides = (unsigned char *)Mem_Alloc(r_main_mempool, maxshadowsides * sizeof(*shadowsides)); + shadowsideslist = (int *)Mem_Alloc(r_main_mempool, maxshadowsides * sizeof(*shadowsideslist)); + } + numshadowsides = 0; +} + +static int R_Shadow_ConstructShadowVolume_ZFail(int innumvertices, int innumtris, const int *inelement3i, const int *inneighbor3i, const float *invertex3f, int *outnumvertices, int *outelement3i, float *outvertex3f, const float *projectorigin, const float *projectdirection, float projectdistance, int numshadowmarktris, const int *shadowmarktris) +{ + int i, j; + int outtriangles = 0, outvertices = 0; + const int *element; + const float *vertex; + float ratio, direction[3], projectvector[3]; + + if (projectdirection) + VectorScale(projectdirection, projectdistance, projectvector); + else + VectorClear(projectvector); + + // create the vertices + if (projectdirection) + { + for (i = 0;i < numshadowmarktris;i++) + { + element = inelement3i + shadowmarktris[i] * 3; + for (j = 0;j < 3;j++) + { + if (vertexupdate[element[j]] != vertexupdatenum) + { + vertexupdate[element[j]] = vertexupdatenum; + vertexremap[element[j]] = outvertices; + vertex = invertex3f + element[j] * 3; + // project one copy of the vertex according to projectvector + VectorCopy(vertex, outvertex3f); + VectorAdd(vertex, projectvector, (outvertex3f + 3)); + outvertex3f += 6; + outvertices += 2; + } + } + } + } + else + { + for (i = 0;i < numshadowmarktris;i++) + { + element = inelement3i + shadowmarktris[i] * 3; + for (j = 0;j < 3;j++) + { + if (vertexupdate[element[j]] != vertexupdatenum) + { + vertexupdate[element[j]] = vertexupdatenum; + vertexremap[element[j]] = outvertices; + vertex = invertex3f + element[j] * 3; + // project one copy of the vertex to the sphere radius of the light + // (FIXME: would projecting it to the light box be better?) + VectorSubtract(vertex, projectorigin, direction); + ratio = projectdistance / VectorLength(direction); + VectorCopy(vertex, outvertex3f); + VectorMA(projectorigin, ratio, direction, (outvertex3f + 3)); + outvertex3f += 6; + outvertices += 2; + } + } + } + } + + if (r_shadow_frontsidecasting.integer) + { + for (i = 0;i < numshadowmarktris;i++) + { + int remappedelement[3]; + int markindex; + const int *neighbortriangle; + + markindex = shadowmarktris[i] * 3; + element = inelement3i + markindex; + neighbortriangle = inneighbor3i + markindex; + // output the front and back triangles + outelement3i[0] = vertexremap[element[0]]; + outelement3i[1] = vertexremap[element[1]]; + outelement3i[2] = vertexremap[element[2]]; + outelement3i[3] = vertexremap[element[2]] + 1; + outelement3i[4] = vertexremap[element[1]] + 1; + outelement3i[5] = vertexremap[element[0]] + 1; + + outelement3i += 6; + outtriangles += 2; + // output the sides (facing outward from this triangle) + if (shadowmark[neighbortriangle[0]] != shadowmarkcount) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[1] = vertexremap[element[1]]; + outelement3i[0] = remappedelement[1]; + outelement3i[1] = remappedelement[0]; + outelement3i[2] = remappedelement[0] + 1; + outelement3i[3] = remappedelement[1]; + outelement3i[4] = remappedelement[0] + 1; + outelement3i[5] = remappedelement[1] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (shadowmark[neighbortriangle[1]] != shadowmarkcount) + { + remappedelement[1] = vertexremap[element[1]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[2]; + outelement3i[1] = remappedelement[1]; + outelement3i[2] = remappedelement[1] + 1; + outelement3i[3] = remappedelement[2]; + outelement3i[4] = remappedelement[1] + 1; + outelement3i[5] = remappedelement[2] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (shadowmark[neighbortriangle[2]] != shadowmarkcount) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[0]; + outelement3i[1] = remappedelement[2]; + outelement3i[2] = remappedelement[2] + 1; + outelement3i[3] = remappedelement[0]; + outelement3i[4] = remappedelement[2] + 1; + outelement3i[5] = remappedelement[0] + 1; + + outelement3i += 6; + outtriangles += 2; + } + } + } + else + { + for (i = 0;i < numshadowmarktris;i++) + { + int remappedelement[3]; + int markindex; + const int *neighbortriangle; + + markindex = shadowmarktris[i] * 3; + element = inelement3i + markindex; + neighbortriangle = inneighbor3i + markindex; + // output the front and back triangles + outelement3i[0] = vertexremap[element[2]]; + outelement3i[1] = vertexremap[element[1]]; + outelement3i[2] = vertexremap[element[0]]; + outelement3i[3] = vertexremap[element[0]] + 1; + outelement3i[4] = vertexremap[element[1]] + 1; + outelement3i[5] = vertexremap[element[2]] + 1; + + outelement3i += 6; + outtriangles += 2; + // output the sides (facing outward from this triangle) + if (shadowmark[neighbortriangle[0]] != shadowmarkcount) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[1] = vertexremap[element[1]]; + outelement3i[0] = remappedelement[0]; + outelement3i[1] = remappedelement[1]; + outelement3i[2] = remappedelement[1] + 1; + outelement3i[3] = remappedelement[0]; + outelement3i[4] = remappedelement[1] + 1; + outelement3i[5] = remappedelement[0] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (shadowmark[neighbortriangle[1]] != shadowmarkcount) + { + remappedelement[1] = vertexremap[element[1]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[1]; + outelement3i[1] = remappedelement[2]; + outelement3i[2] = remappedelement[2] + 1; + outelement3i[3] = remappedelement[1]; + outelement3i[4] = remappedelement[2] + 1; + outelement3i[5] = remappedelement[1] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (shadowmark[neighbortriangle[2]] != shadowmarkcount) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[2]; + outelement3i[1] = remappedelement[0]; + outelement3i[2] = remappedelement[0] + 1; + outelement3i[3] = remappedelement[2]; + outelement3i[4] = remappedelement[0] + 1; + outelement3i[5] = remappedelement[2] + 1; + + outelement3i += 6; + outtriangles += 2; + } + } + } + if (outnumvertices) + *outnumvertices = outvertices; + return outtriangles; +} + +static int R_Shadow_ConstructShadowVolume_ZPass(int innumvertices, int innumtris, const int *inelement3i, const int *inneighbor3i, const float *invertex3f, int *outnumvertices, int *outelement3i, float *outvertex3f, const float *projectorigin, const float *projectdirection, float projectdistance, int numshadowmarktris, const int *shadowmarktris) +{ + int i, j, k; + int outtriangles = 0, outvertices = 0; + const int *element; + const float *vertex; + float ratio, direction[3], projectvector[3]; + qboolean side[4]; + + if (projectdirection) + VectorScale(projectdirection, projectdistance, projectvector); + else + VectorClear(projectvector); + + for (i = 0;i < numshadowmarktris;i++) + { + int remappedelement[3]; + int markindex; + const int *neighbortriangle; + + markindex = shadowmarktris[i] * 3; + neighbortriangle = inneighbor3i + markindex; + side[0] = shadowmark[neighbortriangle[0]] == shadowmarkcount; + side[1] = shadowmark[neighbortriangle[1]] == shadowmarkcount; + side[2] = shadowmark[neighbortriangle[2]] == shadowmarkcount; + if (side[0] + side[1] + side[2] == 0) + continue; + + side[3] = side[0]; + element = inelement3i + markindex; + + // create the vertices + for (j = 0;j < 3;j++) + { + if (side[j] + side[j+1] == 0) + continue; + k = element[j]; + if (vertexupdate[k] != vertexupdatenum) + { + vertexupdate[k] = vertexupdatenum; + vertexremap[k] = outvertices; + vertex = invertex3f + k * 3; + VectorCopy(vertex, outvertex3f); + if (projectdirection) + { + // project one copy of the vertex according to projectvector + VectorAdd(vertex, projectvector, (outvertex3f + 3)); + } + else + { + // project one copy of the vertex to the sphere radius of the light + // (FIXME: would projecting it to the light box be better?) + VectorSubtract(vertex, projectorigin, direction); + ratio = projectdistance / VectorLength(direction); + VectorMA(projectorigin, ratio, direction, (outvertex3f + 3)); + } + outvertex3f += 6; + outvertices += 2; + } + } + + // output the sides (facing outward from this triangle) + if (!side[0]) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[1] = vertexremap[element[1]]; + outelement3i[0] = remappedelement[1]; + outelement3i[1] = remappedelement[0]; + outelement3i[2] = remappedelement[0] + 1; + outelement3i[3] = remappedelement[1]; + outelement3i[4] = remappedelement[0] + 1; + outelement3i[5] = remappedelement[1] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (!side[1]) + { + remappedelement[1] = vertexremap[element[1]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[2]; + outelement3i[1] = remappedelement[1]; + outelement3i[2] = remappedelement[1] + 1; + outelement3i[3] = remappedelement[2]; + outelement3i[4] = remappedelement[1] + 1; + outelement3i[5] = remappedelement[2] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (!side[2]) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[0]; + outelement3i[1] = remappedelement[2]; + outelement3i[2] = remappedelement[2] + 1; + outelement3i[3] = remappedelement[0]; + outelement3i[4] = remappedelement[2] + 1; + outelement3i[5] = remappedelement[0] + 1; + + outelement3i += 6; + outtriangles += 2; + } + } + if (outnumvertices) + *outnumvertices = outvertices; + return outtriangles; +} + +void R_Shadow_MarkVolumeFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs) +{ + int t, tend; + const int *e; + const float *v[3]; + float normal[3]; + if (!BoxesOverlap(lightmins, lightmaxs, surfacemins, surfacemaxs)) + return; + tend = firsttriangle + numtris; + if (BoxInsideBox(surfacemins, surfacemaxs, lightmins, lightmaxs)) + { + // surface box entirely inside light box, no box cull + if (projectdirection) + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + TriangleNormal(invertex3f + e[0] * 3, invertex3f + e[1] * 3, invertex3f + e[2] * 3, normal); + if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0)) + shadowmarklist[numshadowmark++] = t; + } + } + else + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, invertex3f + e[0] * 3, invertex3f + e[1] * 3, invertex3f + e[2] * 3)) + shadowmarklist[numshadowmark++] = t; + } + } + else + { + // surface box not entirely inside light box, cull each triangle + if (projectdirection) + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3; + v[1] = invertex3f + e[1] * 3; + v[2] = invertex3f + e[2] * 3; + TriangleNormal(v[0], v[1], v[2], normal); + if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0) + && TriangleOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) + shadowmarklist[numshadowmark++] = t; + } + } + else + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3; + v[1] = invertex3f + e[1] * 3; + v[2] = invertex3f + e[2] * 3; + if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2]) + && TriangleOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) + shadowmarklist[numshadowmark++] = t; + } + } + } +} + +qboolean R_Shadow_UseZPass(vec3_t mins, vec3_t maxs) +{ +#if 1 + return false; +#else + if (r_shadow_compilingrtlight || !r_shadow_frontsidecasting.integer || !r_shadow_usezpassifpossible.integer) + return false; + // check if the shadow volume intersects the near plane + // + // a ray between the eye and light origin may intersect the caster, + // indicating that the shadow may touch the eye location, however we must + // test the near plane (a polygon), not merely the eye location, so it is + // easiest to enlarge the caster bounding shape slightly for this. + // TODO + return true; +#endif +} + +void R_Shadow_VolumeFromList(int numverts, int numtris, const float *invertex3f, const int *elements, const int *neighbors, const vec3_t projectorigin, const vec3_t projectdirection, float projectdistance, int nummarktris, const int *marktris, vec3_t trismins, vec3_t trismaxs) +{ + int i, tris, outverts; + if (projectdistance < 0.1) + { + Con_Printf("R_Shadow_Volume: projectdistance %f\n", projectdistance); + return; + } + if (!numverts || !nummarktris) + return; + // make sure shadowelements is big enough for this volume + if (maxshadowtriangles < nummarktris*8 || maxshadowvertices < numverts*2) + R_Shadow_ResizeShadowArrays(numverts, nummarktris, 2, 8); + + if (maxvertexupdate < numverts) + { + maxvertexupdate = numverts; + if (vertexupdate) + Mem_Free(vertexupdate); + if (vertexremap) + Mem_Free(vertexremap); + vertexupdate = (int *)Mem_Alloc(r_main_mempool, maxvertexupdate * sizeof(int)); + vertexremap = (int *)Mem_Alloc(r_main_mempool, maxvertexupdate * sizeof(int)); + vertexupdatenum = 0; + } + vertexupdatenum++; + if (vertexupdatenum == 0) + { + vertexupdatenum = 1; + memset(vertexupdate, 0, maxvertexupdate * sizeof(int)); + memset(vertexremap, 0, maxvertexupdate * sizeof(int)); + } + + for (i = 0;i < nummarktris;i++) + shadowmark[marktris[i]] = shadowmarkcount; + + if (r_shadow_compilingrtlight) + { + // if we're compiling an rtlight, capture the mesh + //tris = R_Shadow_ConstructShadowVolume_ZPass(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + //Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zpass, NULL, NULL, NULL, shadowvertex3f, NULL, NULL, NULL, NULL, tris, shadowelements); + tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zfail, NULL, NULL, NULL, shadowvertex3f, NULL, NULL, NULL, NULL, tris, shadowelements); + } + else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_VISIBLEVOLUMES) + { + tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + R_Mesh_PrepareVertices_Vertex3f(outverts, shadowvertex3f, NULL); + R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); + } + else + { + // decide which type of shadow to generate and set stencil mode + R_Shadow_RenderMode_StencilShadowVolumes(R_Shadow_UseZPass(trismins, trismaxs)); + // generate the sides or a solid volume, depending on type + if (r_shadow_rendermode >= R_SHADOW_RENDERMODE_ZPASS_STENCIL && r_shadow_rendermode <= R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE) + tris = R_Shadow_ConstructShadowVolume_ZPass(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + else + tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + r_refdef.stats.lights_dynamicshadowtriangles += tris; + r_refdef.stats.lights_shadowtriangles += tris; + if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZPASS_STENCIL) + { + // increment stencil if frontface is infront of depthbuffer + GL_CullFace(r_refdef.view.cullface_front); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, 128, 255); + R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); + // decrement stencil if backface is infront of depthbuffer + GL_CullFace(r_refdef.view.cullface_back); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_ALWAYS, 128, 255); + } + else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZFAIL_STENCIL) + { + // decrement stencil if backface is behind depthbuffer + GL_CullFace(r_refdef.view.cullface_front); + R_SetStencil(true, 255, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, 128, 255); + R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); + // increment stencil if frontface is behind depthbuffer + GL_CullFace(r_refdef.view.cullface_back); + R_SetStencil(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_ALWAYS, 128, 255); + } + R_Mesh_PrepareVertices_Vertex3f(outverts, shadowvertex3f, NULL); + R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); + } +} + +int R_Shadow_CalcTriangleSideMask(const vec3_t p1, const vec3_t p2, const vec3_t p3, float bias) +{ + // p1, p2, p3 are in the cubemap's local coordinate system + // bias = border/(size - border) + int mask = 0x3F; + + float dp1 = p1[0] + p1[1], dn1 = p1[0] - p1[1], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = p2[0] + p2[1], dn2 = p2[0] - p2[1], ap2 = fabs(dp2), an2 = fabs(dn2), + dp3 = p3[0] + p3[1], dn3 = p3[0] - p3[1], ap3 = fabs(dp3), an3 = fabs(dn3); + if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) + mask &= (3<<4) + | (dp1 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) + | (dp2 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) + | (dp3 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) + mask &= (3<<4) + | (dn1 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) + | (dn2 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) + | (dn3 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + + dp1 = p1[1] + p1[2], dn1 = p1[1] - p1[2], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = p2[1] + p2[2], dn2 = p2[1] - p2[2], ap2 = fabs(dp2), an2 = fabs(dn2), + dp3 = p3[1] + p3[2], dn3 = p3[1] - p3[2], ap3 = fabs(dp3), an3 = fabs(dn3); + if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) + mask &= (3<<0) + | (dp1 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) + | (dp2 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) + | (dp3 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) + mask &= (3<<0) + | (dn1 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) + | (dn2 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) + | (dn3 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + + dp1 = p1[2] + p1[0], dn1 = p1[2] - p1[0], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = p2[2] + p2[0], dn2 = p2[2] - p2[0], ap2 = fabs(dp2), an2 = fabs(dn2), + dp3 = p3[2] + p3[0], dn3 = p3[2] - p3[0], ap3 = fabs(dp3), an3 = fabs(dn3); + if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) + mask &= (3<<2) + | (dp1 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) + | (dp2 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) + | (dp3 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) + mask &= (3<<2) + | (dn1 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) + | (dn2 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) + | (dn3 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + + return mask; +} + +int R_Shadow_CalcBBoxSideMask(const vec3_t mins, const vec3_t maxs, const matrix4x4_t *worldtolight, const matrix4x4_t *radiustolight, float bias) +{ + vec3_t center, radius, lightcenter, lightradius, pmin, pmax; + float dp1, dn1, ap1, an1, dp2, dn2, ap2, an2; + int mask = 0x3F; + + VectorSubtract(maxs, mins, radius); + VectorScale(radius, 0.5f, radius); + VectorAdd(mins, radius, center); + Matrix4x4_Transform(worldtolight, center, lightcenter); + Matrix4x4_Transform3x3(radiustolight, radius, lightradius); + VectorSubtract(lightcenter, lightradius, pmin); + VectorAdd(lightcenter, lightradius, pmax); + + dp1 = pmax[0] + pmax[1], dn1 = pmax[0] - pmin[1], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = pmin[0] + pmin[1], dn2 = pmin[0] - pmax[1], ap2 = fabs(dp2), an2 = fabs(dn2); + if(ap1 > bias*an1 && ap2 > bias*an2) + mask &= (3<<4) + | (dp1 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) + | (dp2 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + if(an1 > bias*ap1 && an2 > bias*ap2) + mask &= (3<<4) + | (dn1 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) + | (dn2 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + + dp1 = pmax[1] + pmax[2], dn1 = pmax[1] - pmin[2], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = pmin[1] + pmin[2], dn2 = pmin[1] - pmax[2], ap2 = fabs(dp2), an2 = fabs(dn2); + if(ap1 > bias*an1 && ap2 > bias*an2) + mask &= (3<<0) + | (dp1 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) + | (dp2 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + if(an1 > bias*ap1 && an2 > bias*ap2) + mask &= (3<<0) + | (dn1 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) + | (dn2 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + + dp1 = pmax[2] + pmax[0], dn1 = pmax[2] - pmin[0], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = pmin[2] + pmin[0], dn2 = pmin[2] - pmax[0], ap2 = fabs(dp2), an2 = fabs(dn2); + if(ap1 > bias*an1 && ap2 > bias*an2) + mask &= (3<<2) + | (dp1 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) + | (dp2 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + if(an1 > bias*ap1 && an2 > bias*ap2) + mask &= (3<<2) + | (dn1 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) + | (dn2 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + + return mask; +} + +#define R_Shadow_CalcEntitySideMask(ent, worldtolight, radiustolight, bias) R_Shadow_CalcBBoxSideMask((ent)->mins, (ent)->maxs, worldtolight, radiustolight, bias) + +int R_Shadow_CalcSphereSideMask(const vec3_t p, float radius, float bias) +{ + // p is in the cubemap's local coordinate system + // bias = border/(size - border) + float dxyp = p[0] + p[1], dxyn = p[0] - p[1], axyp = fabs(dxyp), axyn = fabs(dxyn); + float dyzp = p[1] + p[2], dyzn = p[1] - p[2], ayzp = fabs(dyzp), ayzn = fabs(dyzn); + float dzxp = p[2] + p[0], dzxn = p[2] - p[0], azxp = fabs(dzxp), azxn = fabs(dzxn); + int mask = 0x3F; + if(axyp > bias*axyn + radius) mask &= dxyp < 0 ? ~((1<<0)|(1<<2)) : ~((2<<0)|(2<<2)); + if(axyn > bias*axyp + radius) mask &= dxyn < 0 ? ~((1<<0)|(2<<2)) : ~((2<<0)|(1<<2)); + if(ayzp > bias*ayzn + radius) mask &= dyzp < 0 ? ~((1<<2)|(1<<4)) : ~((2<<2)|(2<<4)); + if(ayzn > bias*ayzp + radius) mask &= dyzn < 0 ? ~((1<<2)|(2<<4)) : ~((2<<2)|(1<<4)); + if(azxp > bias*azxn + radius) mask &= dzxp < 0 ? ~((1<<4)|(1<<0)) : ~((2<<4)|(2<<0)); + if(azxn > bias*azxp + radius) mask &= dzxn < 0 ? ~((1<<4)|(2<<0)) : ~((2<<4)|(1<<0)); + return mask; +} + +int R_Shadow_CullFrustumSides(rtlight_t *rtlight, float size, float border) +{ + int i; + vec3_t p, n; + int sides = 0x3F, masks[6] = { 3<<4, 3<<4, 3<<0, 3<<0, 3<<2, 3<<2 }; + float scale = (size - 2*border)/size, len; + float bias = border / (float)(size - border), dp, dn, ap, an; + // check if cone enclosing side would cross frustum plane + scale = 2 / (scale*scale + 2); + for (i = 0;i < 5;i++) + { + if (PlaneDiff(rtlight->shadoworigin, &r_refdef.view.frustum[i]) > -0.03125) + continue; + Matrix4x4_Transform3x3(&rtlight->matrix_worldtolight, r_refdef.view.frustum[i].normal, n); + len = scale*VectorLength2(n); + if(n[0]*n[0] > len) sides &= n[0] < 0 ? ~(1<<0) : ~(2 << 0); + if(n[1]*n[1] > len) sides &= n[1] < 0 ? ~(1<<2) : ~(2 << 2); + if(n[2]*n[2] > len) sides &= n[2] < 0 ? ~(1<<4) : ~(2 << 4); + } + if (PlaneDiff(rtlight->shadoworigin, &r_refdef.view.frustum[4]) >= r_refdef.farclip - r_refdef.nearclip + 0.03125) + { + Matrix4x4_Transform3x3(&rtlight->matrix_worldtolight, r_refdef.view.frustum[4].normal, n); + len = scale*VectorLength(n); + if(n[0]*n[0] > len) sides &= n[0] >= 0 ? ~(1<<0) : ~(2 << 0); + if(n[1]*n[1] > len) sides &= n[1] >= 0 ? ~(1<<2) : ~(2 << 2); + if(n[2]*n[2] > len) sides &= n[2] >= 0 ? ~(1<<4) : ~(2 << 4); + } + // this next test usually clips off more sides than the former, but occasionally clips fewer/different ones, so do both and combine results + // check if frustum corners/origin cross plane sides +#if 1 + // infinite version, assumes frustum corners merely give direction and extend to infinite distance + Matrix4x4_Transform(&rtlight->matrix_worldtolight, r_refdef.view.origin, p); + dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn); + masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn); + masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn); + masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + for (i = 0;i < 4;i++) + { + Matrix4x4_Transform(&rtlight->matrix_worldtolight, r_refdef.view.frustumcorner[i], n); + VectorSubtract(n, p, n); + dp = n[0] + n[1], dn = n[0] - n[1], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[0] |= dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2); + if(an > 0) masks[1] |= dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2); + dp = n[1] + n[2], dn = n[1] - n[2], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[2] |= dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4); + if(an > 0) masks[3] |= dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4); + dp = n[2] + n[0], dn = n[2] - n[0], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[4] |= dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0); + if(an > 0) masks[5] |= dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0); + } +#else + // finite version, assumes corners are a finite distance from origin dependent on far plane + for (i = 0;i < 5;i++) + { + Matrix4x4_Transform(&rtlight->matrix_worldtolight, !i ? r_refdef.view.origin : r_refdef.view.frustumcorner[i-1], p); + dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn); + masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn); + masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn); + masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + } +#endif + return sides & masks[0] & masks[1] & masks[2] & masks[3] & masks[4] & masks[5]; +} + +int R_Shadow_ChooseSidesFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const matrix4x4_t *worldtolight, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs, int *totals) +{ + int t, tend; + const int *e; + const float *v[3]; + float normal[3]; + vec3_t p[3]; + float bias; + int mask, surfacemask = 0; + if (!BoxesOverlap(lightmins, lightmaxs, surfacemins, surfacemaxs)) + return 0; + bias = r_shadow_shadowmapborder / (float)(r_shadow_shadowmapmaxsize - r_shadow_shadowmapborder); + tend = firsttriangle + numtris; + if (BoxInsideBox(surfacemins, surfacemaxs, lightmins, lightmaxs)) + { + // surface box entirely inside light box, no box cull + if (projectdirection) + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; + TriangleNormal(v[0], v[1], v[2], normal); + if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0)) + { + Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); + mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); + surfacemask |= mask; + if(totals) + { + totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; + shadowsides[numshadowsides] = mask; + shadowsideslist[numshadowsides++] = t; + } + } + } + } + else + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; + if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2])) + { + Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); + mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); + surfacemask |= mask; + if(totals) + { + totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; + shadowsides[numshadowsides] = mask; + shadowsideslist[numshadowsides++] = t; + } + } + } + } + } + else + { + // surface box not entirely inside light box, cull each triangle + if (projectdirection) + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; + TriangleNormal(v[0], v[1], v[2], normal); + if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0) + && TriangleOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) + { + Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); + mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); + surfacemask |= mask; + if(totals) + { + totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; + shadowsides[numshadowsides] = mask; + shadowsideslist[numshadowsides++] = t; + } + } + } + } + else + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; + if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2]) + && TriangleOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) + { + Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); + mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); + surfacemask |= mask; + if(totals) + { + totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; + shadowsides[numshadowsides] = mask; + shadowsideslist[numshadowsides++] = t; + } + } + } + } + } + return surfacemask; +} + +void R_Shadow_ShadowMapFromList(int numverts, int numtris, const float *vertex3f, const int *elements, int numsidetris, const int *sidetotals, const unsigned char *sides, const int *sidetris) +{ + int i, j, outtriangles = 0; + int *outelement3i[6]; + if (!numverts || !numsidetris || !r_shadow_compilingrtlight) + return; + outtriangles = sidetotals[0] + sidetotals[1] + sidetotals[2] + sidetotals[3] + sidetotals[4] + sidetotals[5]; + // make sure shadowelements is big enough for this mesh + if (maxshadowtriangles < outtriangles) + R_Shadow_ResizeShadowArrays(0, outtriangles, 0, 1); + + // compute the offset and size of the separate index lists for each cubemap side + outtriangles = 0; + for (i = 0;i < 6;i++) + { + outelement3i[i] = shadowelements + outtriangles * 3; + r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap->sideoffsets[i] = outtriangles; + r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap->sidetotals[i] = sidetotals[i]; + outtriangles += sidetotals[i]; + } + + // gather up the (sparse) triangles into separate index lists for each cubemap side + for (i = 0;i < numsidetris;i++) + { + const int *element = elements + sidetris[i] * 3; + for (j = 0;j < 6;j++) + { + if (sides[i] & (1 << j)) + { + outelement3i[j][0] = element[0]; + outelement3i[j][1] = element[1]; + outelement3i[j][2] = element[2]; + outelement3i[j] += 3; + } + } + } + + Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap, NULL, NULL, NULL, vertex3f, NULL, NULL, NULL, NULL, outtriangles, shadowelements); +} + +static void R_Shadow_MakeTextures_MakeCorona(void) +{ + float dx, dy; + int x, y, a; + unsigned char pixels[32][32][4]; + for (y = 0;y < 32;y++) + { + dy = (y - 15.5f) * (1.0f / 16.0f); + for (x = 0;x < 32;x++) + { + dx = (x - 15.5f) * (1.0f / 16.0f); + a = (int)(((1.0f / (dx * dx + dy * dy + 0.2f)) - (1.0f / (1.0f + 0.2))) * 32.0f / (1.0f / (1.0f + 0.2))); + a = bound(0, a, 255); + pixels[y][x][0] = a; + pixels[y][x][1] = a; + pixels[y][x][2] = a; + pixels[y][x][3] = 255; + } + } + r_shadow_lightcorona = R_SkinFrame_LoadInternalBGRA("lightcorona", TEXF_FORCELINEAR, &pixels[0][0][0], 32, 32, false); +} + +static unsigned int R_Shadow_MakeTextures_SamplePoint(float x, float y, float z) +{ + float dist = sqrt(x*x+y*y+z*z); + float intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; + // note this code could suffer byte order issues except that it is multiplying by an integer that reads the same both ways + return (unsigned char)bound(0, intensity * 256.0f, 255) * 0x01010101; +} + +static void R_Shadow_MakeTextures(void) +{ + int x, y, z; + float intensity, dist; + unsigned int *data; + R_Shadow_FreeShadowMaps(); + R_FreeTexturePool(&r_shadow_texturepool); + r_shadow_texturepool = R_AllocTexturePool(); + r_shadow_attenlinearscale = r_shadow_lightattenuationlinearscale.value; + r_shadow_attendividebias = r_shadow_lightattenuationdividebias.value; + data = (unsigned int *)Mem_Alloc(tempmempool, max(max(ATTEN3DSIZE*ATTEN3DSIZE*ATTEN3DSIZE, ATTEN2DSIZE*ATTEN2DSIZE), ATTEN1DSIZE) * 4); + // the table includes one additional value to avoid the need to clamp indexing due to minor math errors + for (x = 0;x <= ATTENTABLESIZE;x++) + { + dist = (x + 0.5f) * (1.0f / ATTENTABLESIZE) * (1.0f / 0.9375); + intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; + r_shadow_attentable[x] = bound(0, intensity, 1); + } + // 1D gradient texture + for (x = 0;x < ATTEN1DSIZE;x++) + data[x] = R_Shadow_MakeTextures_SamplePoint((x + 0.5f) * (1.0f / ATTEN1DSIZE) * (1.0f / 0.9375), 0, 0); + r_shadow_attenuationgradienttexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation1d", ATTEN1DSIZE, 1, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); + // 2D circle texture + for (y = 0;y < ATTEN2DSIZE;y++) + for (x = 0;x < ATTEN2DSIZE;x++) + data[y*ATTEN2DSIZE+x] = R_Shadow_MakeTextures_SamplePoint(((x + 0.5f) * (2.0f / ATTEN2DSIZE) - 1.0f) * (1.0f / 0.9375), ((y + 0.5f) * (2.0f / ATTEN2DSIZE) - 1.0f) * (1.0f / 0.9375), 0); + r_shadow_attenuation2dtexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation2d", ATTEN2DSIZE, ATTEN2DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); + // 3D sphere texture + if (r_shadow_texture3d.integer && vid.support.ext_texture_3d) + { + for (z = 0;z < ATTEN3DSIZE;z++) + for (y = 0;y < ATTEN3DSIZE;y++) + for (x = 0;x < ATTEN3DSIZE;x++) + data[(z*ATTEN3DSIZE+y)*ATTEN3DSIZE+x] = R_Shadow_MakeTextures_SamplePoint(((x + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375), ((y + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375), ((z + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375)); + r_shadow_attenuation3dtexture = R_LoadTexture3D(r_shadow_texturepool, "attenuation3d", ATTEN3DSIZE, ATTEN3DSIZE, ATTEN3DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); + } + else + r_shadow_attenuation3dtexture = NULL; + Mem_Free(data); + + R_Shadow_MakeTextures_MakeCorona(); + + // Editor light sprites + r_editlights_sprcursor = R_SkinFrame_LoadInternal8bit("gfx/editlights/cursor", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + ".3............3." + "..5...2332...5.." + "...7.3....3.7..." + "....7......7...." + "...3.7....7.3..." + "..2...7..7...2.." + "..3..........3.." + "..3..........3.." + "..2...7..7...2.." + "...3.7....7.3..." + "....7......7...." + "...7.3....3.7..." + "..5...2332...5.." + ".3............3." + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/light", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + "................" + "......1111......" + "....11233211...." + "...1234554321..." + "...1356776531..." + "..124677776421.." + "..135777777531.." + "..135777777531.." + "..124677776421.." + "...1356776531..." + "...1234554321..." + "....11233211...." + "......1111......" + "................" + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprnoshadowlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/noshadow", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + "................" + "......1111......" + "....11233211...." + "...1234554321..." + "...1356226531..." + "..12462..26421.." + "..1352....2531.." + "..1352....2531.." + "..12462..26421.." + "...1356226531..." + "...1234554321..." + "....11233211...." + "......1111......" + "................" + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprcubemaplight = R_SkinFrame_LoadInternal8bit("gfx/editlights/cubemaplight", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + "................" + "......2772......" + "....27755772...." + "..277533335772.." + "..753333333357.." + "..777533335777.." + "..735775577537.." + "..733357753337.." + "..733337733337.." + "..753337733357.." + "..277537735772.." + "....27777772...." + "......2772......" + "................" + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprcubemapnoshadowlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/cubemapnoshadowlight", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + "................" + "......2772......" + "....27722772...." + "..2772....2772.." + "..72........27.." + "..7772....2777.." + "..7.27722772.7.." + "..7...2772...7.." + "..7....77....7.." + "..72...77...27.." + "..2772.77.2772.." + "....27777772...." + "......2772......" + "................" + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprselection = R_SkinFrame_LoadInternal8bit("gfx/editlights/selection", TEXF_ALPHA | TEXF_CLAMP, (unsigned char *) + "................" + ".777752..257777." + ".742........247." + ".72..........27." + ".7............7." + ".5............5." + ".2............2." + "................" + "................" + ".2............2." + ".5............5." + ".7............7." + ".72..........27." + ".742........247." + ".777752..257777." + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); +} + +void R_Shadow_ValidateCvars(void) +{ + if (r_shadow_texture3d.integer && !vid.support.ext_texture_3d) + Cvar_SetValueQuick(&r_shadow_texture3d, 0); + if (gl_ext_separatestencil.integer && !vid.support.ati_separate_stencil) + Cvar_SetValueQuick(&gl_ext_separatestencil, 0); + if (gl_ext_stenciltwoside.integer && !vid.support.ext_stencil_two_side) + Cvar_SetValueQuick(&gl_ext_stenciltwoside, 0); +} + +void R_Shadow_RenderMode_Begin(void) +{ +#if 0 + GLint drawbuffer; + GLint readbuffer; +#endif + R_Shadow_ValidateCvars(); + + if (!r_shadow_attenuation2dtexture + || (!r_shadow_attenuation3dtexture && r_shadow_texture3d.integer) + || r_shadow_lightattenuationdividebias.value != r_shadow_attendividebias + || r_shadow_lightattenuationlinearscale.value != r_shadow_attenlinearscale) + R_Shadow_MakeTextures(); + + CHECKGLERROR + R_Mesh_ResetTextureState(); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + GL_DepthTest(true); + GL_DepthMask(false); + GL_Color(0, 0, 0, 1); + GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + + r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; + + if (gl_ext_separatestencil.integer && vid.support.ati_separate_stencil) + { + r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL; + r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL; + } + else if (gl_ext_stenciltwoside.integer && vid.support.ext_stencil_two_side) + { + r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE; + r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE; + } + else + { + r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_STENCIL; + r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_STENCIL; + } + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_GLSL; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (r_textureunits.integer >= 2 && vid.texunits >= 2 && r_shadow_texture3d.integer && r_shadow_attenuation3dtexture) + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN; + else if (r_textureunits.integer >= 3 && vid.texunits >= 3) + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN; + else if (r_textureunits.integer >= 2 && vid.texunits >= 2) + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN; + else + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX; + break; + } + + CHECKGLERROR +#if 0 + qglGetIntegerv(GL_DRAW_BUFFER, &drawbuffer);CHECKGLERROR + qglGetIntegerv(GL_READ_BUFFER, &readbuffer);CHECKGLERROR + r_shadow_drawbuffer = drawbuffer; + r_shadow_readbuffer = readbuffer; +#endif + r_shadow_cullface_front = r_refdef.view.cullface_front; + r_shadow_cullface_back = r_refdef.view.cullface_back; +} + +void R_Shadow_RenderMode_ActiveLight(const rtlight_t *rtlight) +{ + rsurface.rtlight = rtlight; +} + +void R_Shadow_RenderMode_Reset(void) +{ + R_Mesh_SetMainRenderTargets(); + R_SetViewport(&r_refdef.view.viewport); + GL_Scissor(r_shadow_lightscissor[0], r_shadow_lightscissor[1], r_shadow_lightscissor[2], r_shadow_lightscissor[3]); + R_Mesh_ResetTextureState(); + GL_DepthRange(0, 1); + GL_DepthTest(true); + GL_DepthMask(false); + GL_DepthFunc(GL_LEQUAL); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset);CHECKGLERROR + r_refdef.view.cullface_front = r_shadow_cullface_front; + r_refdef.view.cullface_back = r_shadow_cullface_back; + GL_CullFace(r_refdef.view.cullface_back); + GL_Color(1, 1, 1, 1); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + GL_BlendFunc(GL_ONE, GL_ZERO); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + r_shadow_usingshadowmap2d = false; + r_shadow_usingshadowmaportho = false; + R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); +} + +void R_Shadow_ClearStencil(void) +{ + GL_Clear(GL_STENCIL_BUFFER_BIT, NULL, 1.0f, 128); + r_refdef.stats.lights_clears++; +} + +void R_Shadow_RenderMode_StencilShadowVolumes(qboolean zpass) +{ + r_shadow_rendermode_t mode = zpass ? r_shadow_shadowingrendermode_zpass : r_shadow_shadowingrendermode_zfail; + if (r_shadow_rendermode == mode) + return; + R_Shadow_RenderMode_Reset(); + GL_DepthFunc(GL_LESS); + GL_ColorMask(0, 0, 0, 0); + GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset);CHECKGLERROR + GL_CullFace(GL_NONE); + R_SetupShader_DepthOrShadow(false); + r_shadow_rendermode = mode; + switch(mode) + { + default: + break; + case R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE: + case R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL: + R_SetStencilSeparate(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, GL_ALWAYS, 128, 255); + break; + case R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE: + case R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL: + R_SetStencilSeparate(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, GL_ALWAYS, 128, 255); + break; + } +} + +static void R_Shadow_MakeVSDCT(void) +{ + // maps to a 2x3 texture rectangle with normalized coordinates + // +- + // XX + // YY + // ZZ + // stores abs(dir.xy), offset.xy/2.5 + unsigned char data[4*6] = + { + 255, 0, 0x33, 0x33, // +X: <1, 0>, <0.5, 0.5> + 255, 0, 0x99, 0x33, // -X: <1, 0>, <1.5, 0.5> + 0, 255, 0x33, 0x99, // +Y: <0, 1>, <0.5, 1.5> + 0, 255, 0x99, 0x99, // -Y: <0, 1>, <1.5, 1.5> + 0, 0, 0x33, 0xFF, // +Z: <0, 0>, <0.5, 2.5> + 0, 0, 0x99, 0xFF, // -Z: <0, 0>, <1.5, 2.5> + }; + r_shadow_shadowmapvsdcttexture = R_LoadTextureCubeMap(r_shadow_texturepool, "shadowmapvsdct", 1, data, TEXTYPE_RGBA, TEXF_FORCENEAREST | TEXF_CLAMP | TEXF_ALPHA, -1, NULL); +} + +static void R_Shadow_MakeShadowMap(int side, int size) +{ + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (r_shadow_shadowmap2dtexture) return; + r_shadow_shadowmap2dtexture = R_LoadTextureShadowMap2D(r_shadow_texturepool, "shadowmap", size*2, size*(vid.support.arb_texture_non_power_of_two ? 3 : 4), r_shadow_shadowmapdepthbits, r_shadow_shadowmapsampler); + r_shadow_shadowmap2dcolortexture = NULL; + switch(vid.renderpath) + { +#ifdef SUPPORTD3D + case RENDERPATH_D3D9: + r_shadow_shadowmap2dcolortexture = R_LoadTexture2D(r_shadow_texturepool, "shadowmaprendertarget", size*2, size*(vid.support.arb_texture_non_power_of_two ? 3 : 4), NULL, TEXTYPE_BGRA, TEXF_RENDERTARGET | TEXF_FORCENEAREST | TEXF_CLAMP | TEXF_ALPHA, -1, NULL); + r_shadow_fbo2d = R_Mesh_CreateFramebufferObject(r_shadow_shadowmap2dtexture, r_shadow_shadowmap2dcolortexture, NULL, NULL, NULL); + break; +#endif + default: + r_shadow_fbo2d = R_Mesh_CreateFramebufferObject(r_shadow_shadowmap2dtexture, NULL, NULL, NULL, NULL); + break; + } + break; + default: + return; + } + + // render depth into the fbo, do not render color at all + // validate the fbo now + if (qglDrawBuffer) + { + int status; + qglDrawBuffer(GL_NONE);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + status = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);CHECKGLERROR + if (status != GL_FRAMEBUFFER_COMPLETE_EXT && (r_shadow_shadowmapping.integer || r_shadow_deferred.integer)) + { + Con_Printf("R_Shadow_MakeShadowMap: glCheckFramebufferStatusEXT returned %i\n", status); + Cvar_SetValueQuick(&r_shadow_shadowmapping, 0); + Cvar_SetValueQuick(&r_shadow_deferred, 0); + } + } +} + +void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size) +{ + float nearclip, farclip, bias; + r_viewport_t viewport; + int flipped; + GLuint fbo = 0; + float clearcolor[4]; + nearclip = r_shadow_shadowmapping_nearclip.value / rsurface.rtlight->radius; + farclip = 1.0f; + bias = r_shadow_shadowmapping_bias.value * nearclip * (1024.0f / size);// * rsurface.rtlight->radius; + r_shadow_shadowmap_parameters[1] = -nearclip * farclip / (farclip - nearclip) - 0.5f * bias; + r_shadow_shadowmap_parameters[3] = 0.5f + 0.5f * (farclip + nearclip) / (farclip - nearclip); + r_shadow_shadowmapside = side; + r_shadow_shadowmapsize = size; + + r_shadow_shadowmap_parameters[0] = 0.5f * (size - r_shadow_shadowmapborder); + r_shadow_shadowmap_parameters[2] = r_shadow_shadowmapvsdct ? 2.5f*size : size; + R_Viewport_InitRectSideView(&viewport, &rsurface.rtlight->matrix_lighttoworld, side, size, r_shadow_shadowmapborder, nearclip, farclip, NULL); + if (r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAP2D) goto init_done; + + // complex unrolled cube approach (more flexible) + if (r_shadow_shadowmapvsdct && !r_shadow_shadowmapvsdcttexture) + R_Shadow_MakeVSDCT(); + if (!r_shadow_shadowmap2dtexture) + R_Shadow_MakeShadowMap(side, r_shadow_shadowmapmaxsize); + if (r_shadow_shadowmap2dtexture) fbo = r_shadow_fbo2d; + r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2dtexture); + r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2dtexture); + r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D; + + R_Mesh_ResetTextureState(); + R_Shadow_RenderMode_Reset(); + R_Mesh_SetRenderTargets(fbo, r_shadow_shadowmap2dtexture, r_shadow_shadowmap2dcolortexture, NULL, NULL, NULL); + R_SetupShader_DepthOrShadow(true); + GL_PolygonOffset(r_shadow_shadowmapping_polygonfactor.value, r_shadow_shadowmapping_polygonoffset.value); + GL_DepthMask(true); + GL_DepthTest(true); + +init_done: + R_SetViewport(&viewport); + flipped = (side & 1) ^ (side >> 2); + r_refdef.view.cullface_front = flipped ? r_shadow_cullface_back : r_shadow_cullface_front; + r_refdef.view.cullface_back = flipped ? r_shadow_cullface_front : r_shadow_cullface_back; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + GL_CullFace(r_refdef.view.cullface_back); + // OpenGL lets us scissor larger than the viewport, so go ahead and clear all views at once + if ((clear & ((2 << side) - 1)) == (1 << side)) // only clear if the side is the first in the mask + { + // get tightest scissor rectangle that encloses all viewports in the clear mask + int x1 = clear & 0x15 ? 0 : size; + int x2 = clear & 0x2A ? 2 * size : size; + int y1 = clear & 0x03 ? 0 : (clear & 0xC ? size : 2 * size); + int y2 = clear & 0x30 ? 3 * size : (clear & 0xC ? 2 * size : size); + GL_Scissor(x1, y1, x2 - x1, y2 - y1); + GL_Clear(GL_DEPTH_BUFFER_BIT, NULL, 1.0f, 0); + } + GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + Vector4Set(clearcolor, 1,1,1,1); + // completely different meaning than in OpenGL path + r_shadow_shadowmap_parameters[1] = 0; + r_shadow_shadowmap_parameters[3] = -bias; + // we invert the cull mode because we flip the projection matrix + // NOTE: this actually does nothing because the DrawShadowMap code sets it to doublesided... + GL_CullFace(r_refdef.view.cullface_front); + // D3D considers it an error to use a scissor larger than the viewport... clear just this view + GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); + if (r_shadow_shadowmapsampler) + { + GL_ColorMask(0,0,0,0); + if (clear) + GL_Clear(GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + } + else + { + GL_ColorMask(1,1,1,1); + if (clear) + GL_Clear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT, clearcolor, 1.0f, 0); + } + break; + } +} + +void R_Shadow_RenderMode_Lighting(qboolean stenciltest, qboolean transparent, qboolean shadowmapping) +{ + R_Mesh_ResetTextureState(); + R_Mesh_SetMainRenderTargets(); + if (transparent) + { + r_shadow_lightscissor[0] = r_refdef.view.viewport.x; + r_shadow_lightscissor[1] = r_refdef.view.viewport.y; + r_shadow_lightscissor[2] = r_refdef.view.viewport.width; + r_shadow_lightscissor[3] = r_refdef.view.viewport.height; + } + R_Shadow_RenderMode_Reset(); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + if (!transparent) + GL_DepthFunc(GL_EQUAL); + // do global setup needed for the chosen lighting mode + if (r_shadow_rendermode == R_SHADOW_RENDERMODE_LIGHT_GLSL) + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 0); + r_shadow_usingshadowmap2d = shadowmapping; + r_shadow_rendermode = r_shadow_lightingrendermode; + // only draw light where this geometry was already rendered AND the + // stencil is 128 (values other than this mean shadow) + if (stenciltest) + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); + else + R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); +} + +static const unsigned short bboxelements[36] = +{ + 5, 1, 3, 5, 3, 7, + 6, 2, 0, 6, 0, 4, + 7, 3, 2, 7, 2, 6, + 4, 0, 1, 4, 1, 5, + 4, 5, 7, 4, 7, 6, + 1, 0, 2, 1, 2, 3, +}; + +static const float bboxpoints[8][3] = +{ + {-1,-1,-1}, + { 1,-1,-1}, + {-1, 1,-1}, + { 1, 1,-1}, + {-1,-1, 1}, + { 1,-1, 1}, + {-1, 1, 1}, + { 1, 1, 1}, +}; + +void R_Shadow_RenderMode_DrawDeferredLight(qboolean stenciltest, qboolean shadowmapping) +{ + int i; + float vertex3f[8*3]; + const matrix4x4_t *matrix = &rsurface.rtlight->matrix_lighttoworld; +// do global setup needed for the chosen lighting mode + R_Shadow_RenderMode_Reset(); + r_shadow_rendermode = r_shadow_lightingrendermode; + R_EntityMatrix(&identitymatrix); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + // only draw light where this geometry was already rendered AND the + // stencil is 128 (values other than this mean shadow) + R_SetStencil(stenciltest, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); + if (rsurface.rtlight->specularscale > 0 && r_shadow_gloss.integer > 0) + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthtexture, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); + else + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusefbo, r_shadow_prepassgeometrydepthtexture, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); + + r_shadow_usingshadowmap2d = shadowmapping; + + // render the lighting + R_SetupShader_DeferredLight(rsurface.rtlight); + for (i = 0;i < 8;i++) + Matrix4x4_Transform(matrix, bboxpoints[i], vertex3f + i*3); + GL_ColorMask(1,1,1,1); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(true); + GL_DepthFunc(GL_GREATER); + GL_CullFace(r_refdef.view.cullface_back); + R_Mesh_PrepareVertices_Vertex3f(8, vertex3f, NULL); + R_Mesh_Draw(0, 8, 0, 12, NULL, NULL, 0, bboxelements, NULL, 0); +} + +void R_Shadow_UpdateBounceGridTexture(void) +{ +#define MAXBOUNCEGRIDPARTICLESPERLIGHT 1048576 + dlight_t *light; + int flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + int bouncecount; + int hitsupercontentsmask; + int maxbounce; + int numpixels; + int resolution[3]; + int shootparticles; + int shotparticles; + int photoncount; + int tex[3]; + trace_t cliptrace; + //trace_t cliptrace2; + //trace_t cliptrace3; + unsigned char *pixel; + unsigned char *pixels; + float *highpixel; + float *highpixels; + unsigned int lightindex; + unsigned int range; + unsigned int range1; + unsigned int range2; + unsigned int seed = (unsigned int)(realtime * 1000.0f); + vec3_t shotcolor; + vec3_t baseshotcolor; + vec3_t surfcolor; + vec3_t clipend; + vec3_t clipstart; + vec3_t clipdiff; + vec3_t ispacing; + vec3_t maxs; + vec3_t mins; + vec3_t size; + vec3_t spacing; + vec3_t lightcolor; + vec3_t steppos; + vec3_t stepdelta; + vec3_t cullmins, cullmaxs; + vec_t radius; + vec_t s; + vec_t lightintensity; + vec_t photonscaling; + vec_t photonresidual; + float m[16]; + float texlerp[2][3]; + float splatcolor[32]; + float pixelweight[8]; + float w; + int c[4]; + int pixelindex[8]; + int corner; + int pixelsperband; + int pixelband; + int pixelbands; + int numsteps; + int step; + int x, y, z; + rtlight_t *rtlight; + r_shadow_bouncegrid_settings_t settings; + qboolean enable = r_shadow_bouncegrid.integer != 0 && r_refdef.scene.worldmodel; + qboolean allowdirectionalshading = false; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + allowdirectionalshading = true; + if (!vid.support.ext_texture_3d) + return; + break; + case RENDERPATH_GLES2: + // for performance reasons, do not use directional shading on GLES devices + if (!vid.support.ext_texture_3d) + return; + break; + // these renderpaths do not currently have the code to display the bouncegrid, so disable it on them... + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_SOFT: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + return; + } + + r_shadow_bouncegridintensity = r_shadow_bouncegrid_intensity.value; + + // see if there are really any lights to render... + if (enable && r_shadow_bouncegrid_static.integer) + { + enable = false; + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light || !(light->flags & flag)) + continue; + rtlight = &light->rtlight; + // when static, we skip styled lights because they tend to change... + if (rtlight->style > 0) + continue; + VectorScale(rtlight->color, (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale), lightcolor); + if (!VectorLength2(lightcolor)) + continue; + enable = true; + break; + } + } + + if (!enable) + { + if (r_shadow_bouncegridtexture) + { + R_FreeTexture(r_shadow_bouncegridtexture); + r_shadow_bouncegridtexture = NULL; + } + if (r_shadow_bouncegridpixels) + Mem_Free(r_shadow_bouncegridpixels); + r_shadow_bouncegridpixels = NULL; + if (r_shadow_bouncegridhighpixels) + Mem_Free(r_shadow_bouncegridhighpixels); + r_shadow_bouncegridhighpixels = NULL; + r_shadow_bouncegridnumpixels = 0; + r_shadow_bouncegriddirectional = false; + return; + } + + // build up a complete collection of the desired settings, so that memcmp can be used to compare parameters + memset(&settings, 0, sizeof(settings)); + settings.staticmode = r_shadow_bouncegrid_static.integer != 0; + settings.bounceanglediffuse = r_shadow_bouncegrid_bounceanglediffuse.integer != 0; + settings.directionalshading = (r_shadow_bouncegrid_static.integer != 0 ? r_shadow_bouncegrid_static_directionalshading.integer != 0 : r_shadow_bouncegrid_directionalshading.integer != 0) && allowdirectionalshading; + settings.dlightparticlemultiplier = r_shadow_bouncegrid_dlightparticlemultiplier.value; + settings.hitmodels = r_shadow_bouncegrid_hitmodels.integer != 0; + settings.includedirectlighting = r_shadow_bouncegrid_includedirectlighting.integer != 0 || r_shadow_bouncegrid.integer == 2; + settings.lightradiusscale = (r_shadow_bouncegrid_static.integer != 0 ? r_shadow_bouncegrid_static_lightradiusscale.value : r_shadow_bouncegrid_lightradiusscale.value); + settings.maxbounce = (r_shadow_bouncegrid_static.integer != 0 ? r_shadow_bouncegrid_static_maxbounce.integer : r_shadow_bouncegrid_maxbounce.integer); + settings.particlebounceintensity = r_shadow_bouncegrid_particlebounceintensity.value; + settings.particleintensity = r_shadow_bouncegrid_particleintensity.value * 16384.0f * (settings.directionalshading ? 4.0f : 1.0f) / (r_shadow_bouncegrid_spacing.value * r_shadow_bouncegrid_spacing.value); + settings.photons = r_shadow_bouncegrid_static.integer ? r_shadow_bouncegrid_static_photons.integer : r_shadow_bouncegrid_photons.integer; + settings.spacing[0] = r_shadow_bouncegrid_spacing.value; + settings.spacing[1] = r_shadow_bouncegrid_spacing.value; + settings.spacing[2] = r_shadow_bouncegrid_spacing.value; + settings.stablerandom = r_shadow_bouncegrid_stablerandom.integer; + + // bound the values for sanity + settings.photons = bound(1, settings.photons, 1048576); + settings.lightradiusscale = bound(0.0001f, settings.lightradiusscale, 1024.0f); + settings.maxbounce = bound(0, settings.maxbounce, 16); + settings.spacing[0] = bound(1, settings.spacing[0], 512); + settings.spacing[1] = bound(1, settings.spacing[1], 512); + settings.spacing[2] = bound(1, settings.spacing[2], 512); + + // get the spacing values + spacing[0] = settings.spacing[0]; + spacing[1] = settings.spacing[1]; + spacing[2] = settings.spacing[2]; + ispacing[0] = 1.0f / spacing[0]; + ispacing[1] = 1.0f / spacing[1]; + ispacing[2] = 1.0f / spacing[2]; + + // calculate texture size enclosing entire world bounds at the spacing + VectorMA(r_refdef.scene.worldmodel->normalmins, -2.0f, spacing, mins); + VectorMA(r_refdef.scene.worldmodel->normalmaxs, 2.0f, spacing, maxs); + VectorSubtract(maxs, mins, size); + // now we can calculate the resolution we want + c[0] = (int)floor(size[0] / spacing[0] + 0.5f); + c[1] = (int)floor(size[1] / spacing[1] + 0.5f); + c[2] = (int)floor(size[2] / spacing[2] + 0.5f); + // figure out the exact texture size (honoring power of 2 if required) + c[0] = bound(4, c[0], (int)vid.maxtexturesize_3d); + c[1] = bound(4, c[1], (int)vid.maxtexturesize_3d); + c[2] = bound(4, c[2], (int)vid.maxtexturesize_3d); + if (vid.support.arb_texture_non_power_of_two) + { + resolution[0] = c[0]; + resolution[1] = c[1]; + resolution[2] = c[2]; + } + else + { + for (resolution[0] = 4;resolution[0] < c[0];resolution[0]*=2) ; + for (resolution[1] = 4;resolution[1] < c[1];resolution[1]*=2) ; + for (resolution[2] = 4;resolution[2] < c[2];resolution[2]*=2) ; + } + size[0] = spacing[0] * resolution[0]; + size[1] = spacing[1] * resolution[1]; + size[2] = spacing[2] * resolution[2]; + + // if dynamic we may or may not want to use the world bounds + // if the dynamic size is smaller than the world bounds, use it instead + if (!settings.staticmode && (r_shadow_bouncegrid_x.integer * r_shadow_bouncegrid_y.integer * r_shadow_bouncegrid_z.integer < resolution[0] * resolution[1] * resolution[2])) + { + // we know the resolution we want + c[0] = r_shadow_bouncegrid_x.integer; + c[1] = r_shadow_bouncegrid_y.integer; + c[2] = r_shadow_bouncegrid_z.integer; + // now we can calculate the texture size (power of 2 if required) + c[0] = bound(4, c[0], (int)vid.maxtexturesize_3d); + c[1] = bound(4, c[1], (int)vid.maxtexturesize_3d); + c[2] = bound(4, c[2], (int)vid.maxtexturesize_3d); + if (vid.support.arb_texture_non_power_of_two) + { + resolution[0] = c[0]; + resolution[1] = c[1]; + resolution[2] = c[2]; + } + else + { + for (resolution[0] = 4;resolution[0] < c[0];resolution[0]*=2) ; + for (resolution[1] = 4;resolution[1] < c[1];resolution[1]*=2) ; + for (resolution[2] = 4;resolution[2] < c[2];resolution[2]*=2) ; + } + size[0] = spacing[0] * resolution[0]; + size[1] = spacing[1] * resolution[1]; + size[2] = spacing[2] * resolution[2]; + // center the rendering on the view + mins[0] = floor(r_refdef.view.origin[0] * ispacing[0] + 0.5f) * spacing[0] - 0.5f * size[0]; + mins[1] = floor(r_refdef.view.origin[1] * ispacing[1] + 0.5f) * spacing[1] - 0.5f * size[1]; + mins[2] = floor(r_refdef.view.origin[2] * ispacing[2] + 0.5f) * spacing[2] - 0.5f * size[2]; + } + + // recalculate the maxs in case the resolution was not satisfactory + VectorAdd(mins, size, maxs); + + // if all the settings seem identical to the previous update, return + if (r_shadow_bouncegridtexture && (settings.staticmode || realtime < r_shadow_bouncegridtime + r_shadow_bouncegrid_updateinterval.value) && !memcmp(&r_shadow_bouncegridsettings, &settings, sizeof(settings))) + return; + + // store the new settings + r_shadow_bouncegridsettings = settings; + + pixelbands = settings.directionalshading ? 8 : 1; + pixelsperband = resolution[0]*resolution[1]*resolution[2]; + numpixels = pixelsperband*pixelbands; + + // we're going to update the bouncegrid, update the matrix... + memset(m, 0, sizeof(m)); + m[0] = 1.0f / size[0]; + m[3] = -mins[0] * m[0]; + m[5] = 1.0f / size[1]; + m[7] = -mins[1] * m[5]; + m[10] = 1.0f / size[2]; + m[11] = -mins[2] * m[10]; + m[15] = 1.0f; + Matrix4x4_FromArrayFloatD3D(&r_shadow_bouncegridmatrix, m); + // reallocate pixels for this update if needed... + if (r_shadow_bouncegridnumpixels != numpixels || !r_shadow_bouncegridpixels || !r_shadow_bouncegridhighpixels) + { + if (r_shadow_bouncegridtexture) + { + R_FreeTexture(r_shadow_bouncegridtexture); + r_shadow_bouncegridtexture = NULL; + } + r_shadow_bouncegridpixels = (unsigned char *)Mem_Realloc(r_main_mempool, r_shadow_bouncegridpixels, numpixels * sizeof(unsigned char[4])); + r_shadow_bouncegridhighpixels = (float *)Mem_Realloc(r_main_mempool, r_shadow_bouncegridhighpixels, numpixels * sizeof(float[4])); + } + r_shadow_bouncegridnumpixels = numpixels; + pixels = r_shadow_bouncegridpixels; + highpixels = r_shadow_bouncegridhighpixels; + x = pixelsperband*4; + for (pixelband = 0;pixelband < pixelbands;pixelband++) + { + if (pixelband == 1) + memset(pixels + pixelband * x, 128, x); + else + memset(pixels + pixelband * x, 0, x); + } + memset(highpixels, 0, numpixels * sizeof(float[4])); + // figure out what we want to interact with + if (settings.hitmodels) + hitsupercontentsmask = SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY;// | SUPERCONTENTS_LIQUIDSMASK; + else + hitsupercontentsmask = SUPERCONTENTS_SOLID;// | SUPERCONTENTS_LIQUIDSMASK; + maxbounce = settings.maxbounce; + // clear variables that produce warnings otherwise + memset(splatcolor, 0, sizeof(splatcolor)); + // iterate world rtlights + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + range1 = settings.staticmode ? 0 : r_refdef.scene.numlights; + range2 = range + range1; + photoncount = 0; + for (lightindex = 0;lightindex < range2;lightindex++) + { + if (lightindex < range) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + rtlight = &light->rtlight; + VectorClear(rtlight->photoncolor); + rtlight->photons = 0; + if (!(light->flags & flag)) + continue; + if (settings.staticmode) + { + // when static, we skip styled lights because they tend to change... + if (rtlight->style > 0 && r_shadow_bouncegrid.integer != 2) + continue; + } + } + else + { + rtlight = r_refdef.scene.lights[lightindex - range]; + VectorClear(rtlight->photoncolor); + rtlight->photons = 0; + } + // draw only visible lights (major speedup) + radius = rtlight->radius * settings.lightradiusscale; + cullmins[0] = rtlight->shadoworigin[0] - radius; + cullmins[1] = rtlight->shadoworigin[1] - radius; + cullmins[2] = rtlight->shadoworigin[2] - radius; + cullmaxs[0] = rtlight->shadoworigin[0] + radius; + cullmaxs[1] = rtlight->shadoworigin[1] + radius; + cullmaxs[2] = rtlight->shadoworigin[2] + radius; + if (R_CullBox(cullmins, cullmaxs)) + continue; + if (r_refdef.scene.worldmodel + && r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs + && !r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs(r_refdef.scene.worldmodel, r_refdef.viewcache.world_leafvisible, cullmins, cullmaxs)) + continue; + w = r_shadow_lightintensityscale.value * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale); + if (w * VectorLength2(rtlight->color) == 0.0f) + continue; + w *= (rtlight->style >= 0 ? r_refdef.scene.rtlightstylevalue[rtlight->style] : 1); + VectorScale(rtlight->color, w, rtlight->photoncolor); + //if (!VectorLength2(rtlight->photoncolor)) + // continue; + // shoot particles from this light + // use a calculation for the number of particles that will not + // vary with lightstyle, otherwise we get randomized particle + // distribution, the seeded random is only consistent for a + // consistent number of particles on this light... + s = rtlight->radius; + lightintensity = VectorLength(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale); + if (lightindex >= range) + lightintensity *= settings.dlightparticlemultiplier; + rtlight->photons = max(0.0f, lightintensity * s * s); + photoncount += rtlight->photons; + } + photonscaling = (float)settings.photons / max(1, photoncount); + photonresidual = 0.0f; + for (lightindex = 0;lightindex < range2;lightindex++) + { + if (lightindex < range) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + rtlight = &light->rtlight; + } + else + rtlight = r_refdef.scene.lights[lightindex - range]; + // skip a light with no photons + if (rtlight->photons == 0.0f) + continue; + // skip a light with no photon color) + if (VectorLength2(rtlight->photoncolor) == 0.0f) + continue; + photonresidual += rtlight->photons * photonscaling; + shootparticles = (int)bound(0, photonresidual, MAXBOUNCEGRIDPARTICLESPERLIGHT); + if (!shootparticles) + continue; + photonresidual -= shootparticles; + radius = rtlight->radius * settings.lightradiusscale; + s = settings.particleintensity / shootparticles; + VectorScale(rtlight->photoncolor, s, baseshotcolor); + r_refdef.stats.bouncegrid_lights++; + r_refdef.stats.bouncegrid_particles += shootparticles; + for (shotparticles = 0;shotparticles < shootparticles;shotparticles++) + { + if (settings.stablerandom > 0) + seed = lightindex * 11937 + shotparticles; + VectorCopy(baseshotcolor, shotcolor); + VectorCopy(rtlight->shadoworigin, clipstart); + if (settings.stablerandom < 0) + VectorRandom(clipend); + else + VectorCheeseRandom(clipend); + VectorMA(clipstart, radius, clipend, clipend); + for (bouncecount = 0;;bouncecount++) + { + r_refdef.stats.bouncegrid_traces++; + //r_refdef.scene.worldmodel->TraceLineAgainstSurfaces(r_refdef.scene.worldmodel, NULL, NULL, &cliptrace, clipstart, clipend, hitsupercontentsmask); + //r_refdef.scene.worldmodel->TraceLine(r_refdef.scene.worldmodel, NULL, NULL, &cliptrace2, clipstart, clipend, hitsupercontentsmask); + //if (settings.staticmode) + // Collision_ClipLineToWorld(&cliptrace, cl.worldmodel, clipstart, clipend, hitsupercontentsmask, true); + //else + cliptrace = CL_TraceLine(clipstart, clipend, settings.staticmode ? MOVE_WORLDONLY : (settings.hitmodels ? MOVE_HITMODEL : MOVE_NOMONSTERS), NULL, hitsupercontentsmask, true, false, NULL, true, true); + if (bouncecount > 0 || settings.includedirectlighting) + { + // calculate second order spherical harmonics values (average, slopeX, slopeY, slopeZ) + // accumulate average shotcolor + w = VectorLength(shotcolor); + splatcolor[ 0] = shotcolor[0]; + splatcolor[ 1] = shotcolor[1]; + splatcolor[ 2] = shotcolor[2]; + splatcolor[ 3] = 0.0f; + if (pixelbands > 1) + { + VectorSubtract(clipstart, cliptrace.endpos, clipdiff); + VectorNormalize(clipdiff); + // store bentnormal in case the shader has a use for it + splatcolor[ 4] = clipdiff[0] * w; + splatcolor[ 5] = clipdiff[1] * w; + splatcolor[ 6] = clipdiff[2] * w; + splatcolor[ 7] = w; + // accumulate directional contributions (+X, +Y, +Z, -X, -Y, -Z) + splatcolor[ 8] = shotcolor[0] * max(0.0f, clipdiff[0]); + splatcolor[ 9] = shotcolor[0] * max(0.0f, clipdiff[1]); + splatcolor[10] = shotcolor[0] * max(0.0f, clipdiff[2]); + splatcolor[11] = 0.0f; + splatcolor[12] = shotcolor[1] * max(0.0f, clipdiff[0]); + splatcolor[13] = shotcolor[1] * max(0.0f, clipdiff[1]); + splatcolor[14] = shotcolor[1] * max(0.0f, clipdiff[2]); + splatcolor[15] = 0.0f; + splatcolor[16] = shotcolor[2] * max(0.0f, clipdiff[0]); + splatcolor[17] = shotcolor[2] * max(0.0f, clipdiff[1]); + splatcolor[18] = shotcolor[2] * max(0.0f, clipdiff[2]); + splatcolor[19] = 0.0f; + splatcolor[20] = shotcolor[0] * max(0.0f, -clipdiff[0]); + splatcolor[21] = shotcolor[0] * max(0.0f, -clipdiff[1]); + splatcolor[22] = shotcolor[0] * max(0.0f, -clipdiff[2]); + splatcolor[23] = 0.0f; + splatcolor[24] = shotcolor[1] * max(0.0f, -clipdiff[0]); + splatcolor[25] = shotcolor[1] * max(0.0f, -clipdiff[1]); + splatcolor[26] = shotcolor[1] * max(0.0f, -clipdiff[2]); + splatcolor[27] = 0.0f; + splatcolor[28] = shotcolor[2] * max(0.0f, -clipdiff[0]); + splatcolor[29] = shotcolor[2] * max(0.0f, -clipdiff[1]); + splatcolor[30] = shotcolor[2] * max(0.0f, -clipdiff[2]); + splatcolor[31] = 0.0f; + } + // calculate the number of steps we need to traverse this distance + VectorSubtract(cliptrace.endpos, clipstart, stepdelta); + numsteps = (int)(VectorLength(stepdelta) * ispacing[0]); + numsteps = bound(1, numsteps, 1024); + w = 1.0f / numsteps; + VectorScale(stepdelta, w, stepdelta); + VectorMA(clipstart, 0.5f, stepdelta, steppos); + for (step = 0;step < numsteps;step++) + { + r_refdef.stats.bouncegrid_splats++; + // figure out which texture pixel this is in + texlerp[1][0] = ((steppos[0] - mins[0]) * ispacing[0]) - 0.5f; + texlerp[1][1] = ((steppos[1] - mins[1]) * ispacing[1]) - 0.5f; + texlerp[1][2] = ((steppos[2] - mins[2]) * ispacing[2]) - 0.5f; + tex[0] = (int)floor(texlerp[1][0]); + tex[1] = (int)floor(texlerp[1][1]); + tex[2] = (int)floor(texlerp[1][2]); + if (tex[0] >= 1 && tex[1] >= 1 && tex[2] >= 1 && tex[0] < resolution[0] - 2 && tex[1] < resolution[1] - 2 && tex[2] < resolution[2] - 2) + { + // it is within bounds... do the real work now + // calculate the lerp factors + texlerp[1][0] -= tex[0]; + texlerp[1][1] -= tex[1]; + texlerp[1][2] -= tex[2]; + texlerp[0][0] = 1.0f - texlerp[1][0]; + texlerp[0][1] = 1.0f - texlerp[1][1]; + texlerp[0][2] = 1.0f - texlerp[1][2]; + // calculate individual pixel indexes and weights + pixelindex[0] = (((tex[2] )*resolution[1]+tex[1] )*resolution[0]+tex[0] );pixelweight[0] = (texlerp[0][0]*texlerp[0][1]*texlerp[0][2]); + pixelindex[1] = (((tex[2] )*resolution[1]+tex[1] )*resolution[0]+tex[0]+1);pixelweight[1] = (texlerp[1][0]*texlerp[0][1]*texlerp[0][2]); + pixelindex[2] = (((tex[2] )*resolution[1]+tex[1]+1)*resolution[0]+tex[0] );pixelweight[2] = (texlerp[0][0]*texlerp[1][1]*texlerp[0][2]); + pixelindex[3] = (((tex[2] )*resolution[1]+tex[1]+1)*resolution[0]+tex[0]+1);pixelweight[3] = (texlerp[1][0]*texlerp[1][1]*texlerp[0][2]); + pixelindex[4] = (((tex[2]+1)*resolution[1]+tex[1] )*resolution[0]+tex[0] );pixelweight[4] = (texlerp[0][0]*texlerp[0][1]*texlerp[1][2]); + pixelindex[5] = (((tex[2]+1)*resolution[1]+tex[1] )*resolution[0]+tex[0]+1);pixelweight[5] = (texlerp[1][0]*texlerp[0][1]*texlerp[1][2]); + pixelindex[6] = (((tex[2]+1)*resolution[1]+tex[1]+1)*resolution[0]+tex[0] );pixelweight[6] = (texlerp[0][0]*texlerp[1][1]*texlerp[1][2]); + pixelindex[7] = (((tex[2]+1)*resolution[1]+tex[1]+1)*resolution[0]+tex[0]+1);pixelweight[7] = (texlerp[1][0]*texlerp[1][1]*texlerp[1][2]); + // update the 8 pixels... + for (pixelband = 0;pixelband < pixelbands;pixelband++) + { + for (corner = 0;corner < 8;corner++) + { + // calculate address for pixel + w = pixelweight[corner]; + pixel = pixels + 4 * pixelindex[corner] + pixelband * pixelsperband * 4; + highpixel = highpixels + 4 * pixelindex[corner] + pixelband * pixelsperband * 4; + // add to the high precision pixel color + highpixel[0] += (splatcolor[pixelband*4+0]*w); + highpixel[1] += (splatcolor[pixelband*4+1]*w); + highpixel[2] += (splatcolor[pixelband*4+2]*w); + highpixel[3] += (splatcolor[pixelband*4+3]*w); + // flag the low precision pixel as needing to be updated + pixel[3] = 255; + // advance to next band of coefficients + //pixel += pixelsperband*4; + //highpixel += pixelsperband*4; + } + } + } + VectorAdd(steppos, stepdelta, steppos); + } + } + if (cliptrace.fraction >= 1.0f) + break; + r_refdef.stats.bouncegrid_hits++; + if (bouncecount >= maxbounce) + break; + // scale down shot color by bounce intensity and texture color (or 50% if no texture reported) + // also clamp the resulting color to never add energy, even if the user requests extreme values + if (cliptrace.hittexture && cliptrace.hittexture->currentskinframe) + VectorCopy(cliptrace.hittexture->currentskinframe->avgcolor, surfcolor); + else + VectorSet(surfcolor, 0.5f, 0.5f, 0.5f); + VectorScale(surfcolor, settings.particlebounceintensity, surfcolor); + surfcolor[0] = min(surfcolor[0], 1.0f); + surfcolor[1] = min(surfcolor[1], 1.0f); + surfcolor[2] = min(surfcolor[2], 1.0f); + VectorMultiply(shotcolor, surfcolor, shotcolor); + if (VectorLength2(baseshotcolor) == 0.0f) + break; + r_refdef.stats.bouncegrid_bounces++; + if (settings.bounceanglediffuse) + { + // random direction, primarily along plane normal + s = VectorDistance(cliptrace.endpos, clipend); + if (settings.stablerandom < 0) + VectorRandom(clipend); + else + VectorCheeseRandom(clipend); + VectorMA(cliptrace.plane.normal, 0.95f, clipend, clipend); + VectorNormalize(clipend); + VectorScale(clipend, s, clipend); + } + else + { + // reflect the remaining portion of the line across plane normal + VectorSubtract(clipend, cliptrace.endpos, clipdiff); + VectorReflect(clipdiff, 1.0, cliptrace.plane.normal, clipend); + } + // calculate the new line start and end + VectorCopy(cliptrace.endpos, clipstart); + VectorAdd(clipstart, clipend, clipend); + } + } + } + // generate pixels array from highpixels array + // skip first and last columns, rows, and layers as these are blank + // the pixel[3] value was written above, so we can use it to detect only pixels that need to be calculated + for (pixelband = 0;pixelband < pixelbands;pixelband++) + { + for (z = 1;z < resolution[2]-1;z++) + { + for (y = 1;y < resolution[1]-1;y++) + { + for (x = 1, pixelindex[0] = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x, pixel = pixels + 4*pixelindex[0], highpixel = highpixels + 4*pixelindex[0];x < resolution[0]-1;x++, pixel += 4, highpixel += 4) + { + // only convert pixels that were hit by photons + if (pixel[3] == 255) + { + // normalize the bentnormal... + if (pixelband == 1) + { + VectorNormalize(highpixel); + c[0] = (int)(highpixel[0]*128.0f+128.0f); + c[1] = (int)(highpixel[1]*128.0f+128.0f); + c[2] = (int)(highpixel[2]*128.0f+128.0f); + c[3] = (int)(highpixel[3]*128.0f+128.0f); + } + else + { + c[0] = (int)(highpixel[0]*256.0f); + c[1] = (int)(highpixel[1]*256.0f); + c[2] = (int)(highpixel[2]*256.0f); + c[3] = (int)(highpixel[3]*256.0f); + } + pixel[2] = (unsigned char)bound(0, c[0], 255); + pixel[1] = (unsigned char)bound(0, c[1], 255); + pixel[0] = (unsigned char)bound(0, c[2], 255); + pixel[3] = (unsigned char)bound(0, c[3], 255); + } + } + } + } + } + if (r_shadow_bouncegridtexture && r_shadow_bouncegridresolution[0] == resolution[0] && r_shadow_bouncegridresolution[1] == resolution[1] && r_shadow_bouncegridresolution[2] == resolution[2] && r_shadow_bouncegriddirectional == settings.directionalshading) + R_UpdateTexture(r_shadow_bouncegridtexture, pixels, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands); + else + { + VectorCopy(resolution, r_shadow_bouncegridresolution); + r_shadow_bouncegriddirectional = settings.directionalshading; + if (r_shadow_bouncegridtexture) + R_FreeTexture(r_shadow_bouncegridtexture); + r_shadow_bouncegridtexture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, pixels, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL); + } + r_shadow_bouncegridtime = realtime; +} + +void R_Shadow_RenderMode_VisibleShadowVolumes(void) +{ + R_Shadow_RenderMode_Reset(); + GL_BlendFunc(GL_ONE, GL_ONE); + GL_DepthRange(0, 1); + GL_DepthTest(r_showshadowvolumes.integer < 2); + GL_Color(0.0, 0.0125 * r_refdef.view.colorscale, 0.1 * r_refdef.view.colorscale, 1); + GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset);CHECKGLERROR + GL_CullFace(GL_NONE); + r_shadow_rendermode = R_SHADOW_RENDERMODE_VISIBLEVOLUMES; +} + +void R_Shadow_RenderMode_VisibleLighting(qboolean stenciltest, qboolean transparent) +{ + R_Shadow_RenderMode_Reset(); + GL_BlendFunc(GL_ONE, GL_ONE); + GL_DepthRange(0, 1); + GL_DepthTest(r_showlighting.integer < 2); + GL_Color(0.1 * r_refdef.view.colorscale, 0.0125 * r_refdef.view.colorscale, 0, 1); + if (!transparent) + GL_DepthFunc(GL_EQUAL); + R_SetStencil(stenciltest, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); + r_shadow_rendermode = R_SHADOW_RENDERMODE_VISIBLELIGHTING; +} + +void R_Shadow_RenderMode_End(void) +{ + R_Shadow_RenderMode_Reset(); + R_Shadow_RenderMode_ActiveLight(NULL); + GL_DepthMask(true); + GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; +} + +int bboxedges[12][2] = +{ + // top + {0, 1}, // +X + {0, 2}, // +Y + {1, 3}, // Y, +X + {2, 3}, // X, +Y + // bottom + {4, 5}, // +X + {4, 6}, // +Y + {5, 7}, // Y, +X + {6, 7}, // X, +Y + // verticals + {0, 4}, // +Z + {1, 5}, // X, +Z + {2, 6}, // Y, +Z + {3, 7}, // XY, +Z +}; + +qboolean R_Shadow_ScissorForBBox(const float *mins, const float *maxs) +{ + if (!r_shadow_scissor.integer || r_shadow_usingdeferredprepass || r_trippy.integer) + { + r_shadow_lightscissor[0] = r_refdef.view.viewport.x; + r_shadow_lightscissor[1] = r_refdef.view.viewport.y; + r_shadow_lightscissor[2] = r_refdef.view.viewport.width; + r_shadow_lightscissor[3] = r_refdef.view.viewport.height; + return false; + } + if(R_ScissorForBBox(mins, maxs, r_shadow_lightscissor)) + return true; // invisible + if(r_shadow_lightscissor[0] != r_refdef.view.viewport.x + || r_shadow_lightscissor[1] != r_refdef.view.viewport.y + || r_shadow_lightscissor[2] != r_refdef.view.viewport.width + || r_shadow_lightscissor[3] != r_refdef.view.viewport.height) + r_refdef.stats.lights_scissored++; + return false; +} + +static void R_Shadow_RenderLighting_Light_Vertex_Shading(int firstvertex, int numverts, const float *diffusecolor, const float *ambientcolor) +{ + int i; + const float *vertex3f; + const float *normal3f; + float *color4f; + float dist, dot, distintensity, shadeintensity, v[3], n[3]; + switch (r_shadow_rendermode) + { + case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: + if (VectorLength2(diffusecolor) > 0) + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); + if ((dot = DotProduct(n, v)) < 0) + { + shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); + VectorMA(ambientcolor, shadeintensity, diffusecolor, color4f); + } + else + VectorCopy(ambientcolor, color4f); + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + color4f[3] = 1; + } + } + else + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) + { + VectorCopy(ambientcolor, color4f); + if (r_refdef.fogenabled) + { + float f; + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f + 4*i, f, color4f); + } + color4f[3] = 1; + } + } + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: + if (VectorLength2(diffusecolor) > 0) + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + if ((dist = fabs(v[2])) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) + { + Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); + if ((dot = DotProduct(n, v)) < 0) + { + shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); + color4f[0] = (ambientcolor[0] + shadeintensity * diffusecolor[0]) * distintensity; + color4f[1] = (ambientcolor[1] + shadeintensity * diffusecolor[1]) * distintensity; + color4f[2] = (ambientcolor[2] + shadeintensity * diffusecolor[2]) * distintensity; + } + else + { + color4f[0] = ambientcolor[0] * distintensity; + color4f[1] = ambientcolor[1] * distintensity; + color4f[2] = ambientcolor[2] * distintensity; + } + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + } + else + VectorClear(color4f); + color4f[3] = 1; + } + } + else + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + if ((dist = fabs(v[2])) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) + { + color4f[0] = ambientcolor[0] * distintensity; + color4f[1] = ambientcolor[1] * distintensity; + color4f[2] = ambientcolor[2] * distintensity; + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + } + else + VectorClear(color4f); + color4f[3] = 1; + } + } + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX: + if (VectorLength2(diffusecolor) > 0) + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + if ((dist = VectorLength(v)) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) + { + distintensity = (1 - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); + Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); + if ((dot = DotProduct(n, v)) < 0) + { + shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); + color4f[0] = (ambientcolor[0] + shadeintensity * diffusecolor[0]) * distintensity; + color4f[1] = (ambientcolor[1] + shadeintensity * diffusecolor[1]) * distintensity; + color4f[2] = (ambientcolor[2] + shadeintensity * diffusecolor[2]) * distintensity; + } + else + { + color4f[0] = ambientcolor[0] * distintensity; + color4f[1] = ambientcolor[1] * distintensity; + color4f[2] = ambientcolor[2] * distintensity; + } + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + } + else + VectorClear(color4f); + color4f[3] = 1; + } + } + else + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + if ((dist = VectorLength(v)) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) + { + distintensity = (1 - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); + color4f[0] = ambientcolor[0] * distintensity; + color4f[1] = ambientcolor[1] * distintensity; + color4f[2] = ambientcolor[2] * distintensity; + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + } + else + VectorClear(color4f); + color4f[3] = 1; + } + } + break; + default: + break; + } +} + +static void R_Shadow_RenderLighting_VisibleLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + // used to display how many times a surface is lit for level design purposes + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); + RSurf_DrawBatch(); +} + +static void R_Shadow_RenderLighting_Light_GLSL(int texturenumsurfaces, const msurface_t **texturesurfacelist, const vec3_t lightcolor, float ambientscale, float diffusescale, float specularscale) +{ + // ARB2 GLSL shader path (GFFX5200, Radeon 9500) + R_SetupShader_Surface(lightcolor, false, ambientscale, diffusescale, specularscale, RSURFPASS_RTLIGHT, texturenumsurfaces, texturesurfacelist, NULL, false); + RSurf_DrawBatch(); +} + +static void R_Shadow_RenderLighting_Light_Vertex_Pass(int firstvertex, int numvertices, int numtriangles, const int *element3i, vec3_t diffusecolor2, vec3_t ambientcolor2) +{ + int renders; + int i; + int stop; + int newfirstvertex; + int newlastvertex; + int newnumtriangles; + int *newe; + const int *e; + float *c; + int maxtriangles = 4096; + static int newelements[4096*3]; + R_Shadow_RenderLighting_Light_Vertex_Shading(firstvertex, numvertices, diffusecolor2, ambientcolor2); + for (renders = 0;renders < 4;renders++) + { + stop = true; + newfirstvertex = 0; + newlastvertex = 0; + newnumtriangles = 0; + newe = newelements; + // due to low fillrate on the cards this vertex lighting path is + // designed for, we manually cull all triangles that do not + // contain a lit vertex + // this builds batches of triangles from multiple surfaces and + // renders them at once + for (i = 0, e = element3i;i < numtriangles;i++, e += 3) + { + if (VectorLength2(rsurface.passcolor4f + e[0] * 4) + VectorLength2(rsurface.passcolor4f + e[1] * 4) + VectorLength2(rsurface.passcolor4f + e[2] * 4) >= 0.01) + { + if (newnumtriangles) + { + newfirstvertex = min(newfirstvertex, e[0]); + newlastvertex = max(newlastvertex, e[0]); + } + else + { + newfirstvertex = e[0]; + newlastvertex = e[0]; + } + newfirstvertex = min(newfirstvertex, e[1]); + newlastvertex = max(newlastvertex, e[1]); + newfirstvertex = min(newfirstvertex, e[2]); + newlastvertex = max(newlastvertex, e[2]); + newe[0] = e[0]; + newe[1] = e[1]; + newe[2] = e[2]; + newnumtriangles++; + newe += 3; + if (newnumtriangles >= maxtriangles) + { + R_Mesh_Draw(newfirstvertex, newlastvertex - newfirstvertex + 1, 0, newnumtriangles, newelements, NULL, 0, NULL, NULL, 0); + newnumtriangles = 0; + newe = newelements; + stop = false; + } + } + } + if (newnumtriangles >= 1) + { + R_Mesh_Draw(newfirstvertex, newlastvertex - newfirstvertex + 1, 0, newnumtriangles, newelements, NULL, 0, NULL, NULL, 0); + stop = false; + } + // if we couldn't find any lit triangles, exit early + if (stop) + break; + // now reduce the intensity for the next overbright pass + // we have to clamp to 0 here incase the drivers have improper + // handling of negative colors + // (some old drivers even have improper handling of >1 color) + stop = true; + for (i = 0, c = rsurface.passcolor4f + 4 * firstvertex;i < numvertices;i++, c += 4) + { + if (c[0] > 1 || c[1] > 1 || c[2] > 1) + { + c[0] = max(0, c[0] - 1); + c[1] = max(0, c[1] - 1); + c[2] = max(0, c[2] - 1); + stop = false; + } + else + VectorClear(c); + } + // another check... + if (stop) + break; + } +} + +static void R_Shadow_RenderLighting_Light_Vertex(int texturenumsurfaces, const msurface_t **texturesurfacelist, const vec3_t lightcolor, float ambientscale, float diffusescale) +{ + // OpenGL 1.1 path (anything) + float ambientcolorbase[3], diffusecolorbase[3]; + float ambientcolorpants[3], diffusecolorpants[3]; + float ambientcolorshirt[3], diffusecolorshirt[3]; + const float *surfacecolor = rsurface.texture->dlightcolor; + const float *surfacepants = rsurface.colormap_pantscolor; + const float *surfaceshirt = rsurface.colormap_shirtcolor; + rtexture_t *basetexture = rsurface.texture->basetexture; + rtexture_t *pantstexture = rsurface.texture->pantstexture; + rtexture_t *shirttexture = rsurface.texture->shirttexture; + qboolean dopants = pantstexture && VectorLength2(surfacepants) >= (1.0f / 1048576.0f); + qboolean doshirt = shirttexture && VectorLength2(surfaceshirt) >= (1.0f / 1048576.0f); + ambientscale *= 2 * r_refdef.view.colorscale; + diffusescale *= 2 * r_refdef.view.colorscale; + ambientcolorbase[0] = lightcolor[0] * ambientscale * surfacecolor[0];ambientcolorbase[1] = lightcolor[1] * ambientscale * surfacecolor[1];ambientcolorbase[2] = lightcolor[2] * ambientscale * surfacecolor[2]; + diffusecolorbase[0] = lightcolor[0] * diffusescale * surfacecolor[0];diffusecolorbase[1] = lightcolor[1] * diffusescale * surfacecolor[1];diffusecolorbase[2] = lightcolor[2] * diffusescale * surfacecolor[2]; + ambientcolorpants[0] = ambientcolorbase[0] * surfacepants[0];ambientcolorpants[1] = ambientcolorbase[1] * surfacepants[1];ambientcolorpants[2] = ambientcolorbase[2] * surfacepants[2]; + diffusecolorpants[0] = diffusecolorbase[0] * surfacepants[0];diffusecolorpants[1] = diffusecolorbase[1] * surfacepants[1];diffusecolorpants[2] = diffusecolorbase[2] * surfacepants[2]; + ambientcolorshirt[0] = ambientcolorbase[0] * surfaceshirt[0];ambientcolorshirt[1] = ambientcolorbase[1] * surfaceshirt[1];ambientcolorshirt[2] = ambientcolorbase[2] * surfaceshirt[2]; + diffusecolorshirt[0] = diffusecolorbase[0] * surfaceshirt[0];diffusecolorshirt[1] = diffusecolorbase[1] * surfaceshirt[1];diffusecolorshirt[2] = diffusecolorbase[2] * surfaceshirt[2]; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | (diffusescale > 0 ? BATCHNEED_ARRAY_NORMAL : 0) | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + rsurface.passcolor4f = (float *)R_FrameData_Alloc((rsurface.batchfirstvertex + rsurface.batchnumvertices) * sizeof(float[4])); + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + R_Mesh_TexBind(0, basetexture); + R_Mesh_TexMatrix(0, &rsurface.texture->currenttexmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + switch(r_shadow_rendermode) + { + case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: + R_Mesh_TexBind(1, r_shadow_attenuation3dtexture); + R_Mesh_TexMatrix(1, &rsurface.entitytoattenuationxyz); + R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: + R_Mesh_TexBind(2, r_shadow_attenuation2dtexture); + R_Mesh_TexMatrix(2, &rsurface.entitytoattenuationz); + R_Mesh_TexCombine(2, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + // fall through + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: + R_Mesh_TexBind(1, r_shadow_attenuation2dtexture); + R_Mesh_TexMatrix(1, &rsurface.entitytoattenuationxyz); + R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX: + break; + default: + break; + } + //R_Mesh_TexBind(0, basetexture); + R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorbase, ambientcolorbase); + if (dopants) + { + R_Mesh_TexBind(0, pantstexture); + R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorpants, ambientcolorpants); + } + if (doshirt) + { + R_Mesh_TexBind(0, shirttexture); + R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorshirt, ambientcolorshirt); + } +} + +extern cvar_t gl_lightmaps; +void R_Shadow_RenderLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + float ambientscale, diffusescale, specularscale; + qboolean negated; + float lightcolor[3]; + VectorCopy(rsurface.rtlight->currentcolor, lightcolor); + ambientscale = rsurface.rtlight->ambientscale; + diffusescale = rsurface.rtlight->diffusescale; + specularscale = rsurface.rtlight->specularscale * rsurface.texture->specularscale; + if (!r_shadow_usenormalmap.integer) + { + ambientscale += 1.0f * diffusescale; + diffusescale = 0; + specularscale = 0; + } + if ((ambientscale + diffusescale) * VectorLength2(lightcolor) + specularscale * VectorLength2(lightcolor) < (1.0f / 1048576.0f)) + return; + negated = (lightcolor[0] + lightcolor[1] + lightcolor[2] < 0) && vid.support.ext_blend_subtract; + if(negated) + { + VectorNegate(lightcolor, lightcolor); + GL_BlendEquationSubtract(true); + } + RSurf_SetupDepthAndCulling(); + switch (r_shadow_rendermode) + { + case R_SHADOW_RENDERMODE_VISIBLELIGHTING: + GL_DepthTest(!(rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) && !r_showdisabledepthtest.integer); + R_Shadow_RenderLighting_VisibleLighting(texturenumsurfaces, texturesurfacelist); + break; + case R_SHADOW_RENDERMODE_LIGHT_GLSL: + R_Shadow_RenderLighting_Light_GLSL(texturenumsurfaces, texturesurfacelist, lightcolor, ambientscale, diffusescale, specularscale); + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: + case R_SHADOW_RENDERMODE_LIGHT_VERTEX: + R_Shadow_RenderLighting_Light_Vertex(texturenumsurfaces, texturesurfacelist, lightcolor, ambientscale, diffusescale); + break; + default: + Con_Printf("R_Shadow_RenderLighting: unknown r_shadow_rendermode %i\n", r_shadow_rendermode); + break; + } + if(negated) + GL_BlendEquationSubtract(false); +} + +void R_RTLight_Update(rtlight_t *rtlight, int isstatic, matrix4x4_t *matrix, vec3_t color, int style, const char *cubemapname, int shadow, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) +{ + matrix4x4_t tempmatrix = *matrix; + Matrix4x4_Scale(&tempmatrix, r_shadow_lightradiusscale.value, 1); + + // if this light has been compiled before, free the associated data + R_RTLight_Uncompile(rtlight); + + // clear it completely to avoid any lingering data + memset(rtlight, 0, sizeof(*rtlight)); + + // copy the properties + rtlight->matrix_lighttoworld = tempmatrix; + Matrix4x4_Invert_Simple(&rtlight->matrix_worldtolight, &tempmatrix); + Matrix4x4_OriginFromMatrix(&tempmatrix, rtlight->shadoworigin); + rtlight->radius = Matrix4x4_ScaleFromMatrix(&tempmatrix); + VectorCopy(color, rtlight->color); + rtlight->cubemapname[0] = 0; + if (cubemapname && cubemapname[0]) + strlcpy(rtlight->cubemapname, cubemapname, sizeof(rtlight->cubemapname)); + rtlight->shadow = shadow; + rtlight->corona = corona; + rtlight->style = style; + rtlight->isstatic = isstatic; + rtlight->coronasizescale = coronasizescale; + rtlight->ambientscale = ambientscale; + rtlight->diffusescale = diffusescale; + rtlight->specularscale = specularscale; + rtlight->flags = flags; + + // compute derived data + //rtlight->cullradius = rtlight->radius; + //rtlight->cullradius2 = rtlight->radius * rtlight->radius; + rtlight->cullmins[0] = rtlight->shadoworigin[0] - rtlight->radius; + rtlight->cullmins[1] = rtlight->shadoworigin[1] - rtlight->radius; + rtlight->cullmins[2] = rtlight->shadoworigin[2] - rtlight->radius; + rtlight->cullmaxs[0] = rtlight->shadoworigin[0] + rtlight->radius; + rtlight->cullmaxs[1] = rtlight->shadoworigin[1] + rtlight->radius; + rtlight->cullmaxs[2] = rtlight->shadoworigin[2] + rtlight->radius; +} + +// compiles rtlight geometry +// (undone by R_FreeCompiledRTLight, which R_UpdateLight calls) +void R_RTLight_Compile(rtlight_t *rtlight) +{ + int i; + int numsurfaces, numleafs, numleafpvsbytes, numshadowtrispvsbytes, numlighttrispvsbytes; + int lighttris, shadowtris, shadowzpasstris, shadowzfailtris; + entity_render_t *ent = r_refdef.scene.worldentity; + dp_model_t *model = r_refdef.scene.worldmodel; + unsigned char *data; + shadowmesh_t *mesh; + + // compile the light + rtlight->compiled = true; + rtlight->shadowmode = rtlight->shadow ? (int)r_shadow_shadowmode : -1; + rtlight->static_numleafs = 0; + rtlight->static_numleafpvsbytes = 0; + rtlight->static_leaflist = NULL; + rtlight->static_leafpvs = NULL; + rtlight->static_numsurfaces = 0; + rtlight->static_surfacelist = NULL; + rtlight->static_shadowmap_receivers = 0x3F; + rtlight->static_shadowmap_casters = 0x3F; + rtlight->cullmins[0] = rtlight->shadoworigin[0] - rtlight->radius; + rtlight->cullmins[1] = rtlight->shadoworigin[1] - rtlight->radius; + rtlight->cullmins[2] = rtlight->shadoworigin[2] - rtlight->radius; + rtlight->cullmaxs[0] = rtlight->shadoworigin[0] + rtlight->radius; + rtlight->cullmaxs[1] = rtlight->shadoworigin[1] + rtlight->radius; + rtlight->cullmaxs[2] = rtlight->shadoworigin[2] + rtlight->radius; + + if (model && model->GetLightInfo) + { + // this variable must be set for the CompileShadowVolume/CompileShadowMap code + r_shadow_compilingrtlight = rtlight; + R_FrameData_SetMark(); + model->GetLightInfo(ent, rtlight->shadoworigin, rtlight->radius, rtlight->cullmins, rtlight->cullmaxs, r_shadow_buffer_leaflist, r_shadow_buffer_leafpvs, &numleafs, r_shadow_buffer_surfacelist, r_shadow_buffer_surfacepvs, &numsurfaces, r_shadow_buffer_shadowtrispvs, r_shadow_buffer_lighttrispvs, r_shadow_buffer_visitingleafpvs, 0, NULL); + R_FrameData_ReturnToMark(); + numleafpvsbytes = (model->brush.num_leafs + 7) >> 3; + numshadowtrispvsbytes = ((model->brush.shadowmesh ? model->brush.shadowmesh->numtriangles : model->surfmesh.num_triangles) + 7) >> 3; + numlighttrispvsbytes = (model->surfmesh.num_triangles + 7) >> 3; + data = (unsigned char *)Mem_Alloc(r_main_mempool, sizeof(int) * numsurfaces + sizeof(int) * numleafs + numleafpvsbytes + numshadowtrispvsbytes + numlighttrispvsbytes); + rtlight->static_numsurfaces = numsurfaces; + rtlight->static_surfacelist = (int *)data;data += sizeof(int) * numsurfaces; + rtlight->static_numleafs = numleafs; + rtlight->static_leaflist = (int *)data;data += sizeof(int) * numleafs; + rtlight->static_numleafpvsbytes = numleafpvsbytes; + rtlight->static_leafpvs = (unsigned char *)data;data += numleafpvsbytes; + rtlight->static_numshadowtrispvsbytes = numshadowtrispvsbytes; + rtlight->static_shadowtrispvs = (unsigned char *)data;data += numshadowtrispvsbytes; + rtlight->static_numlighttrispvsbytes = numlighttrispvsbytes; + rtlight->static_lighttrispvs = (unsigned char *)data;data += numlighttrispvsbytes; + if (rtlight->static_numsurfaces) + memcpy(rtlight->static_surfacelist, r_shadow_buffer_surfacelist, rtlight->static_numsurfaces * sizeof(*rtlight->static_surfacelist)); + if (rtlight->static_numleafs) + memcpy(rtlight->static_leaflist, r_shadow_buffer_leaflist, rtlight->static_numleafs * sizeof(*rtlight->static_leaflist)); + if (rtlight->static_numleafpvsbytes) + memcpy(rtlight->static_leafpvs, r_shadow_buffer_leafpvs, rtlight->static_numleafpvsbytes); + if (rtlight->static_numshadowtrispvsbytes) + memcpy(rtlight->static_shadowtrispvs, r_shadow_buffer_shadowtrispvs, rtlight->static_numshadowtrispvsbytes); + if (rtlight->static_numlighttrispvsbytes) + memcpy(rtlight->static_lighttrispvs, r_shadow_buffer_lighttrispvs, rtlight->static_numlighttrispvsbytes); + R_FrameData_SetMark(); + switch (rtlight->shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (model->CompileShadowMap && rtlight->shadow) + model->CompileShadowMap(ent, rtlight->shadoworigin, NULL, rtlight->radius, numsurfaces, r_shadow_buffer_surfacelist); + break; + default: + if (model->CompileShadowVolume && rtlight->shadow) + model->CompileShadowVolume(ent, rtlight->shadoworigin, NULL, rtlight->radius, numsurfaces, r_shadow_buffer_surfacelist); + break; + } + R_FrameData_ReturnToMark(); + // now we're done compiling the rtlight + r_shadow_compilingrtlight = NULL; + } + + + // use smallest available cullradius - box radius or light radius + //rtlight->cullradius = RadiusFromBoundsAndOrigin(rtlight->cullmins, rtlight->cullmaxs, rtlight->shadoworigin); + //rtlight->cullradius = min(rtlight->cullradius, rtlight->radius); + + shadowzpasstris = 0; + if (rtlight->static_meshchain_shadow_zpass) + for (mesh = rtlight->static_meshchain_shadow_zpass;mesh;mesh = mesh->next) + shadowzpasstris += mesh->numtriangles; + + shadowzfailtris = 0; + if (rtlight->static_meshchain_shadow_zfail) + for (mesh = rtlight->static_meshchain_shadow_zfail;mesh;mesh = mesh->next) + shadowzfailtris += mesh->numtriangles; + + lighttris = 0; + if (rtlight->static_numlighttrispvsbytes) + for (i = 0;i < rtlight->static_numlighttrispvsbytes*8;i++) + if (CHECKPVSBIT(rtlight->static_lighttrispvs, i)) + lighttris++; + + shadowtris = 0; + if (rtlight->static_numlighttrispvsbytes) + for (i = 0;i < rtlight->static_numshadowtrispvsbytes*8;i++) + if (CHECKPVSBIT(rtlight->static_shadowtrispvs, i)) + shadowtris++; + + if (developer_extra.integer) + Con_DPrintf("static light built: %f %f %f : %f %f %f box, %i light triangles, %i shadow triangles, %i zpass/%i zfail compiled shadow volume triangles\n", rtlight->cullmins[0], rtlight->cullmins[1], rtlight->cullmins[2], rtlight->cullmaxs[0], rtlight->cullmaxs[1], rtlight->cullmaxs[2], lighttris, shadowtris, shadowzpasstris, shadowzfailtris); +} + +void R_RTLight_Uncompile(rtlight_t *rtlight) +{ + if (rtlight->compiled) + { + if (rtlight->static_meshchain_shadow_zpass) + Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_zpass); + rtlight->static_meshchain_shadow_zpass = NULL; + if (rtlight->static_meshchain_shadow_zfail) + Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_zfail); + rtlight->static_meshchain_shadow_zfail = NULL; + if (rtlight->static_meshchain_shadow_shadowmap) + Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_shadowmap); + rtlight->static_meshchain_shadow_shadowmap = NULL; + // these allocations are grouped + if (rtlight->static_surfacelist) + Mem_Free(rtlight->static_surfacelist); + rtlight->static_numleafs = 0; + rtlight->static_numleafpvsbytes = 0; + rtlight->static_leaflist = NULL; + rtlight->static_leafpvs = NULL; + rtlight->static_numsurfaces = 0; + rtlight->static_surfacelist = NULL; + rtlight->static_numshadowtrispvsbytes = 0; + rtlight->static_shadowtrispvs = NULL; + rtlight->static_numlighttrispvsbytes = 0; + rtlight->static_lighttrispvs = NULL; + rtlight->compiled = false; + } +} + +void R_Shadow_UncompileWorldLights(void) +{ + size_t lightindex; + dlight_t *light; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + R_RTLight_Uncompile(&light->rtlight); + } +} + +void R_Shadow_ComputeShadowCasterCullingPlanes(rtlight_t *rtlight) +{ + int i, j; + mplane_t plane; + // reset the count of frustum planes + // see rtlight->cached_frustumplanes definition for how much this array + // can hold + rtlight->cached_numfrustumplanes = 0; + + if (r_trippy.integer) + return; + + // haven't implemented a culling path for ortho rendering + if (!r_refdef.view.useperspective) + { + // check if the light is on screen and copy the 4 planes if it is + for (i = 0;i < 4;i++) + if (PlaneDiff(rtlight->shadoworigin, &r_refdef.view.frustum[i]) < -0.03125) + break; + if (i == 4) + for (i = 0;i < 4;i++) + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = r_refdef.view.frustum[i]; + return; + } + +#if 1 + // generate a deformed frustum that includes the light origin, this is + // used to cull shadow casting surfaces that can not possibly cast a + // shadow onto the visible light-receiving surfaces, which can be a + // performance gain + // + // if the light origin is onscreen the result will be 4 planes exactly + // if the light origin is offscreen on only one axis the result will + // be exactly 5 planes (split-side case) + // if the light origin is offscreen on two axes the result will be + // exactly 4 planes (stretched corner case) + for (i = 0;i < 4;i++) + { + // quickly reject standard frustum planes that put the light + // origin outside the frustum + if (PlaneDiff(rtlight->shadoworigin, &r_refdef.view.frustum[i]) < -0.03125) + continue; + // copy the plane + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = r_refdef.view.frustum[i]; + } + // if all the standard frustum planes were accepted, the light is onscreen + // otherwise we need to generate some more planes below... + if (rtlight->cached_numfrustumplanes < 4) + { + // at least one of the stock frustum planes failed, so we need to + // create one or two custom planes to enclose the light origin + for (i = 0;i < 4;i++) + { + // create a plane using the view origin and light origin, and a + // single point from the frustum corner set + TriangleNormal(r_refdef.view.origin, r_refdef.view.frustumcorner[i], rtlight->shadoworigin, plane.normal); + VectorNormalize(plane.normal); + plane.dist = DotProduct(r_refdef.view.origin, plane.normal); + // see if this plane is backwards and flip it if so + for (j = 0;j < 4;j++) + if (j != i && DotProduct(r_refdef.view.frustumcorner[j], plane.normal) - plane.dist < -0.03125) + break; + if (j < 4) + { + VectorNegate(plane.normal, plane.normal); + plane.dist *= -1; + // flipped plane, test again to see if it is now valid + for (j = 0;j < 4;j++) + if (j != i && DotProduct(r_refdef.view.frustumcorner[j], plane.normal) - plane.dist < -0.03125) + break; + // if the plane is still not valid, then it is dividing the + // frustum and has to be rejected + if (j < 4) + continue; + } + // we have created a valid plane, compute extra info + PlaneClassify(&plane); + // copy the plane + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; +#if 1 + // if we've found 5 frustum planes then we have constructed a + // proper split-side case and do not need to keep searching for + // planes to enclose the light origin + if (rtlight->cached_numfrustumplanes == 5) + break; +#endif + } + } +#endif + +#if 0 + for (i = 0;i < rtlight->cached_numfrustumplanes;i++) + { + plane = rtlight->cached_frustumplanes[i]; + Con_Printf("light %p plane #%i %f %f %f : %f (%f %f %f %f %f)\n", rtlight, i, plane.normal[0], plane.normal[1], plane.normal[2], plane.dist, PlaneDiff(r_refdef.view.frustumcorner[0], &plane), PlaneDiff(r_refdef.view.frustumcorner[1], &plane), PlaneDiff(r_refdef.view.frustumcorner[2], &plane), PlaneDiff(r_refdef.view.frustumcorner[3], &plane), PlaneDiff(rtlight->shadoworigin, &plane)); + } +#endif + +#if 0 + // now add the light-space box planes if the light box is rotated, as any + // caster outside the oriented light box is irrelevant (even if it passed + // the worldspace light box, which is axial) + if (rtlight->matrix_lighttoworld.m[0][0] != 1 || rtlight->matrix_lighttoworld.m[1][1] != 1 || rtlight->matrix_lighttoworld.m[2][2] != 1) + { + for (i = 0;i < 6;i++) + { + vec3_t v; + VectorClear(v); + v[i >> 1] = (i & 1) ? -1 : 1; + Matrix4x4_Transform(&rtlight->matrix_lighttoworld, v, plane.normal); + VectorSubtract(plane.normal, rtlight->shadoworigin, plane.normal); + plane.dist = VectorNormalizeLength(plane.normal); + plane.dist += DotProduct(plane.normal, rtlight->shadoworigin); + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; + } + } +#endif + +#if 0 + // add the world-space reduced box planes + for (i = 0;i < 6;i++) + { + VectorClear(plane.normal); + plane.normal[i >> 1] = (i & 1) ? -1 : 1; + plane.dist = (i & 1) ? -rtlight->cached_cullmaxs[i >> 1] : rtlight->cached_cullmins[i >> 1]; + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; + } +#endif + +#if 0 + { + int j, oldnum; + vec3_t points[8]; + vec_t bestdist; + // reduce all plane distances to tightly fit the rtlight cull box, which + // is in worldspace + VectorSet(points[0], rtlight->cached_cullmins[0], rtlight->cached_cullmins[1], rtlight->cached_cullmins[2]); + VectorSet(points[1], rtlight->cached_cullmaxs[0], rtlight->cached_cullmins[1], rtlight->cached_cullmins[2]); + VectorSet(points[2], rtlight->cached_cullmins[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmins[2]); + VectorSet(points[3], rtlight->cached_cullmaxs[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmins[2]); + VectorSet(points[4], rtlight->cached_cullmins[0], rtlight->cached_cullmins[1], rtlight->cached_cullmaxs[2]); + VectorSet(points[5], rtlight->cached_cullmaxs[0], rtlight->cached_cullmins[1], rtlight->cached_cullmaxs[2]); + VectorSet(points[6], rtlight->cached_cullmins[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmaxs[2]); + VectorSet(points[7], rtlight->cached_cullmaxs[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmaxs[2]); + oldnum = rtlight->cached_numfrustumplanes; + rtlight->cached_numfrustumplanes = 0; + for (j = 0;j < oldnum;j++) + { + // find the nearest point on the box to this plane + bestdist = DotProduct(rtlight->cached_frustumplanes[j].normal, points[0]); + for (i = 1;i < 8;i++) + { + dist = DotProduct(rtlight->cached_frustumplanes[j].normal, points[i]); + if (bestdist > dist) + bestdist = dist; + } + Con_Printf("light %p %splane #%i %f %f %f : %f < %f\n", rtlight, rtlight->cached_frustumplanes[j].dist < bestdist + 0.03125 ? "^2" : "^1", j, rtlight->cached_frustumplanes[j].normal[0], rtlight->cached_frustumplanes[j].normal[1], rtlight->cached_frustumplanes[j].normal[2], rtlight->cached_frustumplanes[j].dist, bestdist); + // if the nearest point is near or behind the plane, we want this + // plane, otherwise the plane is useless as it won't cull anything + if (rtlight->cached_frustumplanes[j].dist < bestdist + 0.03125) + { + PlaneClassify(&rtlight->cached_frustumplanes[j]); + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = rtlight->cached_frustumplanes[j]; + } + } + } +#endif +} + +void R_Shadow_DrawWorldShadow_ShadowMap(int numsurfaces, int *surfacelist, const unsigned char *trispvs, const unsigned char *surfacesides) +{ + shadowmesh_t *mesh; + + RSurf_ActiveWorldEntity(); + + if (rsurface.rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) + { + CHECKGLERROR + GL_CullFace(GL_NONE); + mesh = rsurface.rtlight->static_meshchain_shadow_shadowmap; + for (;mesh;mesh = mesh->next) + { + if (!mesh->sidetotals[r_shadow_shadowmapside]) + continue; + r_refdef.stats.lights_shadowtriangles += mesh->sidetotals[r_shadow_shadowmapside]; + if (mesh->vertex3fbuffer) + R_Mesh_PrepareVertices_Vertex3f(mesh->numverts, mesh->vertex3f, mesh->vertex3fbuffer); + else + R_Mesh_PrepareVertices_Vertex3f(mesh->numverts, mesh->vertex3f, mesh->vbo_vertexbuffer); + R_Mesh_Draw(0, mesh->numverts, mesh->sideoffsets[r_shadow_shadowmapside], mesh->sidetotals[r_shadow_shadowmapside], mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); + } + CHECKGLERROR + } + else if (r_refdef.scene.worldentity->model) + r_refdef.scene.worldmodel->DrawShadowMap(r_shadow_shadowmapside, r_refdef.scene.worldentity, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius, numsurfaces, surfacelist, surfacesides, rsurface.rtlight->cached_cullmins, rsurface.rtlight->cached_cullmaxs); + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_Shadow_DrawWorldShadow_ShadowVolume(int numsurfaces, int *surfacelist, const unsigned char *trispvs) +{ + qboolean zpass = false; + shadowmesh_t *mesh; + int t, tend; + int surfacelistindex; + msurface_t *surface; + + // if triangle neighbors are disabled, shadowvolumes are disabled + if (r_refdef.scene.worldmodel->brush.shadowmesh ? !r_refdef.scene.worldmodel->brush.shadowmesh->neighbor3i : !r_refdef.scene.worldmodel->surfmesh.data_neighbor3i) + return; + + RSurf_ActiveWorldEntity(); + + if (rsurface.rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) + { + CHECKGLERROR + if (r_shadow_rendermode != R_SHADOW_RENDERMODE_VISIBLEVOLUMES) + { + zpass = R_Shadow_UseZPass(r_refdef.scene.worldmodel->normalmins, r_refdef.scene.worldmodel->normalmaxs); + R_Shadow_RenderMode_StencilShadowVolumes(zpass); + } + mesh = zpass ? rsurface.rtlight->static_meshchain_shadow_zpass : rsurface.rtlight->static_meshchain_shadow_zfail; + for (;mesh;mesh = mesh->next) + { + r_refdef.stats.lights_shadowtriangles += mesh->numtriangles; + if (mesh->vertex3fbuffer) + R_Mesh_PrepareVertices_Vertex3f(mesh->numverts, mesh->vertex3f, mesh->vertex3fbuffer); + else + R_Mesh_PrepareVertices_Vertex3f(mesh->numverts, mesh->vertex3f, mesh->vbo_vertexbuffer); + if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZPASS_STENCIL) + { + // increment stencil if frontface is infront of depthbuffer + GL_CullFace(r_refdef.view.cullface_back); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_ALWAYS, 128, 255); + R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); + // decrement stencil if backface is infront of depthbuffer + GL_CullFace(r_refdef.view.cullface_front); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, 128, 255); + } + else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZFAIL_STENCIL) + { + // decrement stencil if backface is behind depthbuffer + GL_CullFace(r_refdef.view.cullface_front); + R_SetStencil(true, 255, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, 128, 255); + R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); + // increment stencil if frontface is behind depthbuffer + GL_CullFace(r_refdef.view.cullface_back); + R_SetStencil(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_ALWAYS, 128, 255); + } + R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); + } + CHECKGLERROR + } + else if (numsurfaces && r_refdef.scene.worldmodel->brush.shadowmesh) + { + // use the shadow trispvs calculated earlier by GetLightInfo to cull world triangles on this dynamic light + R_Shadow_PrepareShadowMark(r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + surface = r_refdef.scene.worldmodel->data_surfaces + surfacelist[surfacelistindex]; + for (t = surface->num_firstshadowmeshtriangle, tend = t + surface->num_triangles;t < tend;t++) + if (CHECKPVSBIT(trispvs, t)) + shadowmarklist[numshadowmark++] = t; + } + R_Shadow_VolumeFromList(r_refdef.scene.worldmodel->brush.shadowmesh->numverts, r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles, r_refdef.scene.worldmodel->brush.shadowmesh->vertex3f, r_refdef.scene.worldmodel->brush.shadowmesh->element3i, r_refdef.scene.worldmodel->brush.shadowmesh->neighbor3i, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius + r_refdef.scene.worldmodel->radius*2 + r_shadow_projectdistance.value, numshadowmark, shadowmarklist, r_refdef.scene.worldmodel->normalmins, r_refdef.scene.worldmodel->normalmaxs); + } + else if (numsurfaces) + { + r_refdef.scene.worldmodel->DrawShadowVolume(r_refdef.scene.worldentity, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius, numsurfaces, surfacelist, rsurface.rtlight->cached_cullmins, rsurface.rtlight->cached_cullmaxs); + } + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_Shadow_DrawEntityShadow(entity_render_t *ent) +{ + vec3_t relativeshadoworigin, relativeshadowmins, relativeshadowmaxs; + vec_t relativeshadowradius; + RSurf_ActiveModelEntity(ent, false, false, false); + Matrix4x4_Transform(&ent->inversematrix, rsurface.rtlight->shadoworigin, relativeshadoworigin); + // we need to re-init the shader for each entity because the matrix changed + relativeshadowradius = rsurface.rtlight->radius / ent->scale; + relativeshadowmins[0] = relativeshadoworigin[0] - relativeshadowradius; + relativeshadowmins[1] = relativeshadoworigin[1] - relativeshadowradius; + relativeshadowmins[2] = relativeshadoworigin[2] - relativeshadowradius; + relativeshadowmaxs[0] = relativeshadoworigin[0] + relativeshadowradius; + relativeshadowmaxs[1] = relativeshadoworigin[1] + relativeshadowradius; + relativeshadowmaxs[2] = relativeshadoworigin[2] + relativeshadowradius; + switch (r_shadow_rendermode) + { + case R_SHADOW_RENDERMODE_SHADOWMAP2D: + ent->model->DrawShadowMap(r_shadow_shadowmapside, ent, relativeshadoworigin, NULL, relativeshadowradius, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, NULL, relativeshadowmins, relativeshadowmaxs); + break; + default: + ent->model->DrawShadowVolume(ent, relativeshadoworigin, NULL, relativeshadowradius, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, relativeshadowmins, relativeshadowmaxs); + break; + } + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_Shadow_SetupEntityLight(const entity_render_t *ent) +{ + // set up properties for rendering light onto this entity + RSurf_ActiveModelEntity(ent, true, true, false); + Matrix4x4_Concat(&rsurface.entitytolight, &rsurface.rtlight->matrix_worldtolight, &ent->matrix); + Matrix4x4_Concat(&rsurface.entitytoattenuationxyz, &matrix_attenuationxyz, &rsurface.entitytolight); + Matrix4x4_Concat(&rsurface.entitytoattenuationz, &matrix_attenuationz, &rsurface.entitytolight); + Matrix4x4_Transform(&ent->inversematrix, rsurface.rtlight->shadoworigin, rsurface.entitylightorigin); +} + +void R_Shadow_DrawWorldLight(int numsurfaces, int *surfacelist, const unsigned char *lighttrispvs) +{ + if (!r_refdef.scene.worldmodel->DrawLight) + return; + + // set up properties for rendering light onto this entity + RSurf_ActiveWorldEntity(); + rsurface.entitytolight = rsurface.rtlight->matrix_worldtolight; + Matrix4x4_Concat(&rsurface.entitytoattenuationxyz, &matrix_attenuationxyz, &rsurface.entitytolight); + Matrix4x4_Concat(&rsurface.entitytoattenuationz, &matrix_attenuationz, &rsurface.entitytolight); + VectorCopy(rsurface.rtlight->shadoworigin, rsurface.entitylightorigin); + + r_refdef.scene.worldmodel->DrawLight(r_refdef.scene.worldentity, numsurfaces, surfacelist, lighttrispvs); + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_Shadow_DrawEntityLight(entity_render_t *ent) +{ + dp_model_t *model = ent->model; + if (!model->DrawLight) + return; + + R_Shadow_SetupEntityLight(ent); + + model->DrawLight(ent, model->nummodelsurfaces, model->sortedmodelsurfaces, NULL); + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_Shadow_PrepareLight(rtlight_t *rtlight) +{ + int i; + float f; + int numleafs, numsurfaces; + int *leaflist, *surfacelist; + unsigned char *leafpvs; + unsigned char *shadowtrispvs; + unsigned char *lighttrispvs; + //unsigned char *surfacesides; + int numlightentities; + int numlightentities_noselfshadow; + int numshadowentities; + int numshadowentities_noselfshadow; + static entity_render_t *lightentities[MAX_EDICTS]; + static entity_render_t *lightentities_noselfshadow[MAX_EDICTS]; + static entity_render_t *shadowentities[MAX_EDICTS]; + static entity_render_t *shadowentities_noselfshadow[MAX_EDICTS]; + qboolean nolight; + + rtlight->draw = false; + + // skip lights that don't light because of ambientscale+diffusescale+specularscale being 0 (corona only lights) + // skip lights that are basically invisible (color 0 0 0) + nolight = VectorLength2(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale) < (1.0f / 1048576.0f); + + // loading is done before visibility checks because loading should happen + // all at once at the start of a level, not when it stalls gameplay. + // (especially important to benchmarks) + // compile light + if (rtlight->isstatic && !nolight && (!rtlight->compiled || (rtlight->shadow && rtlight->shadowmode != (int)r_shadow_shadowmode)) && r_shadow_realtime_world_compile.integer) + { + if (rtlight->compiled) + R_RTLight_Uncompile(rtlight); + R_RTLight_Compile(rtlight); + } + + // load cubemap + rtlight->currentcubemap = rtlight->cubemapname[0] ? R_GetCubemap(rtlight->cubemapname) : r_texture_whitecube; + + // look up the light style value at this time + f = (rtlight->style >= 0 ? r_refdef.scene.rtlightstylevalue[rtlight->style] : 1) * r_shadow_lightintensityscale.value; + VectorScale(rtlight->color, f, rtlight->currentcolor); + /* + if (rtlight->selected) + { + f = 2 + sin(realtime * M_PI * 4.0); + VectorScale(rtlight->currentcolor, f, rtlight->currentcolor); + } + */ + + // if lightstyle is currently off, don't draw the light + if (VectorLength2(rtlight->currentcolor) < (1.0f / 1048576.0f)) + return; + + // skip processing on corona-only lights + if (nolight) + return; + + // if the light box is offscreen, skip it + if (R_CullBox(rtlight->cullmins, rtlight->cullmaxs)) + return; + + VectorCopy(rtlight->cullmins, rtlight->cached_cullmins); + VectorCopy(rtlight->cullmaxs, rtlight->cached_cullmaxs); + + R_Shadow_ComputeShadowCasterCullingPlanes(rtlight); + + if (rtlight->compiled && r_shadow_realtime_world_compile.integer) + { + // compiled light, world available and can receive realtime lighting + // retrieve leaf information + numleafs = rtlight->static_numleafs; + leaflist = rtlight->static_leaflist; + leafpvs = rtlight->static_leafpvs; + numsurfaces = rtlight->static_numsurfaces; + surfacelist = rtlight->static_surfacelist; + //surfacesides = NULL; + shadowtrispvs = rtlight->static_shadowtrispvs; + lighttrispvs = rtlight->static_lighttrispvs; + } + else if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->GetLightInfo) + { + // dynamic light, world available and can receive realtime lighting + // calculate lit surfaces and leafs + r_refdef.scene.worldmodel->GetLightInfo(r_refdef.scene.worldentity, rtlight->shadoworigin, rtlight->radius, rtlight->cached_cullmins, rtlight->cached_cullmaxs, r_shadow_buffer_leaflist, r_shadow_buffer_leafpvs, &numleafs, r_shadow_buffer_surfacelist, r_shadow_buffer_surfacepvs, &numsurfaces, r_shadow_buffer_shadowtrispvs, r_shadow_buffer_lighttrispvs, r_shadow_buffer_visitingleafpvs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes); + R_Shadow_ComputeShadowCasterCullingPlanes(rtlight); + leaflist = r_shadow_buffer_leaflist; + leafpvs = r_shadow_buffer_leafpvs; + surfacelist = r_shadow_buffer_surfacelist; + //surfacesides = r_shadow_buffer_surfacesides; + shadowtrispvs = r_shadow_buffer_shadowtrispvs; + lighttrispvs = r_shadow_buffer_lighttrispvs; + // if the reduced leaf bounds are offscreen, skip it + if (R_CullBox(rtlight->cached_cullmins, rtlight->cached_cullmaxs)) + return; + } + else + { + // no world + numleafs = 0; + leaflist = NULL; + leafpvs = NULL; + numsurfaces = 0; + surfacelist = NULL; + //surfacesides = NULL; + shadowtrispvs = NULL; + lighttrispvs = NULL; + } + // check if light is illuminating any visible leafs + if (numleafs) + { + for (i = 0;i < numleafs;i++) + if (r_refdef.viewcache.world_leafvisible[leaflist[i]]) + break; + if (i == numleafs) + return; + } + + // make a list of lit entities and shadow casting entities + numlightentities = 0; + numlightentities_noselfshadow = 0; + numshadowentities = 0; + numshadowentities_noselfshadow = 0; + + // add dynamic entities that are lit by the light + for (i = 0;i < r_refdef.scene.numentities;i++) + { + dp_model_t *model; + entity_render_t *ent = r_refdef.scene.entities[i]; + vec3_t org; + if (!BoxesOverlap(ent->mins, ent->maxs, rtlight->cached_cullmins, rtlight->cached_cullmaxs)) + continue; + // skip the object entirely if it is not within the valid + // shadow-casting region (which includes the lit region) + if (R_CullBoxCustomPlanes(ent->mins, ent->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) + continue; + if (!(model = ent->model)) + continue; + if (r_refdef.viewcache.entityvisible[i] && model->DrawLight && (ent->flags & RENDER_LIGHT)) + { + // this entity wants to receive light, is visible, and is + // inside the light box + // TODO: check if the surfaces in the model can receive light + // so now check if it's in a leaf seen by the light + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS && !r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS(r_refdef.scene.worldmodel, leafpvs, ent->mins, ent->maxs)) + continue; + if (ent->flags & RENDER_NOSELFSHADOW) + lightentities_noselfshadow[numlightentities_noselfshadow++] = ent; + else + lightentities[numlightentities++] = ent; + // since it is lit, it probably also casts a shadow... + // about the VectorDistance2 - light emitting entities should not cast their own shadow + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + if ((ent->flags & RENDER_SHADOW) && model->DrawShadowVolume && VectorDistance2(org, rtlight->shadoworigin) > 0.1) + { + // note: exterior models without the RENDER_NOSELFSHADOW + // flag still create a RENDER_NOSELFSHADOW shadow but + // are lit normally, this means that they are + // self-shadowing but do not shadow other + // RENDER_NOSELFSHADOW entities such as the gun + // (very weird, but keeps the player shadow off the gun) + if (ent->flags & (RENDER_NOSELFSHADOW | RENDER_EXTERIORMODEL)) + shadowentities_noselfshadow[numshadowentities_noselfshadow++] = ent; + else + shadowentities[numshadowentities++] = ent; + } + } + else if (ent->flags & RENDER_SHADOW) + { + // this entity is not receiving light, but may still need to + // cast a shadow... + // TODO: check if the surfaces in the model can cast shadow + // now check if it is in a leaf seen by the light + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS && !r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS(r_refdef.scene.worldmodel, leafpvs, ent->mins, ent->maxs)) + continue; + // about the VectorDistance2 - light emitting entities should not cast their own shadow + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + if ((ent->flags & RENDER_SHADOW) && model->DrawShadowVolume && VectorDistance2(org, rtlight->shadoworigin) > 0.1) + { + if (ent->flags & (RENDER_NOSELFSHADOW | RENDER_EXTERIORMODEL)) + shadowentities_noselfshadow[numshadowentities_noselfshadow++] = ent; + else + shadowentities[numshadowentities++] = ent; + } + } + } + + // return if there's nothing at all to light + if (numsurfaces + numlightentities + numlightentities_noselfshadow == 0) + return; + + // count this light in the r_speeds + r_refdef.stats.lights++; + + // flag it as worth drawing later + rtlight->draw = true; + + // cache all the animated entities that cast a shadow but are not visible + for (i = 0;i < numshadowentities;i++) + if (!shadowentities[i]->animcache_vertex3f) + R_AnimCache_GetEntity(shadowentities[i], false, false); + for (i = 0;i < numshadowentities_noselfshadow;i++) + if (!shadowentities_noselfshadow[i]->animcache_vertex3f) + R_AnimCache_GetEntity(shadowentities_noselfshadow[i], false, false); + + // allocate some temporary memory for rendering this light later in the frame + // reusable buffers need to be copied, static data can be used as-is + rtlight->cached_numlightentities = numlightentities; + rtlight->cached_numlightentities_noselfshadow = numlightentities_noselfshadow; + rtlight->cached_numshadowentities = numshadowentities; + rtlight->cached_numshadowentities_noselfshadow = numshadowentities_noselfshadow; + rtlight->cached_numsurfaces = numsurfaces; + rtlight->cached_lightentities = (entity_render_t**)R_FrameData_Store(numlightentities*sizeof(entity_render_t*), (void*)lightentities); + rtlight->cached_lightentities_noselfshadow = (entity_render_t**)R_FrameData_Store(numlightentities_noselfshadow*sizeof(entity_render_t*), (void*)lightentities_noselfshadow); + rtlight->cached_shadowentities = (entity_render_t**)R_FrameData_Store(numshadowentities*sizeof(entity_render_t*), (void*)shadowentities); + rtlight->cached_shadowentities_noselfshadow = (entity_render_t**)R_FrameData_Store(numshadowentities_noselfshadow*sizeof(entity_render_t *), (void*)shadowentities_noselfshadow); + if (shadowtrispvs == r_shadow_buffer_shadowtrispvs) + { + int numshadowtrispvsbytes = (((r_refdef.scene.worldmodel->brush.shadowmesh ? r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles : r_refdef.scene.worldmodel->surfmesh.num_triangles) + 7) >> 3); + int numlighttrispvsbytes = ((r_refdef.scene.worldmodel->surfmesh.num_triangles + 7) >> 3); + rtlight->cached_shadowtrispvs = (unsigned char *)R_FrameData_Store(numshadowtrispvsbytes, shadowtrispvs); + rtlight->cached_lighttrispvs = (unsigned char *)R_FrameData_Store(numlighttrispvsbytes, lighttrispvs); + rtlight->cached_surfacelist = (int*)R_FrameData_Store(numsurfaces*sizeof(int), (void*)surfacelist); + } + else + { + // compiled light data + rtlight->cached_shadowtrispvs = shadowtrispvs; + rtlight->cached_lighttrispvs = lighttrispvs; + rtlight->cached_surfacelist = surfacelist; + } +} + +void R_Shadow_DrawLight(rtlight_t *rtlight) +{ + int i; + int numsurfaces; + unsigned char *shadowtrispvs, *lighttrispvs, *surfacesides; + int numlightentities; + int numlightentities_noselfshadow; + int numshadowentities; + int numshadowentities_noselfshadow; + entity_render_t **lightentities; + entity_render_t **lightentities_noselfshadow; + entity_render_t **shadowentities; + entity_render_t **shadowentities_noselfshadow; + int *surfacelist; + static unsigned char entitysides[MAX_EDICTS]; + static unsigned char entitysides_noselfshadow[MAX_EDICTS]; + vec3_t nearestpoint; + vec_t distance; + qboolean castshadows; + int lodlinear; + + // check if we cached this light this frame (meaning it is worth drawing) + if (!rtlight->draw) + return; + + numlightentities = rtlight->cached_numlightentities; + numlightentities_noselfshadow = rtlight->cached_numlightentities_noselfshadow; + numshadowentities = rtlight->cached_numshadowentities; + numshadowentities_noselfshadow = rtlight->cached_numshadowentities_noselfshadow; + numsurfaces = rtlight->cached_numsurfaces; + lightentities = rtlight->cached_lightentities; + lightentities_noselfshadow = rtlight->cached_lightentities_noselfshadow; + shadowentities = rtlight->cached_shadowentities; + shadowentities_noselfshadow = rtlight->cached_shadowentities_noselfshadow; + shadowtrispvs = rtlight->cached_shadowtrispvs; + lighttrispvs = rtlight->cached_lighttrispvs; + surfacelist = rtlight->cached_surfacelist; + + // set up a scissor rectangle for this light + if (R_Shadow_ScissorForBBox(rtlight->cached_cullmins, rtlight->cached_cullmaxs)) + return; + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + // make this the active rtlight for rendering purposes + R_Shadow_RenderMode_ActiveLight(rtlight); + + if (r_showshadowvolumes.integer && r_refdef.view.showdebug && numsurfaces + numshadowentities + numshadowentities_noselfshadow && rtlight->shadow && (rtlight->isstatic ? r_refdef.scene.rtworldshadows : r_refdef.scene.rtdlightshadows)) + { + // optionally draw visible shape of the shadow volumes + // for performance analysis by level designers + R_Shadow_RenderMode_VisibleShadowVolumes(); + if (numsurfaces) + R_Shadow_DrawWorldShadow_ShadowVolume(numsurfaces, surfacelist, shadowtrispvs); + for (i = 0;i < numshadowentities;i++) + R_Shadow_DrawEntityShadow(shadowentities[i]); + for (i = 0;i < numshadowentities_noselfshadow;i++) + R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); + R_Shadow_RenderMode_VisibleLighting(false, false); + } + + if (r_showlighting.integer && r_refdef.view.showdebug && numsurfaces + numlightentities + numlightentities_noselfshadow) + { + // optionally draw the illuminated areas + // for performance analysis by level designers + R_Shadow_RenderMode_VisibleLighting(false, false); + if (numsurfaces) + R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); + for (i = 0;i < numlightentities;i++) + R_Shadow_DrawEntityLight(lightentities[i]); + for (i = 0;i < numlightentities_noselfshadow;i++) + R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); + } + + castshadows = numsurfaces + numshadowentities + numshadowentities_noselfshadow > 0 && rtlight->shadow && (rtlight->isstatic ? r_refdef.scene.rtworldshadows : r_refdef.scene.rtdlightshadows); + + nearestpoint[0] = bound(rtlight->cullmins[0], r_refdef.view.origin[0], rtlight->cullmaxs[0]); + nearestpoint[1] = bound(rtlight->cullmins[1], r_refdef.view.origin[1], rtlight->cullmaxs[1]); + nearestpoint[2] = bound(rtlight->cullmins[2], r_refdef.view.origin[2], rtlight->cullmaxs[2]); + distance = VectorDistance(nearestpoint, r_refdef.view.origin); + + lodlinear = (rtlight->radius * r_shadow_shadowmapping_precision.value) / sqrt(max(1.0f, distance/rtlight->radius)); + //lodlinear = (int)(r_shadow_shadowmapping_lod_bias.value + r_shadow_shadowmapping_lod_scale.value * rtlight->radius / max(1.0f, distance)); + lodlinear = bound(r_shadow_shadowmapping_minsize.integer, lodlinear, r_shadow_shadowmapmaxsize); + + if (castshadows && r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAP2D) + { + float borderbias; + int side; + int size; + int castermask = 0; + int receivermask = 0; + matrix4x4_t radiustolight = rtlight->matrix_worldtolight; + Matrix4x4_Abs(&radiustolight); + + r_shadow_shadowmaplod = 0; + for (i = 1;i < R_SHADOW_SHADOWMAP_NUMCUBEMAPS;i++) + if ((r_shadow_shadowmapmaxsize >> i) > lodlinear) + r_shadow_shadowmaplod = i; + + size = bound(r_shadow_shadowmapborder, lodlinear, r_shadow_shadowmapmaxsize); + + borderbias = r_shadow_shadowmapborder / (float)(size - r_shadow_shadowmapborder); + + surfacesides = NULL; + if (numsurfaces) + { + if (rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) + { + castermask = rtlight->static_shadowmap_casters; + receivermask = rtlight->static_shadowmap_receivers; + } + else + { + surfacesides = r_shadow_buffer_surfacesides; + for(i = 0;i < numsurfaces;i++) + { + msurface_t *surface = r_refdef.scene.worldmodel->data_surfaces + surfacelist[i]; + surfacesides[i] = R_Shadow_CalcBBoxSideMask(surface->mins, surface->maxs, &rtlight->matrix_worldtolight, &radiustolight, borderbias); + castermask |= surfacesides[i]; + receivermask |= surfacesides[i]; + } + } + } + if (receivermask < 0x3F) + { + for (i = 0;i < numlightentities;i++) + receivermask |= R_Shadow_CalcEntitySideMask(lightentities[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias); + if (receivermask < 0x3F) + for(i = 0; i < numlightentities_noselfshadow;i++) + receivermask |= R_Shadow_CalcEntitySideMask(lightentities_noselfshadow[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias); + } + + receivermask &= R_Shadow_CullFrustumSides(rtlight, size, r_shadow_shadowmapborder); + + if (receivermask) + { + for (i = 0;i < numshadowentities;i++) + castermask |= (entitysides[i] = R_Shadow_CalcEntitySideMask(shadowentities[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias)); + for (i = 0;i < numshadowentities_noselfshadow;i++) + castermask |= (entitysides_noselfshadow[i] = R_Shadow_CalcEntitySideMask(shadowentities_noselfshadow[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias)); + } + + //Con_Printf("distance %f lodlinear %i (lod %i) size %i\n", distance, lodlinear, r_shadow_shadowmaplod, size); + + // render shadow casters into 6 sided depth texture + for (side = 0;side < 6;side++) if (receivermask & (1 << side)) + { + R_Shadow_RenderMode_ShadowMap(side, receivermask, size); + if (! (castermask & (1 << side))) continue; + if (numsurfaces) + R_Shadow_DrawWorldShadow_ShadowMap(numsurfaces, surfacelist, shadowtrispvs, surfacesides); + for (i = 0;i < numshadowentities;i++) if (entitysides[i] & (1 << side)) + R_Shadow_DrawEntityShadow(shadowentities[i]); + } + + if (numlightentities_noselfshadow) + { + // render lighting using the depth texture as shadowmap + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(false, false, true); + for (i = 0;i < numlightentities_noselfshadow;i++) + R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); + } + + // render shadow casters into 6 sided depth texture + if (numshadowentities_noselfshadow) + { + for (side = 0;side < 6;side++) if ((receivermask & castermask) & (1 << side)) + { + R_Shadow_RenderMode_ShadowMap(side, 0, size); + for (i = 0;i < numshadowentities_noselfshadow;i++) if (entitysides_noselfshadow[i] & (1 << side)) + R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); + } + } + + // render lighting using the depth texture as shadowmap + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(false, false, true); + // draw lighting in the unmasked areas + if (numsurfaces) + R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); + for (i = 0;i < numlightentities;i++) + R_Shadow_DrawEntityLight(lightentities[i]); + } + else if (castshadows && vid.stencil) + { + // draw stencil shadow volumes to mask off pixels that are in shadow + // so that they won't receive lighting + GL_Scissor(r_shadow_lightscissor[0], r_shadow_lightscissor[1], r_shadow_lightscissor[2], r_shadow_lightscissor[3]); + R_Shadow_ClearStencil(); + + if (numsurfaces) + R_Shadow_DrawWorldShadow_ShadowVolume(numsurfaces, surfacelist, shadowtrispvs); + for (i = 0;i < numshadowentities;i++) + R_Shadow_DrawEntityShadow(shadowentities[i]); + + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(true, false, false); + for (i = 0;i < numlightentities_noselfshadow;i++) + R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); + + for (i = 0;i < numshadowentities_noselfshadow;i++) + R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); + + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(true, false, false); + if (numsurfaces) + R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); + for (i = 0;i < numlightentities;i++) + R_Shadow_DrawEntityLight(lightentities[i]); + } + else + { + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(false, false, false); + if (numsurfaces) + R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); + for (i = 0;i < numlightentities;i++) + R_Shadow_DrawEntityLight(lightentities[i]); + for (i = 0;i < numlightentities_noselfshadow;i++) + R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); + } + + if (r_shadow_usingdeferredprepass) + { + // when rendering deferred lighting, we simply rasterize the box + if (castshadows && r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAP2D) + R_Shadow_RenderMode_DrawDeferredLight(false, true); + else if (castshadows && vid.stencil) + R_Shadow_RenderMode_DrawDeferredLight(true, false); + else + R_Shadow_RenderMode_DrawDeferredLight(false, false); + } +} + +static void R_Shadow_FreeDeferred(void) +{ + R_Mesh_DestroyFramebufferObject(r_shadow_prepassgeometryfbo); + r_shadow_prepassgeometryfbo = 0; + + R_Mesh_DestroyFramebufferObject(r_shadow_prepasslightingdiffusespecularfbo); + r_shadow_prepasslightingdiffusespecularfbo = 0; + + R_Mesh_DestroyFramebufferObject(r_shadow_prepasslightingdiffusefbo); + r_shadow_prepasslightingdiffusefbo = 0; + + if (r_shadow_prepassgeometrydepthtexture) + R_FreeTexture(r_shadow_prepassgeometrydepthtexture); + r_shadow_prepassgeometrydepthtexture = NULL; + + if (r_shadow_prepassgeometrydepthcolortexture) + R_FreeTexture(r_shadow_prepassgeometrydepthcolortexture); + r_shadow_prepassgeometrydepthcolortexture = NULL; + + if (r_shadow_prepassgeometrynormalmaptexture) + R_FreeTexture(r_shadow_prepassgeometrynormalmaptexture); + r_shadow_prepassgeometrynormalmaptexture = NULL; + + if (r_shadow_prepasslightingdiffusetexture) + R_FreeTexture(r_shadow_prepasslightingdiffusetexture); + r_shadow_prepasslightingdiffusetexture = NULL; + + if (r_shadow_prepasslightingspeculartexture) + R_FreeTexture(r_shadow_prepasslightingspeculartexture); + r_shadow_prepasslightingspeculartexture = NULL; +} + +void R_Shadow_DrawPrepass(void) +{ + int i; + int flag; + int lnum; + size_t lightindex; + dlight_t *light; + size_t range; + entity_render_t *ent; + float clearcolor[4]; + + R_Mesh_ResetTextureState(); + GL_DepthMask(true); + GL_ColorMask(1,1,1,1); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_Color(1,1,1,1); + GL_DepthTest(true); + R_Mesh_SetRenderTargets(r_shadow_prepassgeometryfbo, r_shadow_prepassgeometrydepthtexture, r_shadow_prepassgeometrynormalmaptexture, r_shadow_prepassgeometrydepthcolortexture, NULL, NULL); + Vector4Set(clearcolor, 0.5f,0.5f,0.5f,1.0f); + GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + if (r_timereport_active) + R_TimeReport("prepasscleargeom"); + + if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawPrepass) + r_refdef.scene.worldmodel->DrawPrepass(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("prepassworld"); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawPrepass != NULL) + ent->model->DrawPrepass(ent); + } + + if (r_timereport_active) + R_TimeReport("prepassmodels"); + + GL_DepthMask(false); + GL_ColorMask(1,1,1,1); + GL_Color(1,1,1,1); + GL_DepthTest(true); + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthtexture, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); + Vector4Set(clearcolor, 0, 0, 0, 0); + GL_Clear(GL_COLOR_BUFFER_BIT, clearcolor, 1.0f, 0); + if (r_timereport_active) + R_TimeReport("prepassclearlit"); + + R_Shadow_RenderMode_Begin(); + + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + if (r_shadow_debuglight.integer >= 0) + { + lightindex = r_shadow_debuglight.integer; + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light && (light->flags & flag) && light->rtlight.draw) + R_Shadow_DrawLight(&light->rtlight); + } + else + { + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light && (light->flags & flag) && light->rtlight.draw) + R_Shadow_DrawLight(&light->rtlight); + } + } + if (r_refdef.scene.rtdlight) + for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) + if (r_refdef.scene.lights[lnum]->draw) + R_Shadow_DrawLight(r_refdef.scene.lights[lnum]); + + R_Mesh_SetMainRenderTargets(); + + R_Shadow_RenderMode_End(); + + if (r_timereport_active) + R_TimeReport("prepasslights"); +} + +void R_Shadow_DrawLightSprites(void); +void R_Shadow_PrepareLights(void) +{ + int flag; + int lnum; + size_t lightindex; + dlight_t *light; + size_t range; + float f; + GLenum status; + + if (r_shadow_shadowmapmaxsize != bound(1, r_shadow_shadowmapping_maxsize.integer, (int)vid.maxtexturesize_2d / 4) || + (r_shadow_shadowmode != R_SHADOW_SHADOWMODE_STENCIL) != (r_shadow_shadowmapping.integer || r_shadow_deferred.integer) || + r_shadow_shadowmapvsdct != (r_shadow_shadowmapping_vsdct.integer != 0 && vid.renderpath == RENDERPATH_GL20) || + r_shadow_shadowmapfilterquality != r_shadow_shadowmapping_filterquality.integer || + r_shadow_shadowmapdepthbits != r_shadow_shadowmapping_depthbits.integer || + r_shadow_shadowmapborder != bound(0, r_shadow_shadowmapping_bordersize.integer, 16)) + R_Shadow_FreeShadowMaps(); + + r_shadow_usingshadowmaportho = false; + + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + if (!r_shadow_deferred.integer || r_shadow_shadowmode == R_SHADOW_SHADOWMODE_STENCIL || !vid.support.ext_framebuffer_object || vid.maxdrawbuffers < 2) + { + r_shadow_usingdeferredprepass = false; + if (r_shadow_prepass_width) + R_Shadow_FreeDeferred(); + r_shadow_prepass_width = r_shadow_prepass_height = 0; + break; + } + + if (r_shadow_prepass_width != vid.width || r_shadow_prepass_height != vid.height) + { + R_Shadow_FreeDeferred(); + + r_shadow_usingdeferredprepass = true; + r_shadow_prepass_width = vid.width; + r_shadow_prepass_height = vid.height; + r_shadow_prepassgeometrydepthtexture = R_LoadTextureShadowMap2D(r_shadow_texturepool, "prepassgeometrydepthmap", vid.width, vid.height, 24, false); + switch (vid.renderpath) + { + case RENDERPATH_D3D9: + r_shadow_prepassgeometrydepthcolortexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrydepthcolormap", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + break; + default: + break; + } + r_shadow_prepassgeometrynormalmaptexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrynormalmap", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + r_shadow_prepasslightingdiffusetexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingdiffuse", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + r_shadow_prepasslightingspeculartexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingspecular", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + + // set up the geometry pass fbo (depth + normalmap) + r_shadow_prepassgeometryfbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthtexture, r_shadow_prepassgeometrynormalmaptexture, NULL, NULL, NULL); + R_Mesh_SetRenderTargets(r_shadow_prepassgeometryfbo, r_shadow_prepassgeometrydepthtexture, r_shadow_prepassgeometrynormalmaptexture, r_shadow_prepassgeometrydepthcolortexture, NULL, NULL); + // render depth into one texture and normalmap into the other + if (qglDrawBuffersARB) + { + qglDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + status = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);CHECKGLERROR + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) + { + Con_Printf("R_PrepareRTLights: glCheckFramebufferStatusEXT returned %i\n", status); + Cvar_SetValueQuick(&r_shadow_deferred, 0); + r_shadow_usingdeferredprepass = false; + } + } + + // set up the lighting pass fbo (diffuse + specular) + r_shadow_prepasslightingdiffusespecularfbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthtexture, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthtexture, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); + // render diffuse into one texture and specular into another, + // with depth and normalmap bound as textures, + // with depth bound as attachment as well + if (qglDrawBuffersARB) + { + qglDrawBuffersARB(2, r_shadow_prepasslightingdrawbuffers);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + status = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);CHECKGLERROR + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) + { + Con_Printf("R_PrepareRTLights: glCheckFramebufferStatusEXT returned %i\n", status); + Cvar_SetValueQuick(&r_shadow_deferred, 0); + r_shadow_usingdeferredprepass = false; + } + } + + // set up the lighting pass fbo (diffuse) + r_shadow_prepasslightingdiffusefbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthtexture, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusefbo, r_shadow_prepassgeometrydepthtexture, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); + // render diffuse into one texture, + // with depth and normalmap bound as textures, + // with depth bound as attachment as well + if (qglDrawBuffersARB) + { + qglDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + status = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);CHECKGLERROR + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) + { + Con_Printf("R_PrepareRTLights: glCheckFramebufferStatusEXT returned %i\n", status); + Cvar_SetValueQuick(&r_shadow_deferred, 0); + r_shadow_usingdeferredprepass = false; + } + } + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + r_shadow_usingdeferredprepass = false; + break; + } + + R_Shadow_EnlargeLeafSurfaceTrisBuffer(r_refdef.scene.worldmodel->brush.num_leafs, r_refdef.scene.worldmodel->num_surfaces, r_refdef.scene.worldmodel->brush.shadowmesh ? r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles : r_refdef.scene.worldmodel->surfmesh.num_triangles, r_refdef.scene.worldmodel->surfmesh.num_triangles); + + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + if (r_shadow_bouncegrid.integer != 2) + { + if (r_shadow_debuglight.integer >= 0) + { + lightindex = r_shadow_debuglight.integer; + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light) + R_Shadow_PrepareLight(&light->rtlight); + } + else + { + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light && (light->flags & flag)) + R_Shadow_PrepareLight(&light->rtlight); + } + } + } + if (r_refdef.scene.rtdlight) + { + for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) + R_Shadow_PrepareLight(r_refdef.scene.lights[lnum]); + } + else if(gl_flashblend.integer) + { + for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) + { + rtlight_t *rtlight = r_refdef.scene.lights[lnum]; + f = (rtlight->style >= 0 ? r_refdef.scene.lightstylevalue[rtlight->style] : 1) * r_shadow_lightintensityscale.value; + VectorScale(rtlight->color, f, rtlight->currentcolor); + } + } + + if (r_editlights.integer) + R_Shadow_DrawLightSprites(); +} + +void R_Shadow_DrawLights(void) +{ + int flag; + int lnum; + size_t lightindex; + dlight_t *light; + size_t range; + + R_Shadow_RenderMode_Begin(); + + if (r_shadow_bouncegrid.integer != 2) + { + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + if (r_shadow_debuglight.integer >= 0) + { + lightindex = r_shadow_debuglight.integer; + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light) + R_Shadow_DrawLight(&light->rtlight); + } + else + { + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light && (light->flags & flag)) + R_Shadow_DrawLight(&light->rtlight); + } + } + } + if (r_refdef.scene.rtdlight) + for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) + R_Shadow_DrawLight(r_refdef.scene.lights[lnum]); + + R_Shadow_RenderMode_End(); +} + +extern const float r_screenvertex3f[12]; +extern void R_SetupView(qboolean allowwaterclippingplane); +extern void R_ResetViewRendering3D(void); +extern void R_ResetViewRendering2D(void); +extern cvar_t r_shadows; +extern cvar_t r_shadows_darken; +extern cvar_t r_shadows_drawafterrtlighting; +extern cvar_t r_shadows_castfrombmodels; +extern cvar_t r_shadows_throwdistance; +extern cvar_t r_shadows_throwdirection; +extern cvar_t r_shadows_focus; +extern cvar_t r_shadows_shadowmapscale; + +void R_Shadow_PrepareModelShadows(void) +{ + int i; + float scale, size, radius, dot1, dot2; + vec3_t shadowdir, shadowforward, shadowright, shadoworigin, shadowfocus, shadowmins, shadowmaxs; + entity_render_t *ent; + + if (!r_refdef.scene.numentities) + return; + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (r_shadows.integer >= 2) + break; + // fall through + case R_SHADOW_SHADOWMODE_STENCIL: + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + if (!ent->animcache_vertex3f && ent->model && ent->model->DrawShadowVolume != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + R_AnimCache_GetEntity(ent, false, false); + } + return; + default: + return; + } + + size = 2*r_shadow_shadowmapmaxsize; + scale = r_shadow_shadowmapping_precision.value * r_shadows_shadowmapscale.value; + radius = 0.5f * size / scale; + + Math_atov(r_shadows_throwdirection.string, shadowdir); + VectorNormalize(shadowdir); + dot1 = DotProduct(r_refdef.view.forward, shadowdir); + dot2 = DotProduct(r_refdef.view.up, shadowdir); + if (fabs(dot1) <= fabs(dot2)) + VectorMA(r_refdef.view.forward, -dot1, shadowdir, shadowforward); + else + VectorMA(r_refdef.view.up, -dot2, shadowdir, shadowforward); + VectorNormalize(shadowforward); + CrossProduct(shadowdir, shadowforward, shadowright); + Math_atov(r_shadows_focus.string, shadowfocus); + VectorM(shadowfocus[0], r_refdef.view.right, shadoworigin); + VectorMA(shadoworigin, shadowfocus[1], r_refdef.view.up, shadoworigin); + VectorMA(shadoworigin, -shadowfocus[2], r_refdef.view.forward, shadoworigin); + VectorAdd(shadoworigin, r_refdef.view.origin, shadoworigin); + if (shadowfocus[0] || shadowfocus[1] || shadowfocus[2]) + dot1 = 1; + VectorMA(shadoworigin, (1.0f - fabs(dot1)) * radius, shadowforward, shadoworigin); + + shadowmins[0] = shadoworigin[0] - r_shadows_throwdistance.value * fabs(shadowdir[0]) - radius * (fabs(shadowforward[0]) + fabs(shadowright[0])); + shadowmins[1] = shadoworigin[1] - r_shadows_throwdistance.value * fabs(shadowdir[1]) - radius * (fabs(shadowforward[1]) + fabs(shadowright[1])); + shadowmins[2] = shadoworigin[2] - r_shadows_throwdistance.value * fabs(shadowdir[2]) - radius * (fabs(shadowforward[2]) + fabs(shadowright[2])); + shadowmaxs[0] = shadoworigin[0] + r_shadows_throwdistance.value * fabs(shadowdir[0]) + radius * (fabs(shadowforward[0]) + fabs(shadowright[0])); + shadowmaxs[1] = shadoworigin[1] + r_shadows_throwdistance.value * fabs(shadowdir[1]) + radius * (fabs(shadowforward[1]) + fabs(shadowright[1])); + shadowmaxs[2] = shadoworigin[2] + r_shadows_throwdistance.value * fabs(shadowdir[2]) + radius * (fabs(shadowforward[2]) + fabs(shadowright[2])); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + if (!BoxesOverlap(ent->mins, ent->maxs, shadowmins, shadowmaxs)) + continue; + // cast shadows from anything of the map (submodels are optional) + if (!ent->animcache_vertex3f && ent->model && ent->model->DrawShadowMap != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + R_AnimCache_GetEntity(ent, false, false); + } +} + +void R_DrawModelShadowMaps(void) +{ + int i; + float relativethrowdistance, scale, size, radius, nearclip, farclip, bias, dot1, dot2; + entity_render_t *ent; + vec3_t relativelightorigin; + vec3_t relativelightdirection, relativeforward, relativeright; + vec3_t relativeshadowmins, relativeshadowmaxs; + vec3_t shadowdir, shadowforward, shadowright, shadoworigin, shadowfocus; + float m[12]; + matrix4x4_t shadowmatrix, cameramatrix, mvpmatrix, invmvpmatrix, scalematrix, texmatrix; + r_viewport_t viewport; + GLuint fbo = 0; + float clearcolor[4]; + + if (!r_refdef.scene.numentities) + return; + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + break; + default: + return; + } + + R_ResetViewRendering3D(); + R_Shadow_RenderMode_Begin(); + R_Shadow_RenderMode_ActiveLight(NULL); + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (!r_shadow_shadowmap2dtexture) + R_Shadow_MakeShadowMap(0, r_shadow_shadowmapmaxsize); + fbo = r_shadow_fbo2d; + r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2dtexture); + r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2dtexture); + r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D; + break; + default: + break; + } + + size = 2*r_shadow_shadowmapmaxsize; + scale = (r_shadow_shadowmapping_precision.value * r_shadows_shadowmapscale.value) / size; + radius = 0.5f / scale; + nearclip = -r_shadows_throwdistance.value; + farclip = r_shadows_throwdistance.value; + bias = r_shadow_shadowmapping_bias.value * r_shadow_shadowmapping_nearclip.value / (2 * r_shadows_throwdistance.value) * (1024.0f / size); + + r_shadow_shadowmap_parameters[0] = size; + r_shadow_shadowmap_parameters[1] = size; + r_shadow_shadowmap_parameters[2] = 1.0; + r_shadow_shadowmap_parameters[3] = bound(0.0f, 1.0f - r_shadows_darken.value, 1.0f); + + Math_atov(r_shadows_throwdirection.string, shadowdir); + VectorNormalize(shadowdir); + Math_atov(r_shadows_focus.string, shadowfocus); + VectorM(shadowfocus[0], r_refdef.view.right, shadoworigin); + VectorMA(shadoworigin, shadowfocus[1], r_refdef.view.up, shadoworigin); + VectorMA(shadoworigin, -shadowfocus[2], r_refdef.view.forward, shadoworigin); + VectorAdd(shadoworigin, r_refdef.view.origin, shadoworigin); + dot1 = DotProduct(r_refdef.view.forward, shadowdir); + dot2 = DotProduct(r_refdef.view.up, shadowdir); + if (fabs(dot1) <= fabs(dot2)) + VectorMA(r_refdef.view.forward, -dot1, shadowdir, shadowforward); + else + VectorMA(r_refdef.view.up, -dot2, shadowdir, shadowforward); + VectorNormalize(shadowforward); + VectorM(scale, shadowforward, &m[0]); + if (shadowfocus[0] || shadowfocus[1] || shadowfocus[2]) + dot1 = 1; + m[3] = fabs(dot1) * 0.5f - DotProduct(shadoworigin, &m[0]); + CrossProduct(shadowdir, shadowforward, shadowright); + VectorM(scale, shadowright, &m[4]); + m[7] = 0.5f - DotProduct(shadoworigin, &m[4]); + VectorM(1.0f / (farclip - nearclip), shadowdir, &m[8]); + m[11] = 0.5f - DotProduct(shadoworigin, &m[8]); + Matrix4x4_FromArray12FloatD3D(&shadowmatrix, m); + Matrix4x4_Invert_Full(&cameramatrix, &shadowmatrix); + R_Viewport_InitOrtho(&viewport, &cameramatrix, 0, 0, size, size, 0, 0, 1, 1, 0, -1, NULL); + + VectorMA(shadoworigin, (1.0f - fabs(dot1)) * radius, shadowforward, shadoworigin); + + R_Mesh_SetRenderTargets(fbo, r_shadow_shadowmap2dtexture, r_shadow_shadowmap2dcolortexture, NULL, NULL, NULL); + R_SetupShader_DepthOrShadow(true); + GL_PolygonOffset(r_shadow_shadowmapping_polygonfactor.value, r_shadow_shadowmapping_polygonoffset.value); + GL_DepthMask(true); + GL_DepthTest(true); + R_SetViewport(&viewport); + GL_Scissor(viewport.x, viewport.y, min(viewport.width + r_shadow_shadowmapborder, 2*r_shadow_shadowmapmaxsize), viewport.height + r_shadow_shadowmapborder); + Vector4Set(clearcolor, 1,1,1,1); + // in D3D9 we have to render to a color texture shadowmap + // in GL we render directly to a depth texture only + if (r_shadow_shadowmap2dtexture) + GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + else + GL_Clear(GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + // render into a slightly restricted region so that the borders of the + // shadowmap area fade away, rather than streaking across everything + // outside the usable area + GL_Scissor(viewport.x + r_shadow_shadowmapborder, viewport.y + r_shadow_shadowmapborder, viewport.width - 2*r_shadow_shadowmapborder, viewport.height - 2*r_shadow_shadowmapborder); + +#if 0 + // debugging + R_Mesh_SetMainRenderTargets(); + R_SetupShader_ShowDepth(true); + GL_ColorMask(1,1,1,1); + GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); +#endif + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + + // cast shadows from anything of the map (submodels are optional) + if (ent->model && ent->model->DrawShadowMap != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + { + relativethrowdistance = r_shadows_throwdistance.value * Matrix4x4_ScaleFromMatrix(&ent->inversematrix); + Matrix4x4_Transform(&ent->inversematrix, shadoworigin, relativelightorigin); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowdir, relativelightdirection); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowforward, relativeforward); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowright, relativeright); + relativeshadowmins[0] = relativelightorigin[0] - r_shadows_throwdistance.value * fabs(relativelightdirection[0]) - radius * (fabs(relativeforward[0]) + fabs(relativeright[0])); + relativeshadowmins[1] = relativelightorigin[1] - r_shadows_throwdistance.value * fabs(relativelightdirection[1]) - radius * (fabs(relativeforward[1]) + fabs(relativeright[1])); + relativeshadowmins[2] = relativelightorigin[2] - r_shadows_throwdistance.value * fabs(relativelightdirection[2]) - radius * (fabs(relativeforward[2]) + fabs(relativeright[2])); + relativeshadowmaxs[0] = relativelightorigin[0] + r_shadows_throwdistance.value * fabs(relativelightdirection[0]) + radius * (fabs(relativeforward[0]) + fabs(relativeright[0])); + relativeshadowmaxs[1] = relativelightorigin[1] + r_shadows_throwdistance.value * fabs(relativelightdirection[1]) + radius * (fabs(relativeforward[1]) + fabs(relativeright[1])); + relativeshadowmaxs[2] = relativelightorigin[2] + r_shadows_throwdistance.value * fabs(relativelightdirection[2]) + radius * (fabs(relativeforward[2]) + fabs(relativeright[2])); + RSurf_ActiveModelEntity(ent, false, false, false); + ent->model->DrawShadowMap(0, ent, relativelightorigin, relativelightdirection, relativethrowdistance, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, NULL, relativeshadowmins, relativeshadowmaxs); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + } + } + +#if 0 + if (r_test.integer) + { + unsigned char *rawpixels = Z_Malloc(viewport.width*viewport.height*4); + CHECKGLERROR + qglReadPixels(viewport.x, viewport.y, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, rawpixels); + CHECKGLERROR + Image_WriteTGABGRA("r_shadows_2.tga", viewport.width, viewport.height, rawpixels); + Cvar_SetValueQuick(&r_test, 0); + Z_Free(rawpixels); + } +#endif + + R_Shadow_RenderMode_End(); + + Matrix4x4_Concat(&mvpmatrix, &r_refdef.view.viewport.projectmatrix, &r_refdef.view.viewport.viewmatrix); + Matrix4x4_Invert_Full(&invmvpmatrix, &mvpmatrix); + Matrix4x4_CreateScale3(&scalematrix, size, -size, 1); + Matrix4x4_AdjustOrigin(&scalematrix, 0, size, -0.5f * bias); + Matrix4x4_Concat(&texmatrix, &scalematrix, &shadowmatrix); + Matrix4x4_Concat(&r_shadow_shadowmapmatrix, &texmatrix, &invmvpmatrix); + + switch (vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: +#ifdef OPENGL_ORIENTATION + r_shadow_shadowmapmatrix.m[0][0] *= -1.0f; + r_shadow_shadowmapmatrix.m[0][1] *= -1.0f; + r_shadow_shadowmapmatrix.m[0][2] *= -1.0f; + r_shadow_shadowmapmatrix.m[0][3] *= -1.0f; +#else + r_shadow_shadowmapmatrix.m[0][0] *= -1.0f; + r_shadow_shadowmapmatrix.m[1][0] *= -1.0f; + r_shadow_shadowmapmatrix.m[2][0] *= -1.0f; + r_shadow_shadowmapmatrix.m[3][0] *= -1.0f; +#endif + break; + } + + r_shadow_usingshadowmaportho = true; + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + r_shadow_usingshadowmap2d = true; + break; + default: + break; + } +} + +void R_DrawModelShadows(void) +{ + int i; + float relativethrowdistance; + entity_render_t *ent; + vec3_t relativelightorigin; + vec3_t relativelightdirection; + vec3_t relativeshadowmins, relativeshadowmaxs; + vec3_t tmp, shadowdir; + + if (!r_refdef.scene.numentities || !vid.stencil || (r_shadow_shadowmode != R_SHADOW_SHADOWMODE_STENCIL && r_shadows.integer != 1)) + return; + + R_ResetViewRendering3D(); + //GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + //GL_Scissor(r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height); + R_Shadow_RenderMode_Begin(); + R_Shadow_RenderMode_ActiveLight(NULL); + r_shadow_lightscissor[0] = r_refdef.view.x; + r_shadow_lightscissor[1] = vid.height - r_refdef.view.y - r_refdef.view.height; + r_shadow_lightscissor[2] = r_refdef.view.width; + r_shadow_lightscissor[3] = r_refdef.view.height; + R_Shadow_RenderMode_StencilShadowVolumes(false); + + // get shadow dir + if (r_shadows.integer == 2) + { + Math_atov(r_shadows_throwdirection.string, shadowdir); + VectorNormalize(shadowdir); + } + + R_Shadow_ClearStencil(); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + + // cast shadows from anything of the map (submodels are optional) + if (ent->model && ent->model->DrawShadowVolume != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + { + relativethrowdistance = r_shadows_throwdistance.value * Matrix4x4_ScaleFromMatrix(&ent->inversematrix); + VectorSet(relativeshadowmins, -relativethrowdistance, -relativethrowdistance, -relativethrowdistance); + VectorSet(relativeshadowmaxs, relativethrowdistance, relativethrowdistance, relativethrowdistance); + if (r_shadows.integer == 2) // 2: simpler mode, throw shadows always in same direction + Matrix4x4_Transform3x3(&ent->inversematrix, shadowdir, relativelightdirection); + else + { + if(ent->entitynumber != 0) + { + if(ent->entitynumber >= MAX_EDICTS) // csqc entity + { + // FIXME handle this + VectorNegate(ent->modellight_lightdir, relativelightdirection); + } + else + { + // networked entity - might be attached in some way (then we should use the parent's light direction, to not tear apart attached entities) + int entnum, entnum2, recursion; + entnum = entnum2 = ent->entitynumber; + for(recursion = 32; recursion > 0; --recursion) + { + entnum2 = cl.entities[entnum].state_current.tagentity; + if(entnum2 >= 1 && entnum2 < cl.num_entities && cl.entities_active[entnum2]) + entnum = entnum2; + else + break; + } + if(recursion && recursion != 32) // if we followed a valid non-empty attachment chain + { + VectorNegate(cl.entities[entnum].render.modellight_lightdir, relativelightdirection); + // transform into modelspace of OUR entity + Matrix4x4_Transform3x3(&cl.entities[entnum].render.matrix, relativelightdirection, tmp); + Matrix4x4_Transform3x3(&ent->inversematrix, tmp, relativelightdirection); + } + else + VectorNegate(ent->modellight_lightdir, relativelightdirection); + } + } + else + VectorNegate(ent->modellight_lightdir, relativelightdirection); + } + + VectorScale(relativelightdirection, -relativethrowdistance, relativelightorigin); + RSurf_ActiveModelEntity(ent, false, false, false); + ent->model->DrawShadowVolume(ent, relativelightorigin, relativelightdirection, relativethrowdistance, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, relativeshadowmins, relativeshadowmaxs); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + } + } + + // not really the right mode, but this will disable any silly stencil features + R_Shadow_RenderMode_End(); + + // set up ortho view for rendering this pass + //GL_Scissor(r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height); + //GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + //GL_ScissorTest(true); + //R_EntityMatrix(&identitymatrix); + //R_Mesh_ResetTextureState(); + R_ResetViewRendering2D(); + + // set up a darkening blend on shadowed areas + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //GL_DepthRange(0, 1); + //GL_DepthTest(false); + //GL_DepthMask(false); + //GL_PolygonOffset(0, 0);CHECKGLERROR + GL_Color(0, 0, 0, r_shadows_darken.value); + //GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + //GL_DepthFunc(GL_ALWAYS); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_NOTEQUAL, 128, 255); + + // apply the blend to the shadowed areas + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + + // restore the viewport + R_SetViewport(&r_refdef.view.viewport); + + // restore other state to normal + //R_Shadow_RenderMode_End(); +} + +void R_BeginCoronaQuery(rtlight_t *rtlight, float scale, qboolean usequery) +{ + float zdist; + vec3_t centerorigin; + float vertex3f[12]; + // if it's too close, skip it + if (VectorLength(rtlight->currentcolor) < (1.0f / 256.0f)) + return; + zdist = (DotProduct(rtlight->shadoworigin, r_refdef.view.forward) - DotProduct(r_refdef.view.origin, r_refdef.view.forward)); + if (zdist < 32) + return; + if (usequery && r_numqueries + 2 <= r_maxqueries) + { + rtlight->corona_queryindex_allpixels = r_queries[r_numqueries++]; + rtlight->corona_queryindex_visiblepixels = r_queries[r_numqueries++]; + // we count potential samples in the middle of the screen, we count actual samples at the light location, this allows counting potential samples of off-screen lights + VectorMA(r_refdef.view.origin, zdist, r_refdef.view.forward, centerorigin); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + // NOTE: GL_DEPTH_TEST must be enabled or ATI won't count samples, so use GL_DepthFunc instead + qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, rtlight->corona_queryindex_allpixels); + GL_DepthFunc(GL_ALWAYS); + R_CalcSprite_Vertex3f(vertex3f, centerorigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); + R_Mesh_PrepareVertices_Vertex3f(4, vertex3f, NULL); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + qglEndQueryARB(GL_SAMPLES_PASSED_ARB); + GL_DepthFunc(GL_LEQUAL); + qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, rtlight->corona_queryindex_visiblepixels); + R_CalcSprite_Vertex3f(vertex3f, rtlight->shadoworigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); + R_Mesh_PrepareVertices_Vertex3f(4, vertex3f, NULL); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + qglEndQueryARB(GL_SAMPLES_PASSED_ARB); + CHECKGLERROR + break; + case RENDERPATH_D3D9: + Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + } + rtlight->corona_visibility = bound(0, (zdist - 32) / 32, 1); +} + +static float spritetexcoord2f[4*2] = {0, 1, 0, 0, 1, 0, 1, 1}; + +void R_DrawCorona(rtlight_t *rtlight, float cscale, float scale) +{ + vec3_t color; + GLint allpixels = 0, visiblepixels = 0; + // now we have to check the query result + if (rtlight->corona_queryindex_visiblepixels) + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglGetQueryObjectivARB(rtlight->corona_queryindex_visiblepixels, GL_QUERY_RESULT_ARB, &visiblepixels); + qglGetQueryObjectivARB(rtlight->corona_queryindex_allpixels, GL_QUERY_RESULT_ARB, &allpixels); + CHECKGLERROR + break; + case RENDERPATH_D3D9: + Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + //Con_Printf("%i of %i pixels\n", (int)visiblepixels, (int)allpixels); + if (visiblepixels < 1 || allpixels < 1) + return; + rtlight->corona_visibility *= bound(0, (float)visiblepixels / (float)allpixels, 1); + cscale *= rtlight->corona_visibility; + } + else + { + // FIXME: these traces should scan all render entities instead of cl.world + if (CL_TraceLine(r_refdef.view.origin, rtlight->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction < 1) + return; + } + VectorScale(rtlight->currentcolor, cscale, color); + if (VectorLength(color) > (1.0f / 256.0f)) + { + float vertex3f[12]; + qboolean negated = (color[0] + color[1] + color[2] < 0) && vid.support.ext_blend_subtract; + if(negated) + { + VectorNegate(color, color); + GL_BlendEquationSubtract(true); + } + R_CalcSprite_Vertex3f(vertex3f, rtlight->shadoworigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, RENDER_NODEPTHTEST, 0, color[0], color[1], color[2], 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + R_DrawCustomSurface(r_shadow_lightcorona, &identitymatrix, MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); + if(negated) + GL_BlendEquationSubtract(false); + } +} + +void R_Shadow_DrawCoronas(void) +{ + int i, flag; + qboolean usequery = false; + size_t lightindex; + dlight_t *light; + rtlight_t *rtlight; + size_t range; + if (r_coronas.value < (1.0f / 256.0f) && !gl_flashblend.integer) + return; + if (r_waterstate.renderingscene) + return; + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + R_EntityMatrix(&identitymatrix); + + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + + // check occlusion of coronas + // use GL_ARB_occlusion_query if available + // otherwise use raytraces + r_numqueries = 0; + switch (vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + usequery = vid.support.arb_occlusion_query && r_coronas_occlusionquery.integer; + if (usequery) + { + GL_ColorMask(0,0,0,0); + if (r_maxqueries < (range + r_refdef.scene.numlights) * 2) + if (r_maxqueries < MAX_OCCLUSION_QUERIES) + { + i = r_maxqueries; + r_maxqueries = (range + r_refdef.scene.numlights) * 4; + r_maxqueries = min(r_maxqueries, MAX_OCCLUSION_QUERIES); + CHECKGLERROR + qglGenQueriesARB(r_maxqueries - i, r_queries + i); + CHECKGLERROR + } + RSurf_ActiveWorldEntity(); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_CullFace(GL_NONE); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(true); + R_Mesh_ResetTextureState(); + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, false); + } + break; + case RENDERPATH_D3D9: + usequery = false; + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + usequery = false; + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + rtlight = &light->rtlight; + rtlight->corona_visibility = 0; + rtlight->corona_queryindex_visiblepixels = 0; + rtlight->corona_queryindex_allpixels = 0; + if (!(rtlight->flags & flag)) + continue; + if (rtlight->corona <= 0) + continue; + if (r_shadow_debuglight.integer >= 0 && r_shadow_debuglight.integer != (int)lightindex) + continue; + R_BeginCoronaQuery(rtlight, rtlight->radius * rtlight->coronasizescale * r_coronas_occlusionsizescale.value, usequery); + } + for (i = 0;i < r_refdef.scene.numlights;i++) + { + rtlight = r_refdef.scene.lights[i]; + rtlight->corona_visibility = 0; + rtlight->corona_queryindex_visiblepixels = 0; + rtlight->corona_queryindex_allpixels = 0; + if (!(rtlight->flags & flag)) + continue; + if (rtlight->corona <= 0) + continue; + R_BeginCoronaQuery(rtlight, rtlight->radius * rtlight->coronasizescale * r_coronas_occlusionsizescale.value, usequery); + } + if (usequery) + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + + // now draw the coronas using the query data for intensity info + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + rtlight = &light->rtlight; + if (rtlight->corona_visibility <= 0) + continue; + R_DrawCorona(rtlight, rtlight->corona * r_coronas.value * 0.25f, rtlight->radius * rtlight->coronasizescale); + } + for (i = 0;i < r_refdef.scene.numlights;i++) + { + rtlight = r_refdef.scene.lights[i]; + if (rtlight->corona_visibility <= 0) + continue; + if (gl_flashblend.integer) + R_DrawCorona(rtlight, rtlight->corona, rtlight->radius * rtlight->coronasizescale * 2.0f); + else + R_DrawCorona(rtlight, rtlight->corona * r_coronas.value * 0.25f, rtlight->radius * rtlight->coronasizescale); + } +} + + + +dlight_t *R_Shadow_NewWorldLight(void) +{ + return (dlight_t *)Mem_ExpandableArray_AllocRecord(&r_shadow_worldlightsarray); +} + +void R_Shadow_UpdateWorldLight(dlight_t *light, vec3_t origin, vec3_t angles, vec3_t color, vec_t radius, vec_t corona, int style, int shadowenable, const char *cubemapname, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) +{ + matrix4x4_t matrix; + // validate parameters + if (style < 0 || style >= MAX_LIGHTSTYLES) + { + Con_Printf("R_Shadow_NewWorldLight: invalid light style number %i, must be >= 0 and < %i\n", light->style, MAX_LIGHTSTYLES); + style = 0; + } + if (!cubemapname) + cubemapname = ""; + + // copy to light properties + VectorCopy(origin, light->origin); + light->angles[0] = angles[0] - 360 * floor(angles[0] / 360); + light->angles[1] = angles[1] - 360 * floor(angles[1] / 360); + light->angles[2] = angles[2] - 360 * floor(angles[2] / 360); + /* + light->color[0] = max(color[0], 0); + light->color[1] = max(color[1], 0); + light->color[2] = max(color[2], 0); + */ + light->color[0] = color[0]; + light->color[1] = color[1]; + light->color[2] = color[2]; + light->radius = max(radius, 0); + light->style = style; + light->shadow = shadowenable; + light->corona = corona; + strlcpy(light->cubemapname, cubemapname, sizeof(light->cubemapname)); + light->coronasizescale = coronasizescale; + light->ambientscale = ambientscale; + light->diffusescale = diffusescale; + light->specularscale = specularscale; + light->flags = flags; + + // update renderable light data + Matrix4x4_CreateFromQuakeEntity(&matrix, light->origin[0], light->origin[1], light->origin[2], light->angles[0], light->angles[1], light->angles[2], light->radius); + R_RTLight_Update(&light->rtlight, true, &matrix, light->color, light->style, light->cubemapname[0] ? light->cubemapname : NULL, light->shadow, light->corona, light->coronasizescale, light->ambientscale, light->diffusescale, light->specularscale, light->flags); +} + +void R_Shadow_FreeWorldLight(dlight_t *light) +{ + if (r_shadow_selectedlight == light) + r_shadow_selectedlight = NULL; + R_RTLight_Uncompile(&light->rtlight); + Mem_ExpandableArray_FreeRecord(&r_shadow_worldlightsarray, light); +} + +void R_Shadow_ClearWorldLights(void) +{ + size_t lightindex; + dlight_t *light; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light) + R_Shadow_FreeWorldLight(light); + } + r_shadow_selectedlight = NULL; +} + +void R_Shadow_SelectLight(dlight_t *light) +{ + if (r_shadow_selectedlight) + r_shadow_selectedlight->selected = false; + r_shadow_selectedlight = light; + if (r_shadow_selectedlight) + r_shadow_selectedlight->selected = true; +} + +void R_Shadow_DrawCursor_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + // this is never batched (there can be only one) + float vertex3f[12]; + R_CalcSprite_Vertex3f(vertex3f, r_editlights_cursorlocation, r_refdef.view.right, r_refdef.view.up, EDLIGHTSPRSIZE, -EDLIGHTSPRSIZE, -EDLIGHTSPRSIZE, EDLIGHTSPRSIZE); + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, 1, 1, 1, 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + R_DrawCustomSurface(r_editlights_sprcursor, &identitymatrix, MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); +} + +void R_Shadow_DrawLightSprite_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + float intensity; + float s; + vec3_t spritecolor; + skinframe_t *skinframe; + float vertex3f[12]; + + // this is never batched (due to the ent parameter changing every time) + // so numsurfaces == 1 and surfacelist[0] == lightnumber + const dlight_t *light = (dlight_t *)ent; + s = EDLIGHTSPRSIZE; + + R_CalcSprite_Vertex3f(vertex3f, light->origin, r_refdef.view.right, r_refdef.view.up, s, -s, -s, s); + + intensity = 0.5f; + VectorScale(light->color, intensity, spritecolor); + if (VectorLength(spritecolor) < 0.1732f) + VectorSet(spritecolor, 0.1f, 0.1f, 0.1f); + if (VectorLength(spritecolor) > 1.0f) + VectorNormalize(spritecolor); + + // draw light sprite + if (light->cubemapname[0] && !light->shadow) + skinframe = r_editlights_sprcubemapnoshadowlight; + else if (light->cubemapname[0]) + skinframe = r_editlights_sprcubemaplight; + else if (!light->shadow) + skinframe = r_editlights_sprnoshadowlight; + else + skinframe = r_editlights_sprlight; + + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, spritecolor[0], spritecolor[1], spritecolor[2], 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + R_DrawCustomSurface(skinframe, &identitymatrix, MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); + + // draw selection sprite if light is selected + if (light->selected) + { + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, 1, 1, 1, 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + R_DrawCustomSurface(r_editlights_sprselection, &identitymatrix, MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); + // VorteX todo: add normalmode/realtime mode light overlay sprites? + } +} + +void R_Shadow_DrawLightSprites(void) +{ + size_t lightindex; + dlight_t *light; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light) + R_MeshQueue_AddTransparent(light->origin, R_Shadow_DrawLightSprite_TransparentCallback, (entity_render_t *)light, 5, &light->rtlight); + } + if (!r_editlights_lockcursor) + R_MeshQueue_AddTransparent(r_editlights_cursorlocation, R_Shadow_DrawCursor_TransparentCallback, NULL, 0, NULL); +} + +int R_Shadow_GetRTLightInfo(unsigned int lightindex, float *origin, float *radius, float *color) +{ + unsigned int range; + dlight_t *light; + rtlight_t *rtlight; + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); + if (lightindex >= range) + return -1; + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + return 0; + rtlight = &light->rtlight; + //if (!(rtlight->flags & flag)) + // return 0; + VectorCopy(rtlight->shadoworigin, origin); + *radius = rtlight->radius; + VectorCopy(rtlight->color, color); + return 1; +} + +void R_Shadow_SelectLightInView(void) +{ + float bestrating, rating, temp[3]; + dlight_t *best; + size_t lightindex; + dlight_t *light; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + best = NULL; + bestrating = 0; + + if (r_editlights_lockcursor) + return; + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + VectorSubtract(light->origin, r_refdef.view.origin, temp); + rating = (DotProduct(temp, r_refdef.view.forward) / sqrt(DotProduct(temp, temp))); + if (rating >= 0.95) + { + rating /= (1 + 0.0625f * sqrt(DotProduct(temp, temp))); + if (bestrating < rating && CL_TraceLine(light->origin, r_refdef.view.origin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction == 1.0f) + { + bestrating = rating; + best = light; + } + } + } + R_Shadow_SelectLight(best); +} + +void R_Shadow_LoadWorldLights(void) +{ + int n, a, style, shadow, flags; + char tempchar, *lightsstring, *s, *t, name[MAX_QPATH], cubemapname[MAX_QPATH]; + float origin[3], radius, color[3], angles[3], corona, coronasizescale, ambientscale, diffusescale, specularscale; + if (cl.worldmodel == NULL) + { + Con_Print("No map loaded.\n"); + return; + } + dpsnprintf(name, sizeof(name), "%s.rtlights", cl.worldnamenoextension); + lightsstring = (char *)FS_LoadFile(name, tempmempool, false, NULL); + if (lightsstring) + { + s = lightsstring; + n = 0; + while (*s) + { + t = s; + /* + shadow = true; + for (;COM_Parse(t, true) && strcmp( + if (COM_Parse(t, true)) + { + if (com_token[0] == '!') + { + shadow = false; + origin[0] = atof(com_token+1); + } + else + origin[0] = atof(com_token); + if (Com_Parse(t + } + */ + t = s; + while (*s && *s != '\n' && *s != '\r') + s++; + if (!*s) + break; + tempchar = *s; + shadow = true; + // check for modifier flags + if (*t == '!') + { + shadow = false; + t++; + } + *s = 0; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + cubemapname[sizeof(cubemapname)-1] = 0; +#if MAX_QPATH != 128 +#error update this code if MAX_QPATH changes +#endif + a = sscanf(t, "%f %f %f %f %f %f %f %d %127s %f %f %f %f %f %f %f %f %i", &origin[0], &origin[1], &origin[2], &radius, &color[0], &color[1], &color[2], &style, cubemapname +#if _MSC_VER >= 1400 +, sizeof(cubemapname) +#endif +, &corona, &angles[0], &angles[1], &angles[2], &coronasizescale, &ambientscale, &diffusescale, &specularscale, &flags); + *s = tempchar; + if (a < 18) + flags = LIGHTFLAG_REALTIMEMODE; + if (a < 17) + specularscale = 1; + if (a < 16) + diffusescale = 1; + if (a < 15) + ambientscale = 0; + if (a < 14) + coronasizescale = 0.25f; + if (a < 13) + VectorClear(angles); + if (a < 10) + corona = 0; + if (a < 9 || !strcmp(cubemapname, "\"\"")) + cubemapname[0] = 0; + // remove quotes on cubemapname + if (cubemapname[0] == '"' && cubemapname[strlen(cubemapname) - 1] == '"') + { + size_t namelen; + namelen = strlen(cubemapname) - 2; + memmove(cubemapname, cubemapname + 1, namelen); + cubemapname[namelen] = '\0'; + } + if (a < 8) + { + Con_Printf("found %d parameters on line %i, should be 8 or more parameters (origin[0] origin[1] origin[2] radius color[0] color[1] color[2] style \"cubemapname\" corona angles[0] angles[1] angles[2] coronasizescale ambientscale diffusescale specularscale flags)\n", a, n + 1); + break; + } + R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, angles, color, radius, corona, style, shadow, cubemapname, coronasizescale, ambientscale, diffusescale, specularscale, flags); + if (*s == '\r') + s++; + if (*s == '\n') + s++; + n++; + } + if (*s) + Con_Printf("invalid rtlights file \"%s\"\n", name); + Mem_Free(lightsstring); + } +} + +void R_Shadow_SaveWorldLights(void) +{ + size_t lightindex; + dlight_t *light; + size_t bufchars, bufmaxchars; + char *buf, *oldbuf; + char name[MAX_QPATH]; + char line[MAX_INPUTLINE]; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked, assuming the dpsnprintf mess doesn't screw it up... + // I hate lines which are 3 times my screen size :( --blub + if (!range) + return; + if (cl.worldmodel == NULL) + { + Con_Print("No map loaded.\n"); + return; + } + dpsnprintf(name, sizeof(name), "%s.rtlights", cl.worldnamenoextension); + bufchars = bufmaxchars = 0; + buf = NULL; + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + if (light->coronasizescale != 0.25f || light->ambientscale != 0 || light->diffusescale != 1 || light->specularscale != 1 || light->flags != LIGHTFLAG_REALTIMEMODE) + dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d \"%s\" %f %f %f %f %f %f %f %f %i\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style, light->cubemapname, light->corona, light->angles[0], light->angles[1], light->angles[2], light->coronasizescale, light->ambientscale, light->diffusescale, light->specularscale, light->flags); + else if (light->cubemapname[0] || light->corona || light->angles[0] || light->angles[1] || light->angles[2]) + dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d \"%s\" %f %f %f %f\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style, light->cubemapname, light->corona, light->angles[0], light->angles[1], light->angles[2]); + else + dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style); + if (bufchars + strlen(line) > bufmaxchars) + { + bufmaxchars = bufchars + strlen(line) + 2048; + oldbuf = buf; + buf = (char *)Mem_Alloc(tempmempool, bufmaxchars); + if (oldbuf) + { + if (bufchars) + memcpy(buf, oldbuf, bufchars); + Mem_Free(oldbuf); + } + } + if (strlen(line)) + { + memcpy(buf + bufchars, line, strlen(line)); + bufchars += strlen(line); + } + } + if (bufchars) + FS_WriteFile(name, buf, (fs_offset_t)bufchars); + if (buf) + Mem_Free(buf); +} + +void R_Shadow_LoadLightsFile(void) +{ + int n, a, style; + char tempchar, *lightsstring, *s, *t, name[MAX_QPATH]; + float origin[3], radius, color[3], subtract, spotdir[3], spotcone, falloff, distbias; + if (cl.worldmodel == NULL) + { + Con_Print("No map loaded.\n"); + return; + } + dpsnprintf(name, sizeof(name), "%s.lights", cl.worldnamenoextension); + lightsstring = (char *)FS_LoadFile(name, tempmempool, false, NULL); + if (lightsstring) + { + s = lightsstring; + n = 0; + while (*s) + { + t = s; + while (*s && *s != '\n' && *s != '\r') + s++; + if (!*s) + break; + tempchar = *s; + *s = 0; + a = sscanf(t, "%f %f %f %f %f %f %f %f %f %f %f %f %f %d", &origin[0], &origin[1], &origin[2], &falloff, &color[0], &color[1], &color[2], &subtract, &spotdir[0], &spotdir[1], &spotdir[2], &spotcone, &distbias, &style); + *s = tempchar; + if (a < 14) + { + Con_Printf("invalid lights file, found %d parameters on line %i, should be 14 parameters (origin[0] origin[1] origin[2] falloff light[0] light[1] light[2] subtract spotdir[0] spotdir[1] spotdir[2] spotcone distancebias style)\n", a, n + 1); + break; + } + radius = sqrt(DotProduct(color, color) / (falloff * falloff * 8192.0f * 8192.0f)); + radius = bound(15, radius, 4096); + VectorScale(color, (2.0f / (8388608.0f)), color); + R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, vec3_origin, color, radius, 0, style, true, NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); + if (*s == '\r') + s++; + if (*s == '\n') + s++; + n++; + } + if (*s) + Con_Printf("invalid lights file \"%s\"\n", name); + Mem_Free(lightsstring); + } +} + +// tyrlite/hmap2 light types in the delay field +typedef enum lighttype_e {LIGHTTYPE_MINUSX, LIGHTTYPE_RECIPX, LIGHTTYPE_RECIPXX, LIGHTTYPE_NONE, LIGHTTYPE_SUN, LIGHTTYPE_MINUSXX} lighttype_t; + +void R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(void) +{ + int entnum; + int style; + int islight; + int skin; + int pflags; + //int effects; + int type; + int n; + char *entfiledata; + const char *data; + float origin[3], angles[3], radius, color[3], light[4], fadescale, lightscale, originhack[3], overridecolor[3], vec[4]; + char key[256], value[MAX_INPUTLINE]; + + if (cl.worldmodel == NULL) + { + Con_Print("No map loaded.\n"); + return; + } + // try to load a .ent file first + dpsnprintf(key, sizeof(key), "%s.ent", cl.worldnamenoextension); + data = entfiledata = (char *)FS_LoadFile(key, tempmempool, true, NULL); + // and if that is not found, fall back to the bsp file entity string + if (!data) + data = cl.worldmodel->brush.entities; + if (!data) + return; + for (entnum = 0;COM_ParseToken_Simple(&data, false, false) && com_token[0] == '{';entnum++) + { + type = LIGHTTYPE_MINUSX; + origin[0] = origin[1] = origin[2] = 0; + originhack[0] = originhack[1] = originhack[2] = 0; + angles[0] = angles[1] = angles[2] = 0; + color[0] = color[1] = color[2] = 1; + light[0] = light[1] = light[2] = 1;light[3] = 300; + overridecolor[0] = overridecolor[1] = overridecolor[2] = 1; + fadescale = 1; + lightscale = 1; + style = 0; + skin = 0; + pflags = 0; + //effects = 0; + islight = false; + while (1) + { + if (!COM_ParseToken_Simple(&data, false, false)) + break; // error + if (com_token[0] == '}') + break; // end of entity + if (com_token[0] == '_') + strlcpy(key, com_token + 1, sizeof(key)); + else + strlcpy(key, com_token, sizeof(key)); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + if (!COM_ParseToken_Simple(&data, false, false)) + break; // error + strlcpy(value, com_token, sizeof(value)); + + // now that we have the key pair worked out... + if (!strcmp("light", key)) + { + n = sscanf(value, "%f %f %f %f", &vec[0], &vec[1], &vec[2], &vec[3]); + if (n == 1) + { + // quake + light[0] = vec[0] * (1.0f / 256.0f); + light[1] = vec[0] * (1.0f / 256.0f); + light[2] = vec[0] * (1.0f / 256.0f); + light[3] = vec[0]; + } + else if (n == 4) + { + // halflife + light[0] = vec[0] * (1.0f / 255.0f); + light[1] = vec[1] * (1.0f / 255.0f); + light[2] = vec[2] * (1.0f / 255.0f); + light[3] = vec[3]; + } + } + else if (!strcmp("delay", key)) + type = atoi(value); + else if (!strcmp("origin", key)) + sscanf(value, "%f %f %f", &origin[0], &origin[1], &origin[2]); + else if (!strcmp("angle", key)) + angles[0] = 0, angles[1] = atof(value), angles[2] = 0; + else if (!strcmp("angles", key)) + sscanf(value, "%f %f %f", &angles[0], &angles[1], &angles[2]); + else if (!strcmp("color", key)) + sscanf(value, "%f %f %f", &color[0], &color[1], &color[2]); + else if (!strcmp("wait", key)) + fadescale = atof(value); + else if (!strcmp("classname", key)) + { + if (!strncmp(value, "light", 5)) + { + islight = true; + if (!strcmp(value, "light_fluoro")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 1; + overridecolor[2] = 1; + } + if (!strcmp(value, "light_fluorospark")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 1; + overridecolor[2] = 1; + } + if (!strcmp(value, "light_globe")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.8; + overridecolor[2] = 0.4; + } + if (!strcmp(value, "light_flame_large_yellow")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.5; + overridecolor[2] = 0.1; + } + if (!strcmp(value, "light_flame_small_yellow")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.5; + overridecolor[2] = 0.1; + } + if (!strcmp(value, "light_torch_small_white")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.5; + overridecolor[2] = 0.1; + } + if (!strcmp(value, "light_torch_small_walltorch")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.5; + overridecolor[2] = 0.1; + } + } + } + else if (!strcmp("style", key)) + style = atoi(value); + else if (!strcmp("skin", key)) + skin = (int)atof(value); + else if (!strcmp("pflags", key)) + pflags = (int)atof(value); + //else if (!strcmp("effects", key)) + // effects = (int)atof(value); + else if (cl.worldmodel->type == mod_brushq3) + { + if (!strcmp("scale", key)) + lightscale = atof(value); + if (!strcmp("fade", key)) + fadescale = atof(value); + } + } + if (!islight) + continue; + if (lightscale <= 0) + lightscale = 1; + if (fadescale <= 0) + fadescale = 1; + if (color[0] == color[1] && color[0] == color[2]) + { + color[0] *= overridecolor[0]; + color[1] *= overridecolor[1]; + color[2] *= overridecolor[2]; + } + radius = light[3] * r_editlights_quakelightsizescale.value * lightscale / fadescale; + color[0] = color[0] * light[0]; + color[1] = color[1] * light[1]; + color[2] = color[2] * light[2]; + switch (type) + { + case LIGHTTYPE_MINUSX: + break; + case LIGHTTYPE_RECIPX: + radius *= 2; + VectorScale(color, (1.0f / 16.0f), color); + break; + case LIGHTTYPE_RECIPXX: + radius *= 2; + VectorScale(color, (1.0f / 16.0f), color); + break; + default: + case LIGHTTYPE_NONE: + break; + case LIGHTTYPE_SUN: + break; + case LIGHTTYPE_MINUSXX: + break; + } + VectorAdd(origin, originhack, origin); + if (radius >= 1) + R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, angles, color, radius, (pflags & PFLAGS_CORONA) != 0, style, (pflags & PFLAGS_NOSHADOW) == 0, skin >= 16 ? va("cubemaps/%i", skin) : NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); + } + if (entfiledata) + Mem_Free(entfiledata); +} + + +void R_Shadow_SetCursorLocationForView(void) +{ + vec_t dist, push; + vec3_t dest, endpos; + trace_t trace; + VectorMA(r_refdef.view.origin, r_editlights_cursordistance.value, r_refdef.view.forward, dest); + trace = CL_TraceLine(r_refdef.view.origin, dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true); + if (trace.fraction < 1) + { + dist = trace.fraction * r_editlights_cursordistance.value; + push = r_editlights_cursorpushback.value; + if (push > dist) + push = dist; + push = -push; + VectorMA(trace.endpos, push, r_refdef.view.forward, endpos); + VectorMA(endpos, r_editlights_cursorpushoff.value, trace.plane.normal, endpos); + } + else + { + VectorClear( endpos ); + } + r_editlights_cursorlocation[0] = floor(endpos[0] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; + r_editlights_cursorlocation[1] = floor(endpos[1] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; + r_editlights_cursorlocation[2] = floor(endpos[2] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; +} + +void R_Shadow_UpdateWorldLightSelection(void) +{ + if (r_editlights.integer) + { + R_Shadow_SetCursorLocationForView(); + R_Shadow_SelectLightInView(); + } + else + R_Shadow_SelectLight(NULL); +} + +void R_Shadow_EditLights_Clear_f(void) +{ + R_Shadow_ClearWorldLights(); +} + +void R_Shadow_EditLights_Reload_f(void) +{ + if (!cl.worldmodel) + return; + strlcpy(r_shadow_mapname, cl.worldname, sizeof(r_shadow_mapname)); + R_Shadow_ClearWorldLights(); + R_Shadow_LoadWorldLights(); + if (!Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)) + { + R_Shadow_LoadLightsFile(); + if (!Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)) + R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(); + } +} + +void R_Shadow_EditLights_Save_f(void) +{ + if (!cl.worldmodel) + return; + R_Shadow_SaveWorldLights(); +} + +void R_Shadow_EditLights_ImportLightEntitiesFromMap_f(void) +{ + R_Shadow_ClearWorldLights(); + R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(); +} + +void R_Shadow_EditLights_ImportLightsFile_f(void) +{ + R_Shadow_ClearWorldLights(); + R_Shadow_LoadLightsFile(); +} + +void R_Shadow_EditLights_Spawn_f(void) +{ + vec3_t color; + if (!r_editlights.integer) + { + Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (Cmd_Argc() != 1) + { + Con_Print("r_editlights_spawn does not take parameters\n"); + return; + } + color[0] = color[1] = color[2] = 1; + R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), r_editlights_cursorlocation, vec3_origin, color, 200, 0, 0, true, NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); +} + +void R_Shadow_EditLights_Edit_f(void) +{ + vec3_t origin, angles, color; + vec_t radius, corona, coronasizescale, ambientscale, diffusescale, specularscale; + int style, shadows, flags, normalmode, realtimemode; + char cubemapname[MAX_INPUTLINE]; + if (!r_editlights.integer) + { + Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + VectorCopy(r_shadow_selectedlight->origin, origin); + VectorCopy(r_shadow_selectedlight->angles, angles); + VectorCopy(r_shadow_selectedlight->color, color); + radius = r_shadow_selectedlight->radius; + style = r_shadow_selectedlight->style; + if (r_shadow_selectedlight->cubemapname) + strlcpy(cubemapname, r_shadow_selectedlight->cubemapname, sizeof(cubemapname)); + else + cubemapname[0] = 0; + shadows = r_shadow_selectedlight->shadow; + corona = r_shadow_selectedlight->corona; + coronasizescale = r_shadow_selectedlight->coronasizescale; + ambientscale = r_shadow_selectedlight->ambientscale; + diffusescale = r_shadow_selectedlight->diffusescale; + specularscale = r_shadow_selectedlight->specularscale; + flags = r_shadow_selectedlight->flags; + normalmode = (flags & LIGHTFLAG_NORMALMODE) != 0; + realtimemode = (flags & LIGHTFLAG_REALTIMEMODE) != 0; + if (!strcmp(Cmd_Argv(1), "origin")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); + return; + } + origin[0] = atof(Cmd_Argv(2)); + origin[1] = atof(Cmd_Argv(3)); + origin[2] = atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "originscale")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); + return; + } + origin[0] *= atof(Cmd_Argv(2)); + origin[1] *= atof(Cmd_Argv(3)); + origin[2] *= atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "originx")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[0] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "originy")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[1] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "originz")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[2] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "move")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); + return; + } + origin[0] += atof(Cmd_Argv(2)); + origin[1] += atof(Cmd_Argv(3)); + origin[2] += atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "movex")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[0] += atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "movey")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[1] += atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "movez")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[2] += atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "angles")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); + return; + } + angles[0] = atof(Cmd_Argv(2)); + angles[1] = atof(Cmd_Argv(3)); + angles[2] = atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "anglesx")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + angles[0] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "anglesy")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + angles[1] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "anglesz")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + angles[2] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "color")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s red green blue\n", Cmd_Argv(1)); + return; + } + color[0] = atof(Cmd_Argv(2)); + color[1] = atof(Cmd_Argv(3)); + color[2] = atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "radius")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + radius = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "colorscale")) + { + if (Cmd_Argc() == 3) + { + double scale = atof(Cmd_Argv(2)); + color[0] *= scale; + color[1] *= scale; + color[2] *= scale; + } + else + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s red green blue (OR grey instead of red green blue)\n", Cmd_Argv(1)); + return; + } + color[0] *= atof(Cmd_Argv(2)); + color[1] *= atof(Cmd_Argv(3)); + color[2] *= atof(Cmd_Argv(4)); + } + } + else if (!strcmp(Cmd_Argv(1), "radiusscale") || !strcmp(Cmd_Argv(1), "sizescale")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + radius *= atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "style")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + style = atoi(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "cubemap")) + { + if (Cmd_Argc() > 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + if (Cmd_Argc() == 3) + strlcpy(cubemapname, Cmd_Argv(2), sizeof(cubemapname)); + else + cubemapname[0] = 0; + } + else if (!strcmp(Cmd_Argv(1), "shadows")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + shadows = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "corona")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + corona = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "coronasize")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + coronasizescale = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "ambient")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + ambientscale = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "diffuse")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + diffusescale = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "specular")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + specularscale = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "normalmode")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + normalmode = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "realtimemode")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + realtimemode = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); + } + else + { + Con_Print("usage: r_editlights_edit [property] [value]\n"); + Con_Print("Selected light's properties:\n"); + Con_Printf("Origin : %f %f %f\n", r_shadow_selectedlight->origin[0], r_shadow_selectedlight->origin[1], r_shadow_selectedlight->origin[2]); + Con_Printf("Angles : %f %f %f\n", r_shadow_selectedlight->angles[0], r_shadow_selectedlight->angles[1], r_shadow_selectedlight->angles[2]); + Con_Printf("Color : %f %f %f\n", r_shadow_selectedlight->color[0], r_shadow_selectedlight->color[1], r_shadow_selectedlight->color[2]); + Con_Printf("Radius : %f\n", r_shadow_selectedlight->radius); + Con_Printf("Corona : %f\n", r_shadow_selectedlight->corona); + Con_Printf("Style : %i\n", r_shadow_selectedlight->style); + Con_Printf("Shadows : %s\n", r_shadow_selectedlight->shadow ? "yes" : "no"); + Con_Printf("Cubemap : %s\n", r_shadow_selectedlight->cubemapname); + Con_Printf("CoronaSize : %f\n", r_shadow_selectedlight->coronasizescale); + Con_Printf("Ambient : %f\n", r_shadow_selectedlight->ambientscale); + Con_Printf("Diffuse : %f\n", r_shadow_selectedlight->diffusescale); + Con_Printf("Specular : %f\n", r_shadow_selectedlight->specularscale); + Con_Printf("NormalMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_NORMALMODE) ? "yes" : "no"); + Con_Printf("RealTimeMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_REALTIMEMODE) ? "yes" : "no"); + return; + } + flags = (normalmode ? LIGHTFLAG_NORMALMODE : 0) | (realtimemode ? LIGHTFLAG_REALTIMEMODE : 0); + R_Shadow_UpdateWorldLight(r_shadow_selectedlight, origin, angles, color, radius, corona, style, shadows, cubemapname, coronasizescale, ambientscale, diffusescale, specularscale, flags); +} + +void R_Shadow_EditLights_EditAll_f(void) +{ + size_t lightindex; + dlight_t *light, *oldselected; + size_t range; + + if (!r_editlights.integer) + { + Con_Print("Cannot edit lights when not in editing mode. Set r_editlights to 1.\n"); + return; + } + + oldselected = r_shadow_selectedlight; + // EditLights doesn't seem to have a "remove" command or something so: + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + R_Shadow_SelectLight(light); + R_Shadow_EditLights_Edit_f(); + } + // return to old selected (to not mess editing once selection is locked) + R_Shadow_SelectLight(oldselected); +} + +void R_Shadow_EditLights_DrawSelectedLightProperties(void) +{ + int lightnumber, lightcount; + size_t lightindex, range; + dlight_t *light; + float x, y; + char temp[256]; + if (!r_editlights.integer) + return; + x = vid_conwidth.value - 240; + y = 5; + DrawQ_Pic(x-5, y-5, NULL, 250, 155, 0, 0, 0, 0.75, 0); + lightnumber = -1; + lightcount = 0; + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + if (light == r_shadow_selectedlight) + lightnumber = lightindex; + lightcount++; + } + dpsnprintf(temp, sizeof(temp), "Cursor origin: %.0f %.0f %.0f", r_editlights_cursorlocation[0], r_editlights_cursorlocation[1], r_editlights_cursorlocation[2]); DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Total lights : %i active (%i total)", lightcount, (int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)); DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_DEFAULT);y += 8; + y += 8; + if (r_shadow_selectedlight == NULL) + return; + dpsnprintf(temp, sizeof(temp), "Light #%i properties:", lightnumber);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Origin : %.0f %.0f %.0f\n", r_shadow_selectedlight->origin[0], r_shadow_selectedlight->origin[1], r_shadow_selectedlight->origin[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Angles : %.0f %.0f %.0f\n", r_shadow_selectedlight->angles[0], r_shadow_selectedlight->angles[1], r_shadow_selectedlight->angles[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Color : %.2f %.2f %.2f\n", r_shadow_selectedlight->color[0], r_shadow_selectedlight->color[1], r_shadow_selectedlight->color[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Radius : %.0f\n", r_shadow_selectedlight->radius);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Corona : %.0f\n", r_shadow_selectedlight->corona);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Style : %i\n", r_shadow_selectedlight->style);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Shadows : %s\n", r_shadow_selectedlight->shadow ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Cubemap : %s\n", r_shadow_selectedlight->cubemapname);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "CoronaSize : %.2f\n", r_shadow_selectedlight->coronasizescale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Ambient : %.2f\n", r_shadow_selectedlight->ambientscale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Diffuse : %.2f\n", r_shadow_selectedlight->diffusescale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Specular : %.2f\n", r_shadow_selectedlight->specularscale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "NormalMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_NORMALMODE) ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "RealTimeMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_REALTIMEMODE) ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; +} + +void R_Shadow_EditLights_ToggleShadow_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_selectedlight->angles, r_shadow_selectedlight->color, r_shadow_selectedlight->radius, r_shadow_selectedlight->corona, r_shadow_selectedlight->style, !r_shadow_selectedlight->shadow, r_shadow_selectedlight->cubemapname, r_shadow_selectedlight->coronasizescale, r_shadow_selectedlight->ambientscale, r_shadow_selectedlight->diffusescale, r_shadow_selectedlight->specularscale, r_shadow_selectedlight->flags); +} + +void R_Shadow_EditLights_ToggleCorona_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_selectedlight->angles, r_shadow_selectedlight->color, r_shadow_selectedlight->radius, !r_shadow_selectedlight->corona, r_shadow_selectedlight->style, r_shadow_selectedlight->shadow, r_shadow_selectedlight->cubemapname, r_shadow_selectedlight->coronasizescale, r_shadow_selectedlight->ambientscale, r_shadow_selectedlight->diffusescale, r_shadow_selectedlight->specularscale, r_shadow_selectedlight->flags); +} + +void R_Shadow_EditLights_Remove_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot remove light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + R_Shadow_FreeWorldLight(r_shadow_selectedlight); + r_shadow_selectedlight = NULL; +} + +void R_Shadow_EditLights_Help_f(void) +{ + Con_Print( +"Documentation on r_editlights system:\n" +"Settings:\n" +"r_editlights : enable/disable editing mode\n" +"r_editlights_cursordistance : maximum distance of cursor from eye\n" +"r_editlights_cursorpushback : push back cursor this far from surface\n" +"r_editlights_cursorpushoff : push cursor off surface this far\n" +"r_editlights_cursorgrid : snap cursor to grid of this size\n" +"r_editlights_quakelightsizescale : imported quake light entity size scaling\n" +"Commands:\n" +"r_editlights_help : this help\n" +"r_editlights_clear : remove all lights\n" +"r_editlights_reload : reload .rtlights, .lights file, or entities\n" +"r_editlights_lock : lock selection to current light, if already locked - unlock\n" +"r_editlights_save : save to .rtlights file\n" +"r_editlights_spawn : create a light with default settings\n" +"r_editlights_edit command : edit selected light - more documentation below\n" +"r_editlights_remove : remove selected light\n" +"r_editlights_toggleshadow : toggles on/off selected light's shadow property\n" +"r_editlights_importlightentitiesfrommap : reload light entities\n" +"r_editlights_importlightsfile : reload .light file (produced by hlight)\n" +"Edit commands:\n" +"origin x y z : set light location\n" +"originx x: set x component of light location\n" +"originy y: set y component of light location\n" +"originz z: set z component of light location\n" +"move x y z : adjust light location\n" +"movex x: adjust x component of light location\n" +"movey y: adjust y component of light location\n" +"movez z: adjust z component of light location\n" +"angles x y z : set light angles\n" +"anglesx x: set x component of light angles\n" +"anglesy y: set y component of light angles\n" +"anglesz z: set z component of light angles\n" +"color r g b : set color of light (can be brighter than 1 1 1)\n" +"radius radius : set radius (size) of light\n" +"colorscale grey : multiply color of light (1 does nothing)\n" +"colorscale r g b : multiply color of light (1 1 1 does nothing)\n" +"radiusscale scale : multiply radius (size) of light (1 does nothing)\n" +"sizescale scale : multiply radius (size) of light (1 does nothing)\n" +"originscale x y z : multiply origin of light (1 1 1 does nothing)\n" +"style style : set lightstyle of light (flickering patterns, switches, etc)\n" +"cubemap basename : set filter cubemap of light (not yet supported)\n" +"shadows 1/0 : turn on/off shadows\n" +"corona n : set corona intensity\n" +"coronasize n : set corona size (0-1)\n" +"ambient n : set ambient intensity (0-1)\n" +"diffuse n : set diffuse intensity (0-1)\n" +"specular n : set specular intensity (0-1)\n" +"normalmode 1/0 : turn on/off rendering of this light in rtworld 0 mode\n" +"realtimemode 1/0 : turn on/off rendering of this light in rtworld 1 mode\n" +" : print light properties to console\n" + ); +} + +void R_Shadow_EditLights_CopyInfo_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot copy light info when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + VectorCopy(r_shadow_selectedlight->angles, r_shadow_bufferlight.angles); + VectorCopy(r_shadow_selectedlight->color, r_shadow_bufferlight.color); + r_shadow_bufferlight.radius = r_shadow_selectedlight->radius; + r_shadow_bufferlight.style = r_shadow_selectedlight->style; + if (r_shadow_selectedlight->cubemapname) + strlcpy(r_shadow_bufferlight.cubemapname, r_shadow_selectedlight->cubemapname, sizeof(r_shadow_bufferlight.cubemapname)); + else + r_shadow_bufferlight.cubemapname[0] = 0; + r_shadow_bufferlight.shadow = r_shadow_selectedlight->shadow; + r_shadow_bufferlight.corona = r_shadow_selectedlight->corona; + r_shadow_bufferlight.coronasizescale = r_shadow_selectedlight->coronasizescale; + r_shadow_bufferlight.ambientscale = r_shadow_selectedlight->ambientscale; + r_shadow_bufferlight.diffusescale = r_shadow_selectedlight->diffusescale; + r_shadow_bufferlight.specularscale = r_shadow_selectedlight->specularscale; + r_shadow_bufferlight.flags = r_shadow_selectedlight->flags; +} + +void R_Shadow_EditLights_PasteInfo_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot paste light info when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_bufferlight.angles, r_shadow_bufferlight.color, r_shadow_bufferlight.radius, r_shadow_bufferlight.corona, r_shadow_bufferlight.style, r_shadow_bufferlight.shadow, r_shadow_bufferlight.cubemapname, r_shadow_bufferlight.coronasizescale, r_shadow_bufferlight.ambientscale, r_shadow_bufferlight.diffusescale, r_shadow_bufferlight.specularscale, r_shadow_bufferlight.flags); +} + +void R_Shadow_EditLights_Lock_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot lock on light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (r_editlights_lockcursor) + { + r_editlights_lockcursor = false; + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light to lock on.\n"); + return; + } + r_editlights_lockcursor = true; +} + +void R_Shadow_EditLights_Init(void) +{ + Cvar_RegisterVariable(&r_editlights); + Cvar_RegisterVariable(&r_editlights_cursordistance); + Cvar_RegisterVariable(&r_editlights_cursorpushback); + Cvar_RegisterVariable(&r_editlights_cursorpushoff); + Cvar_RegisterVariable(&r_editlights_cursorgrid); + Cvar_RegisterVariable(&r_editlights_quakelightsizescale); + Cmd_AddCommand("r_editlights_help", R_Shadow_EditLights_Help_f, "prints documentation on console commands and variables in rtlight editing system"); + Cmd_AddCommand("r_editlights_clear", R_Shadow_EditLights_Clear_f, "removes all world lights (let there be darkness!)"); + Cmd_AddCommand("r_editlights_reload", R_Shadow_EditLights_Reload_f, "reloads rtlights file (or imports from .lights file or .ent file or the map itself)"); + Cmd_AddCommand("r_editlights_save", R_Shadow_EditLights_Save_f, "save .rtlights file for current level"); + Cmd_AddCommand("r_editlights_spawn", R_Shadow_EditLights_Spawn_f, "creates a light with default properties (let there be light!)"); + Cmd_AddCommand("r_editlights_edit", R_Shadow_EditLights_Edit_f, "changes a property on the selected light"); + Cmd_AddCommand("r_editlights_editall", R_Shadow_EditLights_EditAll_f, "changes a property on ALL lights at once (tip: use radiusscale and colorscale to alter these properties)"); + Cmd_AddCommand("r_editlights_remove", R_Shadow_EditLights_Remove_f, "remove selected light"); + Cmd_AddCommand("r_editlights_toggleshadow", R_Shadow_EditLights_ToggleShadow_f, "toggle on/off the shadow option on the selected light"); + Cmd_AddCommand("r_editlights_togglecorona", R_Shadow_EditLights_ToggleCorona_f, "toggle on/off the corona option on the selected light"); + Cmd_AddCommand("r_editlights_importlightentitiesfrommap", R_Shadow_EditLights_ImportLightEntitiesFromMap_f, "load lights from .ent file or map entities (ignoring .rtlights or .lights file)"); + Cmd_AddCommand("r_editlights_importlightsfile", R_Shadow_EditLights_ImportLightsFile_f, "load lights from .lights file (ignoring .rtlights or .ent files and map entities)"); + Cmd_AddCommand("r_editlights_copyinfo", R_Shadow_EditLights_CopyInfo_f, "store a copy of all properties (except origin) of the selected light"); + Cmd_AddCommand("r_editlights_pasteinfo", R_Shadow_EditLights_PasteInfo_f, "apply the stored properties onto the selected light (making it exactly identical except for origin)"); + Cmd_AddCommand("r_editlights_lock", R_Shadow_EditLights_Lock_f, "lock selection to current light, if already locked - unlock"); +} + + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +void R_LightPoint(vec3_t color, const vec3_t p, const int flags) +{ + int i, numlights, flag; + float f, relativepoint[3], dist, dist2, lightradius2; + vec3_t diffuse, n; + rtlight_t *light; + dlight_t *dlight; + + if (r_fullbright.integer) + { + VectorSet(color, 1, 1, 1); + return; + } + + VectorClear(color); + + if (flags & LP_LIGHTMAP) + { + if (!r_fullbright.integer && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) + { + VectorClear(diffuse); + r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, color, diffuse, n); + VectorAdd(color, diffuse, color); + } + else + VectorSet(color, 1, 1, 1); + color[0] += r_refdef.scene.ambient; + color[1] += r_refdef.scene.ambient; + color[2] += r_refdef.scene.ambient; + } + + if (flags & LP_RTWORLD) + { + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + numlights = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); + for (i = 0; i < numlights; i++) + { + dlight = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, i); + if (!dlight) + continue; + light = &dlight->rtlight; + if (!(light->flags & flag)) + continue; + // sample + lightradius2 = light->radius * light->radius; + VectorSubtract(light->shadoworigin, p, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + dist = sqrt(dist2) / light->radius; + f = dist < 1 ? (r_shadow_lightintensityscale.value * ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist))) : 0; + if (f <= 0) + continue; + // todo: add to both ambient and diffuse + if (!light->shadow || CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction == 1) + VectorMA(color, f, light->currentcolor, color); + } + } + if (flags & LP_DYNLIGHT) + { + // sample dlights + for (i = 0;i < r_refdef.scene.numlights;i++) + { + light = r_refdef.scene.lights[i]; + // sample + lightradius2 = light->radius * light->radius; + VectorSubtract(light->shadoworigin, p, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + dist = sqrt(dist2) / light->radius; + f = dist < 1 ? (r_shadow_lightintensityscale.value * ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist))) : 0; + if (f <= 0) + continue; + // todo: add to both ambient and diffuse + if (!light->shadow || CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction == 1) + VectorMA(color, f, light->color, color); + } + } +} + +void R_CompleteLightPoint(vec3_t ambient, vec3_t diffuse, vec3_t lightdir, const vec3_t p, const int flags) +{ + int i, numlights, flag; + rtlight_t *light; + dlight_t *dlight; + float relativepoint[3]; + float color[3]; + float dir[3]; + float dist; + float dist2; + float intensity; + float sample[5*3]; + float lightradius2; + + if (r_fullbright.integer) + { + VectorSet(ambient, 1, 1, 1); + VectorClear(diffuse); + VectorClear(lightdir); + return; + } + + if (flags == LP_LIGHTMAP) + { + VectorSet(ambient, r_refdef.scene.ambient, r_refdef.scene.ambient, r_refdef.scene.ambient); + VectorClear(diffuse); + VectorClear(lightdir); + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) + r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, ambient, diffuse, lightdir); + else + VectorSet(ambient, 1, 1, 1); + return; + } + + memset(sample, 0, sizeof(sample)); + VectorSet(sample, r_refdef.scene.ambient, r_refdef.scene.ambient, r_refdef.scene.ambient); + + if ((flags & LP_LIGHTMAP) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) + { + vec3_t tempambient; + VectorClear(tempambient); + VectorClear(color); + VectorClear(relativepoint); + r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, tempambient, color, relativepoint); + VectorScale(tempambient, r_refdef.lightmapintensity, tempambient); + VectorScale(color, r_refdef.lightmapintensity, color); + VectorAdd(sample, tempambient, sample); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity = VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } + + if (flags & LP_RTWORLD) + { + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + numlights = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); + for (i = 0; i < numlights; i++) + { + dlight = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, i); + if (!dlight) + continue; + light = &dlight->rtlight; + if (!(light->flags & flag)) + continue; + // sample + lightradius2 = light->radius * light->radius; + VectorSubtract(light->shadoworigin, p, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + dist = sqrt(dist2) / light->radius; + intensity = min(1.0f, (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) * r_shadow_lightintensityscale.value; + if (intensity <= 0.0f) + continue; + if (light->shadow && CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction < 1) + continue; + // scale down intensity to add to both ambient and diffuse + //intensity *= 0.5f; + VectorNormalize(relativepoint); + VectorScale(light->currentcolor, intensity, color); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity *= VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } + } + + if (flags & LP_DYNLIGHT) + { + // sample dlights + for (i = 0;i < r_refdef.scene.numlights;i++) + { + light = r_refdef.scene.lights[i]; + // sample + lightradius2 = light->radius * light->radius; + VectorSubtract(light->shadoworigin, p, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + dist = sqrt(dist2) / light->radius; + intensity = (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist) * r_shadow_lightintensityscale.value; + if (intensity <= 0.0f) + continue; + if (light->shadow && CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction < 1) + continue; + // scale down intensity to add to both ambient and diffuse + //intensity *= 0.5f; + VectorNormalize(relativepoint); + VectorScale(light->currentcolor, intensity, color); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity *= VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } + } + + // calculate the direction we'll use to reduce the sample to a directional light source + VectorCopy(sample + 12, dir); + //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); + VectorNormalize(dir); + // extract the diffuse color along the chosen direction and scale it + diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]); + diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]); + diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]); + // subtract some of diffuse from ambient + VectorMA(sample, -0.333f, diffuse, ambient); + // store the normalized lightdir + VectorCopy(dir, lightdir); +} diff --git a/misc/source/darkplaces-src/r_shadow.h b/misc/source/darkplaces-src/r_shadow.h new file mode 100644 index 00000000..c595fbe9 --- /dev/null +++ b/misc/source/darkplaces-src/r_shadow.h @@ -0,0 +1,107 @@ + +#ifndef R_SHADOW_H +#define R_SHADOW_H + +#define R_SHADOW_SHADOWMAP_NUMCUBEMAPS 8 + +extern cvar_t r_shadow_bumpscale_basetexture; +extern cvar_t r_shadow_bumpscale_bumpmap; +extern cvar_t r_shadow_debuglight; +extern cvar_t r_shadow_gloss; +extern cvar_t r_shadow_gloss2intensity; +extern cvar_t r_shadow_glossintensity; +extern cvar_t r_shadow_glossexponent; +extern cvar_t r_shadow_gloss2exponent; +extern cvar_t r_shadow_glossexact; +extern cvar_t r_shadow_lightattenuationpower; +extern cvar_t r_shadow_lightattenuationscale; +extern cvar_t r_shadow_lightintensityscale; +extern cvar_t r_shadow_lightradiusscale; +extern cvar_t r_shadow_projectdistance; +extern cvar_t r_shadow_frontsidecasting; +extern cvar_t r_shadow_realtime_dlight; +extern cvar_t r_shadow_realtime_dlight_shadows; +extern cvar_t r_shadow_realtime_dlight_svbspculling; +extern cvar_t r_shadow_realtime_dlight_portalculling; +extern cvar_t r_shadow_realtime_world; +extern cvar_t r_shadow_realtime_world_lightmaps; +extern cvar_t r_shadow_realtime_world_shadows; +extern cvar_t r_shadow_realtime_world_compile; +extern cvar_t r_shadow_realtime_world_compileshadow; +extern cvar_t r_shadow_realtime_world_compilesvbsp; +extern cvar_t r_shadow_realtime_world_compileportalculling; +extern cvar_t r_shadow_scissor; +extern cvar_t r_shadow_polygonfactor; +extern cvar_t r_shadow_polygonoffset; +extern cvar_t r_shadow_texture3d; +extern cvar_t gl_ext_separatestencil; +extern cvar_t gl_ext_stenciltwoside; + +// used by shader for bouncegrid feature +extern rtexture_t *r_shadow_bouncegridtexture; +extern matrix4x4_t r_shadow_bouncegridmatrix; +extern vec_t r_shadow_bouncegridintensity; +extern qboolean r_shadow_bouncegriddirectional; + +void R_Shadow_Init(void); +qboolean R_Shadow_ShadowMappingEnabled(void); +void R_Shadow_VolumeFromList(int numverts, int numtris, const float *invertex3f, const int *elements, const int *neighbors, const vec3_t projectorigin, const vec3_t projectdirection, float projectdistance, int nummarktris, const int *marktris, vec3_t trismins, vec3_t trismaxs); +void R_Shadow_ShadowMapFromList(int numverts, int numtris, const float *vertex3f, const int *elements, int numsidetris, const int *sidetotals, const unsigned char *sides, const int *sidetris); +void R_Shadow_MarkVolumeFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs); +int R_Shadow_CalcTriangleSideMask(const vec3_t p1, const vec3_t p2, const vec3_t p3, float bias); +int R_Shadow_CalcSphereSideMask(const vec3_t p1, float radius, float bias); +int R_Shadow_ChooseSidesFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const matrix4x4_t *worldtolight, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs, int *totals); +void R_Shadow_RenderLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist); +void R_Shadow_RenderMode_Begin(void); +void R_Shadow_RenderMode_ActiveLight(const rtlight_t *rtlight); +void R_Shadow_RenderMode_Reset(void); +void R_Shadow_RenderMode_StencilShadowVolumes(qboolean zpass); +void R_Shadow_RenderMode_Lighting(qboolean stenciltest, qboolean transparent, qboolean shadowmapping); +void R_Shadow_RenderMode_DrawDeferredLight(qboolean stenciltest, qboolean shadowmapping); +void R_Shadow_RenderMode_VisibleShadowVolumes(void); +void R_Shadow_RenderMode_VisibleLighting(qboolean stenciltest, qboolean transparent); +void R_Shadow_RenderMode_End(void); +void R_Shadow_ClearStencil(void); +void R_Shadow_SetupEntityLight(const entity_render_t *ent); + +qboolean R_Shadow_ScissorForBBox(const float *mins, const float *maxs); + +// these never change, they are used to create attenuation matrices +extern matrix4x4_t matrix_attenuationxyz; +extern matrix4x4_t matrix_attenuationz; + +void R_Shadow_UpdateWorldLightSelection(void); + +extern rtlight_t *r_shadow_compilingrtlight; + +void R_RTLight_Update(rtlight_t *rtlight, int isstatic, matrix4x4_t *matrix, vec3_t color, int style, const char *cubemapname, int shadow, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags); +void R_RTLight_Compile(rtlight_t *rtlight); +void R_RTLight_Uncompile(rtlight_t *rtlight); + +void R_Shadow_PrepareLights(void); +void R_Shadow_DrawPrepass(void); +void R_Shadow_DrawLights(void); +void R_Shadow_DrawCoronas(void); + +extern int maxshadowmark; +extern int numshadowmark; +extern int *shadowmark; +extern int *shadowmarklist; +extern int shadowmarkcount; +void R_Shadow_PrepareShadowMark(int numtris); + +extern int maxshadowsides; +extern int numshadowsides; +extern unsigned char *shadowsides; +extern int *shadowsideslist; +void R_Shadow_PrepareShadowSides(int numtris); + +void R_Shadow_PrepareModelShadows(void); + +#define LP_LIGHTMAP 1 +#define LP_RTWORLD 2 +#define LP_DYNLIGHT 4 +void R_LightPoint(vec3_t color, const vec3_t p, const int flags); +void R_CompleteLightPoint(vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal, const vec3_t p, const int flags); + +#endif diff --git a/misc/source/darkplaces-src/r_sky.c b/misc/source/darkplaces-src/r_sky.c new file mode 100644 index 00000000..0f019bbb --- /dev/null +++ b/misc/source/darkplaces-src/r_sky.c @@ -0,0 +1,460 @@ + +#include "quakedef.h" +#include "image.h" + +// FIXME: fix skybox after vid_restart +cvar_t r_sky = {CVAR_SAVE, "r_sky", "1", "enables sky rendering (black otherwise)"}; +cvar_t r_skyscroll1 = {CVAR_SAVE, "r_skyscroll1", "1", "speed at which upper clouds layer scrolls in quake sky"}; +cvar_t r_skyscroll2 = {CVAR_SAVE, "r_skyscroll2", "2", "speed at which lower clouds layer scrolls in quake sky"}; +int skyrenderlater; +int skyrendermasked; + +static int skyrendersphere; +static int skyrenderbox; +static rtexturepool_t *skytexturepool; +static char skyname[MAX_QPATH]; +static matrix4x4_t skymatrix; +static matrix4x4_t skyinversematrix; + +typedef struct suffixinfo_s +{ + const char *suffix; + qboolean flipx, flipy, flipdiagonal; +} +suffixinfo_t; +static const suffixinfo_t suffix[3][6] = +{ + { + {"px", false, false, false}, + {"nx", false, false, false}, + {"py", false, false, false}, + {"ny", false, false, false}, + {"pz", false, false, false}, + {"nz", false, false, false} + }, + { + {"posx", false, false, false}, + {"negx", false, false, false}, + {"posy", false, false, false}, + {"negy", false, false, false}, + {"posz", false, false, false}, + {"negz", false, false, false} + }, + { + {"rt", false, false, true}, + {"lf", true, true, true}, + {"bk", false, true, false}, + {"ft", true, false, false}, + {"up", false, false, true}, + {"dn", false, false, true} + } +}; + +static skinframe_t *skyboxskinframe[6]; + +void R_SkyStartFrame(void) +{ + skyrendersphere = false; + skyrenderbox = false; + skyrendermasked = false; + // for depth-masked sky, we need to know whether any sky was rendered + skyrenderlater = false; + if (r_sky.integer) + { + if (skyboxskinframe[0] || skyboxskinframe[1] || skyboxskinframe[2] || skyboxskinframe[3] || skyboxskinframe[4] || skyboxskinframe[5]) + skyrenderbox = true; + else if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.solidskyskinframe) + skyrendersphere = true; + skyrendermasked = true; + } +} + +/* +================== +R_SetSkyBox +================== +*/ +void R_UnloadSkyBox(void) +{ + int i; + int c = 0; + for (i = 0;i < 6;i++) + { + if (skyboxskinframe[i]) + { + // TODO: make a R_SkinFrame_Purge for single skins... + c++; + } + skyboxskinframe[i] = NULL; + } + if (c && developer_loading.integer) + Con_Printf("unloading skybox\n"); +} + +int R_LoadSkyBox(void) +{ + int i, j, success; + int indices[4] = {0,1,2,3}; + char name[MAX_INPUTLINE]; + unsigned char *image_buffer; + unsigned char *temp; + + R_UnloadSkyBox(); + + if (!skyname[0]) + return true; + + for (j=0; j<3; j++) + { + success = 0; + for (i=0; i<6; i++) + { + if (dpsnprintf(name, sizeof(name), "%s_%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + { + if (dpsnprintf(name, sizeof(name), "%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + { + if (dpsnprintf(name, sizeof(name), "env/%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + { + if (dpsnprintf(name, sizeof(name), "gfx/env/%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + continue; + } + } + } + temp = (unsigned char *)Mem_Alloc(tempmempool, image_width*image_height*4); + Image_CopyMux (temp, image_buffer, image_width, image_height, suffix[j][i].flipx, suffix[j][i].flipy, suffix[j][i].flipdiagonal, 4, 4, indices); + skyboxskinframe[i] = R_SkinFrame_LoadInternalBGRA(va("skyboxside%d", i), TEXF_CLAMP | (gl_texturecompression_sky.integer ? TEXF_COMPRESS : 0), temp, image_width, image_height, vid.sRGB3D); + Mem_Free(image_buffer); + Mem_Free(temp); + success++; + } + + if (success) + break; + } + + if (j == 3) + return false; + + if (developer_loading.integer) + Con_Printf("loading skybox \"%s\"\n", name); + + return true; +} + +int R_SetSkyBox(const char *sky) +{ + if (strcmp(sky, skyname) == 0) // no change + return true; + + if (strlen(sky) > 1000) + { + Con_Printf("sky name too long (%i, max is 1000)\n", (int)strlen(sky)); + return false; + } + + strlcpy(skyname, sky, sizeof(skyname)); + + return R_LoadSkyBox(); +} + +// LordHavoc: added LoadSky console command +void LoadSky_f (void) +{ + switch (Cmd_Argc()) + { + case 1: + if (skyname[0]) + Con_Printf("current sky: %s\n", skyname); + else + Con_Print("no skybox has been set\n"); + break; + case 2: + if (R_SetSkyBox(Cmd_Argv(1))) + { + if (skyname[0]) + Con_Printf("skybox set to %s\n", skyname); + else + Con_Print("skybox disabled\n"); + } + else + Con_Printf("failed to load skybox %s\n", Cmd_Argv(1)); + break; + default: + Con_Print("usage: loadsky skyname\n"); + break; + } +} + +static const float skyboxvertex3f[6*4*3] = +{ + // skyside[0] + 16, -16, 16, + 16, -16, -16, + 16, 16, -16, + 16, 16, 16, + // skyside[1] + -16, 16, 16, + -16, 16, -16, + -16, -16, -16, + -16, -16, 16, + // skyside[2] + 16, 16, 16, + 16, 16, -16, + -16, 16, -16, + -16, 16, 16, + // skyside[3] + -16, -16, 16, + -16, -16, -16, + 16, -16, -16, + 16, -16, 16, + // skyside[4] + -16, -16, 16, + 16, -16, 16, + 16, 16, 16, + -16, 16, 16, + // skyside[5] + 16, -16, -16, + -16, -16, -16, + -16, 16, -16, + 16, 16, -16 +}; + +static const float skyboxtexcoord2f[6*4*2] = +{ + // skyside[0] + 0, 1, + 1, 1, + 1, 0, + 0, 0, + // skyside[1] + 1, 0, + 0, 0, + 0, 1, + 1, 1, + // skyside[2] + 1, 1, + 1, 0, + 0, 0, + 0, 1, + // skyside[3] + 0, 0, + 0, 1, + 1, 1, + 1, 0, + // skyside[4] + 0, 1, + 1, 1, + 1, 0, + 0, 0, + // skyside[5] + 0, 1, + 1, 1, + 1, 0, + 0, 0 +}; + +static const int skyboxelement3i[6*2*3] = +{ + // skyside[3] + 0, 1, 2, + 0, 2, 3, + // skyside[1] + 4, 5, 6, + 4, 6, 7, + // skyside[0] + 8, 9, 10, + 8, 10, 11, + // skyside[2] + 12, 13, 14, + 12, 14, 15, + // skyside[4] + 16, 17, 18, + 16, 18, 19, + // skyside[5] + 20, 21, 22, + 20, 22, 23 +}; + +static const unsigned short skyboxelement3s[6*2*3] = +{ + // skyside[3] + 0, 1, 2, + 0, 2, 3, + // skyside[1] + 4, 5, 6, + 4, 6, 7, + // skyside[0] + 8, 9, 10, + 8, 10, 11, + // skyside[2] + 12, 13, 14, + 12, 14, 15, + // skyside[4] + 16, 17, 18, + 16, 18, 19, + // skyside[5] + 20, 21, 22, + 20, 22, 23 +}; + +static void R_SkyBox(void) +{ + int i; + RSurf_ActiveCustomEntity(&skymatrix, &skyinversematrix, 0, 0, 1, 1, 1, 1, 6*4, skyboxvertex3f, skyboxtexcoord2f, NULL, NULL, NULL, NULL, 6*2, skyboxelement3i, skyboxelement3s, false, false); + for (i = 0;i < 6;i++) + if(skyboxskinframe[i]) + R_DrawCustomSurface(skyboxskinframe[i], &identitymatrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST, i*4, 4, i*2, 2, false, false); +} + +#define skygridx 32 +#define skygridx1 (skygridx + 1) +#define skygridxrecip (1.0f / (skygridx)) +#define skygridy 32 +#define skygridy1 (skygridy + 1) +#define skygridyrecip (1.0f / (skygridy)) +#define skysphere_numverts (skygridx1 * skygridy1) +#define skysphere_numtriangles (skygridx * skygridy * 2) +static float skysphere_vertex3f[skysphere_numverts * 3]; +static float skysphere_texcoord2f[skysphere_numverts * 2]; +static int skysphere_element3i[skysphere_numtriangles * 3]; +static unsigned short skysphere_element3s[skysphere_numtriangles * 3]; + +static void skyspherecalc(void) +{ + int i, j; + unsigned short *e; + float a, b, x, ax, ay, v[3], length, *vertex3f, *texcoord2f; + float dx, dy, dz; + dx = 16.0f; + dy = 16.0f; + dz = 16.0f / 3.0f; + vertex3f = skysphere_vertex3f; + texcoord2f = skysphere_texcoord2f; + for (j = 0;j <= skygridy;j++) + { + a = j * skygridyrecip; + ax = cos(a * M_PI * 2); + ay = -sin(a * M_PI * 2); + for (i = 0;i <= skygridx;i++) + { + b = i * skygridxrecip; + x = cos((b + 0.5) * M_PI); + v[0] = ax*x * dx; + v[1] = ay*x * dy; + v[2] = -sin((b + 0.5) * M_PI) * dz; + length = 3.0f / sqrt(v[0]*v[0]+v[1]*v[1]+(v[2]*v[2]*9)); + *texcoord2f++ = v[0] * length; + *texcoord2f++ = v[1] * length; + *vertex3f++ = v[0]; + *vertex3f++ = v[1]; + *vertex3f++ = v[2]; + } + } + e = skysphere_element3s; + for (j = 0;j < skygridy;j++) + { + for (i = 0;i < skygridx;i++) + { + *e++ = j * skygridx1 + i; + *e++ = j * skygridx1 + i + 1; + *e++ = (j + 1) * skygridx1 + i; + + *e++ = j * skygridx1 + i + 1; + *e++ = (j + 1) * skygridx1 + i + 1; + *e++ = (j + 1) * skygridx1 + i; + } + } + for (i = 0;i < skysphere_numtriangles*3;i++) + skysphere_element3i[i] = skysphere_element3s[i]; +} + +static void R_SkySphere(void) +{ + double speedscale; + static qboolean skysphereinitialized = false; + matrix4x4_t scroll1matrix, scroll2matrix; + if (!skysphereinitialized) + { + skysphereinitialized = true; + skyspherecalc(); + } + + // wrap the scroll values just to be extra kind to float accuracy + + // scroll speed for upper layer + speedscale = r_refdef.scene.time*r_skyscroll1.value*8.0/128.0; + speedscale -= floor(speedscale); + Matrix4x4_CreateTranslate(&scroll1matrix, speedscale, speedscale, 0); + // scroll speed for lower layer (transparent layer) + speedscale = r_refdef.scene.time*r_skyscroll2.value*8.0/128.0; + speedscale -= floor(speedscale); + Matrix4x4_CreateTranslate(&scroll2matrix, speedscale, speedscale, 0); + + RSurf_ActiveCustomEntity(&skymatrix, &skyinversematrix, 0, 0, 1, 1, 1, 1, skysphere_numverts, skysphere_vertex3f, skysphere_texcoord2f, NULL, NULL, NULL, NULL, skysphere_numtriangles, skysphere_element3i, skysphere_element3s, false, false); + R_DrawCustomSurface(r_refdef.scene.worldmodel->brush.solidskyskinframe, &scroll1matrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST , 0, skysphere_numverts, 0, skysphere_numtriangles, false, false); + R_DrawCustomSurface(r_refdef.scene.worldmodel->brush.alphaskyskinframe, &scroll2matrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED, 0, skysphere_numverts, 0, skysphere_numtriangles, false, false); +} + +void R_Sky(void) +{ + Matrix4x4_CreateFromQuakeEntity(&skymatrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], 0, 0, 0, r_refdef.farclip * (0.5f / 16.0f)); + Matrix4x4_Invert_Simple(&skyinversematrix, &skymatrix); + + if (skyrendersphere) + { + // this does not modify depth buffer + R_SkySphere(); + } + else if (skyrenderbox) + { + // this does not modify depth buffer + R_SkyBox(); + } + /* this will be skyroom someday + else + { + // this modifies the depth buffer so we have to clear it afterward + //R_SkyRoom(); + // clear the depthbuffer that was used while rendering the skyroom + //GL_Clear(GL_DEPTH_BUFFER_BIT); + } + */ +} + +//=============================================================== + +void R_ResetSkyBox(void) +{ + R_UnloadSkyBox(); + skyname[0] = 0; + R_LoadSkyBox(); +} + +static void r_sky_start(void) +{ + skytexturepool = R_AllocTexturePool(); + R_LoadSkyBox(); +} + +static void r_sky_shutdown(void) +{ + R_UnloadSkyBox(); + R_FreeTexturePool(&skytexturepool); +} + +static void r_sky_newmap(void) +{ +} + + +void R_Sky_Init(void) +{ + Cmd_AddCommand ("loadsky", &LoadSky_f, "load a skybox by basename (for example loadsky mtnsun_ loads mtnsun_ft.tga and so on)"); + Cvar_RegisterVariable (&r_sky); + Cvar_RegisterVariable (&r_skyscroll1); + Cvar_RegisterVariable (&r_skyscroll2); + memset(&skyboxskinframe, 0, sizeof(skyboxskinframe)); + skyname[0] = 0; + R_RegisterModule("R_Sky", r_sky_start, r_sky_shutdown, r_sky_newmap, NULL, NULL); +} + diff --git a/misc/source/darkplaces-src/r_sprites.c b/misc/source/darkplaces-src/r_sprites.c new file mode 100644 index 00000000..486b119f --- /dev/null +++ b/misc/source/darkplaces-src/r_sprites.c @@ -0,0 +1,429 @@ + +#include "quakedef.h" +#include "r_shadow.h" + +extern cvar_t r_labelsprites_scale; +extern cvar_t r_labelsprites_roundtopixels; +extern cvar_t r_track_sprites; +extern cvar_t r_track_sprites_flags; +extern cvar_t r_track_sprites_scalew; +extern cvar_t r_track_sprites_scaleh; +extern cvar_t r_overheadsprites_perspective; +extern cvar_t r_overheadsprites_pushback; +extern cvar_t r_overheadsprites_scalex; +extern cvar_t r_overheadsprites_scaley; + +#define TSF_ROTATE 1 +#define TSF_ROTATE_CONTINOUSLY 2 + +// use same epsilon as in sv_phys.c, it's not in any header, that's why i redefine it +// MIN_EPSILON is for accurateness' sake :) +#ifndef EPSILON +# define EPSILON (1.0f / 32.0f) +# define MIN_EPSILON 0.0001f +#endif + +/* R_Track_Sprite + If the sprite is out of view, track it. + `origin`, `left` and `up` are changed by this function to achive a rotation around + the hotspot. + + --blub + */ +#define SIDE_TOP 1 +#define SIDE_LEFT 2 +#define SIDE_BOTTOM 3 +#define SIDE_RIGHT 4 + +void R_TrackSprite(const entity_render_t *ent, vec3_t origin, vec3_t left, vec3_t up, int *edge, float *dir_angle) +{ + float distance; + vec3_t bCoord; // body coordinates of object + unsigned int i; + + // temporarily abuse bCoord as the vector player->sprite-origin + VectorSubtract(origin, r_refdef.view.origin, bCoord); + distance = VectorLength(bCoord); + + // Now get the bCoords :) + Matrix4x4_Transform(&r_refdef.view.inverse_matrix, origin, bCoord); + + *edge = 0; // FIXME::should assume edge == 0, which is correct currently + for(i = 0; i < 4; ++i) + { + if(PlaneDiff(origin, &r_refdef.view.frustum[i]) < -EPSILON) + break; + } + + // If it wasn't outside a plane, no tracking needed + if(i < 4) + { + float x, y; // screen X and Y coordinates + float ax, ay; // absolute coords, used for division + // I divide x and y by the greater absolute value to get ranges -1.0 to +1.0 + + bCoord[2] *= r_refdef.view.frustum_x; + bCoord[1] *= r_refdef.view.frustum_y; + + //Con_Printf("%f %f %f\n", bCoord[0], bCoord[1], bCoord[2]); + + ax = fabs(bCoord[1]); + ay = fabs(bCoord[2]); + // get the greater value and determine the screen edge it's on + if(ax < ay) + { + ax = ay; + // 180 or 0 degrees + if(bCoord[2] < 0.0f) + *edge = SIDE_BOTTOM; + else + *edge = SIDE_TOP; + } else { + if(bCoord[1] < 0.0f) + *edge = SIDE_RIGHT; + else + *edge = SIDE_LEFT; + } + + // umm... + if(ax < MIN_EPSILON) // this was == 0.0f before --blub + ax = MIN_EPSILON; + // get the -1 to +1 range + x = bCoord[1] / ax; + y = bCoord[2] / ax; + + ax = (1.0f / VectorLength(left)); + ay = (1.0f / VectorLength(up)); + // Using the placement below the distance of a sprite is + // real dist = sqrt(d*d + dfxa*dfxa + dgyb*dgyb) + // d is the distance we use + // f is frustum X + // x is x + // a is ax + // g is frustum Y + // y is y + // b is ay + + // real dist (r) shall be d, so + // r*r = d*d + dfxa*dfxa + dgyb*dgyb + // r*r = d*d * (1 + fxa*fxa + gyb*gyb) + // d*d = r*r / (1 + fxa*fxa + gyb*gyb) + // d = sqrt(r*r / (1 + fxa*fxa + gyb*gyb)) + // thus: + distance = sqrt((distance*distance) / (1.0 + + r_refdef.view.frustum_x*r_refdef.view.frustum_x * x*x * ax*ax + + r_refdef.view.frustum_y*r_refdef.view.frustum_y * y*y * ay*ay)); + // ^ the one we want ^ the one we have ^ our factors + + // Place the sprite a few units ahead of the player + VectorCopy(r_refdef.view.origin, origin); + VectorMA(origin, distance, r_refdef.view.forward, origin); + // Move the sprite left / up the screeen height + VectorMA(origin, distance * r_refdef.view.frustum_x * x * ax, left, origin); + VectorMA(origin, distance * r_refdef.view.frustum_y * y * ay, up, origin); + + if(r_track_sprites_flags.integer & TSF_ROTATE_CONTINOUSLY) + { + // compute the rotation, negate y axis, we're pointing outwards + *dir_angle = atan(-y / x) * 180.0f/M_PI; + // we need the real, full angle + if(x < 0.0f) + *dir_angle += 180.0f; + } + + left[0] *= r_track_sprites_scalew.value; + left[1] *= r_track_sprites_scalew.value; + left[2] *= r_track_sprites_scalew.value; + + up[0] *= r_track_sprites_scaleh.value; + up[1] *= r_track_sprites_scaleh.value; + up[2] *= r_track_sprites_scaleh.value; + } +} + +void R_RotateSprite(const mspriteframe_t *frame, vec3_t origin, vec3_t left, vec3_t up, int edge, float dir_angle) +{ + if(!(r_track_sprites_flags.integer & TSF_ROTATE)) + { + // move down by its size if on top, otherwise it's invisible + if(edge == SIDE_TOP) + VectorMA(origin, -(fabs(frame->up)+fabs(frame->down)), up, origin); + } else { + static float rotation_angles[5] = + { + 0, // no edge + -90.0f, //top + 0.0f, // left + 90.0f, // bottom + 180.0f, // right + }; + + // rotate around the hotspot according to which edge it's on + // since the hotspot == the origin, only rotate the vectors + matrix4x4_t rotm; + vec3_t axis; + vec3_t temp; + vec2_t dir; + float angle; + + if(edge < 1 || edge > 4) + return; // this usually means something went wrong somewhere, there's no way to get a wrong edge value currently + + dir[0] = frame->right + frame->left; + dir[1] = frame->down + frame->up; + + // only rotate when the hotspot isn't the center though. + if(dir[0] < MIN_EPSILON && dir[1] < MIN_EPSILON) + { + return; + } + + // Now that we've kicked center-hotspotted sprites, rotate using the appropriate matrix :) + + // determine the angle of a sprite, we could only do that once though and + // add a `qboolean initialized' to the mspriteframe_t struct... let's get the direction vector of it :) + + angle = atan(dir[1] / dir[0]) * 180.0f/M_PI; + + // we need the real, full angle + if(dir[0] < 0.0f) + angle += 180.0f; + + // Rotate around rotation_angle - frame_angle + // The axis SHOULD equal r_refdef.view.forward, but let's generalize this: + CrossProduct(up, left, axis); + if(r_track_sprites_flags.integer & TSF_ROTATE_CONTINOUSLY) + Matrix4x4_CreateRotate(&rotm, dir_angle - angle, axis[0], axis[1], axis[2]); + else + Matrix4x4_CreateRotate(&rotm, rotation_angles[edge] - angle, axis[0], axis[1], axis[2]); + Matrix4x4_Transform(&rotm, up, temp); + VectorCopy(temp, up); + Matrix4x4_Transform(&rotm, left, temp); + VectorCopy(temp, left); + } +} + +static float spritetexcoord2f[4*2] = {0, 1, 0, 0, 1, 0, 1, 1}; + +void R_Model_Sprite_Draw_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i; + dp_model_t *model = ent->model; + vec3_t left, up, org, mforward, mleft, mup, middle; + float scale, dx, dy, hud_vs_screen; + int edge = 0; + float dir_angle = 0.0f; + float vertex3f[12]; + + // nudge it toward the view to make sure it isn't in a wall + Matrix4x4_ToVectors(&ent->matrix, mforward, mleft, mup, org); + VectorSubtract(org, r_refdef.view.forward, org); + switch(model->sprite.sprnum_type) + { + case SPR_VP_PARALLEL_UPRIGHT: + // flames and such + // vertical beam sprite, faces view plane + scale = ent->scale / sqrt(r_refdef.view.forward[0]*r_refdef.view.forward[0]+r_refdef.view.forward[1]*r_refdef.view.forward[1]); + left[0] = -r_refdef.view.forward[1] * scale; + left[1] = r_refdef.view.forward[0] * scale; + left[2] = 0; + up[0] = 0; + up[1] = 0; + up[2] = ent->scale; + break; + case SPR_FACING_UPRIGHT: + // flames and such + // vertical beam sprite, faces viewer's origin (not the view plane) + scale = ent->scale / sqrt((org[0] - r_refdef.view.origin[0])*(org[0] - r_refdef.view.origin[0])+(org[1] - r_refdef.view.origin[1])*(org[1] - r_refdef.view.origin[1])); + left[0] = (org[1] - r_refdef.view.origin[1]) * scale; + left[1] = -(org[0] - r_refdef.view.origin[0]) * scale; + left[2] = 0; + up[0] = 0; + up[1] = 0; + up[2] = ent->scale; + break; + default: + Con_Printf("R_SpriteSetup: unknown sprite type %i\n", model->sprite.sprnum_type); + // fall through to normal sprite + case SPR_VP_PARALLEL: + // normal sprite + // faces view plane + VectorScale(r_refdef.view.left, ent->scale, left); + VectorScale(r_refdef.view.up, ent->scale, up); + break; + case SPR_LABEL_SCALE: + // normal sprite + // faces view plane + // fixed HUD pixel size specified in sprite + // honors scale + // honors a global label scaling cvar + + if(r_waterstate.renderingscene) // labels are considered HUD items, and don't appear in reflections + return; + + // See the R_TrackSprite definition for a reason for this copying + VectorCopy(r_refdef.view.left, left); + VectorCopy(r_refdef.view.up, up); + // It has to be done before the calculations, because it moves the origin. + if(r_track_sprites.integer) + R_TrackSprite(ent, org, left, up, &edge, &dir_angle); + + scale = 2 * ent->scale * (DotProduct(r_refdef.view.forward, org) - DotProduct(r_refdef.view.forward, r_refdef.view.origin)) * r_labelsprites_scale.value; + VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer, left); // 1px + VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer, up); // 1px + break; + case SPR_LABEL: + // normal sprite + // faces view plane + // fixed pixel size specified in sprite + // tries to get the right size in HUD units, if possible + // ignores scale + // honors a global label scaling cvar before the rounding + // FIXME assumes that 1qu is 1 pixel in the sprite like in SPR32 format. Should not do that, but instead query the source image! This bug only applies to the roundtopixels case, though. + + if(r_waterstate.renderingscene) // labels are considered HUD items, and don't appear in reflections + return; + + // See the R_TrackSprite definition for a reason for this copying + VectorCopy(r_refdef.view.left, left); + VectorCopy(r_refdef.view.up, up); + // It has to be done before the calculations, because it moves the origin. + if(r_track_sprites.integer) + R_TrackSprite(ent, org, left, up, &edge, &dir_angle); + + scale = 2 * (DotProduct(r_refdef.view.forward, org) - DotProduct(r_refdef.view.forward, r_refdef.view.origin)); + + if(r_labelsprites_roundtopixels.integer) + { + hud_vs_screen = max( + vid_conwidth.integer / (float) r_refdef.view.width, + vid_conheight.integer / (float) r_refdef.view.height + ) / max(0.125, r_labelsprites_scale.value); + + // snap to "good sizes" + // 1 for (0.6, 1.41] + // 2 for (1.8, 3.33] + if(hud_vs_screen <= 0.6) + hud_vs_screen = 0; // don't, use real HUD pixels + else if(hud_vs_screen <= 1.41) + hud_vs_screen = 1; + else if(hud_vs_screen <= 3.33) + hud_vs_screen = 2; + else + hud_vs_screen = 0; // don't, use real HUD pixels + + if(hud_vs_screen) + { + // use screen pixels + VectorScale(left, scale * r_refdef.view.frustum_x / (r_refdef.view.width * hud_vs_screen), left); // 1px + VectorScale(up, scale * r_refdef.view.frustum_y / (r_refdef.view.height * hud_vs_screen), up); // 1px + } + else + { + // use HUD pixels + VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer * r_labelsprites_scale.value, left); // 1px + VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer * r_labelsprites_scale.value, up); // 1px + } + + if(hud_vs_screen == 1) + { + VectorMA(r_refdef.view.origin, scale, r_refdef.view.forward, middle); // center of screen in distance scale + dx = 0.5 - fmod(r_refdef.view.width * 0.5 + (DotProduct(org, left) - DotProduct(middle, left)) / DotProduct(left, left) + 0.5, 1.0); + dy = 0.5 - fmod(r_refdef.view.height * 0.5 + (DotProduct(org, up) - DotProduct(middle, up)) / DotProduct(up, up) + 0.5, 1.0); + VectorMAMAM(1, org, dx, left, dy, up, org); + } + } + else + { + // use HUD pixels + VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer * r_labelsprites_scale.value, left); // 1px + VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer * r_labelsprites_scale.value, up); // 1px + } + break; + case SPR_ORIENTED: + // bullet marks on walls + // ignores viewer entirely + VectorCopy(mleft, left); + VectorCopy(mup, up); + break; + case SPR_VP_PARALLEL_ORIENTED: + // I have no idea what people would use this for... + // oriented relative to view space + // FIXME: test this and make sure it mimicks software + left[0] = mleft[0] * r_refdef.view.forward[0] + mleft[1] * r_refdef.view.left[0] + mleft[2] * r_refdef.view.up[0]; + left[1] = mleft[0] * r_refdef.view.forward[1] + mleft[1] * r_refdef.view.left[1] + mleft[2] * r_refdef.view.up[1]; + left[2] = mleft[0] * r_refdef.view.forward[2] + mleft[1] * r_refdef.view.left[2] + mleft[2] * r_refdef.view.up[2]; + up[0] = mup[0] * r_refdef.view.forward[0] + mup[1] * r_refdef.view.left[0] + mup[2] * r_refdef.view.up[0]; + up[1] = mup[0] * r_refdef.view.forward[1] + mup[1] * r_refdef.view.left[1] + mup[2] * r_refdef.view.up[1]; + up[2] = mup[0] * r_refdef.view.forward[2] + mup[1] * r_refdef.view.left[2] + mup[2] * r_refdef.view.up[2]; + break; + case SPR_OVERHEAD: + // Overhead games sprites, have some special hacks to look good + VectorScale(r_refdef.view.left, ent->scale * r_overheadsprites_scalex.value, left); + VectorScale(r_refdef.view.up, ent->scale * r_overheadsprites_scaley.value, up); + VectorSubtract(org, r_refdef.view.origin, middle); + VectorNormalize(middle); + // offset and rotate + dir_angle = r_overheadsprites_perspective.value * (1 - fabs(DotProduct(middle, r_refdef.view.forward))); + up[2] = up[2] + dir_angle; + VectorNormalize(up); + VectorScale(up, ent->scale * r_overheadsprites_scaley.value, up); + // offset (move nearer to player, yz is camera plane) + org[0] = org[0] - middle[0]*r_overheadsprites_pushback.value; + org[1] = org[1] - middle[1]*r_overheadsprites_pushback.value; + org[2] = org[2] - middle[2]*r_overheadsprites_pushback.value; + // little perspective effect + up[2] = up[2] + dir_angle * 0.3; + // a bit of counter-camera rotation + up[0] = up[0] + r_refdef.view.forward[0] * 0.07; + up[1] = up[1] + r_refdef.view.forward[1] * 0.07; + up[2] = up[2] + r_refdef.view.forward[2] * 0.07; + break; + } + + // LordHavoc: interpolated sprite rendering + for (i = 0;i < MAX_FRAMEBLENDS;i++) + { + if (ent->frameblend[i].lerp >= 0.01f) + { + mspriteframe_t *frame; + texture_t *texture; + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, ent->flags, 0, ent->colormod[0], ent->colormod[1], ent->colormod[2], ent->alpha * ent->frameblend[i].lerp, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + frame = model->sprite.sprdata_frames + ent->frameblend[i].subframe; + texture = R_GetCurrentTexture(model->data_textures + ent->frameblend[i].subframe); + + // lit sprite by lightgrid if it is not fullbright, lit only ambient + if (!(texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) + VectorAdd(ent->modellight_ambient, ent->modellight_diffuse, rsurface.modellight_ambient); // sprites dont use lightdirection + + // SPR_LABEL should not use depth test AT ALL + if(model->sprite.sprnum_type == SPR_LABEL || model->sprite.sprnum_type == SPR_LABEL_SCALE) + if(texture->currentmaterialflags & MATERIALFLAG_SHORTDEPTHRANGE) + texture->currentmaterialflags = (texture->currentmaterialflags & ~MATERIALFLAG_SHORTDEPTHRANGE) | MATERIALFLAG_NODEPTHTEST; + + if(edge) + { + // FIXME:: save vectors/origin and re-rotate? necessary if the hotspot can change per frame + R_RotateSprite(frame, org, left, up, edge, dir_angle); + edge = 0; + } + + R_CalcSprite_Vertex3f(vertex3f, org, left, up, frame->left, frame->right, frame->down, frame->up); + + R_DrawCustomSurface_Texture(texture, &identitymatrix, texture->currentmaterialflags, 0, 4, 0, 2, false, false); + } + } + + rsurface.entity = NULL; +} + +void R_Model_Sprite_Draw(entity_render_t *ent) +{ + vec3_t org; + if (ent->frameblend[0].subframe < 0) + return; + + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + R_MeshQueue_AddTransparent(ent->flags & RENDER_NODEPTHTEST ? r_refdef.view.origin : org, R_Model_Sprite_Draw_TransparentCallback, ent, 0, rsurface.rtlight); +} + diff --git a/misc/source/darkplaces-src/r_textures.h b/misc/source/darkplaces-src/r_textures.h new file mode 100644 index 00000000..8c59f72f --- /dev/null +++ b/misc/source/darkplaces-src/r_textures.h @@ -0,0 +1,198 @@ + +#ifndef R_TEXTURES_H +#define R_TEXTURES_H + +// transparent +#define TEXF_ALPHA 0x00000001 +// mipmapped +#define TEXF_MIPMAP 0x00000002 +// multiply RGB by A channel before uploading +#define TEXF_RGBMULTIPLYBYALPHA 0x00000004 +// indicates texture coordinates should be clamped rather than wrapping +#define TEXF_CLAMP 0x00000020 +// indicates texture should be uploaded using GL_NEAREST or GL_NEAREST_MIPMAP_NEAREST mode +#define TEXF_FORCENEAREST 0x00000040 +// indicates texture should be uploaded using GL_LINEAR or GL_LINEAR_MIPMAP_NEAREST or GL_LINEAR_MIPMAP_LINEAR mode +#define TEXF_FORCELINEAR 0x00000080 +// indicates texture should be affected by gl_picmip and gl_max_size cvar +#define TEXF_PICMIP 0x00000100 +// indicates texture should be compressed if possible +#define TEXF_COMPRESS 0x00000200 +// use this flag to block R_PurgeTexture from freeing a texture (only used by r_texture_white and similar which may be used in skinframe_t) +#define TEXF_PERSISTENT 0x00000400 +// indicates texture should use GL_COMPARE_R_TO_TEXTURE mode +#define TEXF_COMPARE 0x00000800 +// indicates texture should use lower precision where supported +#define TEXF_LOWPRECISION 0x00001000 +// indicates texture should support R_UpdateTexture on small regions, actual uploads may be delayed until R_Mesh_TexBind if gl_nopartialtextureupdates is on +#define TEXF_ALLOWUPDATES 0x00002000 +// indicates texture should be affected by gl_picmip_world and r_picmipworld (maybe others in the future) instead of gl_picmip_other +#define TEXF_ISWORLD 0x00004000 +// indicates texture should be affected by gl_picmip_sprites and r_picmipsprites (maybe others in the future) instead of gl_picmip_other +#define TEXF_ISSPRITE 0x00008000 +// indicates the texture will be used as a render target (D3D hint) +#define TEXF_RENDERTARGET 0x0010000 +// used for checking if textures mismatch +#define TEXF_IMPORTANTBITS (TEXF_ALPHA | TEXF_MIPMAP | TEXF_RGBMULTIPLYBYALPHA | TEXF_CLAMP | TEXF_FORCENEAREST | TEXF_FORCELINEAR | TEXF_PICMIP | TEXF_COMPRESS | TEXF_COMPARE | TEXF_LOWPRECISION | TEXF_RENDERTARGET) + +typedef enum textype_e +{ + // 8bit paletted + TEXTYPE_PALETTE, + // 32bit RGBA + TEXTYPE_RGBA, + // 32bit BGRA (preferred format due to faster uploads on most hardware) + TEXTYPE_BGRA, + // 8bit ALPHA (used for freetype fonts) + TEXTYPE_ALPHA, + // 4x4 block compressed 15bit color (4 bits per pixel) + TEXTYPE_DXT1, + // 4x4 block compressed 15bit color plus 1bit alpha (4 bits per pixel) + TEXTYPE_DXT1A, + // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) + TEXTYPE_DXT3, + // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) + TEXTYPE_DXT5, + + // 8bit paletted in sRGB colorspace + TEXTYPE_SRGB_PALETTE, + // 32bit RGBA in sRGB colorspace + TEXTYPE_SRGB_RGBA, + // 32bit BGRA (preferred format due to faster uploads on most hardware) in sRGB colorspace + TEXTYPE_SRGB_BGRA, + // 4x4 block compressed 15bit color (4 bits per pixel) in sRGB colorspace + TEXTYPE_SRGB_DXT1, + // 4x4 block compressed 15bit color plus 1bit alpha (4 bits per pixel) in sRGB colorspace + TEXTYPE_SRGB_DXT1A, + // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) in sRGB colorspace + TEXTYPE_SRGB_DXT3, + // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) in sRGB colorspace + TEXTYPE_SRGB_DXT5, + + // this represents the same format as the framebuffer, for fast copies + TEXTYPE_COLORBUFFER, + // this represents an RGBA half_float texture (4 16bit floats) + TEXTYPE_COLORBUFFER16F, + // this represents an RGBA float texture (4 32bit floats) + TEXTYPE_COLORBUFFER32F, + // 16bit D16 (16bit depth) or 32bit S8D24 (24bit depth, 8bit stencil unused) + TEXTYPE_SHADOWMAP +} +textype_t; + +/* +#ifdef WIN32 +#define SUPPORTD3D +#define SUPPORTDIRECTX +#ifdef SUPPORTD3D +#include +#endif +#endif +*/ + +// contents of this structure are mostly private to gl_textures.c +typedef struct rtexture_s +{ + // this is exposed (rather than private) for speed reasons only + int texnum; + qboolean dirty; + int gltexturetypeenum; // exposed for use in R_Mesh_TexBind + // d3d stuff the backend needs + void *d3dtexture; +#ifdef SUPPORTD3D + qboolean d3disdepthsurface; // for depth/stencil surfaces + int d3dformat; + int d3dusage; + int d3dpool; + int d3daddressu; + int d3daddressv; + int d3daddressw; + int d3dmagfilter; + int d3dminfilter; + int d3dmipfilter; + int d3dmaxmiplevelfilter; + int d3dmipmaplodbias; + int d3dmaxmiplevel; +#endif +} +rtexture_t; + +// contents of this structure are private to gl_textures.c +typedef struct rtexturepool_s +{ + int useless; +} +rtexturepool_t; + +typedef void (*updatecallback_t)(rtexture_t *rt, void *data); + +// allocate a texture pool, to be used with R_LoadTexture +rtexturepool_t *R_AllocTexturePool(void); +// free a texture pool (textures can not be freed individually) +void R_FreeTexturePool(rtexturepool_t **rtexturepool); + +// the color/normal/etc cvars should be checked by callers of R_LoadTexture* functions to decide whether to add TEXF_COMPRESS to the flags +extern cvar_t gl_texturecompression; +extern cvar_t gl_texturecompression_color; +extern cvar_t gl_texturecompression_normal; +extern cvar_t gl_texturecompression_gloss; +extern cvar_t gl_texturecompression_glow; +extern cvar_t gl_texturecompression_2d; +extern cvar_t gl_texturecompression_q3bsplightmaps; +extern cvar_t gl_texturecompression_q3bspdeluxemaps; +extern cvar_t gl_texturecompression_sky; +extern cvar_t gl_texturecompression_lightcubemaps; +extern cvar_t gl_texturecompression_reflectmask; +extern cvar_t r_texture_dds_load; +extern cvar_t r_texture_dds_save; + +// add a texture to a pool and optionally precache (upload) it +// (note: data == NULL is perfectly acceptable) +// (note: palette must not be NULL if using TEXTYPE_PALETTE) +rtexture_t *R_LoadTexture2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); +rtexture_t *R_LoadTexture3D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); +rtexture_t *R_LoadTextureCubeMap(rtexturepool_t *rtexturepool, const char *identifier, int width, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); +rtexture_t *R_LoadTextureShadowMap2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int precision, qboolean filter); +rtexture_t *R_LoadTextureDDSFile(rtexturepool_t *rtexturepool, const char *filename, int flags, qboolean *hasalphaflag, float *avgcolor, int miplevel); + +// saves a texture to a DDS file +int R_SaveTextureDDSFile(rtexture_t *rt, const char *filename, qboolean skipuncompressed, qboolean hasalpha); + +// free a texture +void R_FreeTexture(rtexture_t *rt); + +// update a portion of the image data of a texture, used by lightmap updates +// and procedural textures such as video playback, actual uploads may be +// delayed by gl_nopartialtextureupdates cvar until R_Mesh_TexBind uses it +void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth); + +// returns the renderer dependent texture slot number (call this before each +// use, as a texture might not have been precached) +#define R_GetTexture(rt) ((rt) ? ((rt)->dirty ? R_RealGetTexture(rt) : (rt)->texnum) : r_texture_white->texnum) +int R_RealGetTexture (rtexture_t *rt); + +// returns width of texture, as was specified when it was uploaded +int R_TextureWidth(rtexture_t *rt); + +// returns height of texture, as was specified when it was uploaded +int R_TextureHeight(rtexture_t *rt); + +// only frees the texture if TEXF_PERSISTENT is not set +// also resets the variable +void R_PurgeTexture(rtexture_t *prt); + +// frees processing buffers each frame, and may someday animate procedural textures +void R_Textures_Frame(void); + +// maybe rename this - sounds awful? [11/21/2007 Black] +void R_MarkDirtyTexture(rtexture_t *rt); +void R_MakeTextureDynamic(rtexture_t *rt, updatecallback_t updatecallback, void *data); + +// Clear the texture's contents +void R_ClearTexture (rtexture_t *rt); + +// returns the desired picmip level for given TEXF_ flags +int R_PicmipForFlags(int flags); + +#endif + diff --git a/misc/source/darkplaces-src/render.h b/misc/source/darkplaces-src/render.h new file mode 100644 index 00000000..9ef7403c --- /dev/null +++ b/misc/source/darkplaces-src/render.h @@ -0,0 +1,487 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef RENDER_H +#define RENDER_H + +#include "svbsp.h" + +// 1.0f / N table +extern float ixtable[4096]; + +// fog stuff +extern void FOG_clear(void); + +// sky stuff +extern cvar_t r_sky; +extern cvar_t r_skyscroll1; +extern cvar_t r_skyscroll2; +extern int skyrenderlater, skyrendermasked; +extern int R_SetSkyBox(const char *sky); +extern void R_SkyStartFrame(void); +extern void R_Sky(void); +extern void R_ResetSkyBox(void); + +// SHOWLMP stuff (Nehahra) +extern void SHOWLMP_decodehide(void); +extern void SHOWLMP_decodeshow(void); +extern void SHOWLMP_drawall(void); + +// render profiling stuff +extern int r_timereport_active; + +// lighting stuff +extern cvar_t r_ambient; +extern cvar_t gl_flashblend; + +// vis stuff +extern cvar_t r_novis; + +extern cvar_t r_trippy; + +extern cvar_t r_lerpsprites; +extern cvar_t r_lerpmodels; +extern cvar_t r_lerplightstyles; +extern cvar_t r_waterscroll; + +extern cvar_t developer_texturelogging; + +// shadow volume bsp struct with automatically growing nodes buffer +extern svbsp_t r_svbsp; + +typedef struct rmesh_s +{ + // vertices of this mesh + int maxvertices; + int numvertices; + float *vertex3f; + float *svector3f; + float *tvector3f; + float *normal3f; + float *texcoord2f; + float *texcoordlightmap2f; + float *color4f; + // triangles of this mesh + int maxtriangles; + int numtriangles; + int *element3i; + int *neighbor3i; + // snapping epsilon + float epsilon2; +} +rmesh_t; + +// useful functions for rendering +void R_ModulateColors(float *in, float *out, int verts, float r, float g, float b); +void R_FillColors(float *out, int verts, float r, float g, float b, float a); +int R_Mesh_AddVertex3f(rmesh_t *mesh, const float *v); +void R_Mesh_AddPolygon3f(rmesh_t *mesh, int numvertices, float *vertex3f); +void R_Mesh_AddBrushMeshFromPlanes(rmesh_t *mesh, int numplanes, mplane_t *planes); + +#define TOP_RANGE 16 // soldier uniform colors +#define BOTTOM_RANGE 96 + +//============================================================================= + +extern cvar_t r_nearclip; + +// forces all rendering to draw triangle outlines +extern cvar_t r_showoverdraw; +extern cvar_t r_showtris; +extern cvar_t r_shownormals; +extern cvar_t r_showlighting; +extern cvar_t r_showshadowvolumes; +extern cvar_t r_showcollisionbrushes; +extern cvar_t r_showcollisionbrushes_polygonfactor; +extern cvar_t r_showcollisionbrushes_polygonoffset; +extern cvar_t r_showdisabledepthtest; + +// +// view origin +// +extern cvar_t r_drawentities; +extern cvar_t r_draw2d; +extern qboolean r_draw2d_force; +extern cvar_t r_drawviewmodel; +extern cvar_t r_drawworld; +extern cvar_t r_speeds; +extern cvar_t r_fullbright; +extern cvar_t r_wateralpha; +extern cvar_t r_dynamic; + +void R_Init(void); +void R_UpdateVariables(void); // must call after setting up most of r_refdef, but before calling R_RenderView +void R_RenderView(void); // must set r_refdef and call R_UpdateVariables first + +typedef enum r_refdef_scene_type_s { + RST_CLIENT, + RST_MENU, + RST_COUNT +} r_refdef_scene_type_t; + +void R_SelectScene( r_refdef_scene_type_t scenetype ); +r_refdef_scene_t * R_GetScenePointer( r_refdef_scene_type_t scenetype ); + +void R_SkinFrame_PrepareForPurge(void); +void R_SkinFrame_MarkUsed(skinframe_t *skinframe); +void R_SkinFrame_Purge(void); +// set last to NULL to start from the beginning +skinframe_t *R_SkinFrame_FindNextByName( skinframe_t *last, const char *name ); +skinframe_t *R_SkinFrame_Find(const char *name, int textureflags, int comparewidth, int compareheight, int comparecrc, qboolean add); +skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboolean complain); +skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, const unsigned char *skindata, int width, int height, qboolean sRGB); +skinframe_t *R_SkinFrame_LoadInternalQuake(const char *name, int textureflags, int loadpantsandshirt, int loadglowtexture, const unsigned char *skindata, int width, int height); +skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, const unsigned char *skindata, int width, int height, const unsigned int *palette, const unsigned int *alphapalette); +skinframe_t *R_SkinFrame_LoadMissing(void); + +rtexture_t *R_GetCubemap(const char *basename); + +void R_View_WorldVisibility(qboolean forcenovis); +void R_DrawDecals(void); +void R_DrawParticles(void); +void R_DrawExplosions(void); + +#define gl_solid_format 3 +#define gl_alpha_format 4 + +int R_CullBox(const vec3_t mins, const vec3_t maxs); +int R_CullBoxCustomPlanes(const vec3_t mins, const vec3_t maxs, int numplanes, const mplane_t *planes); + +#include "r_modules.h" + +#include "meshqueue.h" + +void R_FrameData_Reset(void); +void R_FrameData_NewFrame(void); +void *R_FrameData_Alloc(size_t size); +void *R_FrameData_Store(size_t size, void *data); +void R_FrameData_SetMark(void); +void R_FrameData_ReturnToMark(void); + +void R_AnimCache_Free(void); +void R_AnimCache_ClearCache(void); +qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qboolean wanttangents); +void R_AnimCache_CacheVisibleEntities(void); + +#include "r_lerpanim.h" + +extern cvar_t r_render; +extern cvar_t r_renderview; +extern cvar_t r_waterwarp; + +extern cvar_t r_textureunits; + +extern cvar_t r_glsl_offsetmapping; +extern cvar_t r_glsl_offsetmapping_reliefmapping; +extern cvar_t r_glsl_offsetmapping_scale; +extern cvar_t r_glsl_deluxemapping; + +extern cvar_t gl_polyblend; +extern cvar_t gl_dither; + +extern cvar_t cl_deathfade; + +extern cvar_t r_smoothnormals_areaweighting; + +extern cvar_t r_test; + +#include "gl_backend.h" + +extern rtexture_t *r_texture_blanknormalmap; +extern rtexture_t *r_texture_white; +extern rtexture_t *r_texture_grey128; +extern rtexture_t *r_texture_black; +extern rtexture_t *r_texture_notexture; +extern rtexture_t *r_texture_whitecube; +extern rtexture_t *r_texture_normalizationcube; +extern rtexture_t *r_texture_fogattenuation; +extern rtexture_t *r_texture_fogheighttexture; + +extern unsigned int r_queries[MAX_OCCLUSION_QUERIES]; +extern unsigned int r_numqueries; +extern unsigned int r_maxqueries; + +void R_TimeReport(const char *name); + +// r_stain +void R_Stain(const vec3_t origin, float radius, int cr1, int cg1, int cb1, int ca1, int cr2, int cg2, int cb2, int ca2); + +void R_CalcBeam_Vertex3f(float *vert, const vec3_t org1, const vec3_t org2, float width); +void R_CalcSprite_Vertex3f(float *vertex3f, const vec3_t origin, const vec3_t left, const vec3_t up, float scalex1, float scalex2, float scaley1, float scaley2); + +extern mempool_t *r_main_mempool; + +typedef struct rsurfacestate_s +{ + // current model array pointers + // these may point to processing buffers if model is animated, + // otherwise they point to static data. + // these are not directly used for rendering, they are just another level + // of processing + // + // these either point at array_model* buffers (if the model is animated) + // or the model->surfmesh.data_* buffers (if the model is not animated) + // + // these are only set when an entity render begins, they do not change on + // a per surface basis. + // + // this indicates the model* arrays are pointed at array_model* buffers + // (in other words, the model has been animated in software) + qboolean modelgeneratedvertex; + float *modelvertex3f; + const r_meshbuffer_t *modelvertex3f_vertexbuffer; + size_t modelvertex3f_bufferoffset; + float *modelsvector3f; + const r_meshbuffer_t *modelsvector3f_vertexbuffer; + size_t modelsvector3f_bufferoffset; + float *modeltvector3f; + const r_meshbuffer_t *modeltvector3f_vertexbuffer; + size_t modeltvector3f_bufferoffset; + float *modelnormal3f; + const r_meshbuffer_t *modelnormal3f_vertexbuffer; + size_t modelnormal3f_bufferoffset; + float *modellightmapcolor4f; + const r_meshbuffer_t *modellightmapcolor4f_vertexbuffer; + size_t modellightmapcolor4f_bufferoffset; + float *modeltexcoordtexture2f; + const r_meshbuffer_t *modeltexcoordtexture2f_vertexbuffer; + size_t modeltexcoordtexture2f_bufferoffset; + float *modeltexcoordlightmap2f; + const r_meshbuffer_t *modeltexcoordlightmap2f_vertexbuffer; + size_t modeltexcoordlightmap2f_bufferoffset; + r_vertexmesh_t *modelvertexmesh; + const r_meshbuffer_t *modelvertexmeshbuffer; + const r_meshbuffer_t *modelvertex3fbuffer; + int *modelelement3i; + const r_meshbuffer_t *modelelement3i_indexbuffer; + size_t modelelement3i_bufferoffset; + unsigned short *modelelement3s; + const r_meshbuffer_t *modelelement3s_indexbuffer; + size_t modelelement3s_bufferoffset; + int *modellightmapoffsets; + int modelnumvertices; + int modelnumtriangles; + const msurface_t *modelsurfaces; + // current rendering array pointers + // these may point to any of several different buffers depending on how + // much processing was needed to prepare this model for rendering + // these usually equal the model* pointers, they only differ if + // deformvertexes is used in a q3 shader, and consequently these can + // change on a per-surface basis (according to rsurface.texture) + qboolean batchgeneratedvertex; + int batchfirstvertex; + int batchnumvertices; + int batchfirsttriangle; + int batchnumtriangles; + r_vertexmesh_t *batchvertexmesh; + const r_meshbuffer_t *batchvertexmeshbuffer; + const r_meshbuffer_t *batchvertex3fbuffer; + float *batchvertex3f; + const r_meshbuffer_t *batchvertex3f_vertexbuffer; + size_t batchvertex3f_bufferoffset; + float *batchsvector3f; + const r_meshbuffer_t *batchsvector3f_vertexbuffer; + size_t batchsvector3f_bufferoffset; + float *batchtvector3f; + const r_meshbuffer_t *batchtvector3f_vertexbuffer; + size_t batchtvector3f_bufferoffset; + float *batchnormal3f; + const r_meshbuffer_t *batchnormal3f_vertexbuffer; + size_t batchnormal3f_bufferoffset; + float *batchlightmapcolor4f; + const r_meshbuffer_t *batchlightmapcolor4f_vertexbuffer; + size_t batchlightmapcolor4f_bufferoffset; + float *batchtexcoordtexture2f; + const r_meshbuffer_t *batchtexcoordtexture2f_vertexbuffer; + size_t batchtexcoordtexture2f_bufferoffset; + float *batchtexcoordlightmap2f; + const r_meshbuffer_t *batchtexcoordlightmap2f_vertexbuffer; + size_t batchtexcoordlightmap2f_bufferoffset; + int *batchelement3i; + const r_meshbuffer_t *batchelement3i_indexbuffer; + size_t batchelement3i_bufferoffset; + unsigned short *batchelement3s; + const r_meshbuffer_t *batchelement3s_indexbuffer; + size_t batchelement3s_bufferoffset; + // rendering pass processing arrays in GL11 and GL13 paths + float *passcolor4f; + const r_meshbuffer_t *passcolor4f_vertexbuffer; + size_t passcolor4f_bufferoffset; + + // some important fields from the entity + int ent_skinnum; + int ent_qwskin; + int ent_flags; + int ent_alttextures; // used by q1bsp animated textures (pressed buttons) + double shadertime; // r_refdef.scene.time - ent->shadertime + // transform matrices to render this entity and effects on this entity + matrix4x4_t matrix; + matrix4x4_t inversematrix; + // scale factors for transforming lengths into/out of entity space + float matrixscale; + float inversematrixscale; + // animation blending state from entity + frameblend_t frameblend[MAX_FRAMEBLENDS]; + skeleton_t *skeleton; + // directional model shading state from entity + vec3_t modellight_ambient; + vec3_t modellight_diffuse; + vec3_t modellight_lightdir; + // colormapping state from entity (these are black if colormapping is off) + vec3_t colormap_pantscolor; + vec3_t colormap_shirtcolor; + // special coloring of ambient/diffuse textures (gloss not affected) + // colormod[3] is the alpha of the entity + float colormod[4]; + // special coloring of glow textures + float glowmod[3]; + // view location in model space + vec3_t localvieworigin; + // polygon offset data for submodels + float basepolygonfactor; + float basepolygonoffset; + // current textures in batching code + texture_t *texture; + rtexture_t *lightmaptexture; + rtexture_t *deluxemaptexture; + // whether lightmapping is active on this batch + // (otherwise vertex colored) + qboolean uselightmaptexture; + // fog plane in model space for direct application to vertices + float fograngerecip; + float fogmasktabledistmultiplier; + float fogplane[4]; + float fogheightfade; + float fogplaneviewdist; + + // rtlight rendering + // light currently being rendered + const rtlight_t *rtlight; + + // this is the location of the light in entity space + vec3_t entitylightorigin; + // this transforms entity coordinates to light filter cubemap coordinates + // (also often used for other purposes) + matrix4x4_t entitytolight; + // based on entitytolight this transforms -1 to +1 to 0 to 1 for purposes + // of attenuation texturing in full 3D (Z result often ignored) + matrix4x4_t entitytoattenuationxyz; + // this transforms only the Z to S, and T is always 0.5 + matrix4x4_t entitytoattenuationz; + + // user wavefunc parameters (from csqc) + float userwavefunc_param[Q3WAVEFUNC_USER_COUNT]; + + // pointer to an entity_render_t used only by R_GetCurrentTexture and + // RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity as a unique id within + // each frame (see r_frame also) + entity_render_t *entity; +} +rsurfacestate_t; + +extern rsurfacestate_t rsurface; + +void R_HDR_UpdateIrisAdaptation(const vec3_t point); + +void RSurf_ActiveWorldEntity(void); +void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, qboolean wanttangents, qboolean prepass); +void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, int entflags, double shadertime, float r, float g, float b, float a, int numvertices, const float *vertex3f, const float *texcoord2f, const float *normal3f, const float *svector3f, const float *tvector3f, const float *color4f, int numtriangles, const int *element3i, const unsigned short *element3s, qboolean wantnormals, qboolean wanttangents); +void RSurf_SetupDepthAndCulling(void); + +void R_Mesh_ResizeArrays(int newvertices); + +texture_t *R_GetCurrentTexture(texture_t *t); +void R_DrawWorldSurfaces(qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass); +void R_DrawModelSurfaces(entity_render_t *ent, qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass); +void R_AddWaterPlanes(entity_render_t *ent); +void R_DrawCustomSurface(skinframe_t *skinframe, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass); +void R_DrawCustomSurface_Texture(texture_t *texture, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass); + +#define BATCHNEED_VERTEXMESH_VERTEX (1<< 1) // set up rsurface.batchvertexmesh +#define BATCHNEED_VERTEXMESH_NORMAL (1<< 2) // set up normals in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchnormal3f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_VECTOR (1<< 3) // set up vectors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchsvector3f and rsurface.batchtvector3f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_VERTEXCOLOR (1<< 4) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_TEXCOORD (1<< 5) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_LIGHTMAP (1<< 6) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_VERTEX (1<< 7) // set up rsurface.batchvertex3f and optionally others +#define BATCHNEED_ARRAY_NORMAL (1<< 8) // set up normals in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchnormal3f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_VECTOR (1<< 9) // set up vectors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchsvector3f and rsurface.batchtvector3f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_VERTEXCOLOR (1<<10) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_TEXCOORD (1<<11) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_LIGHTMAP (1<<12) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_NOGAPS (1<<13) // force vertex copying (no gaps) +void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const msurface_t **texturesurfacelist); +void RSurf_DrawBatch(void); + +void R_DecalSystem_SplatEntities(const vec3_t org, const vec3_t normal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float size); + +typedef enum rsurfacepass_e +{ + RSURFPASS_BASE, + RSURFPASS_BACKGROUND, + RSURFPASS_RTLIGHT, + RSURFPASS_DEFERREDGEOMETRY +} +rsurfacepass_t; + +void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemode, int rgbscale, qboolean notrippy); +void R_SetupShader_DepthOrShadow(qboolean notrippy); +void R_SetupShader_ShowDepth(qboolean notrippy); +void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting, float ambientscale, float diffusescale, float specularscale, rsurfacepass_t rsurfacepass, int texturenumsurfaces, const msurface_t **texturesurfacelist, void *waterplane, qboolean notrippy); +void R_SetupShader_DeferredLight(const rtlight_t *rtlight); + +typedef struct r_waterstate_waterplane_s +{ + rtexture_t *texture_refraction; + rtexture_t *texture_reflection; + rtexture_t *texture_camera; + mplane_t plane; + int materialflags; // combined flags of all water surfaces on this plane + unsigned char pvsbits[(MAX_MAP_LEAFS+7)>>3]; // FIXME: buffer overflow on huge maps + qboolean pvsvalid; + int camera_entity; + vec3_t mins, maxs; +} +r_waterstate_waterplane_t; + +typedef struct r_waterstate_s +{ + qboolean enabled; + + qboolean renderingscene; // true while rendering a refraction or reflection texture, disables water surfaces + qboolean renderingrefraction; + + int waterwidth, waterheight; + int texturewidth, textureheight; + int camerawidth, cameraheight; + + int maxwaterplanes; // same as MAX_WATERPLANES + int numwaterplanes; + r_waterstate_waterplane_t waterplanes[MAX_WATERPLANES]; + + float screenscale[2]; + float screencenter[2]; +} +r_waterstate_t; + +extern r_waterstate_t r_waterstate; + +#endif + diff --git a/misc/source/darkplaces-src/resource.h b/misc/source/darkplaces-src/resource.h new file mode 100644 index 00000000..27fc918d --- /dev/null +++ b/misc/source/darkplaces-src/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by darkplaces.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/misc/source/darkplaces-src/sbar.c b/misc/source/darkplaces-src/sbar.c new file mode 100644 index 00000000..80c6330f --- /dev/null +++ b/misc/source/darkplaces-src/sbar.c @@ -0,0 +1,2241 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sbar.c -- status bar code + +#include "quakedef.h" +#include "time.h" + +cachepic_t *sb_disc; + +#define STAT_MINUS 10 // num frame for '-' stats digit +cachepic_t *sb_nums[2][11]; +cachepic_t *sb_colon, *sb_slash; +cachepic_t *sb_ibar; +cachepic_t *sb_sbar; +cachepic_t *sb_scorebar; +// AK only used by NEX +cachepic_t *sb_sbar_minimal; +cachepic_t *sb_sbar_overlay; + +// AK changed the bound to 9 +cachepic_t *sb_weapons[7][9]; // 0 is active, 1 is owned, 2-5 are flashes +cachepic_t *sb_ammo[4]; +cachepic_t *sb_sigil[4]; +cachepic_t *sb_armor[3]; +cachepic_t *sb_items[32]; + +// 0-4 are based on health (in 20 increments) +// 0 is static, 1 is temporary animation +cachepic_t *sb_faces[5][2]; +cachepic_t *sb_health; // GAME_NEXUIZ + +cachepic_t *sb_face_invis; +cachepic_t *sb_face_quad; +cachepic_t *sb_face_invuln; +cachepic_t *sb_face_invis_invuln; + +qboolean sb_showscores; + +int sb_lines; // scan lines to draw + +cachepic_t *rsb_invbar[2]; +cachepic_t *rsb_weapons[5]; +cachepic_t *rsb_items[2]; +cachepic_t *rsb_ammo[3]; +cachepic_t *rsb_teambord; // PGM 01/19/97 - team color border + +//MED 01/04/97 added two more weapons + 3 alternates for grenade launcher +cachepic_t *hsb_weapons[7][5]; // 0 is active, 1 is owned, 2-5 are flashes +//MED 01/04/97 added array to simplify weapon parsing +int hipweapons[4] = {HIT_LASER_CANNON_BIT,HIT_MJOLNIR_BIT,4,HIT_PROXIMITY_GUN_BIT}; +//MED 01/04/97 added hipnotic items array +cachepic_t *hsb_items[2]; + +//GAME_SOM stuff: +cachepic_t *somsb_health; +cachepic_t *somsb_ammo[4]; +cachepic_t *somsb_armor[3]; + +cachepic_t *zymsb_crosshair_center; +cachepic_t *zymsb_crosshair_line; +cachepic_t *zymsb_crosshair_health; +cachepic_t *zymsb_crosshair_ammo; +cachepic_t *zymsb_crosshair_clip; +cachepic_t *zymsb_crosshair_background; +cachepic_t *zymsb_crosshair_left1; +cachepic_t *zymsb_crosshair_left2; +cachepic_t *zymsb_crosshair_right; + +cachepic_t *sb_ranking; +cachepic_t *sb_complete; +cachepic_t *sb_inter; +cachepic_t *sb_finale; + +cvar_t showfps = {CVAR_SAVE, "showfps", "0", "shows your rendered fps (frames per second)"}; +cvar_t showsound = {CVAR_SAVE, "showsound", "0", "shows number of active sound sources, sound latency, and other statistics"}; +cvar_t showblur = {CVAR_SAVE, "showblur", "0", "shows the current alpha level of motionblur"}; +cvar_t showspeed = {CVAR_SAVE, "showspeed", "0", "shows your current speed (qu per second); number selects unit: 1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots"}; +cvar_t showtopspeed = {CVAR_SAVE, "showtopspeed", "0", "shows your top speed (kept on screen for max 3 seconds); value -1 takes over the unit from showspeed, otherwise it's an unit number just like in showspeed"}; +cvar_t showtime = {CVAR_SAVE, "showtime", "0", "shows current time of day (useful on screenshots)"}; +cvar_t showtime_format = {CVAR_SAVE, "showtime_format", "%H:%M:%S", "format string for time of day"}; +cvar_t showdate = {CVAR_SAVE, "showdate", "0", "shows current date (useful on screenshots)"}; +cvar_t showdate_format = {CVAR_SAVE, "showdate_format", "%Y-%m-%d", "format string for date"}; +cvar_t sbar_alpha_bg = {CVAR_SAVE, "sbar_alpha_bg", "0.4", "opacity value of the statusbar background image"}; +cvar_t sbar_alpha_fg = {CVAR_SAVE, "sbar_alpha_fg", "1", "opacity value of the statusbar weapon/item icons and numbers"}; +cvar_t sbar_hudselector = {CVAR_SAVE, "sbar_hudselector", "0", "selects which of the builtin hud layouts to use (meaning is somewhat dependent on gamemode, so nexuiz has a very different set of hud layouts than quake for example)"}; +cvar_t sbar_scorerank = {CVAR_SAVE, "sbar_scorerank", "1", "shows an overlay for your score (or team score) and rank in the scoreboard"}; +cvar_t sbar_gametime = {CVAR_SAVE, "sbar_gametime", "1", "shows an overlay for the time left in the current match/level (or current game time if there is no timelimit set)"}; +cvar_t sbar_miniscoreboard_size = {CVAR_SAVE, "sbar_miniscoreboard_size", "-1", "sets the size of the mini deathmatch overlay in items, or disables it when set to 0, or sets it to a sane default when set to -1"}; +cvar_t sbar_flagstatus_right = {CVAR_SAVE, "sbar_flagstatus_right", "0", "moves Nexuiz flag status icons to the right"}; +cvar_t sbar_flagstatus_pos = {CVAR_SAVE, "sbar_flagstatus_pos", "115", "pixel position of the Nexuiz flag status icons, from the bottom"}; +cvar_t sbar_info_pos = {CVAR_SAVE, "sbar_info_pos", "0", "pixel position of the info strings (such as showfps), from the bottom"}; + +cvar_t cl_deathscoreboard = {0, "cl_deathscoreboard", "1", "shows scoreboard (+showscores) while dead"}; + +cvar_t crosshair_color_red = {CVAR_SAVE, "crosshair_color_red", "1", "customizable crosshair color"}; +cvar_t crosshair_color_green = {CVAR_SAVE, "crosshair_color_green", "0", "customizable crosshair color"}; +cvar_t crosshair_color_blue = {CVAR_SAVE, "crosshair_color_blue", "0", "customizable crosshair color"}; +cvar_t crosshair_color_alpha = {CVAR_SAVE, "crosshair_color_alpha", "1", "how opaque the crosshair should be"}; +cvar_t crosshair_size = {CVAR_SAVE, "crosshair_size", "1", "adjusts size of the crosshair on the screen"}; + +void Sbar_MiniDeathmatchOverlay (int x, int y); +void Sbar_DeathmatchOverlay (void); +void Sbar_IntermissionOverlay (void); +void Sbar_FinaleOverlay (void); + +void CL_VM_UpdateShowingScoresState (int showingscores); + + +/* +=============== +Sbar_ShowScores + +Tab key down +=============== +*/ +void Sbar_ShowScores (void) +{ + if (sb_showscores) + return; + sb_showscores = true; + CL_VM_UpdateShowingScoresState(sb_showscores); +} + +/* +=============== +Sbar_DontShowScores + +Tab key up +=============== +*/ +void Sbar_DontShowScores (void) +{ + sb_showscores = false; + CL_VM_UpdateShowingScoresState(sb_showscores); +} + +void sbar_start(void) +{ + int i; + + if (gamemode == GAME_DELUXEQUAKE || gamemode == GAME_BLOODOMNICIDE) + { + } + else if (gamemode == GAME_SOM) + { + sb_disc = Draw_CachePic_Flags ("gfx/disc", CACHEPICFLAG_QUIET); + + for (i = 0;i < 10;i++) + sb_nums[0][i] = Draw_CachePic_Flags (va("gfx/num_%i",i), CACHEPICFLAG_QUIET); + + somsb_health = Draw_CachePic_Flags ("gfx/hud_health", CACHEPICFLAG_QUIET); + somsb_ammo[0] = Draw_CachePic_Flags ("gfx/sb_shells", CACHEPICFLAG_QUIET); + somsb_ammo[1] = Draw_CachePic_Flags ("gfx/sb_nails", CACHEPICFLAG_QUIET); + somsb_ammo[2] = Draw_CachePic_Flags ("gfx/sb_rocket", CACHEPICFLAG_QUIET); + somsb_ammo[3] = Draw_CachePic_Flags ("gfx/sb_cells", CACHEPICFLAG_QUIET); + somsb_armor[0] = Draw_CachePic_Flags ("gfx/sb_armor1", CACHEPICFLAG_QUIET); + somsb_armor[1] = Draw_CachePic_Flags ("gfx/sb_armor2", CACHEPICFLAG_QUIET); + somsb_armor[2] = Draw_CachePic_Flags ("gfx/sb_armor3", CACHEPICFLAG_QUIET); + } + else if (gamemode == GAME_NEXUIZ) + { + for (i = 0;i < 10;i++) + sb_nums[0][i] = Draw_CachePic_Flags (va("gfx/num_%i",i), CACHEPICFLAG_QUIET); + sb_nums[0][10] = Draw_CachePic_Flags ("gfx/num_minus", CACHEPICFLAG_QUIET); + sb_colon = Draw_CachePic_Flags ("gfx/num_colon", CACHEPICFLAG_QUIET); + + sb_ammo[0] = Draw_CachePic_Flags ("gfx/sb_shells", CACHEPICFLAG_QUIET); + sb_ammo[1] = Draw_CachePic_Flags ("gfx/sb_bullets", CACHEPICFLAG_QUIET); + sb_ammo[2] = Draw_CachePic_Flags ("gfx/sb_rocket", CACHEPICFLAG_QUIET); + sb_ammo[3] = Draw_CachePic_Flags ("gfx/sb_cells", CACHEPICFLAG_QUIET); + + sb_armor[0] = Draw_CachePic_Flags ("gfx/sb_armor", CACHEPICFLAG_QUIET); + sb_armor[1] = NULL; + sb_armor[2] = NULL; + + sb_health = Draw_CachePic_Flags ("gfx/sb_health", CACHEPICFLAG_QUIET); + + sb_items[2] = Draw_CachePic_Flags ("gfx/sb_slowmo", CACHEPICFLAG_QUIET); + sb_items[3] = Draw_CachePic_Flags ("gfx/sb_invinc", CACHEPICFLAG_QUIET); + sb_items[4] = Draw_CachePic_Flags ("gfx/sb_energy", CACHEPICFLAG_QUIET); + sb_items[5] = Draw_CachePic_Flags ("gfx/sb_str", CACHEPICFLAG_QUIET); + + sb_items[11] = Draw_CachePic_Flags ("gfx/sb_flag_red_taken", CACHEPICFLAG_QUIET); + sb_items[12] = Draw_CachePic_Flags ("gfx/sb_flag_red_lost", CACHEPICFLAG_QUIET); + sb_items[13] = Draw_CachePic_Flags ("gfx/sb_flag_red_carrying", CACHEPICFLAG_QUIET); + sb_items[14] = Draw_CachePic_Flags ("gfx/sb_key_carrying", CACHEPICFLAG_QUIET); + sb_items[15] = Draw_CachePic_Flags ("gfx/sb_flag_blue_taken", CACHEPICFLAG_QUIET); + sb_items[16] = Draw_CachePic_Flags ("gfx/sb_flag_blue_lost", CACHEPICFLAG_QUIET); + sb_items[17] = Draw_CachePic_Flags ("gfx/sb_flag_blue_carrying", CACHEPICFLAG_QUIET); + + sb_sbar = Draw_CachePic_Flags ("gfx/sbar", CACHEPICFLAG_QUIET); + sb_sbar_minimal = Draw_CachePic_Flags ("gfx/sbar_minimal", CACHEPICFLAG_QUIET); + sb_sbar_overlay = Draw_CachePic_Flags ("gfx/sbar_overlay", CACHEPICFLAG_QUIET); + + for(i = 0; i < 9;i++) + sb_weapons[0][i] = Draw_CachePic_Flags (va("gfx/inv_weapon%i",i), CACHEPICFLAG_QUIET); + } + else if (gamemode == GAME_ZYMOTIC) + { + zymsb_crosshair_center = Draw_CachePic_Flags ("gfx/hud/crosshair_center", CACHEPICFLAG_QUIET); + zymsb_crosshair_line = Draw_CachePic_Flags ("gfx/hud/crosshair_line", CACHEPICFLAG_QUIET); + zymsb_crosshair_health = Draw_CachePic_Flags ("gfx/hud/crosshair_health", CACHEPICFLAG_QUIET); + zymsb_crosshair_clip = Draw_CachePic_Flags ("gfx/hud/crosshair_clip", CACHEPICFLAG_QUIET); + zymsb_crosshair_ammo = Draw_CachePic_Flags ("gfx/hud/crosshair_ammo", CACHEPICFLAG_QUIET); + zymsb_crosshair_background = Draw_CachePic_Flags ("gfx/hud/crosshair_background", CACHEPICFLAG_QUIET); + zymsb_crosshair_left1 = Draw_CachePic_Flags ("gfx/hud/crosshair_left1", CACHEPICFLAG_QUIET); + zymsb_crosshair_left2 = Draw_CachePic_Flags ("gfx/hud/crosshair_left2", CACHEPICFLAG_QUIET); + zymsb_crosshair_right = Draw_CachePic_Flags ("gfx/hud/crosshair_right", CACHEPICFLAG_QUIET); + } + else + { + sb_disc = Draw_CachePic_Flags ("gfx/disc", CACHEPICFLAG_QUIET); + + for (i = 0;i < 10;i++) + { + sb_nums[0][i] = Draw_CachePic_Flags (va("gfx/num_%i",i), CACHEPICFLAG_QUIET); + sb_nums[1][i] = Draw_CachePic_Flags (va("gfx/anum_%i",i), CACHEPICFLAG_QUIET); + } + + sb_nums[0][10] = Draw_CachePic_Flags ("gfx/num_minus", CACHEPICFLAG_QUIET); + sb_nums[1][10] = Draw_CachePic_Flags ("gfx/anum_minus", CACHEPICFLAG_QUIET); + + sb_colon = Draw_CachePic_Flags ("gfx/num_colon", CACHEPICFLAG_QUIET); + sb_slash = Draw_CachePic_Flags ("gfx/num_slash", CACHEPICFLAG_QUIET); + + sb_weapons[0][0] = Draw_CachePic_Flags ("gfx/inv_shotgun", CACHEPICFLAG_QUIET); + sb_weapons[0][1] = Draw_CachePic_Flags ("gfx/inv_sshotgun", CACHEPICFLAG_QUIET); + sb_weapons[0][2] = Draw_CachePic_Flags ("gfx/inv_nailgun", CACHEPICFLAG_QUIET); + sb_weapons[0][3] = Draw_CachePic_Flags ("gfx/inv_snailgun", CACHEPICFLAG_QUIET); + sb_weapons[0][4] = Draw_CachePic_Flags ("gfx/inv_rlaunch", CACHEPICFLAG_QUIET); + sb_weapons[0][5] = Draw_CachePic_Flags ("gfx/inv_srlaunch", CACHEPICFLAG_QUIET); + sb_weapons[0][6] = Draw_CachePic_Flags ("gfx/inv_lightng", CACHEPICFLAG_QUIET); + + sb_weapons[1][0] = Draw_CachePic_Flags ("gfx/inv2_shotgun", CACHEPICFLAG_QUIET); + sb_weapons[1][1] = Draw_CachePic_Flags ("gfx/inv2_sshotgun", CACHEPICFLAG_QUIET); + sb_weapons[1][2] = Draw_CachePic_Flags ("gfx/inv2_nailgun", CACHEPICFLAG_QUIET); + sb_weapons[1][3] = Draw_CachePic_Flags ("gfx/inv2_snailgun", CACHEPICFLAG_QUIET); + sb_weapons[1][4] = Draw_CachePic_Flags ("gfx/inv2_rlaunch", CACHEPICFLAG_QUIET); + sb_weapons[1][5] = Draw_CachePic_Flags ("gfx/inv2_srlaunch", CACHEPICFLAG_QUIET); + sb_weapons[1][6] = Draw_CachePic_Flags ("gfx/inv2_lightng", CACHEPICFLAG_QUIET); + + for (i = 0;i < 5;i++) + { + sb_weapons[2+i][0] = Draw_CachePic_Flags (va("gfx/inva%i_shotgun",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][1] = Draw_CachePic_Flags (va("gfx/inva%i_sshotgun",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][2] = Draw_CachePic_Flags (va("gfx/inva%i_nailgun",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][3] = Draw_CachePic_Flags (va("gfx/inva%i_snailgun",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][4] = Draw_CachePic_Flags (va("gfx/inva%i_rlaunch",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][5] = Draw_CachePic_Flags (va("gfx/inva%i_srlaunch",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][6] = Draw_CachePic_Flags (va("gfx/inva%i_lightng",i+1), CACHEPICFLAG_QUIET); + } + + sb_ammo[0] = Draw_CachePic_Flags ("gfx/sb_shells", CACHEPICFLAG_QUIET); + sb_ammo[1] = Draw_CachePic_Flags ("gfx/sb_nails", CACHEPICFLAG_QUIET); + sb_ammo[2] = Draw_CachePic_Flags ("gfx/sb_rocket", CACHEPICFLAG_QUIET); + sb_ammo[3] = Draw_CachePic_Flags ("gfx/sb_cells", CACHEPICFLAG_QUIET); + + sb_armor[0] = Draw_CachePic_Flags ("gfx/sb_armor1", CACHEPICFLAG_QUIET); + sb_armor[1] = Draw_CachePic_Flags ("gfx/sb_armor2", CACHEPICFLAG_QUIET); + sb_armor[2] = Draw_CachePic_Flags ("gfx/sb_armor3", CACHEPICFLAG_QUIET); + + sb_items[0] = Draw_CachePic_Flags ("gfx/sb_key1", CACHEPICFLAG_QUIET); + sb_items[1] = Draw_CachePic_Flags ("gfx/sb_key2", CACHEPICFLAG_QUIET); + sb_items[2] = Draw_CachePic_Flags ("gfx/sb_invis", CACHEPICFLAG_QUIET); + sb_items[3] = Draw_CachePic_Flags ("gfx/sb_invuln", CACHEPICFLAG_QUIET); + sb_items[4] = Draw_CachePic_Flags ("gfx/sb_suit", CACHEPICFLAG_QUIET); + sb_items[5] = Draw_CachePic_Flags ("gfx/sb_quad", CACHEPICFLAG_QUIET); + + sb_sigil[0] = Draw_CachePic_Flags ("gfx/sb_sigil1", CACHEPICFLAG_QUIET); + sb_sigil[1] = Draw_CachePic_Flags ("gfx/sb_sigil2", CACHEPICFLAG_QUIET); + sb_sigil[2] = Draw_CachePic_Flags ("gfx/sb_sigil3", CACHEPICFLAG_QUIET); + sb_sigil[3] = Draw_CachePic_Flags ("gfx/sb_sigil4", CACHEPICFLAG_QUIET); + + sb_faces[4][0] = Draw_CachePic_Flags ("gfx/face1", CACHEPICFLAG_QUIET); + sb_faces[4][1] = Draw_CachePic_Flags ("gfx/face_p1", CACHEPICFLAG_QUIET); + sb_faces[3][0] = Draw_CachePic_Flags ("gfx/face2", CACHEPICFLAG_QUIET); + sb_faces[3][1] = Draw_CachePic_Flags ("gfx/face_p2", CACHEPICFLAG_QUIET); + sb_faces[2][0] = Draw_CachePic_Flags ("gfx/face3", CACHEPICFLAG_QUIET); + sb_faces[2][1] = Draw_CachePic_Flags ("gfx/face_p3", CACHEPICFLAG_QUIET); + sb_faces[1][0] = Draw_CachePic_Flags ("gfx/face4", CACHEPICFLAG_QUIET); + sb_faces[1][1] = Draw_CachePic_Flags ("gfx/face_p4", CACHEPICFLAG_QUIET); + sb_faces[0][0] = Draw_CachePic_Flags ("gfx/face5", CACHEPICFLAG_QUIET); + sb_faces[0][1] = Draw_CachePic_Flags ("gfx/face_p5", CACHEPICFLAG_QUIET); + + sb_face_invis = Draw_CachePic_Flags ("gfx/face_invis", CACHEPICFLAG_QUIET); + sb_face_invuln = Draw_CachePic_Flags ("gfx/face_invul2", CACHEPICFLAG_QUIET); + sb_face_invis_invuln = Draw_CachePic_Flags ("gfx/face_inv2", CACHEPICFLAG_QUIET); + sb_face_quad = Draw_CachePic_Flags ("gfx/face_quad", CACHEPICFLAG_QUIET); + + sb_sbar = Draw_CachePic_Flags ("gfx/sbar", CACHEPICFLAG_QUIET); + sb_ibar = Draw_CachePic_Flags ("gfx/ibar", CACHEPICFLAG_QUIET); + sb_scorebar = Draw_CachePic_Flags ("gfx/scorebar", CACHEPICFLAG_QUIET); + + //MED 01/04/97 added new hipnotic weapons + if (gamemode == GAME_HIPNOTIC) + { + hsb_weapons[0][0] = Draw_CachePic_Flags ("gfx/inv_laser", CACHEPICFLAG_QUIET); + hsb_weapons[0][1] = Draw_CachePic_Flags ("gfx/inv_mjolnir", CACHEPICFLAG_QUIET); + hsb_weapons[0][2] = Draw_CachePic_Flags ("gfx/inv_gren_prox", CACHEPICFLAG_QUIET); + hsb_weapons[0][3] = Draw_CachePic_Flags ("gfx/inv_prox_gren", CACHEPICFLAG_QUIET); + hsb_weapons[0][4] = Draw_CachePic_Flags ("gfx/inv_prox", CACHEPICFLAG_QUIET); + + hsb_weapons[1][0] = Draw_CachePic_Flags ("gfx/inv2_laser", CACHEPICFLAG_QUIET); + hsb_weapons[1][1] = Draw_CachePic_Flags ("gfx/inv2_mjolnir", CACHEPICFLAG_QUIET); + hsb_weapons[1][2] = Draw_CachePic_Flags ("gfx/inv2_gren_prox", CACHEPICFLAG_QUIET); + hsb_weapons[1][3] = Draw_CachePic_Flags ("gfx/inv2_prox_gren", CACHEPICFLAG_QUIET); + hsb_weapons[1][4] = Draw_CachePic_Flags ("gfx/inv2_prox", CACHEPICFLAG_QUIET); + + for (i = 0;i < 5;i++) + { + hsb_weapons[2+i][0] = Draw_CachePic_Flags (va("gfx/inva%i_laser",i+1), CACHEPICFLAG_QUIET); + hsb_weapons[2+i][1] = Draw_CachePic_Flags (va("gfx/inva%i_mjolnir",i+1), CACHEPICFLAG_QUIET); + hsb_weapons[2+i][2] = Draw_CachePic_Flags (va("gfx/inva%i_gren_prox",i+1), CACHEPICFLAG_QUIET); + hsb_weapons[2+i][3] = Draw_CachePic_Flags (va("gfx/inva%i_prox_gren",i+1), CACHEPICFLAG_QUIET); + hsb_weapons[2+i][4] = Draw_CachePic_Flags (va("gfx/inva%i_prox",i+1), CACHEPICFLAG_QUIET); + } + + hsb_items[0] = Draw_CachePic_Flags ("gfx/sb_wsuit", CACHEPICFLAG_QUIET); + hsb_items[1] = Draw_CachePic_Flags ("gfx/sb_eshld", CACHEPICFLAG_QUIET); + } + else if (gamemode == GAME_ROGUE) + { + rsb_invbar[0] = Draw_CachePic_Flags ("gfx/r_invbar1", CACHEPICFLAG_QUIET); + rsb_invbar[1] = Draw_CachePic_Flags ("gfx/r_invbar2", CACHEPICFLAG_QUIET); + + rsb_weapons[0] = Draw_CachePic_Flags ("gfx/r_lava", CACHEPICFLAG_QUIET); + rsb_weapons[1] = Draw_CachePic_Flags ("gfx/r_superlava", CACHEPICFLAG_QUIET); + rsb_weapons[2] = Draw_CachePic_Flags ("gfx/r_gren", CACHEPICFLAG_QUIET); + rsb_weapons[3] = Draw_CachePic_Flags ("gfx/r_multirock", CACHEPICFLAG_QUIET); + rsb_weapons[4] = Draw_CachePic_Flags ("gfx/r_plasma", CACHEPICFLAG_QUIET); + + rsb_items[0] = Draw_CachePic_Flags ("gfx/r_shield1", CACHEPICFLAG_QUIET); + rsb_items[1] = Draw_CachePic_Flags ("gfx/r_agrav1", CACHEPICFLAG_QUIET); + + // PGM 01/19/97 - team color border + rsb_teambord = Draw_CachePic_Flags ("gfx/r_teambord", CACHEPICFLAG_QUIET); + // PGM 01/19/97 - team color border + + rsb_ammo[0] = Draw_CachePic_Flags ("gfx/r_ammolava", CACHEPICFLAG_QUIET); + rsb_ammo[1] = Draw_CachePic_Flags ("gfx/r_ammomulti", CACHEPICFLAG_QUIET); + rsb_ammo[2] = Draw_CachePic_Flags ("gfx/r_ammoplasma", CACHEPICFLAG_QUIET); + } + } + + sb_ranking = Draw_CachePic_Flags ("gfx/ranking", CACHEPICFLAG_QUIET); + sb_complete = Draw_CachePic_Flags ("gfx/complete", CACHEPICFLAG_QUIET); + sb_inter = Draw_CachePic_Flags ("gfx/inter", CACHEPICFLAG_QUIET); + sb_finale = Draw_CachePic_Flags ("gfx/finale", CACHEPICFLAG_QUIET); +} + +void sbar_shutdown(void) +{ +} + +void sbar_newmap(void) +{ +} + +void Sbar_Init (void) +{ + Cmd_AddCommand("+showscores", Sbar_ShowScores, "show scoreboard"); + Cmd_AddCommand("-showscores", Sbar_DontShowScores, "hide scoreboard"); + Cvar_RegisterVariable(&showfps); + Cvar_RegisterVariable(&showsound); + Cvar_RegisterVariable(&showblur); + Cvar_RegisterVariable(&showspeed); + Cvar_RegisterVariable(&showtopspeed); + Cvar_RegisterVariable(&showtime); + Cvar_RegisterVariable(&showtime_format); + Cvar_RegisterVariable(&showdate); + Cvar_RegisterVariable(&showdate_format); + Cvar_RegisterVariable(&sbar_alpha_bg); + Cvar_RegisterVariable(&sbar_alpha_fg); + Cvar_RegisterVariable(&sbar_hudselector); + Cvar_RegisterVariable(&sbar_scorerank); + Cvar_RegisterVariable(&sbar_gametime); + Cvar_RegisterVariable(&sbar_miniscoreboard_size); + Cvar_RegisterVariable(&sbar_info_pos); + Cvar_RegisterVariable(&cl_deathscoreboard); + + Cvar_RegisterVariable(&crosshair_color_red); + Cvar_RegisterVariable(&crosshair_color_green); + Cvar_RegisterVariable(&crosshair_color_blue); + Cvar_RegisterVariable(&crosshair_color_alpha); + Cvar_RegisterVariable(&crosshair_size); + + Cvar_RegisterVariable(&sbar_flagstatus_right); // (GAME_NEXUZI ONLY) + Cvar_RegisterVariable(&sbar_flagstatus_pos); // (GAME_NEXUIZ ONLY) + + R_RegisterModule("sbar", sbar_start, sbar_shutdown, sbar_newmap, NULL, NULL); +} + + +//============================================================================= + +// drawing routines are relative to the status bar location + +int sbar_x, sbar_y; + +/* +============= +Sbar_DrawPic +============= +*/ +void Sbar_DrawStretchPic (int x, int y, cachepic_t *pic, float alpha, float overridewidth, float overrideheight) +{ + DrawQ_Pic (sbar_x + x, sbar_y + y, pic, overridewidth, overrideheight, 1, 1, 1, alpha, 0); +} + +void Sbar_DrawPic (int x, int y, cachepic_t *pic) +{ + DrawQ_Pic (sbar_x + x, sbar_y + y, pic, 0, 0, 1, 1, 1, sbar_alpha_fg.value, 0); +} + +void Sbar_DrawAlphaPic (int x, int y, cachepic_t *pic, float alpha) +{ + DrawQ_Pic (sbar_x + x, sbar_y + y, pic, 0, 0, 1, 1, 1, alpha, 0); +} + +/* +================ +Sbar_DrawCharacter + +Draws one solid graphics character +================ +*/ +void Sbar_DrawCharacter (int x, int y, int num) +{ + DrawQ_String (sbar_x + x + 4 , sbar_y + y, va("%c", num), 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, true, FONT_SBAR); +} + +/* +================ +Sbar_DrawString +================ +*/ +void Sbar_DrawString (int x, int y, char *str) +{ + DrawQ_String (sbar_x + x, sbar_y + y, str, 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR); +} + +/* +============= +Sbar_DrawNum +============= +*/ +void Sbar_DrawNum (int x, int y, int num, int digits, int color) +{ + char str[32], *ptr; + int l, frame; + + l = dpsnprintf(str, sizeof(str), "%i", num); + ptr = str; + if (l > digits) + ptr += (l-digits); + if (l < digits) + x += (digits-l)*24; + + while (*ptr) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + Sbar_DrawPic (x, y, sb_nums[color][frame]); + x += 24; + + ptr++; + } +} + +/* +============= +Sbar_DrawXNum +============= +*/ + +void Sbar_DrawXNum (int x, int y, int num, int digits, int lettersize, float r, float g, float b, float a, int flags) +{ + char str[32], *ptr; + int l, frame; + + if (digits < 0) + { + digits = -digits; + l = dpsnprintf(str, sizeof(str), "%0*i", digits, num); + } + else + l = dpsnprintf(str, sizeof(str), "%i", num); + ptr = str; + if (l > digits) + ptr += (l-digits); + if (l < digits) + x += (digits-l) * lettersize; + + while (*ptr) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + DrawQ_Pic (sbar_x + x, sbar_y + y, sb_nums[0][frame],lettersize,lettersize,r,g,b,a * sbar_alpha_fg.value,flags); + x += lettersize; + + ptr++; + } +} + +//============================================================================= + + +int Sbar_IsTeammatch(void) +{ + // currently only nexuiz uses the team score board + return ((gamemode == GAME_NEXUIZ) + && (teamplay.integer > 0)); +} + +/* +=============== +Sbar_SortFrags +=============== +*/ +static int fragsort[MAX_SCOREBOARD]; +static int scoreboardlines; + +int Sbar_GetSortedPlayerIndex (int index) +{ + return index >= 0 && index < scoreboardlines ? fragsort[index] : -1; +} + +static scoreboard_t teams[MAX_SCOREBOARD]; +static int teamsort[MAX_SCOREBOARD]; +static int teamlines; +void Sbar_SortFrags (void) +{ + int i, j, k, color; + + // sort by frags + scoreboardlines = 0; + for (i=0 ; i max) + str[max] = 0; + + // print the filename and message + Sbar_DrawString(8, 12, str); + + // print the time + Sbar_DrawString(8 + max*8, 12, timestr); + +#else + char str[80]; + int minutes, seconds, tens, units; + int l; + + if (gamemode != GAME_NEXUIZ) { + dpsnprintf (str, sizeof(str), "Monsters:%3i /%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]); + Sbar_DrawString (8, 4, str); + + dpsnprintf (str, sizeof(str), "Secrets :%3i /%3i", cl.stats[STAT_SECRETS], cl.stats[STAT_TOTALSECRETS]); + Sbar_DrawString (8, 12, str); + } + +// time + minutes = (int)(cl.time / 60); + seconds = (int)(cl.time - 60*minutes); + tens = seconds / 10; + units = seconds - 10*tens; + dpsnprintf (str, sizeof(str), "Time :%3i:%i%i", minutes, tens, units); + Sbar_DrawString (184, 4, str); + +// draw level name + if (gamemode == GAME_NEXUIZ) { + l = (int) strlen (cl.worldname); + Sbar_DrawString (232 - l*4, 12, cl.worldname); + } else { + l = (int) strlen (cl.worldmessage); + Sbar_DrawString (232 - l*4, 12, cl.worldmessage); + } +#endif +} + +/* +=============== +Sbar_DrawScoreboard +=============== +*/ +void Sbar_DrawScoreboard (void) +{ + Sbar_SoloScoreboard (); + // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode + //if (cl.gametype == GAME_DEATHMATCH) + if (!cl.islocalgame) + Sbar_DeathmatchOverlay (); +} + +//============================================================================= + +// AK to make DrawInventory smaller +static void Sbar_DrawWeapon(int nr, float fade, int active) +{ + if (sbar_hudselector.integer == 1) + { + // width = 300, height = 100 + const int w_width = 32, w_height = 12, w_space = 2, font_size = 8; + + DrawQ_Pic((vid_conwidth.integer - w_width * 9) * 0.5 + w_width * nr, vid_conheight.integer - w_height, sb_weapons[0][nr], w_width, w_height, (active) ? 1 : 0.6, active ? 1 : 0.6, active ? 1 : 0.6, (active ? 1 : 0.6) * fade * sbar_alpha_fg.value, DRAWFLAG_NORMAL); + // FIXME ?? + DrawQ_String((vid_conwidth.integer - w_width * 9) * 0.5 + w_width * nr + w_space, vid_conheight.integer - w_height + w_space, va("%i",nr+1), 0, font_size, font_size, 1, 1, 0, sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); + } + else + { + // width = 300, height = 100 + const int w_width = 300, w_height = 100, w_space = 10; + const float w_scale = 0.4; + + DrawQ_Pic(vid_conwidth.integer - (w_width + w_space) * w_scale, (w_height + w_space) * w_scale * nr + w_space, sb_weapons[0][nr], w_width * w_scale, w_height * w_scale, (active) ? 1 : 0.6, active ? 1 : 0.6, active ? 1 : 1, fade * sbar_alpha_fg.value, DRAWFLAG_NORMAL); + //DrawQ_String(vid_conwidth.integer - (w_space + font_size ), (w_height + w_space) * w_scale * nr + w_space, va("%i",nr+1), 0, font_size, font_size, 1, 0, 0, fade, 0, NULL, true, FONT_DEFAULT); + } +} + +/* +=============== +Sbar_DrawInventory +=============== +*/ +void Sbar_DrawInventory (void) +{ + int i; + char num[6]; + float time; + int flashon; + + if (gamemode == GAME_ROGUE) + { + if ( cl.stats[STAT_ACTIVEWEAPON] >= RIT_LAVA_NAILGUN ) + Sbar_DrawAlphaPic (0, -24, rsb_invbar[0], sbar_alpha_bg.value); + else + Sbar_DrawAlphaPic (0, -24, rsb_invbar[1], sbar_alpha_bg.value); + } + else + Sbar_DrawAlphaPic (0, -24, sb_ibar, sbar_alpha_bg.value); + + // weapons + for (i=0 ; i<7 ; i++) + { + if (cl.stats[STAT_ITEMS] & (IT_SHOTGUN<= 10) + { + if ( cl.stats[STAT_ACTIVEWEAPON] == (IT_SHOTGUN<= 10) + { + if ( cl.stats[STAT_ACTIVEWEAPON] == (1<= RIT_LAVA_NAILGUN ) + for (i=0;i<5;i++) + if (cl.stats[STAT_ACTIVEWEAPON] == (RIT_LAVA_NAILGUN << i)) + Sbar_DrawPic ((i+2)*24, -16, rsb_weapons[i]); + } + + // ammo counts + for (i=0 ; i<4 ; i++) + { + dpsnprintf (num, sizeof(num), "%4i",cl.stats[STAT_SHELLS+i] ); + if (num[0] != ' ') + Sbar_DrawCharacter ( (6*i+0)*8 - 2, -24, 18 + num[0] - '0'); + if (num[1] != ' ') + Sbar_DrawCharacter ( (6*i+1)*8 - 2, -24, 18 + num[1] - '0'); + if (num[2] != ' ') + Sbar_DrawCharacter ( (6*i+2)*8 - 2, -24, 18 + num[2] - '0'); + if (num[3] != ' ') + Sbar_DrawCharacter ( (6*i+3)*8 - 2, -24, 18 + num[3] - '0'); + } + + // items + for (i=0 ; i<6 ; i++) + if (cl.stats[STAT_ITEMS] & (1<<(17+i))) + { + //MED 01/04/97 changed keys + if (gamemode != GAME_HIPNOTIC || (i>1)) + Sbar_DrawPic (192 + i*16, -16, sb_items[i]); + } + + //MED 01/04/97 added hipnotic items + // hipnotic items + if (gamemode == GAME_HIPNOTIC) + { + for (i=0 ; i<2 ; i++) + if (cl.stats[STAT_ITEMS] & (1<<(24+i))) + Sbar_DrawPic (288 + i*16, -16, hsb_items[i]); + } + + if (gamemode == GAME_ROGUE) + { + // new rogue items + for (i=0 ; i<2 ; i++) + if (cl.stats[STAT_ITEMS] & (1<<(29+i))) + Sbar_DrawPic (288 + i*16, -16, rsb_items[i]); + } + else + { + // sigils + for (i=0 ; i<4 ; i++) + if (cl.stats[STAT_ITEMS] & (1<<(28+i))) + Sbar_DrawPic (320-32 + i*8, -16, sb_sigil[i]); + } +} + +//============================================================================= + +/* +=============== +Sbar_DrawFrags +=============== +*/ +void Sbar_DrawFrags (void) +{ + int i, k, l, x, f; + char num[12]; + scoreboard_t *s; + unsigned char *c; + + Sbar_SortFrags (); + + // draw the text + l = min(scoreboardlines, 4); + + x = 23 * 8; + + for (i = 0;i < l;i++) + { + k = fragsort[i]; + s = &cl.scores[k]; + + // draw background + c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; + DrawQ_Fill (sbar_x + x + 10, sbar_y - 23, 28, 4, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + c = palette_rgb_shirtscoreboard[s->colors & 0xf]; + DrawQ_Fill (sbar_x + x + 10, sbar_y + 4 - 23, 28, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + + // draw number + f = s->frags; + dpsnprintf (num, sizeof(num), "%3i",f); + + if (k == cl.viewentity - 1) + { + Sbar_DrawCharacter ( x + 2, -24, 16); + Sbar_DrawCharacter ( x + 32 - 4, -24, 17); + } + Sbar_DrawCharacter (x + 8, -24, num[0]); + Sbar_DrawCharacter (x + 16, -24, num[1]); + Sbar_DrawCharacter (x + 24, -24, num[2]); + x += 32; + } +} + +//============================================================================= + + +/* +=============== +Sbar_DrawFace +=============== +*/ +void Sbar_DrawFace (void) +{ + int f; + +// PGM 01/19/97 - team color drawing +// PGM 03/02/97 - fixed so color swatch only appears in CTF modes + if (gamemode == GAME_ROGUE && !cl.islocalgame && (teamplay.integer > 3) && (teamplay.integer < 7)) + { + char num[12]; + scoreboard_t *s; + unsigned char *c; + + s = &cl.scores[cl.viewentity - 1]; + // draw background + Sbar_DrawPic (112, 0, rsb_teambord); + c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; + DrawQ_Fill (sbar_x + 113, vid_conheight.integer-SBAR_HEIGHT+3, 22, 9, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + c = palette_rgb_shirtscoreboard[s->colors & 0xf]; + DrawQ_Fill (sbar_x + 113, vid_conheight.integer-SBAR_HEIGHT+12, 22, 9, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + + // draw number + f = s->frags; + dpsnprintf (num, sizeof(num), "%3i",f); + + if ((s->colors & 0xf0)==0) + { + if (num[0] != ' ') + Sbar_DrawCharacter(109, 3, 18 + num[0] - '0'); + if (num[1] != ' ') + Sbar_DrawCharacter(116, 3, 18 + num[1] - '0'); + if (num[2] != ' ') + Sbar_DrawCharacter(123, 3, 18 + num[2] - '0'); + } + else + { + Sbar_DrawCharacter ( 109, 3, num[0]); + Sbar_DrawCharacter ( 116, 3, num[1]); + Sbar_DrawCharacter ( 123, 3, num[2]); + } + + return; + } +// PGM 01/19/97 - team color drawing + + if ( (cl.stats[STAT_ITEMS] & (IT_INVISIBILITY | IT_INVULNERABILITY) ) == (IT_INVISIBILITY | IT_INVULNERABILITY) ) + Sbar_DrawPic (112, 0, sb_face_invis_invuln); + else if (cl.stats[STAT_ITEMS] & IT_QUAD) + Sbar_DrawPic (112, 0, sb_face_quad ); + else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) + Sbar_DrawPic (112, 0, sb_face_invis ); + else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) + Sbar_DrawPic (112, 0, sb_face_invuln); + else + { + f = cl.stats[STAT_HEALTH] / 20; + f = bound(0, f, 4); + Sbar_DrawPic (112, 0, sb_faces[f][cl.time <= cl.faceanimtime]); + } +} +double topspeed = 0; +double topspeedxy = 0; +time_t current_time = 3; +time_t top_time = 0; +time_t topxy_time = 0; + +static void get_showspeed_unit(int unitnumber, double *conversion_factor, const char **unit) +{ + if(unitnumber < 0) + unitnumber = showspeed.integer; + switch(unitnumber) + { + default: + case 1: + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + *unit = "in/s"; + else + *unit = "qu/s"; + *conversion_factor = 1.0; + break; + case 2: + *unit = "m/s"; + *conversion_factor = 0.0254; + if(gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC) *conversion_factor *= 1.5; + // 1qu=1.5in is for non-Nexuiz/Xonotic only - Nexuiz/Xonotic players are overly large, but 1qu=1in fixes that + break; + case 3: + *unit = "km/h"; + *conversion_factor = 0.0254 * 3.6; + if(gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC) *conversion_factor *= 1.5; + break; + case 4: + *unit = "mph"; + *conversion_factor = 0.0254 * 3.6 * 0.6213711922; + if(gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC) *conversion_factor *= 1.5; + break; + case 5: + *unit = "knots"; + *conversion_factor = 0.0254 * 1.943844492; // 1 m/s = 1.943844492 knots, because 1 knot = 1.852 km/h + if(gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC) *conversion_factor *= 1.5; + break; + } +} + +static double showfps_nexttime = 0, showfps_lasttime = -1; +static double showfps_framerate = 0; +static int showfps_framecount = 0; + +void Sbar_ShowFPS_Update(void) +{ + double interval = 1; + double newtime; + newtime = realtime; + if (newtime >= showfps_nexttime) + { + showfps_framerate = showfps_framecount / (newtime - showfps_lasttime); + if (showfps_nexttime < newtime - interval * 1.5) + showfps_nexttime = newtime; + showfps_lasttime = newtime; + showfps_nexttime += interval; + showfps_framecount = 0; + } + showfps_framecount++; +} + +void Sbar_ShowFPS(void) +{ + float fps_x, fps_y, fps_scalex, fps_scaley, fps_strings = 0; + char soundstring[32]; + char fpsstring[32]; + char timestring[32]; + char datestring[32]; + char timedemostring1[32]; + char timedemostring2[32]; + char speedstring[32]; + char blurstring[32]; + char topspeedstring[48]; + qboolean red = false; + soundstring[0] = 0; + fpsstring[0] = 0; + timedemostring1[0] = 0; + timedemostring2[0] = 0; + timestring[0] = 0; + datestring[0] = 0; + speedstring[0] = 0; + blurstring[0] = 0; + topspeedstring[0] = 0; + if (showfps.integer) + { + red = (showfps_framerate < 1.0f); + if(showfps.integer == 2) + dpsnprintf(fpsstring, sizeof(fpsstring), "%7.3f mspf", (1000.0 / showfps_framerate)); + else if (red) + dpsnprintf(fpsstring, sizeof(fpsstring), "%4i spf", (int)(1.0 / showfps_framerate + 0.5)); + else + dpsnprintf(fpsstring, sizeof(fpsstring), "%4i fps", (int)(showfps_framerate + 0.5)); + fps_strings++; + if (cls.timedemo) + { + dpsnprintf(timedemostring1, sizeof(timedemostring1), "frame%4i %f", cls.td_frames, realtime - cls.td_starttime); + dpsnprintf(timedemostring2, sizeof(timedemostring2), "%i seconds %3.0f/%3.0f/%3.0f fps", cls.td_onesecondavgcount, cls.td_onesecondminfps, cls.td_onesecondavgfps / max(1, cls.td_onesecondavgcount), cls.td_onesecondmaxfps); + fps_strings++; + fps_strings++; + } + } + if (showtime.integer) + { + strlcpy(timestring, Sys_TimeString(showtime_format.string), sizeof(timestring)); + fps_strings++; + } + if (showdate.integer) + { + strlcpy(datestring, Sys_TimeString(showdate_format.string), sizeof(datestring)); + fps_strings++; + } + if (showblur.integer) + { + dpsnprintf(blurstring, sizeof(blurstring), "%3i%% blur", (int)(cl.motionbluralpha * 100)); + fps_strings++; + } + if (showsound.integer) + { + dpsnprintf(soundstring, sizeof(soundstring), "%4i/4%i at %3ims", cls.soundstats.mixedsounds, cls.soundstats.totalsounds, cls.soundstats.latency_milliseconds); + fps_strings++; + } + if (showspeed.integer || showtopspeed.integer) + { + double speed, speedxy, f; + const char *unit; + speed = VectorLength(cl.movement_velocity); + speedxy = sqrt(cl.movement_velocity[0] * cl.movement_velocity[0] + cl.movement_velocity[1] * cl.movement_velocity[1]); + if (showspeed.integer) + { + get_showspeed_unit(showspeed.integer, &f, &unit); + dpsnprintf(speedstring, sizeof(speedstring), "%.0f (%.0f) %s", f*speed, f*speedxy, unit); + fps_strings++; + } + if (showtopspeed.integer) + { + qboolean topspeed_latched = false, topspeedxy_latched = false; + get_showspeed_unit(showtopspeed.integer, &f, &unit); + if (speed >= topspeed || current_time - top_time > 3) + { + topspeed = speed; + time(&top_time); + } + else + topspeed_latched = true; + if (speedxy >= topspeedxy || current_time - topxy_time > 3) + { + topspeedxy = speedxy; + time(&topxy_time); + } + else + topspeedxy_latched = true; + dpsnprintf(topspeedstring, sizeof(topspeedstring), "%s%.0f%s (%s%.0f%s) %s", + topspeed_latched ? "^1" : "^xf88", f*topspeed, "^xf88", + topspeedxy_latched ? "^1" : "^xf88", f*topspeedxy, "^xf88", + unit); + time(¤t_time); + fps_strings++; + } + } + if (fps_strings) + { + fps_scalex = 12; + fps_scaley = 12; + //fps_y = vid_conheight.integer - sb_lines; // yes this may draw over the sbar + //fps_y = bound(0, fps_y, vid_conheight.integer - fps_strings*fps_scaley); + fps_y = vid_conheight.integer - sbar_info_pos.integer - fps_strings*fps_scaley; + if (soundstring[0]) + { + fps_x = vid_conwidth.integer - DrawQ_TextWidth(soundstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String(fps_x, fps_y, soundstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (fpsstring[0]) + { + r_draw2d_force = true; + fps_x = vid_conwidth.integer - DrawQ_TextWidth(fpsstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + if (red) + DrawQ_String(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 0, 0, 1, 0, NULL, true, FONT_INFOBAR); + else + DrawQ_String(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + r_draw2d_force = false; + } + if (timedemostring1[0]) + { + fps_x = vid_conwidth.integer - DrawQ_TextWidth(timedemostring1, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String(fps_x, fps_y, timedemostring1, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (timedemostring2[0]) + { + fps_x = vid_conwidth.integer - DrawQ_TextWidth(timedemostring2, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String(fps_x, fps_y, timedemostring2, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (timestring[0]) + { + fps_x = vid_conwidth.integer - DrawQ_TextWidth(timestring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String(fps_x, fps_y, timestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (datestring[0]) + { + fps_x = vid_conwidth.integer - DrawQ_TextWidth(datestring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String(fps_x, fps_y, datestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (speedstring[0]) + { + fps_x = vid_conwidth.integer - DrawQ_TextWidth(speedstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String(fps_x, fps_y, speedstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (topspeedstring[0]) + { + fps_x = vid_conwidth.integer - DrawQ_TextWidth(topspeedstring, 0, fps_scalex, fps_scaley, false, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String(fps_x, fps_y, topspeedstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, false, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (blurstring[0]) + { + fps_x = vid_conwidth.integer - DrawQ_TextWidth(blurstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); + DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); + DrawQ_String(fps_x, fps_y, blurstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + } +} + +void Sbar_DrawGauge(float x, float y, cachepic_t *pic, float width, float height, float rangey, float rangeheight, float c1, float c2, float c1r, float c1g, float c1b, float c1a, float c2r, float c2g, float c2b, float c2a, float c3r, float c3g, float c3b, float c3a, int drawflags) +{ + float r[5]; + c2 = bound(0, c2, 1); + c1 = bound(0, c1, 1 - c2); + r[0] = 0; + r[1] = rangey + rangeheight * (c2 + c1); + r[2] = rangey + rangeheight * (c2); + r[3] = rangey; + r[4] = height; + if (r[1] > r[0]) + DrawQ_SuperPic(x, y + r[0], pic, width, (r[1] - r[0]), 0,(r[0] / height), c3r,c3g,c3b,c3a, 1,(r[0] / height), c3r,c3g,c3b,c3a, 0,(r[1] / height), c3r,c3g,c3b,c3a, 1,(r[1] / height), c3r,c3g,c3b,c3a, drawflags); + if (r[2] > r[1]) + DrawQ_SuperPic(x, y + r[1], pic, width, (r[2] - r[1]), 0,(r[1] / height), c1r,c1g,c1b,c1a, 1,(r[1] / height), c1r,c1g,c1b,c1a, 0,(r[2] / height), c1r,c1g,c1b,c1a, 1,(r[2] / height), c1r,c1g,c1b,c1a, drawflags); + if (r[3] > r[2]) + DrawQ_SuperPic(x, y + r[2], pic, width, (r[3] - r[2]), 0,(r[2] / height), c2r,c2g,c2b,c2a, 1,(r[2] / height), c2r,c2g,c2b,c2a, 0,(r[3] / height), c2r,c2g,c2b,c2a, 1,(r[3] / height), c2r,c2g,c2b,c2a, drawflags); + if (r[4] > r[3]) + DrawQ_SuperPic(x, y + r[3], pic, width, (r[4] - r[3]), 0,(r[3] / height), c3r,c3g,c3b,c3a, 1,(r[3] / height), c3r,c3g,c3b,c3a, 0,(r[4] / height), c3r,c3g,c3b,c3a, 1,(r[4] / height), c3r,c3g,c3b,c3a, drawflags); +} + +/* +=============== +Sbar_Draw +=============== +*/ +extern float v_dmg_time, v_dmg_roll, v_dmg_pitch; +extern cvar_t v_kicktime; +void Sbar_Score (int margin); +void Sbar_Draw (void) +{ + cachepic_t *pic; + + if(cl.csqc_vidvars.drawenginesbar) //[515]: csqc drawsbar + { + if (sb_showscores) + Sbar_DrawScoreboard (); + else if (cl.intermission == 1) + { + if(gamemode == GAME_NEXUIZ) // display full scoreboard (that is, show scores + map name) + { + Sbar_DrawScoreboard(); + return; + } + Sbar_IntermissionOverlay(); + } + else if (cl.intermission == 2) + Sbar_FinaleOverlay(); + else if (gamemode == GAME_DELUXEQUAKE) + { + } + else if (gamemode == GAME_SOM) + { + if (sb_showscores || (cl.stats[STAT_HEALTH] <= 0 && cl_deathscoreboard.integer)) + Sbar_DrawScoreboard (); + else if (sb_lines) + { + // this is the top left of the sbar area + sbar_x = 0; + sbar_y = vid_conheight.integer - 24*3; + + // armor + if (cl.stats[STAT_ARMOR]) + { + if (cl.stats[STAT_ITEMS] & IT_ARMOR3) + Sbar_DrawPic(0, 0, somsb_armor[2]); + else if (cl.stats[STAT_ITEMS] & IT_ARMOR2) + Sbar_DrawPic(0, 0, somsb_armor[1]); + else if (cl.stats[STAT_ITEMS] & IT_ARMOR1) + Sbar_DrawPic(0, 0, somsb_armor[0]); + Sbar_DrawNum(24, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25); + } + + // health + Sbar_DrawPic(0, 24, somsb_health); + Sbar_DrawNum(24, 24, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25); + + // ammo icon + if (cl.stats[STAT_ITEMS] & IT_SHELLS) + Sbar_DrawPic(0, 48, somsb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & IT_NAILS) + Sbar_DrawPic(0, 48, somsb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & IT_ROCKETS) + Sbar_DrawPic(0, 48, somsb_ammo[2]); + else if (cl.stats[STAT_ITEMS] & IT_CELLS) + Sbar_DrawPic(0, 48, somsb_ammo[3]); + Sbar_DrawNum(24, 48, cl.stats[STAT_AMMO], 3, false); + if (cl.stats[STAT_SHELLS]) + Sbar_DrawNum(24 + 3*24, 48, cl.stats[STAT_SHELLS], 1, true); + } + } + else if (gamemode == GAME_NEXUIZ) + { + if (sb_showscores || (cl.stats[STAT_HEALTH] <= 0 && cl_deathscoreboard.integer)) + { + sbar_x = (vid_conwidth.integer - 640)/2; + sbar_y = vid_conheight.integer - 47; + Sbar_DrawAlphaPic (0, 0, sb_scorebar, sbar_alpha_bg.value); + Sbar_DrawScoreboard (); + } + else if (sb_lines && sbar_hudselector.integer == 1) + { + int i; + float fade; + int redflag, blueflag; + float x; + + sbar_x = (vid_conwidth.integer - 320)/2; + sbar_y = vid_conheight.integer - 24 - 16; + + // calculate intensity to draw weapons bar at + fade = 3.2 - 2 * (cl.time - cl.weapontime); + fade = bound(0.7, fade, 1); + for (i = 0; i < 8;i++) + if (cl.stats[STAT_ITEMS] & (1 << i)) + Sbar_DrawWeapon(i + 1, fade, (i + 2 == cl.stats[STAT_ACTIVEWEAPON])); + if((cl.stats[STAT_ITEMS] & (1<<12))) + Sbar_DrawWeapon(0, fade, (cl.stats[STAT_ACTIVEWEAPON] == 1)); + + // flag icons + redflag = ((cl.stats[STAT_ITEMS]>>15) & 3); + blueflag = ((cl.stats[STAT_ITEMS]>>17) & 3); + x = sbar_flagstatus_right.integer ? vid_conwidth.integer - 10 - sbar_x - 64 : 10 - sbar_x; + if (redflag == 3 && blueflag == 3) + { + // The Impossible Combination[tm] + // Can only happen in Key Hunt mode... + Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 128)), sb_items[14]); + } + else + { + if (redflag) + Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 64)), sb_items[redflag+10]); + if (blueflag) + Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 128)), sb_items[blueflag+14]); + } + + // armor + if (cl.stats[STAT_ARMOR] > 0) + { + Sbar_DrawStretchPic (72, 0, sb_armor[0], sbar_alpha_fg.value, 24, 24); + if(cl.stats[STAT_ARMOR] > 200) + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0,1,0,1,0); + else if(cl.stats[STAT_ARMOR] > 100) + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.2,1,0.2,1,0); + else if(cl.stats[STAT_ARMOR] > 50) + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.6,0.7,0.8,1,0); + else if(cl.stats[STAT_ARMOR] > 25) + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,1,1,0.2,1,0); + else + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.7,0,0,1,0); + } + + // health + if (cl.stats[STAT_HEALTH] != 0) + { + Sbar_DrawStretchPic (184, 0, sb_health, sbar_alpha_fg.value, 24, 24); + if(cl.stats[STAT_HEALTH] > 200) + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0,1,0,1,0); + else if(cl.stats[STAT_HEALTH] > 100) + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.2,1,0.2,1,0); + else if(cl.stats[STAT_HEALTH] > 50) + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.6,0.7,0.8,1,0); + else if(cl.stats[STAT_HEALTH] > 25) + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,1,1,0.2,1,0); + else + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.7,0,0,1,0); + } + + // ammo + if ((cl.stats[STAT_ITEMS] & (NEX_IT_SHELLS | NEX_IT_BULLETS | NEX_IT_ROCKETS | NEX_IT_CELLS)) || cl.stats[STAT_AMMO] != 0) + { + if (cl.stats[STAT_ITEMS] & NEX_IT_SHELLS) + Sbar_DrawStretchPic (296, 0, sb_ammo[0], sbar_alpha_fg.value, 24, 24); + else if (cl.stats[STAT_ITEMS] & NEX_IT_BULLETS) + Sbar_DrawStretchPic (296, 0, sb_ammo[1], sbar_alpha_fg.value, 24, 24); + else if (cl.stats[STAT_ITEMS] & NEX_IT_ROCKETS) + Sbar_DrawStretchPic (296, 0, sb_ammo[2], sbar_alpha_fg.value, 24, 24); + else if (cl.stats[STAT_ITEMS] & NEX_IT_CELLS) + Sbar_DrawStretchPic (296, 0, sb_ammo[3], sbar_alpha_fg.value, 24, 24); + if(cl.stats[STAT_AMMO] > 10) + Sbar_DrawXNum(224, 0, cl.stats[STAT_AMMO], 3, 24, 0.6,0.7,0.8,1,0); + else + Sbar_DrawXNum(224, 0, cl.stats[STAT_AMMO], 3, 24, 0.7,0,0,1,0); + } + + if (sbar_x + 320 + 160 <= vid_conwidth.integer) + Sbar_MiniDeathmatchOverlay (sbar_x + 320, sbar_y); + if (sbar_x > 0) + Sbar_Score(16); + // The margin can be at most 8 to support 640x480 console size: + // 320 + 2 * (144 + 16) = 640 + } + else if (sb_lines) + { + int i; + float fade; + int redflag, blueflag; + float x; + + sbar_x = (vid_conwidth.integer - 640)/2; + sbar_y = vid_conheight.integer - 47; + + // calculate intensity to draw weapons bar at + fade = 3 - 2 * (cl.time - cl.weapontime); + if (fade > 0) + { + fade = min(fade, 1); + for (i = 0; i < 8;i++) + if (cl.stats[STAT_ITEMS] & (1 << i)) + Sbar_DrawWeapon(i + 1, fade, (i + 2 == cl.stats[STAT_ACTIVEWEAPON])); + + if((cl.stats[STAT_ITEMS] & (1<<12))) + Sbar_DrawWeapon(0, fade, (cl.stats[STAT_ACTIVEWEAPON] == 1)); + } + + //if (!cl.islocalgame) + // Sbar_DrawFrags (); + + if (sb_lines > 24) + Sbar_DrawAlphaPic (0, 0, sb_sbar, sbar_alpha_fg.value); + else + Sbar_DrawAlphaPic (0, 0, sb_sbar_minimal, sbar_alpha_fg.value); + + // flag icons + redflag = ((cl.stats[STAT_ITEMS]>>15) & 3); + blueflag = ((cl.stats[STAT_ITEMS]>>17) & 3); + x = sbar_flagstatus_right.integer ? vid_conwidth.integer - 10 - sbar_x - 64 : 10 - sbar_x; + if (redflag == 3 && blueflag == 3) + { + // The Impossible Combination[tm] + // Can only happen in Key Hunt mode... + Sbar_DrawPic ((int) x, -179, sb_items[14]); + } + else + { + if (redflag) + Sbar_DrawPic ((int) x, -117, sb_items[redflag+10]); + if (blueflag) + Sbar_DrawPic ((int) x, -177, sb_items[blueflag+14]); + } + + // armor + Sbar_DrawXNum ((340-3*24), 12, cl.stats[STAT_ARMOR], 3, 24, 0.6,0.7,0.8,1,0); + + // health + if(cl.stats[STAT_HEALTH] > 100) + Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,1,1,1,1,0); + else if(cl.stats[STAT_HEALTH] <= 25 && cl.time - (int)cl.time > 0.5) + Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,0.7,0,0,1,0); + else + Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,0.6,0.7,0.8,1,0); + + // AK dont draw ammo for the laser + if(cl.stats[STAT_ACTIVEWEAPON] != 12) + { + if (cl.stats[STAT_ITEMS] & NEX_IT_SHELLS) + Sbar_DrawPic (519, 0, sb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & NEX_IT_BULLETS) + Sbar_DrawPic (519, 0, sb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & NEX_IT_ROCKETS) + Sbar_DrawPic (519, 0, sb_ammo[2]); + else if (cl.stats[STAT_ITEMS] & NEX_IT_CELLS) + Sbar_DrawPic (519, 0, sb_ammo[3]); + + if(cl.stats[STAT_AMMO] <= 10) + Sbar_DrawXNum ((519-3*24), 12, cl.stats[STAT_AMMO], 3, 24, 0.7, 0,0,1,0); + else + Sbar_DrawXNum ((519-3*24), 12, cl.stats[STAT_AMMO], 3, 24, 0.6, 0.7,0.8,1,0); + + } + + if (sb_lines > 24) + DrawQ_Pic(sbar_x,sbar_y,sb_sbar_overlay,0,0,1,1,1,1,DRAWFLAG_MODULATE); + + if (sbar_x + 600 + 160 <= vid_conwidth.integer) + Sbar_MiniDeathmatchOverlay (sbar_x + 600, sbar_y); + + if (sbar_x > 0) + Sbar_Score(-16); + // Because: + // Mini scoreboard uses 12*4 per other team, that is, 144 + // pixels when there are four teams... + // Nexuiz by default sets vid_conwidth to 800... makes + // sbar_x == 80... + // so we need to shift it by 64 pixels to the right to fit + // BUT: then it overlaps with the image that gets drawn + // for viewsize 100! Therefore, just account for 3 teams, + // that is, 96 pixels mini scoreboard size, needing 16 pixels + // to the right! + } + } + else if (gamemode == GAME_ZYMOTIC) + { +#if 1 + float scale = 64.0f / 256.0f; + float kickoffset[3]; + VectorClear(kickoffset); + if (v_dmg_time > 0) + { + kickoffset[0] = (v_dmg_time/v_kicktime.value*v_dmg_roll) * 10 * scale; + kickoffset[1] = (v_dmg_time/v_kicktime.value*v_dmg_pitch) * 10 * scale; + } + sbar_x = (int)((vid_conwidth.integer - 256 * scale)/2 + kickoffset[0]); + sbar_y = (int)((vid_conheight.integer - 256 * scale)/2 + kickoffset[1]); + // left1 16, 48 : 126 -66 + // left2 16, 128 : 196 -66 + // right 176, 48 : 196 -136 + Sbar_DrawGauge(sbar_x + 16 * scale, sbar_y + 48 * scale, zymsb_crosshair_left1, 64*scale, 80*scale, 78*scale, -66*scale, cl.stats[STAT_AMMO] * (1.0 / 200.0), cl.stats[STAT_SHELLS] * (1.0 / 200.0), 0.8f,0.8f,0.0f,1.0f, 0.8f,0.5f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); + Sbar_DrawGauge(sbar_x + 16 * scale, sbar_y + 128 * scale, zymsb_crosshair_left2, 64*scale, 80*scale, 68*scale, -66*scale, cl.stats[STAT_NAILS] * (1.0 / 200.0), cl.stats[STAT_ROCKETS] * (1.0 / 200.0), 0.8f,0.8f,0.0f,1.0f, 0.8f,0.5f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); + Sbar_DrawGauge(sbar_x + 176 * scale, sbar_y + 48 * scale, zymsb_crosshair_right, 64*scale, 160*scale, 148*scale, -136*scale, cl.stats[STAT_ARMOR] * (1.0 / 300.0), cl.stats[STAT_HEALTH] * (1.0 / 300.0), 0.0f,0.5f,1.0f,1.0f, 1.0f,0.0f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); + DrawQ_Pic(sbar_x + 120 * scale, sbar_y + 120 * scale, zymsb_crosshair_center, 16 * scale, 16 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); +#else + float scale = 128.0f / 256.0f; + float healthstart, healthheight, healthstarttc, healthendtc; + float shieldstart, shieldheight, shieldstarttc, shieldendtc; + float ammostart, ammoheight, ammostarttc, ammoendtc; + float clipstart, clipheight, clipstarttc, clipendtc; + float kickoffset[3], offset; + VectorClear(kickoffset); + if (v_dmg_time > 0) + { + kickoffset[0] = (v_dmg_time/v_kicktime.value*v_dmg_roll) * 10 * scale; + kickoffset[1] = (v_dmg_time/v_kicktime.value*v_dmg_pitch) * 10 * scale; + } + sbar_x = (vid_conwidth.integer - 256 * scale)/2 + kickoffset[0]; + sbar_y = (vid_conheight.integer - 256 * scale)/2 + kickoffset[1]; + offset = 0; // TODO: offset should be controlled by recoil (question: how to detect firing?) + DrawQ_SuperPic(sbar_x + 120 * scale, sbar_y + ( 88 - offset) * scale, zymsb_crosshair_line, 16 * scale, 36 * scale, 0,0, 1,1,1,1, 1,0, 1,1,1,1, 0,1, 1,1,1,1, 1,1, 1,1,1,1, 0); + DrawQ_SuperPic(sbar_x + (132 + offset) * scale, sbar_y + 120 * scale, zymsb_crosshair_line, 36 * scale, 16 * scale, 0,1, 1,1,1,1, 0,0, 1,1,1,1, 1,1, 1,1,1,1, 1,0, 1,1,1,1, 0); + DrawQ_SuperPic(sbar_x + 120 * scale, sbar_y + (132 + offset) * scale, zymsb_crosshair_line, 16 * scale, 36 * scale, 1,1, 1,1,1,1, 0,1, 1,1,1,1, 1,0, 1,1,1,1, 0,0, 1,1,1,1, 0); + DrawQ_SuperPic(sbar_x + ( 88 - offset) * scale, sbar_y + 120 * scale, zymsb_crosshair_line, 36 * scale, 16 * scale, 1,0, 1,1,1,1, 1,1, 1,1,1,1, 0,0, 1,1,1,1, 0,1, 1,1,1,1, 0); + healthheight = cl.stats[STAT_HEALTH] * (152.0f / 300.0f); + shieldheight = cl.stats[STAT_ARMOR] * (152.0f / 300.0f); + healthstart = 204 - healthheight; + shieldstart = healthstart - shieldheight; + healthstarttc = healthstart * (1.0f / 256.0f); + healthendtc = (healthstart + healthheight) * (1.0f / 256.0f); + shieldstarttc = shieldstart * (1.0f / 256.0f); + shieldendtc = (shieldstart + shieldheight) * (1.0f / 256.0f); + ammoheight = cl.stats[STAT_SHELLS] * (62.0f / 200.0f); + ammostart = 114 - ammoheight; + ammostarttc = ammostart * (1.0f / 256.0f); + ammoendtc = (ammostart + ammoheight) * (1.0f / 256.0f); + clipheight = cl.stats[STAT_AMMO] * (122.0f / 200.0f); + clipstart = 190 - clipheight; + clipstarttc = clipstart * (1.0f / 256.0f); + clipendtc = (clipstart + clipheight) * (1.0f / 256.0f); + if (healthheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + healthstart * scale, zymsb_crosshair_health, 256 * scale, healthheight * scale, 0,healthstarttc, 1.0f,0.0f,0.0f,1.0f, 1,healthstarttc, 1.0f,0.0f,0.0f,1.0f, 0,healthendtc, 1.0f,0.0f,0.0f,1.0f, 1,healthendtc, 1.0f,0.0f,0.0f,1.0f, DRAWFLAG_NORMAL); + if (shieldheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + shieldstart * scale, zymsb_crosshair_health, 256 * scale, shieldheight * scale, 0,shieldstarttc, 0.0f,0.5f,1.0f,1.0f, 1,shieldstarttc, 0.0f,0.5f,1.0f,1.0f, 0,shieldendtc, 0.0f,0.5f,1.0f,1.0f, 1,shieldendtc, 0.0f,0.5f,1.0f,1.0f, DRAWFLAG_NORMAL); + if (ammoheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + ammostart * scale, zymsb_crosshair_ammo, 256 * scale, ammoheight * scale, 0,ammostarttc, 0.8f,0.8f,0.0f,1.0f, 1,ammostarttc, 0.8f,0.8f,0.0f,1.0f, 0,ammoendtc, 0.8f,0.8f,0.0f,1.0f, 1,ammoendtc, 0.8f,0.8f,0.0f,1.0f, DRAWFLAG_NORMAL); + if (clipheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + clipstart * scale, zymsb_crosshair_clip, 256 * scale, clipheight * scale, 0,clipstarttc, 1.0f,1.0f,0.0f,1.0f, 1,clipstarttc, 1.0f,1.0f,0.0f,1.0f, 0,clipendtc, 1.0f,1.0f,0.0f,1.0f, 1,clipendtc, 1.0f,1.0f,0.0f,1.0f, DRAWFLAG_NORMAL); + DrawQ_Pic(sbar_x + 0 * scale, sbar_y + 0 * scale, zymsb_crosshair_background, 256 * scale, 256 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); + DrawQ_Pic(sbar_x + 120 * scale, sbar_y + 120 * scale, zymsb_crosshair_center, 16 * scale, 16 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); +#endif + } + else // Quake and others + { + sbar_x = (vid_conwidth.integer - 320)/2; + sbar_y = vid_conheight.integer - SBAR_HEIGHT; + // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode + //if (cl.gametype == GAME_DEATHMATCH && gamemode != GAME_TRANSFUSION) + + if (sb_lines > 24) + { + if (gamemode != GAME_GOODVSBAD2) + Sbar_DrawInventory (); + if (!cl.islocalgame && gamemode != GAME_TRANSFUSION) + Sbar_DrawFrags (); + } + + if (sb_showscores || (cl.stats[STAT_HEALTH] <= 0 && cl_deathscoreboard.integer)) + { + if (gamemode != GAME_GOODVSBAD2) + Sbar_DrawAlphaPic (0, 0, sb_scorebar, sbar_alpha_bg.value); + Sbar_DrawScoreboard (); + } + else if (sb_lines) + { + Sbar_DrawAlphaPic (0, 0, sb_sbar, sbar_alpha_bg.value); + + // keys (hipnotic only) + //MED 01/04/97 moved keys here so they would not be overwritten + if (gamemode == GAME_HIPNOTIC) + { + if (cl.stats[STAT_ITEMS] & IT_KEY1) + Sbar_DrawPic (209, 3, sb_items[0]); + if (cl.stats[STAT_ITEMS] & IT_KEY2) + Sbar_DrawPic (209, 12, sb_items[1]); + } + // armor + if (gamemode != GAME_GOODVSBAD2) + { + if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) + { + Sbar_DrawNum (24, 0, 666, 3, 1); + Sbar_DrawPic (0, 0, sb_disc); + } + else + { + if (gamemode == GAME_ROGUE) + { + Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25); + if (cl.stats[STAT_ITEMS] & RIT_ARMOR3) + Sbar_DrawPic (0, 0, sb_armor[2]); + else if (cl.stats[STAT_ITEMS] & RIT_ARMOR2) + Sbar_DrawPic (0, 0, sb_armor[1]); + else if (cl.stats[STAT_ITEMS] & RIT_ARMOR1) + Sbar_DrawPic (0, 0, sb_armor[0]); + } + else + { + Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25); + if (cl.stats[STAT_ITEMS] & IT_ARMOR3) + Sbar_DrawPic (0, 0, sb_armor[2]); + else if (cl.stats[STAT_ITEMS] & IT_ARMOR2) + Sbar_DrawPic (0, 0, sb_armor[1]); + else if (cl.stats[STAT_ITEMS] & IT_ARMOR1) + Sbar_DrawPic (0, 0, sb_armor[0]); + } + } + } + + // face + Sbar_DrawFace (); + + // health + Sbar_DrawNum (136, 0, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25); + + // ammo icon + if (gamemode == GAME_ROGUE) + { + if (cl.stats[STAT_ITEMS] & RIT_SHELLS) + Sbar_DrawPic (224, 0, sb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & RIT_NAILS) + Sbar_DrawPic (224, 0, sb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & RIT_ROCKETS) + Sbar_DrawPic (224, 0, sb_ammo[2]); + else if (cl.stats[STAT_ITEMS] & RIT_CELLS) + Sbar_DrawPic (224, 0, sb_ammo[3]); + else if (cl.stats[STAT_ITEMS] & RIT_LAVA_NAILS) + Sbar_DrawPic (224, 0, rsb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & RIT_PLASMA_AMMO) + Sbar_DrawPic (224, 0, rsb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & RIT_MULTI_ROCKETS) + Sbar_DrawPic (224, 0, rsb_ammo[2]); + } + else + { + if (cl.stats[STAT_ITEMS] & IT_SHELLS) + Sbar_DrawPic (224, 0, sb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & IT_NAILS) + Sbar_DrawPic (224, 0, sb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & IT_ROCKETS) + Sbar_DrawPic (224, 0, sb_ammo[2]); + else if (cl.stats[STAT_ITEMS] & IT_CELLS) + Sbar_DrawPic (224, 0, sb_ammo[3]); + } + + Sbar_DrawNum (248, 0, cl.stats[STAT_AMMO], 3, cl.stats[STAT_AMMO] <= 10); + + // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode + if ((!cl.islocalgame || cl.gametype != GAME_COOP)) + { + if (gamemode == GAME_TRANSFUSION) + Sbar_MiniDeathmatchOverlay (0, 0); + else + Sbar_MiniDeathmatchOverlay (sbar_x + 324, vid_conheight.integer - 8*8); + Sbar_Score(24); + } + } + } + } + + if (cl.csqc_vidvars.drawcrosshair && crosshair.integer >= 1 && !cl.intermission && !r_letterbox.value) + { + pic = Draw_CachePic (va("gfx/crosshair%i", crosshair.integer)); + DrawQ_Pic((vid_conwidth.integer - pic->width * crosshair_size.value) * 0.5f, (vid_conheight.integer - pic->height * crosshair_size.value) * 0.5f, pic, pic->width * crosshair_size.value, pic->height * crosshair_size.value, crosshair_color_red.value, crosshair_color_green.value, crosshair_color_blue.value, crosshair_color_alpha.value, 0); + } + + if (cl_prydoncursor.integer > 0) + DrawQ_Pic((cl.cmd.cursor_screen[0] + 1) * 0.5 * vid_conwidth.integer, (cl.cmd.cursor_screen[1] + 1) * 0.5 * vid_conheight.integer, Draw_CachePic (va("gfx/prydoncursor%03i", cl_prydoncursor.integer)), 0, 0, 1, 1, 1, 1, 0); +} + +//============================================================================= + +/* +================== +Sbar_DeathmatchOverlay + +================== +*/ +float Sbar_PrintScoreboardItem(scoreboard_t *s, float x, float y) +{ + int minutes; + qboolean myself = false; + unsigned char *c; + minutes = (int)((cl.intermission ? cl.completed_time - s->qw_entertime : cl.time - s->qw_entertime) / 60.0); + + if((s - cl.scores) == cl.playerentity - 1) + myself = true; + if((s - teams) >= 0 && (s - teams) < MAX_SCOREBOARD) + if((s->colors & 15) == (cl.scores[cl.playerentity - 1].colors & 15)) + myself = true; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + if (s->qw_spectator) + { + if (s->qw_ping || s->qw_packetloss) + DrawQ_String(x, y, va("%4i %3i %4i spectator %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + else + DrawQ_String(x, y, va(" %4i spectator %c%s", minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + else + { + // draw colors behind score + // + // + // + // + // + c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; + DrawQ_Fill(x + 14*8*FONT_SBAR->maxwidth, y+1, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + c = palette_rgb_shirtscoreboard[s->colors & 0xf]; + DrawQ_Fill(x + 14*8*FONT_SBAR->maxwidth, y+4, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + // print the text + //DrawQ_String(x, y, va("%c%4i %s", myself ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); + if (s->qw_ping || s->qw_packetloss) + DrawQ_String(x, y, va("%4i %3i %4i %5i %-4s %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + else + DrawQ_String(x, y, va(" %4i %5i %-4s %c%s", minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + } + else + { + if (s->qw_spectator) + { + if (s->qw_ping || s->qw_packetloss) + DrawQ_String(x, y, va("%4i %3i spect %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + else + DrawQ_String(x, y, va(" spect %c%s", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + else + { + // draw colors behind score + c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; + DrawQ_Fill(x + 9*8*FONT_SBAR->maxwidth, y+1, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + c = palette_rgb_shirtscoreboard[s->colors & 0xf]; + DrawQ_Fill(x + 9*8*FONT_SBAR->maxwidth, y+4, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + // print the text + //DrawQ_String(x, y, va("%c%4i %s", myself ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); + if (s->qw_ping || s->qw_packetloss) + DrawQ_String(x, y, va("%4i %3i %5i %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + else + DrawQ_String(x, y, va(" %5i %c%s", (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + } + return 8; +} + +void Sbar_DeathmatchOverlay (void) +{ + int i, y, xmin, xmax, ymin, ymax; + + // request new ping times every two second + if (cl.last_ping_request < realtime - 2 && cls.netcon) + { + cl.last_ping_request = realtime; + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "pings"); + } + else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3 || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5 || cls.protocol == PROTOCOL_DARKPLACES6/* || cls.protocol == PROTOCOL_DARKPLACES7*/) + { + // these servers usually lack the pings command and so a less efficient "ping" command must be sent, which on modern DP servers will also reply with a pingplreport command after the ping listing + static int ping_anyway_counter = 0; + if(cl.parsingtextexpectingpingforscores == 1) + { + Con_DPrintf("want to send ping, but still waiting for other reply\n"); + if(++ping_anyway_counter >= 5) + cl.parsingtextexpectingpingforscores = 0; + } + if(cl.parsingtextexpectingpingforscores != 1) + { + ping_anyway_counter = 0; + cl.parsingtextexpectingpingforscores = 1; // hide the output of the next ping report + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "ping"); + } + } + else + { + // newer server definitely has pings command, so use it for more efficiency, avoids ping reports spamming the console if they are misparsed, and saves a little bandwidth + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "pings"); + } + } + + // scores + Sbar_SortFrags (); + + ymin = 8; + ymax = 40 + 8 + (Sbar_IsTeammatch() ? (teamlines * 8 + 5): 0) + scoreboardlines * 8 - 1; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + xmin = (int) (vid_conwidth.integer - (26 + 15) * 8 * FONT_SBAR->maxwidth) / 2; // 26 characters until name, then we assume 15 character names (they can be longer but usually aren't) + else + xmin = (int) (vid_conwidth.integer - (16 + 25) * 8 * FONT_SBAR->maxwidth) / 2; // 16 characters until name, then we assume 25 character names (they can be longer but usually aren't) + xmax = vid_conwidth.integer - xmin; + + if(gamemode == GAME_NEXUIZ) + DrawQ_Pic (xmin - 8, ymin - 8, 0, xmax-xmin+1 + 2*8, ymax-ymin+1 + 2*8, 0, 0, 0, sbar_alpha_bg.value, 0); + + DrawQ_Pic ((vid_conwidth.integer - sb_ranking->width)/2, 8, sb_ranking, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); + + // draw the text + y = 40; + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + DrawQ_String(xmin, y, va("ping pl%% time frags team name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + else + { + DrawQ_String(xmin, y, va("ping pl%% frags name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + y += 8; + + if (Sbar_IsTeammatch ()) + { + // show team scores first + for (i = 0;i < teamlines && y < vid_conheight.integer;i++) + y += (int)Sbar_PrintScoreboardItem((teams + teamsort[i]), xmin, y); + y += 5; + } + + for (i = 0;i < scoreboardlines && y < vid_conheight.integer;i++) + y += (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], xmin, y); +} + +/* +================== +Sbar_MiniDeathmatchOverlay + +================== +*/ +void Sbar_MiniDeathmatchOverlay (int x, int y) +{ + int i, j, numlines, range_begin, range_end, myteam, teamsep; + + // do not draw this if sbar_miniscoreboard_size is zero + if(sbar_miniscoreboard_size.value == 0) + return; + // adjust the given y if sbar_miniscoreboard_size doesn't indicate default (< 0) + if(sbar_miniscoreboard_size.value > 0) + y = (int) (vid_conheight.integer - sbar_miniscoreboard_size.value * 8); + + // scores + Sbar_SortFrags (); + + // decide where to print + if (gamemode == GAME_TRANSFUSION) + numlines = (vid_conwidth.integer - x + 127) / 128; + else + numlines = (vid_conheight.integer - y + 7) / 8; + + // give up if there isn't room + if (x >= vid_conwidth.integer || y >= vid_conheight.integer || numlines < 1) + return; + + //find us + for (i = 0; i < scoreboardlines; i++) + if (fragsort[i] == cl.playerentity - 1) + break; + + range_begin = 0; + range_end = scoreboardlines; + teamsep = 0; + + if (gamemode != GAME_TRANSFUSION) + if (Sbar_IsTeammatch ()) + { + // reserve space for the team scores + numlines -= teamlines; + + // find first and last player of my team (only draw the team totals and my own team) + range_begin = range_end = i; + myteam = cl.scores[fragsort[i]].colors & 15; + while(range_begin > 0 && (cl.scores[fragsort[range_begin-1]].colors & 15) == myteam) + --range_begin; + while(range_end < scoreboardlines && (cl.scores[fragsort[range_end]].colors & 15) == myteam) + ++range_end; + + // looks better than two players + if(numlines == 2) + { + teamsep = 8; + numlines = 1; + } + } + + // figure out start + i -= numlines/2; + i = min(i, range_end - numlines); + i = max(i, range_begin); + + if (gamemode == GAME_TRANSFUSION) + { + for (;i < range_end && x < vid_conwidth.integer;i++) + x += 128 + (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], x, y); + } + else + { + if(range_end - i < numlines) // won't draw to bottom? + y += 8 * (numlines - (range_end - i)); // bottom align + // show team scores first + for (j = 0;j < teamlines && y < vid_conheight.integer;j++) + y += (int)Sbar_PrintScoreboardItem((teams + teamsort[j]), x, y); + y += teamsep; + for (;i < range_end && y < vid_conheight.integer;i++) + y += (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], x, y); + } +} + +int Sbar_TeamColorCompare(const void *t1_, const void *t2_) +{ + static int const sortorder[16] = + { + 1001, + 1002, + 1003, + 1004, + 1, // red + 1005, + 1006, + 1007, + 1008, + 4, // pink + 1009, + 1010, + 3, // yellow + 2, // blue + 1011, + 1012 + }; + const scoreboard_t *t1 = *(scoreboard_t **) t1_; + const scoreboard_t *t2 = *(scoreboard_t **) t2_; + int tc1 = sortorder[t1->colors & 15]; + int tc2 = sortorder[t2->colors & 15]; + return tc1 - tc2; +} + +void Sbar_Score (int margin) +{ + int i, me, score, otherleader, place, distribution, minutes, seconds; + double timeleft; + int sbar_x_save = sbar_x; + int sbar_y_save = sbar_y; + + + sbar_y = (int) (vid_conheight.value - (32+12)); + sbar_x -= margin; + + me = cl.playerentity - 1; + if (sbar_scorerank.integer && me >= 0 && me < cl.maxclients) + { + if(Sbar_IsTeammatch()) + { + // Layout: + // + // team1 team3 team4 + // + // TEAM2 + + scoreboard_t *teamcolorsort[16]; + + Sbar_SortFrags(); + for(i = 0; i < teamlines; ++i) + teamcolorsort[i] = &(teams[i]); + + // Now sort them by color + qsort(teamcolorsort, teamlines, sizeof(*teamcolorsort), Sbar_TeamColorCompare); + + // : margin + // -12*4: four digits space + place = (teamlines - 1) * (-12 * 4); + + for(i = 0; i < teamlines; ++i) + { + int cindex = teamcolorsort[i]->colors & 15; + unsigned char *c = palette_rgb_shirtscoreboard[cindex]; + float cm = max(max(c[0], c[1]), c[2]); + float cr = c[0] / cm; + float cg = c[1] / cm; + float cb = c[2] / cm; + if(cindex == (cl.scores[cl.playerentity - 1].colors & 15)) // my team + { + Sbar_DrawXNum(-32*4, 0, teamcolorsort[i]->frags, 4, 32, cr, cg, cb, 1, 0); + } + else // other team + { + Sbar_DrawXNum(place, -12, teamcolorsort[i]->frags, 4, 12, cr, cg, cb, 1, 0); + place += 4 * 12; + } + } + } + else + { + // Layout: + // + // leading place + // + // FRAGS + // + // find leading score other than ourselves, to calculate distribution + // find our place in the scoreboard + score = cl.scores[me].frags; + for (i = 0, otherleader = -1, place = 1;i < cl.maxclients;i++) + { + if (cl.scores[i].name[0] && i != me) + { + if (otherleader == -1 || cl.scores[i].frags > cl.scores[otherleader].frags) + otherleader = i; + if (score < cl.scores[i].frags || (score == cl.scores[i].frags && i < me)) + place++; + } + } + distribution = otherleader >= 0 ? score - cl.scores[otherleader].frags : 0; + if (place == 1) + Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 1, 1, 1, 0); + else if (place == 2) + Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 1, 0, 1, 0); + else + Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 0, 0, 1, 0); + if (otherleader < 0) + Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 1, 1, 0); + if (distribution >= 0) + { + Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 1, 1, 1, 0); + Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 1, 1, 0); + } + else if (distribution >= -5) + { + Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 1, 0, 1, 0); + Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 0, 1, 0); + } + else + { + Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 0, 0, 1, 0); + Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 0, 0, 1, 0); + } + } + } + + if (sbar_gametime.integer && cl.statsf[STAT_TIMELIMIT]) + { + timeleft = max(0, cl.statsf[STAT_TIMELIMIT] * 60 - cl.time); + minutes = (int)floor(timeleft / 60); + seconds = (int)(floor(timeleft) - minutes * 60); + if (minutes >= 5) + { + Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 1, 1, 0); + if(sb_colon && sb_colon->tex != r_texture_notexture) + DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 1, sbar_alpha_fg.value, 0); + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); + } + else if (minutes >= 1) + { + Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 0, 1, 0); + if(sb_colon && sb_colon->tex != r_texture_notexture) + DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 0, sbar_alpha_fg.value, 0); + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 0, 1, 0); + } + else if ((int)(timeleft * 4) & 1) + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); + else + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 0, 0, 1, 0); + } + else if (sbar_gametime.integer) + { + minutes = (int)floor(cl.time / 60); + seconds = (int)(floor(cl.time) - minutes * 60); + Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 1, 1, 0); + if(sb_colon && sb_colon->tex != r_texture_notexture) + DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 1, sbar_alpha_fg.value, 0); + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); + } + + sbar_x = sbar_x_save; + sbar_y = sbar_y_save; +} + +/* +================== +Sbar_IntermissionOverlay + +================== +*/ +void Sbar_IntermissionOverlay (void) +{ + int dig; + int num; + + if (cl.gametype == GAME_DEATHMATCH) + { + Sbar_DeathmatchOverlay (); + return; + } + + sbar_x = (vid_conwidth.integer - 320) >> 1; + sbar_y = (vid_conheight.integer - 200) >> 1; + + DrawQ_Pic (sbar_x + 64, sbar_y + 24, sb_complete, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); + DrawQ_Pic (sbar_x + 0, sbar_y + 56, sb_inter, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); + +// time + dig = (int)cl.completed_time / 60; + Sbar_DrawNum (160, 64, dig, 3, 0); + num = (int)cl.completed_time - dig*60; + Sbar_DrawPic (234,64,sb_colon); + Sbar_DrawPic (246,64,sb_nums[0][num/10]); + Sbar_DrawPic (266,64,sb_nums[0][num%10]); + +// LA: Display as "a" instead of "a/b" if b is 0 + if(cl.stats[STAT_TOTALSECRETS]) + { + Sbar_DrawNum (160, 104, cl.stats[STAT_SECRETS], 3, 0); + if (gamemode != GAME_NEXUIZ) + Sbar_DrawPic (232, 104, sb_slash); + Sbar_DrawNum (240, 104, cl.stats[STAT_TOTALSECRETS], 3, 0); + } + else + { + Sbar_DrawNum (240, 104, cl.stats[STAT_SECRETS], 3, 0); + } + + if(cl.stats[STAT_TOTALMONSTERS]) + { + Sbar_DrawNum (160, 144, cl.stats[STAT_MONSTERS], 3, 0); + if (gamemode != GAME_NEXUIZ) + Sbar_DrawPic (232, 144, sb_slash); + Sbar_DrawNum (240, 144, cl.stats[STAT_TOTALMONSTERS], 3, 0); + } + else + { + Sbar_DrawNum (240, 144, cl.stats[STAT_MONSTERS], 3, 0); + } +} + + +/* +================== +Sbar_FinaleOverlay + +================== +*/ +void Sbar_FinaleOverlay (void) +{ + DrawQ_Pic((vid_conwidth.integer - sb_finale->width)/2, 16, sb_finale, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); +} + diff --git a/misc/source/darkplaces-src/sbar.h b/misc/source/darkplaces-src/sbar.h new file mode 100644 index 00000000..4b566b6d --- /dev/null +++ b/misc/source/darkplaces-src/sbar.h @@ -0,0 +1,36 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef SBAR_H +#define SBAR_H + +#define SBAR_HEIGHT 24 + +extern int sb_lines; ///< scan lines to draw +extern cvar_t sbar_alpha_bg; +extern cvar_t sbar_alpha_fg; + +void Sbar_Init (void); + +/// called every frame by screen +void Sbar_Draw (void); + +#endif + diff --git a/misc/source/darkplaces-src/screen.h b/misc/source/darkplaces-src/screen.h new file mode 100644 index 00000000..b88a80d5 --- /dev/null +++ b/misc/source/darkplaces-src/screen.h @@ -0,0 +1,86 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// screen.h + +#ifndef SCREEN_H +#define SCREEN_H + +void CL_Screen_Init (void); +void CL_UpdateScreen (void); +void SCR_CenterPrint(const char *str); + +void SCR_BeginLoadingPlaque (void); + +// invoke refresh of loading plaque (nothing else seen) +void SCR_UpdateLoadingScreen(qboolean clear); +void SCR_UpdateLoadingScreenIfShown(void); + +// pushes an item on the loading screen +void SCR_PushLoadingScreen (qboolean redraw, const char *msg, float len_in_parent); +void SCR_PopLoadingScreen (qboolean redraw); +void SCR_ClearLoadingScreen (qboolean redraw); + +extern float scr_con_current; // current height of displayed console + +extern int sb_lines; + +extern cvar_t scr_viewsize; +extern cvar_t scr_fov; +extern cvar_t showfps; +extern cvar_t showtime; +extern cvar_t showdate; + +extern cvar_t crosshair; +extern cvar_t crosshair_size; + +extern cvar_t scr_conalpha; +extern cvar_t scr_conalphafactor; +extern cvar_t scr_conalpha2factor; +extern cvar_t scr_conalpha3factor; +extern cvar_t scr_conscroll_x; +extern cvar_t scr_conscroll_y; +extern cvar_t scr_conscroll2_x; +extern cvar_t scr_conscroll2_y; +extern cvar_t scr_conscroll3_x; +extern cvar_t scr_conscroll3_y; +extern cvar_t scr_conbrightness; +extern cvar_t r_letterbox; + +extern cvar_t scr_refresh; +extern cvar_t scr_stipple; + +extern cvar_t r_stereo_separation; +extern cvar_t r_stereo_angle; +qboolean R_Stereo_Active(void); +extern int r_stereo_side; + +typedef struct scr_touchscreenarea_s +{ + const char *pic; + float rect[4]; + float active; +} +scr_touchscreenarea_t; + +extern int scr_numtouchscreenareas; +extern scr_touchscreenarea_t scr_touchscreenareas[16]; + +#endif + diff --git a/misc/source/darkplaces-src/server.h b/misc/source/darkplaces-src/server.h new file mode 100644 index 00000000..cb32fe42 --- /dev/null +++ b/misc/source/darkplaces-src/server.h @@ -0,0 +1,580 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// server.h + +#ifndef SERVER_H +#define SERVER_H + +typedef struct server_static_s +{ + /// number of svs.clients slots (updated by maxplayers command) + int maxclients, maxclients_next; + /// client slots + struct client_s *clients; + /// episode completion information + int serverflags; + /// cleared when at SV_SpawnServer + qboolean changelevel_issued; + /// server infostring + char serverinfo[MAX_SERVERINFO_STRING]; + // performance data + float perf_cpuload; + float perf_lost; + float perf_offset_avg; + float perf_offset_max; + float perf_offset_sdev; + // temporary performance data accumulators + float perf_acc_realtime; + float perf_acc_sleeptime; + float perf_acc_lost; + float perf_acc_offset; + float perf_acc_offset_squared; + float perf_acc_offset_max; + int perf_acc_offset_samples; + + // csqc stuff + unsigned char *csqc_progdata; + size_t csqc_progsize_deflated; + unsigned char *csqc_progdata_deflated; +} server_static_t; + +//============================================================================= + +typedef enum server_state_e {ss_loading, ss_active} server_state_t; + +#define MAX_CONNECTFLOODADDRESSES 16 +typedef struct server_connectfloodaddress_s +{ + double lasttime; + lhnetaddress_t address; +} +server_connectfloodaddress_t; + +typedef struct server_s +{ + /// false if only a net client + qboolean active; + + qboolean paused; + double pausedstart; + /// handle connections specially + qboolean loadgame; + + /// one of the PROTOCOL_ values + protocolversion_t protocol; + + double time; + + double frametime; + + // used by PF_checkclient + int lastcheck; + double lastchecktime; + + // crc of clientside progs at time of level start + int csqc_progcrc; // -1 = no progs + int csqc_progsize; // -1 = no progs + char csqc_progname[MAX_QPATH]; // copied from csqc_progname at level start + + /// collision culling data + world_t world; + + /// map name + char name[64]; // %s followed by entrance name + // variants of map name + char worldmessage[40]; // map title (not related to filename) + char worldbasename[MAX_QPATH]; // %s + char worldname[MAX_QPATH]; // maps/%s.bsp + char worldnamenoextension[MAX_QPATH]; // maps/%s + struct model_s *worldmodel; + // NULL terminated + // LordHavoc: precaches are now MAX_QPATH rather than a pointer + // updated by SV_ModelIndex + char model_precache[MAX_MODELS][MAX_QPATH]; + struct model_s *models[MAX_MODELS]; + // NULL terminated + // LordHavoc: precaches are now MAX_QPATH rather than a pointer + // updated by SV_SoundIndex + char sound_precache[MAX_SOUNDS][MAX_QPATH]; + char lightstyles[MAX_LIGHTSTYLES][64]; + /// some actions are only valid during load + server_state_t state; + + sizebuf_t datagram; + unsigned char datagram_buf[NET_MAXMESSAGE]; + + // copied to all clients at end of frame + sizebuf_t reliable_datagram; + unsigned char reliable_datagram_buf[NET_MAXMESSAGE]; + + sizebuf_t signon; + /// LordHavoc: increased signon message buffer from 8192 + unsigned char signon_buf[NET_MAXMESSAGE]; + + /// connection flood blocking + /// note this is in server_t rather than server_static_t so that it is + /// reset on each map command (such as New Game in singleplayer) + server_connectfloodaddress_t connectfloodaddresses[MAX_CONNECTFLOODADDRESSES]; + +#define SV_MAX_PARTICLEEFFECTNAME 256 + qboolean particleeffectnamesloaded; + char particleeffectname[SV_MAX_PARTICLEEFFECTNAME][MAX_QPATH]; + + int writeentitiestoclient_stats_culled_pvs; + int writeentitiestoclient_stats_culled_trace; + int writeentitiestoclient_stats_visibleentities; + int writeentitiestoclient_stats_totalentities; + int writeentitiestoclient_cliententitynumber; + int writeentitiestoclient_clientnumber; + sizebuf_t *writeentitiestoclient_msg; + vec3_t writeentitiestoclient_eyes[MAX_CLIENTNETWORKEYES]; + int writeentitiestoclient_numeyes; + int writeentitiestoclient_pvsbytes; + unsigned char writeentitiestoclient_pvs[MAX_MAP_LEAFS/8]; + const entity_state_t *writeentitiestoclient_sendstates[MAX_EDICTS]; + unsigned short writeentitiestoclient_csqcsendstates[MAX_EDICTS]; + + int numsendentities; + entity_state_t sendentities[MAX_EDICTS]; + entity_state_t *sendentitiesindex[MAX_EDICTS]; + + int sententitiesmark; + int sententities[MAX_EDICTS]; + int sententitiesconsideration[MAX_EDICTS]; + + /// legacy support for self.Version based csqc entity networking + unsigned char csqcentityversion[MAX_EDICTS]; // legacy +} server_t; + +#define NUM_CSQCENTITIES_PER_FRAME 256 +typedef struct csqcentityframedb_s +{ + int framenum; + int num; + unsigned short entno[NUM_CSQCENTITIES_PER_FRAME]; + int sendflags[NUM_CSQCENTITIES_PER_FRAME]; +} csqcentityframedb_t; + +// if defined this does ping smoothing, otherwise it does not +//#define NUM_PING_TIMES 16 + +#define NUM_SPAWN_PARMS 16 + +typedef struct client_s +{ + /// false = empty client slot + qboolean active; + /// false = don't do ClientDisconnect on drop + qboolean clientconnectcalled; + /// false = don't send datagrams + qboolean spawned; + /// 1 = send svc_serverinfo and advance to 2, 2 doesn't send, then advances to 0 (allowing unlimited sending) when prespawn is received + int sendsignon; + + /// requested rate in bytes per second + int rate; + + /// realtime this client connected + double connecttime; + + /// keepalive messages must be sent periodically during signon + double keepalivetime; + + /// communications handle + netconn_t *netconnection; + + int movesequence; + signed char movement_count[NETGRAPH_PACKETS]; + int movement_highestsequence_seen; // not the same as movesequence if prediction is off + /// movement + usercmd_t cmd; + /// intended motion calced from cmd + vec3_t wishdir; + + /// PRVM_EDICT_NUM(clientnum+1) + prvm_edict_t *edict; + +#ifdef NUM_PING_TIMES + float ping_times[NUM_PING_TIMES]; + /// ping_times[num_pings%NUM_PING_TIMES] + int num_pings; +#endif + /// LordHavoc: can be used for prediction or whatever... + float ping; + + /// this is used by sv_clmovement_minping code + double clmovement_disabletimeout; + /// this is used by sv_clmovement_inputtimeout code + float clmovement_inputtimeout; + +/// spawn parms are carried from level to level + float spawn_parms[NUM_SPAWN_PARMS]; + + // properties that are sent across the network only when changed + char name[MAX_SCOREBOARDNAME], old_name[MAX_SCOREBOARDNAME]; + int colors, old_colors; + int frags, old_frags; + char playermodel[MAX_QPATH], old_model[MAX_QPATH]; + char playerskin[MAX_QPATH], old_skin[MAX_QPATH]; + + /// netaddress support + char netaddress[MAX_QPATH]; + + /// visibility state + float visibletime[MAX_EDICTS]; + + // scope is whether an entity is currently being networked to this client + // sendflags is what properties have changed on the entity since the last + // update that was sent + int csqcnumedicts; + unsigned char csqcentityscope[MAX_EDICTS]; + unsigned int csqcentitysendflags[MAX_EDICTS]; + +#define NUM_CSQCENTITYDB_FRAMES 256 + unsigned char csqcentityglobalhistory[MAX_EDICTS]; // set to 1 if the entity was ever csqc networked to the client, and never reset back to 0 + csqcentityframedb_t csqcentityframehistory[NUM_CSQCENTITYDB_FRAMES]; + int csqcentityframehistory_next; + int csqcentityframe_lastreset; + + /// prevent animated names + float nametime; + + /// latest received clc_ackframe (used to detect packet loss) + int latestframenum; + + /// cache weaponmodel name lookups + char weaponmodel[MAX_QPATH]; + int weaponmodelindex; + + /// clientcamera (entity to use as camera) + int clientcamera; + + entityframe_database_t *entitydatabase; + entityframe4_database_t *entitydatabase4; + entityframe5_database_t *entitydatabase5; + + // delta compression of stats + unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; + int stats[MAX_CL_STATS]; + + unsigned char unreliablemsg_data[NET_MAXMESSAGE]; + sizebuf_t unreliablemsg; + int unreliablemsg_splitpoints; + int unreliablemsg_splitpoint[NET_MAXMESSAGE/16]; + + // information on an active download if any + qfile_t *download_file; + int download_expectedposition; ///< next position the client should ack + qboolean download_started; + char download_name[MAX_QPATH]; + qboolean download_deflate; + + // fixangle data + qboolean fixangle_angles_set; + vec3_t fixangle_angles; + + /// demo recording + qfile_t *sv_demo_file; + + // number of skipped entity frames + // if it exceeds a limit, an empty entity frame is sent + int num_skippedentityframes; +} client_t; + + +//============================================================================= + +// edict->movetype values +#define MOVETYPE_NONE 0 ///< never moves +#define MOVETYPE_ANGLENOCLIP 1 +#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 ///< gravity +#define MOVETYPE_STEP 4 ///< gravity, special edge handling +#define MOVETYPE_FLY 5 +#define MOVETYPE_TOSS 6 ///< gravity +#define MOVETYPE_PUSH 7 ///< no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 +#define MOVETYPE_FLYMISSILE 9 ///< extra size to monsters +#define MOVETYPE_BOUNCE 10 +#define MOVETYPE_BOUNCEMISSILE 11 ///< bounce w/o gravity +#define MOVETYPE_FOLLOW 12 ///< track movement of aiment +#define MOVETYPE_FAKEPUSH 13 ///< tenebrae's push that doesn't push +#define MOVETYPE_PHYSICS 32 ///< indicates this object is physics controlled + +// edict->solid values +#define SOLID_NOT 0 ///< no interaction with other objects +#define SOLID_TRIGGER 1 ///< touch on edge, but not blocking +#define SOLID_BBOX 2 ///< touch on edge, block +#define SOLID_SLIDEBOX 3 ///< touch on edge, but not an onground +#define SOLID_BSP 4 ///< bsp clip, touch on edge, block +// LordHavoc: corpse code +#define SOLID_CORPSE 5 ///< same as SOLID_BBOX, except it behaves as SOLID_NOT against SOLID_SLIDEBOX objects (players/monsters) +// LordHavoc: physics +#define SOLID_PHYSICS_BOX 32 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) +#define SOLID_PHYSICS_SPHERE 33 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) +#define SOLID_PHYSICS_CAPSULE 34 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) + +// edict->deadflag values +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// edict->flags +#define FL_FLY 1 +#define FL_SWIM 2 +#define FL_CONVEYOR 4 +#define FL_CLIENT 8 +#define FL_INWATER 16 +#define FL_MONSTER 32 +#define FL_GODMODE 64 +#define FL_NOTARGET 128 +#define FL_ITEM 256 +#define FL_ONGROUND 512 +#define FL_PARTIALGROUND 1024 ///< not all corners are valid +#define FL_WATERJUMP 2048 ///< player jumping out of water +#define FL_JUMPRELEASED 4096 ///< for jump debouncing + +#define SPAWNFLAG_NOT_EASY 256 +#define SPAWNFLAG_NOT_MEDIUM 512 +#define SPAWNFLAG_NOT_HARD 1024 +#define SPAWNFLAG_NOT_DEATHMATCH 2048 + +//============================================================================ + +extern cvar_t coop; +extern cvar_t deathmatch; +extern cvar_t fraglimit; +extern cvar_t gamecfg; +extern cvar_t noexit; +extern cvar_t nomonsters; +extern cvar_t pausable; +extern cvar_t pr_checkextension; +extern cvar_t samelevel; +extern cvar_t saved1; +extern cvar_t saved2; +extern cvar_t saved3; +extern cvar_t saved4; +extern cvar_t savedgamecfg; +extern cvar_t scratch1; +extern cvar_t scratch2; +extern cvar_t scratch3; +extern cvar_t scratch4; +extern cvar_t skill; +extern cvar_t slowmo; +extern cvar_t sv_accelerate; +extern cvar_t sv_aim; +extern cvar_t sv_airaccel_qw; +extern cvar_t sv_airaccel_sideways_friction; +extern cvar_t sv_airaccelerate; +extern cvar_t sv_airstopaccelerate; +extern cvar_t sv_airstrafeaccelerate; +extern cvar_t sv_maxairstrafespeed; +extern cvar_t sv_airstrafeaccel_qw; +extern cvar_t sv_aircontrol; +extern cvar_t sv_aircontrol_power; +extern cvar_t sv_aircontrol_penalty; +extern cvar_t sv_airspeedlimit_nonqw; +extern cvar_t sv_allowdownloads; +extern cvar_t sv_allowdownloads_archive; +extern cvar_t sv_allowdownloads_config; +extern cvar_t sv_allowdownloads_dlcache; +extern cvar_t sv_allowdownloads_inarchive; +extern cvar_t sv_areagrid_mingridsize; +extern cvar_t sv_checkforpacketsduringsleep; +extern cvar_t sv_clmovement_enable; +extern cvar_t sv_clmovement_minping; +extern cvar_t sv_clmovement_minping_disabletime; +extern cvar_t sv_clmovement_inputtimeout; +extern cvar_t sv_clmovement_maxnetfps; +extern cvar_t sv_cullentities_nevercullbmodels; +extern cvar_t sv_cullentities_pvs; +extern cvar_t sv_cullentities_stats; +extern cvar_t sv_cullentities_trace; +extern cvar_t sv_cullentities_trace_delay; +extern cvar_t sv_cullentities_trace_enlarge; +extern cvar_t sv_cullentities_trace_prediction; +extern cvar_t sv_cullentities_trace_samples; +extern cvar_t sv_cullentities_trace_samples_extra; +extern cvar_t sv_debugmove; +extern cvar_t sv_echobprint; +extern cvar_t sv_edgefriction; +extern cvar_t sv_entpatch; +extern cvar_t sv_fixedframeratesingleplayer; +extern cvar_t sv_freezenonclients; +extern cvar_t sv_friction; +extern cvar_t sv_gameplayfix_blowupfallenzombies; +extern cvar_t sv_gameplayfix_consistentplayerprethink; +extern cvar_t sv_gameplayfix_delayprojectiles; +extern cvar_t sv_gameplayfix_droptofloorstartsolid; +extern cvar_t sv_gameplayfix_droptofloorstartsolid_nudgetocorrect; +extern cvar_t sv_gameplayfix_easierwaterjump; +extern cvar_t sv_gameplayfix_findradiusdistancetobox; +extern cvar_t sv_gameplayfix_gravityunaffectedbyticrate; +extern cvar_t sv_gameplayfix_grenadebouncedownslopes; +extern cvar_t sv_gameplayfix_multiplethinksperframe; +extern cvar_t sv_gameplayfix_noairborncorpse; +extern cvar_t sv_gameplayfix_noairborncorpse_allowsuspendeditems; +extern cvar_t sv_gameplayfix_nudgeoutofsolid; +extern cvar_t sv_gameplayfix_nudgeoutofsolid_separation; +extern cvar_t sv_gameplayfix_q2airaccelerate; +extern cvar_t sv_gameplayfix_nogravityonground; +extern cvar_t sv_gameplayfix_setmodelrealbox; +extern cvar_t sv_gameplayfix_slidemoveprojectiles; +extern cvar_t sv_gameplayfix_stepdown; +extern cvar_t sv_gameplayfix_stepwhilejumping; +extern cvar_t sv_gameplayfix_stepmultipletimes; +extern cvar_t sv_gameplayfix_nostepmoveonsteepslopes; +extern cvar_t sv_gameplayfix_swiminbmodels; +extern cvar_t sv_gameplayfix_upwardvelocityclearsongroundflag; +extern cvar_t sv_gameplayfix_downtracesupportsongroundflag; +extern cvar_t sv_gameplayfix_q1bsptracelinereportstexture; +extern cvar_t sv_gravity; +extern cvar_t sv_idealpitchscale; +extern cvar_t sv_jumpstep; +extern cvar_t sv_jumpvelocity; +extern cvar_t sv_maxairspeed; +extern cvar_t sv_maxrate; +extern cvar_t sv_maxspeed; +extern cvar_t sv_maxvelocity; +extern cvar_t sv_nostep; +extern cvar_t sv_playerphysicsqc; +extern cvar_t sv_progs; +extern cvar_t sv_protocolname; +extern cvar_t sv_random_seed; +extern cvar_t sv_ratelimitlocalplayer; +extern cvar_t sv_sound_land; +extern cvar_t sv_sound_watersplash; +extern cvar_t sv_stepheight; +extern cvar_t sv_stopspeed; +extern cvar_t sv_wallfriction; +extern cvar_t sv_wateraccelerate; +extern cvar_t sv_waterfriction; +extern cvar_t sys_ticrate; +extern cvar_t teamplay; +extern cvar_t temp1; +extern cvar_t timelimit; + +extern mempool_t *sv_mempool; + +/// persistant server info +extern server_static_t svs; +/// local server +extern server_t sv; + +extern client_t *host_client; + +//=========================================================== + +void SV_Init (void); + +void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count); +void SV_StartEffect (vec3_t org, int modelindex, int startframe, int framecount, int framerate); +void SV_StartSound (prvm_edict_t *entity, int channel, const char *sample, int volume, float attenuation, qboolean reliable); +void SV_StartPointSound (vec3_t origin, const char *sample, int volume, float attenuation); + +void SV_ConnectClient (int clientnum, netconn_t *netconnection); +void SV_DropClient (qboolean crash); + +void SV_SendClientMessages (void); + +void SV_ReadClientMessage(void); + +// precachemode values: +// 0 = fail if not precached, +// 1 = warn if not found and precache if possible +// 2 = precache +int SV_ModelIndex(const char *s, int precachemode); +int SV_SoundIndex(const char *s, int precachemode); + +int SV_ParticleEffectIndex(const char *name); + +dp_model_t *SV_GetModelByIndex(int modelindex); +dp_model_t *SV_GetModelFromEdict(prvm_edict_t *ed); + +void SV_SetIdealPitch (void); + +void SV_AddUpdates (void); + +void SV_ClientThink (void); + +void SV_ClientPrint(const char *msg); +void SV_ClientPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); +void SV_BroadcastPrint(const char *msg); +void SV_BroadcastPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); + +void SV_Physics (void); +void SV_Physics_ClientMove (void); +//void SV_Physics_ClientEntity (prvm_edict_t *ent); + +qboolean SV_PlayerCheckGround (prvm_edict_t *ent); +qboolean SV_CheckBottom (prvm_edict_t *ent); +qboolean SV_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace); + +/*! Needs to be called any time an entity changes origin, mins, maxs, or solid + * sets ent->v.absmin and ent->v.absmax + * call TouchAreaGrid as well to fire triggers that overlap the box + */ +void SV_LinkEdict(prvm_edict_t *ent); +void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent); +void SV_LinkEdict_TouchAreaGrid_Call(prvm_edict_t *touch, prvm_edict_t *ent); // if we detected a touch from another source + +/*! move an entity that is stuck by small amounts in various directions to try to nudge it back into the collision hull + * returns true if it found a better place + */ +qboolean SV_UnstickEntity (prvm_edict_t *ent); + +/// calculates hitsupercontentsmask for a generic qc entity +int SV_GenericHitSuperContentsMask(const prvm_edict_t *edict); +/// traces a box move against worldmodel and all entities in the specified area +trace_t SV_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask); +trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask); +trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask); + +qboolean SV_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs); + +int SV_PointSuperContents(const vec3_t point); + +void SV_FlushBroadcastMessages(void); +void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); + +void SV_MoveToGoal (void); + +void SV_ApplyClientMove (void); +void SV_SaveSpawnparms (void); +void SV_SpawnServer (const char *server); + +void SV_CheckVelocity (prvm_edict_t *ent); + +void SV_SetupVM(void); + +void SV_VM_Begin(void); +void SV_VM_End(void); + +const char *Host_TimingReport(void); ///< for output in Host_Status_f + +int SV_GetPitchSign(prvm_edict_t *ent); +void SV_GetEntityMatrix (prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix); + +#endif + diff --git a/misc/source/darkplaces-src/shader_glsl.h b/misc/source/darkplaces-src/shader_glsl.h new file mode 100644 index 00000000..b02bd3c0 --- /dev/null +++ b/misc/source/darkplaces-src/shader_glsl.h @@ -0,0 +1,1398 @@ +"// ambient+diffuse+specular+normalmap+attenuation+cubemap+fog shader\n" +"// written by Forest 'LordHavoc' Hale\n" +"// shadowmapping enhancements by Lee 'eihrul' Salzman\n" +"\n" +"#ifdef GLSL130\n" +"precision highp float;\n" +"# ifdef VERTEX_SHADER\n" +"# define dp_varying out\n" +"# define dp_attribute in\n" +"# endif\n" +"# ifdef FRAGMENT_SHADER\n" +"out vec4 dp_FragColor;\n" +"# define dp_varying in\n" +"# define dp_attribute in\n" +"# endif\n" +"# define dp_offsetmapping_dFdx dFdx\n" +"# define dp_offsetmapping_dFdy dFdy\n" +"# define dp_textureGrad textureGrad\n" +"# define dp_texture2D texture\n" +"# define dp_texture3D texture\n" +"# define dp_textureCube texture\n" +"# define dp_shadow2D(a,b) texture(a,b)\n" +"#else\n" +"# ifdef FRAGMENT_SHADER\n" +"# define dp_FragColor gl_FragColor\n" +"# endif\n" +"# define dp_varying varying\n" +"# define dp_attribute attribute\n" +"# define dp_offsetmapping_dFdx(a) vec2(0.0, 0.0)\n" +"# define dp_offsetmapping_dFdy(a) vec2(0.0, 0.0)\n" +"# define dp_textureGrad(a,b,c,d) texture2D(a,b)\n" +"# define dp_texture2D texture2D\n" +"# define dp_texture3D texture3D\n" +"# define dp_textureCube textureCube\n" +"# define dp_shadow2D(a,b) float(shadow2D(a,b))\n" +"#endif\n" +"\n" +"// GL ES and GLSL130 shaders use precision modifiers, standard GL does not\n" +"// in GLSL130 we don't use them though because of syntax differences (can't use precision with inout)\n" +"#ifndef GL_ES\n" +"#define lowp\n" +"#define mediump\n" +"#define highp\n" +"#endif\n" +"\n" +"#ifdef VERTEX_SHADER\n" +"dp_attribute vec4 Attrib_Position; // vertex\n" +"dp_attribute vec4 Attrib_Color; // color\n" +"dp_attribute vec4 Attrib_TexCoord0; // material texcoords\n" +"dp_attribute vec3 Attrib_TexCoord1; // svector\n" +"dp_attribute vec3 Attrib_TexCoord2; // tvector\n" +"dp_attribute vec3 Attrib_TexCoord3; // normal\n" +"dp_attribute vec4 Attrib_TexCoord4; // lightmap texcoords\n" +"#endif\n" +"dp_varying mediump vec4 VertexColor;\n" +"\n" +"#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE) || defined(USEFOGHEIGHTTEXTURE)\n" +"# define USEFOG\n" +"#endif\n" +"#if defined(MODE_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n" +"# define USELIGHTMAP\n" +"#endif\n" +"#if defined(USESPECULAR) || defined(USEOFFSETMAPPING) || defined(USEREFLECTCUBE) || defined(MODE_FAKELIGHT) || defined(USEFOG)\n" +"# define USEEYEVECTOR\n" +"#endif\n" +"\n" +"#ifdef USESHADOWMAP2D\n" +"# ifdef GL_EXT_gpu_shader4\n" +"# extension GL_EXT_gpu_shader4 : enable\n" +"# endif\n" +"# ifdef GL_ARB_texture_gather\n" +"# extension GL_ARB_texture_gather : enable\n" +"# else\n" +"# ifdef GL_AMD_texture_texture4\n" +"# extension GL_AMD_texture_texture4 : enable\n" +"# endif\n" +"# endif\n" +"#endif\n" +"\n" +"//#ifdef USESHADOWSAMPLER\n" +"//# extension GL_ARB_shadow : enable\n" +"//#endif\n" +"\n" +"//#ifdef __GLSL_CG_DATA_TYPES\n" +"//# define myhalf half\n" +"//# define myhalf2 half2\n" +"//# define myhalf3 half3\n" +"//# define myhalf4 half4\n" +"//#else\n" +"# define myhalf mediump float\n" +"# define myhalf2 mediump vec2\n" +"# define myhalf3 mediump vec3\n" +"# define myhalf4 mediump vec4\n" +"//#endif\n" +"\n" +"#ifdef VERTEX_SHADER\n" +"uniform highp mat4 ModelViewProjectionMatrix;\n" +"#endif\n" +"\n" +"#ifdef VERTEX_SHADER\n" +"#ifdef USETRIPPY\n" +"// LordHavoc: based on shader code linked at: http://www.youtube.com/watch?v=JpksyojwqzE\n" +"// tweaked scale\n" +"uniform highp float ClientTime;\n" +"vec4 TrippyVertex(vec4 position)\n" +"{\n" +" float worldTime = ClientTime;\n" +" // tweaked for Quake\n" +" worldTime *= 10.0;\n" +" position *= 0.125;\n" +" //~tweaked for Quake\n" +" float distanceSquared = (position.x * position.x + position.z * position.z);\n" +" position.y += 5.0*sin(distanceSquared*sin(worldTime/143.0)/1000.0);\n" +" float y = position.y;\n" +" float x = position.x;\n" +" float om = sin(distanceSquared*sin(worldTime/256.0)/5000.0) * sin(worldTime/200.0);\n" +" position.y = x*sin(om)+y*cos(om);\n" +" position.x = x*cos(om)-y*sin(om);\n" +" return position;\n" +"}\n" +"#endif\n" +"#endif\n" +"\n" +"#ifdef MODE_DEPTH_OR_SHADOW\n" +"#ifdef VERTEX_SHADER\n" +"void main(void)\n" +"{\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif\n" +"#else // !MODE_DEPTH_ORSHADOW\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_SHOWDEPTH\n" +"#ifdef VERTEX_SHADER\n" +"void main(void)\n" +"{\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +" VertexColor = vec4(gl_Position.z, gl_Position.z, gl_Position.z, 1.0);\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main(void)\n" +"{\n" +" dp_FragColor = VertexColor;\n" +"}\n" +"#endif\n" +"#else // !MODE_SHOWDEPTH\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_POSTPROCESS\n" +"dp_varying mediump vec2 TexCoord1;\n" +"dp_varying mediump vec2 TexCoord2;\n" +"\n" +"#ifdef VERTEX_SHADER\n" +"void main(void)\n" +"{\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +" TexCoord1 = Attrib_TexCoord0.xy;\n" +"#ifdef USEBLOOM\n" +" TexCoord2 = Attrib_TexCoord4.xy;\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"uniform sampler2D Texture_First;\n" +"#ifdef USEBLOOM\n" +"uniform sampler2D Texture_Second;\n" +"uniform mediump vec4 BloomColorSubtract;\n" +"#endif\n" +"#ifdef USEGAMMARAMPS\n" +"uniform sampler2D Texture_GammaRamps;\n" +"#endif\n" +"#ifdef USESATURATION\n" +"uniform mediump float Saturation;\n" +"#endif\n" +"#ifdef USEVIEWTINT\n" +"uniform mediump vec4 ViewTintColor;\n" +"#endif\n" +"//uncomment these if you want to use them:\n" +"uniform mediump vec4 UserVec1;\n" +"uniform mediump vec4 UserVec2;\n" +"// uniform mediump vec4 UserVec3;\n" +"// uniform mediump vec4 UserVec4;\n" +"// uniform highp float ClientTime;\n" +"uniform mediump vec2 PixelSize;\n" +"void main(void)\n" +"{\n" +" dp_FragColor = dp_texture2D(Texture_First, TexCoord1);\n" +"#ifdef USEBLOOM\n" +" dp_FragColor += max(vec4(0,0,0,0), dp_texture2D(Texture_Second, TexCoord2) - BloomColorSubtract);\n" +"#endif\n" +"#ifdef USEVIEWTINT\n" +" dp_FragColor = mix(dp_FragColor, ViewTintColor, ViewTintColor.a);\n" +"#endif\n" +"\n" +"#ifdef USEPOSTPROCESSING\n" +"// do r_glsl_dumpshader, edit glsl/default.glsl, and replace this by your own postprocessing if you want\n" +"// this code does a blur with the radius specified in the first component of r_glsl_postprocess_uservec1 and blends it using the second component\n" +" float sobel = 1.0;\n" +" // vec2 ts = textureSize(Texture_First, 0);\n" +" // vec2 px = vec2(1/ts.x, 1/ts.y);\n" +" vec2 px = PixelSize;\n" +" vec3 x1 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, px.y)).rgb;\n" +" vec3 x2 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, 0.0)).rgb;\n" +" vec3 x3 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x,-px.y)).rgb;\n" +" vec3 x4 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, px.y)).rgb;\n" +" vec3 x5 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, 0.0)).rgb;\n" +" vec3 x6 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x,-px.y)).rgb;\n" +" vec3 y1 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x,-px.y)).rgb;\n" +" vec3 y2 = dp_texture2D(Texture_First, TexCoord1 + vec2( 0.0,-px.y)).rgb;\n" +" vec3 y3 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x,-px.y)).rgb;\n" +" vec3 y4 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, px.y)).rgb;\n" +" vec3 y5 = dp_texture2D(Texture_First, TexCoord1 + vec2( 0.0, px.y)).rgb;\n" +" vec3 y6 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, px.y)).rgb;\n" +" float px1 = -1.0 * dot(vec3(0.3, 0.59, 0.11), x1);\n" +" float px2 = -2.0 * dot(vec3(0.3, 0.59, 0.11), x2);\n" +" float px3 = -1.0 * dot(vec3(0.3, 0.59, 0.11), x3);\n" +" float px4 = 1.0 * dot(vec3(0.3, 0.59, 0.11), x4);\n" +" float px5 = 2.0 * dot(vec3(0.3, 0.59, 0.11), x5);\n" +" float px6 = 1.0 * dot(vec3(0.3, 0.59, 0.11), x6);\n" +" float py1 = -1.0 * dot(vec3(0.3, 0.59, 0.11), y1);\n" +" float py2 = -2.0 * dot(vec3(0.3, 0.59, 0.11), y2);\n" +" float py3 = -1.0 * dot(vec3(0.3, 0.59, 0.11), y3);\n" +" float py4 = 1.0 * dot(vec3(0.3, 0.59, 0.11), y4);\n" +" float py5 = 2.0 * dot(vec3(0.3, 0.59, 0.11), y5);\n" +" float py6 = 1.0 * dot(vec3(0.3, 0.59, 0.11), y6);\n" +" sobel = 0.25 * abs(px1 + px2 + px3 + px4 + px5 + px6) + 0.25 * abs(py1 + py2 + py3 + py4 + py5 + py6);\n" +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.987688, -0.156434)) * UserVec1.y;\n" +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.156434, -0.891007)) * UserVec1.y;\n" +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2( 0.891007, -0.453990)) * UserVec1.y;\n" +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2( 0.707107, 0.707107)) * UserVec1.y;\n" +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.453990, 0.891007)) * UserVec1.y;\n" +" dp_FragColor /= (1.0 + 5.0 * UserVec1.y);\n" +" dp_FragColor.rgb = dp_FragColor.rgb * (1.0 + UserVec2.x) + vec3(max(0.0, sobel - UserVec2.z))*UserVec2.y;\n" +"#endif\n" +"\n" +"#ifdef USESATURATION\n" +" //apply saturation BEFORE gamma ramps, so v_glslgamma value does not matter\n" +" float y = dot(dp_FragColor.rgb, vec3(0.299, 0.587, 0.114));\n" +" // 'vampire sight' effect, wheres red is compensated\n" +" #ifdef SATURATION_REDCOMPENSATE\n" +" float rboost = max(0.0, (dp_FragColor.r - max(dp_FragColor.g, dp_FragColor.b))*(1.0 - Saturation));\n" +" dp_FragColor.rgb = mix(vec3(y), dp_FragColor.rgb, Saturation);\n" +" dp_FragColor.r += rboost;\n" +" #else\n" +" // normal desaturation\n" +" //dp_FragColor = vec3(y) + (dp_FragColor.rgb - vec3(y)) * Saturation;\n" +" dp_FragColor.rgb = mix(vec3(y), dp_FragColor.rgb, Saturation);\n" +" #endif\n" +"#endif\n" +"\n" +"#ifdef USEGAMMARAMPS\n" +" dp_FragColor.r = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.r, 0)).r;\n" +" dp_FragColor.g = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.g, 0)).g;\n" +" dp_FragColor.b = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.b, 0)).b;\n" +"#endif\n" +"}\n" +"#endif\n" +"#else // !MODE_POSTPROCESS\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_GENERIC\n" +"#ifdef USEDIFFUSE\n" +"dp_varying mediump vec2 TexCoord1;\n" +"#endif\n" +"#ifdef USESPECULAR\n" +"dp_varying mediump vec2 TexCoord2;\n" +"#endif\n" +"#ifdef VERTEX_SHADER\n" +"void main(void)\n" +"{\n" +" VertexColor = Attrib_Color;\n" +"#ifdef USEDIFFUSE\n" +" TexCoord1 = Attrib_TexCoord0.xy;\n" +"#endif\n" +"#ifdef USESPECULAR\n" +" TexCoord2 = Attrib_TexCoord1.xy;\n" +"#endif\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"#ifdef USEDIFFUSE\n" +"uniform sampler2D Texture_First;\n" +"#endif\n" +"#ifdef USESPECULAR\n" +"uniform sampler2D Texture_Second;\n" +"#endif\n" +"\n" +"void main(void)\n" +"{\n" +"#ifdef USEVIEWTINT\n" +" dp_FragColor = VertexColor;\n" +"#else\n" +" dp_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +"#endif\n" +"#ifdef USEDIFFUSE\n" +" dp_FragColor *= dp_texture2D(Texture_First, TexCoord1);\n" +"#endif\n" +"\n" +"#ifdef USESPECULAR\n" +" vec4 tex2 = dp_texture2D(Texture_Second, TexCoord2);\n" +"# ifdef USECOLORMAPPING\n" +" dp_FragColor *= tex2;\n" +"# endif\n" +"# ifdef USEGLOW\n" +" dp_FragColor += tex2;\n" +"# endif\n" +"# ifdef USEVERTEXTEXTUREBLEND\n" +" dp_FragColor = mix(dp_FragColor, tex2, tex2.a);\n" +"# endif\n" +"#endif\n" +"}\n" +"#endif\n" +"#else // !MODE_GENERIC\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_BLOOMBLUR\n" +"dp_varying mediump vec2 TexCoord;\n" +"#ifdef VERTEX_SHADER\n" +"void main(void)\n" +"{\n" +" VertexColor = Attrib_Color;\n" +" TexCoord = Attrib_TexCoord0.xy;\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"uniform sampler2D Texture_First;\n" +"uniform mediump vec4 BloomBlur_Parameters;\n" +"\n" +"void main(void)\n" +"{\n" +" int i;\n" +" vec2 tc = TexCoord;\n" +" vec3 color = dp_texture2D(Texture_First, tc).rgb;\n" +" tc += BloomBlur_Parameters.xy;\n" +" for (i = 1;i < SAMPLES;i++)\n" +" {\n" +" color += dp_texture2D(Texture_First, tc).rgb;\n" +" tc += BloomBlur_Parameters.xy;\n" +" }\n" +" dp_FragColor = vec4(color * BloomBlur_Parameters.z + vec3(BloomBlur_Parameters.w), 1);\n" +"}\n" +"#endif\n" +"#else // !MODE_BLOOMBLUR\n" +"#ifdef MODE_REFRACTION\n" +"dp_varying mediump vec2 TexCoord;\n" +"dp_varying highp vec4 ModelViewProjectionPosition;\n" +"uniform highp mat4 TexMatrix;\n" +"#ifdef VERTEX_SHADER\n" +"\n" +"void main(void)\n" +"{\n" +" TexCoord = vec2(TexMatrix * Attrib_TexCoord0);\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +" ModelViewProjectionPosition = gl_Position;\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"uniform sampler2D Texture_Normal;\n" +"uniform sampler2D Texture_Refraction;\n" +"\n" +"uniform mediump vec4 DistortScaleRefractReflect;\n" +"uniform mediump vec4 ScreenScaleRefractReflect;\n" +"uniform mediump vec4 ScreenCenterRefractReflect;\n" +"uniform mediump vec4 RefractColor;\n" +"uniform mediump vec4 ReflectColor;\n" +"uniform highp float ClientTime;\n" +"#ifdef USENORMALMAPSCROLLBLEND\n" +"uniform highp vec2 NormalmapScrollBlend;\n" +"#endif\n" +"\n" +"void main(void)\n" +"{\n" +" vec2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n" +" //vec2 ScreenTexCoord = (ModelViewProjectionPosition.xy + normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5)).xy * DistortScaleRefractReflect.xy * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n" +" vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n" +" #ifdef USENORMALMAPSCROLLBLEND\n" +" vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n" +" normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n" +" vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(myhalf3(normal))).xy * DistortScaleRefractReflect.xy;\n" +" #else\n" +" vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(myhalf3(dp_texture2D(Texture_Normal, TexCoord)) - myhalf3(0.5))).xy * DistortScaleRefractReflect.xy;\n" +" #endif\n" +" // FIXME temporary hack to detect the case that the reflection\n" +" // gets blackened at edges due to leaving the area that contains actual\n" +" // content.\n" +" // Remove this 'ack once we have a better way to stop this thing from\n" +" // 'appening.\n" +" float f = min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(0.01, -0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(-0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(-0.01, -0.01)).rgb) / 0.05);\n" +" ScreenTexCoord = mix(SafeScreenTexCoord, ScreenTexCoord, f);\n" +" dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * RefractColor;\n" +"}\n" +"#endif\n" +"#else // !MODE_REFRACTION\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_WATER\n" +"dp_varying mediump vec2 TexCoord;\n" +"dp_varying highp vec3 EyeVector;\n" +"dp_varying highp vec4 ModelViewProjectionPosition;\n" +"#ifdef VERTEX_SHADER\n" +"uniform highp vec3 EyePosition;\n" +"uniform highp mat4 TexMatrix;\n" +"\n" +"void main(void)\n" +"{\n" +" TexCoord = vec2(TexMatrix * Attrib_TexCoord0);\n" +" vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n" +" EyeVector.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n" +" EyeVector.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n" +" EyeVector.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +" ModelViewProjectionPosition = gl_Position;\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"uniform sampler2D Texture_Normal;\n" +"uniform sampler2D Texture_Refraction;\n" +"uniform sampler2D Texture_Reflection;\n" +"\n" +"uniform mediump vec4 DistortScaleRefractReflect;\n" +"uniform mediump vec4 ScreenScaleRefractReflect;\n" +"uniform mediump vec4 ScreenCenterRefractReflect;\n" +"uniform mediump vec4 RefractColor;\n" +"uniform mediump vec4 ReflectColor;\n" +"uniform mediump float ReflectFactor;\n" +"uniform mediump float ReflectOffset;\n" +"uniform highp float ClientTime;\n" +"#ifdef USENORMALMAPSCROLLBLEND\n" +"uniform highp vec2 NormalmapScrollBlend;\n" +"#endif\n" +"\n" +"void main(void)\n" +"{\n" +" vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n" +" //vec4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" +" vec4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" +" //SafeScreenTexCoord = gl_FragCoord.xyxy * vec4(1.0 / 1920.0, 1.0 / 1200.0, 1.0 / 1920.0, 1.0 / 1200.0);\n" +" // slight water animation via 2 layer scrolling (todo: tweak)\n" +" #ifdef USENORMALMAPSCROLLBLEND\n" +" vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n" +" normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n" +" vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(normal) + vec3(0.15)).xyxy * DistortScaleRefractReflect;\n" +" #else\n" +" vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5))).xyxy * DistortScaleRefractReflect;\n" +" #endif\n" +" // FIXME temporary hack to detect the case that the reflection\n" +" // gets blackened at edges due to leaving the area that contains actual\n" +" // content.\n" +" // Remove this 'ack once we have a better way to stop this thing from\n" +" // 'appening.\n" +" float f = min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(0.005, 0.01)).rgb) / 0.002);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(0.005, -0.01)).rgb) / 0.002);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(-0.005, 0.01)).rgb) / 0.002);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(-0.005, -0.01)).rgb) / 0.002);\n" +" ScreenTexCoord.xy = mix(SafeScreenTexCoord.xy, ScreenTexCoord.xy, f);\n" +" f = min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(0.005, 0.005)).rgb) / 0.002);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(0.005, -0.005)).rgb) / 0.002);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(-0.005, 0.005)).rgb) / 0.002);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(-0.005, -0.005)).rgb) / 0.002);\n" +" ScreenTexCoord.zw = mix(SafeScreenTexCoord.zw, ScreenTexCoord.zw, f);\n" +" float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * ReflectFactor + ReflectOffset;\n" +" dp_FragColor = mix(vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * RefractColor, vec4(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n" +"}\n" +"#endif\n" +"#else // !MODE_WATER\n" +"\n" +"\n" +"\n" +"\n" +"// common definitions between vertex shader and fragment shader:\n" +"\n" +"dp_varying mediump vec4 TexCoordSurfaceLightmap;\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"dp_varying mediump vec2 TexCoord2;\n" +"#endif\n" +"\n" +"#ifdef MODE_LIGHTSOURCE\n" +"dp_varying mediump vec3 CubeVector;\n" +"#endif\n" +"\n" +"#if (defined(MODE_LIGHTSOURCE) || defined(MODE_LIGHTDIRECTION)) && defined(USEDIFFUSE)\n" +"dp_varying mediump vec3 LightVector;\n" +"#endif\n" +"\n" +"#ifdef USEEYEVECTOR\n" +"dp_varying highp vec4 EyeVectorFogDepth;\n" +"#endif\n" +"\n" +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE) || defined(USEBOUNCEGRIDDIRECTIONAL)\n" +"dp_varying highp vec4 VectorS; // direction of S texcoord (sometimes crudely called tangent)\n" +"dp_varying highp vec4 VectorT; // direction of T texcoord (sometimes crudely called binormal)\n" +"dp_varying highp vec4 VectorR; // direction of R texcoord (surface normal)\n" +"#else\n" +"# ifdef USEFOG\n" +"dp_varying highp vec3 EyeVectorModelSpace;\n" +"# endif\n" +"#endif\n" +"\n" +"#ifdef USEREFLECTION\n" +"dp_varying highp vec4 ModelViewProjectionPosition;\n" +"#endif\n" +"#ifdef MODE_DEFERREDLIGHTSOURCE\n" +"uniform highp vec3 LightPosition;\n" +"dp_varying highp vec4 ModelViewPosition;\n" +"#endif\n" +"\n" +"#ifdef MODE_LIGHTSOURCE\n" +"uniform highp vec3 LightPosition;\n" +"#endif\n" +"uniform highp vec3 EyePosition;\n" +"#ifdef MODE_LIGHTDIRECTION\n" +"uniform highp vec3 LightDir;\n" +"#endif\n" +"uniform highp vec4 FogPlane;\n" +"\n" +"#ifdef USESHADOWMAPORTHO\n" +"dp_varying highp vec3 ShadowMapTC;\n" +"#endif\n" +"\n" +"#ifdef USEBOUNCEGRID\n" +"dp_varying highp vec3 BounceGridTexCoord;\n" +"#endif\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"// TODO: get rid of tangentt (texcoord2) and use a crossproduct to regenerate it from tangents (texcoord1) and normal (texcoord3), this would require sending a 4 component texcoord1 with W as 1 or -1 according to which side the texcoord2 should be on\n" +"\n" +"// fragment shader specific:\n" +"#ifdef FRAGMENT_SHADER\n" +"\n" +"uniform sampler2D Texture_Normal;\n" +"uniform sampler2D Texture_Color;\n" +"uniform sampler2D Texture_Gloss;\n" +"#ifdef USEGLOW\n" +"uniform sampler2D Texture_Glow;\n" +"#endif\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"uniform sampler2D Texture_SecondaryNormal;\n" +"uniform sampler2D Texture_SecondaryColor;\n" +"uniform sampler2D Texture_SecondaryGloss;\n" +"#ifdef USEGLOW\n" +"uniform sampler2D Texture_SecondaryGlow;\n" +"#endif\n" +"#endif\n" +"#ifdef USECOLORMAPPING\n" +"uniform sampler2D Texture_Pants;\n" +"uniform sampler2D Texture_Shirt;\n" +"#endif\n" +"#ifdef USEFOG\n" +"#ifdef USEFOGHEIGHTTEXTURE\n" +"uniform sampler2D Texture_FogHeightTexture;\n" +"#endif\n" +"uniform sampler2D Texture_FogMask;\n" +"#endif\n" +"#ifdef USELIGHTMAP\n" +"uniform sampler2D Texture_Lightmap;\n" +"#endif\n" +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n" +"uniform sampler2D Texture_Deluxemap;\n" +"#endif\n" +"#ifdef USEREFLECTION\n" +"uniform sampler2D Texture_Reflection;\n" +"#endif\n" +"\n" +"#ifdef MODE_DEFERREDLIGHTSOURCE\n" +"uniform sampler2D Texture_ScreenDepth;\n" +"uniform sampler2D Texture_ScreenNormalMap;\n" +"#endif\n" +"#ifdef USEDEFERREDLIGHTMAP\n" +"uniform sampler2D Texture_ScreenDiffuse;\n" +"uniform sampler2D Texture_ScreenSpecular;\n" +"#endif\n" +"\n" +"uniform mediump vec3 Color_Pants;\n" +"uniform mediump vec3 Color_Shirt;\n" +"uniform mediump vec3 FogColor;\n" +"\n" +"#ifdef USEFOG\n" +"uniform highp float FogRangeRecip;\n" +"uniform highp float FogPlaneViewDist;\n" +"uniform highp float FogHeightFade;\n" +"vec3 FogVertex(vec4 surfacecolor)\n" +"{\n" +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE)\n" +" vec3 EyeVectorModelSpace = vec3(VectorS.w, VectorT.w, VectorR.w);\n" +"#endif\n" +" float FogPlaneVertexDist = EyeVectorFogDepth.w;\n" +" float fogfrac;\n" +" vec3 fc = FogColor;\n" +"#ifdef USEFOGALPHAHACK\n" +" fc *= surfacecolor.a;\n" +"#endif\n" +"#ifdef USEFOGHEIGHTTEXTURE\n" +" vec4 fogheightpixel = dp_texture2D(Texture_FogHeightTexture, vec2(1,1) + vec2(FogPlaneVertexDist, FogPlaneViewDist) * (-2.0 * FogHeightFade));\n" +" fogfrac = fogheightpixel.a;\n" +" return mix(fogheightpixel.rgb * fc, surfacecolor.rgb, dp_texture2D(Texture_FogMask, myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n" +"#else\n" +"# ifdef USEFOGOUTSIDE\n" +" fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n" +"# else\n" +" fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0, FogPlaneVertexDist)) * min(1.0, (min(0.0, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade);\n" +"# endif\n" +" return mix(fc, surfacecolor.rgb, dp_texture2D(Texture_FogMask, myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef USEOFFSETMAPPING\n" +"uniform mediump vec4 OffsetMapping_ScaleSteps;\n" +"vec2 OffsetMapping(vec2 TexCoord, vec2 dPdx, vec2 dPdy)\n" +"{\n" +" float i;\n" +"#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n" +" float f;\n" +" // 14 sample relief mapping: linear search and then binary search\n" +" // this basically steps forward a small amount repeatedly until it finds\n" +" // itself inside solid, then jitters forward and back using decreasing\n" +" // amounts to find the impact\n" +" //vec3 OffsetVector = vec3(EyeVectorFogDepth.xy * ((1.0 / EyeVectorFogDepth.z) * OffsetMapping_ScaleSteps.x) * vec2(-1, 1), -1);\n" +" //vec3 OffsetVector = vec3(normalize(EyeVectorFogDepth.xy) * OffsetMapping_ScaleSteps.x * vec2(-1, 1), -1);\n" +" vec3 OffsetVector = vec3(normalize(EyeVectorFogDepth.xyz).xy * OffsetMapping_ScaleSteps.x * vec2(-1, 1), -1);\n" +" vec3 RT = vec3(TexCoord, 1);\n" +" OffsetVector *= OffsetMapping_ScaleSteps.z;\n" +" for(i = 1.0; i < OffsetMapping_ScaleSteps.y; ++i)\n" +" RT += OffsetVector * step(dp_textureGrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z);\n" +" for(i = 0.0, f = 1.0; i < OffsetMapping_ScaleSteps.w; ++i, f *= 0.5)\n" +" RT += OffsetVector * (step(dp_textureGrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z) * f - 0.5 * f);\n" +" return RT.xy;\n" +"#else\n" +" // 2 sample offset mapping (only 2 samples because of ATI Radeon 9500-9800/X300 limits)\n" +" //vec2 OffsetVector = vec2(EyeVectorFogDepth.xy * ((1.0 / EyeVectorFogDepth.z) * OffsetMapping_ScaleSteps.x) * vec2(-1, 1));\n" +" //vec2 OffsetVector = vec2(normalize(EyeVectorFogDepth.xy) * OffsetMapping_ScaleSteps.x * vec2(-1, 1));\n" +" vec2 OffsetVector = vec2(normalize(EyeVectorFogDepth.xyz).xy * OffsetMapping_ScaleSteps.x * vec2(-1, 1));\n" +" OffsetVector *= OffsetMapping_ScaleSteps.z;\n" +" for(i = 0.0; i < OffsetMapping_ScaleSteps.y; ++i)\n" +" TexCoord += OffsetVector * (1.0 - dp_textureGrad(Texture_Normal, TexCoord, dPdx, dPdy).a);\n" +" return TexCoord;\n" +"#endif\n" +"}\n" +"#endif // USEOFFSETMAPPING\n" +"\n" +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE)\n" +"uniform sampler2D Texture_Attenuation;\n" +"uniform samplerCube Texture_Cube;\n" +"#endif\n" +"\n" +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n" +"\n" +"#ifdef USESHADOWMAP2D\n" +"# ifdef USESHADOWSAMPLER\n" +"uniform sampler2DShadow Texture_ShadowMap2D;\n" +"# else\n" +"uniform sampler2D Texture_ShadowMap2D;\n" +"# endif\n" +"#endif\n" +"\n" +"#ifdef USESHADOWMAPVSDCT\n" +"uniform samplerCube Texture_CubeProjection;\n" +"#endif\n" +"\n" +"#if defined(USESHADOWMAP2D)\n" +"uniform mediump vec2 ShadowMap_TextureScale;\n" +"uniform mediump vec4 ShadowMap_Parameters;\n" +"#endif\n" +"\n" +"#if defined(USESHADOWMAP2D)\n" +"# ifdef USESHADOWMAPORTHO\n" +"# define GetShadowMapTC2D(dir) (min(dir, ShadowMap_Parameters.xyz))\n" +"# else\n" +"# ifdef USESHADOWMAPVSDCT\n" +"vec3 GetShadowMapTC2D(vec3 dir)\n" +"{\n" +" vec3 adir = abs(dir);\n" +" vec2 aparams = ShadowMap_Parameters.xy / max(max(adir.x, adir.y), adir.z);\n" +" vec4 proj = dp_textureCube(Texture_CubeProjection, dir);\n" +" return vec3(mix(dir.xy, dir.zz, proj.xy) * aparams.x + proj.zw * ShadowMap_Parameters.z, aparams.y + ShadowMap_Parameters.w);\n" +"}\n" +"# else\n" +"vec3 GetShadowMapTC2D(vec3 dir)\n" +"{\n" +" vec3 adir = abs(dir);\n" +" float ma = adir.z;\n" +" vec4 proj = vec4(dir, 2.5);\n" +" if (adir.x > ma) { ma = adir.x; proj = vec4(dir.zyx, 0.5); }\n" +" if (adir.y > ma) { ma = adir.y; proj = vec4(dir.xzy, 1.5); }\n" +" vec2 aparams = ShadowMap_Parameters.xy / ma;\n" +" return vec3(proj.xy * aparams.x + vec2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, aparams.y + ShadowMap_Parameters.w);\n" +"}\n" +"# endif\n" +"# endif\n" +"#endif // defined(USESHADOWMAP2D)\n" +"\n" +"# ifdef USESHADOWMAP2D\n" +"float ShadowMapCompare(vec3 dir)\n" +"{\n" +" vec3 shadowmaptc = GetShadowMapTC2D(dir);\n" +" float f;\n" +"\n" +"# ifdef USESHADOWSAMPLER\n" +"# ifdef USESHADOWMAPPCF\n" +"# define texval(x, y) dp_shadow2D(Texture_ShadowMap2D, vec3(center + vec2(x, y)*ShadowMap_TextureScale, shadowmaptc.z)) \n" +" vec2 center = shadowmaptc.xy*ShadowMap_TextureScale;\n" +" f = dot(vec4(0.25), vec4(texval(-0.4, 1.0), texval(-1.0, -0.4), texval(0.4, -1.0), texval(1.0, 0.4)));\n" +"# else\n" +" f = dp_shadow2D(Texture_ShadowMap2D, vec3(shadowmaptc.xy*ShadowMap_TextureScale, shadowmaptc.z));\n" +"# endif\n" +"# else\n" +"# ifdef USESHADOWMAPPCF\n" +"# if defined(GL_ARB_texture_gather) || defined(GL_AMD_texture_texture4)\n" +"# ifdef GL_ARB_texture_gather\n" +"# define texval(x, y) textureGatherOffset(Texture_ShadowMap2D, center, ivec2(x, y))\n" +"# else\n" +"# define texval(x, y) texture4(Texture_ShadowMap2D, center + vec2(x, y)*ShadowMap_TextureScale)\n" +"# endif\n" +" vec2 offset = fract(shadowmaptc.xy - 0.5), center = (shadowmaptc.xy - offset)*ShadowMap_TextureScale;\n" +"# if USESHADOWMAPPCF > 1\n" +" vec4 group1 = step(shadowmaptc.z, texval(-2.0, -2.0));\n" +" vec4 group2 = step(shadowmaptc.z, texval( 0.0, -2.0));\n" +" vec4 group3 = step(shadowmaptc.z, texval( 2.0, -2.0));\n" +" vec4 group4 = step(shadowmaptc.z, texval(-2.0, 0.0));\n" +" vec4 group5 = step(shadowmaptc.z, texval( 0.0, 0.0));\n" +" vec4 group6 = step(shadowmaptc.z, texval( 2.0, 0.0));\n" +" vec4 group7 = step(shadowmaptc.z, texval(-2.0, 2.0));\n" +" vec4 group8 = step(shadowmaptc.z, texval( 0.0, 2.0));\n" +" vec4 group9 = step(shadowmaptc.z, texval( 2.0, 2.0));\n" +" vec4 locols = vec4(group1.ab, group3.ab);\n" +" vec4 hicols = vec4(group7.rg, group9.rg);\n" +" locols.yz += group2.ab;\n" +" hicols.yz += group8.rg;\n" +" vec4 midcols = vec4(group1.rg, group3.rg) + vec4(group7.ab, group9.ab) +\n" +" vec4(group4.rg, group6.rg) + vec4(group4.ab, group6.ab) +\n" +" mix(locols, hicols, offset.y);\n" +" vec4 cols = group5 + vec4(group2.rg, group8.ab);\n" +" cols.xyz += mix(midcols.xyz, midcols.yzw, offset.x);\n" +" f = dot(cols, vec4(1.0/25.0));\n" +"# else\n" +" vec4 group1 = step(shadowmaptc.z, texval(-1.0, -1.0));\n" +" vec4 group2 = step(shadowmaptc.z, texval( 1.0, -1.0));\n" +" vec4 group3 = step(shadowmaptc.z, texval(-1.0, 1.0));\n" +" vec4 group4 = step(shadowmaptc.z, texval( 1.0, 1.0));\n" +" vec4 cols = vec4(group1.rg, group2.rg) + vec4(group3.ab, group4.ab) +\n" +" mix(vec4(group1.ab, group2.ab), vec4(group3.rg, group4.rg), offset.y);\n" +" f = dot(mix(cols.xyz, cols.yzw, offset.x), vec3(1.0/9.0));\n" +"# endif\n" +"# else\n" +"# ifdef GL_EXT_gpu_shader4\n" +"# define texval(x, y) texture2DOffset(Texture_ShadowMap2D, center, ivec2(x, y)).r\n" +"# else\n" +"# define texval(x, y) dp_texture2D(Texture_ShadowMap2D, center + vec2(x, y)*ShadowMap_TextureScale).r \n" +"# endif\n" +"# if USESHADOWMAPPCF > 1\n" +" vec2 center = shadowmaptc.xy - 0.5, offset = fract(center);\n" +" center *= ShadowMap_TextureScale;\n" +" vec4 row1 = step(shadowmaptc.z, vec4(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0), texval( 2.0, -1.0)));\n" +" vec4 row2 = step(shadowmaptc.z, vec4(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0), texval( 2.0, 0.0)));\n" +" vec4 row3 = step(shadowmaptc.z, vec4(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0), texval( 2.0, 1.0)));\n" +" vec4 row4 = step(shadowmaptc.z, vec4(texval(-1.0, 2.0), texval( 0.0, 2.0), texval( 1.0, 2.0), texval( 2.0, 2.0)));\n" +" vec4 cols = row2 + row3 + mix(row1, row4, offset.y);\n" +" f = dot(mix(cols.xyz, cols.yzw, offset.x), vec3(1.0/9.0));\n" +"# else\n" +" vec2 center = shadowmaptc.xy*ShadowMap_TextureScale, offset = fract(shadowmaptc.xy);\n" +" vec3 row1 = step(shadowmaptc.z, vec3(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0)));\n" +" vec3 row2 = step(shadowmaptc.z, vec3(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0)));\n" +" vec3 row3 = step(shadowmaptc.z, vec3(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0)));\n" +" vec3 cols = row2 + mix(row1, row3, offset.y);\n" +" f = dot(mix(cols.xy, cols.yz, offset.x), vec2(0.25));\n" +"# endif\n" +"# endif\n" +"# else\n" +" f = step(shadowmaptc.z, dp_texture2D(Texture_ShadowMap2D, shadowmaptc.xy*ShadowMap_TextureScale).r);\n" +"# endif\n" +"# endif\n" +"# ifdef USESHADOWMAPORTHO\n" +" return mix(ShadowMap_Parameters.w, 1.0, f);\n" +"# else\n" +" return f;\n" +"# endif\n" +"}\n" +"# endif\n" +"#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n" +"#endif // FRAGMENT_SHADER\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_DEFERREDGEOMETRY\n" +"#ifdef VERTEX_SHADER\n" +"uniform highp mat4 TexMatrix;\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"uniform highp mat4 BackgroundTexMatrix;\n" +"#endif\n" +"uniform highp mat4 ModelViewMatrix;\n" +"void main(void)\n" +"{\n" +" TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, 0.0, 0.0);\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" VertexColor = Attrib_Color;\n" +" TexCoord2 = vec2(BackgroundTexMatrix * Attrib_TexCoord0);\n" +"#endif\n" +"\n" +" // transform unnormalized eye direction into tangent space\n" +"#ifdef USEOFFSETMAPPING\n" +" vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n" +" EyeVectorFogDepth.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n" +" EyeVectorFogDepth.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n" +" EyeVectorFogDepth.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n" +" EyeVectorFogDepth.w = 0.0;\n" +"#endif\n" +"\n" +" VectorS = (ModelViewMatrix * vec4(Attrib_TexCoord1.xyz, 0));\n" +" VectorT = (ModelViewMatrix * vec4(Attrib_TexCoord2.xyz, 0));\n" +" VectorR = (ModelViewMatrix * vec4(Attrib_TexCoord3.xyz, 0));\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif // VERTEX_SHADER\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main(void)\n" +"{\n" +"#ifdef USEOFFSETMAPPING\n" +" // apply offsetmapping\n" +" vec2 dPdx = dp_offsetmapping_dFdx(TexCoordSurfaceLightmap.xy);\n" +" vec2 dPdy = dp_offsetmapping_dFdy(TexCoordSurfaceLightmap.xy);\n" +" vec2 TexCoordOffset = OffsetMapping(TexCoordSurfaceLightmap.xy, dPdx, dPdy);\n" +"# define offsetMappedTexture2D(t) dp_textureGrad(t, TexCoordOffset, dPdx, dPdy)\n" +"#else\n" +"# define offsetMappedTexture2D(t) dp_texture2D(t, TexCoordSurfaceLightmap.xy)\n" +"#endif\n" +"\n" +"#ifdef USEALPHAKILL\n" +" if (offsetMappedTexture2D(Texture_Color).a < 0.5)\n" +" discard;\n" +"#endif\n" +"\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" float alpha = offsetMappedTexture2D(Texture_Color).a;\n" +" float terrainblend = clamp(float(VertexColor.a) * alpha * 2.0 - 0.5, float(0.0), float(1.0));\n" +" //float terrainblend = min(float(VertexColor.a) * alpha * 2.0, float(1.0));\n" +" //float terrainblend = float(VertexColor.a) * alpha > 0.5;\n" +"#endif\n" +"\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" vec3 surfacenormal = mix(vec3(dp_texture2D(Texture_SecondaryNormal, TexCoord2)), vec3(offsetMappedTexture2D(Texture_Normal)), terrainblend) - vec3(0.5, 0.5, 0.5);\n" +" float a = mix(dp_texture2D(Texture_SecondaryGloss, TexCoord2).a, offsetMappedTexture2D(Texture_Gloss).a, terrainblend);\n" +"#else\n" +" vec3 surfacenormal = vec3(offsetMappedTexture2D(Texture_Normal)) - vec3(0.5, 0.5, 0.5);\n" +" float a = offsetMappedTexture2D(Texture_Gloss).a;\n" +"#endif\n" +"\n" +" dp_FragColor = vec4(normalize(surfacenormal.x * VectorS.xyz + surfacenormal.y * VectorT.xyz + surfacenormal.z * VectorR.xyz) * 0.5 + vec3(0.5, 0.5, 0.5), a);\n" +"}\n" +"#endif // FRAGMENT_SHADER\n" +"#else // !MODE_DEFERREDGEOMETRY\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_DEFERREDLIGHTSOURCE\n" +"#ifdef VERTEX_SHADER\n" +"uniform highp mat4 ModelViewMatrix;\n" +"void main(void)\n" +"{\n" +" ModelViewPosition = ModelViewMatrix * Attrib_Position;\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +"}\n" +"#endif // VERTEX_SHADER\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"uniform highp mat4 ViewToLight;\n" +"// ScreenToDepth = vec2(Far / (Far - Near), Far * Near / (Near - Far));\n" +"uniform highp vec2 ScreenToDepth;\n" +"uniform myhalf3 DeferredColor_Ambient;\n" +"uniform myhalf3 DeferredColor_Diffuse;\n" +"#ifdef USESPECULAR\n" +"uniform myhalf3 DeferredColor_Specular;\n" +"uniform myhalf SpecularPower;\n" +"#endif\n" +"uniform myhalf2 PixelToScreenTexCoord;\n" +"void main(void)\n" +"{\n" +" // calculate viewspace pixel position\n" +" vec2 ScreenTexCoord = gl_FragCoord.xy * PixelToScreenTexCoord;\n" +" vec3 position;\n" +" position.z = ScreenToDepth.y / (dp_texture2D(Texture_ScreenDepth, ScreenTexCoord).r + ScreenToDepth.x);\n" +" position.xy = ModelViewPosition.xy * (position.z / ModelViewPosition.z);\n" +" // decode viewspace pixel normal\n" +" myhalf4 normalmap = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord);\n" +" myhalf3 surfacenormal = normalize(normalmap.rgb - myhalf3(0.5,0.5,0.5));\n" +" // surfacenormal = pixel normal in viewspace\n" +" // LightVector = pixel to light in viewspace\n" +" // CubeVector = position in lightspace\n" +" // eyevector = pixel to view in viewspace\n" +" vec3 CubeVector = vec3(ViewToLight * vec4(position,1));\n" +" myhalf fade = myhalf(dp_texture2D(Texture_Attenuation, vec2(length(CubeVector), 0.0)));\n" +"#ifdef USEDIFFUSE\n" +" // calculate diffuse shading\n" +" myhalf3 lightnormal = myhalf3(normalize(LightPosition - position));\n" +" myhalf diffuse = myhalf(max(float(dot(surfacenormal, lightnormal)), 0.0));\n" +"#endif\n" +"#ifdef USESPECULAR\n" +" // calculate directional shading\n" +" vec3 eyevector = position * -1.0;\n" +"# ifdef USEEXACTSPECULARMATH\n" +" myhalf specular = pow(myhalf(max(float(dot(reflect(lightnormal, surfacenormal), normalize(eyevector)))*-1.0, 0.0)), SpecularPower * normalmap.a);\n" +"# else\n" +" myhalf3 specularnormal = normalize(lightnormal + myhalf3(normalize(eyevector)));\n" +" myhalf specular = pow(myhalf(max(float(dot(surfacenormal, specularnormal)), 0.0)), SpecularPower * normalmap.a);\n" +"# endif\n" +"#endif\n" +"\n" +"#if defined(USESHADOWMAP2D)\n" +" fade *= ShadowMapCompare(CubeVector);\n" +"#endif\n" +"\n" +"#ifdef USESPECULAR\n" +" gl_FragData[0] = vec4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n" +" gl_FragData[1] = vec4(DeferredColor_Specular * (specular * fade), 1.0);\n" +"# ifdef USECUBEFILTER\n" +" vec3 cubecolor = dp_textureCube(Texture_Cube, CubeVector).rgb;\n" +" gl_FragData[0].rgb *= cubecolor;\n" +" gl_FragData[1].rgb *= cubecolor;\n" +"# endif\n" +"#else\n" +"# ifdef USEDIFFUSE\n" +" gl_FragColor = vec4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n" +"# else\n" +" gl_FragColor = vec4(DeferredColor_Ambient * fade, 1.0);\n" +"# endif\n" +"# ifdef USECUBEFILTER\n" +" vec3 cubecolor = dp_textureCube(Texture_Cube, CubeVector).rgb;\n" +" gl_FragColor.rgb *= cubecolor;\n" +"# endif\n" +"#endif\n" +" \n" +"}\n" +"#endif // FRAGMENT_SHADER\n" +"#else // !MODE_DEFERREDLIGHTSOURCE\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef VERTEX_SHADER\n" +"uniform highp mat4 TexMatrix;\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"uniform highp mat4 BackgroundTexMatrix;\n" +"#endif\n" +"#ifdef MODE_LIGHTSOURCE\n" +"uniform highp mat4 ModelToLight;\n" +"#endif\n" +"#ifdef USESHADOWMAPORTHO\n" +"uniform highp mat4 ShadowMapMatrix;\n" +"#endif\n" +"#ifdef USEBOUNCEGRID\n" +"uniform highp mat4 BounceGridMatrix;\n" +"#endif\n" +"void main(void)\n" +"{\n" +"#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND)\n" +" VertexColor = Attrib_Color;\n" +"#endif\n" +" // copy the surface texcoord\n" +"#ifdef USELIGHTMAP\n" +" TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, Attrib_TexCoord4.xy);\n" +"#else\n" +" TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, 0.0, 0.0);\n" +"#endif\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" TexCoord2 = vec2(BackgroundTexMatrix * Attrib_TexCoord0);\n" +"#endif\n" +"\n" +"#ifdef USEBOUNCEGRID\n" +" BounceGridTexCoord = vec3(BounceGridMatrix * Attrib_Position);\n" +"#ifdef USEBOUNCEGRIDDIRECTIONAL\n" +" BounceGridTexCoord.z *= 0.125;\n" +"#endif\n" +"#endif\n" +"\n" +"#ifdef MODE_LIGHTSOURCE\n" +" // transform vertex position into light attenuation/cubemap space\n" +" // (-1 to +1 across the light box)\n" +" CubeVector = vec3(ModelToLight * Attrib_Position);\n" +"\n" +"# ifdef USEDIFFUSE\n" +" // transform unnormalized light direction into tangent space\n" +" // (we use unnormalized to ensure that it interpolates correctly and then\n" +" // normalize it per pixel)\n" +" vec3 lightminusvertex = LightPosition - Attrib_Position.xyz;\n" +" LightVector.x = dot(lightminusvertex, Attrib_TexCoord1.xyz);\n" +" LightVector.y = dot(lightminusvertex, Attrib_TexCoord2.xyz);\n" +" LightVector.z = dot(lightminusvertex, Attrib_TexCoord3.xyz);\n" +"# endif\n" +"#endif\n" +"\n" +"#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE)\n" +" LightVector.x = dot(LightDir, Attrib_TexCoord1.xyz);\n" +" LightVector.y = dot(LightDir, Attrib_TexCoord2.xyz);\n" +" LightVector.z = dot(LightDir, Attrib_TexCoord3.xyz);\n" +"#endif\n" +"\n" +" // transform unnormalized eye direction into tangent space\n" +"#ifdef USEEYEVECTOR\n" +" vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n" +" EyeVectorFogDepth.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n" +" EyeVectorFogDepth.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n" +" EyeVectorFogDepth.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n" +"#ifdef USEFOG\n" +" EyeVectorFogDepth.w = dot(FogPlane, Attrib_Position);\n" +"#else\n" +" EyeVectorFogDepth.w = 0.0;\n" +"#endif\n" +"#endif\n" +"\n" +"\n" +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(USEREFLECTCUBE) || defined(USEBOUNCEGRIDDIRECTIONAL)\n" +"# ifdef USEFOG\n" +" VectorS = vec4(Attrib_TexCoord1.xyz, EyePosition.x - Attrib_Position.x);\n" +" VectorT = vec4(Attrib_TexCoord2.xyz, EyePosition.y - Attrib_Position.y);\n" +" VectorR = vec4(Attrib_TexCoord3.xyz, EyePosition.z - Attrib_Position.z);\n" +"# else\n" +" VectorS = vec4(Attrib_TexCoord1, 0);\n" +" VectorT = vec4(Attrib_TexCoord2, 0);\n" +" VectorR = vec4(Attrib_TexCoord3, 0);\n" +"# endif\n" +"#else\n" +"# ifdef USEFOG\n" +" EyeVectorModelSpace = EyePosition - Attrib_Position.xyz;\n" +"# endif\n" +"#endif\n" +"\n" +" // transform vertex to clipspace (post-projection, but before perspective divide by W occurs)\n" +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n" +"\n" +"#ifdef USESHADOWMAPORTHO\n" +" ShadowMapTC = vec3(ShadowMapMatrix * gl_Position);\n" +"#endif\n" +"\n" +"#ifdef USEREFLECTION\n" +" ModelViewProjectionPosition = gl_Position;\n" +"#endif\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif // VERTEX_SHADER\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"#ifdef USEDEFERREDLIGHTMAP\n" +"uniform myhalf2 PixelToScreenTexCoord;\n" +"uniform myhalf3 DeferredMod_Diffuse;\n" +"uniform myhalf3 DeferredMod_Specular;\n" +"#endif\n" +"uniform myhalf3 Color_Ambient;\n" +"uniform myhalf3 Color_Diffuse;\n" +"uniform myhalf3 Color_Specular;\n" +"uniform myhalf SpecularPower;\n" +"#ifdef USEGLOW\n" +"uniform myhalf3 Color_Glow;\n" +"#endif\n" +"uniform myhalf Alpha;\n" +"#ifdef USEREFLECTION\n" +"uniform mediump vec4 DistortScaleRefractReflect;\n" +"uniform mediump vec4 ScreenScaleRefractReflect;\n" +"uniform mediump vec4 ScreenCenterRefractReflect;\n" +"uniform mediump vec4 ReflectColor;\n" +"#endif\n" +"#ifdef USEREFLECTCUBE\n" +"uniform highp mat4 ModelToReflectCube;\n" +"uniform sampler2D Texture_ReflectMask;\n" +"uniform samplerCube Texture_ReflectCube;\n" +"#endif\n" +"#ifdef MODE_LIGHTDIRECTION\n" +"uniform myhalf3 LightColor;\n" +"#endif\n" +"#ifdef MODE_LIGHTSOURCE\n" +"uniform myhalf3 LightColor;\n" +"#endif\n" +"#ifdef USEBOUNCEGRID\n" +"uniform sampler3D Texture_BounceGrid;\n" +"uniform float BounceGridIntensity;\n" +"uniform highp mat4 BounceGridMatrix;\n" +"#endif\n" +"uniform highp float ClientTime;\n" +"#ifdef USENORMALMAPSCROLLBLEND\n" +"uniform highp vec2 NormalmapScrollBlend;\n" +"#endif\n" +"void main(void)\n" +"{\n" +"#ifdef USEOFFSETMAPPING\n" +" // apply offsetmapping\n" +" vec2 dPdx = dp_offsetmapping_dFdx(TexCoordSurfaceLightmap.xy);\n" +" vec2 dPdy = dp_offsetmapping_dFdy(TexCoordSurfaceLightmap.xy);\n" +" vec2 TexCoordOffset = OffsetMapping(TexCoordSurfaceLightmap.xy, dPdx, dPdy);\n" +"# define offsetMappedTexture2D(t) dp_textureGrad(t, TexCoordOffset, dPdx, dPdy)\n" +"# define TexCoord TexCoordOffset\n" +"#else\n" +"# define offsetMappedTexture2D(t) dp_texture2D(t, TexCoordSurfaceLightmap.xy)\n" +"# define TexCoord TexCoordSurfaceLightmap.xy\n" +"#endif\n" +"\n" +" // combine the diffuse textures (base, pants, shirt)\n" +" myhalf4 color = myhalf4(offsetMappedTexture2D(Texture_Color));\n" +"#ifdef USEALPHAKILL\n" +" if (color.a < 0.5)\n" +" discard;\n" +"#endif\n" +" color.a *= Alpha;\n" +"#ifdef USECOLORMAPPING\n" +" color.rgb += myhalf3(offsetMappedTexture2D(Texture_Pants)) * Color_Pants + myhalf3(offsetMappedTexture2D(Texture_Shirt)) * Color_Shirt;\n" +"#endif\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"#ifdef USEBOTHALPHAS\n" +" myhalf4 color2 = myhalf4(dp_texture2D(Texture_SecondaryColor, TexCoord2));\n" +" myhalf terrainblend = clamp(myhalf(VertexColor.a) * color.a, myhalf(1.0 - color2.a), myhalf(1.0));\n" +" color.rgb = mix(color2.rgb, color.rgb, terrainblend);\n" +"#else\n" +" myhalf terrainblend = clamp(myhalf(VertexColor.a) * color.a * 2.0 - 0.5, myhalf(0.0), myhalf(1.0));\n" +" //myhalf terrainblend = min(myhalf(VertexColor.a) * color.a * 2.0, myhalf(1.0));\n" +" //myhalf terrainblend = myhalf(VertexColor.a) * color.a > 0.5;\n" +" color.rgb = mix(myhalf3(dp_texture2D(Texture_SecondaryColor, TexCoord2)), color.rgb, terrainblend);\n" +"#endif\n" +" color.a = 1.0;\n" +" //color = mix(myhalf4(1, 0, 0, 1), color, terrainblend);\n" +"#endif\n" +"\n" +" // get the surface normal\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" myhalf3 surfacenormal = normalize(mix(myhalf3(dp_texture2D(Texture_SecondaryNormal, TexCoord2)), myhalf3(offsetMappedTexture2D(Texture_Normal)), terrainblend) - myhalf3(0.5, 0.5, 0.5));\n" +"#else\n" +" myhalf3 surfacenormal = normalize(myhalf3(offsetMappedTexture2D(Texture_Normal)) - myhalf3(0.5, 0.5, 0.5));\n" +"#endif\n" +"\n" +" // get the material colors\n" +" myhalf3 diffusetex = color.rgb;\n" +"#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n" +"# ifdef USEVERTEXTEXTUREBLEND\n" +" myhalf4 glosstex = mix(myhalf4(dp_texture2D(Texture_SecondaryGloss, TexCoord2)), myhalf4(offsetMappedTexture2D(Texture_Gloss)), terrainblend);\n" +"# else\n" +" myhalf4 glosstex = myhalf4(offsetMappedTexture2D(Texture_Gloss));\n" +"# endif\n" +"#endif\n" +"\n" +"#ifdef USEREFLECTCUBE\n" +" vec3 TangentReflectVector = reflect(-EyeVectorFogDepth.xyz, surfacenormal);\n" +" vec3 ModelReflectVector = TangentReflectVector.x * VectorS.xyz + TangentReflectVector.y * VectorT.xyz + TangentReflectVector.z * VectorR.xyz;\n" +" vec3 ReflectCubeTexCoord = vec3(ModelToReflectCube * vec4(ModelReflectVector, 0));\n" +" diffusetex += myhalf3(offsetMappedTexture2D(Texture_ReflectMask)) * myhalf3(dp_textureCube(Texture_ReflectCube, ReflectCubeTexCoord));\n" +"#endif\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_LIGHTSOURCE\n" +" // light source\n" +"#ifdef USEDIFFUSE\n" +" myhalf3 lightnormal = myhalf3(normalize(LightVector));\n" +" myhalf diffuse = myhalf(max(float(dot(surfacenormal, lightnormal)), 0.0));\n" +" color.rgb = diffusetex * (Color_Ambient + diffuse * Color_Diffuse);\n" +"#ifdef USESPECULAR\n" +"#ifdef USEEXACTSPECULARMATH\n" +" myhalf specular = pow(myhalf(max(float(dot(reflect(lightnormal, surfacenormal), normalize(EyeVectorFogDepth.xyz)))*-1.0, 0.0)), SpecularPower * glosstex.a);\n" +"#else\n" +" myhalf3 specularnormal = normalize(lightnormal + myhalf3(normalize(EyeVectorFogDepth.xyz)));\n" +" myhalf specular = pow(myhalf(max(float(dot(surfacenormal, specularnormal)), 0.0)), SpecularPower * glosstex.a);\n" +"#endif\n" +" color.rgb += glosstex.rgb * (specular * Color_Specular);\n" +"#endif\n" +"#else\n" +" color.rgb = diffusetex * Color_Ambient;\n" +"#endif\n" +" color.rgb *= LightColor;\n" +" color.rgb *= myhalf(dp_texture2D(Texture_Attenuation, vec2(length(CubeVector), 0.0)));\n" +"#if defined(USESHADOWMAP2D)\n" +" color.rgb *= ShadowMapCompare(CubeVector);\n" +"#endif\n" +"# ifdef USECUBEFILTER\n" +" color.rgb *= myhalf3(dp_textureCube(Texture_Cube, CubeVector));\n" +"# endif\n" +"#endif // MODE_LIGHTSOURCE\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_LIGHTDIRECTION\n" +"#define SHADING\n" +"#ifdef USEDIFFUSE\n" +" myhalf3 lightnormal = myhalf3(normalize(LightVector));\n" +"#endif\n" +"#define lightcolor LightColor\n" +"#endif // MODE_LIGHTDIRECTION\n" +"#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n" +"#define SHADING\n" +" // deluxemap lightmapping using light vectors in modelspace (q3map2 -light -deluxe)\n" +" myhalf3 lightnormal_modelspace = myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + myhalf3(-1.0, -1.0, -1.0);\n" +" myhalf3 lightcolor = myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n" +" // convert modelspace light vector to tangentspace\n" +" myhalf3 lightnormal;\n" +" lightnormal.x = dot(lightnormal_modelspace, myhalf3(VectorS));\n" +" lightnormal.y = dot(lightnormal_modelspace, myhalf3(VectorT));\n" +" lightnormal.z = dot(lightnormal_modelspace, myhalf3(VectorR));\n" +" lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n" +" // calculate directional shading (and undoing the existing angle attenuation on the lightmap by the division)\n" +" // note that q3map2 is too stupid to calculate proper surface normals when q3map_nonplanar\n" +" // is used (the lightmap and deluxemap coords correspond to virtually random coordinates\n" +" // on that luxel, and NOT to its center, because recursive triangle subdivision is used\n" +" // to map the luxels to coordinates on the draw surfaces), which also causes\n" +" // deluxemaps to be wrong because light contributions from the wrong side of the surface\n" +" // are added up. To prevent divisions by zero or strong exaggerations, a max()\n" +" // nudge is done here at expense of some additional fps. This is ONLY needed for\n" +" // deluxemaps, tangentspace deluxemap avoid this problem by design.\n" +" lightcolor *= 1.0 / max(0.25, lightnormal.z);\n" +"#endif // MODE_LIGHTDIRECTIONMAP_MODELSPACE\n" +"#ifdef MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n" +"#define SHADING\n" +" // deluxemap lightmapping using light vectors in tangentspace (hmap2 -light)\n" +" myhalf3 lightnormal = myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + myhalf3(-1.0, -1.0, -1.0);\n" +" myhalf3 lightcolor = myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n" +"#endif\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_FAKELIGHT\n" +"#define SHADING\n" +"myhalf3 lightnormal = myhalf3(normalize(EyeVectorFogDepth.xyz));\n" +"myhalf3 lightcolor = myhalf3(1.0);\n" +"#endif // MODE_FAKELIGHT\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_LIGHTMAP\n" +" color.rgb = diffusetex * (Color_Ambient + myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw)) * Color_Diffuse);\n" +"#endif // MODE_LIGHTMAP\n" +"#ifdef MODE_VERTEXCOLOR\n" +" color.rgb = diffusetex * (Color_Ambient + myhalf3(VertexColor.rgb) * Color_Diffuse);\n" +"#endif // MODE_VERTEXCOLOR\n" +"#ifdef MODE_FLATCOLOR\n" +" color.rgb = diffusetex * Color_Ambient;\n" +"#endif // MODE_FLATCOLOR\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef SHADING\n" +"# ifdef USEDIFFUSE\n" +" myhalf diffuse = myhalf(max(float(dot(surfacenormal, lightnormal)), 0.0));\n" +"# ifdef USESPECULAR\n" +"# ifdef USEEXACTSPECULARMATH\n" +" myhalf specular = pow(myhalf(max(float(dot(reflect(lightnormal, surfacenormal), normalize(EyeVectorFogDepth.xyz)))*-1.0, 0.0)), SpecularPower * glosstex.a);\n" +"# else\n" +" myhalf3 specularnormal = normalize(lightnormal + myhalf3(normalize(EyeVectorFogDepth.xyz)));\n" +" myhalf specular = pow(myhalf(max(float(dot(surfacenormal, specularnormal)), 0.0)), SpecularPower * glosstex.a);\n" +"# endif\n" +" color.rgb = diffusetex * Color_Ambient + (diffusetex * Color_Diffuse * diffuse + glosstex.rgb * Color_Specular * specular) * lightcolor;\n" +"# else\n" +" color.rgb = diffusetex * (Color_Ambient + Color_Diffuse * diffuse * lightcolor);\n" +"# endif\n" +"# else\n" +" color.rgb = diffusetex * Color_Ambient;\n" +"# endif\n" +"#endif\n" +"\n" +"#ifdef USESHADOWMAPORTHO\n" +" color.rgb *= ShadowMapCompare(ShadowMapTC);\n" +"#endif\n" +"\n" +"#ifdef USEDEFERREDLIGHTMAP\n" +" vec2 ScreenTexCoord = gl_FragCoord.xy * PixelToScreenTexCoord;\n" +" color.rgb += diffusetex * myhalf3(dp_texture2D(Texture_ScreenDiffuse, ScreenTexCoord)) * DeferredMod_Diffuse;\n" +" color.rgb += glosstex.rgb * myhalf3(dp_texture2D(Texture_ScreenSpecular, ScreenTexCoord)) * DeferredMod_Specular;\n" +"#endif\n" +"\n" +"#ifdef USEBOUNCEGRID\n" +"#ifdef USEBOUNCEGRIDDIRECTIONAL\n" +"// myhalf4 bouncegrid_coeff1 = myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord ));\n" +"// myhalf4 bouncegrid_coeff2 = myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.125))) * 2.0 + myhalf4(-1.0, -1.0, -1.0, -1.0);\n" +" myhalf4 bouncegrid_coeff3 = myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.250)));\n" +" myhalf4 bouncegrid_coeff4 = myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.375)));\n" +" myhalf4 bouncegrid_coeff5 = myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.500)));\n" +" myhalf4 bouncegrid_coeff6 = myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.625)));\n" +" myhalf4 bouncegrid_coeff7 = myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.750)));\n" +" myhalf4 bouncegrid_coeff8 = myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.875)));\n" +" myhalf3 bouncegrid_dir = normalize(mat3(BounceGridMatrix) * (surfacenormal.x * VectorS.xyz + surfacenormal.y * VectorT.xyz + surfacenormal.z * VectorR.xyz));\n" +" myhalf3 bouncegrid_dirp = max(myhalf3(0.0, 0.0, 0.0), bouncegrid_dir);\n" +" myhalf3 bouncegrid_dirn = max(myhalf3(0.0, 0.0, 0.0), -bouncegrid_dir);\n" +"// bouncegrid_dirp = bouncegrid_dirn = myhalf3(1.0,1.0,1.0);\n" +" myhalf3 bouncegrid_light = myhalf3(\n" +" dot(bouncegrid_coeff3.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff6.xyz, bouncegrid_dirn),\n" +" dot(bouncegrid_coeff4.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff7.xyz, bouncegrid_dirn),\n" +" dot(bouncegrid_coeff5.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff8.xyz, bouncegrid_dirn));\n" +" color.rgb += diffusetex * bouncegrid_light * BounceGridIntensity;\n" +"// color.rgb = bouncegrid_dir.rgb * 0.5 + vec3(0.5, 0.5, 0.5);\n" +"#else\n" +" color.rgb += diffusetex * myhalf3(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord)) * BounceGridIntensity;\n" +"#endif\n" +"#endif\n" +"\n" +"#ifdef USEGLOW\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" color.rgb += mix(myhalf3(dp_texture2D(Texture_SecondaryGlow, TexCoord2)), myhalf3(offsetMappedTexture2D(Texture_Glow)), terrainblend) * Color_Glow;\n" +"#else\n" +" color.rgb += myhalf3(offsetMappedTexture2D(Texture_Glow)) * Color_Glow;\n" +"#endif\n" +"#endif\n" +"\n" +"#ifdef USEFOG\n" +" color.rgb = FogVertex(color);\n" +"#endif\n" +"\n" +" // reflection must come last because it already contains exactly the correct fog (the reflection render preserves camera distance from the plane, it only flips the side) and ContrastBoost/SceneBrightness\n" +"#ifdef USEREFLECTION\n" +" vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n" +" //vec4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(myhalf3(offsetMappedTexture2D(Texture_Normal)) - myhalf3(0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" +" vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW.zw + ScreenCenterRefractReflect.zw;\n" +" #ifdef USENORMALMAPSCROLLBLEND\n" +"# ifdef USEOFFSETMAPPING\n" +" vec3 normal = dp_textureGrad(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y, dPdx*NormalmapScrollBlend.y, dPdy*NormalmapScrollBlend.y).rgb - vec3(1.0);\n" +"# else\n" +" vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n" +"# endif\n" +" normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n" +" vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(myhalf3(normal))).xy * DistortScaleRefractReflect.zw;\n" +" #else\n" +" vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(myhalf3(offsetMappedTexture2D(Texture_Normal)) - myhalf3(0.5))).xy * DistortScaleRefractReflect.zw;\n" +" #endif\n" +" // FIXME temporary hack to detect the case that the reflection\n" +" // gets blackened at edges due to leaving the area that contains actual\n" +" // content.\n" +" // Remove this 'ack once we have a better way to stop this thing from\n" +" // 'appening.\n" +" float f = min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(0.01, -0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(-0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(-0.01, -0.01)).rgb) / 0.05);\n" +" ScreenTexCoord = mix(SafeScreenTexCoord, ScreenTexCoord, f);\n" +" color.rgb = mix(color.rgb, myhalf3(dp_texture2D(Texture_Reflection, ScreenTexCoord)) * ReflectColor.rgb, ReflectColor.a);\n" +"#endif\n" +"\n" +" dp_FragColor = vec4(color);\n" +"}\n" +"#endif // FRAGMENT_SHADER\n" +"\n" +"#endif // !MODE_DEFERREDLIGHTSOURCE\n" +"#endif // !MODE_DEFERREDGEOMETRY\n" +"#endif // !MODE_WATER\n" +"#endif // !MODE_REFRACTION\n" +"#endif // !MODE_BLOOMBLUR\n" +"#endif // !MODE_GENERIC\n" +"#endif // !MODE_POSTPROCESS\n" +"#endif // !MODE_SHOWDEPTH\n" +"#endif // !MODE_DEPTH_OR_SHADOW\n" diff --git a/misc/source/darkplaces-src/shader_hlsl.h b/misc/source/darkplaces-src/shader_hlsl.h new file mode 100644 index 00000000..c93d4724 --- /dev/null +++ b/misc/source/darkplaces-src/shader_hlsl.h @@ -0,0 +1,1531 @@ +"// ambient+diffuse+specular+normalmap+attenuation+cubemap+fog shader\n" +"// written by Forest 'LordHavoc' Hale\n" +"// shadowmapping enhancements by Lee 'eihrul' Salzman\n" +"\n" +"// FIXME: we need to get rid of ModelViewProjectionPosition to make room for the texcoord for this\n" +"#if defined(USEREFLECTION)\n" +"#undef USESHADOWMAPORTHO\n" +"#endif\n" +"\n" +"#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE) || defined(USEFOGHEIGHTTEXTURE)\n" +"# define USEFOG\n" +"#endif\n" +"#if defined(MODE_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n" +"#define USELIGHTMAP\n" +"#endif\n" +"#if defined(USESPECULAR) || defined(USEOFFSETMAPPING) || defined(USEREFLECTCUBE) || defined(MODE_FAKELIGHT)\n" +"#define USEEYEVECTOR\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"#ifdef HLSL\n" +"//#undef USESHADOWMAPPCF\n" +"//#define texDepth2D(tex,texcoord) tex2D(tex,texcoord).r\n" +"#define texDepth2D(tex,texcoord) dot(tex2D(tex,texcoord).rgb, float3(1.0, 255.0/65536.0, 255.0/16777216.0))\n" +"#else\n" +"#define texDepth2D(tex,texcoord) tex2D(tex,texcoord).r\n" +"#endif\n" +"#endif\n" +"\n" +"#ifdef VERTEX_SHADER\n" +"#ifdef USETRIPPY\n" +"// LordHavoc: based on shader code linked at: http://www.youtube.com/watch?v=JpksyojwqzE\n" +"// tweaked scale\n" +"float4 TrippyVertex(float4 position)\n" +"(\n" +"uniform float ClientTime : register(c2)\n" +")\n" +"{\n" +" float worldTime = ClientTime;\n" +" // tweaked for Quake\n" +" worldTime *= 10.0;\n" +" position *= 0.125;\n" +" //~tweaked for Quake\n" +" float distanceSquared = (position.x * position.x + position.z * position.z);\n" +" position.y += 5.0*sin(distanceSquared*sin(worldTime/143.0)/1000.0);\n" +" float y = position.y;\n" +" float x = position.x;\n" +" float om = sin(distanceSquared*sin(worldTime/256.0)/5000.0) * sin(worldTime/200.0);\n" +" position.y = x*sin(om)+y*cos(om);\n" +" position.x = x*cos(om)-y*sin(om);\n" +" return position;\n" +"}\n" +"#endif\n" +"#endif\n" +"\n" +"#ifdef MODE_DEPTH_OR_SHADOW\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"out float4 gl_Position : POSITION,\n" +"out float Depth : TEXCOORD0\n" +")\n" +"{\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +" Depth = gl_Position.z;\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main\n" +"(\n" +"float Depth : TEXCOORD0,\n" +"out float4 gl_FragColor : COLOR\n" +")\n" +"{\n" +"// float4 temp = float4(Depth,Depth*(65536.0/255.0),Depth*(16777216.0/255.0),0.0);\n" +" float4 temp = float4(Depth,Depth*256.0,Depth*65536.0,0.0);\n" +" temp.yz -= floor(temp.yz);\n" +" gl_FragColor = temp;\n" +"// gl_FragColor = float4(Depth,0,0,0);\n" +"}\n" +"#endif\n" +"#else // !MODE_DEPTH_ORSHADOW\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_SHOWDEPTH\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"out float4 gl_Position : POSITION,\n" +"out float4 gl_FrontColor : COLOR0\n" +")\n" +"{\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +" gl_FrontColor = float4(gl_Position.z, gl_Position.z, gl_Position.z, 1.0);\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main\n" +"(\n" +"float4 gl_FrontColor : COLOR0,\n" +"out float4 gl_FragColor : COLOR\n" +")\n" +"{\n" +" gl_FragColor = gl_FrontColor;\n" +"}\n" +"#endif\n" +"#else // !MODE_SHOWDEPTH\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_POSTPROCESS\n" +"\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n" +"float4 gl_MultiTexCoord4 : TEXCOORD4,\n" +"out float4 gl_Position : POSITION,\n" +"out float2 TexCoord1 : TEXCOORD0,\n" +"out float2 TexCoord2 : TEXCOORD1\n" +")\n" +"{\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +" TexCoord1 = gl_MultiTexCoord0.xy;\n" +"#ifdef USEBLOOM\n" +" TexCoord2 = gl_MultiTexCoord4.xy;\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main\n" +"(\n" +"float2 TexCoord1 : TEXCOORD0,\n" +"float2 TexCoord2 : TEXCOORD1,\n" +"uniform sampler Texture_First : register(s0),\n" +"#ifdef USEBLOOM\n" +"uniform sampler Texture_Second : register(s1),\n" +"#endif\n" +"#ifdef USEGAMMARAMPS\n" +"uniform sampler Texture_GammaRamps : register(s2),\n" +"#endif\n" +"#ifdef USESATURATION\n" +"uniform float Saturation : register(c30),\n" +"#endif\n" +"#ifdef USEVIEWTINT\n" +"uniform float4 ViewTintColor : register(c41),\n" +"#endif\n" +"uniform float4 UserVec1 : register(c37),\n" +"uniform float4 UserVec2 : register(c38),\n" +"uniform float4 UserVec3 : register(c39),\n" +"uniform float4 UserVec4 : register(c40),\n" +"uniform float ClientTime : register(c2),\n" +"uniform float2 PixelSize : register(c25),\n" +"uniform float4 BloomColorSubtract : register(c43),\n" +"out float4 gl_FragColor : COLOR\n" +")\n" +"{\n" +" gl_FragColor = tex2D(Texture_First, TexCoord1);\n" +"#ifdef USEBLOOM\n" +" gl_FragColor += max(float4(0,0,0,0), tex2D(Texture_Second, TexCoord2) - BloomColorSubtract);\n" +"#endif\n" +"#ifdef USEVIEWTINT\n" +" gl_FragColor = lerp(gl_FragColor, ViewTintColor, ViewTintColor.a);\n" +"#endif\n" +"\n" +"#ifdef USEPOSTPROCESSING\n" +"// do r_glsl_dumpshader, edit glsl/default.glsl, and replace this by your own postprocessing if you want\n" +"// this code does a blur with the radius specified in the first component of r_glsl_postprocess_uservec1 and blends it using the second component\n" +" float sobel = 1.0;\n" +" // float2 ts = textureSize(Texture_First, 0);\n" +" // float2 px = float2(1/ts.x, 1/ts.y);\n" +" float2 px = PixelSize;\n" +" float3 x1 = tex2D(Texture_First, TexCoord1 + float2(-px.x, px.y)).rgb;\n" +" float3 x2 = tex2D(Texture_First, TexCoord1 + float2(-px.x, 0.0)).rgb;\n" +" float3 x3 = tex2D(Texture_First, TexCoord1 + float2(-px.x,-px.y)).rgb;\n" +" float3 x4 = tex2D(Texture_First, TexCoord1 + float2( px.x, px.y)).rgb;\n" +" float3 x5 = tex2D(Texture_First, TexCoord1 + float2( px.x, 0.0)).rgb;\n" +" float3 x6 = tex2D(Texture_First, TexCoord1 + float2( px.x,-px.y)).rgb;\n" +" float3 y1 = tex2D(Texture_First, TexCoord1 + float2( px.x,-px.y)).rgb;\n" +" float3 y2 = tex2D(Texture_First, TexCoord1 + float2( 0.0,-px.y)).rgb;\n" +" float3 y3 = tex2D(Texture_First, TexCoord1 + float2(-px.x,-px.y)).rgb;\n" +" float3 y4 = tex2D(Texture_First, TexCoord1 + float2( px.x, px.y)).rgb;\n" +" float3 y5 = tex2D(Texture_First, TexCoord1 + float2( 0.0, px.y)).rgb;\n" +" float3 y6 = tex2D(Texture_First, TexCoord1 + float2(-px.x, px.y)).rgb;\n" +" float px1 = -1.0 * dot(float3(0.3, 0.59, 0.11), x1);\n" +" float px2 = -2.0 * dot(float3(0.3, 0.59, 0.11), x2);\n" +" float px3 = -1.0 * dot(float3(0.3, 0.59, 0.11), x3);\n" +" float px4 = 1.0 * dot(float3(0.3, 0.59, 0.11), x4);\n" +" float px5 = 2.0 * dot(float3(0.3, 0.59, 0.11), x5);\n" +" float px6 = 1.0 * dot(float3(0.3, 0.59, 0.11), x6);\n" +" float py1 = -1.0 * dot(float3(0.3, 0.59, 0.11), y1);\n" +" float py2 = -2.0 * dot(float3(0.3, 0.59, 0.11), y2);\n" +" float py3 = -1.0 * dot(float3(0.3, 0.59, 0.11), y3);\n" +" float py4 = 1.0 * dot(float3(0.3, 0.59, 0.11), y4);\n" +" float py5 = 2.0 * dot(float3(0.3, 0.59, 0.11), y5);\n" +" float py6 = 1.0 * dot(float3(0.3, 0.59, 0.11), y6);\n" +" sobel = 0.25 * abs(px1 + px2 + px3 + px4 + px5 + px6) + 0.25 * abs(py1 + py2 + py3 + py4 + py5 + py6);\n" +" gl_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.987688, -0.156434)) * UserVec1.y;\n" +" gl_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.156434, -0.891007)) * UserVec1.y;\n" +" gl_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2( 0.891007, -0.453990)) * UserVec1.y;\n" +" gl_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2( 0.707107, 0.707107)) * UserVec1.y;\n" +" gl_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.453990, 0.891007)) * UserVec1.y;\n" +" gl_FragColor /= (1.0 + 5.0 * UserVec1.y);\n" +" gl_FragColor.rgb = gl_FragColor.rgb * (1.0 + UserVec2.x) + float3(1,1,1)*max(0.0, sobel - UserVec2.z)*UserVec2.y;\n" +"#endif\n" +"\n" +"#ifdef USESATURATION\n" +" //apply saturation BEFORE gamma ramps, so v_glslgamma value does not matter\n" +" float y = dot(gl_FragColor.rgb, float3(0.299, 0.587, 0.114));\n" +" // 'vampire sight' effect, wheres red is compensated\n" +" #ifdef SATURATION_REDCOMPENSATE\n" +" float rboost = max(0.0, (gl_FragColor.r - max(gl_FragColor.g, gl_FragColor.b))*(1.0 - Saturation));\n" +" gl_FragColor.rgb = lerp(float3(y,y,y), gl_FragColor.rgb, Saturation);\n" +" gl_FragColor.r += r;\n" +" #else\n" +" // normal desaturation\n" +" //gl_FragColor = float3(y,y,y) + (gl_FragColor.rgb - float3(y)) * Saturation;\n" +" gl_FragColor.rgb = lerp(float3(y,y,y), gl_FragColor.rgb, Saturation);\n" +" #endif\n" +"#endif\n" +"\n" +"#ifdef USEGAMMARAMPS\n" +" gl_FragColor.r = tex2D(Texture_GammaRamps, float2(gl_FragColor.r, 0)).r;\n" +" gl_FragColor.g = tex2D(Texture_GammaRamps, float2(gl_FragColor.g, 0)).g;\n" +" gl_FragColor.b = tex2D(Texture_GammaRamps, float2(gl_FragColor.b, 0)).b;\n" +"#endif\n" +"}\n" +"#endif\n" +"#else // !MODE_POSTPROCESS\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_GENERIC\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"float4 gl_Color : COLOR0,\n" +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n" +"float4 gl_MultiTexCoord1 : TEXCOORD1,\n" +"out float4 gl_Position : POSITION,\n" +"#ifdef USEDIFFUSE\n" +"out float2 TexCoord1 : TEXCOORD0,\n" +"#endif\n" +"#ifdef USESPECULAR\n" +"out float2 TexCoord2 : TEXCOORD1,\n" +"#endif\n" +"out float4 gl_FrontColor : COLOR\n" +")\n" +"{\n" +" gl_FrontColor = gl_Color;\n" +"#ifdef USEDIFFUSE\n" +" TexCoord1 = gl_MultiTexCoord0.xy;\n" +"#endif\n" +"#ifdef USESPECULAR\n" +" TexCoord2 = gl_MultiTexCoord1.xy;\n" +"#endif\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"\n" +"void main\n" +"(\n" +"float4 gl_FrontColor : COLOR0,\n" +"float2 TexCoord1 : TEXCOORD0,\n" +"float2 TexCoord2 : TEXCOORD1,\n" +"#ifdef USEDIFFUSE\n" +"uniform sampler Texture_First : register(s0),\n" +"#endif\n" +"#ifdef USESPECULAR\n" +"uniform sampler Texture_Second : register(s1),\n" +"#endif\n" +"out float4 gl_FragColor : COLOR\n" +")\n" +"{\n" +"#ifdef USEVIEWTINT\n" +" gl_FragColor = gl_FrontColor;\n" +"#else\n" +" gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +"#endif\n" +"#ifdef USEDIFFUSE\n" +" gl_FragColor *= tex2D(Texture_First, TexCoord1);\n" +"#endif\n" +"\n" +"#ifdef USESPECULAR\n" +" float4 tex2 = tex2D(Texture_Second, TexCoord2);\n" +"# ifdef USECOLORMAPPING\n" +" gl_FragColor *= tex2;\n" +"# endif\n" +"# ifdef USEGLOW\n" +" gl_FragColor += tex2;\n" +"# endif\n" +"# ifdef USEVERTEXTEXTUREBLEND\n" +" gl_FragColor = lerp(gl_FragColor, tex2, tex2.a);\n" +"# endif\n" +"#endif\n" +"}\n" +"#endif\n" +"#else // !MODE_GENERIC\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_BLOOMBLUR\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n" +"out float4 gl_Position : POSITION,\n" +"out float2 TexCoord : TEXCOORD0\n" +")\n" +"{\n" +" TexCoord = gl_MultiTexCoord0.xy;\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"\n" +"void main\n" +"(\n" +"float2 TexCoord : TEXCOORD0,\n" +"uniform sampler Texture_First : register(s0),\n" +"uniform float4 BloomBlur_Parameters : register(c1),\n" +"out float4 gl_FragColor : COLOR\n" +")\n" +"{\n" +" int i;\n" +" float2 tc = TexCoord;\n" +" float3 color = tex2D(Texture_First, tc).rgb;\n" +" tc += BloomBlur_Parameters.xy;\n" +" for (i = 1;i < SAMPLES;i++)\n" +" {\n" +" color += tex2D(Texture_First, tc).rgb;\n" +" tc += BloomBlur_Parameters.xy;\n" +" }\n" +" gl_FragColor = float4(color * BloomBlur_Parameters.z + float3(BloomBlur_Parameters.w), 1);\n" +"}\n" +"#endif\n" +"#else // !MODE_BLOOMBLUR\n" +"#ifdef MODE_REFRACTION\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n" +"uniform float4x4 TexMatrix : register(c0),\n" +"uniform float3 EyePosition : register(c24),\n" +"out float4 gl_Position : POSITION,\n" +"out float2 TexCoord : TEXCOORD0,\n" +"out float3 EyeVector : TEXCOORD1,\n" +"out float4 ModelViewProjectionPosition : TEXCOORD2\n" +")\n" +"{\n" +" TexCoord = mul(TexMatrix, gl_MultiTexCoord0).xy;\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +" ModelViewProjectionPosition = gl_Position;\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main\n" +"(\n" +"float2 TexCoord : TEXCOORD0,\n" +"float3 EyeVector : TEXCOORD1,\n" +"float4 ModelViewProjectionPosition : TEXCOORD2,\n" +"uniform sampler Texture_Normal : register(s0),\n" +"uniform sampler Texture_Refraction : register(s3),\n" +"uniform sampler Texture_Reflection : register(s7),\n" +"uniform float4 DistortScaleRefractReflect : register(c14),\n" +"uniform float4 ScreenScaleRefractReflect : register(c32),\n" +"uniform float4 ScreenCenterRefractReflect : register(c31),\n" +"uniform float4 RefractColor : register(c29),\n" +"out float4 gl_FragColor : COLOR\n" +")\n" +"{\n" +" float2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n" +" //float2 ScreenTexCoord = (ModelViewProjectionPosition.xy + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy * DistortScaleRefractReflect.xy * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n" +" float2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n" +" float2 ScreenTexCoord = SafeScreenTexCoord + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy * DistortScaleRefractReflect.xy;\n" +" // FIXME temporary hack to detect the case that the reflection\n" +" // gets blackened at edges due to leaving the area that contains actual\n" +" // content.\n" +" // Remove this 'ack once we have a better way to stop this thing from\n" +" // 'appening.\n" +" float f = min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(0.01, -0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(-0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(-0.01, -0.01)).rgb) / 0.05);\n" +" ScreenTexCoord = lerp(SafeScreenTexCoord, ScreenTexCoord, f);\n" +" gl_FragColor = float4(tex2D(Texture_Refraction, ScreenTexCoord).rgb, 1) * RefractColor;\n" +"}\n" +"#endif\n" +"#else // !MODE_REFRACTION\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_WATER\n" +"#ifdef VERTEX_SHADER\n" +"\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n" +"float4 gl_MultiTexCoord1 : TEXCOORD1,\n" +"float4 gl_MultiTexCoord2 : TEXCOORD2,\n" +"float4 gl_MultiTexCoord3 : TEXCOORD3,\n" +"uniform float4x4 TexMatrix : register(c0),\n" +"uniform float3 EyePosition : register(c24),\n" +"out float4 gl_Position : POSITION,\n" +"out float2 TexCoord : TEXCOORD0,\n" +"out float3 EyeVector : TEXCOORD1,\n" +"out float4 ModelViewProjectionPosition : TEXCOORD2\n" +")\n" +"{\n" +" TexCoord = mul(TexMatrix, gl_MultiTexCoord0).xy;\n" +" float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n" +" EyeVector.x = dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz);\n" +" EyeVector.y = dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz);\n" +" EyeVector.z = dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz);\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +" ModelViewProjectionPosition = gl_Position;\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main\n" +"(\n" +"float2 TexCoord : TEXCOORD0,\n" +"float3 EyeVector : TEXCOORD1,\n" +"float4 ModelViewProjectionPosition : TEXCOORD2,\n" +"uniform sampler Texture_Normal : register(s0),\n" +"uniform sampler Texture_Refraction : register(s3),\n" +"uniform sampler Texture_Reflection : register(s7),\n" +"uniform float4 DistortScaleRefractReflect : register(c14),\n" +"uniform float4 ScreenScaleRefractReflect : register(c32),\n" +"uniform float4 ScreenCenterRefractReflect : register(c31),\n" +"uniform float4 RefractColor : register(c29),\n" +"uniform float4 ReflectColor : register(c26),\n" +"uniform float ReflectFactor : register(c27),\n" +"uniform float ReflectOffset : register(c28),\n" +"out float4 gl_FragColor : COLOR\n" +")\n" +"{\n" +" float4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n" +" //float4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" +" float4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" +" //SafeScreenTexCoord = gl_FragCoord.xyxy * float4(1.0 / 1920.0, 1.0 / 1200.0, 1.0 / 1920.0, 1.0 / 1200.0);\n" +" float4 ScreenTexCoord = SafeScreenTexCoord + float2(normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy).xyxy * DistortScaleRefractReflect;\n" +" // FIXME temporary hack to detect the case that the reflection\n" +" // gets blackened at edges due to leaving the area that contains actual\n" +" // content.\n" +" // Remove this 'ack once we have a better way to stop this thing from\n" +" // 'appening.\n" +" float f = min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(0.01, -0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(-0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(-0.01, -0.01)).rgb) / 0.05);\n" +" ScreenTexCoord.xy = lerp(SafeScreenTexCoord.xy, ScreenTexCoord.xy, f);\n" +" f = min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(0.01, -0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(-0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(-0.01, -0.01)).rgb) / 0.05);\n" +" ScreenTexCoord.zw = lerp(SafeScreenTexCoord.zw, ScreenTexCoord.zw, f);\n" +" float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * ReflectFactor + ReflectOffset;\n" +" gl_FragColor = lerp(float4(tex2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * RefractColor, float4(tex2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n" +"}\n" +"#endif\n" +"#else // !MODE_WATER\n" +"\n" +"\n" +"\n" +"\n" +"// TODO: get rid of tangentt (texcoord2) and use a crossproduct to regenerate it from tangents (texcoord1) and normal (texcoord3), this would require sending a 4 component texcoord1 with W as 1 or -1 according to which side the texcoord2 should be on\n" +"\n" +"// fragment shader specific:\n" +"#ifdef FRAGMENT_SHADER\n" +"\n" +"#ifdef USEFOG\n" +"float3 FogVertex(float4 surfacecolor, float3 FogColor, float3 EyeVectorModelSpace, float FogPlaneVertexDist, float FogRangeRecip, float FogPlaneViewDist, float FogHeightFade, sampler Texture_FogMask, sampler Texture_FogHeightTexture)\n" +"{\n" +" float fogfrac;\n" +" float3 fc = FogColor;\n" +"#ifdef USEFOGALPHAHACK\n" +" fc *= surfacecolor.a;\n" +"#endif\n" +"#ifdef USEFOGHEIGHTTEXTURE\n" +" float4 fogheightpixel = tex2D(Texture_FogHeightTexture, float2(1,1) + float2(FogPlaneVertexDist, FogPlaneViewDist) * (-2.0 * FogHeightFade));\n" +" fogfrac = fogheightpixel.a;\n" +" return lerp(fogheightpixel.rgb * fc, surfacecolor.rgb, tex2D(Texture_FogMask, float2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n" +"#else\n" +"# ifdef USEFOGOUTSIDE\n" +" fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n" +"# else\n" +" fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0, FogPlaneVertexDist)) * min(1.0, (min(0.0, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade);\n" +"# endif\n" +" return lerp(fc, surfacecolor.rgb, tex2D(Texture_FogMask, float2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n" +"#endif\n" +"}\n" +"#endif\n" +"\n" +"#ifdef USEOFFSETMAPPING\n" +"float2 OffsetMapping(float2 TexCoord, float4 OffsetMapping_ScaleSteps, float3 EyeVector, sampler Texture_Normal, float2 dPdx, float2 dPdy)\n" +"{\n" +" float i;\n" +"#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n" +" // 14 sample relief mapping: linear search and then binary search\n" +" // this basically steps forward a small amount repeatedly until it finds\n" +" // itself inside solid, then jitters forward and back using decreasing\n" +" // amounts to find the impact\n" +" //float3 OffsetVector = float3(EyeVector.xy * ((1.0 / EyeVector.z) * OffsetMapping_ScaleSteps.x) * float2(-1, 1), -1);\n" +" //float3 OffsetVector = float3(normalize(EyeVector.xy) * OffsetMapping_ScaleSteps.x * float2(-1, 1), -1);\n" +" float3 OffsetVector = float3(normalize(EyeVector).xy * OffsetMapping_ScaleSteps.x * float2(-1, 1), -1);\n" +" float3 RT = float3(TexCoord, 1);\n" +" OffsetVector *= OffsetMapping_ScaleSteps.z;\n" +" for(i = 1.0; i < OffsetMapping_ScaleSteps.y; ++i)\n" +" RT += OffsetVector * step(tex2Dgrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z);\n" +" for(i = 0.0, f = 1.0; i < OffsetMapping_ScaleSteps.w; ++i, f *= 0.5)\n" +" RT += OffsetVector * (step(tex2Dgrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z) * f - 0.5 * f);\n" +" return RT.xy;\n" +"#else\n" +" // 2 sample offset mapping (only 2 samples because of ATI Radeon 9500-9800/X300 limits)\n" +" //float2 OffsetVector = float2(EyeVector.xy * ((1.0 / EyeVector.z) * OffsetMapping_ScaleSteps.x) * float2(-1, 1));\n" +" //float2 OffsetVector = float2(normalize(EyeVector.xy) * OffsetMapping_ScaleSteps.x * float2(-1, 1));\n" +" float2 OffsetVector = float2(normalize(EyeVector).xy * OffsetMapping_ScaleSteps.x * float2(-1, 1));\n" +" OffsetVector *= OffsetMapping_ScaleSteps.z;\n" +" for(i = 0.0; i < OffsetMapping_ScaleSteps.y; ++i)\n" +" TexCoord += OffsetVector * (1.0 - tex2Dgrad(Texture_Normal, TexCoord, dPdx, dPdy).a);\n" +" return TexCoord;\n" +"#endif\n" +"}\n" +"#endif // USEOFFSETMAPPING\n" +"\n" +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n" +"#if defined(USESHADOWMAP2D)\n" +"# ifdef USESHADOWMAPORTHO\n" +"# define GetShadowMapTC2D(dir, ShadowMap_Parameters) (min(dir, ShadowMap_Parameters.xyz))\n" +"# else\n" +"# ifdef USESHADOWMAPVSDCT\n" +"float3 GetShadowMapTC2D(float3 dir, float4 ShadowMap_Parameters, samplerCUBE Texture_CubeProjection)\n" +"{\n" +" float3 adir = abs(dir);\n" +" float2 aparams = ShadowMap_Parameters.xy / max(max(adir.x, adir.y), adir.z);\n" +" float4 proj = texCUBE(Texture_CubeProjection, dir);\n" +" return float3(lerp(dir.xy, dir.zz, proj.xy) * aparams.x + proj.zw * ShadowMap_Parameters.z, aparams.y + ShadowMap_Parameters.w);\n" +"}\n" +"# else\n" +"float3 GetShadowMapTC2D(float3 dir, float4 ShadowMap_Parameters)\n" +"{\n" +" float3 adir = abs(dir);\n" +" float ma = adir.z;\n" +" float4 proj = float4(dir, 2.5);\n" +" if (adir.x > ma) { ma = adir.x; proj = float4(dir.zyx, 0.5); }\n" +" if (adir.y > ma) { ma = adir.y; proj = float4(dir.xzy, 1.5); }\n" +"#ifdef HLSL\n" +" return float3(proj.xy * ShadowMap_Parameters.x / ma + float2(0.5,0.5) + float2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, ma + 64 * ShadowMap_Parameters.w);\n" +"#else\n" +" float2 aparams = ShadowMap_Parameters.xy / ma;\n" +" return float3(proj.xy * aparams.x + float2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, aparams.y + ShadowMap_Parameters.w);\n" +"#endif\n" +"}\n" +"# endif\n" +"# endif\n" +"#endif // defined(USESHADOWMAP2D)\n" +"\n" +"# ifdef USESHADOWMAP2D\n" +"#ifdef USESHADOWMAPVSDCT\n" +"float ShadowMapCompare(float3 dir, sampler Texture_ShadowMap2D, float4 ShadowMap_Parameters, float2 ShadowMap_TextureScale, samplerCUBE Texture_CubeProjection)\n" +"#else\n" +"float ShadowMapCompare(float3 dir, sampler Texture_ShadowMap2D, float4 ShadowMap_Parameters, float2 ShadowMap_TextureScale)\n" +"#endif\n" +"{\n" +"#ifdef USESHADOWMAPVSDCT\n" +" float3 shadowmaptc = GetShadowMapTC2D(dir, ShadowMap_Parameters, Texture_CubeProjection);\n" +"#else\n" +" float3 shadowmaptc = GetShadowMapTC2D(dir, ShadowMap_Parameters);\n" +"#endif\n" +" float f;\n" +"\n" +"# ifdef USESHADOWSAMPLER\n" +"# ifdef USESHADOWMAPPCF\n" +"# define texval(x, y) tex2Dproj(Texture_ShadowMap2D, float4(center + float2(x, y)*ShadowMap_TextureScale, shadowmaptc.z, 1.0)).r \n" +" float2 center = shadowmaptc.xy*ShadowMap_TextureScale;\n" +" f = dot(float4(0.25,0.25,0.25,0.25), float4(texval(-0.4, 1.0), texval(-1.0, -0.4), texval(0.4, -1.0), texval(1.0, 0.4)));\n" +"# else\n" +" f = tex2Dproj(Texture_ShadowMap2D, float4(shadowmaptc.xy*ShadowMap_TextureScale, shadowmaptc.z, 1.0)).r;\n" +"# endif\n" +"# else\n" +"# ifdef USESHADOWMAPPCF\n" +"# if defined(GL_ARB_texture_gather) || defined(GL_AMD_texture_texture4)\n" +"# ifdef GL_ARB_texture_gather\n" +"# define texval(x, y) textureGatherOffset(Texture_ShadowMap2D, center, int2(x, y))\n" +"# else\n" +"# define texval(x, y) texture4(Texture_ShadowMap2D, center + float2(x, y)*ShadowMap_TextureScale)\n" +"# endif\n" +" float2 offset = frac(shadowmaptc.xy - 0.5), center = (shadowmaptc.xy - offset)*ShadowMap_TextureScale;\n" +"# if USESHADOWMAPPCF > 1\n" +" float4 group1 = step(shadowmaptc.z, texval(-2.0, -2.0));\n" +" float4 group2 = step(shadowmaptc.z, texval( 0.0, -2.0));\n" +" float4 group3 = step(shadowmaptc.z, texval( 2.0, -2.0));\n" +" float4 group4 = step(shadowmaptc.z, texval(-2.0, 0.0));\n" +" float4 group5 = step(shadowmaptc.z, texval( 0.0, 0.0));\n" +" float4 group6 = step(shadowmaptc.z, texval( 2.0, 0.0));\n" +" float4 group7 = step(shadowmaptc.z, texval(-2.0, 2.0));\n" +" float4 group8 = step(shadowmaptc.z, texval( 0.0, 2.0));\n" +" float4 group9 = step(shadowmaptc.z, texval( 2.0, 2.0));\n" +" float4 locols = float4(group1.ab, group3.ab);\n" +" float4 hicols = float4(group7.rg, group9.rg);\n" +" locols.yz += group2.ab;\n" +" hicols.yz += group8.rg;\n" +" float4 midcols = float4(group1.rg, group3.rg) + float4(group7.ab, group9.ab) +\n" +" float4(group4.rg, group6.rg) + float4(group4.ab, group6.ab) +\n" +" lerp(locols, hicols, offset.y);\n" +" float4 cols = group5 + float4(group2.rg, group8.ab);\n" +" cols.xyz += lerp(midcols.xyz, midcols.yzw, offset.x);\n" +" f = dot(cols, float4(1.0/25.0));\n" +"# else\n" +" float4 group1 = step(shadowmaptc.z, texval(-1.0, -1.0));\n" +" float4 group2 = step(shadowmaptc.z, texval( 1.0, -1.0));\n" +" float4 group3 = step(shadowmaptc.z, texval(-1.0, 1.0));\n" +" float4 group4 = step(shadowmaptc.z, texval( 1.0, 1.0));\n" +" float4 cols = float4(group1.rg, group2.rg) + float4(group3.ab, group4.ab) +\n" +" lerp(float4(group1.ab, group2.ab), float4(group3.rg, group4.rg), offset.y);\n" +" f = dot(lerp(cols.xyz, cols.yzw, offset.x), float3(1.0/9.0));\n" +"# endif\n" +"# else\n" +"# ifdef GL_EXT_gpu_shader4\n" +"# define texval(x, y) tex2DOffset(Texture_ShadowMap2D, center, int2(x, y)).r\n" +"# else\n" +"# define texval(x, y) texDepth2D(Texture_ShadowMap2D, center + float2(x, y)*ShadowMap_TextureScale).r \n" +"# endif\n" +"# if USESHADOWMAPPCF > 1\n" +" float2 center = shadowmaptc.xy - 0.5, offset = frac(center);\n" +" center *= ShadowMap_TextureScale;\n" +" float4 row1 = step(shadowmaptc.z, float4(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0), texval( 2.0, -1.0)));\n" +" float4 row2 = step(shadowmaptc.z, float4(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0), texval( 2.0, 0.0)));\n" +" float4 row3 = step(shadowmaptc.z, float4(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0), texval( 2.0, 1.0)));\n" +" float4 row4 = step(shadowmaptc.z, float4(texval(-1.0, 2.0), texval( 0.0, 2.0), texval( 1.0, 2.0), texval( 2.0, 2.0)));\n" +" float4 cols = row2 + row3 + lerp(row1, row4, offset.y);\n" +" f = dot(lerp(cols.xyz, cols.yzw, offset.x), float3(1.0/9.0));\n" +"# else\n" +" float2 center = shadowmaptc.xy*ShadowMap_TextureScale, offset = frac(shadowmaptc.xy);\n" +" float3 row1 = step(shadowmaptc.z, float3(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0)));\n" +" float3 row2 = step(shadowmaptc.z, float3(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0)));\n" +" float3 row3 = step(shadowmaptc.z, float3(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0)));\n" +" float3 cols = row2 + lerp(row1, row3, offset.y);\n" +" f = dot(lerp(cols.xy, cols.yz, offset.x), float2(0.25,0.25));\n" +"# endif\n" +"# endif\n" +"# else\n" +" f = step(shadowmaptc.z, tex2D(Texture_ShadowMap2D, shadowmaptc.xy*ShadowMap_TextureScale).r);\n" +"# endif\n" +"# endif\n" +"# ifdef USESHADOWMAPORTHO\n" +" return lerp(ShadowMap_Parameters.w, 1.0, f);\n" +"# else\n" +" return f;\n" +"# endif\n" +"}\n" +"# endif\n" +"#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n" +"#endif // FRAGMENT_SHADER\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_DEFERREDGEOMETRY\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"float4 gl_Color : COLOR0,\n" +"#endif\n" +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n" +"float4 gl_MultiTexCoord1 : TEXCOORD1,\n" +"float4 gl_MultiTexCoord2 : TEXCOORD2,\n" +"float4 gl_MultiTexCoord3 : TEXCOORD3,\n" +"uniform float4x4 TexMatrix : register(c0),\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"uniform float4x4 BackgroundTexMatrix : register(c4),\n" +"#endif\n" +"uniform float4x4 ModelViewMatrix : register(c12),\n" +"#ifdef USEOFFSETMAPPING\n" +"uniform float3 EyePosition : register(c24),\n" +"#endif\n" +"out float4 gl_Position : POSITION,\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"out float4 gl_FrontColor : COLOR,\n" +"#endif\n" +"out float4 TexCoordBoth : TEXCOORD0,\n" +"#ifdef USEOFFSETMAPPING\n" +"out float3 EyeVector : TEXCOORD2,\n" +"#endif\n" +"out float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n" +"out float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n" +"out float4 VectorR : TEXCOORD7 // direction of R texcoord (surface normal), Depth value\n" +")\n" +"{\n" +" TexCoordBoth = mul(TexMatrix, gl_MultiTexCoord0);\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" gl_FrontColor = gl_Color;\n" +" TexCoordBoth.zw = float2(Backgroundmul(TexMatrix, gl_MultiTexCoord0));\n" +"#endif\n" +"\n" +" // transform unnormalized eye direction into tangent space\n" +"#ifdef USEOFFSETMAPPING\n" +" float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n" +" EyeVector.x = dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz);\n" +" EyeVector.y = dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz);\n" +" EyeVector.z = dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz);\n" +"#endif\n" +"\n" +" VectorS = mul(ModelViewMatrix, float4(gl_MultiTexCoord1.xyz, 0)).xyz;\n" +" VectorT = mul(ModelViewMatrix, float4(gl_MultiTexCoord2.xyz, 0)).xyz;\n" +" VectorR.xyz = mul(ModelViewMatrix, float4(gl_MultiTexCoord3.xyz, 0)).xyz;\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +" VectorR.w = gl_Position.z;\n" +"}\n" +"#endif // VERTEX_SHADER\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main\n" +"(\n" +"float4 TexCoordBoth : TEXCOORD0,\n" +"float3 EyeVector : TEXCOORD2,\n" +"float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n" +"float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n" +"float4 VectorR : TEXCOORD7, // direction of R texcoord (surface normal), Depth value\n" +"uniform sampler Texture_Normal : register(s0),\n" +"#ifdef USEALPHAKILL\n" +"uniform sampler Texture_Color : register(s1),\n" +"#endif\n" +"uniform sampler Texture_Gloss : register(s2),\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"uniform sampler Texture_SecondaryNormal : register(s4),\n" +"uniform sampler Texture_SecondaryGloss : register(s6),\n" +"#endif\n" +"#ifdef USEOFFSETMAPPING\n" +"uniform float4 OffsetMapping_ScaleSteps : register(c24),\n" +"#endif\n" +"uniform half SpecularPower : register(c36),\n" +"#ifdef HLSL\n" +"out float4 gl_FragData0 : COLOR0,\n" +"out float4 gl_FragData1 : COLOR1\n" +"#else\n" +"out float4 gl_FragColor : COLOR\n" +"#endif\n" +")\n" +"{\n" +" float2 TexCoord = TexCoordBoth.xy;\n" +"#ifdef USEOFFSETMAPPING\n" +" // apply offsetmapping\n" +" float2 dPdx = ddx(TexCoord);\n" +" float2 dPdy = ddy(TexCoord);\n" +" float2 TexCoordOffset = OffsetMapping(TexCoord, OffsetMapping_ScaleSteps, EyeVector, Texture_Normal, dPdx, dPdy);\n" +"# define offsetMappedTexture2D(t) tex2Dgrad(t, TexCoordOffset, dPdx, dPdy)\n" +"#else\n" +"# define offsetMappedTexture2D(t) tex2D(t, TexCoord)\n" +"#endif\n" +"\n" +"#ifdef USEALPHAKILL\n" +" if (offsetMappedTexture2D(Texture_Color).a < 0.5)\n" +" discard;\n" +"#endif\n" +"\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" float alpha = offsetMappedTexture2D(Texture_Color).a;\n" +" float terrainblend = clamp(float(gl_FrontColor.a) * alpha * 2.0 - 0.5, float(0.0), float(1.0));\n" +" //float terrainblend = min(float(gl_FrontColor.a) * alpha * 2.0, float(1.0));\n" +" //float terrainblend = float(gl_FrontColor.a) * alpha > 0.5;\n" +"#endif\n" +"\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" float3 surfacenormal = lerp(tex2D(Texture_SecondaryNormal, TexCoord2).rgb, offsetMappedTexture2D(Texture_Normal).rgb, terrainblend) - float3(0.5, 0.5, 0.5);\n" +" float a = lerp(tex2D(Texture_SecondaryGloss, TexCoord2).a, offsetMappedTexture2D(Texture_Gloss).a, terrainblend);\n" +"#else\n" +" float3 surfacenormal = offsetMappedTexture2D(Texture_Normal).rgb - float3(0.5, 0.5, 0.5);\n" +" float a = offsetMappedTexture2D(Texture_Gloss).a;\n" +"#endif\n" +"\n" +"#ifdef HLSL\n" +" gl_FragData0 = float4(normalize(surfacenormal.x * VectorS + surfacenormal.y * VectorT + surfacenormal.z * VectorR.xyz) * 0.5 + float3(0.5, 0.5, 0.5), a);\n" +" float Depth = VectorR.w / 256.0;\n" +" float4 depthcolor = float4(Depth,Depth*65536.0/255.0,Depth*16777216.0/255.0,0.0);\n" +"// float4 depthcolor = float4(Depth,Depth*256.0,Depth*65536.0,0.0);\n" +" depthcolor.yz -= floor(depthcolor.yz);\n" +" gl_FragData1 = depthcolor;\n" +"#else\n" +" gl_FragColor = float4(normalize(surfacenormal.x * VectorS + surfacenormal.y * VectorT + surfacenormal.z * VectorR) * 0.5 + float3(0.5, 0.5, 0.5), a);\n" +"#endif\n" +"}\n" +"#endif // FRAGMENT_SHADER\n" +"#else // !MODE_DEFERREDGEOMETRY\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_DEFERREDLIGHTSOURCE\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"uniform float4x4 ModelViewMatrix : register(c12),\n" +"out float4 gl_Position : POSITION,\n" +"out float4 ModelViewPosition : TEXCOORD0\n" +")\n" +"{\n" +" ModelViewPosition = mul(ModelViewMatrix, gl_Vertex);\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +"}\n" +"#endif // VERTEX_SHADER\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main\n" +"(\n" +"#ifdef HLSL\n" +"float2 Pixel : VPOS,\n" +"#else\n" +"float2 Pixel : WPOS,\n" +"#endif\n" +"float4 ModelViewPosition : TEXCOORD0,\n" +"uniform float4x4 ViewToLight : register(c44),\n" +"uniform float2 ScreenToDepth : register(c33), // ScreenToDepth = float2(Far / (Far - Near), Far * Near / (Near - Far));\n" +"uniform float3 LightPosition : register(c23),\n" +"uniform half2 PixelToScreenTexCoord : register(c42),\n" +"uniform half3 DeferredColor_Ambient : register(c9),\n" +"uniform half3 DeferredColor_Diffuse : register(c10),\n" +"#ifdef USESPECULAR\n" +"uniform half3 DeferredColor_Specular : register(c11),\n" +"uniform half SpecularPower : register(c36),\n" +"#endif\n" +"uniform sampler Texture_Attenuation : register(s9),\n" +"uniform sampler Texture_ScreenDepth : register(s13),\n" +"uniform sampler Texture_ScreenNormalMap : register(s14),\n" +"\n" +"#ifdef USECUBEFILTER\n" +"uniform samplerCUBE Texture_Cube : register(s10),\n" +"#endif\n" +"\n" +"#ifdef USESHADOWMAP2D\n" +"# ifdef USESHADOWSAMPLER\n" +"uniform sampler Texture_ShadowMap2D : register(s15),\n" +"# else\n" +"uniform sampler Texture_ShadowMap2D : register(s15),\n" +"# endif\n" +"#endif\n" +"\n" +"#ifdef USESHADOWMAPVSDCT\n" +"uniform samplerCUBE Texture_CubeProjection : register(s12),\n" +"#endif\n" +"\n" +"#if defined(USESHADOWMAP2D)\n" +"uniform float2 ShadowMap_TextureScale : register(c35),\n" +"uniform float4 ShadowMap_Parameters : register(c34),\n" +"#endif\n" +"\n" +"out float4 gl_FragData0 : COLOR0,\n" +"out float4 gl_FragData1 : COLOR1\n" +")\n" +"{\n" +" // calculate viewspace pixel position\n" +" float2 ScreenTexCoord = Pixel * PixelToScreenTexCoord;\n" +" //ScreenTexCoord.y = ScreenTexCoord.y * -1 + 1; // Cg is opposite?\n" +" float3 position;\n" +"#ifdef HLSL\n" +" position.z = texDepth2D(Texture_ScreenDepth, ScreenTexCoord) * 256.0;\n" +"#else\n" +" position.z = ScreenToDepth.y / (texDepth2D(Texture_ScreenDepth, ScreenTexCoord) + ScreenToDepth.x);\n" +"#endif\n" +" position.xy = ModelViewPosition.xy * (position.z / ModelViewPosition.z);\n" +" // decode viewspace pixel normal\n" +" half4 normalmap = half4(tex2D(Texture_ScreenNormalMap, ScreenTexCoord));\n" +" half3 surfacenormal = half3(normalize(normalmap.rgb - half3(0.5,0.5,0.5)));\n" +" // surfacenormal = pixel normal in viewspace\n" +" // LightVector = pixel to light in viewspace\n" +" // CubeVector = position in lightspace\n" +" // eyevector = pixel to view in viewspace\n" +" float3 CubeVector = mul(ViewToLight, float4(position,1)).xyz;\n" +" half fade = half(tex2D(Texture_Attenuation, float2(length(CubeVector), 0.0)).r);\n" +"#ifdef USEDIFFUSE\n" +" // calculate diffuse shading\n" +" half3 lightnormal = half3(normalize(LightPosition - position));\n" +" half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n" +"#endif\n" +"#ifdef USESPECULAR\n" +" // calculate directional shading\n" +" float3 eyevector = position * -1.0;\n" +"# ifdef USEEXACTSPECULARMATH\n" +" half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(eyevector)))*-1.0, 0.0)), SpecularPower * normalmap.a));\n" +"# else\n" +" half3 specularnormal = half3(normalize(lightnormal + half3(normalize(eyevector))));\n" +" half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), SpecularPower * normalmap.a));\n" +"# endif\n" +"#endif\n" +"\n" +"#if defined(USESHADOWMAP2D)\n" +" fade *= half(ShadowMapCompare(CubeVector, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale\n" +"#ifdef USESHADOWMAPVSDCT\n" +", Texture_CubeProjection\n" +"#endif\n" +" ));\n" +"#endif\n" +"\n" +"#ifdef USEDIFFUSE\n" +" gl_FragData0 = float4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n" +"#else\n" +" gl_FragData0 = float4(DeferredColor_Ambient * fade, 1.0);\n" +"#endif\n" +"#ifdef USESPECULAR\n" +" gl_FragData1 = float4(DeferredColor_Specular * (specular * fade), 1.0);\n" +"#else\n" +" gl_FragData1 = float4(0.0, 0.0, 0.0, 1.0);\n" +"#endif\n" +"\n" +"# ifdef USECUBEFILTER\n" +" float3 cubecolor = texCUBE(Texture_Cube, CubeVector).rgb;\n" +" gl_FragData0.rgb *= cubecolor;\n" +" gl_FragData1.rgb *= cubecolor;\n" +"# endif\n" +"}\n" +"#endif // FRAGMENT_SHADER\n" +"#else // !MODE_DEFERREDLIGHTSOURCE\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef VERTEX_SHADER\n" +"void main\n" +"(\n" +"float4 gl_Vertex : POSITION,\n" +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n" +"#if defined(USEVERTEXTEXTUREBLEND) || defined(MODE_VERTEXCOLOR)\n" +"float4 gl_Color : COLOR0,\n" +"#endif\n" +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n" +"float4 gl_MultiTexCoord1 : TEXCOORD1,\n" +"float4 gl_MultiTexCoord2 : TEXCOORD2,\n" +"float4 gl_MultiTexCoord3 : TEXCOORD3,\n" +"float4 gl_MultiTexCoord4 : TEXCOORD4,\n" +"\n" +"uniform float3 EyePosition : register(c24),\n" +"uniform float4x4 TexMatrix : register(c0),\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"uniform float4x4 BackgroundTexMatrix : register(c4),\n" +"#endif\n" +"#ifdef MODE_LIGHTSOURCE\n" +"uniform float4x4 ModelToLight : register(c20),\n" +"#endif\n" +"#ifdef MODE_LIGHTSOURCE\n" +"uniform float3 LightPosition : register(c27),\n" +"#endif\n" +"#ifdef MODE_LIGHTDIRECTION\n" +"uniform float3 LightDir : register(c26),\n" +"#endif\n" +"uniform float4 FogPlane : register(c25),\n" +"#ifdef MODE_DEFERREDLIGHTSOURCE\n" +"uniform float3 LightPosition : register(c27),\n" +"#endif\n" +"#ifdef USESHADOWMAPORTHO\n" +"uniform float4x4 ShadowMapMatrix : register(c16),\n" +"#endif\n" +"#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND)\n" +"out float4 gl_FrontColor : COLOR,\n" +"#endif\n" +"out float4 TexCoordBoth : TEXCOORD0,\n" +"#ifdef USELIGHTMAP\n" +"out float2 TexCoordLightmap : TEXCOORD1,\n" +"#endif\n" +"#ifdef USEEYEVECTOR\n" +"out float3 EyeVector : TEXCOORD2,\n" +"#endif\n" +"#ifdef USEREFLECTION\n" +"out float4 ModelViewProjectionPosition : TEXCOORD3,\n" +"#endif\n" +"#ifdef USEFOG\n" +"out float4 EyeVectorModelSpaceFogPlaneVertexDist : TEXCOORD4,\n" +"#endif\n" +"#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE) || defined(USEDIFFUSE)\n" +"out float3 LightVector : TEXCOORD1,\n" +"#endif\n" +"#ifdef MODE_LIGHTSOURCE\n" +"out float3 CubeVector : TEXCOORD3,\n" +"#endif\n" +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE)\n" +"out float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n" +"out float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n" +"out float3 VectorR : TEXCOORD7, // direction of R texcoord (surface normal)\n" +"#endif\n" +"#ifdef USESHADOWMAPORTHO\n" +"out float3 ShadowMapTC : TEXCOORD3, // CONFLICTS WITH USEREFLECTION!\n" +"#endif\n" +"out float4 gl_Position : POSITION\n" +")\n" +"{\n" +"#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND)\n" +" gl_FrontColor = gl_Color;\n" +"#endif\n" +" // copy the surface texcoord\n" +" TexCoordBoth = mul(TexMatrix, gl_MultiTexCoord0);\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" TexCoordBoth.zw = mul(BackgroundTexMatrix, gl_MultiTexCoord0).xy;\n" +"#endif\n" +"#ifdef USELIGHTMAP\n" +" TexCoordLightmap = gl_MultiTexCoord4.xy;\n" +"#endif\n" +"\n" +"#ifdef MODE_LIGHTSOURCE\n" +" // transform vertex position into light attenuation/cubemap space\n" +" // (-1 to +1 across the light box)\n" +" CubeVector = mul(ModelToLight, gl_Vertex).xyz;\n" +"\n" +"# ifdef USEDIFFUSE\n" +" // transform unnormalized light direction into tangent space\n" +" // (we use unnormalized to ensure that it interpolates correctly and then\n" +" // normalize it per pixel)\n" +" float3 lightminusvertex = LightPosition - gl_Vertex.xyz;\n" +" LightVector.x = dot(lightminusvertex, gl_MultiTexCoord1.xyz);\n" +" LightVector.y = dot(lightminusvertex, gl_MultiTexCoord2.xyz);\n" +" LightVector.z = dot(lightminusvertex, gl_MultiTexCoord3.xyz);\n" +"# endif\n" +"#endif\n" +"\n" +"#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE)\n" +" LightVector.x = dot(LightDir, gl_MultiTexCoord1.xyz);\n" +" LightVector.y = dot(LightDir, gl_MultiTexCoord2.xyz);\n" +" LightVector.z = dot(LightDir, gl_MultiTexCoord3.xyz);\n" +"#endif\n" +"\n" +" // transform unnormalized eye direction into tangent space\n" +"#ifdef USEEYEVECTOR\n" +" float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n" +" EyeVector.x = dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz);\n" +" EyeVector.y = dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz);\n" +" EyeVector.z = dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz);\n" +"#endif\n" +"\n" +"#ifdef USEFOG\n" +" EyeVectorModelSpaceFogPlaneVertexDist.xyz = EyePosition - gl_Vertex.xyz;\n" +" EyeVectorModelSpaceFogPlaneVertexDist.w = dot(FogPlane, gl_Vertex);\n" +"#endif\n" +"\n" +"#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n" +" VectorS = gl_MultiTexCoord1.xyz;\n" +" VectorT = gl_MultiTexCoord2.xyz;\n" +" VectorR = gl_MultiTexCoord3.xyz;\n" +"#endif\n" +"\n" +" // transform vertex to camera space, using ftransform to match non-VS rendering\n" +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n" +"\n" +"#ifdef USESHADOWMAPORTHO\n" +" ShadowMapTC = mul(ShadowMapMatrix, gl_Position).xyz;\n" +"#endif\n" +"\n" +"#ifdef USEREFLECTION\n" +" ModelViewProjectionPosition = gl_Position;\n" +"#endif\n" +"#ifdef USETRIPPY\n" +" gl_Position = TrippyVertex(gl_Position);\n" +"#endif\n" +"}\n" +"#endif // VERTEX_SHADER\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef FRAGMENT_SHADER\n" +"void main\n" +"(\n" +"#ifdef USEDEFERREDLIGHTMAP\n" +"#ifdef HLSL\n" +"float2 Pixel : VPOS,\n" +"#else\n" +"float2 Pixel : WPOS,\n" +"#endif\n" +"#endif\n" +"float4 gl_FrontColor : COLOR,\n" +"float4 TexCoordBoth : TEXCOORD0,\n" +"#ifdef USELIGHTMAP\n" +"float2 TexCoordLightmap : TEXCOORD1,\n" +"#endif\n" +"#ifdef USEEYEVECTOR\n" +"float3 EyeVector : TEXCOORD2,\n" +"#endif\n" +"#ifdef USEREFLECTION\n" +"float4 ModelViewProjectionPosition : TEXCOORD3,\n" +"#endif\n" +"#ifdef USEFOG\n" +"float4 EyeVectorModelSpaceFogPlaneVertexDist : TEXCOORD4,\n" +"#endif\n" +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_LIGHTDIRECTION)\n" +"float3 LightVector : TEXCOORD1,\n" +"#endif\n" +"#ifdef MODE_LIGHTSOURCE\n" +"float3 CubeVector : TEXCOORD3,\n" +"#endif\n" +"#ifdef MODE_DEFERREDLIGHTSOURCE\n" +"float4 ModelViewPosition : TEXCOORD0,\n" +"#endif\n" +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE)\n" +"float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n" +"float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n" +"float3 VectorR : TEXCOORD7, // direction of R texcoord (surface normal)\n" +"#endif\n" +"#ifdef USESHADOWMAPORTHO\n" +"float3 ShadowMapTC : TEXCOORD3, // CONFLICTS WITH USEREFLECTION!\n" +"#endif\n" +"\n" +"uniform sampler Texture_Normal : register(s0),\n" +"uniform sampler Texture_Color : register(s1),\n" +"#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n" +"uniform sampler Texture_Gloss : register(s2),\n" +"#endif\n" +"#ifdef USEGLOW\n" +"uniform sampler Texture_Glow : register(s3),\n" +"#endif\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"uniform sampler Texture_SecondaryNormal : register(s4),\n" +"uniform sampler Texture_SecondaryColor : register(s5),\n" +"#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n" +"uniform sampler Texture_SecondaryGloss : register(s6),\n" +"#endif\n" +"#ifdef USEGLOW\n" +"uniform sampler Texture_SecondaryGlow : register(s7),\n" +"#endif\n" +"#endif\n" +"#ifdef USECOLORMAPPING\n" +"uniform sampler Texture_Pants : register(s4),\n" +"uniform sampler Texture_Shirt : register(s7),\n" +"#endif\n" +"#ifdef USEFOG\n" +"uniform sampler Texture_FogHeightTexture : register(s14),\n" +"uniform sampler Texture_FogMask : register(s8),\n" +"#endif\n" +"#ifdef USELIGHTMAP\n" +"uniform sampler Texture_Lightmap : register(s9),\n" +"#endif\n" +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n" +"uniform sampler Texture_Deluxemap : register(s10),\n" +"#endif\n" +"#ifdef USEREFLECTION\n" +"uniform sampler Texture_Reflection : register(s7),\n" +"#endif\n" +"\n" +"#ifdef MODE_DEFERREDLIGHTSOURCE\n" +"uniform sampler Texture_ScreenDepth : register(s13),\n" +"uniform sampler Texture_ScreenNormalMap : register(s14),\n" +"#endif\n" +"#ifdef USEDEFERREDLIGHTMAP\n" +"uniform sampler Texture_ScreenDepth : register(s13),\n" +"uniform sampler Texture_ScreenNormalMap : register(s14),\n" +"uniform sampler Texture_ScreenDiffuse : register(s11),\n" +"uniform sampler Texture_ScreenSpecular : register(s12),\n" +"#endif\n" +"\n" +"#ifdef USECOLORMAPPING\n" +"uniform half3 Color_Pants : register(c7),\n" +"uniform half3 Color_Shirt : register(c8),\n" +"#endif\n" +"#ifdef USEFOG\n" +"uniform float3 FogColor : register(c16),\n" +"uniform float FogRangeRecip : register(c20),\n" +"uniform float FogPlaneViewDist : register(c19),\n" +"uniform float FogHeightFade : register(c17),\n" +"#endif\n" +"\n" +"#ifdef USEOFFSETMAPPING\n" +"uniform float4 OffsetMapping_ScaleSteps : register(c24),\n" +"#endif\n" +"\n" +"#ifdef USEDEFERREDLIGHTMAP\n" +"uniform half2 PixelToScreenTexCoord : register(c42),\n" +"uniform half3 DeferredMod_Diffuse : register(c12),\n" +"uniform half3 DeferredMod_Specular : register(c13),\n" +"#endif\n" +"uniform half3 Color_Ambient : register(c3),\n" +"uniform half3 Color_Diffuse : register(c4),\n" +"uniform half3 Color_Specular : register(c5),\n" +"uniform half SpecularPower : register(c36),\n" +"#ifdef USEGLOW\n" +"uniform half3 Color_Glow : register(c6),\n" +"#endif\n" +"uniform half Alpha : register(c0),\n" +"#ifdef USEREFLECTION\n" +"uniform float4 DistortScaleRefractReflect : register(c14),\n" +"uniform float4 ScreenScaleRefractReflect : register(c32),\n" +"uniform float4 ScreenCenterRefractReflect : register(c31),\n" +"uniform half4 ReflectColor : register(c26),\n" +"#endif\n" +"#ifdef USEREFLECTCUBE\n" +"uniform float4x4 ModelToReflectCube : register(c48),\n" +"uniform sampler Texture_ReflectMask : register(s5),\n" +"uniform samplerCUBE Texture_ReflectCube : register(s6),\n" +"#endif\n" +"#ifdef MODE_LIGHTDIRECTION\n" +"uniform half3 LightColor : register(c21),\n" +"#endif\n" +"#ifdef MODE_LIGHTSOURCE\n" +"uniform half3 LightColor : register(c21),\n" +"#endif\n" +"\n" +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE)\n" +"uniform sampler Texture_Attenuation : register(s9),\n" +"uniform samplerCUBE Texture_Cube : register(s10),\n" +"#endif\n" +"\n" +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n" +"\n" +"#ifdef USESHADOWMAP2D\n" +"# ifdef USESHADOWSAMPLER\n" +"uniform sampler Texture_ShadowMap2D : register(s15),\n" +"# else\n" +"uniform sampler Texture_ShadowMap2D : register(s15),\n" +"# endif\n" +"#endif\n" +"\n" +"#ifdef USESHADOWMAPVSDCT\n" +"uniform samplerCUBE Texture_CubeProjection : register(s12),\n" +"#endif\n" +"\n" +"#if defined(USESHADOWMAP2D)\n" +"uniform float2 ShadowMap_TextureScale : register(c35),\n" +"uniform float4 ShadowMap_Parameters : register(c34),\n" +"#endif\n" +"#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n" +"\n" +"out float4 gl_FragColor : COLOR\n" +")\n" +"{\n" +" float2 TexCoord = TexCoordBoth.xy;\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" float2 TexCoord2 = TexCoordBoth.zw;\n" +"#endif\n" +"#ifdef USEOFFSETMAPPING\n" +" // apply offsetmapping\n" +" float2 dPdx = ddx(TexCoord);\n" +" float2 dPdy = ddy(TexCoord);\n" +" float2 TexCoordOffset = OffsetMapping(TexCoord, OffsetMapping_ScaleSteps, EyeVector, Texture_Normal, dPdx, dPdy);\n" +"# define offsetMappedTexture2D(t) tex2Dgrad(t, TexCoordOffset, dPdx, dPdy)\n" +"#else\n" +"# define offsetMappedTexture2D(t) tex2D(t, TexCoord)\n" +"#endif\n" +"\n" +" // combine the diffuse textures (base, pants, shirt)\n" +" half4 color = half4(offsetMappedTexture2D(Texture_Color));\n" +"#ifdef USEALPHAKILL\n" +" if (color.a < 0.5)\n" +" discard;\n" +"#endif\n" +" color.a *= Alpha;\n" +"#ifdef USECOLORMAPPING\n" +" color.rgb += half3(offsetMappedTexture2D(Texture_Pants).rgb) * Color_Pants + half3(offsetMappedTexture2D(Texture_Shirt).rgb) * Color_Shirt;\n" +"#endif\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +"#ifdef USEBOTHALPHAS\n" +" half4 color2 = half4(tex2D(Texture_SecondaryColor, TexCoord2));\n" +" half terrainblend = clamp(half(gl_FrontColor.a) * color.a, half(1.0 - color2.a), half(1.0));\n" +" color.rgb = lerp(color2.rgb, color.rgb, terrainblend);\n" +"#else\n" +" half terrainblend = clamp(half(gl_FrontColor.a) * color.a * 2.0 - 0.5, half(0.0), half(1.0));\n" +" //half terrainblend = min(half(gl_FrontColor.a) * color.a * 2.0, half(1.0));\n" +" //half terrainblend = half(gl_FrontColor.a) * color.a > 0.5;\n" +" color.rgb = half3(lerp(tex2D(Texture_SecondaryColor, TexCoord2).rgb, float3(color.rgb), terrainblend));\n" +" color.a = 1.0;\n" +" //color = half4(lerp(float4(1, 0, 0, 1), color, terrainblend));\n" +"#endif\n" +"#endif\n" +"\n" +" // get the surface normal\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" half3 surfacenormal = normalize(half3(lerp(tex2D(Texture_SecondaryNormal, TexCoord2).rgb, offsetMappedTexture2D(Texture_Normal).rgb, terrainblend)) - half3(0.5, 0.5, 0.5));\n" +"#else\n" +" half3 surfacenormal = half3(normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5, 0.5, 0.5)));\n" +"#endif\n" +"\n" +" // get the material colors\n" +" half3 diffusetex = color.rgb;\n" +"#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n" +"# ifdef USEVERTEXTEXTUREBLEND\n" +" half4 glosstex = half4(lerp(tex2D(Texture_SecondaryGloss, TexCoord2), offsetMappedTexture2D(Texture_Gloss), terrainblend));\n" +"# else\n" +" half4 glosstex = half4(offsetMappedTexture2D(Texture_Gloss));\n" +"# endif\n" +"#endif\n" +"\n" +"#ifdef USEREFLECTCUBE\n" +" float3 TangentReflectVector = reflect(-EyeVector, surfacenormal);\n" +" float3 ModelReflectVector = TangentReflectVector.x * VectorS + TangentReflectVector.y * VectorT + TangentReflectVector.z * VectorR;\n" +" float3 ReflectCubeTexCoord = mul(ModelToReflectCube, float4(ModelReflectVector, 0)).xyz;\n" +" diffusetex += half3(offsetMappedTexture2D(Texture_ReflectMask).rgb) * half3(texCUBE(Texture_ReflectCube, ReflectCubeTexCoord).rgb);\n" +"#endif\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_LIGHTSOURCE\n" +" // light source\n" +"#ifdef USEDIFFUSE\n" +" half3 lightnormal = half3(normalize(LightVector));\n" +" half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n" +" color.rgb = diffusetex * (Color_Ambient + diffuse * Color_Diffuse);\n" +"#ifdef USESPECULAR\n" +"#ifdef USEEXACTSPECULARMATH\n" +" half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(EyeVector)))*-1.0, 0.0)), SpecularPower * glosstex.a));\n" +"#else\n" +" half3 specularnormal = half3(normalize(lightnormal + half3(normalize(EyeVector))));\n" +" half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), SpecularPower * glosstex.a));\n" +"#endif\n" +" color.rgb += glosstex.rgb * (specular * Color_Specular);\n" +"#endif\n" +"#else\n" +" color.rgb = diffusetex * Color_Ambient;\n" +"#endif\n" +" color.rgb *= LightColor;\n" +" color.rgb *= half(tex2D(Texture_Attenuation, float2(length(CubeVector), 0.0)).r);\n" +"#if defined(USESHADOWMAP2D)\n" +" color.rgb *= half(ShadowMapCompare(CubeVector, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale\n" +"#ifdef USESHADOWMAPVSDCT\n" +", Texture_CubeProjection\n" +"#endif\n" +" ));\n" +"\n" +"#endif\n" +"# ifdef USECUBEFILTER\n" +" color.rgb *= half3(texCUBE(Texture_Cube, CubeVector).rgb);\n" +"# endif\n" +"\n" +"#ifdef USESHADOWMAP2D\n" +"#ifdef USESHADOWMAPVSDCT\n" +"// float3 shadowmaptc = GetShadowMapTC2D(CubeVector, ShadowMap_Parameters, Texture_CubeProjection);\n" +"#else\n" +"// float3 shadowmaptc = GetShadowMapTC2D(CubeVector, ShadowMap_Parameters);\n" +"#endif\n" +"// color.rgb = half3(tex2D(Texture_ShadowMap2D, float2(0.1,0.1)).rgb);\n" +"// color.rgb = half3(tex2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale).rgb);\n" +"// color.rgb = half3(shadowmaptc.xyz * float3(ShadowMap_TextureScale,1.0));\n" +"// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n" +"// color.rgb = half3(tex2D(Texture_ShadowMap2D, float2(0.1,0.1)).rgb);\n" +"// color.rgb = half3(tex2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale).rgb);\n" +"// color.rgb = half3(shadowmaptc.xyz * float3(ShadowMap_TextureScale,1.0));\n" +"// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n" +"// color.r = half(shadowmaptc.z - texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n" +"// color.r = half(shadowmaptc.z);\n" +"// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n" +"// color.r = half(shadowmaptc.z);\n" +"// color.r = 1;\n" +"// color.rgb = abs(CubeVector);\n" +"#endif\n" +"// color.rgb = half3(1,1,1);\n" +"#endif // MODE_LIGHTSOURCE\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_LIGHTDIRECTION\n" +"#define SHADING\n" +"#ifdef USEDIFFUSE\n" +" half3 lightnormal = half3(normalize(LightVector));\n" +"#endif\n" +"#define lightcolor LightColor\n" +"#endif // MODE_LIGHTDIRECTION\n" +"#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n" +"#define SHADING\n" +" // deluxemap lightmapping using light vectors in modelspace (q3map2 -light -deluxe)\n" +" half3 lightnormal_modelspace = half3(tex2D(Texture_Deluxemap, TexCoordLightmap).rgb) * 2.0 + half3(-1.0, -1.0, -1.0);\n" +" half3 lightcolor = half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb);\n" +" // convert modelspace light vector to tangentspace\n" +" half3 lightnormal;\n" +" lightnormal.x = dot(lightnormal_modelspace, half3(VectorS));\n" +" lightnormal.y = dot(lightnormal_modelspace, half3(VectorT));\n" +" lightnormal.z = dot(lightnormal_modelspace, half3(VectorR));\n" +" // calculate directional shading (and undoing the existing angle attenuation on the lightmap by the division)\n" +" // note that q3map2 is too stupid to calculate proper surface normals when q3map_nonplanar\n" +" // is used (the lightmap and deluxemap coords correspond to virtually random coordinates\n" +" // on that luxel, and NOT to its center, because recursive triangle subdivision is used\n" +" // to map the luxels to coordinates on the draw surfaces), which also causes\n" +" // deluxemaps to be wrong because light contributions from the wrong side of the surface\n" +" // are added up. To prevent divisions by zero or strong exaggerations, a max()\n" +" // nudge is done here at expense of some additional fps. This is ONLY needed for\n" +" // deluxemaps, tangentspace deluxemap avoid this problem by design.\n" +" lightcolor *= 1.0 / max(0.25, lightnormal.z);\n" +"#endif // MODE_LIGHTDIRECTIONMAP_MODELSPACE\n" +"#ifdef MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n" +"#define SHADING\n" +" // deluxemap lightmapping using light vectors in tangentspace (hmap2 -light)\n" +" half3 lightnormal = half3(tex2D(Texture_Deluxemap, TexCoordLightmap).rgb) * 2.0 + half3(-1.0, -1.0, -1.0);\n" +" half3 lightcolor = half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb);\n" +"#endif\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_FAKELIGHT\n" +"#define SHADING\n" +"half3 lightnormal = half3(normalize(EyeVector));\n" +"half3 lightcolor = half3(1.0,1.0,1.0);\n" +"#endif // MODE_FAKELIGHT\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef MODE_LIGHTMAP\n" +" color.rgb = diffusetex * (Color_Ambient + half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb) * Color_Diffuse);\n" +"#endif // MODE_LIGHTMAP\n" +"#ifdef MODE_VERTEXCOLOR\n" +" color.rgb = diffusetex * (Color_Ambient + half3(gl_FrontColor.rgb) * Color_Diffuse);\n" +"#endif // MODE_VERTEXCOLOR\n" +"#ifdef MODE_FLATCOLOR\n" +" color.rgb = diffusetex * Color_Ambient;\n" +"#endif // MODE_FLATCOLOR\n" +"\n" +"\n" +"\n" +"\n" +"#ifdef SHADING\n" +"# ifdef USEDIFFUSE\n" +" half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n" +"# ifdef USESPECULAR\n" +"# ifdef USEEXACTSPECULARMATH\n" +" half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(EyeVector)))*-1.0, 0.0)), SpecularPower * glosstex.a));\n" +"# else\n" +" half3 specularnormal = half3(normalize(lightnormal + half3(normalize(EyeVector))));\n" +" half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), SpecularPower * glosstex.a));\n" +"# endif\n" +" color.rgb = diffusetex * Color_Ambient + (diffusetex * Color_Diffuse * diffuse + glosstex.rgb * Color_Specular * specular) * lightcolor;\n" +"# else\n" +" color.rgb = diffusetex * (Color_Ambient + Color_Diffuse * diffuse * lightcolor);\n" +"# endif\n" +"# else\n" +" color.rgb = diffusetex * Color_Ambient;\n" +"# endif\n" +"#endif\n" +"\n" +"#ifdef USESHADOWMAPORTHO\n" +" color.rgb *= half(ShadowMapCompare(ShadowMapTC, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale));\n" +"#endif\n" +"\n" +"#ifdef USEDEFERREDLIGHTMAP\n" +" float2 ScreenTexCoord = Pixel * PixelToScreenTexCoord;\n" +" color.rgb += diffusetex * half3(tex2D(Texture_ScreenDiffuse, ScreenTexCoord).rgb) * DeferredMod_Diffuse;\n" +" color.rgb += glosstex.rgb * half3(tex2D(Texture_ScreenSpecular, ScreenTexCoord).rgb) * DeferredMod_Specular;\n" +"// color.rgb = half3(tex2D(Texture_ScreenDepth, ScreenTexCoord).rgb);\n" +"// color.r = half(texDepth2D(Texture_ScreenDepth, ScreenTexCoord)) * 1.0;\n" +"#endif\n" +"\n" +"#ifdef USEGLOW\n" +"#ifdef USEVERTEXTEXTUREBLEND\n" +" color.rgb += half3(lerp(tex2D(Texture_SecondaryGlow, TexCoord2).rgb, offsetMappedTexture2D(Texture_Glow).rgb, terrainblend)) * Color_Glow;\n" +"#else\n" +" color.rgb += half3(offsetMappedTexture2D(Texture_Glow).rgb) * Color_Glow;\n" +"#endif\n" +"#endif\n" +"\n" +"#ifdef USEFOG\n" +" color.rgb = half3(FogVertex(color, FogColor, EyeVectorModelSpaceFogPlaneVertexDist.xyz, EyeVectorModelSpaceFogPlaneVertexDist.w, FogRangeRecip, FogPlaneViewDist, FogHeightFade, Texture_FogMask, Texture_FogHeightTexture));\n" +"#endif\n" +"\n" +" // reflection must come last because it already contains exactly the correct fog (the reflection render preserves camera distance from the plane, it only flips the side) and ContrastBoost/SceneBrightness\n" +"#ifdef USEREFLECTION\n" +" float4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n" +" //float4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5,0.5,0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" +" float2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW.zw + ScreenCenterRefractReflect.zw;\n" +" float2 ScreenTexCoord = SafeScreenTexCoord + float3(normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5,0.5,0.5))).xy * DistortScaleRefractReflect.zw;\n" +" // FIXME temporary hack to detect the case that the reflection\n" +" // gets blackened at edges due to leaving the area that contains actual\n" +" // content.\n" +" // Remove this 'ack once we have a better way to stop this thing from\n" +" // 'appening.\n" +" float f = min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(0.01, -0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(-0.01, 0.01)).rgb) / 0.05);\n" +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(-0.01, -0.01)).rgb) / 0.05);\n" +" ScreenTexCoord = lerp(SafeScreenTexCoord, ScreenTexCoord, f);\n" +" color.rgb = lerp(color.rgb, half3(tex2D(Texture_Reflection, ScreenTexCoord).rgb) * ReflectColor.rgb, ReflectColor.a);\n" +"#endif\n" +"\n" +" gl_FragColor = float4(color);\n" +"}\n" +"#endif // FRAGMENT_SHADER\n" +"\n" +"#endif // !MODE_DEFERREDLIGHTSOURCE\n" +"#endif // !MODE_DEFERREDGEOMETRY\n" +"#endif // !MODE_WATER\n" +"#endif // !MODE_REFRACTION\n" +"#endif // !MODE_BLOOMBLUR\n" +"#endif // !MODE_GENERIC\n" +"#endif // !MODE_POSTPROCESS\n" +"#endif // !MODE_SHOWDEPTH\n" +"#endif // !MODE_DEPTH_OR_SHADOW\n" diff --git a/misc/source/darkplaces-src/snd_3dras.c b/misc/source/darkplaces-src/snd_3dras.c new file mode 100644 index 00000000..8b71d279 --- /dev/null +++ b/misc/source/darkplaces-src/snd_3dras.c @@ -0,0 +1,1042 @@ +// BSD + +#include "quakedef.h" +#include "snd_3dras_typedefs.h" +#include "snd_3dras.h" + +cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg)"}; +cvar_t mastervolume = {CVAR_SAVE, "mastervolume", "1", "master volume"}; +cvar_t volume = {CVAR_SAVE, "volume", "0.7", "volume of sound effects"}; +cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1", "volume of ambient sound effects (such as swampy sounds at the start of e1m2)"}; +cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0", "indicates the sound subsystem is active"}; +cvar_t snd_mutewhenidle = {CVAR_SAVE, "snd_mutewhenidle", "1", "whether to disable sound output when game window is inactive"}; +static cvar_t snd_precache = {0, "snd_precache", "1", "loads sounds before they are used"}; + +static dllhandle_t ras_dll = NULL; +// This values is used as a multiple version support check AND to check if the lib has been loaded with success (its 0 if it failed) +int ras_version; + +static mempool_t *snd_mempool; +static sfx_t sfx_list ={//This is a header, never contains only useful data, only the first next (makes the code less complex, later) + NULL, //next + "", //name[MAX_QPATH]; + NULL, //sounddata + 0, //locks + 0 //flags + //0, //loopstart, + //0 //total_length +}; +static unsigned int channel_id_count=0; +static channel_t channel_list={ + NULL, //next + NULL, //soundevent + 0, //entnum + 0, //entchannel + 0 //id +}; +static entnum_t entnum_list={ + NULL,// *next; + 0, // entnum; + {0.0,0.0,0.0}, //lastloc + NULL,// *soundsource;// This is also used to indicate a unused slot (when it's pointing to 0) +}; + +int updatecount=0; +int soundblocked=0; +int openframe; +void* soundworld; +void* listener; +float listener_location [3]; + +//1 qu = 0.0381 meter aka 38.1 mm +//2^17 qu's is the max map size in DP +//3DRAS uses atleast 32 bit to store it's locations. +//So the smallest possible step is 0.0381*2^17/2^(32) +// =~ 1.16 nm so let's pick 2 to be safe +static float DP_Ras_UnitSize=(float)2/1000/1000; //2 nm +static float DP_Ras_VolumeScale=0.075; +//static float QU_Size = 0.0381; //meter +//static float DP_QU_Ras_Scale=QU_Size/DP_Ras_UnitSize; +static float DP_QU_Ras_Scale=19050; +static void* (*ras_delete )(void*); +static int (*ras_getversion )(); +static void* (*ras_soundworld_new )(SampleRate, WaveLength); +static void (*ras_soundworld_destroy )(void*); +static void (*ras_soundworld_endframe )(void*); +static int (*ras_soundworld_beginframe )(void*); +static void (*ras_soundworld_setmainlistener )(void*,void*); +static void (*ras_soundworld_setscale )(void*,const Scale); +static void* (*ras_fileinputwhole_new )(unsigned char*, Index); +static void* (*ras_audiodecoderwav_new )(void*, int); +static void* (*ras_audiodecoderogg_new )(void*); +static void* (*ras_sounddataoneshot_new )(void*,WaveLength,Amount); +static void* (*ras_sounddataloop_new )(void*,WaveLength,Amount); +static void* (*ras_listener_new )(void*,Location*,Scalar*); +static void* (*ras_listener_setlocation )(void*,Location*); +static void* (*ras_listener_setrotation )(void*,Scalar *,Scalar*,Scalar*); +static void* (*ras_soundsource_new )(void*,SoundVolume,Location*); +static int (*ras_soundsource_ended )(void*); +static void (*ras_soundsource_setlocation )(void*,Location*); +static void* (*ras_soundevent_new )(void*,void*,void*,SoundPower,Ratio); +static void (*ras_soundevent_setsoundpower )(void*,SoundPower); +static int (*ras_soundevent_ended )(void*); +static int (*ras_setcoordinatesystem )(Location*,Location*,Location*); +static int (*ras_testrotation )(Scalar *,Scalar *,Scalar *); + +// #define RAS_PRINT //Comment out for to not print extra crap. + +static dllfunction_t ras_funcs[] = +{ + {"Delete" ,(void**) &ras_delete }, + {"SetCoordinateSystem" ,(void**) &ras_setcoordinatesystem }, + {"TestRotation" ,(void**) &ras_testrotation }, + {"GetVersion" ,(void**) &ras_getversion }, + {"SoundWorld_New" ,(void**) &ras_soundworld_new }, + {"SoundWorld_Destroy" ,(void**) &ras_soundworld_destroy }, + {"SoundWorld_EndFrame" ,(void**) &ras_soundworld_endframe }, + {"SoundWorld_BeginFrame" ,(void**) &ras_soundworld_beginframe }, + {"FileInputWhile_New" ,(void**) &ras_fileinputwhole_new }, + {"AudioDecoderFileWav_New" ,(void**) &ras_audiodecoderwav_new }, + {"AudioDecoderFileOgg_New" ,(void**) &ras_audiodecoderogg_new }, + {"SoundDataAudioDecoderOneShot_New" ,(void**) &ras_sounddataoneshot_new }, + //{"SoundDataAudioDecoderFileLoop_New" ,(void**) &ras_sounddataloop_new }, + {"SoundWorld_SetMainListener" ,(void**) &ras_soundworld_setmainlistener }, + {"SoundWorld_SetScale" ,(void**) &ras_soundworld_setscale }, + {"ListenerPlayer_New" ,(void**) &ras_listener_new }, + {"ListenerPlayer_SetLocation" ,(void**) &ras_listener_setlocation }, + {"ListenerPlayer_SetRotation_InVectors" ,(void**) &ras_listener_setrotation }, + {"SoundSource_Ended" ,(void**) &ras_soundsource_ended }, + {"SoundSourcePoint_New" ,(void**) &ras_soundsource_new }, + {"SoundSourcePoint_SetLocation" ,(void**) &ras_soundsource_setlocation }, + {"SoundEvent_New" ,(void**) &ras_soundevent_new }, + {"SoundEvent_Ended" ,(void**) &ras_soundevent_ended }, + {"SoundEvent_SetSoundPower" ,(void**) &ras_soundevent_setsoundpower }, + { NULL , NULL } +}; +static const char* ras_dllname [] = +{ +#if defined(WIN32) + "3dras32.dll", +#elif defined(MACOSX) + "3dras.dylib", +#else + "3dras.so", +#endif + NULL +}; + +// --- entnum_t List functions ---- +void entnum_new(entnum_t** prev, entnum_t** new){ //Adds a new item to the start of the list and sets the pointers. + (*new)=Mem_Alloc(snd_mempool,sizeof(entnum_t)); + if(&new){ + (*new)->next=entnum_list.next; + entnum_list.next=(*new); + (*prev)=&entnum_list; + }else{ + Con_Printf("Could not allocate memory for a new entnum_t"); + } +} +void entnum_begin(entnum_t** prev, entnum_t** now){ //Goes to the beginning of the list and sets the pointers. + (*prev)=&entnum_list; + (*now )=entnum_list.next; +} +void entnum_next(entnum_t** prev, entnum_t** now){ //Goes to the next element + (*prev)=(*now); + (*now )=(*now)->next; +} +void entnum_delete_and_next(entnum_t** prev, entnum_t** now){ //Deletes the element and goes to the next element + entnum_t* next; + next=(*now)->next; + if((*now)->rasptr) ras_delete((*now)->rasptr); + Mem_Free(*now); + (*now)=next; + (*prev)->next=(*now); +} +// --- End Of entnum_t List functions ---- + +// --- channel_t List functions ---- +void channel_new(channel_t** prev, channel_t** new){ //Adds a new item to the start of the list and sets the pointers. + (*new)=Mem_Alloc(snd_mempool,sizeof(channel_t)); + if(&new){ + (*new)->next=channel_list.next; + channel_list.next=(*new); + (*prev)=&channel_list; + }else{ + Con_Printf("Could not allocate memory for a new channel_t"); + } +} +void channel_begin(channel_t** prev, channel_t** now){ //Goes to the beginning of the list and sets the pointers. + (*prev)=&channel_list; + (*now )=channel_list.next; +} +void channel_next(channel_t** prev, channel_t** now){ //Goes to the next element + (*prev)=(*now ); + (*now )=(*now)->next; +} +void channel_delete_and_next(channel_t** prev, channel_t** now){ //Deletes the element and goes to the next element + channel_t* next; + next=(*now)->next; + if((*now)->rasptr) ras_delete((*now)->rasptr); + Mem_Free(*now); + (*now)=next; + (*prev)->next=(*now); +} +// --- End Of channel_t List functions ---- + +// --- sfx_t List functions ---- +void sfx_new(sfx_t** prev, sfx_t** new){ //Adds a new item to the start of the list and sets the pointers. + (*new)=Mem_Alloc(snd_mempool,sizeof(sfx_t)); + if(&new){ + (*new)->next=sfx_list.next; + sfx_list.next=(*new); + (*prev)=&sfx_list; + }else{ + Con_Printf("Could not allocate memory for a new sfx_t"); + } +} +void sfx_begin(sfx_t** prev, sfx_t** now){ //Goes to the beginning of the list and sets the pointers. + (*prev)=&sfx_list; + (*now )=sfx_list.next; +} +void sfx_next(sfx_t** prev, sfx_t** now){ //Goes to the next element + (*prev)=(*now ); + (*now )=(*now)->next; +} +void sfx_delete_and_next(sfx_t** prev, sfx_t** now){ //Deletes the element and goes to the next element + sfx_t* next; + next=(*now)->next; + if((*now)->rasptr) ras_delete((*now)->rasptr); + Mem_Free(*now); + (*now)=next; + (*prev)->next=(*now); +} +// --- End Of sfx_t List functions ---- + +void channel_new_smart(channel_t** prev, channel_t** now){ + channel_new(prev,now); + ++channel_id_count; + (*now)->id=channel_id_count; +} +void Free_Unlocked_Sfx(void){ + sfx_t *prev, *now; + sfx_begin(&prev,&now); + while(now){ + if( + !(now->flags & SFXFLAG_SERVERSOUND) && + now->locks<=0 + ){ + sfx_delete_and_next(&prev,&now); + }else{ + sfx_next(&prev,&now); + } + } +} +void DP_To_Ras_Location(const float in[3],Location out[3]){ + out[0]=(Location)(in[0]*DP_QU_Ras_Scale ); + out[1]=(Location)(in[1]*DP_QU_Ras_Scale ); + out[2]=(Location)(in[2]*DP_QU_Ras_Scale ); +} +void S_Restart_f(void){ + S_Shutdown(); + S_Startup(); +} +static void S_Play_Common (float fvol, float attenuation){ + int i; + char name [MAX_QPATH]; + sfx_t *sfx; + if(ras_version>0 && ras_dll){ + #ifdef RAS_PRINT + Con_Printf("Called S_Play_Common\n"); + Con_Printf("Does this need to be location in depend channel ?\n"); + #endif + + i = 1; + while (i < Cmd_Argc ()) + { + // Get the name, and appends ".wav" as an extension if there's none + strlcpy (name, Cmd_Argv (i), sizeof (name)); + if (!strrchr (name, '.')) + strlcat (name, ".wav", sizeof (name)); + i++; + + // If we need to get the volume from the command line + if (fvol == -1.0f) + { + fvol = atof (Cmd_Argv (i)); + i++; + } + + sfx = S_PrecacheSound (name, true, true); + if (sfx) + S_StartSound (-1, 0, sfx, listener_location, fvol, attenuation); + } + } +} +static void S_Play_f(void){ + S_Play_Common (1.0f, 1.0f); +} +static void S_Play2_f(void){ + S_Play_Common (1.0f, 0.0f); +} +static void S_PlayVol_f(void){ + S_Play_Common (-1.0f, 0.0f); +} +static void S_SoundList_f(void){ + channel_t *prev_c, *now_c; + sfx_t *prev_s, *now_s; + entnum_t *prev_e, *now_e; + int count_c,count_s,count_e; + + if(ras_version>0 && ras_dll){ + + Con_Printf("Sfx (SoundDatas) :\n" + "------------------\n" + "Locks\tflags\tpointer\tName\n"); + count_s=0; + sfx_begin(&prev_s,&now_s); + while(now_s){ + ++count_s; + Con_Printf("%i\t%i\t%i\t%s\n", + now_s->locks, now_s->flags, now_s->rasptr!=NULL, now_s->name + ); + sfx_next(&prev_s,&now_s); + } + + Con_Printf("Entnum (SoundSources) :\n" + "-----------------------\n" + "Ent\tpointer\n"); + count_e=0; + entnum_begin(&prev_e,&now_e); + while(now_e){ + ++count_e; + Con_Printf("%i\t%i\n", + now_e->entnum, now_e->rasptr!=NULL + ); + entnum_next(&prev_e,&now_e); + } + + Con_Printf("Channels (SoundEvents) :\n" + "------------------------\n" + "Ent\tChannel\tID\tpointer\n"); + count_c=0; + channel_begin(&prev_c,&now_c); + while(now_c){ + ++count_c; + Con_Printf("%i\t%i\t%i\t%i\n", + now_c->entnum, now_c->entchannel, now_c->id, now_c->rasptr!=NULL + ); + channel_next(&prev_c,&now_c); + } + + Con_Printf( + "Count:\n" + "------\n" + "Channels: %i\n" + "Sfx's: %i\n" + "Entities: %i\n", + count_c,count_s,count_e + ); + } +} +void Free_All_sfx(){ + sfx_t *prev, *now; + sfx_begin(&prev,&now); + while(now) sfx_delete_and_next(&prev,&now); +} +void Free_All_channel(){ + channel_t *prev, *now; + channel_begin(&prev,&now); + while(now) channel_delete_and_next(&prev,&now); +} +void Free_All_entnum(){ + entnum_t *prev, *now; + entnum_begin(&prev,&now); + while(now) entnum_delete_and_next(&prev,&now); +} +void S_Init (void){ + Location up[3],right[3],front[3]; + ras_version=0; + snd_mempool = Mem_AllocPool("sound", 0, NULL); + if(ras_dll) Con_Printf( "3D RAS already loaded ... (this indicates a bug)\n"); + if (Sys_LoadLibrary (ras_dllname, &ras_dll, ras_funcs)) + { + Con_Printf ("Loading 3D RAS succeeded\n"); + Con_Printf ("Checking the lib version\n"); + ras_version=ras_getversion(); + if (ras_version>0){ + + Con_Printf ("Version %i found\n",ras_version); + Cvar_RegisterVariable(&volume); + Cvar_RegisterVariable(&bgmvolume); + Cvar_RegisterVariable(&mastervolume); + Cvar_RegisterVariable(&snd_staticvolume); + Cvar_RegisterVariable(&snd_precache); + + Cmd_AddCommand("play", S_Play_f, "play a sound at your current location (not heard by anyone else)"); + Cmd_AddCommand("snd_play", S_Play_f, "play a sound at your current location (not heard by anyone else)"); + Cmd_AddCommand("play2", S_Play2_f, "play a sound globally throughout the level (not heard by anyone else)"); + Cmd_AddCommand("snd_play2", S_Play2_f, "play a sound globally throughout the level (not heard by anyone else)"); + Cmd_AddCommand("playvol", S_PlayVol_f, "play a sound at the specified volume level at your current location (not heard by anyone else)"); + Cmd_AddCommand("snd_playvol", S_PlayVol_f, "play a sound at the specified volume level at your current location (not heard by anyone else)"); + Cmd_AddCommand("stopsound", S_StopAllSounds, "silence"); + Cmd_AddCommand("soundlist", S_SoundList_f, "list loaded sounds"); + Cmd_AddCommand("snd_stopsound", S_StopAllSounds, "silence"); + Cmd_AddCommand("snd_soundlist", S_SoundList_f, "list loaded sounds"); + Cmd_AddCommand("snd_restart", S_Restart_f, "restart sound system"); + Cmd_AddCommand("snd_shutdown", S_Shutdown, "shutdown the sound system keeping the dll loaded"); + Cmd_AddCommand("snd_startup", S_Startup, "start the sound system"); + Cmd_AddCommand("snd_unloadallsounds", S_UnloadAllSounds_f, "unload all sound files"); + + //Set the coordinate system inside the lib equal to the one inside dp: + up[0]= 0 , up[1]= 0 , up[2]=1; + right[0]= 0 ,right[1]=-1 , right[2]=0; + front[0]= 1 ,front[1]= 0 , front[2]=0; + if(ras_setcoordinatesystem(right,up,front)==0){ + Con_Printf("Failed to set the Coordinate System\n"); + ras_version=0; + } + }else{ + Con_Printf ("Failed to get the lib version\n"); + Sys_UnloadLibrary (&ras_dll); + ras_dll=0; + } + }else{ + ras_dll=0; + Con_Printf ("Loading 3D RAS failed\n"); + Sys_UnloadLibrary (&ras_dll); + } +} +void S_Terminate (void){ + if(ras_dll){ + S_Shutdown(); + Free_All_sfx(); // <= The only valid place to free the sfx. + Sys_UnloadLibrary(&ras_dll); + ras_dll=0; + ras_version=0; + } +} + +void S_Startup (void){ + Location loc[3]={0, 0, 0}; + Scalar rot[4]={1.0, 0, 0, 0}; + if(ras_version>0 && ras_dll){ + channel_id_count=1; + soundworld= ras_soundworld_new(48000,0.1); + if(soundworld==0){ + Con_Printf("Failed to start a SoundWorld\n"); + }else{ + Con_Printf("Succeeded in starting a new SoundWorld\n"); + listener=ras_listener_new(soundworld,loc,rot); + ras_soundworld_setmainlistener(soundworld,listener); + openframe = ras_soundworld_beginframe(soundworld); + ras_soundworld_setscale(soundworld,DP_Ras_UnitSize); + } + } +} +void S_Shutdown (void){ + if(ras_version>0 && ras_dll && soundworld){ + if(openframe) ras_soundworld_endframe(soundworld); + + //Order doesn't really matter because the lib takes care of the references + //Free_All_sfx(); <= DO NOT FREE SFX ... they just keep sending in the old sfx causing havoc. + Free_All_channel(); + Free_All_entnum(); + + ras_soundworld_destroy(soundworld); + soundworld=ras_delete(soundworld); + if(soundworld){ + Con_Printf("Failed to stop the SoundWorld\n"); + }else{ + Con_Printf("Succeeded in stopping the SoundWorld\n"); + } + } +} +void S_UnloadAllSounds_f(void){ + if(ras_version>0 && ras_dll){ + Free_All_sfx(); + } +} + +void S_Update(const matrix4x4_t *listener_matrix){ + float forward [3]; + float left [3]; + float up [3]; + float float3 [3]; + Location location3 [3]; + entnum_t *prev_e, *now_e; + channel_t *prev_c, *now_c; + if(ras_version>0 && ras_dll && soundworld){ + Matrix4x4_ToVectors(listener_matrix,forward,left,up,listener_location); //Add the new player location. + if(openframe){ + VectorNegate(left,left); + DP_To_Ras_Location(listener_location,location3); + ras_listener_setlocation(listener,location3); + ras_listener_setrotation(listener,left,up,forward); + /* + Con_Printf( + "DP: Left={%f|%f|%f} Up={%f|%f|%f} Front={%f|%f|%f}\n", + left[0], left[1], left[2], + up[0], up[1], up[2], + forward[0],forward[1],forward[2] + ); + ras_testrotation(left,up,forward); + Con_Printf( + "RAS: Left={%f|%f|%f} Up={%f|%f|%f} Front={%f|%f|%f}\n", + left[0], left[1], left[2], + up[0], up[1], up[2], + forward[0],forward[1],forward[2] + ); + */ + if(updatecount>100){ + updatecount=0; + #ifdef RAS_PRINT + Con_Printf("S_Update: Add a callback to SCR_CaptureVideo_SoundFrame.\n"); + Con_Printf("S_Update: Add Slomo.\n"); + Con_Printf("S_Update: Add BlockedSoundCheck.\n"); + Con_Printf("S_Update: Add Slomo(as a cvar) and pauze.\n"); + #endif + }else{ + ++updatecount; + } + //(15:20:31) div0: (at the moment, you can extend it to multichannel) + //(15:20:40) div0: see S_CaptureAVISound() + if(cl.entities){ //if there is a list of ents + //Update all entities there position into the sound sources. + entnum_begin(&prev_e,&now_e); + while(now_e){ + if(!now_e->rasptr){ + Con_Printf("S_Update: Found an entnum_t without a valid RAS-ptr... This indicates a bug.\n"); + entnum_delete_and_next(&prev_e,&now_e); + }else{ //Look for unused ent and drop them. + if(now_e->entnum!=-1){ //Talking about an ent ? Or a static sound source ? + if(ras_soundsource_ended(now_e->rasptr)){ + VectorCopy(cl.entities[now_e->entnum].state_current.origin,float3); + VectorCopy(now_e->lastloc,float3); + DP_To_Ras_Location(float3,location3); + ras_soundsource_setlocation(now_e->rasptr,location3); + entnum_next(&prev_e,&now_e); + }else{ + entnum_delete_and_next(&prev_e,&now_e); + } + }else{ + if(ras_soundsource_ended(now_e->rasptr)){ + entnum_delete_and_next(&prev_e,&now_e); + }else{ + entnum_next(&prev_e,&now_e); + } + } + } + } + }else{ + Free_All_entnum(); + } + channel_begin(&prev_c,&now_c); + while(now_c){ + if(!now_c->rasptr){ + Con_Printf("S_Update: Found an channel_t without a valid RAS-ptr... This indicates a bug.\n"); + channel_delete_and_next(&prev_c,&now_c); + }else{ //Look for stopped sound channels and free them + if(ras_soundevent_ended(now_c->rasptr)){ + channel_delete_and_next(&prev_c,&now_c); + }else{ + channel_next(&prev_c,&now_c); + } + } + } + ras_soundworld_endframe (soundworld); + } + openframe =ras_soundworld_beginframe(soundworld); + } +} +void S_ExtraUpdate (void){ + // This lib is unable to use any extra updates. + //if(ras_version>0 && ras_dll){ + //} +} +sfx_t* S_FindName (const char *name){ + sfx_t *prev,*now; + if(ras_version>0 && ras_dll){ + #ifdef RAS_PRINT + Con_Printf("Called S_FindName %s\n",name); + #endif + + if (strlen (name) >= sizeof (now->name)) + { + Con_Printf ("S_FindName: sound name too long (%s)\n", name); + return NULL; + } + + sfx_begin(&prev,&now); + // Seek in list + while (now){ + if(strcmp (now->name, name)==0) return now; + sfx_next(&prev,&now); + } + + // None found in the list, + // Add a sfx_t struct for this sound + sfx_new(&prev,&now); + now->locks=0; + now->flags=0; + now->rasptr=0; + //sfx->looptstart=0; + //sfx->total_length=0; + strlcpy (now->name, name, sizeof (now->name)); + return now; + } + return NULL; +} +int S_LoadSound(sfx_t *sfx, int complain){ + // TODO add SCR_PushLoadingScreen, SCR_PopLoadingScreen calls to this + fs_offset_t filesize; + char namebuffer[MAX_QPATH +16 ]; + char filename [MAX_QPATH +16+4]; + char fileext [4]; + size_t len; + unsigned char *data=NULL; + void* file_ptr=NULL; + void* decoder_ptr=NULL; + if(ras_version>0 && ras_dll){ + + fileext[4]=0; //Terminator + // See if already loaded + if (sfx->rasptr) return true; + + // LordHavoc: if the sound filename does not begin with sound/, try adding it + if (!data && strncasecmp(sfx->name, "sound/", 6)) + { + len = dpsnprintf (namebuffer, sizeof(namebuffer), "sound/%s", sfx->name); + if (len < 0){ // name too long + Con_DPrintf("S_LoadSound: name \"%s\" is too long\n", sfx->name); + return false; + } + if(!data){ + data = FS_LoadFile(namebuffer, snd_mempool, false, &filesize); + if(data) memcpy(fileext,namebuffer+len-3,3); //Copy the extention + } + if(!data){ //Stick .wav to the end and try again + memcpy(filename,namebuffer,len); + memcpy(filename+len-4,".wav",5); + data = FS_LoadFile(filename, snd_mempool, false, &filesize); + if(data) memcpy(fileext,"wav",3); + } + if(!data){ //Stick .ogg to the end and try again + memcpy(filename,namebuffer,len); + memcpy(filename+len-4,".ogg",5); + data = FS_LoadFile(filename, snd_mempool, false, &filesize); + if(data) memcpy(fileext,"ogg",3); + } + } + if(!data){ + // LordHavoc: then try without the added sound/ as wav and ogg + len = dpsnprintf (namebuffer, sizeof(namebuffer), "%s", sfx->name); + if (len < 0){ // name too long + Con_DPrintf("S_LoadSound: name \"%s\" is too long\n", sfx->name); + return false; + } + if(!data){ + data = FS_LoadFile(namebuffer, snd_mempool, false, &filesize); + if(data) memcpy(fileext,namebuffer+len-3,3); //Copy the file extention + } + if(!data){ //Stick .wav to the end + memcpy(filename,namebuffer,len); + memcpy(filename+len-4,".wav",5); + data = FS_LoadFile(filename, snd_mempool, false, &filesize); + if(data) memcpy(fileext,"wav",3); + } + if(!data){ //Stick .ogg to the end + memcpy(filename,namebuffer,len); + memcpy(filename+len-4,".ogg",5); + data = FS_LoadFile(filename, snd_mempool, false, &filesize); + if(data) memcpy(fileext,"ogg",3); + } + } + if (!data){ + if(complain) Con_Printf("Failed attempt load file '%s'\n",namebuffer); + }else{ //if the file loaded: pass to RAS 3D + file_ptr=ras_fileinputwhole_new(data,filesize); + // There we transfered to file to RAS 3D + // So lets free up data shall we ? + FS_Close(data); + + if(!file_ptr){ + Con_Printf("Failed to upload file to audio lib\n"); + }else{ + if(0==strncasecmp(fileext,"wav",3)){ + decoder_ptr=ras_audiodecoderwav_new(file_ptr,true); //(true)use seek mode: some quake files are broken. + } + if(0==strncasecmp(fileext,"ogg",3)){ + decoder_ptr=ras_audiodecoderogg_new(file_ptr); + } + if(!decoder_ptr){ + Con_Printf("File succeeded to load, but no decoder available for '%s'\n",fileext); + }else{ + #ifdef RAS_PRINT + Con_Printf("ToDo: Add a cvar to configure the cache size and number of cache blocks.\n"); + Con_Printf("ToDo: Add support for looping sounds.\n"); + #endif + sfx->rasptr=ras_sounddataoneshot_new(decoder_ptr,0.05,8); + } + file_ptr=ras_delete(file_ptr); + } + return !(sfx->rasptr); + } + return false; + } + return false; +} +sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean serversound){ + sfx_t *sfx; + if(ras_version>0 && ras_dll){ + #ifdef RAS_PRINT + Con_Printf("Called S_PrecacheSound %s, %i, %i\n",name,complain,serversound); + #endif + if (name == NULL || name[0] == 0) + return NULL; + sfx = S_FindName (name); + if (sfx == NULL) return NULL; + if (lock) ++(sfx->locks); + if (snd_precache.integer) S_LoadSound(sfx, complain); + return sfx; + } + return NULL; +} +void S_ClearUsed (void){ + sfx_t *prev_s, *now_s; + unsigned int i; + + if(ras_version>0 && ras_dll){ + Con_Printf("Called S_ClearUsed\n"); + for(i=0;iflags & SFXFLAG_SERVERSOUND) now_s->flags &= ~SFXFLAG_SERVERSOUND; + sfx_next(&prev_s,&now_s); + } + } + } +} +void S_PurgeUnused(void){ + Free_Unlocked_Sfx(); +} +qboolean S_IsSoundPrecached (const sfx_t *sfx){ + if(ras_version>0 && ras_dll){ + return !sfx->rasptr; + } + return 0; +} + +void S_KillChannel (channel_t *now){ //Silences a SoundEvent + if(now->rasptr){ + ras_soundevent_setsoundpower(now->rasptr,0); + ras_delete(now->rasptr); + now->rasptr=0; + }else{ + Con_Printf("S_KillChannel: Warning pointer was 0 ... this indicates a bug.\n"); + } +} + +int S_StartSound_OnEnt (int entnum, int entchannel, sfx_t *sfx, float fvol, float attenuation){ + entnum_t *prev_e, *now_e; + channel_t *prev_c, *now_c; + Location tmp_location[3]; + + //If there is a game world + if(!cl.entities){ + Con_Printf("S_StartSound_OnEnt: no entity list exists\n"); + return -1; + } + + // Look for the correct ent_t + entnum_begin(&prev_e,&now_e); + while(now_e){ + if(now_e->entnum==entnum) break; + entnum_next(&prev_e,&now_e); + } + //We found no ent ... lets make one... + if(!now_e){ + entnum_new(&prev_e,&now_e); + if(!now_e){ + Con_Printf("S_StartSound_OnEnt: could not make new entnum_t\n"); + return -1; + } + VectorCopy(cl.entities[entnum].state_current.origin, now_e->lastloc); + DP_To_Ras_Location(now_e->lastloc,tmp_location); + now_e->rasptr=ras_soundsource_new(soundworld,1.0,tmp_location); + if(!now_e->rasptr){ + Con_Printf("S_StartSound_OnEnt: could not create a new sound source\n"); + return -1; + } + } + + //Ok now lets look for the channel. + channel_begin(&prev_c,&now_c); + while(now_c){ + if( + now_c->entnum==entnum && + now_c->entchannel==entchannel + ) break; + channel_next(&prev_c,&now_c); + } + + if(now_c){ //O dear the channel excists .... + S_KillChannel(now_c); + }else{ //We found no channel .... So we need to make a new one ... + channel_new_smart(&prev_c,&now_c); + now_c->entnum =entnum; + now_c->entchannel=entchannel; + if(!now_c){ + Con_Printf("S_StartSound_OnEnt: could not make new channel_t\n"); + channel_delete_and_next(&prev_c,&now_c); + return -1; + } + } + + //Lets start the sound on the acquired sound source and channel + now_c->rasptr=ras_soundevent_new( + soundworld,now_e->rasptr,sfx->rasptr,fvol*DP_Ras_VolumeScale,1.0 + ); + if(!now_c->rasptr){ //Whoops, failed, lets delete this channel then. + channel_delete_and_next(&prev_c,&now_c); + Con_Printf("S_StartSound_OnEnt: could not make a new soundevent.\n"); + return -1; + } + return now_c->id; +} +int S_StartSound_OnLocation (sfx_t *sfx, vec3_t origin, float fvol, float attenuation){ + entnum_t *prev_e, *now_e; + channel_t *prev_c, *now_c; + Location tmp_location[3]; + DP_To_Ras_Location(origin,tmp_location); + + entnum_new (&prev_e,&now_e); + VectorCopy(now_e->lastloc,origin); + now_e->entnum=-1; + now_e->rasptr=ras_soundsource_new(soundworld,1.0,tmp_location); + if(!now_e->rasptr){ + Con_Printf("S_StartSound_OnLocation: Could not make a new soundsource.\n"); + entnum_delete_and_next(&prev_e,&now_e); + return -1; + } + channel_new_smart(&prev_c,&now_c); + now_c->entnum=-1; + now_c->entchannel=-1; + now_c->rasptr =ras_soundevent_new(soundworld,now_e->rasptr,sfx->rasptr,fvol*DP_Ras_VolumeScale,1.0); + if(!now_c->rasptr){ + entnum_delete_and_next(&prev_e,&now_e); + channel_delete_and_next(&prev_c,&now_c); + Con_Printf("S_StartSound_OnLocation: Could not make a new soundevent.\n"); + return -1; + } + return now_c->id; +} + + +// Qantourisc on the wicked-quake-sound-system: +// -------------------------------------------- +// entnum can be Zero or lower => This means "use the origin" so it's not tracked. +// entnum -1 is a "world" containing more then 1 soundsource. +// If channel != 0 try to overwrite the requested channel. Otherwise play it on some random channel. +// If channel == -1 overwrite the first track of the ent. +// If a channel replacement is requested, only allow overwriting if it's owned by the same channel. +// If no channel can be replaced, pick a new one. +// Also when you overwrite a channel, that one has to stop dead. +// This function returns the channel it was played on (so it can be stopped later) +// This starts CD-music: S_StartSound (-1, 0, sfx, vec3_origin, cdvolume, 0); +// The channel you return then, can later be requested to be stopped. + +int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation){ + sfx_t *prev_s,*now_s; + int sfx_ok; + if(ras_version>0 && ras_dll && soundworld){ + #ifdef RAS_PRINT + Con_Printf("Called S_StartSound %i, %i, %f, %f\n",entnum,entchannel,fvol,attenuation); + #endif + if(sfx==NULL){ // They pass this to me ... but WHY ? it makes no sense ! + #ifdef RAS_PRINT + Con_Printf("S_StartSound: forgot to mention a sfx!\n"); + #endif + return -1; + } + + sfx_ok=0; + sfx_begin(&prev_s,&now_s); + while(now_s){ + if(now_s==sfx){ + sfx_ok=1; + break; + } + sfx_next(&prev_s,&now_s); + } + if(!sfx_ok){ + Con_Printf("S_StartSound: passed illegal sfx_t!\n"); + return -1; + } + if (!S_LoadSound(sfx,true)) return -1; + + + if(entnum!=-1){ //If we are talking about an ent + return S_StartSound_OnEnt(entnum,entchannel,sfx,fvol,attenuation); + }else{ + return S_StartSound_OnLocation( sfx,origin,fvol,attenuation); + } + } + Con_Printf("S_StartSound: engine not stated\n"); + return -1; +} +qboolean S_LocalSound (const char *s){ + sfx_t *sfx; + int ch_ind; + if(ras_version>0 && ras_dll){ + #ifdef RAS_PRINT + Con_Printf("Called S_LocalSound %s\n",s); + #endif + + sfx = S_PrecacheSound (s, true, true); + if (!sfx) + { + Con_Printf("S_LocalSound: can't precache %s\n", s); + return false; + } + + // Local sounds must not be freed + sfx->flags |= SFXFLAG_PERMANENTLOCK; + #ifdef RAS_PRINT + Con_Printf("S_LocalSound: this is still a small hack\n"); + #endif + ch_ind = S_StartSound (cl.viewentity, 0, sfx, listener_location, 1, 0); + if (ch_ind < 0) + return false; + + //channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; + return true; + } + return 0; +} +void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation){ + //Static sounds should not be looped + if(ras_version>0 && ras_dll){ + #ifdef RAS_PRINT + Con_Printf("Called S_StaticSound\n"); + Con_Printf("Waiting on Qantourisc to add Static sounds in his lib.\n"); + #endif + //Static sounds are sounds that are not pauzed, and or played locally. + } +} +void S_StopSound (int entnum, int entchannel){ + channel_t *prev, *now; + if(ras_version>0 && ras_dll){ + //Con_Printf("Called S_StopSound %i, %i\n",entnum,entchannel); + channel_begin(&prev,&now); + while(now){ + if(now->entnum==entnum && now->entchannel==entchannel) break; + channel_next(&prev,&now); + } + if(now){ //If we found our to delete sound. + S_KillChannel(now); + channel_delete_and_next(&prev,&now); + }else{ + Con_Printf("S_StopSound: Could not find the requested entnum-entchannel sound\n"); + } + } +} +void S_StopAllSounds (void){ + channel_t *prev, *now; + if(ras_version>0 && ras_dll){ + //Con_Printf("Called S_StopAllSounds\n"); + channel_begin(&prev,&now); + while(now){ + S_KillChannel(now); + channel_next(&prev,&now); + } + } +} +void S_PauseGameSounds (qboolean toggle){ + if(ras_version>0 && ras_dll){ + Con_Printf("Called S_PauseGameSounds %i\n",toggle); + //Localsounds should not be pauzed + } +} +void S_StopChannel (unsigned int channel_ind){ + channel_t *prev,*now; + if(ras_version>0 && ras_dll){ + channel_begin(&prev,&now); + while(now){ + if(now->id==channel_ind){ + S_KillChannel(now); + channel_delete_and_next(&prev,&now); + }else{ + channel_next(&prev,&now); + } + } + } +} +qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value){ + if(ras_version>0 && ras_dll){ + Con_Printf("Called S_SetChannelFlag %u, %u, %i\n",ch_ind, flag, value); + } + return 0; +} +void S_SetChannelVolume (unsigned int ch_ind, float fvol){ + channel_t *prev,*now; + if(ras_version>0 && ras_dll){ + Con_Printf("Called S_SetChannelVolume %u, %f\n",ch_ind, fvol); + channel_begin(&prev,&now); + while(now){ + if(now->id==ch_ind){ + if(now->rasptr){ + ras_soundevent_setsoundpower(now->rasptr,fvol*DP_Ras_VolumeScale); + }else{ + Con_Printf("S_StopChannel: Warning pointer was 0 ... this indicates a bug.\n"); + } + } + channel_next(&prev,&now); + } + } +} + +float S_GetChannelPosition (unsigned int ch_ind) +{ + // FIXME unsupported + return -1; +} + +void S_BlockSound (void){ + soundblocked++; +} +void S_UnblockSound (void){ + soundblocked--; + if(soundblocked<0){ + Con_Printf("S_UnblockSound: Requested more S_UnblockSound then S_BlockSound.\n"); + } +} + +int S_GetSoundRate (void){ + Con_Printf("Inside 3DRAS, the soundrate of the end-user is NONE of the dev's concern.\n"); + Con_Printf("So let's assume 44100.\n"); + return 44100; +} + +int S_GetSoundChannels (void){ + Con_Printf("Inside 3DRAS, the soundrate of the end-user is NONE of the dev's concern.\n"); + Con_Printf("So let's assume 2.\n"); + return 2; +} + +/* +==================== +SndSys_SendKeyEvents + +Send keyboard events originating from the sound system (e.g. MIDI) +==================== +*/ +void SndSys_SendKeyEvents(void) +{ + // not supported +} diff --git a/misc/source/darkplaces-src/snd_3dras.h b/misc/source/darkplaces-src/snd_3dras.h new file mode 100644 index 00000000..b735a6dd --- /dev/null +++ b/misc/source/darkplaces-src/snd_3dras.h @@ -0,0 +1,49 @@ +//BSD + +#ifndef SND_3DRAS_H +#define SND_3DRAS_H + +#include "sound.h" + +#define DEFAULT_SOUND_PACKET_VOLUME 255 +#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 + +#define CHANNELFLAG_NONE 0 +#define CHANNELFLAG_FORCELOOP (1 << 0) // force looping even if the sound is not looped +#define CHANNELFLAG_LOCALSOUND (1 << 1) // INTERNAL USE. Not settable by S_SetChannelFlag +#define CHANNELFLAG_PAUSED (1 << 2) +#define CHANNELFLAG_FULLVOLUME (1 << 3) // isn't affected by the general volume + +#define SFXFLAG_NONE 0 +//#define SFXFLAG_FILEMISSING (1 << 0) // wasn't able to load the associated sound file +#define SFXFLAG_SERVERSOUND (1 << 1) // the sfx is part of the server precache list +//#define SFXFLAG_STREAMED (1 << 2) // informative only. You shouldn't need to know that +#define SFXFLAG_PERMANENTLOCK (1 << 3) // can never be freed (ex: used by the client code) + +typedef struct channel_s{ + struct channel_s* next; + void* rasptr;//Sound Event // This is also used to indicate a unused slot (when it's pointing to 0) + int entnum;// to allow overriding a specific sound + int entchannel; + unsigned int id; +} channel_t; + +typedef struct entnum_s{ + struct entnum_s *next; + int entnum; + vec3_t lastloc; //Since DP has no way of tracking the deletion, we will use this instead (great jumps indicate teleport or new ent + void *rasptr;//Sound Source // This is also used to indicate a unused slot (when it's pointing to 0) +} entnum_t; + +struct sfx_s{ + struct sfx_s *next; + char name[MAX_QPATH]; + void* rasptr; //Sound Data// The sound data allocated in the lib + + int locks; + unsigned int flags; // cf SFXFLAG_* defines + //unsigned int loopstart; // in sample frames. equals total_length if not looped + //unsigned int total_length; // in sample frames +}; + +#endif diff --git a/misc/source/darkplaces-src/snd_3dras_typedefs.h b/misc/source/darkplaces-src/snd_3dras_typedefs.h new file mode 100644 index 00000000..a56ef9ec --- /dev/null +++ b/misc/source/darkplaces-src/snd_3dras_typedefs.h @@ -0,0 +1,57 @@ +// This file defines a few "basis measurement" types that extern program will need. +#ifndef Typedefs_h +#define Typedefs_h +#include + ///To address a location in a file. +typedef unsigned int FilePosition; + ///This will express an Amount of something. +typedef uint64_t Amount; + /// Index expresses an address in a certain array of data. +typedef uint64_t Index; + /// A signed index, to access things that can be access before 0 +typedef int64_t SignedIndex; + ///The depth at witch we are tracing. +typedef unsigned int TraceDepth; + ///The type of a Location (as in a messurement ... from 0) +typedef int64_t Location; + ///The type of a Location on a texture +typedef float TextureLocation; + ///The type of a Distance +typedef float Distance; + ///The type of a Scalar type for use in: Normal3D, Dot product, Direction3D, ... +typedef float Scalar; + ///Howmuch of something ? +typedef float Ratio; + ///The type of a an EulerAngle for use in: EulerAngle2D, EulerAngle3D, ... +typedef float EulerAngle; + ///The type that detemens the size of 1 Location. Expressed in meters/Location. +typedef float Scale; + ///The frequency of something. +typedef float Frequency; + ///The wavelength of a frequency +typedef Distance WaveLength; + /// Howmany samples we take per secod +typedef float SampleRate; + /// The type in witch we will express a SoundSample. +typedef float Sample; + /// The type that express the speed of sound (meter/second). +typedef float SoundSpeed; + /// The type that that express 1 Time. As in a small step. Note in the feature this will be a class. To make it ring. +typedef unsigned int Time; +typedef float Duration; +//typedef StrongType Time; // Example of a strong typecheck + /// The amplitude of the SoundPower. This for export to an AudioOutputDevice. +typedef float SoundVolume; + /// How mutch power per square meter is received per meter (Watt/Meter^2) +typedef float SoundIntensity; + /// An expression of the power of sound source (Watt) +typedef float SoundPower; // W, The power of the sound source + +typedef float LightIntensity; +typedef float LightPower; +typedef float Brightness; +typedef float Gamma; +typedef float Color; +typedef float RefractionIndex; +typedef unsigned int Resolution; +#endif diff --git a/misc/source/darkplaces-src/snd_alsa.c b/misc/source/darkplaces-src/snd_alsa.c new file mode 100644 index 00000000..3f4292c8 --- /dev/null +++ b/misc/source/darkplaces-src/snd_alsa.c @@ -0,0 +1,524 @@ +/* + Copyright (C) 2006 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +// ALSA module, used by Linux + +#include "quakedef.h" + +#include + +#include "snd_main.h" + + +#define NB_PERIODS 4 + +static snd_pcm_t* pcm_handle = NULL; +static snd_pcm_sframes_t expected_delay = 0; +static unsigned int alsasoundtime; + +static snd_seq_t* seq_handle = NULL; + +/* +==================== +SndSys_Init + +Create "snd_renderbuffer" with the proper sound format if the call is successful +May return a suggested format if the requested format isn't available +==================== +*/ +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) +{ + const char* pcm_name, *seq_name; + int i, err, seq_client, seq_port; + snd_pcm_hw_params_t* hw_params = NULL; + snd_pcm_format_t snd_pcm_format; + snd_pcm_uframes_t buffer_size; + + Con_Print ("SndSys_Init: using the ALSA module\n"); + + seq_name = NULL; +// COMMANDLINEOPTION: Linux ALSA Sound: -sndseqin : selects which sequencer port to use for input, by default no sequencer port is used (MIDI note events from that port get mapped to MIDINOTE keys that can be bound) + i = COM_CheckParm ("-sndseqin"); // TODO turn this into a cvar, maybe + if (i != 0 && i < com_argc - 1) + seq_name = com_argv[i + 1]; + if(seq_name) + { + seq_client = atoi(seq_name); + seq_port = 0; + if(strchr(seq_name, ':')) + seq_port = atoi(strchr(seq_name, ':') + 1); + Con_Printf ("SndSys_Init: seq input port has been set to \"%d:%d\". Enabling sequencer input...\n", seq_client, seq_port); + err = snd_seq_open (&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0); + if (err < 0) + { + Con_Print ("SndSys_Init: can't open seq device\n"); + goto seqdone; + } + err = snd_seq_set_client_name(seq_handle, gamename); + if (err < 0) + { + Con_Print ("SndSys_Init: can't set name of seq device\n"); + goto seqerror; + } + err = snd_seq_create_simple_port(seq_handle, gamename, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + if(err < 0) + { + Con_Print ("SndSys_Init: can't create seq port\n"); + goto seqerror; + } + err = snd_seq_connect_from(seq_handle, 0, seq_client, seq_port); + if(err < 0) + { + Con_Printf ("SndSys_Init: can't connect to seq port \"%d:%d\"\n", seq_client, seq_port); + goto seqerror; + } + err = snd_seq_nonblock(seq_handle, 1); + if(err < 0) + { + Con_Print ("SndSys_Init: can't make seq nonblocking\n"); + goto seqerror; + } + + goto seqdone; + +seqerror: + snd_seq_close(seq_handle); + seq_handle = NULL; + } + +seqdone: + // Check the requested sound format + if (requested->width < 1 || requested->width > 2) + { + Con_Printf ("SndSys_Init: invalid sound width (%hu)\n", + requested->width); + + if (suggested != NULL) + { + memcpy (suggested, requested, sizeof (*suggested)); + + if (requested->width < 1) + suggested->width = 1; + else + suggested->width = 2; + + Con_Printf ("SndSys_Init: suggesting sound width = %hu\n", + suggested->width); + } + + return false; + } + + if (pcm_handle != NULL) + { + Con_Print ("SndSys_Init: WARNING: Init called before Shutdown!\n"); + SndSys_Shutdown (); + } + + // Determine the name of the PCM handle we'll use + switch (requested->channels) + { + case 4: + pcm_name = "surround40"; + break; + case 6: + pcm_name = "surround51"; + break; + case 8: + pcm_name = "surround71"; + break; + default: + pcm_name = "default"; + break; + } +// COMMANDLINEOPTION: Linux ALSA Sound: -sndpcm selects which pcm device to use, default is "default" + i = COM_CheckParm ("-sndpcm"); + if (i != 0 && i < com_argc - 1) + pcm_name = com_argv[i + 1]; + + // Open the audio device + Con_Printf ("SndSys_Init: PCM device is \"%s\"\n", pcm_name); + err = snd_pcm_open (&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't open audio device \"%s\" (%s)\n", + pcm_name, snd_strerror (err)); + return false; + } + + // Allocate the hardware parameters + err = snd_pcm_hw_params_malloc (&hw_params); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't allocate hardware parameters (%s)\n", + snd_strerror (err)); + goto init_error; + } + err = snd_pcm_hw_params_any (pcm_handle, hw_params); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't initialize hardware parameters (%s)\n", + snd_strerror (err)); + goto init_error; + } + + // Set the access type + err = snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't set access type (%s)\n", + snd_strerror (err)); + goto init_error; + } + + // Set the sound width + if (requested->width == 1) + snd_pcm_format = SND_PCM_FORMAT_U8; + else + snd_pcm_format = SND_PCM_FORMAT_S16; + err = snd_pcm_hw_params_set_format (pcm_handle, hw_params, snd_pcm_format); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't set sound width to %hu (%s)\n", + requested->width, snd_strerror (err)); + goto init_error; + } + + // Set the sound channels + err = snd_pcm_hw_params_set_channels (pcm_handle, hw_params, requested->channels); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't set sound channels to %hu (%s)\n", + requested->channels, snd_strerror (err)); + goto init_error; + } + + // Set the sound speed + err = snd_pcm_hw_params_set_rate (pcm_handle, hw_params, requested->speed, 0); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't set sound speed to %u (%s)\n", + requested->speed, snd_strerror (err)); + goto init_error; + } + + // pick a buffer size that is a power of 2 (by masking off low bits) + buffer_size = i = (int)(requested->speed * 0.15f); + while (buffer_size & (buffer_size-1)) + buffer_size &= (buffer_size-1); + // then check if it is the nearest power of 2 and bump it up if not + if (i - buffer_size >= buffer_size >> 1) + buffer_size *= 2; + + err = snd_pcm_hw_params_set_buffer_size_near (pcm_handle, hw_params, &buffer_size); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't set sound buffer size to %lu (%s)\n", + buffer_size, snd_strerror (err)); + goto init_error; + } + + // pick a period size near the buffer_size we got from ALSA + snd_pcm_hw_params_get_buffer_size (hw_params, &buffer_size); + buffer_size /= NB_PERIODS; + err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &buffer_size, 0); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't set sound period size to %lu (%s)\n", + buffer_size, snd_strerror (err)); + goto init_error; + } + + err = snd_pcm_hw_params (pcm_handle, hw_params); + if (err < 0) + { + Con_Printf ("SndSys_Init: can't set hardware parameters (%s)\n", + snd_strerror (err)); + goto init_error; + } + + snd_pcm_hw_params_free (hw_params); + + snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL); + expected_delay = 0; + alsasoundtime = 0; + if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) + Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_ALSA); + + return true; + + +// It's not very clean, but it avoids a lot of duplicated code. +init_error: + + if (hw_params != NULL) + snd_pcm_hw_params_free (hw_params); + + snd_pcm_close(pcm_handle); + pcm_handle = NULL; + + return false; +} + + +/* +==================== +SndSys_Shutdown + +Stop the sound card, delete "snd_renderbuffer" and free its other resources +==================== +*/ +void SndSys_Shutdown (void) +{ + if (seq_handle != NULL) + { + snd_seq_close(seq_handle); + seq_handle = NULL; + } + + if (pcm_handle != NULL) + { + snd_pcm_close(pcm_handle); + pcm_handle = NULL; + } + + if (snd_renderbuffer != NULL) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } +} + + +/* +==================== +SndSys_Recover + +Try to recover from errors +==================== +*/ +static qboolean SndSys_Recover (int err_num) +{ + int err; + + // We can only do something on underrun ("broken pipe") errors + if (err_num != -EPIPE) + return false; + + err = snd_pcm_prepare (pcm_handle); + if (err < 0) + { + Con_Printf ("SndSys_Recover: unable to recover (%s)\n", + snd_strerror (err)); + + // TOCHECK: should we stop the playback ? + + return false; + } + + return true; +} + + +/* +==================== +SndSys_Write +==================== +*/ +static snd_pcm_sframes_t SndSys_Write (const unsigned char* buffer, unsigned int nbframes) +{ + snd_pcm_sframes_t written; + + written = snd_pcm_writei (pcm_handle, buffer, nbframes); + if (written < 0) + { + if (developer_insane.integer && vid_activewindow) + Con_DPrintf ("SndSys_Write: audio write returned %ld (%s)!\n", + written, snd_strerror (written)); + + if (SndSys_Recover (written)) + { + written = snd_pcm_writei (pcm_handle, buffer, nbframes); + if (written < 0) + Con_DPrintf ("SndSys_Write: audio write failed again (error %ld: %s)!\n", + written, snd_strerror (written)); + } + } + if (written > 0) + { + snd_renderbuffer->startframe += written; + expected_delay += written; + } + + return written; +} + + +/* +==================== +SndSys_Submit + +Submit the contents of "snd_renderbuffer" to the sound card +==================== +*/ +void SndSys_Submit (void) +{ + unsigned int startoffset, factor; + snd_pcm_uframes_t limit, nbframes; + snd_pcm_sframes_t written; + + if (pcm_handle == NULL || + snd_renderbuffer->startframe == snd_renderbuffer->endframe) + return; + + startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; + factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + limit = snd_renderbuffer->maxframes - startoffset; + nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; + + if (nbframes > limit) + { + written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit); + if (written < 0 || (snd_pcm_uframes_t)written != limit) + return; + + nbframes -= limit; + startoffset = 0; + } + + written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes); + if (written < 0) + return; +} + + +/* +==================== +SndSys_GetSoundTime + +Returns the number of sample frames consumed since the sound started +==================== +*/ +unsigned int SndSys_GetSoundTime (void) +{ + snd_pcm_sframes_t delay, timediff; + int err; + + if (pcm_handle == NULL) + return 0; + + err = snd_pcm_delay (pcm_handle, &delay); + if (err < 0) + { + if (developer_insane.integer && vid_activewindow) + Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay (%s)\n", + snd_strerror (err)); + + if (! SndSys_Recover (err)) + return 0; + + err = snd_pcm_delay (pcm_handle, &delay); + if (err < 0) + { + Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay, again (%s)\n", + snd_strerror (err)); + return 0; + } + } + + if (expected_delay < delay) + { + Con_DPrintf ("SndSys_GetSoundTime: expected_delay(%ld) < delay(%ld)\n", + expected_delay, delay); + timediff = 0; + } + else + timediff = expected_delay - delay; + expected_delay = delay; + + alsasoundtime += (unsigned int)timediff; + + return alsasoundtime; +} + + +/* +==================== +SndSys_LockRenderBuffer + +Get the exclusive lock on "snd_renderbuffer" +==================== +*/ +qboolean SndSys_LockRenderBuffer (void) +{ + // Nothing to do + return true; +} + + +/* +==================== +SndSys_UnlockRenderBuffer + +Release the exclusive lock on "snd_renderbuffer" +==================== +*/ +void SndSys_UnlockRenderBuffer (void) +{ + // Nothing to do +} + +/* +==================== +SndSys_SendKeyEvents + +Send keyboard events originating from the sound system (e.g. MIDI) +==================== +*/ +void SndSys_SendKeyEvents(void) +{ + snd_seq_event_t *event; + if(!seq_handle) + return; + for(;;) + { + if(snd_seq_event_input(seq_handle, &event) <= 0) + break; + if(event) + { + switch(event->type) + { + case SND_SEQ_EVENT_NOTEON: + if(event->data.note.velocity) + { + Key_Event(K_MIDINOTE0 + event->data.note.note, 0, true); + break; + } + case SND_SEQ_EVENT_NOTEOFF: + Key_Event(K_MIDINOTE0 + event->data.note.note, 0, false); + break; + } + } + } +} diff --git a/misc/source/darkplaces-src/snd_bsd.c b/misc/source/darkplaces-src/snd_bsd.c new file mode 100644 index 00000000..0e927f1e --- /dev/null +++ b/misc/source/darkplaces-src/snd_bsd.c @@ -0,0 +1,243 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "quakedef.h" + +#include +#include +#ifndef SUNOS +# include +#endif +#include + +#include +#ifndef SUNOS +# include +#endif +#include + +#include "snd_main.h" + + +static int audio_fd = -1; + + +/* +==================== +SndSys_Init + +Create "snd_renderbuffer" with the proper sound format if the call is successful +May return a suggested format if the requested format isn't available +==================== +*/ +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) +{ + unsigned int i; + const char *snddev; + audio_info_t info; + + // Open the audio device +#ifdef _PATH_SOUND + snddev = _PATH_SOUND; +#elif defined(SUNOS) + snddev = "/dev/audio"; +#else + snddev = "/dev/sound"; +#endif + audio_fd = open (snddev, O_WRONLY | O_NDELAY | O_NONBLOCK); + if (audio_fd < 0) + { + Con_Printf("Can't open the sound device (%s)\n", snddev); + return false; + } + + AUDIO_INITINFO (&info); +#ifdef AUMODE_PLAY // NetBSD / OpenBSD + info.mode = AUMODE_PLAY; +#endif + info.play.sample_rate = requested->speed; + info.play.channels = requested->channels; + info.play.precision = requested->width * 8; + if (requested->width == 1) +#ifdef SUNOS + info.play.encoding = AUDIO_ENCODING_LINEAR8; +#else + info.play.encoding = AUDIO_ENCODING_ULINEAR; +#endif + else +#ifdef SUNOS + info.play.encoding = AUDIO_ENCODING_LINEAR; +#else + if (mem_bigendian) + info.play.encoding = AUDIO_ENCODING_SLINEAR_BE; + else + info.play.encoding = AUDIO_ENCODING_SLINEAR_LE; +#endif + + if (ioctl (audio_fd, AUDIO_SETINFO, &info) != 0) + { + Con_Printf("Can't set up the sound device (%s)\n", snddev); + return false; + } + + // TODO: check the parameters with AUDIO_GETINFO + // TODO: check AUDIO_ENCODINGFLAG_EMULATED with AUDIO_GETENC + + snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL); + return true; +} + + +/* +==================== +SndSys_Shutdown + +Stop the sound card, delete "snd_renderbuffer" and free its other resources +==================== +*/ +void SndSys_Shutdown (void) +{ + if (audio_fd >= 0) + { + close(audio_fd); + audio_fd = -1; + } + + if (snd_renderbuffer != NULL) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } +} + + +/* +==================== +SndSys_Submit + +Submit the contents of "snd_renderbuffer" to the sound card +==================== +*/ +void SndSys_Submit (void) +{ + unsigned int startoffset, factor, limit, nbframes; + int written; + + if (audio_fd < 0 || + snd_renderbuffer->startframe == snd_renderbuffer->endframe) + return; + + startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; + factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + limit = snd_renderbuffer->maxframes - startoffset; + nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; + if (nbframes > limit) + { + written = write (audio_fd, &snd_renderbuffer->ring[startoffset * factor], limit * factor); + if (written < 0) + { + Con_Printf("SndSys_Submit: audio write returned %d!\n", written); + return; + } + + if (written % factor != 0) + Sys_Error("SndSys_Submit: nb of bytes written (%d) isn't aligned to a frame sample!\n", written); + + snd_renderbuffer->startframe += written / factor; + + if ((unsigned int)written < limit * factor) + { + Con_Printf("SndSys_Submit: audio can't keep up! (%u < %u)\n", written, limit * factor); + return; + } + + nbframes -= limit; + startoffset = 0; + } + + written = write (audio_fd, &snd_renderbuffer->ring[startoffset * factor], nbframes * factor); + if (written < 0) + { + Con_Printf("SndSys_Submit: audio write returned %d!\n", written); + return; + } + snd_renderbuffer->startframe += written / factor; +} + + +/* +==================== +SndSys_GetSoundTime + +Returns the number of sample frames consumed since the sound started +==================== +*/ +unsigned int SndSys_GetSoundTime (void) +{ + audio_info_t info; + + if (ioctl (audio_fd, AUDIO_GETINFO, &info) < 0) + { + Con_Print("Error: can't get audio info\n"); + SndSys_Shutdown (); + return 0; + } + + return info.play.samples; +} + + +/* +==================== +SndSys_LockRenderBuffer + +Get the exclusive lock on "snd_renderbuffer" +==================== +*/ +qboolean SndSys_LockRenderBuffer (void) +{ + // Nothing to do + return true; +} + + +/* +==================== +SndSys_UnlockRenderBuffer + +Release the exclusive lock on "snd_renderbuffer" +==================== +*/ +void SndSys_UnlockRenderBuffer (void) +{ + // Nothing to do +} + +/* +==================== +SndSys_SendKeyEvents + +Send keyboard events originating from the sound system (e.g. MIDI) +==================== +*/ +void SndSys_SendKeyEvents(void) +{ + // not supported +} diff --git a/misc/source/darkplaces-src/snd_coreaudio.c b/misc/source/darkplaces-src/snd_coreaudio.c new file mode 100644 index 00000000..ce8fff5b --- /dev/null +++ b/misc/source/darkplaces-src/snd_coreaudio.c @@ -0,0 +1,399 @@ +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "quakedef.h" + +#include +#include + +#include + +#include "snd_main.h" + + +#define CHUNK_SIZE 1024 + +static unsigned int submissionChunk = 0; // in sample frames +static unsigned int coreaudiotime = 0; // based on the number of chunks submitted so far +static qboolean s_isRunning = false; +static pthread_mutex_t coreaudio_mutex; +static AudioDeviceID outputDeviceID = kAudioDeviceUnknown; +static short *mixbuffer = NULL; + + +/* +==================== +audioDeviceIOProc +==================== +*/ +static OSStatus audioDeviceIOProc(AudioDeviceID inDevice, + const AudioTimeStamp *inNow, + const AudioBufferList *inInputData, + const AudioTimeStamp *inInputTime, + AudioBufferList *outOutputData, + const AudioTimeStamp *inOutputTime, + void *inClientData) +{ + float *outBuffer; + unsigned int frameCount, factor, sampleIndex; + float scale = 1.0f / SHRT_MAX; + + outBuffer = (float*)outOutputData->mBuffers[0].mData; + factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width; + frameCount = 0; + if (snd_blocked) + scale = 0; + + // Lock the snd_renderbuffer + if (SndSys_LockRenderBuffer()) + { + unsigned int maxFrames, sampleCount; + unsigned int startOffset, endOffset; + const short *samples; + + if (snd_usethreadedmixing) + { + S_MixToBuffer(mixbuffer, submissionChunk); + sampleCount = submissionChunk * snd_renderbuffer->format.channels; + for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + outBuffer[sampleIndex] = mixbuffer[sampleIndex] * scale; + // unlock the mutex now + SndSys_UnlockRenderBuffer(); + return 0; + } + + // Transfert up to a chunk of sample frames from snd_renderbuffer to outBuffer + maxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe; + if (maxFrames >= submissionChunk) + frameCount = submissionChunk; + else + frameCount = maxFrames; + + // Convert the samples from shorts to floats. Scale the floats to be [-1..1]. + startOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; + endOffset = (snd_renderbuffer->startframe + frameCount) % snd_renderbuffer->maxframes; + if (startOffset > endOffset) // if the buffer wraps + { + sampleCount = (snd_renderbuffer->maxframes - startOffset) * snd_renderbuffer->format.channels; + samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]); + for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + outBuffer[sampleIndex] = samples[sampleIndex] * scale; + + outBuffer = &outBuffer[sampleCount]; + sampleCount = frameCount * snd_renderbuffer->format.channels - sampleCount; + samples = (const short*)(&snd_renderbuffer->ring[0]); + for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + outBuffer[sampleIndex] = samples[sampleIndex] * scale; + } + else + { + sampleCount = frameCount * snd_renderbuffer->format.channels; + samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]); + for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + outBuffer[sampleIndex] = samples[sampleIndex] * scale; + } + + snd_renderbuffer->startframe += frameCount; + + // unlock the mutex now + SndSys_UnlockRenderBuffer(); + } + + // If there was not enough samples, complete with silence samples + if (frameCount < submissionChunk) + { + unsigned int missingFrames; + + missingFrames = submissionChunk - frameCount; + if (developer_insane.integer && vid_activewindow) + Con_DPrintf("audioDeviceIOProc: %u sample frames missing\n", missingFrames); + memset(&outBuffer[frameCount * snd_renderbuffer->format.channels], 0, missingFrames * sizeof(outBuffer[0])); + } + + coreaudiotime += submissionChunk; + return 0; +} + + +/* +==================== +SndSys_Init + +Create "snd_renderbuffer" with the proper sound format if the call is successful +May return a suggested format if the requested format isn't available +==================== +*/ +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) +{ + OSStatus status; + UInt32 propertySize, bufferByteCount; + AudioStreamBasicDescription streamDesc; + + if (s_isRunning) + return true; + + Con_Printf("Initializing CoreAudio...\n"); + snd_threaded = false; + + if(requested->width != 2) + { + // we can only do 16bit per sample for now + if(suggested != NULL) + { + memcpy (suggested, requested, sizeof (*suggested)); + suggested->width = 2; + } + return false; + } + + // Get the output device + propertySize = sizeof(outputDeviceID); + status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID); + if (status) + { + Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioHardwarePropertyDefaultOutputDevice\n", (int)status); + return false; + } + if (outputDeviceID == kAudioDeviceUnknown) + { + Con_Printf("CoreAudio: outputDeviceID is kAudioDeviceUnknown\n"); + return false; + } + + // Configure the output device + propertySize = sizeof(bufferByteCount); + bufferByteCount = CHUNK_SIZE * sizeof(float) * requested->channels; + status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount); + if (status) + { + Con_Printf("CoreAudio: AudioDeviceSetProperty() returned %d when setting kAudioDevicePropertyBufferSize to %d\n", (int)status, CHUNK_SIZE); + return false; + } + + propertySize = sizeof(bufferByteCount); + status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount); + if (status) + { + Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when setting kAudioDevicePropertyBufferSize\n", (int)status); + return false; + } + + submissionChunk = bufferByteCount / sizeof(float); + if (submissionChunk % requested->channels != 0) + { + Con_Print("CoreAudio: chunk size is NOT a multiple of the number of channels\n"); + return false; + } + submissionChunk /= requested->channels; + Con_Printf(" Chunk size = %d sample frames\n", submissionChunk); + + // Print out the device status + propertySize = sizeof(streamDesc); + status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc); + if (status) + { + Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", (int)status); + return false; + } + + Con_Print (" Hardware format:\n"); + Con_Printf(" %5d mSampleRate\n", (unsigned int)streamDesc.mSampleRate); + Con_Printf(" %c%c%c%c mFormatID\n", + (char)(streamDesc.mFormatID >> 24), + (char)(streamDesc.mFormatID >> 16), + (char)(streamDesc.mFormatID >> 8), + (char)(streamDesc.mFormatID >> 0)); + Con_Printf(" %5u mBytesPerPacket\n", (unsigned int)streamDesc.mBytesPerPacket); + Con_Printf(" %5u mFramesPerPacket\n", (unsigned int)streamDesc.mFramesPerPacket); + Con_Printf(" %5u mBytesPerFrame\n", (unsigned int)streamDesc.mBytesPerFrame); + Con_Printf(" %5u mChannelsPerFrame\n", (unsigned int)streamDesc.mChannelsPerFrame); + Con_Printf(" %5u mBitsPerChannel\n", (unsigned int)streamDesc.mBitsPerChannel); + + // Suggest proper settings if they differ + if (requested->channels != streamDesc.mChannelsPerFrame || requested->speed != streamDesc.mSampleRate) + { + if (suggested != NULL) + { + memcpy (suggested, requested, sizeof (*suggested)); + suggested->channels = streamDesc.mChannelsPerFrame; + suggested->speed = streamDesc.mSampleRate; + } + return false; + } + + if(streamDesc.mFormatID == kAudioFormatLinearPCM) + { + // Add the callback function + status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL); + if (!status) + { + // We haven't sent any sample frames yet + coreaudiotime = 0; + if (pthread_mutex_init(&coreaudio_mutex, NULL) == 0) + { + if ((snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL))) + { + if ((mixbuffer = Mem_Alloc(snd_mempool, CHUNK_SIZE * sizeof(*mixbuffer) * requested->channels))) + { + // Start sound running + status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc); + if (!status) + { + s_isRunning = true; + snd_threaded = true; + Con_Print(" Initialization successful\n"); + return true; + } + else + Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", (int)status); + Mem_Free(mixbuffer); + mixbuffer = NULL; + } + else + Con_Print("CoreAudio: can't allocate memory for mixbuffer\n"); + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } + else + Con_Print("CoreAudio: can't allocate memory for ringbuffer\n"); + pthread_mutex_destroy(&coreaudio_mutex); + } + else + Con_Print("CoreAudio: can't create pthread mutex\n"); + AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc); + } + else + Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", (int)status); + } + else + Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n"); + return false; +} + + +/* +==================== +SndSys_Shutdown + +Stop the sound card, delete "snd_renderbuffer" and free its other resources +==================== +*/ +void SndSys_Shutdown(void) +{ + OSStatus status; + + if (!s_isRunning) + return; + + status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc); + if (status) + { + Con_Printf("AudioDeviceStop: returned %d\n", (int)status); + return; + } + s_isRunning = false; + + pthread_mutex_destroy(&coreaudio_mutex); + + status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc); + if (status) + { + Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", (int)status); + return; + } + + if (snd_renderbuffer != NULL) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } + + if (mixbuffer != NULL) + Mem_Free(mixbuffer); + mixbuffer = NULL; +} + + +/* +==================== +SndSys_Submit + +Submit the contents of "snd_renderbuffer" to the sound card +==================== +*/ +void SndSys_Submit (void) +{ + // Nothing to do here (this sound module is callback-based) +} + + +/* +==================== +SndSys_GetSoundTime + +Returns the number of sample frames consumed since the sound started +==================== +*/ +unsigned int SndSys_GetSoundTime (void) +{ + return coreaudiotime; +} + + +/* +==================== +SndSys_LockRenderBuffer + +Get the exclusive lock on "snd_renderbuffer" +==================== +*/ +qboolean SndSys_LockRenderBuffer (void) +{ + return (pthread_mutex_lock(&coreaudio_mutex) == 0); +} + + +/* +==================== +SndSys_UnlockRenderBuffer + +Release the exclusive lock on "snd_renderbuffer" +==================== +*/ +void SndSys_UnlockRenderBuffer (void) +{ + pthread_mutex_unlock(&coreaudio_mutex); +} + +/* +==================== +SndSys_SendKeyEvents + +Send keyboard events originating from the sound system (e.g. MIDI) +==================== +*/ +void SndSys_SendKeyEvents(void) +{ + // not supported +} diff --git a/misc/source/darkplaces-src/snd_main.c b/misc/source/darkplaces-src/snd_main.c new file mode 100644 index 00000000..e537d838 --- /dev/null +++ b/misc/source/darkplaces-src/snd_main.c @@ -0,0 +1,2282 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_main.c -- main control for any streaming sound output device + +#include "quakedef.h" + +#include "snd_main.h" +#include "snd_ogg.h" +#include "snd_modplug.h" +#include "csprogs.h" +#include "cl_collision.h" + + +#define SND_MIN_SPEED 8000 +#define SND_MAX_SPEED 96000 +#define SND_MIN_WIDTH 1 +#define SND_MAX_WIDTH 2 +#define SND_MIN_CHANNELS 1 +#define SND_MAX_CHANNELS 8 +#if SND_LISTENERS != 8 +# error this data only supports up to 8 channel, update it! +#endif + +speakerlayout_t snd_speakerlayout; + +// Our speaker layouts are based on ALSA. They differ from those +// Win32 and Mac OS X APIs use when there's more than 4 channels. +// (rear left + rear right, and front center + LFE are swapped). +#define SND_SPEAKERLAYOUTS (sizeof(snd_speakerlayouts) / sizeof(snd_speakerlayouts[0])) +static const speakerlayout_t snd_speakerlayouts[] = +{ + { + "surround71", 8, + { + {0, 45, 0.2, 0.2, 0.5}, // front left + {1, 315, 0.2, 0.2, 0.5}, // front right + {2, 135, 0.2, 0.2, 0.5}, // rear left + {3, 225, 0.2, 0.2, 0.5}, // rear right + {4, 0, 0.2, 0.2, 0.5}, // front center + {5, 0, 0, 0, 0}, // lfe (we don't have any good lfe sound sources and it would take some filtering work to generate them (and they'd probably still be wrong), so... no lfe) + {6, 90, 0.2, 0.2, 0.5}, // side left + {7, 180, 0.2, 0.2, 0.5}, // side right + } + }, + { + "surround51", 6, + { + {0, 45, 0.2, 0.2, 0.5}, // front left + {1, 315, 0.2, 0.2, 0.5}, // front right + {2, 135, 0.2, 0.2, 0.5}, // rear left + {3, 225, 0.2, 0.2, 0.5}, // rear right + {4, 0, 0.2, 0.2, 0.5}, // front center + {5, 0, 0, 0, 0}, // lfe (we don't have any good lfe sound sources and it would take some filtering work to generate them (and they'd probably still be wrong), so... no lfe) + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + } + }, + { + // these systems sometimes have a subwoofer as well, but it has no + // channel of its own + "surround40", 4, + { + {0, 45, 0.3, 0.3, 0.8}, // front left + {1, 315, 0.3, 0.3, 0.8}, // front right + {2, 135, 0.3, 0.3, 0.8}, // rear left + {3, 225, 0.3, 0.3, 0.8}, // rear right + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + } + }, + { + // these systems sometimes have a subwoofer as well, but it has no + // channel of its own + "stereo", 2, + { + {0, 90, 0.5, 0.5, 1}, // side left + {1, 270, 0.5, 0.5, 1}, // side right + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + } + }, + { + "mono", 1, + { + {0, 0, 0, 1, 1}, // center + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + } + } +}; + + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +channel_t channels[MAX_CHANNELS]; +unsigned int total_channels; + +snd_ringbuffer_t *snd_renderbuffer = NULL; +static unsigned int soundtime = 0; +static unsigned int oldpaintedtime = 0; +static unsigned int extrasoundtime = 0; +static double snd_starttime = 0.0; +qboolean snd_threaded = false; +qboolean snd_usethreadedmixing = false; + +vec3_t listener_origin; +matrix4x4_t listener_basematrix; +static unsigned char *listener_pvs = NULL; +static int listener_pvsbytes = 0; +matrix4x4_t listener_matrix[SND_LISTENERS]; +mempool_t *snd_mempool; + +// Linked list of known sfx +static sfx_t *known_sfx = NULL; + +static qboolean sound_spatialized = false; + +qboolean simsound = false; + +static qboolean recording_sound = false; + +int snd_blocked = 0; +static int current_swapstereo = false; +static int current_channellayout = SND_CHANNELLAYOUT_AUTO; +static int current_channellayout_used = SND_CHANNELLAYOUT_AUTO; + +static float spatialpower, spatialmin, spatialdiff, spatialoffset, spatialfactor; +typedef enum { SPATIAL_NONE, SPATIAL_LOG, SPATIAL_POW, SPATIAL_THRESH } spatialmethod_t; +spatialmethod_t spatialmethod; + +// Cvars declared in sound.h (part of the sound API) +cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg)"}; +cvar_t mastervolume = {CVAR_SAVE, "mastervolume", "0.7", "master volume"}; +cvar_t volume = {CVAR_SAVE, "volume", "0.7", "volume of sound effects"}; +cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0", "indicates the sound subsystem is active"}; +cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1", "volume of ambient sound effects (such as swampy sounds at the start of e1m2)"}; +cvar_t snd_soundradius = {CVAR_SAVE, "snd_soundradius", "1200", "radius of weapon sounds and other standard sound effects (monster idle noises are half this radius and flickering light noises are one third of this radius)"}; +cvar_t snd_spatialization_min_radius = {CVAR_SAVE, "snd_spatialization_min_radius", "10000", "use minimum spatialization above to this radius"}; +cvar_t snd_spatialization_max_radius = {CVAR_SAVE, "snd_spatialization_max_radius", "100", "use maximum spatialization below this radius"}; +cvar_t snd_spatialization_min = {CVAR_SAVE, "snd_spatialization_min", "0.70", "minimum spatializazion of sounds"}; +cvar_t snd_spatialization_max = {CVAR_SAVE, "snd_spatialization_max", "0.95", "maximum spatialization of sounds"}; +cvar_t snd_spatialization_power = {CVAR_SAVE, "snd_spatialization_power", "0", "exponent of the spatialization falloff curve (0: logarithmic)"}; +cvar_t snd_spatialization_control = {CVAR_SAVE, "snd_spatialization_control", "0", "enable spatialization control (headphone friendly mode)"}; +cvar_t snd_spatialization_prologic = {CVAR_SAVE, "snd_spatialization_prologic", "0", "use dolby prologic (I, II or IIx) encoding (snd_channels must be 2)"}; +cvar_t snd_spatialization_prologic_frontangle = {CVAR_SAVE, "snd_spatialization_prologic_frontangle", "30", "the angle between the front speakers and the center speaker"}; +cvar_t snd_spatialization_occlusion = {CVAR_SAVE, "snd_spatialization_occlusion", "1", "enable occlusion testing on spatialized sounds, which simply quiets sounds that are blocked by the world; 1 enables PVS method, 2 enables LineOfSight method, 3 enables both"}; + +// Cvars declared in snd_main.h (shared with other snd_*.c files) +cvar_t _snd_mixahead = {CVAR_SAVE, "_snd_mixahead", "0.15", "how much sound to mix ahead of time"}; +cvar_t snd_streaming = { CVAR_SAVE, "snd_streaming", "1", "enables keeping compressed ogg sound files compressed, decompressing them only as needed, otherwise they will be decompressed completely at load (may use a lot of memory)"}; +cvar_t snd_swapstereo = {CVAR_SAVE, "snd_swapstereo", "0", "swaps left/right speakers for old ISA soundblaster cards"}; +extern cvar_t v_flipped; +cvar_t snd_channellayout = {0, "snd_channellayout", "0", "channel layout. Can be 0 (auto - snd_restart needed), 1 (standard layout), or 2 (ALSA layout)"}; +cvar_t snd_mutewhenidle = {CVAR_SAVE, "snd_mutewhenidle", "1", "whether to disable sound output when game window is inactive"}; +cvar_t snd_entchannel0volume = {CVAR_SAVE, "snd_entchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel1volume = {CVAR_SAVE, "snd_entchannel1volume", "1", "volume multiplier of the 1st entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel2volume = {CVAR_SAVE, "snd_entchannel2volume", "1", "volume multiplier of the 2nd entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel3volume = {CVAR_SAVE, "snd_entchannel3volume", "1", "volume multiplier of the 3rd entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel4volume = {CVAR_SAVE, "snd_entchannel4volume", "1", "volume multiplier of the 4th entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel5volume = {CVAR_SAVE, "snd_entchannel5volume", "1", "volume multiplier of the 5th entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel6volume = {CVAR_SAVE, "snd_entchannel6volume", "1", "volume multiplier of the 6th entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel7volume = {CVAR_SAVE, "snd_entchannel7volume", "1", "volume multiplier of the 7th entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_playerchannel0volume = {CVAR_SAVE, "snd_playerchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel1volume = {CVAR_SAVE, "snd_playerchannel1volume", "1", "volume multiplier of the 1st entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel2volume = {CVAR_SAVE, "snd_playerchannel2volume", "1", "volume multiplier of the 2nd entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel3volume = {CVAR_SAVE, "snd_playerchannel3volume", "1", "volume multiplier of the 3rd entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel4volume = {CVAR_SAVE, "snd_playerchannel4volume", "1", "volume multiplier of the 4th entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel5volume = {CVAR_SAVE, "snd_playerchannel5volume", "1", "volume multiplier of the 5th entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel6volume = {CVAR_SAVE, "snd_playerchannel6volume", "1", "volume multiplier of the 6th entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel7volume = {CVAR_SAVE, "snd_playerchannel7volume", "1", "volume multiplier of the 7th entity channel of player entities (DEPRECATED)"}; +cvar_t snd_worldchannel0volume = {CVAR_SAVE, "snd_worldchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel1volume = {CVAR_SAVE, "snd_worldchannel1volume", "1", "volume multiplier of the 1st entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel2volume = {CVAR_SAVE, "snd_worldchannel2volume", "1", "volume multiplier of the 2nd entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel3volume = {CVAR_SAVE, "snd_worldchannel3volume", "1", "volume multiplier of the 3rd entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel4volume = {CVAR_SAVE, "snd_worldchannel4volume", "1", "volume multiplier of the 4th entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel5volume = {CVAR_SAVE, "snd_worldchannel5volume", "1", "volume multiplier of the 5th entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel6volume = {CVAR_SAVE, "snd_worldchannel6volume", "1", "volume multiplier of the 6th entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel7volume = {CVAR_SAVE, "snd_worldchannel7volume", "1", "volume multiplier of the 7th entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_csqcchannel0volume = {CVAR_SAVE, "snd_csqcchannel0volume", "1", "volume multiplier of the auto-allocate entity channel CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel1volume = {CVAR_SAVE, "snd_csqcchannel1volume", "1", "volume multiplier of the 1st entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel2volume = {CVAR_SAVE, "snd_csqcchannel2volume", "1", "volume multiplier of the 2nd entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel3volume = {CVAR_SAVE, "snd_csqcchannel3volume", "1", "volume multiplier of the 3rd entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel4volume = {CVAR_SAVE, "snd_csqcchannel4volume", "1", "volume multiplier of the 4th entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel5volume = {CVAR_SAVE, "snd_csqcchannel5volume", "1", "volume multiplier of the 5th entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel6volume = {CVAR_SAVE, "snd_csqcchannel6volume", "1", "volume multiplier of the 6th entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel7volume = {CVAR_SAVE, "snd_csqcchannel7volume", "1", "volume multiplier of the 7th entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_channel0volume = {CVAR_SAVE, "snd_channel0volume", "1", "volume multiplier of the auto-allocate entity channel"}; +cvar_t snd_channel1volume = {CVAR_SAVE, "snd_channel1volume", "1", "volume multiplier of the 1st entity channel"}; +cvar_t snd_channel2volume = {CVAR_SAVE, "snd_channel2volume", "1", "volume multiplier of the 2nd entity channel"}; +cvar_t snd_channel3volume = {CVAR_SAVE, "snd_channel3volume", "1", "volume multiplier of the 3rd entity channel"}; +cvar_t snd_channel4volume = {CVAR_SAVE, "snd_channel4volume", "1", "volume multiplier of the 4th entity channel"}; +cvar_t snd_channel5volume = {CVAR_SAVE, "snd_channel5volume", "1", "volume multiplier of the 5th entity channel"}; +cvar_t snd_channel6volume = {CVAR_SAVE, "snd_channel6volume", "1", "volume multiplier of the 6th entity channel"}; +cvar_t snd_channel7volume = {CVAR_SAVE, "snd_channel7volume", "1", "volume multiplier of the 7th entity channel"}; + +// Local cvars +static cvar_t nosound = {0, "nosound", "0", "disables sound"}; +static cvar_t snd_precache = {0, "snd_precache", "1", "loads sounds before they are used"}; +static cvar_t ambient_level = {0, "ambient_level", "0.3", "volume of environment noises (water and wind)"}; +static cvar_t ambient_fade = {0, "ambient_fade", "100", "rate of volume fading when moving from one environment to another"}; +static cvar_t snd_noextraupdate = {0, "snd_noextraupdate", "0", "disables extra sound mixer calls that are meant to reduce the chance of sound breakup at very low framerates"}; +static cvar_t snd_show = {0, "snd_show", "0", "shows some statistics about sound mixing"}; + +// Default sound format is 48KHz, 16-bit, stereo +// (48KHz because a lot of onboard sound cards sucks at any other speed) +static cvar_t snd_speed = {CVAR_SAVE, "snd_speed", "48000", "sound output frequency, in hertz"}; +static cvar_t snd_width = {CVAR_SAVE, "snd_width", "2", "sound output precision, in bytes (1 and 2 supported)"}; +static cvar_t snd_channels = {CVAR_SAVE, "snd_channels", "2", "number of channels for the sound output (2 for stereo; up to 8 supported for 3D sound)"}; + +static cvar_t snd_startloopingsounds = {0, "snd_startloopingsounds", "1", "whether to start sounds that would loop (you want this to be 1); existing sounds are not affected"}; +static cvar_t snd_startnonloopingsounds = {0, "snd_startnonloopingsounds", "1", "whether to start sounds that would not loop (you want this to be 1); existing sounds are not affected"}; + +// Ambient sounds +static sfx_t* ambient_sfxs [2] = { NULL, NULL }; +static const char* ambient_names [2] = { "sound/ambience/water1.wav", "sound/ambience/wind2.wav" }; + + +// ==================================================================== +// Functions +// ==================================================================== + +void S_FreeSfx (sfx_t *sfx, qboolean force); + +static void S_Play_Common (float fvol, float attenuation) +{ + int i, ch_ind; + char name [MAX_QPATH]; + sfx_t *sfx; + + i = 1; + while (i < Cmd_Argc ()) + { + // Get the name, and appends ".wav" as an extension if there's none + strlcpy (name, Cmd_Argv (i), sizeof (name)); + if (!strrchr (name, '.')) + strlcat (name, ".wav", sizeof (name)); + i++; + + // If we need to get the volume from the command line + if (fvol == -1.0f) + { + fvol = atof (Cmd_Argv (i)); + i++; + } + + sfx = S_PrecacheSound (name, true, true); + if (sfx) + { + ch_ind = S_StartSound (-1, 0, sfx, listener_origin, fvol, attenuation); + + // Free the sfx if the file didn't exist + if (!sfx->fetcher) + S_FreeSfx (sfx, false); + else + channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; + } + } +} + +static void S_Play_f(void) +{ + S_Play_Common (1.0f, 1.0f); +} + +static void S_Play2_f(void) +{ + S_Play_Common (1.0f, 0.0f); +} + +static void S_PlayVol_f(void) +{ + S_Play_Common (-1.0f, 0.0f); +} + +static void S_SoundList_f (void) +{ + unsigned int i; + sfx_t *sfx; + unsigned int total; + + total = 0; + for (sfx = known_sfx, i = 0; sfx != NULL; sfx = sfx->next, i++) + { + if (sfx->fetcher != NULL) + { + unsigned int size; + const snd_format_t* format; + + size = sfx->memsize; + format = sfx->fetcher->getfmt(sfx); + Con_Printf ("%c%c%c(%2db, %6s) %8i : %s\n", + (sfx->loopstart < sfx->total_length) ? 'L' : ' ', + (sfx->flags & SFXFLAG_STREAMED) ? 'S' : ' ', + (sfx->flags & SFXFLAG_MENUSOUND) ? 'P' : ' ', + format->width * 8, + (format->channels == 1) ? "mono" : "stereo", + size, + sfx->name); + total += size; + } + else + Con_Printf (" ( unknown ) unloaded : %s\n", sfx->name); + } + Con_Printf("Total resident: %i\n", total); +} + + +void S_SoundInfo_f(void) +{ + if (snd_renderbuffer == NULL) + { + Con_Print("sound system not started\n"); + return; + } + + Con_Printf("%5d speakers\n", snd_renderbuffer->format.channels); + Con_Printf("%5d frames\n", snd_renderbuffer->maxframes); + Con_Printf("%5d samplebits\n", snd_renderbuffer->format.width * 8); + Con_Printf("%5d speed\n", snd_renderbuffer->format.speed); + Con_Printf("%5u total_channels\n", total_channels); +} + + +int S_GetSoundRate(void) +{ + return snd_renderbuffer ? snd_renderbuffer->format.speed : 0; +} + +int S_GetSoundChannels(void) +{ + return snd_renderbuffer ? snd_renderbuffer->format.channels : 0; +} + + +static qboolean S_ChooseCheaperFormat (snd_format_t* format, qboolean fixed_speed, qboolean fixed_width, qboolean fixed_channels) +{ + static const snd_format_t thresholds [] = + { + // speed width channels + { SND_MIN_SPEED, SND_MIN_WIDTH, SND_MIN_CHANNELS }, + { 11025, 1, 2 }, + { 22050, 2, 2 }, + { 44100, 2, 2 }, + { 48000, 2, 6 }, + { 96000, 2, 6 }, + { SND_MAX_SPEED, SND_MAX_WIDTH, SND_MAX_CHANNELS }, + }; + const unsigned int nb_thresholds = sizeof(thresholds) / sizeof(thresholds[0]); + unsigned int speed_level, width_level, channels_level; + + // If we have reached the minimum values, there's nothing more we can do + if ((format->speed == thresholds[0].speed || fixed_speed) && + (format->width == thresholds[0].width || fixed_width) && + (format->channels == thresholds[0].channels || fixed_channels)) + return false; + + // Check the min and max values + #define CHECK_BOUNDARIES(param) \ + if (format->param < thresholds[0].param) \ + { \ + format->param = thresholds[0].param; \ + return true; \ + } \ + if (format->param > thresholds[nb_thresholds - 1].param) \ + { \ + format->param = thresholds[nb_thresholds - 1].param; \ + return true; \ + } + CHECK_BOUNDARIES(speed); + CHECK_BOUNDARIES(width); + CHECK_BOUNDARIES(channels); + #undef CHECK_BOUNDARIES + + // Find the level of each parameter + #define FIND_LEVEL(param) \ + param##_level = 0; \ + while (param##_level < nb_thresholds - 1) \ + { \ + if (format->param <= thresholds[param##_level].param) \ + break; \ + \ + param##_level++; \ + } + FIND_LEVEL(speed); + FIND_LEVEL(width); + FIND_LEVEL(channels); + #undef FIND_LEVEL + + // Decrease the parameter with the highest level to the previous level + if (channels_level >= speed_level && channels_level >= width_level && !fixed_channels) + { + format->channels = thresholds[channels_level - 1].channels; + return true; + } + if (speed_level >= width_level && !fixed_speed) + { + format->speed = thresholds[speed_level - 1].speed; + return true; + } + + format->width = thresholds[width_level - 1].width; + return true; +} + + +#define SWAP_LISTENERS(l1, l2, tmpl) { tmpl = (l1); (l1) = (l2); (l2) = tmpl; } + +static void S_SetChannelLayout (void) +{ + unsigned int i; + listener_t swaplistener; + listener_t *listeners; + int layout; + + for (i = 0; i < SND_SPEAKERLAYOUTS; i++) + if (snd_speakerlayouts[i].channels == snd_renderbuffer->format.channels) + break; + if (i >= SND_SPEAKERLAYOUTS) + { + Con_Printf("S_SetChannelLayout: can't find the speaker layout for %hu channels. Defaulting to mono output\n", + snd_renderbuffer->format.channels); + i = SND_SPEAKERLAYOUTS - 1; + } + + snd_speakerlayout = snd_speakerlayouts[i]; + listeners = snd_speakerlayout.listeners; + + // Swap the left and right channels if snd_swapstereo is set + if (boolxor(snd_swapstereo.integer, v_flipped.integer)) + { + switch (snd_speakerlayout.channels) + { + case 8: + SWAP_LISTENERS(listeners[6], listeners[7], swaplistener); + // no break + case 4: + case 6: + SWAP_LISTENERS(listeners[2], listeners[3], swaplistener); + // no break + case 2: + SWAP_LISTENERS(listeners[0], listeners[1], swaplistener); + break; + + default: + case 1: + // Nothing to do + break; + } + } + + // Sanity check + if (snd_channellayout.integer < SND_CHANNELLAYOUT_AUTO || + snd_channellayout.integer > SND_CHANNELLAYOUT_ALSA) + Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD); + + if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) + { + // If we're in the sound engine initialization + if (current_channellayout_used == SND_CHANNELLAYOUT_AUTO) + { + layout = SND_CHANNELLAYOUT_STANDARD; + Cvar_SetValueQuick (&snd_channellayout, layout); + } + else + layout = current_channellayout_used; + } + else + layout = snd_channellayout.integer; + + // Convert our layout (= ALSA) to the standard layout if necessary + if (snd_speakerlayout.channels == 6 || snd_speakerlayout.channels == 8) + { + if (layout == SND_CHANNELLAYOUT_STANDARD) + { + SWAP_LISTENERS(listeners[2], listeners[4], swaplistener); + SWAP_LISTENERS(listeners[3], listeners[5], swaplistener); + } + + Con_Printf("S_SetChannelLayout: using %s speaker layout for 3D sound\n", + (layout == SND_CHANNELLAYOUT_ALSA) ? "ALSA" : "standard"); + } + + current_swapstereo = boolxor(snd_swapstereo.integer, v_flipped.integer); + current_channellayout = snd_channellayout.integer; + current_channellayout_used = layout; +} + + +void S_Startup (void) +{ + qboolean fixed_speed, fixed_width, fixed_channels; + snd_format_t chosen_fmt; + static snd_format_t prev_render_format = {0, 0, 0}; + char* env; +#if _MSC_VER >= 1400 + size_t envlen; +#endif + int i; + + if (!snd_initialized.integer) + return; + + fixed_speed = false; + fixed_width = false; + fixed_channels = false; + + // Get the starting sound format from the cvars + chosen_fmt.speed = snd_speed.integer; + chosen_fmt.width = snd_width.integer; + chosen_fmt.channels = snd_channels.integer; + + // Check the environment variables to see if the player wants a particular sound format +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_CHANNELS"); +#else + env = getenv("QUAKE_SOUND_CHANNELS"); +#endif + if (env != NULL) + { + chosen_fmt.channels = atoi (env); +#if _MSC_VER >= 1400 + free(env); +#endif + fixed_channels = true; + } +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_SPEED"); +#else + env = getenv("QUAKE_SOUND_SPEED"); +#endif + if (env != NULL) + { + chosen_fmt.speed = atoi (env); +#if _MSC_VER >= 1400 + free(env); +#endif + fixed_speed = true; + } +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_SAMPLEBITS"); +#else + env = getenv("QUAKE_SOUND_SAMPLEBITS"); +#endif + if (env != NULL) + { + chosen_fmt.width = atoi (env) / 8; +#if _MSC_VER >= 1400 + free(env); +#endif + fixed_width = true; + } + + // Parse the command line to see if the player wants a particular sound format +// COMMANDLINEOPTION: Sound: -sndquad sets sound output to 4 channel surround + if (COM_CheckParm ("-sndquad") != 0) + { + chosen_fmt.channels = 4; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndstereo sets sound output to stereo + else if (COM_CheckParm ("-sndstereo") != 0) + { + chosen_fmt.channels = 2; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndmono sets sound output to mono + else if (COM_CheckParm ("-sndmono") != 0) + { + chosen_fmt.channels = 1; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndspeed chooses sound output rate (supported values are 48000, 44100, 32000, 24000, 22050, 16000, 11025 (quake), 8000) + i = COM_CheckParm ("-sndspeed"); + if (0 < i && i < com_argc - 1) + { + chosen_fmt.speed = atoi (com_argv[i + 1]); + fixed_speed = true; + } +// COMMANDLINEOPTION: Sound: -sndbits chooses 8 bit or 16 bit sound output + i = COM_CheckParm ("-sndbits"); + if (0 < i && i < com_argc - 1) + { + chosen_fmt.width = atoi (com_argv[i + 1]) / 8; + fixed_width = true; + } + + // You can't change sound speed after start time (not yet supported) + if (prev_render_format.speed != 0) + { + fixed_speed = true; + if (chosen_fmt.speed != prev_render_format.speed) + { + Con_Printf("S_Startup: sound speed has changed! This is NOT supported yet. Falling back to previous speed (%u Hz)\n", + prev_render_format.speed); + chosen_fmt.speed = prev_render_format.speed; + } + } + + // Sanity checks + if (chosen_fmt.speed < SND_MIN_SPEED) + { + chosen_fmt.speed = SND_MIN_SPEED; + fixed_speed = false; + } + else if (chosen_fmt.speed > SND_MAX_SPEED) + { + chosen_fmt.speed = SND_MAX_SPEED; + fixed_speed = false; + } + + if (chosen_fmt.width < SND_MIN_WIDTH) + { + chosen_fmt.width = SND_MIN_WIDTH; + fixed_width = false; + } + else if (chosen_fmt.width > SND_MAX_WIDTH) + { + chosen_fmt.width = SND_MAX_WIDTH; + fixed_width = false; + } + + if (chosen_fmt.channels < SND_MIN_CHANNELS) + { + chosen_fmt.channels = SND_MIN_CHANNELS; + fixed_channels = false; + } + else if (chosen_fmt.channels > SND_MAX_CHANNELS) + { + chosen_fmt.channels = SND_MAX_CHANNELS; + fixed_channels = false; + } + + // create the sound buffer used for sumitting the samples to the plaform-dependent module + if (!simsound) + { + snd_format_t suggest_fmt; + qboolean accepted; + + accepted = false; + do + { + Con_Printf("S_Startup: initializing sound output format: %dHz, %d bit, %d channels...\n", + chosen_fmt.speed, chosen_fmt.width * 8, + chosen_fmt.channels); + + memset(&suggest_fmt, 0, sizeof(suggest_fmt)); + accepted = SndSys_Init(&chosen_fmt, &suggest_fmt); + + if (!accepted) + { + Con_Printf("S_Startup: sound output initialization FAILED\n"); + + // If the module is suggesting another one + if (suggest_fmt.speed != 0) + { + memcpy(&chosen_fmt, &suggest_fmt, sizeof(chosen_fmt)); + Con_Printf (" Driver has suggested %dHz, %d bit, %d channels. Retrying...\n", + suggest_fmt.speed, suggest_fmt.width * 8, + suggest_fmt.channels); + } + // Else, try to find a less resource-demanding format + else if (!S_ChooseCheaperFormat (&chosen_fmt, fixed_speed, fixed_width, fixed_channels)) + break; + } + } while (!accepted); + + // If we haven't found a suitable format + if (!accepted) + { + Con_Print("S_Startup: SndSys_Init failed.\n"); + sound_spatialized = false; + return; + } + } + else + { + snd_renderbuffer = Snd_CreateRingBuffer(&chosen_fmt, 0, NULL); + Con_Print ("S_Startup: simulating sound output\n"); + } + + memcpy(&prev_render_format, &snd_renderbuffer->format, sizeof(prev_render_format)); + Con_Printf("Sound format: %dHz, %d channels, %d bits per sample\n", + chosen_fmt.speed, chosen_fmt.channels, chosen_fmt.width * 8); + + // Update the cvars + if (snd_speed.integer != (int)chosen_fmt.speed) + Cvar_SetValueQuick(&snd_speed, chosen_fmt.speed); + if (snd_width.integer != chosen_fmt.width) + Cvar_SetValueQuick(&snd_width, chosen_fmt.width); + if (snd_channels.integer != chosen_fmt.channels) + Cvar_SetValueQuick(&snd_channels, chosen_fmt.channels); + + current_channellayout_used = SND_CHANNELLAYOUT_AUTO; + S_SetChannelLayout(); + + snd_starttime = realtime; + + // If the sound module has already run, add an extra time to make sure + // the sound time doesn't decrease, to not confuse playing SFXs + if (oldpaintedtime != 0) + { + // The extra time must be a multiple of the render buffer size + // to avoid modifying the current position in the buffer, + // some modules write directly to a shared (DMA) buffer + extrasoundtime = oldpaintedtime + snd_renderbuffer->maxframes - 1; + extrasoundtime -= extrasoundtime % snd_renderbuffer->maxframes; + Con_Printf("S_Startup: extra sound time = %u\n", extrasoundtime); + + soundtime = extrasoundtime; + } + else + extrasoundtime = 0; + snd_renderbuffer->startframe = soundtime; + snd_renderbuffer->endframe = soundtime; + recording_sound = false; +} + +void S_Shutdown(void) +{ + if (snd_renderbuffer == NULL) + return; + + oldpaintedtime = snd_renderbuffer->endframe; + + if (simsound) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } + else + SndSys_Shutdown(); + + sound_spatialized = false; +} + +void S_Restart_f(void) +{ + // NOTE: we can't free all sounds if we are running a map (this frees sfx_t that are still referenced by precaches) + // So, refuse to do this if we are connected. + if(cls.state == ca_connected) + { + Con_Printf("snd_restart would wreak havoc if you do that while connected!\n"); + return; + } + + S_Shutdown(); + S_Startup(); +} + +/* +================ +S_Init +================ +*/ +void S_Init(void) +{ + Cvar_RegisterVariable(&volume); + Cvar_RegisterVariable(&bgmvolume); + Cvar_RegisterVariable(&mastervolume); + Cvar_RegisterVariable(&snd_staticvolume); + Cvar_RegisterVariable(&snd_entchannel0volume); + Cvar_RegisterVariable(&snd_entchannel1volume); + Cvar_RegisterVariable(&snd_entchannel2volume); + Cvar_RegisterVariable(&snd_entchannel3volume); + Cvar_RegisterVariable(&snd_entchannel4volume); + Cvar_RegisterVariable(&snd_entchannel5volume); + Cvar_RegisterVariable(&snd_entchannel6volume); + Cvar_RegisterVariable(&snd_entchannel7volume); + Cvar_RegisterVariable(&snd_worldchannel0volume); + Cvar_RegisterVariable(&snd_worldchannel1volume); + Cvar_RegisterVariable(&snd_worldchannel2volume); + Cvar_RegisterVariable(&snd_worldchannel3volume); + Cvar_RegisterVariable(&snd_worldchannel4volume); + Cvar_RegisterVariable(&snd_worldchannel5volume); + Cvar_RegisterVariable(&snd_worldchannel6volume); + Cvar_RegisterVariable(&snd_worldchannel7volume); + Cvar_RegisterVariable(&snd_playerchannel0volume); + Cvar_RegisterVariable(&snd_playerchannel1volume); + Cvar_RegisterVariable(&snd_playerchannel2volume); + Cvar_RegisterVariable(&snd_playerchannel3volume); + Cvar_RegisterVariable(&snd_playerchannel4volume); + Cvar_RegisterVariable(&snd_playerchannel5volume); + Cvar_RegisterVariable(&snd_playerchannel6volume); + Cvar_RegisterVariable(&snd_playerchannel7volume); + Cvar_RegisterVariable(&snd_csqcchannel0volume); + Cvar_RegisterVariable(&snd_csqcchannel1volume); + Cvar_RegisterVariable(&snd_csqcchannel2volume); + Cvar_RegisterVariable(&snd_csqcchannel3volume); + Cvar_RegisterVariable(&snd_csqcchannel4volume); + Cvar_RegisterVariable(&snd_csqcchannel5volume); + Cvar_RegisterVariable(&snd_csqcchannel6volume); + Cvar_RegisterVariable(&snd_csqcchannel7volume); + Cvar_RegisterVariable(&snd_channel0volume); + Cvar_RegisterVariable(&snd_channel1volume); + Cvar_RegisterVariable(&snd_channel2volume); + Cvar_RegisterVariable(&snd_channel3volume); + Cvar_RegisterVariable(&snd_channel4volume); + Cvar_RegisterVariable(&snd_channel5volume); + Cvar_RegisterVariable(&snd_channel6volume); + Cvar_RegisterVariable(&snd_channel7volume); + + Cvar_RegisterVariable(&snd_spatialization_min_radius); + Cvar_RegisterVariable(&snd_spatialization_max_radius); + Cvar_RegisterVariable(&snd_spatialization_min); + Cvar_RegisterVariable(&snd_spatialization_max); + Cvar_RegisterVariable(&snd_spatialization_power); + Cvar_RegisterVariable(&snd_spatialization_control); + Cvar_RegisterVariable(&snd_spatialization_occlusion); + Cvar_RegisterVariable(&snd_spatialization_prologic); + Cvar_RegisterVariable(&snd_spatialization_prologic_frontangle); + + Cvar_RegisterVariable(&snd_speed); + Cvar_RegisterVariable(&snd_width); + Cvar_RegisterVariable(&snd_channels); + Cvar_RegisterVariable(&snd_mutewhenidle); + + Cvar_RegisterVariable(&snd_startloopingsounds); + Cvar_RegisterVariable(&snd_startnonloopingsounds); + +// COMMANDLINEOPTION: Sound: -nosound disables sound (including CD audio) + if (COM_CheckParm("-nosound")) + { + // dummy out Play and Play2 because mods stuffcmd that + Cmd_AddCommand("play", Host_NoOperation_f, "does nothing because -nosound was specified"); + Cmd_AddCommand("play2", Host_NoOperation_f, "does nothing because -nosound was specified"); + return; + } + + snd_mempool = Mem_AllocPool("sound", 0, NULL); + +// COMMANDLINEOPTION: Sound: -simsound runs sound mixing but with no output + if (COM_CheckParm("-simsound")) + simsound = true; + + Cmd_AddCommand("play", S_Play_f, "play a sound at your current location (not heard by anyone else)"); + Cmd_AddCommand("play2", S_Play2_f, "play a sound globally throughout the level (not heard by anyone else)"); + Cmd_AddCommand("playvol", S_PlayVol_f, "play a sound at the specified volume level at your current location (not heard by anyone else)"); + Cmd_AddCommand("stopsound", S_StopAllSounds, "silence"); + Cmd_AddCommand("soundlist", S_SoundList_f, "list loaded sounds"); + Cmd_AddCommand("soundinfo", S_SoundInfo_f, "print sound system information (such as channels and speed)"); + Cmd_AddCommand("snd_restart", S_Restart_f, "restart sound system"); + Cmd_AddCommand("snd_unloadallsounds", S_UnloadAllSounds_f, "unload all sound files"); + + Cvar_RegisterVariable(&nosound); + Cvar_RegisterVariable(&snd_precache); + Cvar_RegisterVariable(&snd_initialized); + Cvar_RegisterVariable(&snd_streaming); + Cvar_RegisterVariable(&ambient_level); + Cvar_RegisterVariable(&ambient_fade); + Cvar_RegisterVariable(&snd_noextraupdate); + Cvar_RegisterVariable(&snd_show); + Cvar_RegisterVariable(&_snd_mixahead); + Cvar_RegisterVariable(&snd_swapstereo); // for people with backwards sound wiring + Cvar_RegisterVariable(&snd_channellayout); + Cvar_RegisterVariable(&snd_soundradius); + + Cvar_SetValueQuick(&snd_initialized, true); + + known_sfx = NULL; + + total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics + memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); + + OGG_OpenLibrary (); + ModPlug_OpenLibrary (); +} + + +/* +================ +S_Terminate + +Shutdown and free all resources +================ +*/ +void S_Terminate (void) +{ + S_Shutdown (); + ModPlug_CloseLibrary (); + OGG_CloseLibrary (); + + // Free all SFXs + while (known_sfx != NULL) + S_FreeSfx (known_sfx, true); + + Cvar_SetValueQuick (&snd_initialized, false); + Mem_FreePool (&snd_mempool); +} + + +/* +================== +S_UnloadAllSounds_f +================== +*/ +void S_UnloadAllSounds_f (void) +{ + int i; + + // NOTE: we can't free all sounds if we are running a map (this frees sfx_t that are still referenced by precaches) + // So, refuse to do this if we are connected. + if(cls.state == ca_connected) + { + Con_Printf("snd_unloadallsounds would wreak havoc if you do that while connected!\n"); + return; + } + + // stop any active sounds + S_StopAllSounds(); + + // because the ambient sounds will be freed, clear the pointers + for (i = 0;i < (int)sizeof (ambient_sfxs) / (int)sizeof (ambient_sfxs[0]);i++) + ambient_sfxs[i] = NULL; + + // now free all sounds + while (known_sfx != NULL) + S_FreeSfx (known_sfx, true); +} + + +/* +================== +S_FindName +================== +*/ +sfx_t changevolume_sfx = {""}; +sfx_t *S_FindName (const char *name) +{ + sfx_t *sfx; + + if (!snd_initialized.integer) + return NULL; + + if(!strcmp(name, changevolume_sfx.name)) + return &changevolume_sfx; + + if (strlen (name) >= sizeof (sfx->name)) + { + Con_Printf ("S_FindName: sound name too long (%s)\n", name); + return NULL; + } + + // Look for this sound in the list of known sfx + // TODO: hash table search? + for (sfx = known_sfx; sfx != NULL; sfx = sfx->next) + if(!strcmp (sfx->name, name)) + return sfx; + + // Add a sfx_t struct for this sound + sfx = (sfx_t *)Mem_Alloc (snd_mempool, sizeof (*sfx)); + memset (sfx, 0, sizeof(*sfx)); + strlcpy (sfx->name, name, sizeof (sfx->name)); + sfx->memsize = sizeof(*sfx); + sfx->next = known_sfx; + known_sfx = sfx; + + return sfx; +} + + +/* +================== +S_FreeSfx +================== +*/ +void S_FreeSfx (sfx_t *sfx, qboolean force) +{ + unsigned int i; + + // Do not free a precached sound during purge + if (!force && (sfx->flags & (SFXFLAG_LEVELSOUND | SFXFLAG_MENUSOUND))) + return; + + if (developer_loading.integer) + Con_Printf ("unloading sound %s\n", sfx->name); + + // Remove it from the list of known sfx + if (sfx == known_sfx) + known_sfx = known_sfx->next; + else + { + sfx_t *prev_sfx; + + for (prev_sfx = known_sfx; prev_sfx != NULL; prev_sfx = prev_sfx->next) + if (prev_sfx->next == sfx) + { + prev_sfx->next = sfx->next; + break; + } + if (prev_sfx == NULL) + { + Con_Printf ("S_FreeSfx: Can't find SFX %s in the list!\n", sfx->name); + return; + } + } + + // Stop all channels using this sfx + for (i = 0; i < total_channels; i++) + { + if (channels[i].sfx == sfx) + { + Con_Printf("S_FreeSfx: stopping channel %i for sfx \"%s\"\n", i, sfx->name); + S_StopChannel (i, true, false); + } + } + + // Free it + if (sfx->fetcher != NULL && sfx->fetcher->free != NULL) + sfx->fetcher->free (sfx->fetcher_data); + Mem_Free (sfx); +} + + +/* +================== +S_ClearUsed +================== +*/ +void S_ClearUsed (void) +{ + sfx_t *sfx; +// sfx_t *sfxnext; + unsigned int i; + + // Start the ambient sounds and make them loop + for (i = 0; i < sizeof (ambient_sfxs) / sizeof (ambient_sfxs[0]); i++) + { + // Precache it if it's not done (and pass false for levelsound because these are permanent) + if (ambient_sfxs[i] == NULL) + ambient_sfxs[i] = S_PrecacheSound (ambient_names[i], false, false); + if (ambient_sfxs[i] != NULL) + { + channels[i].sfx = ambient_sfxs[i]; + channels[i].sfx->flags |= SFXFLAG_MENUSOUND; + channels[i].flags |= CHANNELFLAG_FORCELOOP; + channels[i].master_vol = 0; + } + } + + // Clear SFXFLAG_LEVELSOUND flag so that sounds not precached this level will be purged + for (sfx = known_sfx; sfx != NULL; sfx = sfx->next) + sfx->flags &= ~SFXFLAG_LEVELSOUND; +} + +/* +================== +S_PurgeUnused +================== +*/ +void S_PurgeUnused(void) +{ + sfx_t *sfx; + sfx_t *sfxnext; + + // Free all not-precached per-level sfx + for (sfx = known_sfx;sfx;sfx = sfxnext) + { + sfxnext = sfx->next; + if (!(sfx->flags & (SFXFLAG_LEVELSOUND | SFXFLAG_MENUSOUND))) + S_FreeSfx (sfx, false); + } +} + + +/* +================== +S_PrecacheSound +================== +*/ +sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean levelsound) +{ + sfx_t *sfx; + + if (!snd_initialized.integer) + return NULL; + + if (name == NULL || name[0] == 0) + return NULL; + + sfx = S_FindName (name); + + if (sfx == NULL) + return NULL; + + // clear the FILEMISSING flag so that S_LoadSound will try again on a + // previously missing file + sfx->flags &= ~ SFXFLAG_FILEMISSING; + + // set a flag to indicate this has been precached for this level or permanently + if (levelsound) + sfx->flags |= SFXFLAG_LEVELSOUND; + else + sfx->flags |= SFXFLAG_MENUSOUND; + + if (!nosound.integer && snd_precache.integer) + S_LoadSound(sfx, complain); + + return sfx; +} + +/* +================== +S_SoundLength +================== +*/ + +float S_SoundLength(const char *name) +{ + sfx_t *sfx; + + if (!snd_initialized.integer) + return -1; + if (name == NULL || name[0] == 0) + return -1; + + sfx = S_FindName(name); + if (sfx == NULL) + return -1; + return sfx->total_length / (float) S_GetSoundRate(); +} + +/* +================== +S_IsSoundPrecached +================== +*/ +qboolean S_IsSoundPrecached (const sfx_t *sfx) +{ + return (sfx != NULL && sfx->fetcher != NULL) || (sfx == &changevolume_sfx); +} + +/* +================== +S_BlockSound +================== +*/ +void S_BlockSound (void) +{ + snd_blocked++; +} + + +/* +================== +S_UnblockSound +================== +*/ +void S_UnblockSound (void) +{ + snd_blocked--; +} + + +/* +================= +SND_PickChannel + +Picks a channel based on priorities, empty slots, number of channels +================= +*/ +channel_t *SND_PickChannel(int entnum, int entchannel) +{ + int ch_idx; + int first_to_die; + int first_life_left, life_left; + channel_t* ch; + sfx_t *sfx; // use this instead of ch->sfx->, because that is volatile. + +// Check for replacement sound, or find the best one to replace + first_to_die = -1; + first_life_left = 0x7fffffff; + + // entity channels try to replace the existing sound on the channel + // channels <= 0 are autochannels + if (IS_CHAN_SINGLE(entchannel)) + { + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) + { + ch = &channels[ch_idx]; + if (ch->entnum == entnum && ch->entchannel == entchannel) + { + // always override sound from same entity + S_StopChannel (ch_idx, true, false); + return &channels[ch_idx]; + } + } + } + + // there was no channel to override, so look for the first empty one + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) + { + ch = &channels[ch_idx]; + sfx = ch->sfx; // fetch the volatile variable + if (!sfx) + { + // no sound on this channel + first_to_die = ch_idx; + goto emptychan_found; + } + + // don't let monster sounds override player sounds + if (ch->entnum == cl.viewentity && entnum != cl.viewentity) + continue; + + // don't override looped sounds + if ((ch->flags & CHANNELFLAG_FORCELOOP) || sfx->loopstart < sfx->total_length) + continue; + life_left = sfx->total_length - ch->pos; + + if (life_left < first_life_left) + { + first_life_left = life_left; + first_to_die = ch_idx; + } + } + + if (first_to_die == -1) + return NULL; + + S_StopChannel (first_to_die, true, false); + +emptychan_found: + return &channels[first_to_die]; +} + +/* +================= +SND_Spatialize + +Spatializes a channel +================= +*/ +extern cvar_t cl_gameplayfix_soundsmovewithentities; +void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) +{ + int i; + double f; + float angle_side, angle_front, angle_factor; + vec_t dist, mastervol, intensity, vol; + vec3_t source_vec; + + // update sound origin if we know about the entity + if (ch->entnum > 0 && cls.state == ca_connected && cl_gameplayfix_soundsmovewithentities.integer) + { + if (ch->entnum >= MAX_EDICTS) + { + //Con_Printf("-- entnum %i origin %f %f %f neworigin %f %f %f\n", ch->entnum, ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); + + if (ch->entnum > MAX_EDICTS) + if (!CL_VM_GetEntitySoundOrigin(ch->entnum, ch->origin)) + ch->entnum = MAX_EDICTS; // entity was removed, disown sound + } + else if (cl.entities[ch->entnum].state_current.active) + { + dp_model_t *model; + //Con_Printf("-- entnum %i origin %f %f %f neworigin %f %f %f\n", ch->entnum, ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); + model = CL_GetModelByIndex(cl.entities[ch->entnum].state_current.modelindex); + if (model && model->soundfromcenter) + VectorMAM(0.5f, cl.entities[ch->entnum].render.mins, 0.5f, cl.entities[ch->entnum].render.maxs, ch->origin); + else + Matrix4x4_OriginFromMatrix(&cl.entities[ch->entnum].render.matrix, ch->origin); + } + } + + mastervol = ch->master_vol; + + // Adjust volume of static sounds + if (isstatic) + mastervol *= snd_staticvolume.value; + else if(!(ch->flags & CHANNELFLAG_FULLVOLUME)) // same as SND_PaintChannel uses + { + // old legacy separated cvars + if(ch->entnum >= MAX_EDICTS) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_csqcchannel0volume.value; break; + case 1: mastervol *= snd_csqcchannel1volume.value; break; + case 2: mastervol *= snd_csqcchannel2volume.value; break; + case 3: mastervol *= snd_csqcchannel3volume.value; break; + case 4: mastervol *= snd_csqcchannel4volume.value; break; + case 5: mastervol *= snd_csqcchannel5volume.value; break; + case 6: mastervol *= snd_csqcchannel6volume.value; break; + case 7: mastervol *= snd_csqcchannel7volume.value; break; + default: break; + } + } + else if(ch->entnum == 0) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_worldchannel0volume.value; break; + case 1: mastervol *= snd_worldchannel1volume.value; break; + case 2: mastervol *= snd_worldchannel2volume.value; break; + case 3: mastervol *= snd_worldchannel3volume.value; break; + case 4: mastervol *= snd_worldchannel4volume.value; break; + case 5: mastervol *= snd_worldchannel5volume.value; break; + case 6: mastervol *= snd_worldchannel6volume.value; break; + case 7: mastervol *= snd_worldchannel7volume.value; break; + default: break; + } + } + else if(ch->entnum > 0 && ch->entnum <= cl.maxclients) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_playerchannel0volume.value; break; + case 1: mastervol *= snd_playerchannel1volume.value; break; + case 2: mastervol *= snd_playerchannel2volume.value; break; + case 3: mastervol *= snd_playerchannel3volume.value; break; + case 4: mastervol *= snd_playerchannel4volume.value; break; + case 5: mastervol *= snd_playerchannel5volume.value; break; + case 6: mastervol *= snd_playerchannel6volume.value; break; + case 7: mastervol *= snd_playerchannel7volume.value; break; + default: break; + } + } + else + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_entchannel0volume.value; break; + case 1: mastervol *= snd_entchannel1volume.value; break; + case 2: mastervol *= snd_entchannel2volume.value; break; + case 3: mastervol *= snd_entchannel3volume.value; break; + case 4: mastervol *= snd_entchannel4volume.value; break; + case 5: mastervol *= snd_entchannel5volume.value; break; + case 6: mastervol *= snd_entchannel6volume.value; break; + case 7: mastervol *= snd_entchannel7volume.value; break; + default: break; + } + } + + switch(ch->entchannel) + { + case 0: mastervol *= snd_channel0volume.value; break; + case 1: mastervol *= snd_channel1volume.value; break; + case 2: mastervol *= snd_channel2volume.value; break; + case 3: mastervol *= snd_channel3volume.value; break; + case 4: mastervol *= snd_channel4volume.value; break; + case 5: mastervol *= snd_channel5volume.value; break; + case 6: mastervol *= snd_channel6volume.value; break; + case 7: mastervol *= snd_channel7volume.value; break; + default: mastervol *= Cvar_VariableValueOr(va("snd_channel%dvolume", CHAN_ENGINE2CVAR(ch->entchannel)), 1.0); break; + } + } + + // If this channel does not manage its own volume (like CD tracks) + if (!(ch->flags & CHANNELFLAG_FULLVOLUME)) + mastervol *= volume.value; + + // clamp HERE to allow to go at most 10dB past mastervolume (before clamping), when mastervolume < -10dB (so relative volumes don't get too messy) + mastervol = bound(0, mastervol, 655360); + + // always apply "master" + mastervol *= mastervolume.value; + + // add in ReplayGain very late; prevent clipping when close + if(sfx) + if(sfx->volume_peak > 0) + { + // Replaygain support + // Con_DPrintf("Setting volume on ReplayGain-enabled track... %f -> ", fvol); + mastervol *= sfx->volume_mult; + if(mastervol * sfx->volume_peak > 65536) + mastervol = 65536 / sfx->volume_peak; + // Con_DPrintf("%f\n", fvol); + } + + // clamp HERE to keep relative volumes of the channels correct + mastervol = bound(0, mastervol, 65536); + + // anything coming from the view entity will always be full volume + // LordHavoc: make sounds with ATTN_NONE have no spatialization + if (ch->entnum == cl.viewentity || ch->dist_mult == 0) + { + ch->prologic_invert = 1; + if (snd_spatialization_prologic.integer != 0) + { + vol = mastervol * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5); + ch->listener_volume[0] = (int)bound(0, vol, 65536); + vol = mastervol * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5); + ch->listener_volume[1] = (int)bound(0, vol, 65536); + for (i = 2;i < SND_LISTENERS;i++) + ch->listener_volume[i] = 0; + } + else + { + for (i = 0;i < SND_LISTENERS;i++) + { + vol = mastervol * snd_speakerlayout.listeners[i].ambientvolume; + ch->listener_volume[i] = (int)bound(0, vol, 65536); + } + } + } + else + { + // calculate stereo seperation and distance attenuation + VectorSubtract(listener_origin, ch->origin, source_vec); + dist = VectorLength(source_vec); + intensity = mastervol * (1.0 - dist * ch->dist_mult); + if (intensity > 0) + { + qboolean occluded = false; + if (snd_spatialization_occlusion.integer) + { + if(snd_spatialization_occlusion.integer & 1) + if(listener_pvs) + { + int cluster = cl.worldmodel->brush.PointInLeaf(cl.worldmodel, ch->origin)->clusterindex; + if(cluster >= 0 && cluster < 8 * listener_pvsbytes && !CHECKPVSBIT(listener_pvs, cluster)) + occluded = true; + } + + if(snd_spatialization_occlusion.integer & 2) + if(!occluded) + if(cl.worldmodel && cl.worldmodel->brush.TraceLineOfSight && !cl.worldmodel->brush.TraceLineOfSight(cl.worldmodel, listener_origin, ch->origin)) + occluded = true; + } + if(occluded) + intensity *= 0.5; + + ch->prologic_invert = 1; + if (snd_spatialization_prologic.integer != 0) + { + if (dist == 0) + angle_factor = 0.5; + else + { + Matrix4x4_Transform(&listener_basematrix, ch->origin, source_vec); + VectorNormalize(source_vec); + + switch(spatialmethod) + { + case SPATIAL_LOG: + if(dist == 0) + f = spatialmin + spatialdiff * (spatialfactor < 0); // avoid log(0), but do the right thing + else + f = spatialmin + spatialdiff * bound(0, (log(dist) - spatialoffset) * spatialfactor, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_POW: + f = (pow(dist, spatialpower) - spatialoffset) * spatialfactor; + f = spatialmin + spatialdiff * bound(0, f, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_THRESH: + f = spatialmin + spatialdiff * (dist < spatialoffset); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_NONE: + default: + break; + } + + // the z axis needs to be removed and normalized because otherwise the volume would get lower as the sound source goes higher or lower then normal + source_vec[2] = 0; + VectorNormalize(source_vec); + angle_side = acos(source_vec[0]) / M_PI * 180; // angle between 0 and 180 degrees + angle_front = asin(source_vec[1]) / M_PI * 180; // angle between -90 and 90 degrees + if (angle_side > snd_spatialization_prologic_frontangle.value) + { + ch->prologic_invert = -1; // this will cause the right channel to do a 180 degrees phase shift (turning the sound wave upside down), + // but the best would be 90 degrees phase shift left and a -90 degrees phase shift right. + angle_factor = (angle_side - snd_spatialization_prologic_frontangle.value) / (360 - 2 * snd_spatialization_prologic_frontangle.value); + // angle_factor is between 0 and 1 and represents the angle range from the front left, to all the surround speakers (amount may vary, + // 1 in prologic I 2 in prologic II and 3 or 4 in prologic IIx) to the front right speaker. + if (angle_front > 0) + angle_factor = 1 - angle_factor; + } + else + angle_factor = angle_front / snd_spatialization_prologic_frontangle.value / 2.0 + 0.5; + //angle_factor is between 0 and 1 and represents the angle range from the front left to the center to the front right speaker + } + + vol = intensity * sqrt(angle_factor); + ch->listener_volume[0] = (int)bound(0, vol, 65536); + vol = intensity * sqrt(1 - angle_factor); + ch->listener_volume[1] = (int)bound(0, vol, 65536); + for (i = 2;i < SND_LISTENERS;i++) + ch->listener_volume[i] = 0; + } + else + { + for (i = 0;i < SND_LISTENERS;i++) + { + Matrix4x4_Transform(&listener_matrix[i], ch->origin, source_vec); + VectorNormalize(source_vec); + + switch(spatialmethod) + { + case SPATIAL_LOG: + if(dist == 0) + f = spatialmin + spatialdiff * (spatialfactor < 0); // avoid log(0), but do the right thing + else + f = spatialmin + spatialdiff * bound(0, (log(dist) - spatialoffset) * spatialfactor, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_POW: + f = (pow(dist, spatialpower) - spatialoffset) * spatialfactor; + f = spatialmin + spatialdiff * bound(0, f, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_THRESH: + f = spatialmin + spatialdiff * (dist < spatialoffset); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_NONE: + default: + break; + } + + vol = intensity * max(0, source_vec[0] * snd_speakerlayout.listeners[i].dotscale + snd_speakerlayout.listeners[i].dotbias); + + ch->listener_volume[i] = (int)bound(0, vol, 65536); + } + } + } + else + for (i = 0;i < SND_LISTENERS;i++) + ch->listener_volume[i] = 0; + } +} +void SND_Spatialize(channel_t *ch, qboolean isstatic) +{ + sfx_t *sfx = ch->sfx; + SND_Spatialize_WithSfx(ch, isstatic, sfx); +} + + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, vec3_t origin, float fvol, float attenuation, qboolean isstatic, int entnum, int entchannel, int startpos) +{ + if (!sfx) + { + Con_Printf("S_PlaySfxOnChannel called with NULL??\n"); + return; + } + + if ((sfx->loopstart < sfx->total_length) || (flags & CHANNELFLAG_FORCELOOP)) + { + if(!snd_startloopingsounds.integer) + return; + } + else + { + if(!snd_startnonloopingsounds.integer) + return; + } + + // Initialize the channel + // a crash was reported on an in-use channel, so check here... + if (target_chan->sfx) + { + int channelindex = (int)(target_chan - channels); + Con_Printf("S_PlaySfxOnChannel(%s): channel %i already in use?? Clearing.\n", sfx->name, channelindex); + S_StopChannel (channelindex, true, false); + } + // We MUST set sfx LAST because otherwise we could crash a threaded mixer + // (otherwise we'd have to call SndSys_LockRenderBuffer here) + memset (target_chan, 0, sizeof (*target_chan)); + VectorCopy (origin, target_chan->origin); + target_chan->flags = flags; + target_chan->pos = startpos; // start of the sound + target_chan->entnum = entnum; + target_chan->entchannel = entchannel; + + // If it's a static sound + if (isstatic) + { + if (sfx->loopstart >= sfx->total_length && (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEWORLD)) + Con_DPrintf("Quake compatibility warning: Static sound \"%s\" is not looped\n", sfx->name); + target_chan->dist_mult = attenuation / (64.0f * snd_soundradius.value); + } + else + target_chan->dist_mult = attenuation / snd_soundradius.value; + + // set the listener volumes + S_SetChannelVolume(target_chan - channels, fvol); + SND_Spatialize_WithSfx (target_chan, isstatic, sfx); + + // finally, set the sfx pointer, so the channel becomes valid for playback + // and will be noticed by the mixer + target_chan->sfx = sfx; +} + + +int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags) +{ + channel_t *target_chan, *check, *ch; + int ch_idx, startpos; + + if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) + return -1; + + if(sfx == &changevolume_sfx) + { + if (!IS_CHAN_SINGLE(entchannel)) + return -1; + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) + { + ch = &channels[ch_idx]; + if (ch->entnum == entnum && ch->entchannel == entchannel) + { + S_SetChannelVolume(ch_idx, fvol); + ch->dist_mult = attenuation / snd_soundradius.value; + SND_Spatialize(ch, false); + return ch_idx; + } + } + return -1; + } + + if (sfx->fetcher == NULL) + return -1; + + // Pick a channel to play on + target_chan = SND_PickChannel(entnum, entchannel); + if (!target_chan) + return -1; + + // if an identical sound has also been started this frame, offset the pos + // a bit to keep it from just making the first one louder + check = &channels[NUM_AMBIENTS]; + startpos = (int)(startposition * S_GetSoundRate()); + if (startpos == 0) + { + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++, check++) + { + if (check == target_chan) + continue; + if (check->sfx == sfx && check->pos == 0) + { + // use negative pos offset to delay this sound effect + startpos = (int)lhrandom(0, -0.1 * snd_renderbuffer->format.speed); + break; + } + } + } + + S_PlaySfxOnChannel (sfx, target_chan, flags, origin, fvol, attenuation, false, entnum, entchannel, startpos); + + return (target_chan - channels); +} + +int S_StartSound_StartPosition (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition) +{ + return S_StartSound_StartPosition_Flags(entnum, entchannel, sfx, origin, fvol, attenuation, startposition, CHANNELFLAG_NONE); +} + +int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation) +{ + return S_StartSound_StartPosition(entnum, entchannel, sfx, origin, fvol, attenuation, 0); +} + +void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx) +{ + channel_t *ch; + sfx_t *sfx; + + if (channel_ind >= total_channels) + return; + + // we have to lock an audio mutex to prevent crashes if an audio mixer + // thread is currently mixing this channel + // the SndSys_LockRenderBuffer function uses such a mutex in + // threaded sound backends + if (lockmutex && !simsound) + SndSys_LockRenderBuffer(); + + ch = &channels[channel_ind]; + sfx = ch->sfx; + if (ch->sfx != NULL) + { + if (sfx->fetcher != NULL) + { + snd_fetcher_endsb_t fetcher_endsb = sfx->fetcher->endsb; + if (fetcher_endsb != NULL) + fetcher_endsb (ch->fetcher_data); + } + + ch->fetcher_data = NULL; + ch->sfx = NULL; + } + if (lockmutex && !simsound) + SndSys_UnlockRenderBuffer(); + if (freesfx) + S_FreeSfx(sfx, true); +} + + +qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value) +{ + if (ch_ind >= total_channels) + return false; + + if (flag != CHANNELFLAG_FORCELOOP && + flag != CHANNELFLAG_PAUSED && + flag != CHANNELFLAG_FULLVOLUME && + flag != CHANNELFLAG_LOCALSOUND) + return false; + + if (value) + channels[ch_ind].flags |= flag; + else + channels[ch_ind].flags &= ~flag; + + return true; +} + +void S_StopSound(int entnum, int entchannel) +{ + unsigned int i; + + for (i = 0; i < MAX_DYNAMIC_CHANNELS; i++) + if (channels[i].entnum == entnum && channels[i].entchannel == entchannel) + { + S_StopChannel (i, true, false); + return; + } +} + +extern void CDAudio_Stop(void); +void S_StopAllSounds (void) +{ + unsigned int i; + + // TOCHECK: is this test necessary? + if (snd_renderbuffer == NULL) + return; + + // stop CD audio because it may be using a faketrack + CDAudio_Stop(); + + if (simsound || SndSys_LockRenderBuffer ()) + { + int clear; + size_t memsize; + + for (i = 0; i < total_channels; i++) + if (channels[i].sfx) + S_StopChannel (i, false, false); + + total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics + memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); + + // Mute the contents of the submittion buffer + clear = (snd_renderbuffer->format.width == 1) ? 0x80 : 0; + memsize = snd_renderbuffer->maxframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + memset(snd_renderbuffer->ring, clear, memsize); + + if (!simsound) + SndSys_UnlockRenderBuffer (); + } +} + +void S_PauseGameSounds (qboolean toggle) +{ + unsigned int i; + + for (i = 0; i < total_channels; i++) + { + channel_t *ch; + + ch = &channels[i]; + if (ch->sfx != NULL && ! (ch->flags & CHANNELFLAG_LOCALSOUND)) + S_SetChannelFlag (i, CHANNELFLAG_PAUSED, toggle); + } +} + +void S_SetChannelVolume(unsigned int ch_ind, float fvol) +{ + channels[ch_ind].master_vol = (int)(fvol * 65536.0f); +} + +float S_GetChannelPosition (unsigned int ch_ind) +{ + // note: this is NOT accurate yet + int s; + channel_t *ch = &channels[ch_ind]; + sfx_t *sfx = ch->sfx; + if (!sfx) + return -1; + + s = ch->pos; + /* + if(!snd_usethreadedmixing) + s += _snd_mixahead.value * S_GetSoundRate(); + */ + return (s % sfx->total_length) / (float) S_GetSoundRate(); +} + +float S_GetEntChannelPosition(int entnum, int entchannel) +{ + channel_t *ch; + unsigned int i; + + for (i = 0; i < total_channels; i++) + { + ch = &channels[i]; + if (ch->entnum == entnum && ch->entchannel == entchannel) + return S_GetChannelPosition(i); + } + return -1; // no playing sound in this channel +} + +/* +================= +S_StaticSound +================= +*/ +void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) +{ + channel_t *target_chan; + + if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) + return; + if (!sfx->fetcher) + { + Con_Printf ("S_StaticSound: \"%s\" hasn't been precached\n", sfx->name); + return; + } + + if (total_channels == MAX_CHANNELS) + { + Con_Print("S_StaticSound: total_channels == MAX_CHANNELS\n"); + return; + } + + target_chan = &channels[total_channels++]; + S_PlaySfxOnChannel (sfx, target_chan, CHANNELFLAG_FORCELOOP, origin, fvol, attenuation, true, 0, 0, 0); +} + + +/* +=================== +S_UpdateAmbientSounds +=================== +*/ +void S_UpdateAmbientSounds (void) +{ + int i; + int vol; + int ambient_channel; + channel_t *chan; + unsigned char ambientlevels[NUM_AMBIENTS]; + sfx_t *sfx; + + memset(ambientlevels, 0, sizeof(ambientlevels)); + if (cl.worldmodel && cl.worldmodel->brush.AmbientSoundLevelsForPoint) + cl.worldmodel->brush.AmbientSoundLevelsForPoint(cl.worldmodel, listener_origin, ambientlevels, sizeof(ambientlevels)); + + // Calc ambient sound levels + for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++) + { + chan = &channels[ambient_channel]; + sfx = chan->sfx; // fetch the volatile variable + if (sfx == NULL || sfx->fetcher == NULL) + continue; + + vol = (int)ambientlevels[ambient_channel]; + if (vol < 8) + vol = 0; + vol *= 256; + + // Don't adjust volume too fast + // FIXME: this rounds off to an int each frame, meaning there is little to no fade at extremely high framerates! + if (cl.time > cl.oldtime) + { + if (chan->master_vol < vol) + { + chan->master_vol += (int)((cl.time - cl.oldtime) * 256.0 * ambient_fade.value); + if (chan->master_vol > vol) + chan->master_vol = vol; + } + else if (chan->master_vol > vol) + { + chan->master_vol -= (int)((cl.time - cl.oldtime) * 256.0 * ambient_fade.value); + if (chan->master_vol < vol) + chan->master_vol = vol; + } + } + + if (snd_spatialization_prologic.integer != 0) + { + chan->listener_volume[0] = (int)bound(0, chan->master_vol * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5), 65536); + chan->listener_volume[1] = (int)bound(0, chan->master_vol * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5), 65536); + for (i = 2;i < SND_LISTENERS;i++) + chan->listener_volume[i] = 0; + } + else + { + for (i = 0;i < SND_LISTENERS;i++) + chan->listener_volume[i] = (int)bound(0, chan->master_vol * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[i].ambientvolume, 65536); + } + } +} + +static void S_PaintAndSubmit (void) +{ + unsigned int newsoundtime, paintedtime, endtime, maxtime, usedframes; + int usesoundtimehack; + static int soundtimehack = -1; + static int oldsoundtime = 0; + + if (snd_renderbuffer == NULL || nosound.integer) + return; + + // Update sound time + snd_usethreadedmixing = false; + usesoundtimehack = true; + if (cls.timedemo) // SUPER NASTY HACK to mix non-realtime sound for more reliable benchmarking + { + usesoundtimehack = 1; + newsoundtime = (unsigned int)((double)cl.mtime[0] * (double)snd_renderbuffer->format.speed); + } + else if (cls.capturevideo.soundrate && !cls.capturevideo.realtime) // SUPER NASTY HACK to record non-realtime sound + { + usesoundtimehack = 2; + newsoundtime = (unsigned int)((double)cls.capturevideo.frame * (double)snd_renderbuffer->format.speed / (double)cls.capturevideo.framerate); + } + else if (simsound) + { + usesoundtimehack = 3; + newsoundtime = (unsigned int)((realtime - snd_starttime) * (double)snd_renderbuffer->format.speed); + } + else + { + snd_usethreadedmixing = snd_threaded && !cls.capturevideo.soundrate; + usesoundtimehack = 0; + newsoundtime = SndSys_GetSoundTime(); + } + // if the soundtimehack state changes we need to reset the soundtime + if (soundtimehack != usesoundtimehack) + { + snd_renderbuffer->startframe = snd_renderbuffer->endframe = soundtime = newsoundtime; + + // Mute the contents of the submission buffer + if (simsound || SndSys_LockRenderBuffer ()) + { + int clear; + size_t memsize; + + clear = (snd_renderbuffer->format.width == 1) ? 0x80 : 0; + memsize = snd_renderbuffer->maxframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + memset(snd_renderbuffer->ring, clear, memsize); + + if (!simsound) + SndSys_UnlockRenderBuffer (); + } + } + soundtimehack = usesoundtimehack; + + if (!soundtimehack && snd_blocked > 0) + return; + + if (snd_usethreadedmixing) + return; // the audio thread will mix its own data + + newsoundtime += extrasoundtime; + if (newsoundtime < soundtime) + { + if ((cls.capturevideo.soundrate != 0) != recording_sound) + { + unsigned int additionaltime; + + // add some time to extrasoundtime make newsoundtime higher + + // The extra time must be a multiple of the render buffer size + // to avoid modifying the current position in the buffer, + // some modules write directly to a shared (DMA) buffer + additionaltime = (soundtime - newsoundtime) + snd_renderbuffer->maxframes - 1; + additionaltime -= additionaltime % snd_renderbuffer->maxframes; + + extrasoundtime += additionaltime; + newsoundtime += additionaltime; + Con_DPrintf("S_PaintAndSubmit: new extra sound time = %u\n", + extrasoundtime); + } + else if (!soundtimehack) + Con_Printf("S_PaintAndSubmit: WARNING: newsoundtime < soundtime (%u < %u)\n", + newsoundtime, soundtime); + } + soundtime = newsoundtime; + recording_sound = (cls.capturevideo.soundrate != 0); + + // Lock submitbuffer + if (!simsound && !SndSys_LockRenderBuffer()) + { + // If the lock failed, stop here + Con_DPrint(">> S_PaintAndSubmit: SndSys_LockRenderBuffer() failed\n"); + return; + } + + // Check to make sure that we haven't overshot + paintedtime = snd_renderbuffer->endframe; + if (paintedtime < soundtime) + paintedtime = soundtime; + + // mix ahead of current position + if (soundtimehack) + endtime = soundtime + (unsigned int)(_snd_mixahead.value * (float)snd_renderbuffer->format.speed); + else + endtime = soundtime + (unsigned int)(max(_snd_mixahead.value * (float)snd_renderbuffer->format.speed, min(3 * (soundtime - oldsoundtime), 0.3 * (float)snd_renderbuffer->format.speed))); + usedframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; + maxtime = paintedtime + snd_renderbuffer->maxframes - usedframes; + endtime = min(endtime, maxtime); + + while (paintedtime < endtime) + { + unsigned int startoffset; + unsigned int nbframes; + + // see how much we can fit in the paint buffer + nbframes = endtime - paintedtime; + // limit to the end of the ring buffer (in case of wrapping) + startoffset = paintedtime % snd_renderbuffer->maxframes; + nbframes = min(nbframes, snd_renderbuffer->maxframes - startoffset); + + // mix into the buffer + S_MixToBuffer(&snd_renderbuffer->ring[startoffset * snd_renderbuffer->format.width * snd_renderbuffer->format.channels], nbframes); + + paintedtime += nbframes; + snd_renderbuffer->endframe = paintedtime; + } + if (!simsound) + SndSys_UnlockRenderBuffer(); + + // Remove outdated samples from the ring buffer, if any + if (snd_renderbuffer->startframe < soundtime) + snd_renderbuffer->startframe = soundtime; + + if (simsound) + snd_renderbuffer->startframe = snd_renderbuffer->endframe; + else + SndSys_Submit(); + + oldsoundtime = soundtime; + + cls.soundstats.latency_milliseconds = (snd_renderbuffer->endframe - snd_renderbuffer->startframe) * 1000 / snd_renderbuffer->format.speed; + R_TimeReport("audiomix"); +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Update(const matrix4x4_t *listenermatrix) +{ + unsigned int i, j, k; + channel_t *ch, *combine; + matrix4x4_t rotatematrix; + + if (snd_renderbuffer == NULL || nosound.integer) + return; + + { + double mindist_trans, maxdist_trans; + + spatialmin = snd_spatialization_min.value; + spatialdiff = snd_spatialization_max.value - spatialmin; + + if(snd_spatialization_control.value) + { + spatialpower = snd_spatialization_power.value; + + if(spatialpower == 0) + { + spatialmethod = SPATIAL_LOG; + mindist_trans = log(max(1, snd_spatialization_min_radius.value)); + maxdist_trans = log(max(1, snd_spatialization_max_radius.value)); + } + else + { + spatialmethod = SPATIAL_POW; + mindist_trans = pow(snd_spatialization_min_radius.value, spatialpower); + maxdist_trans = pow(snd_spatialization_max_radius.value, spatialpower); + } + + if(mindist_trans - maxdist_trans == 0) + { + spatialmethod = SPATIAL_THRESH; + mindist_trans = snd_spatialization_min_radius.value; + } + else + { + spatialoffset = mindist_trans; + spatialfactor = 1 / (maxdist_trans - mindist_trans); + } + } + else + spatialmethod = SPATIAL_NONE; + + } + + // If snd_swapstereo or snd_channellayout has changed, recompute the channel layout + if (current_swapstereo != boolxor(snd_swapstereo.integer, v_flipped.integer) || + current_channellayout != snd_channellayout.integer) + S_SetChannelLayout(); + + Matrix4x4_Invert_Simple(&listener_basematrix, listenermatrix); + Matrix4x4_OriginFromMatrix(listenermatrix, listener_origin); + if (cl.worldmodel && cl.worldmodel->brush.FatPVS && cl.worldmodel->brush.num_pvsclusterbytes && cl.worldmodel->brush.PointInLeaf) + { + if(cl.worldmodel->brush.num_pvsclusterbytes != listener_pvsbytes) + { + if(listener_pvs) + Mem_Free(listener_pvs); + listener_pvsbytes = cl.worldmodel->brush.num_pvsclusterbytes; + listener_pvs = (unsigned char *) Mem_Alloc(snd_mempool, listener_pvsbytes); + } + cl.worldmodel->brush.FatPVS(cl.worldmodel, listener_origin, 2, listener_pvs, listener_pvsbytes, 0); + } + else + { + if(listener_pvs) + { + Mem_Free(listener_pvs); + listener_pvs = NULL; + } + listener_pvsbytes = 0; + } + + // calculate the current matrices + for (j = 0;j < SND_LISTENERS;j++) + { + Matrix4x4_CreateFromQuakeEntity(&rotatematrix, 0, 0, 0, 0, -snd_speakerlayout.listeners[j].yawangle, 0, 1); + Matrix4x4_Concat(&listener_matrix[j], &rotatematrix, &listener_basematrix); + // I think this should now do this: + // 1. create a rotation matrix for rotating by e.g. -90 degrees CCW + // (note: the matrix will rotate the OBJECT, not the VIEWER, so its + // angle has to be taken negative) + // 2. create a transform which first rotates and moves its argument + // into the player's view coordinates (using basematrix which is + // an inverted "absolute" listener matrix), then applies the + // rotation matrix for the ear + // Isn't Matrix4x4_CreateFromQuakeEntity a bit misleading because this + // does not actually refer to an entity? + } + + // update general area ambient sound sources + S_UpdateAmbientSounds (); + + combine = NULL; + R_TimeReport("audioprep"); + + // update spatialization for static and dynamic sounds + cls.soundstats.totalsounds = 0; + cls.soundstats.mixedsounds = 0; + ch = channels+NUM_AMBIENTS; + for (i=NUM_AMBIENTS ; isfx) + continue; + cls.soundstats.totalsounds++; + + // respatialize channel + SND_Spatialize(ch, i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS); + + // try to combine static sounds with a previous channel of the same + // sound effect so we don't mix five torches every frame + if (i > MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS) + { + // no need to merge silent channels + for (j = 0;j < SND_LISTENERS;j++) + if (ch->listener_volume[j]) + break; + if (j == SND_LISTENERS) + continue; + // if the last combine chosen isn't suitable, find a new one + if (!(combine && combine != ch && combine->sfx == ch->sfx)) + { + // search for one + combine = NULL; + for (j = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS;j < i;j++) + { + if (channels[j].sfx == ch->sfx) + { + combine = channels + j; + break; + } + } + } + if (combine && combine != ch && combine->sfx == ch->sfx) + { + for (j = 0;j < SND_LISTENERS;j++) + { + combine->listener_volume[j] = bound(0, combine->listener_volume[j] + ch->listener_volume[j], 65536); + ch->listener_volume[j] = 0; + } + } + } + for (k = 0;k < SND_LISTENERS;k++) + if (ch->listener_volume[k]) + break; + if (k < SND_LISTENERS) + cls.soundstats.mixedsounds++; + } + R_TimeReport("audiospatialize"); + + sound_spatialized = true; + + // debugging output + if (snd_show.integer) + Con_Printf("----(%u)----\n", cls.soundstats.mixedsounds); + + S_PaintAndSubmit(); +} + +void S_ExtraUpdate (void) +{ + if (snd_noextraupdate.integer || !sound_spatialized) + return; + + S_PaintAndSubmit(); +} + +qboolean S_LocalSound (const char *sound) +{ + sfx_t *sfx; + int ch_ind; + + if (!snd_initialized.integer || nosound.integer) + return true; + + sfx = S_PrecacheSound (sound, true, false); + if (!sfx) + { + Con_Printf("S_LocalSound: can't precache %s\n", sound); + return false; + } + + // menu sounds must not be freed on level change + sfx->flags |= SFXFLAG_MENUSOUND; + + // fun fact: in Quake 1, this used -1 "replace any entity channel", + // which we no longer support anyway + // changed by Black in r4297 "Changed S_LocalSound to play multiple sounds at a time." + ch_ind = S_StartSound (cl.viewentity, 0, sfx, vec3_origin, 1, 0); + if (ch_ind < 0) + return false; + + channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; + return true; +} diff --git a/misc/source/darkplaces-src/snd_main.h b/misc/source/darkplaces-src/snd_main.h new file mode 100644 index 00000000..a166aba2 --- /dev/null +++ b/misc/source/darkplaces-src/snd_main.h @@ -0,0 +1,209 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef SND_MAIN_H +#define SND_MAIN_H + +#include "sound.h" + + +typedef struct snd_format_s +{ + unsigned int speed; + unsigned short width; + unsigned short channels; +} snd_format_t; + +typedef struct snd_buffer_s +{ + snd_format_t format; + unsigned int nbframes; // current size, in sample frames + unsigned int maxframes; // max size (buffer size), in sample frames + unsigned char samples[4]; // variable sized +} snd_buffer_t; + +typedef struct snd_ringbuffer_s +{ + snd_format_t format; + unsigned char* ring; + unsigned int maxframes; // max size (buffer size), in sample frames + unsigned int startframe; // index of the first frame in the buffer + // if startframe == endframe, the bufffer is empty + unsigned int endframe; // index of the first EMPTY frame in the "ring" buffer + // may be smaller than startframe if the "ring" buffer has wrapped +} snd_ringbuffer_t; + +// sfx_t flags +#define SFXFLAG_NONE 0 +#define SFXFLAG_FILEMISSING (1 << 0) // wasn't able to load the associated sound file +#define SFXFLAG_LEVELSOUND (1 << 1) // the sfx is part of the server or client precache list for this level +#define SFXFLAG_STREAMED (1 << 2) // informative only. You shouldn't need to know that +#define SFXFLAG_MENUSOUND (1 << 3) // not freed during level change (menu sounds, music, etc) + +typedef struct snd_fetcher_s snd_fetcher_t; +struct sfx_s +{ + char name[MAX_QPATH]; + sfx_t *next; + size_t memsize; // total memory used (including sfx_t and fetcher data) + + unsigned int flags; // cf SFXFLAG_* defines + unsigned int loopstart; // in sample frames. equals total_length if not looped + unsigned int total_length; // in sample frames + const snd_fetcher_t *fetcher; + void *fetcher_data; // Per-sfx data for the sound fetching functions + + float volume_mult; // for replay gain (multiplier to apply) + float volume_peak; // for replay gain (highest peak); if set to 0, ReplayGain isn't supported +}; + +// maximum supported speakers constant +#define SND_LISTENERS 8 + +typedef struct channel_s +{ + int listener_volume [SND_LISTENERS]; // 0-65536 volume per speaker + int master_vol; // 0-65536 master volume + sfx_t *sfx; // pointer to sound sample being used + unsigned int flags; // cf CHANNELFLAG_* defines + int pos; // sample position in sfx, negative values delay the start of the sound playback + int entnum; // to allow overriding a specific sound + int entchannel; + vec3_t origin; // origin of sound effect + vec_t dist_mult; // distance multiplier (attenuation/clipK) + void *fetcher_data; // Per-channel data for the sound fetching function + int prologic_invert;// whether a sound is played on the surround channels in prologic +} channel_t; + +// Sound fetching functions +// "start" is both an input and output parameter: it returns the actual start time of the sound buffer +typedef const snd_buffer_t* (*snd_fetcher_getsb_t) (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes); +typedef void (*snd_fetcher_endsb_t) (void *chfetcherdata); +typedef void (*snd_fetcher_free_t) (void *sfxfetcherdata); +typedef const snd_format_t* (*snd_fetcher_getfmt_t) (sfx_t* sfx); +struct snd_fetcher_s +{ + snd_fetcher_getsb_t getsb; + snd_fetcher_endsb_t endsb; + snd_fetcher_free_t free; + snd_fetcher_getfmt_t getfmt; +}; + +extern unsigned int total_channels; +extern channel_t channels[MAX_CHANNELS]; + +extern snd_ringbuffer_t *snd_renderbuffer; +extern qboolean snd_threaded; // enables use of snd_usethreadedmixing, provided that no sound hacks are in effect (like timedemo) +extern qboolean snd_usethreadedmixing; // if true, the main thread does not mix sound, soundtime does not advance, and neither does snd_renderbuffer->endframe, instead the audio thread will call S_MixToBuffer as needed + +extern cvar_t _snd_mixahead; +extern cvar_t snd_swapstereo; +extern cvar_t snd_streaming; + +#define SND_CHANNELLAYOUT_AUTO 0 +#define SND_CHANNELLAYOUT_STANDARD 1 +#define SND_CHANNELLAYOUT_ALSA 2 +extern cvar_t snd_channellayout; + +extern int snd_blocked; // counter. When > 0, we stop submitting sound to the audio device + +extern mempool_t *snd_mempool; + +// If simsound is true, the sound card is not initialized and no sound is submitted to it. +// More generally, all arch-dependent operations are skipped or emulated. +// Used for isolating performance in the renderer. +extern qboolean simsound; + + +#define STREAM_BUFFER_DURATION 0.3f // in seconds +#define STREAM_BUFFER_FILL 0.2f // in seconds +#define STREAM_BUFFER_SIZE(format_ptr) ((int)ceil (STREAM_BUFFER_DURATION * (format_ptr)->speed) * (format_ptr)->width * (format_ptr)->channels) + +// We work with 1 sec sequences, so this buffer must be able to contain +// 1 sec of sound of the highest quality (48 KHz, 16 bit samples, stereo) +extern unsigned char resampling_buffer [48000 * 2 * 2]; + + +// ==================================================================== +// Architecture-independent functions +// ==================================================================== + +void S_MixToBuffer(void *stream, unsigned int frames); + +qboolean S_LoadSound (sfx_t *sfx, qboolean complain); + +snd_buffer_t *Snd_CreateSndBuffer (const unsigned char *samples, unsigned int sampleframes, const snd_format_t* in_format, unsigned int sb_speed); +qboolean Snd_AppendToSndBuffer (snd_buffer_t* sb, const unsigned char *samples, unsigned int sampleframes, const snd_format_t* format); + +// If "buffer" is NULL, the function allocates one buffer of "sampleframes" sample frames itself +// (if "sampleframes" is 0, the function chooses the size). +snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int sampleframes, void* buffer); + + +// ==================================================================== +// Architecture-dependent functions +// ==================================================================== + +// Create "snd_renderbuffer" with the proper sound format if the call is successful +// May return a suggested format if the requested format isn't available +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested); + +// Stop the sound card, delete "snd_renderbuffer" and free its other resources +void SndSys_Shutdown (void); + +// Submit the contents of "snd_renderbuffer" to the sound card +void SndSys_Submit (void); + +// Returns the number of sample frames consumed since the sound started +unsigned int SndSys_GetSoundTime (void); + +// Get the exclusive lock on "snd_renderbuffer" +qboolean SndSys_LockRenderBuffer (void); + +// Release the exclusive lock on "snd_renderbuffer" +void SndSys_UnlockRenderBuffer (void); + +// if the sound system can generate events, send them +void SndSys_SendKeyEvents(void); + +// exported for capturevideo so ogg can see all channels +typedef struct portable_samplepair_s +{ + int sample[SND_LISTENERS]; +} portable_sampleframe_t; + +typedef struct listener_s +{ + int channel_unswapped; // for un-swapping + float yawangle; + float dotscale; + float dotbias; + float ambientvolume; +} +listener_t; +typedef struct speakerlayout_s +{ + const char *name; + unsigned int channels; + listener_t listeners[SND_LISTENERS]; +} +speakerlayout_t; + +#endif diff --git a/misc/source/darkplaces-src/snd_mem.c b/misc/source/darkplaces-src/snd_mem.c new file mode 100644 index 00000000..11b0a9d9 --- /dev/null +++ b/misc/source/darkplaces-src/snd_mem.c @@ -0,0 +1,390 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +#include "quakedef.h" + +#include "snd_main.h" +#include "snd_ogg.h" +#include "snd_wav.h" +#include "snd_modplug.h" + +unsigned char resampling_buffer [48000 * 2 * 2]; + + +/* +==================== +Snd_CreateRingBuffer + +If "buffer" is NULL, the function allocates one buffer of "sampleframes" sample frames itself +(if "sampleframes" is 0, the function chooses the size). +==================== +*/ +snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int sampleframes, void* buffer) +{ + snd_ringbuffer_t *ringbuffer; + + // If the caller provides a buffer, it must give us its size + if (sampleframes == 0 && buffer != NULL) + return NULL; + + ringbuffer = (snd_ringbuffer_t*)Mem_Alloc(snd_mempool, sizeof (*ringbuffer)); + memset(ringbuffer, 0, sizeof(*ringbuffer)); + memcpy(&ringbuffer->format, format, sizeof(ringbuffer->format)); + + // If we haven't been given a buffer + if (buffer == NULL) + { + unsigned int maxframes; + size_t memsize; + + if (sampleframes == 0) + maxframes = (format->speed + 1) / 2; // Make the sound buffer large enough for containing 0.5 sec of sound + else + maxframes = sampleframes; + + memsize = maxframes * format->width * format->channels; + ringbuffer->ring = (unsigned char *) Mem_Alloc(snd_mempool, memsize); + ringbuffer->maxframes = maxframes; + } + else + { + ringbuffer->ring = (unsigned char *) buffer; + ringbuffer->maxframes = sampleframes; + } + + return ringbuffer; +} + + +/* +==================== +Snd_CreateSndBuffer +==================== +*/ +snd_buffer_t *Snd_CreateSndBuffer (const unsigned char *samples, unsigned int sampleframes, const snd_format_t* in_format, unsigned int sb_speed) +{ + size_t newsampleframes, memsize; + snd_buffer_t* sb; + + newsampleframes = (size_t) ceil((double)sampleframes * (double)sb_speed / (double)in_format->speed); + + memsize = newsampleframes * in_format->channels * in_format->width; + memsize += sizeof (*sb) - sizeof (sb->samples); + + sb = (snd_buffer_t*)Mem_Alloc (snd_mempool, memsize); + sb->format.channels = in_format->channels; + sb->format.width = in_format->width; + sb->format.speed = sb_speed; + sb->maxframes = newsampleframes; + sb->nbframes = 0; + + if (!Snd_AppendToSndBuffer (sb, samples, sampleframes, in_format)) + { + Mem_Free (sb); + return NULL; + } + + return sb; +} + + +/* +==================== +Snd_AppendToSndBuffer +==================== +*/ +qboolean Snd_AppendToSndBuffer (snd_buffer_t* sb, const unsigned char *samples, unsigned int sampleframes, const snd_format_t* format) +{ + size_t srclength, outcount; + unsigned char *out_data; + + //Con_DPrintf("ResampleSfx: %d samples @ %dHz -> %d samples @ %dHz\n", + // sampleframes, format->speed, outcount, sb->format.speed); + + // If the formats are incompatible + if (sb->format.channels != format->channels || sb->format.width != format->width) + { + Con_Print("AppendToSndBuffer: incompatible sound formats!\n"); + return false; + } + + outcount = (size_t) ((double)sampleframes * (double)sb->format.speed / (double)format->speed); + + // If the sound buffer is too short + if (outcount > sb->maxframes - sb->nbframes) + { + Con_Print("AppendToSndBuffer: sound buffer too short!\n"); + return false; + } + + out_data = &sb->samples[sb->nbframes * sb->format.width * sb->format.channels]; + srclength = sampleframes * format->channels; + + // Trivial case (direct transfer) + if (format->speed == sb->format.speed) + { + if (format->width == 1) + { + size_t i; + + for (i = 0; i < srclength; i++) + ((signed char*)out_data)[i] = samples[i] - 128; + } + else // if (format->width == 2) + memcpy (out_data, samples, srclength * format->width); + } + + // General case (linear interpolation with a fixed-point fractional + // step, 18-bit integer part and 14-bit fractional part) + // Can handle up to 2^18 (262144) samples per second (> 96KHz stereo) +# define FRACTIONAL_BITS 14 +# define FRACTIONAL_MASK ((1 << FRACTIONAL_BITS) - 1) +# define INTEGER_BITS (sizeof(samplefrac)*8 - FRACTIONAL_BITS) + else + { + const unsigned int fracstep = (unsigned int)((double)format->speed / sb->format.speed * (1 << FRACTIONAL_BITS)); + size_t remain_in = srclength, total_out = 0; + unsigned int samplefrac; + const unsigned char *in_ptr = samples; + unsigned char *out_ptr = out_data; + + // Check that we can handle one second of that sound + if (format->speed * format->channels > (1 << INTEGER_BITS)) + { + Con_Printf ("ResampleSfx: sound quality too high for resampling (%uHz, %u channel(s))\n", + format->speed, format->channels); + return 0; + } + + // We work 1 sec at a time to make sure we don't accumulate any + // significant error when adding "fracstep" over several seconds, and + // also to be able to handle very long sounds. + while (total_out < outcount) + { + size_t tmpcount, interpolation_limit, i, j; + unsigned int srcsample; + + samplefrac = 0; + + // If more than 1 sec of sound remains to be converted + if (outcount - total_out > sb->format.speed) + { + tmpcount = sb->format.speed; + interpolation_limit = tmpcount; // all samples can be interpolated + } + else + { + tmpcount = outcount - total_out; + interpolation_limit = (int)ceil((double)(((remain_in / format->channels) - 1) << FRACTIONAL_BITS) / fracstep); + if (interpolation_limit > tmpcount) + interpolation_limit = tmpcount; + } + + // 16 bit samples + if (format->width == 2) + { + const short* in_ptr_short; + + // Interpolated part + for (i = 0; i < interpolation_limit; i++) + { + srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; + in_ptr_short = &((const short*)in_ptr)[srcsample]; + + for (j = 0; j < format->channels; j++) + { + int a, b; + + a = *in_ptr_short; + b = *(in_ptr_short + format->channels); + *((short*)out_ptr) = (((b - a) * (samplefrac & FRACTIONAL_MASK)) >> FRACTIONAL_BITS) + a; + + in_ptr_short++; + out_ptr += sizeof (short); + } + + samplefrac += fracstep; + } + + // Non-interpolated part + for (/* nothing */; i < tmpcount; i++) + { + srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; + in_ptr_short = &((const short*)in_ptr)[srcsample]; + + for (j = 0; j < format->channels; j++) + { + *((short*)out_ptr) = *in_ptr_short; + + in_ptr_short++; + out_ptr += sizeof (short); + } + + samplefrac += fracstep; + } + } + // 8 bit samples + else // if (format->width == 1) + { + const unsigned char* in_ptr_byte; + + // Convert up to 1 sec of sound + for (i = 0; i < interpolation_limit; i++) + { + srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; + in_ptr_byte = &((const unsigned char*)in_ptr)[srcsample]; + + for (j = 0; j < format->channels; j++) + { + int a, b; + + a = *in_ptr_byte - 128; + b = *(in_ptr_byte + format->channels) - 128; + *((signed char*)out_ptr) = (((b - a) * (samplefrac & FRACTIONAL_MASK)) >> FRACTIONAL_BITS) + a; + + in_ptr_byte++; + out_ptr += sizeof (signed char); + } + + samplefrac += fracstep; + } + + // Non-interpolated part + for (/* nothing */; i < tmpcount; i++) + { + srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; + in_ptr_byte = &((const unsigned char*)in_ptr)[srcsample]; + + for (j = 0; j < format->channels; j++) + { + *((signed char*)out_ptr) = *in_ptr_byte - 128; + + in_ptr_byte++; + out_ptr += sizeof (signed char); + } + + samplefrac += fracstep; + } + } + + // Update the counters and the buffer position + remain_in -= format->speed * format->channels; + in_ptr += format->speed * format->channels * format->width; + total_out += tmpcount; + } + } + + sb->nbframes += outcount; + return true; +} + + +//============================================================================= + +/* +============== +S_LoadSound +============== +*/ +qboolean S_LoadSound (sfx_t *sfx, qboolean complain) +{ + char namebuffer[MAX_QPATH + 16]; + size_t len; + + // See if already loaded + if (sfx->fetcher != NULL) + return true; + + // If we weren't able to load it previously, no need to retry + // Note: S_PrecacheSound clears this flag to cause a retry + if (sfx->flags & SFXFLAG_FILEMISSING) + return false; + + // No sound? + if (snd_renderbuffer == NULL) + return false; + + // Initialize volume peak to 0; if ReplayGain is supported, the loader will change this away + sfx->volume_peak = 0.0; + + if (developer_loading.integer) + Con_Printf("loading sound %s\n", sfx->name); + + SCR_PushLoadingScreen(true, sfx->name, 1); + + // LordHavoc: if the sound filename does not begin with sound/, try adding it + if (strncasecmp(sfx->name, "sound/", 6)) + { + dpsnprintf (namebuffer, sizeof(namebuffer), "sound/%s", sfx->name); + len = strlen(namebuffer); + if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".wav")) + { + if (S_LoadWavFile (namebuffer, sfx)) + goto loaded; + memcpy (namebuffer + len - 3, "ogg", 4); + } + if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".ogg")) + { + if (OGG_LoadVorbisFile (namebuffer, sfx)) + goto loaded; + } + else + { + if (ModPlug_LoadModPlugFile (namebuffer, sfx)) + goto loaded; + } + } + + // LordHavoc: then try without the added sound/ as wav and ogg + dpsnprintf (namebuffer, sizeof(namebuffer), "%s", sfx->name); + len = strlen(namebuffer); + // request foo.wav: tries foo.wav, then foo.ogg + // request foo.ogg: tries foo.ogg only + // request foo.mod: tries foo.mod only + if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".wav")) + { + if (S_LoadWavFile (namebuffer, sfx)) + goto loaded; + memcpy (namebuffer + len - 3, "ogg", 4); + } + if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".ogg")) + { + if (OGG_LoadVorbisFile (namebuffer, sfx)) + goto loaded; + } + else + { + if (ModPlug_LoadModPlugFile (namebuffer, sfx)) + goto loaded; + } + + // Can't load the sound! + sfx->flags |= SFXFLAG_FILEMISSING; + if (complain) + Con_DPrintf("failed to load sound \"%s\"\n", sfx->name); + + SCR_PopLoadingScreen(false); + return false; + +loaded: + SCR_PopLoadingScreen(false); + return true; +} diff --git a/misc/source/darkplaces-src/snd_mix.c b/misc/source/darkplaces-src/snd_mix.c new file mode 100644 index 00000000..6bec880e --- /dev/null +++ b/misc/source/darkplaces-src/snd_mix.c @@ -0,0 +1,562 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "snd_main.h" + + +static portable_sampleframe_t paintbuffer[PAINTBUFFER_SIZE]; +static portable_sampleframe_t paintbuffer_unswapped[PAINTBUFFER_SIZE]; + +extern speakerlayout_t snd_speakerlayout; // for querying the listeners + +extern void SCR_CaptureVideo_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length); +static void S_CaptureAVISound(size_t length) +{ + size_t i; + unsigned int j; + + if (!cls.capturevideo.active) + return; + + // undo whatever swapping the channel layout (swapstereo, ALSA) did + for(j = 0; j < snd_speakerlayout.channels; ++j) + { + unsigned int j0 = snd_speakerlayout.listeners[j].channel_unswapped; + for(i = 0; i < length; ++i) + paintbuffer_unswapped[i].sample[j0] = paintbuffer[i].sample[j]; + } + + SCR_CaptureVideo_SoundFrame(paintbuffer_unswapped, length); +} + +static void S_ConvertPaintBuffer(const portable_sampleframe_t *painted_ptr, void *rb_ptr, int nbframes, int width, int channels) +{ + int i, val; + if (width == 2) // 16bit + { + short *snd_out = (short*)rb_ptr; + if (channels == 8) // 7.1 surround + { + for (i = 0;i < nbframes;i++, painted_ptr++) + { + *snd_out++ = bound(-32768, painted_ptr->sample[0], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[1], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[2], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[3], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[4], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[5], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[6], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[7], 32767); + } + } + else if (channels == 6) // 5.1 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + *snd_out++ = bound(-32768, painted_ptr->sample[0], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[1], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[2], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[3], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[4], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[5], 32767); + } + } + else if (channels == 4) // 4.0 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + *snd_out++ = bound(-32768, painted_ptr->sample[0], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[1], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[2], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[3], 32767); + } + } + else if (channels == 2) // 2.0 stereo + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + *snd_out++ = bound(-32768, painted_ptr->sample[0], 32767); + *snd_out++ = bound(-32768, painted_ptr->sample[1], 32767); + } + } + else if (channels == 1) // 1.0 mono + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (painted_ptr->sample[0] + painted_ptr->sample[1]) >> 1; + *snd_out++ = bound(-32768, val, 32767); + } + } + + // noise is really really annoying + if (cls.timedemo) + memset(rb_ptr, 0, nbframes * channels * width); + } + else // 8bit + { + unsigned char *snd_out = (unsigned char*)rb_ptr; + if (channels == 8) // 7.1 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (painted_ptr->sample[0] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[1] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[2] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[3] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[4] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[5] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[6] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[7] >> 8) + 128; *snd_out++ = bound(0, val, 255); + } + } + else if (channels == 6) // 5.1 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (painted_ptr->sample[0] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[1] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[2] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[3] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[4] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[5] >> 8) + 128; *snd_out++ = bound(0, val, 255); + } + } + else if (channels == 4) // 4.0 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (painted_ptr->sample[0] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[1] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[2] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[3] >> 8) + 128; *snd_out++ = bound(0, val, 255); + } + } + else if (channels == 2) // 2.0 stereo + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (painted_ptr->sample[0] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (painted_ptr->sample[1] >> 8) + 128; *snd_out++ = bound(0, val, 255); + } + } + else if (channels == 1) // 1.0 mono + { + for (i = 0;i < nbframes;i++, painted_ptr++) + { + val = ((painted_ptr->sample[0] + painted_ptr->sample[1]) >> 9) + 128; + *snd_out++ = bound(0, val, 255); + } + } + + // noise is really really annoying + if (cls.timedemo) + memset(rb_ptr, 128, nbframes * channels); + } +} + + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +static qboolean SND_PaintChannel (channel_t *ch, portable_sampleframe_t *paint, unsigned int count) +{ + int vol[SND_LISTENERS]; + const snd_buffer_t *sb; + unsigned int i, sb_offset; + sfx_t *sfx; + + sfx = ch->sfx; // fetch the volatile variable + if (!sfx) // given that this is called by the mixer thread, this never happens, but... + return false; + + // move to the stack (do we need to?) + for (i = 0;i < SND_LISTENERS;i++) + vol[i] = ch->listener_volume[i]; + + // if volumes are all zero, just return + for (i = 0;i < SND_LISTENERS;i++) + if (vol[i]) + break; + if (i == SND_LISTENERS) + return false; + + sb_offset = ch->pos; + sb = sfx->fetcher->getsb (sfx->fetcher_data, &ch->fetcher_data, &sb_offset, count); + if (sb == NULL) + { + Con_DPrintf("SND_PaintChannel: ERROR: can't get sound buffer from sfx \"%s\"\n", + sfx->name); // , count); // or add this? FIXME + return false; + } + else + { +#if SND_LISTENERS != 8 +# error the following code only supports up to 8 channels, update it +#endif + if (sb->format.width == 1) + { + const signed char *samples = (signed char*)sb->samples + (ch->pos - sb_offset) * sb->format.channels; + + // Stereo sound support + if (sb->format.channels == 2) + { + if (vol[6] + vol[7] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] += (samples[1] * vol[1]) >> 8; + paint[i].sample[2] += (samples[0] * vol[2]) >> 8; + paint[i].sample[3] += (samples[1] * vol[3]) >> 8; + paint[i].sample[4] += ((samples[0] + samples[1]) * vol[4]) >> 9; + paint[i].sample[5] += ((samples[0] + samples[1]) * vol[5]) >> 9; + paint[i].sample[6] += (samples[0] * vol[6]) >> 8; + paint[i].sample[7] += (samples[1] * vol[7]) >> 8; + samples += 2; + } + } + else if (vol[4] + vol[5] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] += (samples[1] * vol[1]) >> 8; + paint[i].sample[2] += (samples[0] * vol[2]) >> 8; + paint[i].sample[3] += (samples[1] * vol[3]) >> 8; + paint[i].sample[4] += ((samples[0] + samples[1]) * vol[4]) >> 9; + paint[i].sample[5] += ((samples[0] + samples[1]) * vol[5]) >> 9; + samples += 2; + } + } + else if (vol[2] + vol[3] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] += (samples[1] * vol[1]) >> 8; + paint[i].sample[2] += (samples[0] * vol[2]) >> 8; + paint[i].sample[3] += (samples[1] * vol[3]) >> 8; + samples += 2; + } + } + else if (vol[0] + vol[1] > 0 && ch->prologic_invert == -1) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] -= (samples[1] * vol[1]) >> 8; + samples += 2; + } + } + else if (vol[0] + vol[1] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] += (samples[1] * vol[1]) >> 8; + samples += 2; + } + } + } + else if (sb->format.channels == 1) + { + if (vol[6] + vol[7] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] += (samples[0] * vol[1]) >> 8; + paint[i].sample[2] += (samples[0] * vol[2]) >> 8; + paint[i].sample[3] += (samples[0] * vol[3]) >> 8; + paint[i].sample[4] += (samples[0] * vol[4]) >> 8; + paint[i].sample[5] += (samples[0] * vol[5]) >> 8; + paint[i].sample[6] += (samples[0] * vol[6]) >> 8; + paint[i].sample[7] += (samples[0] * vol[7]) >> 8; + samples += 1; + } + } + else if (vol[4] + vol[5] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] += (samples[0] * vol[1]) >> 8; + paint[i].sample[2] += (samples[0] * vol[2]) >> 8; + paint[i].sample[3] += (samples[0] * vol[3]) >> 8; + paint[i].sample[4] += (samples[0] * vol[4]) >> 8; + paint[i].sample[5] += (samples[0] * vol[5]) >> 8; + samples += 1; + } + } + else if (vol[2] + vol[3] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] += (samples[0] * vol[1]) >> 8; + paint[i].sample[2] += (samples[0] * vol[2]) >> 8; + paint[i].sample[3] += (samples[0] * vol[3]) >> 8; + samples += 1; + } + } + else if (vol[0] + vol[1] > 0 && ch->prologic_invert == -1) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] -= (samples[0] * vol[1]) >> 8; + samples += 1; + } + } + else if (vol[0] + vol[1] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 8; + paint[i].sample[1] += (samples[0] * vol[1]) >> 8; + samples += 1; + } + } + } + else + return false; // unsupported number of channels in sound + } + else if (sb->format.width == 2) + { + const signed short *samples = (signed short*)sb->samples + (ch->pos - sb_offset) * sb->format.channels; + + // Stereo sound support + if (sb->format.channels == 2) + { + if (vol[6] + vol[7] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] += (samples[1] * vol[1]) >> 16; + paint[i].sample[2] += (samples[0] * vol[2]) >> 16; + paint[i].sample[3] += (samples[1] * vol[3]) >> 16; + paint[i].sample[4] += ((samples[0] + samples[1]) * vol[4]) >> 17; + paint[i].sample[5] += ((samples[0] + samples[1]) * vol[5]) >> 17; + paint[i].sample[6] += (samples[0] * vol[6]) >> 16; + paint[i].sample[7] += (samples[1] * vol[7]) >> 16; + samples += 2; + } + } + else if (vol[4] + vol[5] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] += (samples[1] * vol[1]) >> 16; + paint[i].sample[2] += (samples[0] * vol[2]) >> 16; + paint[i].sample[3] += (samples[1] * vol[3]) >> 16; + paint[i].sample[4] += ((samples[0] + samples[1]) * vol[4]) >> 17; + paint[i].sample[5] += ((samples[0] + samples[1]) * vol[5]) >> 17; + samples += 2; + } + } + else if (vol[2] + vol[3] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] += (samples[1] * vol[1]) >> 16; + paint[i].sample[2] += (samples[0] * vol[2]) >> 16; + paint[i].sample[3] += (samples[1] * vol[3]) >> 16; + samples += 2; + } + } + else if (vol[0] + vol[1] > 0 && ch->prologic_invert == -1) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] -= (samples[1] * vol[1]) >> 16; + samples += 2; + } + } + else if (vol[0] + vol[1] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] += (samples[1] * vol[1]) >> 16; + samples += 2; + } + } + } + else if (sb->format.channels == 1) + { + if (vol[6] + vol[7] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] += (samples[0] * vol[1]) >> 16; + paint[i].sample[2] += (samples[0] * vol[2]) >> 16; + paint[i].sample[3] += (samples[0] * vol[3]) >> 16; + paint[i].sample[4] += (samples[0] * vol[4]) >> 16; + paint[i].sample[5] += (samples[0] * vol[5]) >> 16; + paint[i].sample[6] += (samples[0] * vol[6]) >> 16; + paint[i].sample[7] += (samples[0] * vol[7]) >> 16; + samples += 1; + } + } + else if (vol[4] + vol[5] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] += (samples[0] * vol[1]) >> 16; + paint[i].sample[2] += (samples[0] * vol[2]) >> 16; + paint[i].sample[3] += (samples[0] * vol[3]) >> 16; + paint[i].sample[4] += (samples[0] * vol[4]) >> 16; + paint[i].sample[5] += (samples[0] * vol[5]) >> 16; + samples += 1; + } + } + else if (vol[2] + vol[3] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] += (samples[0] * vol[1]) >> 16; + paint[i].sample[2] += (samples[0] * vol[2]) >> 16; + paint[i].sample[3] += (samples[0] * vol[3]) >> 16; + samples += 1; + } + } + else if (vol[0] + vol[1] > 0 && ch->prologic_invert == -1) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] -= (samples[0] * vol[1]) >> 16; + samples += 1; + } + } + else if (vol[0] + vol[1] > 0) + { + for (i = 0;i < count;i++) + { + paint[i].sample[0] += (samples[0] * vol[0]) >> 16; + paint[i].sample[1] += (samples[0] * vol[1]) >> 16; + samples += 1; + } + } + } + else + return false; // unsupported number of channels in sound + } + } + return true; +} + +void S_MixToBuffer(void *stream, unsigned int bufferframes) +{ + unsigned int i; + channel_t *ch; + unsigned int frames; + unsigned char *outbytes = (unsigned char *) stream; + + // mix as many times as needed to fill the requested buffer + while (bufferframes) + { + // limit to the size of the paint buffer + frames = min(bufferframes, PAINTBUFFER_SIZE); + + // clear the paint buffer + memset (paintbuffer, 0, frames * sizeof (paintbuffer[0])); + + // paint in the channels. + // channels with zero volumes still advance in time but don't paint. + ch = channels; + for (i = 0; i < total_channels ; i++, ch++) + { + sfx_t *sfx; + int ltime; + int count; + + sfx = ch->sfx; + if (sfx == NULL) + continue; + if (!S_LoadSound (sfx, true)) + continue; + if (ch->flags & CHANNELFLAG_PAUSED) + continue; + if (!sfx->total_length) + continue; + if (sfx->total_length > 1<<30) + Sys_Error("S_MixToBuffer: sfx corrupt\n"); + + ltime = 0; + if (ch->pos < 0) + { + count = -ch->pos; + count = min(count, (int)frames - ltime); + ch->pos += count; + ltime += count; + } + + while (ltime < (int)frames) + { + // paint up to end of buffer or of input, whichever is lower + count = sfx->total_length - ch->pos; + count = bound(0, count, (int)frames - ltime); + // mix the remaining samples + if (count) + { + SND_PaintChannel (ch, paintbuffer + ltime, count); + ch->pos += count; + ltime += count; + } + // if at end of sfx, loop or stop the channel + else + { + if (sfx->loopstart < sfx->total_length) + ch->pos = sfx->loopstart; + else if (ch->flags & CHANNELFLAG_FORCELOOP) + ch->pos = 0; + else + { + S_StopChannel (ch - channels, false, false); + break; + } + } + } + } + + if (!snd_usethreadedmixing) + S_CaptureAVISound(frames); + + S_ConvertPaintBuffer(paintbuffer, outbytes, frames, snd_renderbuffer->format.width, snd_renderbuffer->format.channels); + + // advance the output pointer + outbytes += frames * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + bufferframes -= frames; + } +} diff --git a/misc/source/darkplaces-src/snd_modplug.c b/misc/source/darkplaces-src/snd_modplug.c new file mode 100644 index 00000000..596b38ff --- /dev/null +++ b/misc/source/darkplaces-src/snd_modplug.c @@ -0,0 +1,500 @@ +/* + Copyright (C) 2003-2005 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#include "quakedef.h" +#include "snd_main.h" +#include "snd_modplug.h" + +#ifdef SND_MODPLUG_STATIC + +#include +qboolean ModPlug_OpenLibrary (void) +{ + return true; // statically linked +} +void ModPlug_CloseLibrary (void) +{ +} +#define modplug_dll 1 +#define qModPlug_Load ModPlug_Load +#define qModPlug_Unload ModPlug_Unload +#define qModPlug_Read ModPlug_Read +#define qModPlug_Seek ModPlug_Seek +#define qModPlug_GetSettings ModPlug_GetSettings +#define qModPlug_SetSettings ModPlug_SetSettings +#define qModPlug_SetMasterVolume ModPlug_SetMasterVolume + +#else +// BEGIN SECTION FROM modplug.h + + /* + * This source code is public domain. + * + * Authors: Kenton Varda (C interface wrapper) + */ + + enum _ModPlug_Flags + { + MODPLUG_ENABLE_OVERSAMPLING = 1 << 0, /* Enable oversampling (*highly* recommended) */ + MODPLUG_ENABLE_NOISE_REDUCTION = 1 << 1, /* Enable noise reduction */ + MODPLUG_ENABLE_REVERB = 1 << 2, /* Enable reverb */ + MODPLUG_ENABLE_MEGABASS = 1 << 3, /* Enable megabass */ + MODPLUG_ENABLE_SURROUND = 1 << 4 /* Enable surround sound. */ + }; + + enum _ModPlug_ResamplingMode + { + MODPLUG_RESAMPLE_NEAREST = 0, /* No interpolation (very fast, extremely bad sound quality) */ + MODPLUG_RESAMPLE_LINEAR = 1, /* Linear interpolation (fast, good quality) */ + MODPLUG_RESAMPLE_SPLINE = 2, /* Cubic spline interpolation (high quality) */ + MODPLUG_RESAMPLE_FIR = 3 /* 8-tap fir filter (extremely high quality) */ + }; + + typedef struct _ModPlug_Settings + { + int mFlags; /* One or more of the MODPLUG_ENABLE_* flags above, bitwise-OR'ed */ + + /* Note that ModPlug always decodes sound at 44100kHz, 32 bit, stereo and then + * down-mixes to the settings you choose. */ + int mChannels; /* Number of channels - 1 for mono or 2 for stereo */ + int mBits; /* Bits per sample - 8, 16, or 32 */ + int mFrequency; /* Sampling rate - 11025, 22050, or 44100 */ + int mResamplingMode; /* One of MODPLUG_RESAMPLE_*, above */ + + int mStereoSeparation; /* Stereo separation, 1 - 256 */ + int mMaxMixChannels; /* Maximum number of mixing channels (polyphony), 32 - 256 */ + + int mReverbDepth; /* Reverb level 0(quiet)-100(loud) */ + int mReverbDelay; /* Reverb delay in ms, usually 40-200ms */ + int mBassAmount; /* XBass level 0(quiet)-100(loud) */ + int mBassRange; /* XBass cutoff in Hz 10-100 */ + int mSurroundDepth; /* Surround level 0(quiet)-100(heavy) */ + int mSurroundDelay; /* Surround delay in ms, usually 5-40ms */ + int mLoopCount; /* Number of times to loop. Zero prevents looping. + -1 loops forever. */ + } ModPlug_Settings; + + struct _ModPlugFile; + typedef struct _ModPlugFile ModPlugFile; + +// END SECTION FROM modplug.h + +static ModPlugFile* (*qModPlug_Load) (const void* data, int size); +static void (*qModPlug_Unload) (ModPlugFile* file); +static int (*qModPlug_Read) (ModPlugFile* file, void* buffer, int size); +static void (*qModPlug_Seek) (ModPlugFile* file, int millisecond); +static void (*qModPlug_GetSettings) (ModPlug_Settings* settings); +static void (*qModPlug_SetSettings) (const ModPlug_Settings* settings); +typedef void (ModPlug_SetMasterVolume_t) (ModPlugFile* file,unsigned int cvol) ; +ModPlug_SetMasterVolume_t *qModPlug_SetMasterVolume; + + +static dllfunction_t modplugfuncs[] = +{ + {"ModPlug_Load", (void **) &qModPlug_Load}, + {"ModPlug_Unload", (void **) &qModPlug_Unload}, + {"ModPlug_Read", (void **) &qModPlug_Read}, + {"ModPlug_Seek", (void **) &qModPlug_Seek}, + {"ModPlug_GetSettings", (void **) &qModPlug_GetSettings}, + {"ModPlug_SetSettings", (void **) &qModPlug_SetSettings}, + {NULL, NULL} +}; + +// Handles for the modplug and modplugfile DLLs +static dllhandle_t modplug_dll = NULL; + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +ModPlug_OpenLibrary + +Try to load the modplugFile DLL +==================== +*/ +qboolean ModPlug_OpenLibrary (void) +{ + const char* dllnames_modplug [] = + { +#if defined(WIN32) + "libmodplug-1.dll", + "modplug.dll", +#elif defined(MACOSX) + "libmodplug.dylib", +#else + "libmodplug.so.1", + "libmodplug.so", +#endif + NULL + }; + + // Already loaded? + if (modplug_dll) + return true; + +// COMMANDLINEOPTION: Sound: -nomodplug disables modplug sound support + if (COM_CheckParm("-nomodplug")) + return false; + + // Load the DLLs + // We need to load both by hand because some OSes seem to not load + // the modplug DLL automatically when loading the modplugFile DLL + if(Sys_LoadLibrary (dllnames_modplug, &modplug_dll, modplugfuncs)) + { + qModPlug_SetMasterVolume = (ModPlug_SetMasterVolume_t *) Sys_GetProcAddress(modplug_dll, "ModPlug_SetMasterVolume"); + if(!qModPlug_SetMasterVolume) + Con_Print("Warning: modplug volume control not supported. Try getting a newer version of libmodplug.\n"); + return true; + } + else + return false; +} + + +/* +==================== +ModPlug_CloseLibrary + +Unload the modplugFile DLL +==================== +*/ +void ModPlug_CloseLibrary (void) +{ + Sys_UnloadLibrary (&modplug_dll); +} +#endif + + +/* +================================================================= + + modplug decoding + +================================================================= +*/ + +// Per-sfx data structure +typedef struct +{ + unsigned char *file; + size_t filesize; + snd_format_t format; + unsigned int total_length; + char name[128]; + sfx_t *sfx; +} modplug_stream_persfx_t; + +// Per-channel data structure +typedef struct +{ + ModPlugFile *mf; + unsigned int sb_offset; + int bs; + snd_buffer_t sb; // must be at the end due to its dynamically allocated size +} modplug_stream_perchannel_t; + + +/* +==================== +ModPlug_FetchSound +==================== +*/ +static const snd_buffer_t* ModPlug_FetchSound (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes) +{ + modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)*chfetcherpointer; + modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfxfetcher; + snd_buffer_t* sb; + int newlength, done, ret; + unsigned int real_start; + unsigned int factor; + + // If there's no fetcher structure attached to the channel yet + if (per_ch == NULL) + { + size_t buff_len, memsize; + snd_format_t sb_format; + + sb_format.speed = snd_renderbuffer->format.speed; + sb_format.width = per_sfx->format.width; + sb_format.channels = per_sfx->format.channels; + + buff_len = STREAM_BUFFER_SIZE(&sb_format); + memsize = sizeof (*per_ch) - sizeof (per_ch->sb.samples) + buff_len; + per_ch = (modplug_stream_perchannel_t *)Mem_Alloc (snd_mempool, memsize); + + // Open it with the modplugFile API + per_ch->mf = qModPlug_Load(per_sfx->file, per_sfx->filesize); + if (!per_ch->mf) + { + Con_Printf("error while reading ModPlug stream \"%s\"\n", per_sfx->name); + Mem_Free (per_ch); + return NULL; + } + +#ifndef SND_MODPLUG_STATIC + if(qModPlug_SetMasterVolume) +#endif + qModPlug_SetMasterVolume(per_ch->mf, 512); // max volume, DP scales down! + + per_ch->bs = 0; + + per_ch->sb_offset = 0; + per_ch->sb.format = sb_format; + per_ch->sb.nbframes = 0; + per_ch->sb.maxframes = buff_len / (per_ch->sb.format.channels * per_ch->sb.format.width); + + *chfetcherpointer = per_ch; + } + + real_start = *start; + + sb = &per_ch->sb; + factor = per_sfx->format.width * per_sfx->format.channels; + + // If the stream buffer can't contain that much samples anyway + if (nbsampleframes > sb->maxframes) + { + Con_Printf ("ModPlug_FetchSound: stream buffer too small (%u sample frames required)\n", nbsampleframes); + return NULL; + } + + // If the data we need has already been decompressed in the sfxbuffer, just return it + if (per_ch->sb_offset <= real_start && per_ch->sb_offset + sb->nbframes >= real_start + nbsampleframes) + { + *start = per_ch->sb_offset; + return sb; + } + + newlength = (int)(per_ch->sb_offset + sb->nbframes) - real_start; + + // If we need to skip some data before decompressing the rest, or if the stream has looped + if (newlength < 0 || per_ch->sb_offset > real_start) + { + unsigned int time_start; + unsigned int modplug_start; + + /* + MODs loop on their own, so any position is valid! + if (real_start > (unsigned int)per_sfx->total_length) + { + Con_Printf ("ModPlug_FetchSound: asked for a start position after the end of the sfx! (%u > %u)\n", + real_start, per_sfx->total_length); + return NULL; + } + */ + + // We work with 200ms (1/5 sec) steps to avoid rounding errors + time_start = real_start * 5 / snd_renderbuffer->format.speed; + modplug_start = time_start * (1000 / 5); + + Con_DPrintf("warning: mod file needed to seek (to %d)\n", modplug_start); + + qModPlug_Seek(per_ch->mf, modplug_start); + sb->nbframes = 0; + + real_start = (unsigned int) ((float)modplug_start / 1000 * snd_renderbuffer->format.speed); + if (*start - real_start + nbsampleframes > sb->maxframes) + { + Con_Printf ("ModPlug_FetchSound: stream buffer too small after seek (%u sample frames required)\n", + *start - real_start + nbsampleframes); + per_ch->sb_offset = real_start; + return NULL; + } + } + // Else, move forward the samples we need to keep in the sound buffer + else + { + memmove (sb->samples, sb->samples + (real_start - per_ch->sb_offset) * factor, newlength * factor); + sb->nbframes = newlength; + } + + per_ch->sb_offset = real_start; + + // We add more than one frame of sound to the buffer: + // 1- to ensure we won't lose many samples during the resampling process + // 2- to reduce calls to ModPlug_FetchSound to regulate workload + newlength = (int)(per_sfx->format.speed*STREAM_BUFFER_FILL); + if ((size_t) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed) + sb->nbframes > sb->maxframes) + { + Con_Printf ("ModPlug_FetchSound: stream buffer overflow (%u + %u = %u sample frames / %u)\n", + (unsigned int) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed), sb->nbframes, (unsigned int) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed) + sb->nbframes, sb->maxframes); + return NULL; + } + newlength *= factor; // convert from sample frames to bytes + if(newlength > (int)sizeof(resampling_buffer)) + newlength = sizeof(resampling_buffer); + + // Decompress in the resampling_buffer + done = 0; + while ((ret = qModPlug_Read (per_ch->mf, (char *)&resampling_buffer[done], (int)(newlength - done))) > 0) + done += ret; + if(done < newlength) + { + // Argh. We didn't get as many samples as we wanted. Probably + // libmodplug forgot what mLoopCount==-1 means... basically, this means + // we can't loop like this. Try to let DP fix it later... + per_sfx->sfx->total_length = (real_start + ((size_t)done / (size_t)factor)); + per_sfx->sfx->loopstart = 0; + + if(newlength != done) + Con_DPrintf("ModPlug_Fetch: wanted: %d, got: %d\n", newlength, done); + } + + Snd_AppendToSndBuffer (sb, resampling_buffer, (size_t)done / (size_t)factor, &per_sfx->format); + + *start = per_ch->sb_offset; + return sb; +} + + +/* +==================== +ModPlug_FetchEnd +==================== +*/ +static void ModPlug_FetchEnd (void *chfetcherdata) +{ + modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)chfetcherdata; + + if (per_ch != NULL) + { + // Free the modplug decoder + qModPlug_Unload (per_ch->mf); + + Mem_Free (per_ch); + } +} + + +/* +==================== +ModPlug_FreeSfx +==================== +*/ +static void ModPlug_FreeSfx (void *sfxfetcherdata) +{ + modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfxfetcherdata; + + // Free the modplug file + Mem_Free(per_sfx->file); + + // Free the stream structure + Mem_Free(per_sfx); +} + + +/* +==================== +ModPlug_GetFormat +==================== +*/ +static const snd_format_t* qModPlug_GetFormat (sfx_t* sfx) +{ + modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data; + return &per_sfx->format; +} + +static const snd_fetcher_t modplug_fetcher = { ModPlug_FetchSound, ModPlug_FetchEnd, ModPlug_FreeSfx, qModPlug_GetFormat }; + + +/* +==================== +ModPlug_LoadmodplugFile + +Load an modplug file into memory +==================== +*/ +qboolean ModPlug_LoadModPlugFile (const char *filename, sfx_t *sfx) +{ + unsigned char *data; + fs_offset_t filesize; + ModPlugFile *mf; + modplug_stream_persfx_t* per_sfx; + ModPlug_Settings s; + + if (!modplug_dll) + return false; + + // Already loaded? + if (sfx->fetcher != NULL) + return true; + + // Load the file + data = FS_LoadFile (filename, snd_mempool, false, &filesize); + if (data == NULL) + return false; + + if (developer_loading.integer >= 2) + Con_Printf ("Loading ModPlug file \"%s\"\n", filename); + + qModPlug_GetSettings(&s); + s.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION | MODPLUG_ENABLE_REVERB; + s.mChannels = 2; + s.mBits = 16; + s.mFrequency = 44100; + s.mResamplingMode = MODPLUG_RESAMPLE_SPLINE; + s.mLoopCount = -1; + qModPlug_SetSettings(&s); + + // Open it with the modplugFile API + if (!(mf = qModPlug_Load (data, filesize))) + { + Con_Printf ("error while opening ModPlug file \"%s\"\n", filename); + Mem_Free(data); + return false; + } + +#ifndef SND_MODPLUG_STATIC + if(qModPlug_SetMasterVolume) +#endif + qModPlug_SetMasterVolume(mf, 512); // max volume, DP scales down! + + if (developer_loading.integer >= 2) + Con_Printf ("\"%s\" will be streamed\n", filename); + per_sfx = (modplug_stream_persfx_t *)Mem_Alloc (snd_mempool, sizeof (*per_sfx)); + strlcpy(per_sfx->name, sfx->name, sizeof(per_sfx->name)); + sfx->memsize += sizeof (*per_sfx); + per_sfx->file = data; + per_sfx->filesize = filesize; + sfx->memsize += filesize; + + per_sfx->format.speed = 44100; // modplug always works at that rate + per_sfx->format.width = 2; // We always work with 16 bits samples + per_sfx->format.channels = 2; // stereo rulez ;) (MAYBE default to mono because Amiga MODs sound better then?) + per_sfx->sfx = sfx; + + sfx->fetcher_data = per_sfx; + sfx->fetcher = &modplug_fetcher; + sfx->flags |= SFXFLAG_STREAMED; + sfx->total_length = 2147384647; // they always loop + sfx->loopstart = sfx->total_length; // modplug does it + + return true; +} diff --git a/misc/source/darkplaces-src/snd_modplug.h b/misc/source/darkplaces-src/snd_modplug.h new file mode 100644 index 00000000..08110801 --- /dev/null +++ b/misc/source/darkplaces-src/snd_modplug.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2003 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef SND_ModPlug_H +#define SND_ModPlug_H + + +qboolean ModPlug_OpenLibrary (void); +void ModPlug_CloseLibrary (void); +qboolean ModPlug_LoadModPlugFile (const char *filename, sfx_t *sfx); + + +#endif diff --git a/misc/source/darkplaces-src/snd_null.c b/misc/source/darkplaces-src/snd_null.c new file mode 100644 index 00000000..83859644 --- /dev/null +++ b/misc/source/darkplaces-src/snd_null.c @@ -0,0 +1,174 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_null.c -- include this instead of all the other snd_* files to have +// no sound code whatsoever + +#include "quakedef.h" + +cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg)"}; +cvar_t mastervolume = {CVAR_SAVE, "mastervolume", "1", "master volume"}; +cvar_t volume = {CVAR_SAVE, "volume", "0.7", "volume of sound effects"}; +cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1", "volume of ambient sound effects (such as swampy sounds at the start of e1m2)"}; +cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0", "indicates the sound subsystem is active"}; +cvar_t snd_mutewhenidle = {CVAR_SAVE, "snd_mutewhenidle", "1", "whether to disable sound output when game window is inactive"}; + +void S_Init (void) +{ + Cvar_RegisterVariable(&bgmvolume); + Cvar_RegisterVariable(&mastervolume); + Cvar_RegisterVariable(&volume); + Cvar_RegisterVariable(&snd_staticvolume); + Cvar_RegisterVariable(&snd_initialized); + Cvar_RegisterVariable(&snd_mutewhenidle); +} + +void S_Terminate (void) +{ +} + +void S_Startup (void) +{ +} + +void S_Shutdown (void) +{ +} + +void S_ClearUsed (void) +{ +} + +void S_PurgeUnused (void) +{ +} + + +void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) +{ +} + +int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation) +{ + return -1; +} + +int S_StartSound_StartPosition (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition) +{ + return -1; +} + +int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags) +{ + return -1; +} + +void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx) +{ +} + +qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value) +{ + return false; +} + +void S_StopSound (int entnum, int entchannel) +{ +} + +void S_PauseGameSounds (qboolean toggle) +{ +} + +void S_SetChannelVolume (unsigned int ch_ind, float fvol) +{ +} + +sfx_t *S_PrecacheSound (const char *sample, qboolean complain, qboolean levelsound) +{ + return NULL; +} + +float S_SoundLength(const char *name) +{ + return -1; +} + +qboolean S_IsSoundPrecached (const sfx_t *sfx) +{ + return false; +} + +void S_UnloadAllSounds_f (void) +{ +} + +sfx_t *S_FindName (const char *name) +{ + return NULL; +} + +void S_Update(const matrix4x4_t *matrix) +{ +} + +void S_StopAllSounds (void) +{ +} + +void S_ExtraUpdate (void) +{ +} + +qboolean S_LocalSound (const char *s) +{ + return false; +} + +void S_BlockSound (void) +{ +} + +void S_UnblockSound (void) +{ +} + +int S_GetSoundRate(void) +{ + return 0; +} + +int S_GetSoundChannels(void) +{ + return 0; +} + +float S_GetChannelPosition (unsigned int ch_ind) +{ + return -1; +} + +float S_GetEntChannelPosition(int entnum, int entchannel) +{ + return -1; +} + +void SndSys_SendKeyEvents(void) +{ +} diff --git a/misc/source/darkplaces-src/snd_ogg.c b/misc/source/darkplaces-src/snd_ogg.c new file mode 100644 index 00000000..0ee16bab --- /dev/null +++ b/misc/source/darkplaces-src/snd_ogg.c @@ -0,0 +1,803 @@ +/* + Copyright (C) 2003-2005 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#include "quakedef.h" +#include "snd_main.h" +#include "snd_ogg.h" +#include "snd_wav.h" + +#ifdef LINK_TO_LIBVORBIS +#define OV_EXCLUDE_STATIC_CALLBACKS +#include +#include + +#define qov_clear ov_clear +#define qov_info ov_info +#define qov_comment ov_comment +#define qov_open_callbacks ov_open_callbacks +#define qov_pcm_seek ov_pcm_seek +#define qov_pcm_total ov_pcm_total +#define qov_read ov_read +#define qvorbis_comment_query vorbis_comment_query + +qboolean OGG_OpenLibrary (void) {return true;} +void OGG_CloseLibrary (void) {} +#else + +/* +================================================================= + + Minimal set of definitions from the Ogg Vorbis lib + (C) COPYRIGHT 1994-2001 by the XIPHOPHORUS Company + http://www.xiph.org/ + + WARNING: for a matter of simplicity, several pointer types are + casted to "void*", and most enumerated values are not included + +================================================================= +*/ + +#ifdef _MSC_VER +typedef __int64 ogg_int64_t; +#else +typedef long long ogg_int64_t; +#endif + +typedef struct +{ + size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource); + int (*seek_func) (void *datasource, ogg_int64_t offset, int whence); + int (*close_func) (void *datasource); + long (*tell_func) (void *datasource); +} ov_callbacks; + +typedef struct +{ + unsigned char *data; + int storage; + int fill; + int returned; + int unsynced; + int headerbytes; + int bodybytes; +} ogg_sync_state; + +typedef struct +{ + int version; + int channels; + long rate; + long bitrate_upper; + long bitrate_nominal; + long bitrate_lower; + long bitrate_window; + void *codec_setup; +} vorbis_info; + +typedef struct +{ + unsigned char *body_data; + long body_storage; + long body_fill; + long body_returned; + int *lacing_vals; + ogg_int64_t *granule_vals; + long lacing_storage; + long lacing_fill; + long lacing_packet; + long lacing_returned; + unsigned char header[282]; + int header_fill; + int e_o_s; + int b_o_s; + long serialno; + long pageno; + ogg_int64_t packetno; + ogg_int64_t granulepos; +} ogg_stream_state; + +typedef struct +{ + int analysisp; + vorbis_info *vi; + float **pcm; + float **pcmret; + int pcm_storage; + int pcm_current; + int pcm_returned; + int preextrapolate; + int eofflag; + long lW; + long W; + long nW; + long centerW; + ogg_int64_t granulepos; + ogg_int64_t sequence; + ogg_int64_t glue_bits; + ogg_int64_t time_bits; + ogg_int64_t floor_bits; + ogg_int64_t res_bits; + void *backend_state; +} vorbis_dsp_state; + +typedef struct +{ + long endbyte; + int endbit; + unsigned char *buffer; + unsigned char *ptr; + long storage; +} oggpack_buffer; + +typedef struct +{ + float **pcm; + oggpack_buffer opb; + long lW; + long W; + long nW; + int pcmend; + int mode; + int eofflag; + ogg_int64_t granulepos; + ogg_int64_t sequence; + vorbis_dsp_state *vd; + void *localstore; + long localtop; + long localalloc; + long totaluse; + void *reap; // VOIDED POINTER + long glue_bits; + long time_bits; + long floor_bits; + long res_bits; + void *internal; +} vorbis_block; + +typedef struct +{ + char **user_comments; + int *comment_lengths; + int comments; + char *vendor; +} vorbis_comment; + +typedef struct +{ + void *datasource; + int seekable; + ogg_int64_t offset; + ogg_int64_t end; + ogg_sync_state oy; + int links; + ogg_int64_t *offsets; + ogg_int64_t *dataoffsets; + long *serialnos; + ogg_int64_t *pcmlengths; + vorbis_info *vi; + vorbis_comment *vc; + ogg_int64_t pcm_offset; + int ready_state; + long current_serialno; + int current_link; + double bittrack; + double samptrack; + ogg_stream_state os; + vorbis_dsp_state vd; + vorbis_block vb; + ov_callbacks callbacks; +} OggVorbis_File; + + +/* +================================================================= + + DarkPlaces definitions + +================================================================= +*/ + +// Functions exported from the vorbisfile library +static int (*qov_clear) (OggVorbis_File *vf); +static vorbis_info* (*qov_info) (OggVorbis_File *vf,int link); +static vorbis_comment* (*qov_comment) (OggVorbis_File *vf,int link); +static char * (*qvorbis_comment_query) (vorbis_comment *vc, const char *tag, int count); +static int (*qov_open_callbacks) (void *datasource, OggVorbis_File *vf, + char *initial, long ibytes, + ov_callbacks callbacks); +static int (*qov_pcm_seek) (OggVorbis_File *vf,ogg_int64_t pos); +static ogg_int64_t (*qov_pcm_total) (OggVorbis_File *vf,int i); +static long (*qov_read) (OggVorbis_File *vf,char *buffer,int length, + int bigendianp,int word,int sgned,int *bitstream); + +static dllfunction_t vorbisfilefuncs[] = +{ + {"ov_clear", (void **) &qov_clear}, + {"ov_info", (void **) &qov_info}, + {"ov_comment", (void **) &qov_comment}, + {"ov_open_callbacks", (void **) &qov_open_callbacks}, + {"ov_pcm_seek", (void **) &qov_pcm_seek}, + {"ov_pcm_total", (void **) &qov_pcm_total}, + {"ov_read", (void **) &qov_read}, + {NULL, NULL} +}; + +static dllfunction_t vorbisfuncs[] = +{ + {"vorbis_comment_query", (void **) &qvorbis_comment_query}, + {NULL, NULL} +}; + +// Handles for the Vorbis and Vorbisfile DLLs +static dllhandle_t vo_dll = NULL; +static dllhandle_t vf_dll = NULL; + + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +OGG_OpenLibrary + +Try to load the VorbisFile DLL +==================== +*/ +qboolean OGG_OpenLibrary (void) +{ + const char* dllnames_vo [] = + { +#if defined(WIN32) + "libvorbis-0.dll", + "libvorbis.dll", + "vorbis.dll", +#elif defined(MACOSX) + "libvorbis.dylib", +#else + "libvorbis.so.0", + "libvorbis.so", +#endif + NULL + }; + const char* dllnames_vf [] = + { +#if defined(WIN32) + "libvorbisfile-3.dll", + "libvorbisfile.dll", + "vorbisfile.dll", +#elif defined(MACOSX) + "libvorbisfile.dylib", +#else + "libvorbisfile.so.3", + "libvorbisfile.so", +#endif + NULL + }; + + // Already loaded? + if (vf_dll) + return true; + +// COMMANDLINEOPTION: Sound: -novorbis disables ogg vorbis sound support + if (COM_CheckParm("-novorbis")) + return false; + + // Load the DLLs + // We need to load both by hand because some OSes seem to not load + // the vorbis DLL automatically when loading the VorbisFile DLL + return Sys_LoadLibrary (dllnames_vo, &vo_dll, vorbisfuncs) && Sys_LoadLibrary (dllnames_vf, &vf_dll, vorbisfilefuncs); +} + + +/* +==================== +OGG_CloseLibrary + +Unload the VorbisFile DLL +==================== +*/ +void OGG_CloseLibrary (void) +{ + Sys_UnloadLibrary (&vf_dll); + Sys_UnloadLibrary (&vo_dll); +} + +#endif + +/* +================================================================= + + Ogg Vorbis decoding + +================================================================= +*/ + +typedef struct +{ + unsigned char *buffer; + ogg_int64_t ind, buffsize; +} ov_decode_t; + +static size_t ovcb_read (void *ptr, size_t size, size_t nb, void *datasource) +{ + ov_decode_t *ov_decode = (ov_decode_t*)datasource; + size_t remain, len; + + remain = ov_decode->buffsize - ov_decode->ind; + len = size * nb; + if (remain < len) + len = remain - remain % size; + + memcpy (ptr, ov_decode->buffer + ov_decode->ind, len); + ov_decode->ind += len; + + return len / size; +} + +static int ovcb_seek (void *datasource, ogg_int64_t offset, int whence) +{ + ov_decode_t *ov_decode = (ov_decode_t*)datasource; + + switch (whence) + { + case SEEK_SET: + break; + case SEEK_CUR: + offset += ov_decode->ind; + break; + case SEEK_END: + offset += ov_decode->buffsize; + break; + default: + return -1; + } + if (offset < 0 || offset > ov_decode->buffsize) + return -1; + + ov_decode->ind = offset; + return 0; +} + +static int ovcb_close (void *ov_decode) +{ + return 0; +} + +static long ovcb_tell (void *ov_decode) +{ + return ((ov_decode_t*)ov_decode)->ind; +} + +// Per-sfx data structure +typedef struct +{ + unsigned char *file; + size_t filesize; + snd_format_t format; + unsigned int total_length; + char name[128]; +} ogg_stream_persfx_t; + +// Per-channel data structure +typedef struct +{ + OggVorbis_File vf; + ov_decode_t ov_decode; + unsigned int sb_offset; + int bs; + snd_buffer_t sb; // must be at the end due to its dynamically allocated size +} ogg_stream_perchannel_t; + + +static const ov_callbacks callbacks = {ovcb_read, ovcb_seek, ovcb_close, ovcb_tell}; + +/* +==================== +OGG_FetchSound +==================== +*/ +static const snd_buffer_t* OGG_FetchSound (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes) +{ + ogg_stream_perchannel_t* per_ch = (ogg_stream_perchannel_t *)*chfetcherpointer; + ogg_stream_persfx_t* per_sfx = (ogg_stream_persfx_t *)sfxfetcher; + snd_buffer_t* sb; + int newlength, done, ret; + unsigned int real_start; + unsigned int factor; + + // If there's no fetcher structure attached to the channel yet + if (per_ch == NULL) + { + size_t buff_len, memsize; + snd_format_t sb_format; + + sb_format.speed = snd_renderbuffer->format.speed; + sb_format.width = per_sfx->format.width; + sb_format.channels = per_sfx->format.channels; + + buff_len = STREAM_BUFFER_SIZE(&sb_format); + memsize = sizeof (*per_ch) - sizeof (per_ch->sb.samples) + buff_len; + per_ch = (ogg_stream_perchannel_t *)Mem_Alloc (snd_mempool, memsize); + + // Open it with the VorbisFile API + per_ch->ov_decode.buffer = per_sfx->file; + per_ch->ov_decode.ind = 0; + per_ch->ov_decode.buffsize = per_sfx->filesize; + if (qov_open_callbacks (&per_ch->ov_decode, &per_ch->vf, NULL, 0, callbacks) < 0) + { + Con_Printf("error while reading Ogg Vorbis stream \"%s\"\n", per_sfx->name); + Mem_Free (per_ch); + return NULL; + } + per_ch->bs = 0; + + per_ch->sb_offset = 0; + per_ch->sb.format = sb_format; + per_ch->sb.nbframes = 0; + per_ch->sb.maxframes = buff_len / (per_ch->sb.format.channels * per_ch->sb.format.width); + + *chfetcherpointer = per_ch; + } + + real_start = *start; + + sb = &per_ch->sb; + factor = per_sfx->format.width * per_sfx->format.channels; + + // If the stream buffer can't contain that much samples anyway + if (nbsampleframes > sb->maxframes) + { + Con_Printf ("OGG_FetchSound: stream buffer too small (%u sample frames required)\n", nbsampleframes); + return NULL; + } + + // If the data we need has already been decompressed in the sfxbuffer, just return it + if (per_ch->sb_offset <= real_start && per_ch->sb_offset + sb->nbframes >= real_start + nbsampleframes) + { + *start = per_ch->sb_offset; + return sb; + } + + newlength = (int)(per_ch->sb_offset + sb->nbframes) - real_start; + + // If we need to skip some data before decompressing the rest, or if the stream has looped + if (newlength < 0 || per_ch->sb_offset > real_start) + { + unsigned int time_start; + ogg_int64_t ogg_start; + int err; + + if (real_start > (unsigned int)per_sfx->total_length) + { + Con_Printf ("OGG_FetchSound: asked for a start position after the end of the sfx! (%u > %u)\n", + real_start, per_sfx->total_length); + return NULL; + } + + // We work with 200ms (1/5 sec) steps to avoid rounding errors + time_start = real_start * 5 / snd_renderbuffer->format.speed; + ogg_start = time_start * (per_sfx->format.speed / 5); + err = qov_pcm_seek (&per_ch->vf, ogg_start); + if (err != 0) + { + Con_Printf ("OGG_FetchSound: qov_pcm_seek(..., %d) returned %d\n", + real_start, err); + return NULL; + } + sb->nbframes = 0; + + real_start = (unsigned int) ((float)ogg_start / per_sfx->format.speed * snd_renderbuffer->format.speed); + if (*start - real_start + nbsampleframes > sb->maxframes) + { + Con_Printf ("OGG_FetchSound: stream buffer too small after seek (%u sample frames required)\n", + *start - real_start + nbsampleframes); + per_ch->sb_offset = real_start; + return NULL; + } + } + // Else, move forward the samples we need to keep in the sound buffer + else + { + memmove (sb->samples, sb->samples + (real_start - per_ch->sb_offset) * factor, newlength * factor); + sb->nbframes = newlength; + } + + per_ch->sb_offset = real_start; + + // We add more than one frame of sound to the buffer: + // 1- to ensure we won't lose many samples during the resampling process + // 2- to reduce calls to OGG_FetchSound to regulate workload + newlength = (int)(per_sfx->format.speed*STREAM_BUFFER_FILL); + // this is how much we FETCH... + if ((size_t) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed) + sb->nbframes > sb->maxframes) + { + Con_Printf ("OGG_FetchSound: stream buffer overflow (%u + %u = %u sample frames / %u)\n", + (unsigned int) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed), sb->nbframes, (unsigned int) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed) + sb->nbframes, sb->maxframes); + return NULL; + } + newlength *= factor; // convert from sample frames to bytes + if(newlength > (int)sizeof(resampling_buffer)) + newlength = sizeof(resampling_buffer); + + // Decompress in the resampling_buffer + done = 0; + while ((ret = qov_read (&per_ch->vf, (char *)&resampling_buffer[done], (int)(newlength - done), mem_bigendian, 2, 1, &per_ch->bs)) > 0) + done += ret; + + Snd_AppendToSndBuffer (sb, resampling_buffer, (size_t)done / (size_t)factor, &per_sfx->format); + + *start = per_ch->sb_offset; + return sb; +} + + +/* +==================== +OGG_FetchEnd +==================== +*/ +static void OGG_FetchEnd (void *chfetcherdata) +{ + ogg_stream_perchannel_t* per_ch = (ogg_stream_perchannel_t *)chfetcherdata; + + if (per_ch != NULL) + { + // Free the ogg vorbis decoder + qov_clear (&per_ch->vf); + + Mem_Free (per_ch); + } +} + + +/* +==================== +OGG_FreeSfx +==================== +*/ +static void OGG_FreeSfx (void *sfxfetcherdata) +{ + ogg_stream_persfx_t* per_sfx = (ogg_stream_persfx_t *)sfxfetcherdata; + + // Free the Ogg Vorbis file + Mem_Free(per_sfx->file); + + // Free the stream structure + Mem_Free(per_sfx); +} + + +/* +==================== +OGG_GetFormat +==================== +*/ +static const snd_format_t* OGG_GetFormat (sfx_t* sfx) +{ + ogg_stream_persfx_t* per_sfx = (ogg_stream_persfx_t *)sfx->fetcher_data; + return &per_sfx->format; +} + +static const snd_fetcher_t ogg_fetcher = { OGG_FetchSound, OGG_FetchEnd, OGG_FreeSfx, OGG_GetFormat }; + +static void OGG_DecodeTags(vorbis_comment *vc, unsigned int *start, unsigned int *length, double samplesfactor, unsigned int numsamples, double *peak, double *gaindb) +{ + const char *startcomment = NULL, *lengthcomment = NULL, *endcomment = NULL, *thiscomment = NULL; + + *start = numsamples; + *length = numsamples; + *peak = 0.0; + *gaindb = 0.0; + + if(!vc) + return; + + thiscomment = qvorbis_comment_query(vc, "REPLAYGAIN_TRACK_PEAK", 0); + if(thiscomment) + *peak = atof(thiscomment); + thiscomment = qvorbis_comment_query(vc, "REPLAYGAIN_TRACK_GAIN", 0); + if(thiscomment) + *gaindb = atof(thiscomment); + + startcomment = qvorbis_comment_query(vc, "LOOP_START", 0); // DarkPlaces, and some Japanese app + if(startcomment) + { + endcomment = qvorbis_comment_query(vc, "LOOP_END", 0); + if(!endcomment) + lengthcomment = qvorbis_comment_query(vc, "LOOP_LENGTH", 0); + } + else + { + startcomment = qvorbis_comment_query(vc, "LOOPSTART", 0); // RPG Maker VX + if(startcomment) + { + lengthcomment = qvorbis_comment_query(vc, "LOOPLENGTH", 0); + if(!lengthcomment) + endcomment = qvorbis_comment_query(vc, "LOOPEND", 0); + } + else + { + startcomment = qvorbis_comment_query(vc, "LOOPPOINT", 0); // Sonic Robo Blast 2 + } + } + + if(startcomment) + { + *start = (unsigned int) bound(0, atof(startcomment) * samplesfactor, numsamples); + if(endcomment) + *length = (unsigned int) bound(0, atof(endcomment) * samplesfactor, numsamples); + else if(lengthcomment) + *length = (unsigned int) bound(0, *start + atof(lengthcomment) * samplesfactor, numsamples); + } +} + +/* +==================== +OGG_LoadVorbisFile + +Load an Ogg Vorbis file into memory +==================== +*/ +qboolean OGG_LoadVorbisFile (const char *filename, sfx_t *sfx) +{ + unsigned char *data; + fs_offset_t filesize; + ov_decode_t ov_decode; + OggVorbis_File vf; + vorbis_info *vi; + vorbis_comment *vc; + ogg_int64_t len, buff_len; + double peak, gaindb; + +#ifndef LINK_TO_LIBVORBIS + if (!vf_dll) + return false; +#endif + + // Already loaded? + if (sfx->fetcher != NULL) + return true; + + // Load the file + data = FS_LoadFile (filename, snd_mempool, false, &filesize); + if (data == NULL) + return false; + + if (developer_loading.integer >= 2) + Con_Printf ("Loading Ogg Vorbis file \"%s\"\n", filename); + + // Open it with the VorbisFile API + ov_decode.buffer = data; + ov_decode.ind = 0; + ov_decode.buffsize = filesize; + if (qov_open_callbacks (&ov_decode, &vf, NULL, 0, callbacks) < 0) + { + Con_Printf ("error while opening Ogg Vorbis file \"%s\"\n", filename); + Mem_Free(data); + return false; + } + + // Get the stream information + vi = qov_info (&vf, -1); + if (vi->channels < 1 || vi->channels > 2) + { + Con_Printf("%s has an unsupported number of channels (%i)\n", + sfx->name, vi->channels); + qov_clear (&vf); + Mem_Free(data); + return false; + } + + len = qov_pcm_total (&vf, -1) * vi->channels * 2; // 16 bits => "* 2" + + // Decide if we go for a stream or a simple PCM cache + buff_len = (int)ceil (STREAM_BUFFER_DURATION * snd_renderbuffer->format.speed) * 2 * vi->channels; + if (snd_streaming.integer && (len > (ogg_int64_t)filesize + 3 * buff_len || snd_streaming.integer >= 2)) + { + ogg_stream_persfx_t* per_sfx; + + if (developer_loading.integer >= 2) + Con_Printf ("Ogg sound file \"%s\" will be streamed\n", filename); + per_sfx = (ogg_stream_persfx_t *)Mem_Alloc (snd_mempool, sizeof (*per_sfx)); + strlcpy(per_sfx->name, sfx->name, sizeof(per_sfx->name)); + sfx->memsize += sizeof (*per_sfx); + per_sfx->file = data; + per_sfx->filesize = filesize; + sfx->memsize += filesize; + + per_sfx->format.speed = vi->rate; + per_sfx->format.width = 2; // We always work with 16 bits samples + per_sfx->format.channels = vi->channels; + + sfx->fetcher_data = per_sfx; + sfx->fetcher = &ogg_fetcher; + sfx->flags |= SFXFLAG_STREAMED; + sfx->total_length = (int)((size_t)len / (per_sfx->format.channels * 2) * ((double)snd_renderbuffer->format.speed / per_sfx->format.speed)); + vc = qov_comment(&vf, -1); + OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, (double)snd_renderbuffer->format.speed / (double)per_sfx->format.speed, sfx->total_length, &peak, &gaindb); + per_sfx->total_length = sfx->total_length; + qov_clear (&vf); + } + else + { + char *buff; + ogg_int64_t done; + int bs; + long ret; + snd_buffer_t *sb; + snd_format_t ogg_format; + + if (developer_loading.integer >= 2) + Con_Printf ("Ogg sound file \"%s\" will be cached\n", filename); + + // Decode it + buff = (char *)Mem_Alloc (snd_mempool, (int)len); + done = 0; + bs = 0; + while ((ret = qov_read (&vf, &buff[done], (int)(len - done), mem_bigendian, 2, 1, &bs)) > 0) + done += ret; + + // Build the sound buffer + ogg_format.speed = vi->rate; + ogg_format.channels = vi->channels; + ogg_format.width = 2; // We always work with 16 bits samples + sb = Snd_CreateSndBuffer ((unsigned char *)buff, (size_t)done / (vi->channels * 2), &ogg_format, snd_renderbuffer->format.speed); + if (sb == NULL) + { + qov_clear (&vf); + Mem_Free (data); + Mem_Free (buff); + return false; + } + + sfx->fetcher = &wav_fetcher; + sfx->fetcher_data = sb; + + sfx->total_length = sb->nbframes; + sfx->memsize += sb->maxframes * sb->format.channels * sb->format.width + sizeof (*sb) - sizeof (sb->samples); + + sfx->flags &= ~SFXFLAG_STREAMED; + vc = qov_comment(&vf, -1); + OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, (double)snd_renderbuffer->format.speed / (double)sb->format.speed, sfx->total_length, &peak, &gaindb); + sb->nbframes = sfx->total_length; + qov_clear (&vf); + Mem_Free (data); + Mem_Free (buff); + } + + if(peak) + { + sfx->volume_mult = min(1.0f / peak, exp(gaindb * 0.05f * log(10.0f))); + sfx->volume_peak = peak; + if (developer_loading.integer >= 2) + Con_Printf ("Ogg sound file \"%s\" uses ReplayGain (gain %f, peak %f)\n", filename, sfx->volume_mult, sfx->volume_peak); + } + + return true; +} diff --git a/misc/source/darkplaces-src/snd_ogg.h b/misc/source/darkplaces-src/snd_ogg.h new file mode 100644 index 00000000..f8c5fe78 --- /dev/null +++ b/misc/source/darkplaces-src/snd_ogg.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2003 Mathieu Olivier + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef SND_OGG_H +#define SND_OGG_H + + +qboolean OGG_OpenLibrary (void); +void OGG_CloseLibrary (void); +qboolean OGG_LoadVorbisFile (const char *filename, sfx_t *sfx); + + +#endif diff --git a/misc/source/darkplaces-src/snd_oss.c b/misc/source/darkplaces-src/snd_oss.c new file mode 100644 index 00000000..9926b800 --- /dev/null +++ b/misc/source/darkplaces-src/snd_oss.c @@ -0,0 +1,344 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// OSS module, used by Linux and FreeBSD + +#include "quakedef.h" + +#include +#include +#include +#include +#include + +#include "snd_main.h" + + +#define NB_FRAGMENTS 4 + +static int audio_fd = -1; +static int old_osstime = 0; +static unsigned int osssoundtime; + + +/* +==================== +SndSys_Init + +Create "snd_renderbuffer" with the proper sound format if the call is successful +May return a suggested format if the requested format isn't available +==================== +*/ +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) +{ + int flags, ioctl_param, prev_value; + unsigned int fragmentsize; + + Con_DPrint("SndSys_Init: using the OSS module\n"); + + // Check the requested sound format + if (requested->width < 1 || requested->width > 2) + { + Con_Printf("SndSys_Init: invalid sound width (%hu)\n", + requested->width); + + if (suggested != NULL) + { + memcpy(suggested, requested, sizeof(*suggested)); + + if (requested->width < 1) + suggested->width = 1; + else + suggested->width = 2; + } + + return false; + } + + // Open /dev/dsp + audio_fd = open("/dev/dsp", O_WRONLY); + if (audio_fd < 0) + { + perror("/dev/dsp"); + Con_Print("SndSys_Init: could not open /dev/dsp\n"); + return false; + } + + // Use non-blocking IOs if possible + flags = fcntl(audio_fd, F_GETFL); + if (flags != -1) + { + if (fcntl(audio_fd, F_SETFL, flags | O_NONBLOCK) == -1) + Con_Print("SndSys_Init : fcntl(F_SETFL, O_NONBLOCK) failed!\n"); + } + else + Con_Print("SndSys_Init: fcntl(F_GETFL) failed!\n"); + + // Set the fragment size (up to "NB_FRAGMENTS" fragments of "fragmentsize" bytes) + fragmentsize = requested->speed * requested->channels * requested->width / 10; + fragmentsize = (unsigned int)ceilf((float)fragmentsize / (float)NB_FRAGMENTS); + fragmentsize = CeilPowerOf2(fragmentsize); + ioctl_param = (NB_FRAGMENTS << 16) | log2i(fragmentsize); + if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &ioctl_param) == -1) + { + Con_Print ("SndSys_Init: could not set the fragment size\n"); + SndSys_Shutdown (); + return false; + } + Con_Printf ("SndSys_Init: using %u fragments of %u bytes\n", + ioctl_param >> 16, 1 << (ioctl_param & 0xFFFF)); + + // Set the sound width + if (requested->width == 1) + ioctl_param = AFMT_U8; + else + ioctl_param = AFMT_S16_NE; + prev_value = ioctl_param; + if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &ioctl_param) == -1 || + ioctl_param != prev_value) + { + if (ioctl_param != prev_value && suggested != NULL) + { + memcpy(suggested, requested, sizeof(*suggested)); + + if (ioctl_param == AFMT_S16_NE) + suggested->width = 2; + else + suggested->width = 1; + } + + Con_Printf("SndSys_Init: could not set the sound width to %hu\n", + requested->width); + SndSys_Shutdown(); + return false; + } + + // Set the sound channels + ioctl_param = requested->channels; + if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &ioctl_param) == -1 || + ioctl_param != requested->channels) + { + if (ioctl_param != requested->channels && suggested != NULL) + { + memcpy(suggested, requested, sizeof(*suggested)); + suggested->channels = ioctl_param; + } + + Con_Printf("SndSys_Init: could not set the number of channels to %hu\n", + requested->channels); + SndSys_Shutdown(); + return false; + } + + // Set the sound speed + ioctl_param = requested->speed; + if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &ioctl_param) == -1 || + (unsigned int)ioctl_param != requested->speed) + { + if ((unsigned int)ioctl_param != requested->speed && suggested != NULL) + { + memcpy(suggested, requested, sizeof(*suggested)); + suggested->speed = ioctl_param; + } + + Con_Printf("SndSys_Init: could not set the sound speed to %u\n", + requested->speed); + SndSys_Shutdown(); + return false; + } + + // TOCHECK: I'm not sure which channel layout OSS uses for 5.1 and 7.1 + if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) + Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_ALSA); + + old_osstime = 0; + osssoundtime = 0; + snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL); + return true; +} + + +/* +==================== +SndSys_Shutdown + +Stop the sound card, delete "snd_renderbuffer" and free its other resources +==================== +*/ +void SndSys_Shutdown (void) +{ + // Stop the sound and close the device + if (audio_fd >= 0) + { + ioctl(audio_fd, SNDCTL_DSP_RESET, NULL); + close(audio_fd); + audio_fd = -1; + } + + if (snd_renderbuffer != NULL) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } +} + + +/* +==================== +SndSys_Write +==================== +*/ +static int SndSys_Write (const unsigned char* buffer, unsigned int nb_bytes) +{ + int written; + unsigned int factor; + + written = write (audio_fd, buffer, nb_bytes); + if (written < 0) + { + if (errno != EAGAIN) + Con_Printf ("SndSys_Write: audio write returned %d! (errno= %d)\n", + written, errno); + return written; + } + + factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + if (written % factor != 0) + Sys_Error ("SndSys_Write: nb of bytes written (%d) isn't aligned to a frame sample!\n", + written); + + snd_renderbuffer->startframe += written / factor; + + if ((unsigned int)written < nb_bytes) + { + Con_DPrintf("SndSys_Submit: audio can't keep up! (%u < %u)\n", + written, nb_bytes); + } + + return written; +} + + +/* +==================== +SndSys_Submit + +Submit the contents of "snd_renderbuffer" to the sound card +==================== +*/ +void SndSys_Submit (void) +{ + unsigned int startoffset, factor, limit, nbframes; + int written; + + if (audio_fd < 0 || + snd_renderbuffer->startframe == snd_renderbuffer->endframe) + return; + + startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; + factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + limit = snd_renderbuffer->maxframes - startoffset; + nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; + if (nbframes > limit) + { + written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit * factor); + if (written < 0 || (unsigned int)written < limit * factor) + return; + + nbframes -= limit; + startoffset = 0; + } + + SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes * factor); +} + + +/* +==================== +SndSys_GetSoundTime + +Returns the number of sample frames consumed since the sound started +==================== +*/ +unsigned int SndSys_GetSoundTime (void) +{ + struct count_info count; + int new_osstime; + unsigned int timediff; + + if (ioctl (audio_fd, SNDCTL_DSP_GETOPTR, &count) == -1) + { + Con_Print ("SndSys_GetSoundTimeDiff: can't ioctl (SNDCTL_DSP_GETOPTR)\n"); + return 0; + } + new_osstime = count.bytes / (snd_renderbuffer->format.width * snd_renderbuffer->format.channels); + + if (new_osstime >= old_osstime) + timediff = new_osstime - old_osstime; + else + { + Con_Print ("SndSys_GetSoundTime: osstime wrapped\n"); + timediff = 0; + } + + old_osstime = new_osstime; + osssoundtime += timediff; + return osssoundtime; +} + + +/* +==================== +SndSys_LockRenderBuffer + +Get the exclusive lock on "snd_renderbuffer" +==================== +*/ +qboolean SndSys_LockRenderBuffer (void) +{ + // Nothing to do + return true; +} + + +/* +==================== +SndSys_UnlockRenderBuffer + +Release the exclusive lock on "snd_renderbuffer" +==================== +*/ +void SndSys_UnlockRenderBuffer (void) +{ + // Nothing to do +} + +/* +==================== +SndSys_SendKeyEvents + +Send keyboard events originating from the sound system (e.g. MIDI) +==================== +*/ +void SndSys_SendKeyEvents(void) +{ + // not supported +} diff --git a/misc/source/darkplaces-src/snd_sdl.c b/misc/source/darkplaces-src/snd_sdl.c new file mode 100644 index 00000000..c88b28c8 --- /dev/null +++ b/misc/source/darkplaces-src/snd_sdl.c @@ -0,0 +1,254 @@ +/* +Copyright (C) 2004 Andreas Kirsch + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include "quakedef.h" + +#include +#include + +#include "snd_main.h" + + +static unsigned int sdlaudiotime = 0; + + +// Note: SDL calls SDL_LockAudio() right before this function, so no need to lock the audio data here +static void Buffer_Callback (void *userdata, Uint8 *stream, int len) +{ + unsigned int factor, RequestedFrames, MaxFrames, FrameCount; + unsigned int StartOffset, EndOffset; + + factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width; + if ((unsigned int)len % factor != 0) + Sys_Error("SDL sound: invalid buffer length passed to Buffer_Callback (%d bytes)\n", len); + + RequestedFrames = (unsigned int)len / factor; + + if (SndSys_LockRenderBuffer()) + { + if (snd_usethreadedmixing) + { + S_MixToBuffer(stream, RequestedFrames); + if (snd_blocked) + memset(stream, snd_renderbuffer->format.width == 1 ? 0x80 : 0, len); + SndSys_UnlockRenderBuffer(); + return; + } + + // Transfert up to a chunk of samples from snd_renderbuffer to stream + MaxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe; + if (MaxFrames > RequestedFrames) + FrameCount = RequestedFrames; + else + FrameCount = MaxFrames; + StartOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; + EndOffset = (snd_renderbuffer->startframe + FrameCount) % snd_renderbuffer->maxframes; + if (StartOffset > EndOffset) // if the buffer wraps + { + unsigned int PartialLength1, PartialLength2; + + PartialLength1 = (snd_renderbuffer->maxframes - StartOffset) * factor; + memcpy(stream, &snd_renderbuffer->ring[StartOffset * factor], PartialLength1); + + PartialLength2 = FrameCount * factor - PartialLength1; + memcpy(&stream[PartialLength1], &snd_renderbuffer->ring[0], PartialLength2); + } + else + memcpy(stream, &snd_renderbuffer->ring[StartOffset * factor], FrameCount * factor); + + snd_renderbuffer->startframe += FrameCount; + + if (FrameCount < RequestedFrames && developer_insane.integer && vid_activewindow) + Con_DPrintf("SDL sound: %u sample frames missing\n", RequestedFrames - FrameCount); + + sdlaudiotime += RequestedFrames; + + SndSys_UnlockRenderBuffer(); + } +} + + +/* +==================== +SndSys_Init + +Create "snd_renderbuffer" with the proper sound format if the call is successful +May return a suggested format if the requested format isn't available +==================== +*/ +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) +{ + unsigned int buffersize; + SDL_AudioSpec wantspec; + SDL_AudioSpec obtainspec; + + snd_threaded = false; + + Con_DPrint ("SndSys_Init: using the SDL module\n"); + + // Init the SDL Audio subsystem + if( SDL_InitSubSystem( SDL_INIT_AUDIO ) ) { + Con_Print( "Initializing the SDL Audio subsystem failed!\n" ); + return false; + } + + buffersize = (unsigned int)ceil((double)requested->speed / 25.0); // 2048 bytes on 24kHz to 48kHz + + // Init the SDL Audio subsystem + wantspec.callback = Buffer_Callback; + wantspec.userdata = NULL; + wantspec.freq = requested->speed; + wantspec.format = ((requested->width == 1) ? AUDIO_U8 : AUDIO_S16SYS); + wantspec.channels = requested->channels; + wantspec.samples = CeilPowerOf2(buffersize); // needs to be a power of 2 on some platforms. + + Con_Printf("Wanted audio Specification:\n" + "\tChannels : %i\n" + "\tFormat : 0x%X\n" + "\tFrequency : %i\n" + "\tSamples : %i\n", + wantspec.channels, wantspec.format, wantspec.freq, wantspec.samples); + + if( SDL_OpenAudio( &wantspec, &obtainspec ) ) + { + Con_Printf( "Failed to open the audio device! (%s)\n", SDL_GetError() ); + return false; + } + + Con_Printf("Obtained audio specification:\n" + "\tChannels : %i\n" + "\tFormat : 0x%X\n" + "\tFrequency : %i\n" + "\tSamples : %i\n", + obtainspec.channels, obtainspec.format, obtainspec.freq, obtainspec.samples); + + // If we haven't obtained what we wanted + if (wantspec.freq != obtainspec.freq || + wantspec.format != obtainspec.format || + wantspec.channels != obtainspec.channels) + { + SDL_CloseAudio(); + + // Pass the obtained format as a suggested format + if (suggested != NULL) + { + suggested->speed = obtainspec.freq; + // FIXME: check the format more carefully. There are plenty of unsupported cases + suggested->width = ((obtainspec.format == AUDIO_U8) ? 1 : 2); + suggested->channels = obtainspec.channels; + } + + return false; + } + + snd_threaded = true; + + snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL); + if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) + Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD); + + sdlaudiotime = 0; + SDL_PauseAudio( false ); + + return true; +} + + +/* +==================== +SndSys_Shutdown + +Stop the sound card, delete "snd_renderbuffer" and free its other resources +==================== +*/ +void SndSys_Shutdown(void) +{ + SDL_CloseAudio(); + + if (snd_renderbuffer != NULL) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } +} + + +/* +==================== +SndSys_Submit + +Submit the contents of "snd_renderbuffer" to the sound card +==================== +*/ +void SndSys_Submit (void) +{ + // Nothing to do here (this sound module is callback-based) +} + + +/* +==================== +SndSys_GetSoundTime + +Returns the number of sample frames consumed since the sound started +==================== +*/ +unsigned int SndSys_GetSoundTime (void) +{ + return sdlaudiotime; +} + + +/* +==================== +SndSys_LockRenderBuffer + +Get the exclusive lock on "snd_renderbuffer" +==================== +*/ +qboolean SndSys_LockRenderBuffer (void) +{ + SDL_LockAudio(); + return true; +} + + +/* +==================== +SndSys_UnlockRenderBuffer + +Release the exclusive lock on "snd_renderbuffer" +==================== +*/ +void SndSys_UnlockRenderBuffer (void) +{ + SDL_UnlockAudio(); +} + +/* +==================== +SndSys_SendKeyEvents + +Send keyboard events originating from the sound system (e.g. MIDI) +==================== +*/ +void SndSys_SendKeyEvents(void) +{ + // not supported +} diff --git a/misc/source/darkplaces-src/snd_wav.c b/misc/source/darkplaces-src/snd_wav.c new file mode 100644 index 00000000..b55c3665 --- /dev/null +++ b/misc/source/darkplaces-src/snd_wav.c @@ -0,0 +1,343 @@ +/* + Copyright (C) 1996-1997 Id Software, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#include "quakedef.h" +#include "snd_main.h" +#include "snd_wav.h" + + +typedef struct wavinfo_s +{ + int rate; + int width; + int channels; + int loopstart; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + + +static unsigned char *data_p; +static unsigned char *iff_end; +static unsigned char *last_chunk; +static unsigned char *iff_data; +static int iff_chunk_len; + + +static short GetLittleShort(void) +{ + short val; + + val = BuffLittleShort (data_p); + data_p += 2; + + return val; +} + +static int GetLittleLong(void) +{ + int val = 0; + + val = BuffLittleLong (data_p); + data_p += 4; + + return val; +} + +static void FindNextChunk(const char *name) +{ + while (1) + { + data_p=last_chunk; + + if (data_p >= iff_end) + { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0) + { + data_p = NULL; + return; + } + if (data_p + iff_chunk_len > iff_end) + { + // truncated chunk! + data_p = NULL; + return; + } + data_p -= 8; + last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); + if (!strncmp((const char *)data_p, name, 4)) + return; + } +} + +static void FindChunk(const char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + + +/* +static void DumpChunks(void) +{ + char str[5]; + + str[4] = 0; + data_p=iff_data; + do + { + memcpy (str, data_p, 4); + data_p += 4; + iff_chunk_len = GetLittleLong(); + Con_Printf("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); + data_p += (iff_chunk_len + 1) & ~1; + } while (data_p < iff_end); +} +*/ + + +/* +============ +GetWavinfo +============ +*/ +static wavinfo_t GetWavinfo (char *name, unsigned char *wav, int wavlength) +{ + wavinfo_t info; + int i; + int format; + int samples; + + memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + + // find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp((const char *)data_p+8, "WAVE", 4))) + { + Con_Print("Missing RIFF/WAVE chunks\n"); + return info; + } + + // get "fmt " chunk + iff_data = data_p + 12; + //DumpChunks (); + + FindChunk("fmt "); + if (!data_p) + { + Con_Print("Missing fmt chunk\n"); + return info; + } + data_p += 8; + format = GetLittleShort(); + if (format != 1) + { + Con_Print("Microsoft PCM format only\n"); + return info; + } + + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4+2; + info.width = GetLittleShort() / 8; + + // get cue chunk + FindChunk("cue "); + if (data_p) + { + data_p += 32; + info.loopstart = GetLittleLong(); + + // if the next chunk is a LIST chunk, look for a cue length marker + FindNextChunk ("LIST"); + if (data_p) + { + if (!strncmp ((const char *)data_p + 28, "mark", 4)) + { // this is not a proper parse, but it works with cooledit... + data_p += 24; + i = GetLittleLong (); // samples in loop + info.samples = info.loopstart + i; + } + } + } + else + info.loopstart = -1; + + // find data chunk + FindChunk("data"); + if (!data_p) + { + Con_Print("Missing data chunk\n"); + return info; + } + + data_p += 4; + samples = GetLittleLong () / info.width / info.channels; + + if (info.samples) + { + if (samples < info.samples) + { + Con_Printf ("Sound %s has a bad loop length\n", name); + info.samples = samples; + } + } + else + info.samples = samples; + + info.dataofs = data_p - wav; + + return info; +} + + +/* +==================== +WAV_FetchSound +==================== +*/ +static const snd_buffer_t* WAV_FetchSound (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes) +{ + *start = 0; + return (snd_buffer_t *)sfxfetcher; +} + +/* +==================== +WAV_FreeSfx +==================== +*/ +static void WAV_FreeSfx (void *sfxfetcherdata) +{ + snd_buffer_t* sb = (snd_buffer_t *)sfxfetcherdata; + // Free the sound buffer + Mem_Free(sb); +} + +/* +==================== +WAV_GetFormat +==================== +*/ +static const snd_format_t* WAV_GetFormat (sfx_t* sfx) +{ + snd_buffer_t* sb = (snd_buffer_t *)sfx->fetcher_data; + return &sb->format; +} + +const snd_fetcher_t wav_fetcher = { WAV_FetchSound, NULL, WAV_FreeSfx, WAV_GetFormat }; + + +/* +============== +S_LoadWavFile +============== +*/ +qboolean S_LoadWavFile (const char *filename, sfx_t *sfx) +{ + fs_offset_t filesize; + unsigned char *data; + wavinfo_t info; + snd_format_t wav_format; + snd_buffer_t* sb; + + // Already loaded? + if (sfx->fetcher != NULL) + return true; + + // Load the file + data = FS_LoadFile(filename, snd_mempool, false, &filesize); + if (!data) + return false; + + // Don't try to load it if it's not a WAV file + if (memcmp (data, "RIFF", 4) || memcmp (data + 8, "WAVE", 4)) + { + Mem_Free(data); + return false; + } + + if (developer_loading.integer >= 2) + Con_Printf ("Loading WAV file \"%s\"\n", filename); + + info = GetWavinfo (sfx->name, data, (int)filesize); + if (info.channels < 1 || info.channels > 2) // Stereo sounds are allowed (intended for music) + { + Con_Printf("%s has an unsupported number of channels (%i)\n",sfx->name, info.channels); + Mem_Free(data); + return false; + } + //if (info.channels == 2) + // Log_Printf("stereosounds.log", "%s\n", sfx->name); + + // We must convert the WAV data from little endian + // to the machine endianess before resampling it + if (info.width == 2 && mem_bigendian) + { + unsigned int len, i; + short* ptr; + + len = info.samples * info.channels; + ptr = (short*)(data + info.dataofs); + for (i = 0; i < len; i++) + ptr[i] = LittleShort (ptr[i]); + } + + wav_format.speed = info.rate; + wav_format.width = info.width; + wav_format.channels = info.channels; + sb = Snd_CreateSndBuffer (data + info.dataofs, info.samples, &wav_format, snd_renderbuffer->format.speed); + if (sb == NULL) + { + Mem_Free(data); + return false; + } + sfx->fetcher = &wav_fetcher; + sfx->fetcher_data = sb; + + sfx->total_length = sb->nbframes; + sfx->memsize += sb->maxframes * sb->format.channels * sb->format.width + sizeof (*sb) - sizeof (sb->samples); + + if (info.loopstart < 0) + sfx->loopstart = sfx->total_length; + else + sfx->loopstart = (unsigned int) ((double)info.loopstart * (double)sb->format.speed / (double)info.rate); + sfx->loopstart = min(sfx->loopstart, sfx->total_length); + sfx->flags &= ~SFXFLAG_STREAMED; + + Mem_Free (data); + return true; +} diff --git a/misc/source/darkplaces-src/snd_wav.h b/misc/source/darkplaces-src/snd_wav.h new file mode 100644 index 00000000..89c7ee51 --- /dev/null +++ b/misc/source/darkplaces-src/snd_wav.h @@ -0,0 +1,34 @@ +/* + Copyright (C) 1996-1997 Id Software, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#ifndef SND_WAV_H +#define SND_WAV_H + + +extern const snd_fetcher_t wav_fetcher; + +qboolean S_LoadWavFile (const char *filename, sfx_t *sfx); + + +#endif diff --git a/misc/source/darkplaces-src/snd_win.c b/misc/source/darkplaces-src/snd_win.c new file mode 100644 index 00000000..08012248 --- /dev/null +++ b/misc/source/darkplaces-src/snd_win.c @@ -0,0 +1,897 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifdef SUPPORTDIRECTX +#ifndef DIRECTSOUND_VERSION +# define DIRECTSOUND_VERSION 0x0500 /* Version 5.0 */ +#endif +#endif +#include +#include +#ifdef SUPPORTDIRECTX +#include +#endif + +#include "qtypes.h" +#include "quakedef.h" +#include "snd_main.h" + +// ============================================================================== + +#ifndef _WAVEFORMATEXTENSIBLE_ +#define _WAVEFORMATEXTENSIBLE_ +typedef struct +{ + WAVEFORMATEX Format; + union + { + WORD wValidBitsPerSample; // bits of precision + WORD wSamplesPerBlock; // valid if wBitsPerSample==0 + WORD wReserved; // If neither applies, set to zero + } Samples; + DWORD dwChannelMask; // which channels are present in stream + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE; +#endif + +#if !defined(WAVE_FORMAT_EXTENSIBLE) +# define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif + +// Some speaker positions +#ifndef SPEAKER_FRONT_LEFT +# define SPEAKER_FRONT_LEFT 0x1 +# define SPEAKER_FRONT_RIGHT 0x2 +# define SPEAKER_FRONT_CENTER 0x4 +# define SPEAKER_LOW_FREQUENCY 0x8 +# define SPEAKER_BACK_LEFT 0x10 +# define SPEAKER_BACK_RIGHT 0x20 +# define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +# define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +// ... we never use the other values +#endif + +// KSDATAFORMAT_SUBTYPE_PCM = GUID "00000001-0000-0010-8000-00aa00389b71" +static const GUID MY_KSDATAFORMAT_SUBTYPE_PCM = +{ + 0x00000001, + 0x0000, + 0x0010, + { + 0x80, 0x00, + 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + } +}; + + +// ============================================================================== + +extern HWND mainwindow; +static cvar_t snd_wav_partitionsize = {CVAR_SAVE, "snd_wav_partitionsize", "1024", "controls sound delay in samples, values too low will cause crackling, too high will cause delayed sounds"}; +static qboolean sndsys_registeredcvars = false; + +#ifdef SUPPORTDIRECTX +HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter); +#endif + +// Wave output: queue of this many sound buffers to play, reused cyclically +#define WAV_BUFFERS 16 +#define WAV_MASK (WAV_BUFFERS - 1) +static unsigned int wav_buffer_size; + +// DirectSound output: 64KB in 1 buffer +//#define SECONDARY_BUFFER_SIZE(fmt_ptr) ((fmt_ptr)->width * (fmt_ptr)->channels * (fmt_ptr)->speed / 2) +// LordHavoc: changed this to be a multiple of 32768 +#define SECONDARY_BUFFER_SIZE(fmt_ptr) ((fmt_ptr)->channels * 32768) + +typedef enum sndinitstat_e {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat; + +#ifdef SUPPORTDIRECTX +static qboolean dsound_init; +static unsigned int dsound_time; +static qboolean primary_format_set; +#endif + +static qboolean wav_init; + +static int snd_sent, snd_completed; + +static int prev_painted; +static unsigned int paintpot; + + + +/* + * Global variables. Must be visible to window-procedure function + * so it can unlock and free the data block after it has been played. + */ + +HANDLE hData; +HPSTR lpData, lpData2; + +HGLOBAL hWaveHdr; +LPWAVEHDR lpWaveHdr; + +HWAVEOUT hWaveOut; + +WAVEOUTCAPS wavecaps; + +DWORD gSndBufSize; + +DWORD dwStartTime; + +#ifdef SUPPORTDIRECTX +LPDIRECTSOUND pDS; +LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; + +HINSTANCE hInstDS; +#endif + +qboolean SNDDMA_InitWav (void); +#ifdef SUPPORTDIRECTX +sndinitstat SNDDMA_InitDirect (void); +#endif + + +/* +================== +SndSys_BuildWaveFormat +================== +*/ +static qboolean SndSys_BuildWaveFormat (const snd_format_t* requested, WAVEFORMATEXTENSIBLE* fmt_ptr) +{ + WAVEFORMATEX* pfmtex; + + memset (fmt_ptr, 0, sizeof(*fmt_ptr)); + + pfmtex = &fmt_ptr->Format; + pfmtex->nChannels = requested->channels; + pfmtex->wBitsPerSample = requested->width * 8; + pfmtex->nSamplesPerSec = requested->speed; + pfmtex->nBlockAlign = pfmtex->nChannels * pfmtex->wBitsPerSample / 8; + pfmtex->nAvgBytesPerSec = pfmtex->nSamplesPerSec * pfmtex->nBlockAlign; + + // LordHavoc: disabled this WAVE_FORMAT_EXTENSIBLE support because it does not seem to be working +#if 0 + if (requested->channels <= 2) + { +#endif + pfmtex->wFormatTag = WAVE_FORMAT_PCM; + pfmtex->cbSize = 0; +#if 0 + } + else + { + pfmtex->wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pfmtex->cbSize = sizeof(*fmt_ptr) - sizeof(fmt_ptr->Format); + fmt_ptr->Samples.wValidBitsPerSample = fmt_ptr->Format.wBitsPerSample; + fmt_ptr->SubFormat = MY_KSDATAFORMAT_SUBTYPE_PCM; + + // Build the channel mask + fmt_ptr->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + switch (requested->channels) + { + case 8: + fmt_ptr->dwChannelMask |= SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER; + // no break + case 6: + fmt_ptr->dwChannelMask |= SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY; + // no break + case 4: + fmt_ptr->dwChannelMask |= SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; + break; + + default: + Con_Printf("SndSys_BuildWaveFormat: invalid number of channels (%hu)\n", requested->channels); + return false; + } + } +#endif + + return true; +} + + +#ifdef SUPPORTDIRECTX +/* +================== +SndSys_InitDirectSound + +DirectSound 5 support +================== +*/ +static sndinitstat SndSys_InitDirectSound (const snd_format_t* requested) +{ + DSBUFFERDESC dsbuf; + DSBCAPS dsbcaps; + DWORD dwSize; + DSCAPS dscaps; + WAVEFORMATEXTENSIBLE format, pformat; + HRESULT hresult; + int reps; + + if (! SndSys_BuildWaveFormat(requested, &format)) + return SIS_FAILURE; + + if (!hInstDS) + { + hInstDS = LoadLibrary("dsound.dll"); + + if (hInstDS == NULL) + { + Con_Print("Couldn't load dsound.dll\n"); + return SIS_FAILURE; + } + + pDirectSoundCreate = (HRESULT (__stdcall *)(GUID *, LPDIRECTSOUND *,IUnknown *))GetProcAddress(hInstDS,"DirectSoundCreate"); + + if (!pDirectSoundCreate) + { + Con_Print("Couldn't get DS proc addr\n"); + return SIS_FAILURE; + } + } + + while ((hresult = pDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK) + { + if (hresult != DSERR_ALLOCATED) + { + Con_Print("DirectSound create failed\n"); + return SIS_FAILURE; + } + + if (MessageBox (NULL, + "The sound hardware is in use by another app.\n\n" + "Select Retry to try to start sound again or Cancel to run Quake with no sound.", + "Sound not available", + MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) + { + Con_Print("DirectSoundCreate failure\n hardware already in use\n"); + return SIS_NOTAVAIL; + } + } + + dscaps.dwSize = sizeof(dscaps); + + if (DS_OK != IDirectSound_GetCaps (pDS, &dscaps)) + { + Con_Print("Couldn't get DS caps\n"); + } + + if (dscaps.dwFlags & DSCAPS_EMULDRIVER) + { + Con_Print("No DirectSound driver installed\n"); + SndSys_Shutdown (); + return SIS_FAILURE; + } + + if (DS_OK != IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE)) + { + Con_Print("Set coop level failed\n"); + SndSys_Shutdown (); + return SIS_FAILURE; + } + + // get access to the primary buffer, if possible, so we can set the + // sound hardware format + memset (&dsbuf, 0, sizeof(dsbuf)); + dsbuf.dwSize = sizeof(DSBUFFERDESC); + dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbuf.dwBufferBytes = 0; + dsbuf.lpwfxFormat = NULL; + + memset(&dsbcaps, 0, sizeof(dsbcaps)); + dsbcaps.dwSize = sizeof(dsbcaps); + primary_format_set = false; + +// COMMANDLINEOPTION: Windows DirectSound: -snoforceformat uses the format that DirectSound returns, rather than forcing it + if (!COM_CheckParm ("-snoforceformat")) + { + if (DS_OK == IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL)) + { + pformat = format; + + if (DS_OK != IDirectSoundBuffer_SetFormat (pDSPBuf, (WAVEFORMATEX*)&pformat)) + { + Con_Print("Set primary sound buffer format: no\n"); + } + else + { + Con_Print("Set primary sound buffer format: yes\n"); + + primary_format_set = true; + } + } + } + +// COMMANDLINEOPTION: Windows DirectSound: -primarysound locks the sound hardware for exclusive use + if (!primary_format_set || !COM_CheckParm ("-primarysound")) + { + HRESULT result; + + // create the secondary buffer we'll actually work with + memset (&dsbuf, 0, sizeof(dsbuf)); + dsbuf.dwSize = sizeof(DSBUFFERDESC); + dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE; + dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE(requested); + dsbuf.lpwfxFormat = (WAVEFORMATEX*)&format; + + memset(&dsbcaps, 0, sizeof(dsbcaps)); + dsbcaps.dwSize = sizeof(dsbcaps); + + result = IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL); + if (result != DS_OK || + requested->channels != format.Format.nChannels || + requested->width != format.Format.wBitsPerSample / 8 || + requested->speed != format.Format.nSamplesPerSec) + { + Con_Printf("DS:CreateSoundBuffer Failed (%d): channels=%u, width=%u, speed=%u\n", + (int)result, (unsigned)format.Format.nChannels, (unsigned)format.Format.wBitsPerSample / 8, (unsigned)format.Format.nSamplesPerSec); + SndSys_Shutdown (); + return SIS_FAILURE; + } + + if (DS_OK != IDirectSoundBuffer_GetCaps (pDSBuf, &dsbcaps)) + { + Con_Print("DS:GetCaps failed\n"); + SndSys_Shutdown (); + return SIS_FAILURE; + } + + Con_Print("Using secondary sound buffer\n"); + } + else + { + if (DS_OK != IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY)) + { + Con_Print("Set coop level failed\n"); + SndSys_Shutdown (); + return SIS_FAILURE; + } + + if (DS_OK != IDirectSoundBuffer_GetCaps (pDSPBuf, &dsbcaps)) + { + Con_Print("DS:GetCaps failed\n"); + return SIS_FAILURE; + } + + pDSBuf = pDSPBuf; + Con_Print("Using primary sound buffer\n"); + } + + // Make sure mixer is active + IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); + + Con_Printf(" %d channel(s)\n" + " %d bits/sample\n" + " %d samples/sec\n", + requested->channels, requested->width * 8, requested->speed); + + gSndBufSize = dsbcaps.dwBufferBytes; + + // initialize the buffer + reps = 0; + + while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK) + { + if (hresult != DSERR_BUFFERLOST) + { + Con_Print("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n"); + SndSys_Shutdown (); + return SIS_FAILURE; + } + + if (++reps > 10000) + { + Con_Print("SNDDMA_InitDirect: DS: couldn't restore buffer\n"); + SndSys_Shutdown (); + return SIS_FAILURE; + } + + } + + memset(lpData, 0, dwSize); + IDirectSoundBuffer_Unlock(pDSBuf, lpData, dwSize, NULL, 0); + + IDirectSoundBuffer_Stop(pDSBuf); + IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); + + dwStartTime = 0; + dsound_time = 0; + snd_renderbuffer = Snd_CreateRingBuffer(requested, gSndBufSize / (requested->width * requested->channels), lpData); + + dsound_init = true; + + return SIS_SUCCESS; +} +#endif + + +/* +================== +SndSys_InitMmsystem + +Crappy windows multimedia base +================== +*/ +static qboolean SndSys_InitMmsystem (const snd_format_t* requested) +{ + WAVEFORMATEXTENSIBLE format; + int i; + HRESULT hr; + + if (! SndSys_BuildWaveFormat(requested, &format)) + return false; + + // Open a waveform device for output using window callback + while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, (WAVEFORMATEX*)&format, + 0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR) + { + if (hr != MMSYSERR_ALLOCATED) + { + Con_Print("waveOutOpen failed\n"); + return false; + } + + if (MessageBox (NULL, + "The sound hardware is in use by another app.\n\n" + "Select Retry to try to start sound again or Cancel to run Quake with no sound.", + "Sound not available", + MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) + { + Con_Print("waveOutOpen failure;\n hardware already in use\n"); + return false; + } + } + + wav_buffer_size = bound(128, snd_wav_partitionsize.integer, 8192) * requested->channels * requested->width; + + /* + * Allocate and lock memory for the waveform data. The memory + * for waveform data must be globally allocated with + * GMEM_MOVEABLE and GMEM_SHARE flags. + */ + gSndBufSize = WAV_BUFFERS * wav_buffer_size; + hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize); + if (!hData) + { + Con_Print("Sound: Out of memory.\n"); + SndSys_Shutdown (); + return false; + } + lpData = (HPSTR)GlobalLock(hData); + if (!lpData) + { + Con_Print("Sound: Failed to lock.\n"); + SndSys_Shutdown (); + return false; + } + memset (lpData, 0, gSndBufSize); + + /* + * Allocate and lock memory for the header. This memory must + * also be globally allocated with GMEM_MOVEABLE and + * GMEM_SHARE flags. + */ + hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS); + + if (hWaveHdr == NULL) + { + Con_Print("Sound: Failed to Alloc header.\n"); + SndSys_Shutdown (); + return false; + } + + lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr); + + if (lpWaveHdr == NULL) + { + Con_Print("Sound: Failed to lock header.\n"); + SndSys_Shutdown (); + return false; + } + + memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS); + + // After allocation, set up and prepare headers + for (i=0 ; iwidth * requested->channels), lpData); + + prev_painted = 0; + paintpot = 0; + + snd_sent = 0; + snd_completed = 0; + + wav_init = true; + + return true; +} + + +/* +==================== +SndSys_Init + +Create "snd_renderbuffer" with the proper sound format if the call is successful +May return a suggested format if the requested format isn't available +==================== +*/ +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) +{ +#ifdef SUPPORTDIRECTX + qboolean wavonly; +#endif + sndinitstat stat; + + if (!sndsys_registeredcvars) + { + sndsys_registeredcvars = true; + Cvar_RegisterVariable(&snd_wav_partitionsize); + } + + Con_Print ("SndSys_Init: using the Win32 module\n"); + +#ifdef SUPPORTDIRECTX +// COMMANDLINEOPTION: Windows Sound: -wavonly uses wave sound instead of DirectSound + wavonly = (COM_CheckParm ("-wavonly") != 0); + dsound_init = false; +#endif + wav_init = false; + + stat = SIS_FAILURE; // assume DirectSound won't initialize + +#ifdef SUPPORTDIRECTX + // Init DirectSound + if (!wavonly) + { + stat = SndSys_InitDirectSound (requested); + + if (stat == SIS_SUCCESS) + Con_Print("DirectSound initialized\n"); + else + Con_Print("DirectSound failed to init\n"); + } +#endif + + // if DirectSound didn't succeed in initializing, try to initialize + // waveOut sound, unless DirectSound failed because the hardware is + // already allocated (in which case the user has already chosen not + // to have sound) +#ifdef SUPPORTDIRECTX + if (!dsound_init && (stat != SIS_NOTAVAIL)) +#endif + { + if (SndSys_InitMmsystem (requested)) + Con_Print("Wave sound (MMSYSTEM) initialized\n"); + else + Con_Print("Wave sound failed to init\n"); + } + +#ifdef SUPPORTDIRECTX + return (dsound_init || wav_init); +#else + return wav_init; +#endif +} + + +/* +==================== +SndSys_Shutdown + +Stop the sound card, delete "snd_renderbuffer" and free its other resources +==================== +*/ +void SndSys_Shutdown (void) +{ +#ifdef SUPPORTDIRECTX + if (pDSBuf) + { + IDirectSoundBuffer_Stop(pDSBuf); + IDirectSoundBuffer_Release(pDSBuf); + } + + // only release primary buffer if it's not also the mixing buffer we just released + if (pDSPBuf && (pDSBuf != pDSPBuf)) + { + IDirectSoundBuffer_Release(pDSPBuf); + } + + if (pDS) + { + IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL); + IDirectSound_Release(pDS); + } +#endif + + if (hWaveOut) + { + waveOutReset (hWaveOut); + + if (lpWaveHdr) + { + unsigned int i; + + for (i=0 ; i< WAV_BUFFERS ; i++) + waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)); + } + + waveOutClose (hWaveOut); + + if (hWaveHdr) + { + GlobalUnlock(hWaveHdr); + GlobalFree(hWaveHdr); + } + + if (hData) + { + GlobalUnlock(hData); + GlobalFree(hData); + } + } + + if (snd_renderbuffer != NULL) + { + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } + +#ifdef SUPPORTDIRECTX + pDS = NULL; + pDSBuf = NULL; + pDSPBuf = NULL; + dsound_init = false; +#endif + hWaveOut = 0; + hData = 0; + hWaveHdr = 0; + lpData = NULL; + lpWaveHdr = NULL; + wav_init = false; +} + + +/* +==================== +SndSys_Submit + +Submit the contents of "snd_renderbuffer" to the sound card +==================== +*/ +void SndSys_Submit (void) +{ + LPWAVEHDR h; + int wResult; + + // DirectSound doesn't need this + if (!wav_init) + return; + + paintpot += (snd_renderbuffer->endframe - prev_painted) * snd_renderbuffer->format.channels * snd_renderbuffer->format.width; + if (paintpot > WAV_BUFFERS * wav_buffer_size) + paintpot = WAV_BUFFERS * wav_buffer_size; + prev_painted = snd_renderbuffer->endframe; + + // submit new sound blocks + while (paintpot > wav_buffer_size) + { + h = lpWaveHdr + (snd_sent & WAV_MASK); + + /* + * Now the data block can be sent to the output device. The + * waveOutWrite function returns immediately and waveform + * data is sent to the output device in the background. + */ + wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR)); + if (wResult == MMSYSERR_NOERROR) + snd_sent++; + else if (wResult == WAVERR_STILLPLAYING) + { + if(developer_insane.integer) + Con_DPrint("waveOutWrite failed (too much sound data)\n"); + //h->dwFlags |= WHDR_DONE; + //snd_sent++; + } + else + { + Con_Printf("waveOutWrite failed, error code %d\n", (int) wResult); + SndSys_Shutdown (); + return; + } + + paintpot -= wav_buffer_size; + } + +} + + +/* +==================== +SndSys_GetSoundTime + +Returns the number of sample frames consumed since the sound started +==================== +*/ +unsigned int SndSys_GetSoundTime (void) +{ + unsigned int factor; + + factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + +#ifdef SUPPORTDIRECTX + if (dsound_init) + { + DWORD dwTime; + unsigned int diff; + + IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &dwTime, NULL); + diff = (unsigned int)(dwTime - dwStartTime) % (unsigned int)gSndBufSize; + dwStartTime = dwTime; + + dsound_time += diff / factor; + return dsound_time; + } +#endif + + if (wav_init) + { + // Find which sound blocks have completed + for (;;) + { + if (snd_completed == snd_sent) + { + // Con_DPrint("Sound overrun\n"); + break; + } + + if (!(lpWaveHdr[snd_completed & WAV_MASK].dwFlags & WHDR_DONE)) + break; + + snd_completed++; // this buffer has been played + } + + return (snd_completed * wav_buffer_size) / factor; + + /* + * S_PaintAndSubmit: WARNING: newsoundtime (soundtime (275 < 134217707) + * apparently this sound time wraps quite early? + { + MMRESULT res; + MMTIME mmtime; + + mmtime.wType = TIME_SAMPLES; + res = waveOutGetPosition(hWaveOut, &mmtime, sizeof(mmtime)); + if(res == MMSYSERR_NOERROR) + return mmtime.u.sample; + } + */ + } + + return 0; +} + + +#ifdef SUPPORTDIRECTX +static DWORD dsound_dwSize; +static DWORD dsound_dwSize2; +static DWORD *dsound_pbuf; +static DWORD *dsound_pbuf2; +#endif + +/* +==================== +SndSys_LockRenderBuffer + +Get the exclusive lock on "snd_renderbuffer" +==================== +*/ +qboolean SndSys_LockRenderBuffer (void) +{ +#ifdef SUPPORTDIRECTX + int reps; + HRESULT hresult; + DWORD dwStatus; + + if (pDSBuf) + { + // if the buffer was lost or stopped, restore it and/or restart it + if (IDirectSoundBuffer_GetStatus (pDSBuf, &dwStatus) != DS_OK) + Con_Print("Couldn't get sound buffer status\n"); + + if (dwStatus & DSBSTATUS_BUFFERLOST) + { + Con_Print("DSound buffer is lost!!\n"); + IDirectSoundBuffer_Restore (pDSBuf); + } + + if (!(dwStatus & DSBSTATUS_PLAYING)) + IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); + + reps = 0; + + while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&dsound_pbuf, &dsound_dwSize, (LPVOID*)&dsound_pbuf2, &dsound_dwSize2, 0)) != DS_OK) + { + if (hresult != DSERR_BUFFERLOST) + { + Con_Print("S_LockBuffer: DS: Lock Sound Buffer Failed\n"); + S_Shutdown (); + S_Startup (); + return false; + } + + if (++reps > 10000) + { + Con_Print("S_LockBuffer: DS: couldn't restore buffer\n"); + S_Shutdown (); + S_Startup (); + return false; + } + } + + if ((void*)dsound_pbuf != snd_renderbuffer->ring) + Sys_Error("SndSys_LockRenderBuffer: the ring address has changed!!!\n"); + return true; + } +#endif + + return wav_init; +} + + +/* +==================== +SndSys_UnlockRenderBuffer + +Release the exclusive lock on "snd_renderbuffer" +==================== +*/ +void SndSys_UnlockRenderBuffer (void) +{ +#ifdef SUPPORTDIRECTX + if (pDSBuf) + IDirectSoundBuffer_Unlock(pDSBuf, dsound_pbuf, dsound_dwSize, dsound_pbuf2, dsound_dwSize2); +#endif +} + +/* +==================== +SndSys_SendKeyEvents + +Send keyboard events originating from the sound system (e.g. MIDI) +==================== +*/ +void SndSys_SendKeyEvents(void) +{ + // not supported +} diff --git a/misc/source/darkplaces-src/sound.h b/misc/source/darkplaces-src/sound.h new file mode 100644 index 00000000..ff1e9187 --- /dev/null +++ b/misc/source/darkplaces-src/sound.h @@ -0,0 +1,121 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef SOUND_H +#define SOUND_H + +#include "matrixlib.h" + + +// ==================================================================== +// Constants +// ==================================================================== + +#define DEFAULT_SOUND_PACKET_VOLUME 255 +#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 + +// Channel flags +#define CHANNELFLAG_NONE 0 +#define CHANNELFLAG_FORCELOOP (1 << 0) // force looping even if the sound is not looped +#define CHANNELFLAG_LOCALSOUND (1 << 1) // INTERNAL USE. Not settable by S_SetChannelFlag +#define CHANNELFLAG_PAUSED (1 << 2) +#define CHANNELFLAG_FULLVOLUME (1 << 3) // isn't affected by the general volume + + +// ==================================================================== +// Types and variables +// ==================================================================== + +typedef struct sfx_s sfx_t; + +extern cvar_t mastervolume; +extern cvar_t bgmvolume; +extern cvar_t volume; +extern cvar_t snd_initialized; +extern cvar_t snd_staticvolume; +extern cvar_t snd_mutewhenidle; + + +// ==================================================================== +// Functions +// ==================================================================== + +void S_Init (void); +void S_Terminate (void); + +void S_Startup (void); +void S_Shutdown (void); +void S_UnloadAllSounds_f (void); + +void S_Update(const matrix4x4_t *listenermatrix); +void S_ExtraUpdate (void); + +sfx_t *S_PrecacheSound (const char *sample, qboolean complain, qboolean levelsound); +float S_SoundLength(const char *name); +void S_ClearUsed (void); +void S_PurgeUnused (void); +qboolean S_IsSoundPrecached (const sfx_t *sfx); + +// for sound() builtins +#define CHANFLAG_RELIABLE 1 + +// these define the "engine" channel namespace +#define CHAN_MIN_AUTO -128 +#define CHAN_MAX_AUTO 0 +#define CHAN_MIN_SINGLE 1 +#define CHAN_MAX_SINGLE 127 +#define IS_CHAN_AUTO(n) ((n) >= CHAN_MIN_AUTO && (n) <= CHAN_MAX_AUTO) +#define IS_CHAN_SINGLE(n) ((n) >= CHAN_MIN_SINGLE && (n) <= CHAN_MAX_SINGLE) +#define IS_CHAN(n) (IS_CHAN_AUTO(n) || IS_CHAN_SINGLE(n)) + +// engine channel == network channel +#define CHAN_ENGINE2NET(c) (c) +#define CHAN_NET2ENGINE(c) (c) + +// engine view of channel encodes the auto flag into the channel number (see CHAN_ constants below) +// user view uses the flags bitmask for it +#define CHAN_USER2ENGINE(c) (c) +#define CHAN_ENGINE2USER(c) (c) +#define CHAN_ENGINE2CVAR(c) (abs(c)) + +// S_StartSound returns the channel index, or -1 if an error occurred +int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation); +int S_StartSound_StartPosition (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition); +int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags); +qboolean S_LocalSound (const char *s); + +void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation); +void S_StopSound (int entnum, int entchannel); +void S_StopAllSounds (void); +void S_PauseGameSounds (qboolean toggle); + +void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx); +qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value); +void S_SetChannelVolume (unsigned int ch_ind, float fvol); +float S_GetChannelPosition (unsigned int ch_ind); +float S_GetEntChannelPosition(int entnum, int entchannel); + +void S_BlockSound (void); +void S_UnblockSound (void); + +int S_GetSoundRate (void); +int S_GetSoundChannels (void); + +#endif diff --git a/misc/source/darkplaces-src/spritegn.h b/misc/source/darkplaces-src/spritegn.h new file mode 100644 index 00000000..b2d2e3b1 --- /dev/null +++ b/misc/source/darkplaces-src/spritegn.h @@ -0,0 +1,131 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// +// spritegn.h: header file for sprite generation program +// + +// ********************************************************** +// * This file must be identical in the spritegen directory * +// * and in the Quake directory, because it's used to * +// * pass data from one to the other via .spr files. * +// ********************************************************** + +#ifndef SPRITEGEN_H +#define SPRITEGEN_H + +//------------------------------------------------------- +// This program generates .spr sprite package files. +// The format of the files is as follows: +// +// dsprite_t file header structure +// +// +// dspriteframe_t frame header structure +// sprite bitmap +// +// dspriteframe_t frame header structure +// sprite bitmap +// +//------------------------------------------------------- + +#define SPRITE_VERSION 1 +#define SPRITEHL_VERSION 2 +#define SPRITE32_VERSION 32 + +#define SPRITE2_VERSION 2 + +typedef struct dsprite_s +{ + int ident; + int version; + int type; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dsprite_t; + +typedef struct dspritehl_s +{ + int ident; + int version; + int type; + int rendermode; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dspritehl_t; + +typedef struct dsprite2frame_s +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[64]; // name of pcx file +} dsprite2frame_t; + +typedef struct dsprite2_s +{ + int ident; + int version; + int numframes; + dsprite2frame_t frames[1]; // variable sized +} dsprite2_t; + +#define SPR_VP_PARALLEL_UPRIGHT 0 +#define SPR_FACING_UPRIGHT 1 +#define SPR_VP_PARALLEL 2 +#define SPR_ORIENTED 3 +#define SPR_VP_PARALLEL_ORIENTED 4 +#define SPR_LABEL 5 +#define SPR_LABEL_SCALE 6 +#define SPR_OVERHEAD 7 + +#define SPRHL_OPAQUE 0 +#define SPRHL_ADDITIVE 1 +#define SPRHL_INDEXALPHA 2 +#define SPRHL_ALPHATEST 3 + +typedef struct dspriteframe_s { + int origin[2]; + int width; + int height; +} dspriteframe_t; + +typedef struct dspritegroup_s { + int numframes; +} dspritegroup_t; + +typedef struct dspriteinterval_s { + float interval; +} dspriteinterval_t; + +typedef enum spriteframetype_e { SPR_SINGLE=0, SPR_GROUP } spriteframetype_t; + +typedef struct dspriteframetype_s { + spriteframetype_t type; +} dspriteframetype_t; + +#endif + diff --git a/misc/source/darkplaces-src/sv_demo.c b/misc/source/darkplaces-src/sv_demo.c new file mode 100644 index 00000000..f6f00d33 --- /dev/null +++ b/misc/source/darkplaces-src/sv_demo.c @@ -0,0 +1,96 @@ +#include "quakedef.h" +#include "sv_demo.h" + +extern cvar_t sv_autodemo_perclient_discardable; + +void SV_StartDemoRecording(client_t *client, const char *filename, int forcetrack) +{ + char name[MAX_QPATH]; + + if(client->sv_demo_file != NULL) + return; // we already have a demo + + strlcpy(name, filename, sizeof(name)); + FS_DefaultExtension(name, ".dem", sizeof(name)); + + Con_Printf("Recording demo for # %d (%s) to %s\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress, name); + + // Reset discardable flag for every new demo. + PRVM_serveredictfloat(client->edict, discardabledemo) = 0; + + client->sv_demo_file = FS_OpenRealFile(name, "wb", false); + if(!client->sv_demo_file) + { + Con_Print("ERROR: couldn't open.\n"); + return; + } + + FS_Printf(client->sv_demo_file, "%i\n", forcetrack); +} + +void SV_WriteDemoMessage(client_t *client, sizebuf_t *sendbuffer, qboolean clienttoserver) +{ + int len, i; + float f; + int temp; + + if(client->sv_demo_file == NULL) + return; + if(sendbuffer->cursize == 0) + return; + + temp = sendbuffer->cursize | (clienttoserver ? DEMOMSG_CLIENT_TO_SERVER : 0); + len = LittleLong(temp); + FS_Write(client->sv_demo_file, &len, 4); + for(i = 0; i < 3; ++i) + { + f = LittleFloat(PRVM_serveredictvector(client->edict, v_angle)[i]); + FS_Write(client->sv_demo_file, &f, 4); + } + FS_Write(client->sv_demo_file, sendbuffer->data, sendbuffer->cursize); +} + +void SV_StopDemoRecording(client_t *client) +{ + sizebuf_t buf; + unsigned char bufdata[64]; + + if(client->sv_demo_file == NULL) + return; + + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + SZ_Clear(&buf); + MSG_WriteByte(&buf, svc_disconnect); + SV_WriteDemoMessage(client, &buf, false); + + if (sv_autodemo_perclient_discardable.integer && PRVM_serveredictfloat(client->edict, discardabledemo)) + { + FS_RemoveOnClose(client->sv_demo_file); + Con_Printf("Stopped recording discardable demo for # %d (%s)\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress); + } + else + Con_Printf("Stopped recording demo for # %d (%s)\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress); + + FS_Close(client->sv_demo_file); + client->sv_demo_file = NULL; +} + +void SV_WriteNetnameIntoDemo(client_t *client) +{ + // This "pseudo packet" is written so a program can easily find out whose demo this is + sizebuf_t buf; + unsigned char bufdata[128]; + + if(client->sv_demo_file == NULL) + return; + + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + SZ_Clear(&buf); + MSG_WriteByte(&buf, svc_stufftext); + MSG_WriteUnterminatedString(&buf, "\n// this demo contains the point of view of: "); + MSG_WriteUnterminatedString(&buf, client->name); + MSG_WriteString(&buf, "\n"); + SV_WriteDemoMessage(client, &buf, false); +} diff --git a/misc/source/darkplaces-src/sv_demo.h b/misc/source/darkplaces-src/sv_demo.h new file mode 100644 index 00000000..65c19248 --- /dev/null +++ b/misc/source/darkplaces-src/sv_demo.h @@ -0,0 +1,9 @@ +#ifndef SV_DEMO_H +#define SV_DEMO_H + +void SV_StartDemoRecording(client_t *client, const char *filename, int forcetrack); +void SV_WriteDemoMessage(client_t *client, sizebuf_t *sendbuffer, qboolean clienttoserver); +void SV_StopDemoRecording(client_t *client); +void SV_WriteNetnameIntoDemo(client_t *client); + +#endif diff --git a/misc/source/darkplaces-src/sv_main.c b/misc/source/darkplaces-src/sv_main.c new file mode 100644 index 00000000..bc6eaf0a --- /dev/null +++ b/misc/source/darkplaces-src/sv_main.c @@ -0,0 +1,3802 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_main.c -- server main program + +#include "quakedef.h" +#include "sv_demo.h" +#include "libcurl.h" +#include "csprogs.h" + +static void SV_SaveEntFile_f(void); +static void SV_StartDownload_f(void); +static void SV_Download_f(void); +static void SV_VM_Setup(void); +extern cvar_t net_connecttimeout; + +void VM_CustomStats_Clear (void); +void VM_SV_UpdateCustomStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); + +cvar_t sv_worldmessage = {CVAR_READONLY, "sv_worldmessage", "", "title of current level"}; +cvar_t sv_worldname = {CVAR_READONLY, "sv_worldname", "", "name of current worldmodel"}; +cvar_t sv_worldnamenoextension = {CVAR_READONLY, "sv_worldnamenoextension", "", "name of current worldmodel without extension"}; +cvar_t sv_worldbasename = {CVAR_READONLY, "sv_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"}; + +cvar_t coop = {0, "coop","0", "coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch)"}; +cvar_t deathmatch = {0, "deathmatch","0", "deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons)"}; +cvar_t fraglimit = {CVAR_NOTIFY, "fraglimit","0", "ends level if this many frags is reached by any player"}; +cvar_t gamecfg = {0, "gamecfg", "0", "unused cvar in quake, can be used by mods"}; +cvar_t noexit = {CVAR_NOTIFY, "noexit","0", "kills anyone attempting to use an exit"}; +cvar_t nomonsters = {0, "nomonsters", "0", "unused cvar in quake, can be used by mods"}; +cvar_t pausable = {0, "pausable","1", "allow players to pause or not"}; +cvar_t pr_checkextension = {CVAR_READONLY, "pr_checkextension", "1", "indicates to QuakeC that the standard quakec extensions system is available (if 0, quakec should not attempt to use extensions)"}; +cvar_t samelevel = {CVAR_NOTIFY, "samelevel","0", "repeats same level if level ends (due to timelimit or someone hitting an exit)"}; +cvar_t skill = {0, "skill","1", "difficulty level of game, affects monster layouts in levels, 0 = easy, 1 = normal, 2 = hard, 3 = nightmare (same layout as hard but monsters fire twice)"}; +cvar_t slowmo = {0, "slowmo", "1.0", "controls game speed, 0.5 is half speed, 2 is double speed"}; + +cvar_t sv_accelerate = {0, "sv_accelerate", "10", "rate at which a player accelerates to sv_maxspeed"}; +cvar_t sv_aim = {CVAR_SAVE, "sv_aim", "2", "maximum cosine angle for quake's vertical autoaim, a value above 1 completely disables the autoaim, quake used 0.93"}; +cvar_t sv_airaccel_qw = {0, "sv_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration; when < 0, the speed is clamped against the maximum allowed forward speed after the move"}; +cvar_t sv_airaccel_qw_stretchfactor = {0, "sv_airaccel_qw_stretchfactor", "0", "when set, the maximum acceleration increase the player may get compared to forward-acceleration when strafejumping"}; +cvar_t sv_airaccel_sideways_friction = {0, "sv_airaccel_sideways_friction", "", "anti-sideways movement stabilization (reduces speed gain when zigzagging); when < 0, only so much friction is applied that braking (by accelerating backwards) cannot be stronger"}; +cvar_t sv_airaccelerate = {0, "sv_airaccelerate", "-1", "rate at which a player accelerates to sv_maxairspeed while in the air, if less than 0 the sv_accelerate variable is used instead"}; +cvar_t sv_airstopaccelerate = {0, "sv_airstopaccelerate", "0", "when set, replacement for sv_airaccelerate when moving backwards"}; +cvar_t sv_airspeedlimit_nonqw = {0, "sv_airspeedlimit_nonqw", "0", "when set, this is a soft speed limit while in air when using airaccel_qw not equal to 1"}; +cvar_t sv_airstrafeaccelerate = {0, "sv_airstrafeaccelerate", "0", "when set, replacement for sv_airaccelerate when just strafing"}; +cvar_t sv_maxairstrafespeed = {0, "sv_maxairstrafespeed", "0", "when set, replacement for sv_maxairspeed when just strafing"}; +cvar_t sv_airstrafeaccel_qw = {0, "sv_airstrafeaccel_qw", "0", "when set, replacement for sv_airaccel_qw when just strafing"}; +cvar_t sv_aircontrol = {0, "sv_aircontrol", "0", "CPMA-style air control"}; +cvar_t sv_aircontrol_power = {0, "sv_aircontrol_power", "2", "CPMA-style air control exponent"}; +cvar_t sv_aircontrol_penalty = {0, "sv_aircontrol_penalty", "0", "deceleration while using CPMA-style air control"}; +cvar_t sv_allowdownloads = {0, "sv_allowdownloads", "1", "whether to allow clients to download files from the server (does not affect http downloads)"}; +cvar_t sv_allowdownloads_archive = {0, "sv_allowdownloads_archive", "0", "whether to allow downloads of archives (pak/pk3)"}; +cvar_t sv_allowdownloads_config = {0, "sv_allowdownloads_config", "0", "whether to allow downloads of config files (cfg)"}; +cvar_t sv_allowdownloads_dlcache = {0, "sv_allowdownloads_dlcache", "0", "whether to allow downloads of dlcache files (dlcache/)"}; +cvar_t sv_allowdownloads_inarchive = {0, "sv_allowdownloads_inarchive", "0", "whether to allow downloads from archives (pak/pk3)"}; +cvar_t sv_areagrid_mingridsize = {CVAR_NOTIFY, "sv_areagrid_mingridsize", "128", "minimum areagrid cell size, smaller values work better for lots of small objects, higher values for large objects"}; +cvar_t sv_checkforpacketsduringsleep = {0, "sv_checkforpacketsduringsleep", "0", "uses select() function to wait between frames which can be interrupted by packets being received, instead of Sleep()/usleep()/SDL_Sleep() functions which do not check for packets"}; +cvar_t sv_clmovement_enable = {0, "sv_clmovement_enable", "1", "whether to allow clients to use cl_movement prediction, which can cause choppy movement on the server which may annoy other players"}; +cvar_t sv_clmovement_minping = {0, "sv_clmovement_minping", "0", "if client ping is below this time in milliseconds, then their ability to use cl_movement prediction is disabled for a while (as they don't need it)"}; +cvar_t sv_clmovement_minping_disabletime = {0, "sv_clmovement_minping_disabletime", "1000", "when client falls below minping, disable their prediction for this many milliseconds (should be at least 1000 or else their prediction may turn on/off frequently)"}; +cvar_t sv_clmovement_inputtimeout = {0, "sv_clmovement_inputtimeout", "0.2", "when a client does not send input for this many seconds, force them to move anyway (unlike QuakeWorld)"}; +cvar_t sv_cullentities_nevercullbmodels = {0, "sv_cullentities_nevercullbmodels", "0", "if enabled the clients are always notified of moving doors and lifts and other submodels of world (warning: eats a lot of network bandwidth on some levels!)"}; +cvar_t sv_cullentities_pvs = {0, "sv_cullentities_pvs", "1", "fast but loose culling of hidden entities"}; +cvar_t sv_cullentities_stats = {0, "sv_cullentities_stats", "0", "displays stats on network entities culled by various methods for each client"}; +cvar_t sv_cullentities_trace = {0, "sv_cullentities_trace", "0", "somewhat slow but very tight culling of hidden entities, minimizes network traffic and makes wallhack cheats useless"}; +cvar_t sv_cullentities_trace_delay = {0, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; +cvar_t sv_cullentities_trace_delay_players = {0, "sv_cullentities_trace_delay_players", "0.2", "number of seconds until the entity gets actually culled if it is a player entity"}; +cvar_t sv_cullentities_trace_enlarge = {0, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; +cvar_t sv_cullentities_trace_prediction = {0, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"}; +cvar_t sv_cullentities_trace_prediction_time = {0, "sv_cullentities_trace_prediction_time", "0.2", "how many seconds of prediction to use"}; +cvar_t sv_cullentities_trace_entityocclusion = {0, "sv_cullentities_trace_entityocclusion", "0", "also check if doors and other bsp models are in the way"}; +cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "2", "number of samples to test for entity culling"}; +cvar_t sv_cullentities_trace_samples_extra = {0, "sv_cullentities_trace_samples_extra", "2", "number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight"}; +cvar_t sv_cullentities_trace_samples_players = {0, "sv_cullentities_trace_samples_players", "8", "number of samples to test for entity culling when the entity is a player entity"}; +cvar_t sv_debugmove = {CVAR_NOTIFY, "sv_debugmove", "0", "disables collision detection optimizations for debugging purposes"}; +cvar_t sv_echobprint = {CVAR_SAVE, "sv_echobprint", "1", "prints gamecode bprint() calls to server console"}; +cvar_t sv_edgefriction = {0, "edgefriction", "1", "how much you slow down when nearing a ledge you might fall off, multiplier of sv_friction (Quake used 2, QuakeWorld used 1 due to a bug in physics code)"}; +cvar_t sv_entpatch = {0, "sv_entpatch", "1", "enables loading of .ent files to override entities in the bsp (for example Threewave CTF server pack contains .ent patch files enabling play of CTF on id1 maps)"}; +cvar_t sv_fixedframeratesingleplayer = {0, "sv_fixedframeratesingleplayer", "1", "allows you to use server-style timing system in singleplayer (don't run faster than sys_ticrate)"}; +cvar_t sv_freezenonclients = {CVAR_NOTIFY, "sv_freezenonclients", "0", "freezes time, except for players, allowing you to walk around and take screenshots of explosions"}; +cvar_t sv_friction = {CVAR_NOTIFY, "sv_friction","4", "how fast you slow down"}; +cvar_t sv_gameplayfix_blowupfallenzombies = {0, "sv_gameplayfix_blowupfallenzombies", "1", "causes findradius to detect SOLID_NOT entities such as zombies and corpses on the floor, allowing splash damage to apply to them"}; +cvar_t sv_gameplayfix_consistentplayerprethink = {0, "sv_gameplayfix_consistentplayerprethink", "0", "improves fairness in multiplayer by running all PlayerPreThink functions (which fire weapons) before performing physics, then running all PlayerPostThink functions"}; +cvar_t sv_gameplayfix_delayprojectiles = {0, "sv_gameplayfix_delayprojectiles", "1", "causes entities to not move on the same frame they are spawned, meaning that projectiles wait until the next frame to perform their first move, giving proper interpolation and rocket trails, but making weapons harder to use at low framerates"}; +cvar_t sv_gameplayfix_droptofloorstartsolid = {0, "sv_gameplayfix_droptofloorstartsolid", "1", "prevents items and monsters that start in a solid area from falling out of the level (makes droptofloor treat trace_startsolid as an acceptable outcome)"}; +cvar_t sv_gameplayfix_droptofloorstartsolid_nudgetocorrect = {0, "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect", "1", "tries to nudge stuck items and monsters out of walls before droptofloor is performed"}; +cvar_t sv_gameplayfix_easierwaterjump = {0, "sv_gameplayfix_easierwaterjump", "1", "changes water jumping to make it easier to get out of water (exactly like in QuakeWorld)"}; +cvar_t sv_gameplayfix_findradiusdistancetobox = {0, "sv_gameplayfix_findradiusdistancetobox", "1", "causes findradius to check the distance to the corner of a box rather than the center of the box, makes findradius detect bmodels such as very large doors that would otherwise be unaffected by splash damage"}; +cvar_t sv_gameplayfix_gravityunaffectedbyticrate = {0, "sv_gameplayfix_gravityunaffectedbyticrate", "0", "fix some ticrate issues in physics."}; +cvar_t sv_gameplayfix_grenadebouncedownslopes = {0, "sv_gameplayfix_grenadebouncedownslopes", "1", "prevents MOVETYPE_BOUNCE (grenades) from getting stuck when fired down a downward sloping surface"}; +cvar_t sv_gameplayfix_multiplethinksperframe = {0, "sv_gameplayfix_multiplethinksperframe", "1", "allows entities to think more often than the server framerate, primarily useful for very high fire rate weapons"}; +cvar_t sv_gameplayfix_noairborncorpse = {0, "sv_gameplayfix_noairborncorpse", "1", "causes entities (corpses, items, etc) sitting ontop of moving entities (players) to fall when the moving entity (player) is no longer supporting them"}; +cvar_t sv_gameplayfix_noairborncorpse_allowsuspendeditems = {0, "sv_gameplayfix_noairborncorpse_allowsuspendeditems", "1", "causes entities sitting ontop of objects that are instantaneously remove to float in midair (special hack to allow a common level design trick for floating items)"}; +cvar_t sv_gameplayfix_nudgeoutofsolid = {0, "sv_gameplayfix_nudgeoutofsolid", "0", "attempts to fix physics errors (where an object ended up in solid for some reason)"}; +cvar_t sv_gameplayfix_nudgeoutofsolid_separation = {0, "sv_gameplayfix_nudgeoutofsolid_separation", "0.03125", "keep objects this distance apart to prevent collision issues on seams"}; +cvar_t sv_gameplayfix_q2airaccelerate = {0, "sv_gameplayfix_q2airaccelerate", "0", "Quake2-style air acceleration"}; +cvar_t sv_gameplayfix_nogravityonground = {0, "sv_gameplayfix_nogravityonground", "0", "turn off gravity when on ground (to get rid of sliding)"}; +cvar_t sv_gameplayfix_setmodelrealbox = {0, "sv_gameplayfix_setmodelrealbox", "1", "fixes a bug in Quake that made setmodel always set the entity box to ('-16 -16 -16', '16 16 16') rather than properly checking the model box, breaks some poorly coded mods"}; +cvar_t sv_gameplayfix_slidemoveprojectiles = {0, "sv_gameplayfix_slidemoveprojectiles", "1", "allows MOVETYPE_FLY/FLYMISSILE/TOSS/BOUNCE/BOUNCEMISSILE entities to finish their move in a frame even if they hit something, fixes 'gravity accumulation' bug for grenades on steep slopes"}; +cvar_t sv_gameplayfix_stepdown = {0, "sv_gameplayfix_stepdown", "0", "attempts to step down stairs, not just up them (prevents the familiar thud..thud..thud.. when running down stairs and slopes)"}; +cvar_t sv_gameplayfix_stepwhilejumping = {0, "sv_gameplayfix_stepwhilejumping", "1", "applies step-up onto a ledge even while airborn, useful if you would otherwise just-miss the floor when running across small areas with gaps (for instance running across the moving platforms in dm2, or jumping to the megahealth and red armor in dm2 rather than using the bridge)"}; +cvar_t sv_gameplayfix_stepmultipletimes = {0, "sv_gameplayfix_stepmultipletimes", "0", "applies step-up onto a ledge more than once in a single frame, when running quickly up stairs"}; +cvar_t sv_gameplayfix_nostepmoveonsteepslopes = {0, "sv_gameplayfix_nostepmoveonsteepslopes", "0", "grude fix which prevents MOVETYPE_STEP (not swimming or flying) to move on slopes whose angle is bigger than 45 degree"}; +cvar_t sv_gameplayfix_swiminbmodels = {0, "sv_gameplayfix_swiminbmodels", "1", "causes pointcontents (used to determine if you are in a liquid) to check bmodel entities as well as the world model, so you can swim around in (possibly moving) water bmodel entities"}; +cvar_t sv_gameplayfix_upwardvelocityclearsongroundflag = {0, "sv_gameplayfix_upwardvelocityclearsongroundflag", "1", "prevents monsters, items, and most other objects from being stuck to the floor when pushed around by damage, and other situations in mods"}; +cvar_t sv_gameplayfix_downtracesupportsongroundflag = {0, "sv_gameplayfix_downtracesupportsongroundflag", "1", "prevents very short moves from clearing onground (which may make the player stick to the floor at high netfps)"}; +cvar_t sv_gameplayfix_q1bsptracelinereportstexture = {0, "sv_gameplayfix_q1bsptracelinereportstexture", "1", "enables mods to get accurate trace_texture results on q1bsp by using a surface-hitting traceline implementation rather than the standard solidbsp method, q3bsp always reports texture accurately"}; +cvar_t sv_gravity = {CVAR_NOTIFY, "sv_gravity","800", "how fast you fall (512 = roughly earth gravity)"}; +cvar_t sv_idealpitchscale = {0, "sv_idealpitchscale","0.8", "how much to look up/down slopes and stairs when not using freelook"}; +cvar_t sv_jumpstep = {CVAR_NOTIFY, "sv_jumpstep", "0", "whether you can step up while jumping (sv_gameplayfix_stepwhilejumping must also be 1)"}; +cvar_t sv_jumpvelocity = {0, "sv_jumpvelocity", "270", "cvar that can be used by QuakeC code for jump velocity"}; +cvar_t sv_maxairspeed = {0, "sv_maxairspeed", "30", "maximum speed a player can accelerate to when airborn (note that it is possible to completely stop by moving the opposite direction)"}; +cvar_t sv_maxrate = {CVAR_SAVE | CVAR_NOTIFY, "sv_maxrate", "1000000", "upper limit on client rate cvar, should reflect your network connection quality"}; +cvar_t sv_maxspeed = {CVAR_NOTIFY, "sv_maxspeed", "320", "maximum speed a player can accelerate to when on ground (can be exceeded by tricks)"}; +cvar_t sv_maxvelocity = {CVAR_NOTIFY, "sv_maxvelocity","2000", "universal speed limit on all entities"}; +cvar_t sv_nostep = {CVAR_NOTIFY, "sv_nostep","0", "prevents MOVETYPE_STEP entities (monsters) from moving"}; +cvar_t sv_playerphysicsqc = {CVAR_NOTIFY, "sv_playerphysicsqc", "1", "enables QuakeC function to override player physics"}; +cvar_t sv_progs = {0, "sv_progs", "progs.dat", "selects which quakec progs.dat file to run" }; +cvar_t sv_protocolname = {0, "sv_protocolname", "DP7", "selects network protocol to host for (values include QUAKE, QUAKEDP, NEHAHRAMOVIE, DP1 and up)"}; +cvar_t sv_random_seed = {0, "sv_random_seed", "", "random seed; when set, on every map start this random seed is used to initialize the random number generator. Don't touch it unless for benchmarking or debugging"}; +cvar_t sv_ratelimitlocalplayer = {0, "sv_ratelimitlocalplayer", "0", "whether to apply rate limiting to the local player in a listen server (only useful for testing)"}; +cvar_t sv_sound_land = {0, "sv_sound_land", "demon/dland2.wav", "sound to play when MOVETYPE_STEP entity hits the ground at high speed (empty cvar disables the sound)"}; +cvar_t sv_sound_watersplash = {0, "sv_sound_watersplash", "misc/h2ohit1.wav", "sound to play when MOVETYPE_FLY/TOSS/BOUNCE/STEP entity enters or leaves water (empty cvar disables the sound)"}; +cvar_t sv_stepheight = {CVAR_NOTIFY, "sv_stepheight", "18", "how high you can step up (TW_SV_STEPCONTROL extension)"}; +cvar_t sv_stopspeed = {CVAR_NOTIFY, "sv_stopspeed","100", "how fast you come to a complete stop"}; +cvar_t sv_wallfriction = {CVAR_NOTIFY, "sv_wallfriction", "1", "how much you slow down when sliding along a wall"}; +cvar_t sv_wateraccelerate = {0, "sv_wateraccelerate", "-1", "rate at which a player accelerates to sv_maxspeed while in the air, if less than 0 the sv_accelerate variable is used instead"}; +cvar_t sv_waterfriction = {CVAR_NOTIFY, "sv_waterfriction","-1", "how fast you slow down, if less than 0 the sv_friction variable is used instead"}; +cvar_t sv_warsowbunny_airforwardaccel = {0, "sv_warsowbunny_airforwardaccel", "1.00001", "how fast you accelerate until you reach sv_maxspeed"}; +cvar_t sv_warsowbunny_accel = {0, "sv_warsowbunny_accel", "0.1585", "how fast you accelerate until after reaching sv_maxspeed (it gets harder as you near sv_warsowbunny_topspeed)"}; +cvar_t sv_warsowbunny_topspeed = {0, "sv_warsowbunny_topspeed", "925", "soft speed limit (can get faster with rjs and on ramps)"}; +cvar_t sv_warsowbunny_turnaccel = {0, "sv_warsowbunny_turnaccel", "0", "max sharpness of turns (also master switch for the sv_warsowbunny_* mode; set this to 9 to enable)"}; +cvar_t sv_warsowbunny_backtosideratio = {0, "sv_warsowbunny_backtosideratio", "0.8", "lower values make it easier to change direction without losing speed; the drawback is \"understeering\" in sharp turns"}; +cvar_t sv_onlycsqcnetworking = {0, "sv_onlycsqcnetworking", "0", "disables legacy entity networking code for higher performance (except on clients, which can still be legacy)"}; +cvar_t sys_ticrate = {CVAR_SAVE, "sys_ticrate","0.0138889", "how long a server frame is in seconds, 0.05 is 20fps server rate, 0.1 is 10fps (can not be set higher than 0.1), 0 runs as many server frames as possible (makes games against bots a little smoother, overwhelms network players), 0.0138889 matches QuakeWorld physics"}; +cvar_t teamplay = {CVAR_NOTIFY, "teamplay","0", "teamplay mode, values depend on mod but typically 0 = no teams, 1 = no team damage no self damage, 2 = team damage and self damage, some mods support 3 = no team damage but can damage self"}; +cvar_t timelimit = {CVAR_NOTIFY, "timelimit","0", "ends level at this time (in minutes)"}; + +cvar_t saved1 = {CVAR_SAVE, "saved1", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved2 = {CVAR_SAVE, "saved2", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved3 = {CVAR_SAVE, "saved3", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved4 = {CVAR_SAVE, "saved4", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t savedgamecfg = {CVAR_SAVE, "savedgamecfg", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t scratch1 = {0, "scratch1", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch2 = {0,"scratch2", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch3 = {0, "scratch3", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch4 = {0, "scratch4", "0", "unused cvar in quake, can be used by mods"}; +cvar_t temp1 = {0, "temp1","0", "general cvar for mods to use, in stock id1 this selects which death animation to use on players (0 = random death, other values select specific death scenes)"}; + +cvar_t nehx00 = {0, "nehx00", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx01 = {0, "nehx01", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx02 = {0, "nehx02", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx03 = {0, "nehx03", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx04 = {0, "nehx04", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx05 = {0, "nehx05", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx06 = {0, "nehx06", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx07 = {0, "nehx07", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx08 = {0, "nehx08", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx09 = {0, "nehx09", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx10 = {0, "nehx10", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx11 = {0, "nehx11", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx12 = {0, "nehx12", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx13 = {0, "nehx13", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx14 = {0, "nehx14", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx15 = {0, "nehx15", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx16 = {0, "nehx16", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx17 = {0, "nehx17", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx18 = {0, "nehx18", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx19 = {0, "nehx19", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t cutscene = {0, "cutscene", "1", "enables cutscenes in nehahra, can be used by other mods"}; + +cvar_t sv_autodemo_perclient = {CVAR_SAVE, "sv_autodemo_perclient", "0", "set to 1 to enable autorecorded per-client demos (they'll start to record at the beginning of a match); set it to 2 to also record client->server packets (for debugging)"}; +cvar_t sv_autodemo_perclient_nameformat = {CVAR_SAVE, "sv_autodemo_perclient_nameformat", "sv_autodemos/%Y-%m-%d_%H-%M", "The format of the sv_autodemo_perclient filename, followed by the map name, the client number and the IP address + port number, separated by underscores (the date is encoded using strftime escapes)" }; +cvar_t sv_autodemo_perclient_discardable = {CVAR_SAVE, "sv_autodemo_perclient_discardable", "0", "Allow game code to decide whether a demo should be kept or discarded."}; + +cvar_t halflifebsp = {0, "halflifebsp", "0", "indicates the current map is hlbsp format (useful to know because of different bounding box sizes)"}; + +server_t sv; +server_static_t svs; + +mempool_t *sv_mempool = NULL; + +extern cvar_t slowmo; +extern float scr_centertime_off; + +// MUST match effectnameindex_t in client.h +static const char *standardeffectnames[EFFECT_TOTAL] = +{ + "", + "TE_GUNSHOT", + "TE_GUNSHOTQUAD", + "TE_SPIKE", + "TE_SPIKEQUAD", + "TE_SUPERSPIKE", + "TE_SUPERSPIKEQUAD", + "TE_WIZSPIKE", + "TE_KNIGHTSPIKE", + "TE_EXPLOSION", + "TE_EXPLOSIONQUAD", + "TE_TAREXPLOSION", + "TE_TELEPORT", + "TE_LAVASPLASH", + "TE_SMALLFLASH", + "TE_FLAMEJET", + "EF_FLAME", + "TE_BLOOD", + "TE_SPARK", + "TE_PLASMABURN", + "TE_TEI_G3", + "TE_TEI_SMOKE", + "TE_TEI_BIGEXPLOSION", + "TE_TEI_PLASMAHIT", + "EF_STARDUST", + "TR_ROCKET", + "TR_GRENADE", + "TR_BLOOD", + "TR_WIZSPIKE", + "TR_SLIGHTBLOOD", + "TR_KNIGHTSPIKE", + "TR_VORESPIKE", + "TR_NEHAHRASMOKE", + "TR_NEXUIZPLASMA", + "TR_GLOWTRAIL", + "SVC_PARTICLE" +}; + +#define SV_REQFUNCS 0 +#define sv_reqfuncs NULL + +//#define SV_REQFUNCS (sizeof(sv_reqfuncs) / sizeof(const char *)) +//static const char *sv_reqfuncs[] = { +//}; + +#define SV_REQFIELDS (sizeof(sv_reqfields) / sizeof(prvm_required_field_t)) + +prvm_required_field_t sv_reqfields[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_serverfieldvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_serverfieldstring(x) {ev_string, #x}, +#define PRVM_DECLARE_serverfieldedict(x) {ev_entity, #x}, +#define PRVM_DECLARE_serverfieldfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +#define SV_REQGLOBALS (sizeof(sv_reqglobals) / sizeof(prvm_required_field_t)) + +prvm_required_field_t sv_reqglobals[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_serverglobalvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_serverglobalstring(x) {ev_string, #x}, +#define PRVM_DECLARE_serverglobaledict(x) {ev_entity, #x}, +#define PRVM_DECLARE_serverglobalfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + + + +//============================================================================ + +void SV_AreaStats_f(void) +{ + World_PrintAreaStats(&sv.world, "server"); +} + +/* +=============== +SV_Init +=============== +*/ +void SV_Init (void) +{ + // init the csqc progs cvars, since they are updated/used by the server code + // TODO: fix this since this is a quick hack to make some of [515]'s broken code run ;) [9/13/2006 Black] + extern cvar_t csqc_progname; //[515]: csqc crc check and right csprogs name according to progs.dat + extern cvar_t csqc_progcrc; + extern cvar_t csqc_progsize; + + Cvar_RegisterVariable(&sv_worldmessage); + Cvar_RegisterVariable(&sv_worldname); + Cvar_RegisterVariable(&sv_worldnamenoextension); + Cvar_RegisterVariable(&sv_worldbasename); + + Cvar_RegisterVariable (&csqc_progname); + Cvar_RegisterVariable (&csqc_progcrc); + Cvar_RegisterVariable (&csqc_progsize); + + Cmd_AddCommand("sv_saveentfile", SV_SaveEntFile_f, "save map entities to .ent file (to allow external editing)"); + Cmd_AddCommand("sv_areastats", SV_AreaStats_f, "prints statistics on entity culling during collision traces"); + Cmd_AddCommand_WithClientCommand("sv_startdownload", NULL, SV_StartDownload_f, "begins sending a file to the client (network protocol use only)"); + Cmd_AddCommand_WithClientCommand("download", NULL, SV_Download_f, "downloads a specified file from the server"); + + Cvar_RegisterVariable (&coop); + Cvar_RegisterVariable (&deathmatch); + Cvar_RegisterVariable (&fraglimit); + Cvar_RegisterVariable (&gamecfg); + Cvar_RegisterVariable (&noexit); + Cvar_RegisterVariable (&nomonsters); + Cvar_RegisterVariable (&pausable); + Cvar_RegisterVariable (&pr_checkextension); + Cvar_RegisterVariable (&samelevel); + Cvar_RegisterVariable (&skill); + Cvar_RegisterVariable (&slowmo); + Cvar_RegisterVariable (&sv_accelerate); + Cvar_RegisterVariable (&sv_aim); + Cvar_RegisterVariable (&sv_airaccel_qw); + Cvar_RegisterVariable (&sv_airaccel_qw_stretchfactor); + Cvar_RegisterVariable (&sv_airaccel_sideways_friction); + Cvar_RegisterVariable (&sv_airaccelerate); + Cvar_RegisterVariable (&sv_airstopaccelerate); + Cvar_RegisterVariable (&sv_airstrafeaccelerate); + Cvar_RegisterVariable (&sv_maxairstrafespeed); + Cvar_RegisterVariable (&sv_airstrafeaccel_qw); + Cvar_RegisterVariable (&sv_airspeedlimit_nonqw); + Cvar_RegisterVariable (&sv_aircontrol); + Cvar_RegisterVariable (&sv_aircontrol_power); + Cvar_RegisterVariable (&sv_aircontrol_penalty); + Cvar_RegisterVariable (&sv_allowdownloads); + Cvar_RegisterVariable (&sv_allowdownloads_archive); + Cvar_RegisterVariable (&sv_allowdownloads_config); + Cvar_RegisterVariable (&sv_allowdownloads_dlcache); + Cvar_RegisterVariable (&sv_allowdownloads_inarchive); + Cvar_RegisterVariable (&sv_areagrid_mingridsize); + Cvar_RegisterVariable (&sv_checkforpacketsduringsleep); + Cvar_RegisterVariable (&sv_clmovement_enable); + Cvar_RegisterVariable (&sv_clmovement_minping); + Cvar_RegisterVariable (&sv_clmovement_minping_disabletime); + Cvar_RegisterVariable (&sv_clmovement_inputtimeout); + Cvar_RegisterVariable (&sv_cullentities_nevercullbmodels); + Cvar_RegisterVariable (&sv_cullentities_pvs); + Cvar_RegisterVariable (&sv_cullentities_stats); + Cvar_RegisterVariable (&sv_cullentities_trace); + Cvar_RegisterVariable (&sv_cullentities_trace_delay); + Cvar_RegisterVariable (&sv_cullentities_trace_delay_players); + Cvar_RegisterVariable (&sv_cullentities_trace_enlarge); + Cvar_RegisterVariable (&sv_cullentities_trace_entityocclusion); + Cvar_RegisterVariable (&sv_cullentities_trace_prediction); + Cvar_RegisterVariable (&sv_cullentities_trace_prediction_time); + Cvar_RegisterVariable (&sv_cullentities_trace_samples); + Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra); + Cvar_RegisterVariable (&sv_cullentities_trace_samples_players); + Cvar_RegisterVariable (&sv_debugmove); + Cvar_RegisterVariable (&sv_echobprint); + Cvar_RegisterVariable (&sv_edgefriction); + Cvar_RegisterVariable (&sv_entpatch); + Cvar_RegisterVariable (&sv_fixedframeratesingleplayer); + Cvar_RegisterVariable (&sv_freezenonclients); + Cvar_RegisterVariable (&sv_friction); + Cvar_RegisterVariable (&sv_gameplayfix_blowupfallenzombies); + Cvar_RegisterVariable (&sv_gameplayfix_consistentplayerprethink); + Cvar_RegisterVariable (&sv_gameplayfix_delayprojectiles); + Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid); + Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid_nudgetocorrect); + Cvar_RegisterVariable (&sv_gameplayfix_easierwaterjump); + Cvar_RegisterVariable (&sv_gameplayfix_findradiusdistancetobox); + Cvar_RegisterVariable (&sv_gameplayfix_gravityunaffectedbyticrate); + Cvar_RegisterVariable (&sv_gameplayfix_grenadebouncedownslopes); + Cvar_RegisterVariable (&sv_gameplayfix_multiplethinksperframe); + Cvar_RegisterVariable (&sv_gameplayfix_noairborncorpse); + Cvar_RegisterVariable (&sv_gameplayfix_noairborncorpse_allowsuspendeditems); + Cvar_RegisterVariable (&sv_gameplayfix_nudgeoutofsolid); + Cvar_RegisterVariable (&sv_gameplayfix_nudgeoutofsolid_separation); + Cvar_RegisterVariable (&sv_gameplayfix_q2airaccelerate); + Cvar_RegisterVariable (&sv_gameplayfix_nogravityonground); + Cvar_RegisterVariable (&sv_gameplayfix_setmodelrealbox); + Cvar_RegisterVariable (&sv_gameplayfix_slidemoveprojectiles); + Cvar_RegisterVariable (&sv_gameplayfix_stepdown); + Cvar_RegisterVariable (&sv_gameplayfix_stepwhilejumping); + Cvar_RegisterVariable (&sv_gameplayfix_stepmultipletimes); + Cvar_RegisterVariable (&sv_gameplayfix_nostepmoveonsteepslopes); + Cvar_RegisterVariable (&sv_gameplayfix_swiminbmodels); + Cvar_RegisterVariable (&sv_gameplayfix_upwardvelocityclearsongroundflag); + Cvar_RegisterVariable (&sv_gameplayfix_downtracesupportsongroundflag); + Cvar_RegisterVariable (&sv_gameplayfix_q1bsptracelinereportstexture); + Cvar_RegisterVariable (&sv_gravity); + Cvar_RegisterVariable (&sv_idealpitchscale); + Cvar_RegisterVariable (&sv_jumpstep); + Cvar_RegisterVariable (&sv_jumpvelocity); + Cvar_RegisterVariable (&sv_maxairspeed); + Cvar_RegisterVariable (&sv_maxrate); + Cvar_RegisterVariable (&sv_maxspeed); + Cvar_RegisterVariable (&sv_maxvelocity); + Cvar_RegisterVariable (&sv_nostep); + Cvar_RegisterVariable (&sv_playerphysicsqc); + Cvar_RegisterVariable (&sv_progs); + Cvar_RegisterVariable (&sv_protocolname); + Cvar_RegisterVariable (&sv_random_seed); + Cvar_RegisterVariable (&sv_ratelimitlocalplayer); + Cvar_RegisterVariable (&sv_sound_land); + Cvar_RegisterVariable (&sv_sound_watersplash); + Cvar_RegisterVariable (&sv_stepheight); + Cvar_RegisterVariable (&sv_stopspeed); + Cvar_RegisterVariable (&sv_wallfriction); + Cvar_RegisterVariable (&sv_wateraccelerate); + Cvar_RegisterVariable (&sv_waterfriction); + Cvar_RegisterVariable (&sv_warsowbunny_airforwardaccel); + Cvar_RegisterVariable (&sv_warsowbunny_accel); + Cvar_RegisterVariable (&sv_warsowbunny_topspeed); + Cvar_RegisterVariable (&sv_warsowbunny_turnaccel); + Cvar_RegisterVariable (&sv_warsowbunny_backtosideratio); + Cvar_RegisterVariable (&sv_onlycsqcnetworking); + Cvar_RegisterVariable (&sys_ticrate); + Cvar_RegisterVariable (&teamplay); + Cvar_RegisterVariable (&timelimit); + + Cvar_RegisterVariable (&saved1); + Cvar_RegisterVariable (&saved2); + Cvar_RegisterVariable (&saved3); + Cvar_RegisterVariable (&saved4); + Cvar_RegisterVariable (&savedgamecfg); + Cvar_RegisterVariable (&scratch1); + Cvar_RegisterVariable (&scratch2); + Cvar_RegisterVariable (&scratch3); + Cvar_RegisterVariable (&scratch4); + Cvar_RegisterVariable (&temp1); + + // LordHavoc: Nehahra uses these to pass data around cutscene demos + Cvar_RegisterVariable (&nehx00); + Cvar_RegisterVariable (&nehx01); + Cvar_RegisterVariable (&nehx02); + Cvar_RegisterVariable (&nehx03); + Cvar_RegisterVariable (&nehx04); + Cvar_RegisterVariable (&nehx05); + Cvar_RegisterVariable (&nehx06); + Cvar_RegisterVariable (&nehx07); + Cvar_RegisterVariable (&nehx08); + Cvar_RegisterVariable (&nehx09); + Cvar_RegisterVariable (&nehx10); + Cvar_RegisterVariable (&nehx11); + Cvar_RegisterVariable (&nehx12); + Cvar_RegisterVariable (&nehx13); + Cvar_RegisterVariable (&nehx14); + Cvar_RegisterVariable (&nehx15); + Cvar_RegisterVariable (&nehx16); + Cvar_RegisterVariable (&nehx17); + Cvar_RegisterVariable (&nehx18); + Cvar_RegisterVariable (&nehx19); + Cvar_RegisterVariable (&cutscene); // for Nehahra but useful to other mods as well + + Cvar_RegisterVariable (&sv_autodemo_perclient); + Cvar_RegisterVariable (&sv_autodemo_perclient_nameformat); + Cvar_RegisterVariable (&sv_autodemo_perclient_discardable); + + Cvar_RegisterVariable (&halflifebsp); + + sv_mempool = Mem_AllocPool("server", 0, NULL); +} + +static void SV_SaveEntFile_f(void) +{ + if (!sv.active || !sv.worldmodel) + { + Con_Print("Not running a server\n"); + return; + } + FS_WriteFile(va("%s.ent", sv.worldnamenoextension), sv.worldmodel->brush.entities, (fs_offset_t)strlen(sv.worldmodel->brush.entities)); +} + + +/* +============================================================================= + +EVENT MESSAGES + +============================================================================= +*/ + +/* +================== +SV_StartParticle + +Make sure the event gets sent to all clients +================== +*/ +void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count) +{ + int i; + + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-18) + return; + MSG_WriteByte (&sv.datagram, svc_particle); + MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); + for (i=0 ; i<3 ; i++) + MSG_WriteChar (&sv.datagram, (int)bound(-128, dir[i]*16, 127)); + MSG_WriteByte (&sv.datagram, count); + MSG_WriteByte (&sv.datagram, color); + SV_FlushBroadcastMessages(); +} + +/* +================== +SV_StartEffect + +Make sure the event gets sent to all clients +================== +*/ +void SV_StartEffect (vec3_t org, int modelindex, int startframe, int framecount, int framerate) +{ + if (modelindex >= 256 || startframe >= 256) + { + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-19) + return; + MSG_WriteByte (&sv.datagram, svc_effect2); + MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); + MSG_WriteShort (&sv.datagram, modelindex); + MSG_WriteShort (&sv.datagram, startframe); + MSG_WriteByte (&sv.datagram, framecount); + MSG_WriteByte (&sv.datagram, framerate); + } + else + { + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-17) + return; + MSG_WriteByte (&sv.datagram, svc_effect); + MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); + MSG_WriteByte (&sv.datagram, modelindex); + MSG_WriteByte (&sv.datagram, startframe); + MSG_WriteByte (&sv.datagram, framecount); + MSG_WriteByte (&sv.datagram, framerate); + } + SV_FlushBroadcastMessages(); +} + +/* +================== +SV_StartSound + +Each entity can have eight independant sound sources, like voice, +weapon, feet, etc. + +Channel 0 is an auto-allocate channel, the others override anything +already running on that entity/channel pair. + +An attenuation of 0 will play full volume everywhere in the level. +Larger attenuations will drop off. (max 4 attenuation) + +================== +*/ +void SV_StartSound (prvm_edict_t *entity, int channel, const char *sample, int volume, float attenuation, qboolean reliable) +{ + sizebuf_t *dest; + int sound_num, field_mask, i, ent; + + dest = (reliable ? &sv.reliable_datagram : &sv.datagram); + + if (volume < 0 || volume > 255) + { + Con_Printf ("SV_StartSound: volume = %i\n", volume); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + Con_Printf ("SV_StartSound: attenuation = %f\n", attenuation); + return; + } + + if (!IS_CHAN(channel)) + { + Con_Printf ("SV_StartSound: channel = %i\n", channel); + return; + } + + channel = CHAN_ENGINE2NET(channel); + + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-21) + return; + +// find precache number for sound + sound_num = SV_SoundIndex(sample, 1); + if (!sound_num) + return; + + ent = PRVM_NUM_FOR_EDICT(entity); + + field_mask = 0; + if (volume != DEFAULT_SOUND_PACKET_VOLUME) + field_mask |= SND_VOLUME; + if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) + field_mask |= SND_ATTENUATION; + if (ent >= 8192 || channel < 0 || channel > 7) + field_mask |= SND_LARGEENTITY; + if (sound_num >= 256) + field_mask |= SND_LARGESOUND; + +// directed messages go only to the entity they are targeted on + MSG_WriteByte (dest, svc_sound); + MSG_WriteByte (dest, field_mask); + if (field_mask & SND_VOLUME) + MSG_WriteByte (dest, volume); + if (field_mask & SND_ATTENUATION) + MSG_WriteByte (dest, (int)(attenuation*64)); + if (field_mask & SND_LARGEENTITY) + { + MSG_WriteShort (dest, ent); + MSG_WriteChar (dest, channel); + } + else + MSG_WriteShort (dest, (ent<<3) | channel); + if ((field_mask & SND_LARGESOUND) || sv.protocol == PROTOCOL_NEHAHRABJP2) + MSG_WriteShort (dest, sound_num); + else + MSG_WriteByte (dest, sound_num); + for (i = 0;i < 3;i++) + MSG_WriteCoord (dest, PRVM_serveredictvector(entity, origin)[i]+0.5*(PRVM_serveredictvector(entity, mins)[i]+PRVM_serveredictvector(entity, maxs)[i]), sv.protocol); + + // TODO do we have to do anything here when dest is &sv.reliable_datagram? + if(!reliable) + SV_FlushBroadcastMessages(); +} + +/* +================== +SV_StartPointSound + +Nearly the same logic as SV_StartSound, except an origin +instead of an entity is provided and channel is omitted. + +The entity sent to the client is 0 (world) and the channel +is 0 (CHAN_AUTO). SND_LARGEENTITY will never occur in this +function, therefore the check for it is omitted. + +================== +*/ +void SV_StartPointSound (vec3_t origin, const char *sample, int volume, float attenuation) +{ + int sound_num, field_mask, i; + + if (volume < 0 || volume > 255) + { + Con_Printf ("SV_StartPointSound: volume = %i\n", volume); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + Con_Printf ("SV_StartPointSound: attenuation = %f\n", attenuation); + return; + } + + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-21) + return; + + // find precache number for sound + sound_num = SV_SoundIndex(sample, 1); + if (!sound_num) + return; + + field_mask = 0; + if (volume != DEFAULT_SOUND_PACKET_VOLUME) + field_mask |= SND_VOLUME; + if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) + field_mask |= SND_ATTENUATION; + if (sound_num >= 256) + field_mask |= SND_LARGESOUND; + +// directed messages go only to the entity they are targeted on + MSG_WriteByte (&sv.datagram, svc_sound); + MSG_WriteByte (&sv.datagram, field_mask); + if (field_mask & SND_VOLUME) + MSG_WriteByte (&sv.datagram, volume); + if (field_mask & SND_ATTENUATION) + MSG_WriteByte (&sv.datagram, (int)(attenuation*64)); + // Always write entnum 0 for the world entity + MSG_WriteShort (&sv.datagram, (0<<3) | 0); + if (field_mask & SND_LARGESOUND) + MSG_WriteShort (&sv.datagram, sound_num); + else + MSG_WriteByte (&sv.datagram, sound_num); + for (i = 0;i < 3;i++) + MSG_WriteCoord (&sv.datagram, origin[i], sv.protocol); + SV_FlushBroadcastMessages(); +} + +/* +============================================================================== + +CLIENT SPAWNING + +============================================================================== +*/ + +/* +================ +SV_SendServerinfo + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each server load. +================ +*/ +void SV_SendServerinfo (client_t *client) +{ + int i; + char message[128]; + + // we know that this client has a netconnection and thus is not a bot + + // edicts get reallocated on level changes, so we need to update it here + client->edict = PRVM_EDICT_NUM((client - svs.clients) + 1); + + // clear cached stuff that depends on the level + client->weaponmodel[0] = 0; + client->weaponmodelindex = 0; + + // LordHavoc: clear entityframe tracking + client->latestframenum = 0; + + // initialize the movetime, so a speedhack can't make use of the time before this client joined + client->cmd.time = sv.time; + + if (client->entitydatabase) + EntityFrame_FreeDatabase(client->entitydatabase); + if (client->entitydatabase4) + EntityFrame4_FreeDatabase(client->entitydatabase4); + if (client->entitydatabase5) + EntityFrame5_FreeDatabase(client->entitydatabase5); + + memset(client->stats, 0, sizeof(client->stats)); + memset(client->statsdeltabits, 0, sizeof(client->statsdeltabits)); + + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3) + { + if (sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) + client->entitydatabase = EntityFrame_AllocDatabase(sv_mempool); + else if (sv.protocol == PROTOCOL_DARKPLACES4) + client->entitydatabase4 = EntityFrame4_AllocDatabase(sv_mempool); + else + client->entitydatabase5 = EntityFrame5_AllocDatabase(sv_mempool); + } + + // reset csqc entity versions + for (i = 0;i < prog->max_edicts;i++) + { + client->csqcentityscope[i] = 0; + client->csqcentitysendflags[i] = 0xFFFFFF; + client->csqcentityglobalhistory[i] = 0; + } + for (i = 0;i < NUM_CSQCENTITYDB_FRAMES;i++) + { + client->csqcentityframehistory[i].num = 0; + client->csqcentityframehistory[i].framenum = -1; + } + client->csqcnumedicts = 0; + client->csqcentityframehistory_next = 0; + + SZ_Clear (&client->netconnection->message); + MSG_WriteByte (&client->netconnection->message, svc_print); + dpsnprintf (message, sizeof (message), "\nServer: %s build %s (progs %i crc)\n", gamename, buildstring, prog->filecrc); + MSG_WriteString (&client->netconnection->message,message); + + SV_StopDemoRecording(client); // to split up demos into different files + if(sv_autodemo_perclient.integer && client->netconnection) + { + char demofile[MAX_OSPATH]; + char ipaddress[MAX_QPATH]; + size_t i; + + // start a new demo file + LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true); + for(i = 0; ipaddress[i]; ++i) + if(!isalnum(ipaddress[i])) + ipaddress[i] = '-'; + dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), sv.worldbasename, PRVM_NUM_FOR_EDICT(client->edict), ipaddress); + + SV_StartDemoRecording(client, demofile, -1); + } + + //[515]: init csprogs according to version of svprogs, check the crc, etc. + if (sv.csqc_progname[0]) + { + Con_DPrintf("sending csqc info to client (\"%s\" with size %i and crc %i)\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va("csqc_progname %s\n", sv.csqc_progname)); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va("csqc_progsize %i\n", sv.csqc_progsize)); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va("csqc_progcrc %i\n", sv.csqc_progcrc)); + + if(client->sv_demo_file != NULL) + { + int i; + static char buf[NET_MAXMESSAGE]; + sizebuf_t sb; + + sb.data = (unsigned char *) buf; + sb.maxsize = sizeof(buf); + i = 0; + while(MakeDownloadPacket(sv.csqc_progname, svs.csqc_progdata, sv.csqc_progsize, sv.csqc_progcrc, i++, &sb, sv.protocol)) + SV_WriteDemoMessage(client, &sb, false); + } + + //[515]: init stufftext string (it is sent before svc_serverinfo) + if (PRVM_GetString(PRVM_serverglobalstring(SV_InitCmd))) + { + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va("%s\n", PRVM_GetString(PRVM_serverglobalstring(SV_InitCmd)))); + } + } + + //if (sv_allowdownloads.integer) + // always send the info that the server supports the protocol, even if downloads are forbidden + // only because of that, the CSQC exception can work + { + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, "cl_serverextension_download 2\n"); + } + + // send at this time so it's guaranteed to get executed at the right time + { + client_t *save; + save = host_client; + host_client = client; + Curl_SendRequirements(); + host_client = save; + } + + MSG_WriteByte (&client->netconnection->message, svc_serverinfo); + MSG_WriteLong (&client->netconnection->message, Protocol_NumberForEnum(sv.protocol)); + MSG_WriteByte (&client->netconnection->message, svs.maxclients); + + if (!coop.integer && deathmatch.integer) + MSG_WriteByte (&client->netconnection->message, GAME_DEATHMATCH); + else + MSG_WriteByte (&client->netconnection->message, GAME_COOP); + + MSG_WriteString (&client->netconnection->message,PRVM_GetString(PRVM_serveredictstring(prog->edicts, message))); + + for (i = 1;i < MAX_MODELS && sv.model_precache[i][0];i++) + MSG_WriteString (&client->netconnection->message, sv.model_precache[i]); + MSG_WriteByte (&client->netconnection->message, 0); + + for (i = 1;i < MAX_SOUNDS && sv.sound_precache[i][0];i++) + MSG_WriteString (&client->netconnection->message, sv.sound_precache[i]); + MSG_WriteByte (&client->netconnection->message, 0); + +// send music + MSG_WriteByte (&client->netconnection->message, svc_cdtrack); + MSG_WriteByte (&client->netconnection->message, (int)PRVM_serveredictfloat(prog->edicts, sounds)); + MSG_WriteByte (&client->netconnection->message, (int)PRVM_serveredictfloat(prog->edicts, sounds)); + +// set view +// store this in clientcamera, too + client->clientcamera = PRVM_NUM_FOR_EDICT(client->edict); + MSG_WriteByte (&client->netconnection->message, svc_setview); + MSG_WriteShort (&client->netconnection->message, client->clientcamera); + + MSG_WriteByte (&client->netconnection->message, svc_signonnum); + MSG_WriteByte (&client->netconnection->message, 1); + + client->spawned = false; // need prespawn, spawn, etc + client->sendsignon = 1; // send this message, and increment to 2, 2 will be set to 0 by the prespawn command + + // clear movement info until client enters the new level properly + memset(&client->cmd, 0, sizeof(client->cmd)); + client->movesequence = 0; + client->movement_highestsequence_seen = 0; + memset(&client->movement_count, 0, sizeof(client->movement_count)); +#ifdef NUM_PING_TIMES + for (i = 0;i < NUM_PING_TIMES;i++) + client->ping_times[i] = 0; + client->num_pings = 0; +#endif + client->ping = 0; + + // allow the client some time to send his keepalives, even if map loading took ages + client->netconnection->timeout = realtime + net_connecttimeout.value; +} + +/* +================ +SV_ConnectClient + +Initializes a client_t for a new net connection. This will only be called +once for a player each game, not once for each level change. +================ +*/ +void SV_ConnectClient (int clientnum, netconn_t *netconnection) +{ + client_t *client; + int i; + + client = svs.clients + clientnum; + +// set up the client_t + if (sv.loadgame) + { + float backupparms[NUM_SPAWN_PARMS]; + memcpy(backupparms, client->spawn_parms, sizeof(backupparms)); + memset(client, 0, sizeof(*client)); + memcpy(client->spawn_parms, backupparms, sizeof(backupparms)); + } + else + memset(client, 0, sizeof(*client)); + client->active = true; + client->netconnection = netconnection; + + Con_DPrintf("Client %s connected\n", client->netconnection ? client->netconnection->address : "botclient"); + + if(client->netconnection && client->netconnection->crypto.authenticated) + { + Con_Printf("%s connection to %s has been established: client is %s@%.*s, I am %.*s@%.*s\n", + client->netconnection->crypto.use_aes ? "Encrypted" : "Authenticated", + client->netconnection->address, + client->netconnection->crypto.client_idfp[0] ? client->netconnection->crypto.client_idfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.client_keyfp[0] ? client->netconnection->crypto.client_keyfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.server_idfp[0] ? client->netconnection->crypto.server_idfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.server_keyfp[0] ? client->netconnection->crypto.server_keyfp : "-" + ); + } + + strlcpy(client->name, "unconnected", sizeof(client->name)); + strlcpy(client->old_name, "unconnected", sizeof(client->old_name)); + client->spawned = false; + client->edict = PRVM_EDICT_NUM(clientnum+1); + if (client->netconnection) + client->netconnection->message.allowoverflow = true; // we can catch it + // prepare the unreliable message buffer + client->unreliablemsg.data = client->unreliablemsg_data; + client->unreliablemsg.maxsize = sizeof(client->unreliablemsg_data); + // updated by receiving "rate" command from client, this is also the default if not using a DP client + client->rate = 1000000000; + // no limits for local player + if (client->netconnection && LHNETADDRESS_GetAddressType(&client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP) + client->rate = 1000000000; + client->connecttime = realtime; + + if (!sv.loadgame) + { + // call the progs to get default spawn parms for the new client + // set self to world to intentionally cause errors with broken SetNewParms code in some mods + PRVM_serverglobaledict(self) = 0; + PRVM_ExecuteProgram (PRVM_serverfunction(SetNewParms), "QC function SetNewParms is missing"); + for (i=0 ; ispawn_parms[i] = (&PRVM_serverglobalfloat(parm1))[i]; + + // set up the entity for this client (including .colormap, .team, etc) + PRVM_ED_ClearEdict(client->edict); + } + + // don't call SendServerinfo for a fresh botclient because its fields have + // not been set up by the qc yet + if (client->netconnection) + SV_SendServerinfo (client); + else + client->spawned = true; +} + + +/* +=============================================================================== + +FRAME UPDATES + +=============================================================================== +*/ + +/* +============================================================================= + +The PVS must include a small area around the client to allow head bobbing +or other small motion on the client side. Otherwise, a bob might cause an +entity that should be visible to not show up, especially when the bob +crosses a waterline. + +============================================================================= +*/ + +static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int enumber) +{ + int i; + unsigned int sendflags; + unsigned int version; + unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius; + unsigned int customizeentityforclient; + unsigned int sendentity; + float f; + float *v; + vec3_t cullmins, cullmaxs; + dp_model_t *model; + + // fast path for games that do not use legacy entity networking + // note: still networks clients even if they are legacy + sendentity = PRVM_serveredictfunction(ent, SendEntity); + if (sv_onlycsqcnetworking.integer && !sendentity && enumber > svs.maxclients) + return false; + + // this 2 billion unit check is actually to detect NAN origins + // (we really don't want to send those) + if (!(VectorLength2(PRVM_serveredictvector(ent, origin)) < 2000000000.0*2000000000.0)) + return false; + + // EF_NODRAW prevents sending for any reason except for your own + // client, so we must keep all clients in this superset + effects = (unsigned)PRVM_serveredictfloat(ent, effects); + + // we can omit invisible entities with no effects that are not clients + // LordHavoc: this could kill tags attached to an invisible entity, I + // just hope we never have to support that case + i = (int)PRVM_serveredictfloat(ent, modelindex); + modelindex = (i >= 1 && i < MAX_MODELS && PRVM_serveredictstring(ent, model) && *PRVM_GetString(PRVM_serveredictstring(ent, model)) && sv.models[i]) ? i : 0; + + flags = 0; + i = (int)(PRVM_serveredictfloat(ent, glow_size) * 0.25f); + glowsize = (unsigned char)bound(0, i, 255); + if (PRVM_serveredictfloat(ent, glow_trail)) + flags |= RENDER_GLOWTRAIL; + if (PRVM_serveredictedict(ent, viewmodelforclient)) + flags |= RENDER_VIEWMODEL; + + v = PRVM_serveredictvector(ent, color); + f = v[0]*256; + light[0] = (unsigned short)bound(0, f, 65535); + f = v[1]*256; + light[1] = (unsigned short)bound(0, f, 65535); + f = v[2]*256; + light[2] = (unsigned short)bound(0, f, 65535); + f = PRVM_serveredictfloat(ent, light_lev); + light[3] = (unsigned short)bound(0, f, 65535); + lightstyle = (unsigned char)PRVM_serveredictfloat(ent, style); + lightpflags = (unsigned char)PRVM_serveredictfloat(ent, pflags); + + if (gamemode == GAME_TENEBRAE) + { + // tenebrae's EF_FULLDYNAMIC conflicts with Q2's EF_NODRAW + if (effects & 16) + { + effects &= ~16; + lightpflags |= PFLAGS_FULLDYNAMIC; + } + // tenebrae's EF_GREEN conflicts with DP's EF_ADDITIVE + if (effects & 32) + { + effects &= ~32; + light[0] = (int)(0.2*256); + light[1] = (int)(1.0*256); + light[2] = (int)(0.2*256); + light[3] = 200; + lightpflags |= PFLAGS_FULLDYNAMIC; + } + } + + specialvisibilityradius = 0; + if (lightpflags & PFLAGS_FULLDYNAMIC) + specialvisibilityradius = max(specialvisibilityradius, light[3]); + if (glowsize) + specialvisibilityradius = max(specialvisibilityradius, glowsize * 4); + if (flags & RENDER_GLOWTRAIL) + specialvisibilityradius = max(specialvisibilityradius, 100); + if (effects & (EF_BRIGHTFIELD | EF_MUZZLEFLASH | EF_BRIGHTLIGHT | EF_DIMLIGHT | EF_RED | EF_BLUE | EF_FLAME | EF_STARDUST)) + { + if (effects & EF_BRIGHTFIELD) + specialvisibilityradius = max(specialvisibilityradius, 80); + if (effects & EF_MUZZLEFLASH) + specialvisibilityradius = max(specialvisibilityradius, 100); + if (effects & EF_BRIGHTLIGHT) + specialvisibilityradius = max(specialvisibilityradius, 400); + if (effects & EF_DIMLIGHT) + specialvisibilityradius = max(specialvisibilityradius, 200); + if (effects & EF_RED) + specialvisibilityradius = max(specialvisibilityradius, 200); + if (effects & EF_BLUE) + specialvisibilityradius = max(specialvisibilityradius, 200); + if (effects & EF_FLAME) + specialvisibilityradius = max(specialvisibilityradius, 250); + if (effects & EF_STARDUST) + specialvisibilityradius = max(specialvisibilityradius, 100); + } + + // early culling checks + // (final culling is done by SV_MarkWriteEntityStateToClient) + customizeentityforclient = PRVM_serveredictfunction(ent, customizeentityforclient); + if (!customizeentityforclient && enumber > svs.maxclients && (!modelindex && !specialvisibilityradius)) + return false; + + *cs = defaultstate; + cs->active = ACTIVE_NETWORK; + cs->number = enumber; + VectorCopy(PRVM_serveredictvector(ent, origin), cs->origin); + VectorCopy(PRVM_serveredictvector(ent, angles), cs->angles); + cs->flags = flags; + cs->effects = effects; + cs->colormap = (unsigned)PRVM_serveredictfloat(ent, colormap); + cs->modelindex = modelindex; + cs->skin = (unsigned)PRVM_serveredictfloat(ent, skin); + cs->frame = (unsigned)PRVM_serveredictfloat(ent, frame); + cs->viewmodelforclient = PRVM_serveredictedict(ent, viewmodelforclient); + cs->exteriormodelforclient = PRVM_serveredictedict(ent, exteriormodeltoclient); + cs->nodrawtoclient = PRVM_serveredictedict(ent, nodrawtoclient); + cs->drawonlytoclient = PRVM_serveredictedict(ent, drawonlytoclient); + cs->customizeentityforclient = customizeentityforclient; + cs->tagentity = PRVM_serveredictedict(ent, tag_entity); + cs->tagindex = (unsigned char)PRVM_serveredictfloat(ent, tag_index); + cs->glowsize = glowsize; + cs->traileffectnum = PRVM_serveredictfloat(ent, traileffectnum); + + // don't need to init cs->colormod because the defaultstate did that for us + //cs->colormod[0] = cs->colormod[1] = cs->colormod[2] = 32; + v = PRVM_serveredictvector(ent, colormod); + if (VectorLength2(v)) + { + i = (int)(v[0] * 32.0f);cs->colormod[0] = bound(0, i, 255); + i = (int)(v[1] * 32.0f);cs->colormod[1] = bound(0, i, 255); + i = (int)(v[2] * 32.0f);cs->colormod[2] = bound(0, i, 255); + } + + // don't need to init cs->glowmod because the defaultstate did that for us + //cs->glowmod[0] = cs->glowmod[1] = cs->glowmod[2] = 32; + v = PRVM_serveredictvector(ent, glowmod); + if (VectorLength2(v)) + { + i = (int)(v[0] * 32.0f);cs->glowmod[0] = bound(0, i, 255); + i = (int)(v[1] * 32.0f);cs->glowmod[1] = bound(0, i, 255); + i = (int)(v[2] * 32.0f);cs->glowmod[2] = bound(0, i, 255); + } + + cs->modelindex = modelindex; + + cs->alpha = 255; + f = (PRVM_serveredictfloat(ent, alpha) * 255.0f); + if (f) + { + i = (int)f; + cs->alpha = (unsigned char)bound(0, i, 255); + } + // halflife + f = (PRVM_serveredictfloat(ent, renderamt)); + if (f) + { + i = (int)f; + cs->alpha = (unsigned char)bound(0, i, 255); + } + + cs->scale = 16; + f = (PRVM_serveredictfloat(ent, scale) * 16.0f); + if (f) + { + i = (int)f; + cs->scale = (unsigned char)bound(0, i, 255); + } + + cs->glowcolor = 254; + f = PRVM_serveredictfloat(ent, glow_color); + if (f) + cs->glowcolor = (int)f; + + if (PRVM_serveredictfloat(ent, fullbright)) + cs->effects |= EF_FULLBRIGHT; + + f = PRVM_serveredictfloat(ent, modelflags); + if (f) + cs->effects |= ((unsigned int)f & 0xff) << 24; + + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_STEP) + cs->flags |= RENDER_STEP; + if (cs->number != sv.writeentitiestoclient_cliententitynumber && (cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767) + cs->flags |= RENDER_LOWPRECISION; + if (PRVM_serveredictfloat(ent, colormap) >= 1024) + cs->flags |= RENDER_COLORMAPPED; + if (cs->viewmodelforclient) + cs->flags |= RENDER_VIEWMODEL; // show relative to the view + + if (PRVM_serveredictfloat(ent, sendcomplexanimation)) + { + cs->flags |= RENDER_COMPLEXANIMATION; + if (PRVM_serveredictfloat(ent, skeletonindex) >= 1) + cs->skeletonobject = ent->priv.server->skeleton; + cs->framegroupblend[0].frame = PRVM_serveredictfloat(ent, frame); + cs->framegroupblend[1].frame = PRVM_serveredictfloat(ent, frame2); + cs->framegroupblend[2].frame = PRVM_serveredictfloat(ent, frame3); + cs->framegroupblend[3].frame = PRVM_serveredictfloat(ent, frame4); + cs->framegroupblend[0].start = PRVM_serveredictfloat(ent, frame1time); + cs->framegroupblend[1].start = PRVM_serveredictfloat(ent, frame2time); + cs->framegroupblend[2].start = PRVM_serveredictfloat(ent, frame3time); + cs->framegroupblend[3].start = PRVM_serveredictfloat(ent, frame4time); + cs->framegroupblend[1].lerp = PRVM_serveredictfloat(ent, lerpfrac); + cs->framegroupblend[2].lerp = PRVM_serveredictfloat(ent, lerpfrac3); + cs->framegroupblend[3].lerp = PRVM_serveredictfloat(ent, lerpfrac4); + cs->framegroupblend[0].lerp = 1.0f - cs->framegroupblend[1].lerp - cs->framegroupblend[2].lerp - cs->framegroupblend[3].lerp; + } + + cs->light[0] = light[0]; + cs->light[1] = light[1]; + cs->light[2] = light[2]; + cs->light[3] = light[3]; + cs->lightstyle = lightstyle; + cs->lightpflags = lightpflags; + + cs->specialvisibilityradius = specialvisibilityradius; + + // calculate the visible box of this entity (don't use the physics box + // as that is often smaller than a model, and would not count + // specialvisibilityradius) + if ((model = SV_GetModelByIndex(modelindex)) && (model->type != mod_null)) + { + float scale = cs->scale * (1.0f / 16.0f); + if (cs->angles[0] || cs->angles[2]) // pitch and roll + { + VectorMA(cs->origin, scale, model->rotatedmins, cullmins); + VectorMA(cs->origin, scale, model->rotatedmaxs, cullmaxs); + } + else if (cs->angles[1] || ((effects | model->effects) & EF_ROTATE)) + { + VectorMA(cs->origin, scale, model->yawmins, cullmins); + VectorMA(cs->origin, scale, model->yawmaxs, cullmaxs); + } + else + { + VectorMA(cs->origin, scale, model->normalmins, cullmins); + VectorMA(cs->origin, scale, model->normalmaxs, cullmaxs); + } + } + else + { + // if there is no model (or it could not be loaded), use the physics box + VectorAdd(cs->origin, PRVM_serveredictvector(ent, mins), cullmins); + VectorAdd(cs->origin, PRVM_serveredictvector(ent, maxs), cullmaxs); + } + if (specialvisibilityradius) + { + cullmins[0] = min(cullmins[0], cs->origin[0] - specialvisibilityradius); + cullmins[1] = min(cullmins[1], cs->origin[1] - specialvisibilityradius); + cullmins[2] = min(cullmins[2], cs->origin[2] - specialvisibilityradius); + cullmaxs[0] = max(cullmaxs[0], cs->origin[0] + specialvisibilityradius); + cullmaxs[1] = max(cullmaxs[1], cs->origin[1] + specialvisibilityradius); + cullmaxs[2] = max(cullmaxs[2], cs->origin[2] + specialvisibilityradius); + } + + // calculate center of bbox for network prioritization purposes + VectorMAM(0.5f, cullmins, 0.5f, cullmaxs, cs->netcenter); + + // if culling box has moved, update pvs cluster links + if (!VectorCompare(cullmins, ent->priv.server->cullmins) || !VectorCompare(cullmaxs, ent->priv.server->cullmaxs)) + { + VectorCopy(cullmins, ent->priv.server->cullmins); + VectorCopy(cullmaxs, ent->priv.server->cullmaxs); + // a value of -1 for pvs_numclusters indicates that the links are not + // cached, and should be re-tested each time, this is the case if the + // culling box touches too many pvs clusters to store, or if the world + // model does not support FindBoxClusters + ent->priv.server->pvs_numclusters = -1; + if (sv.worldmodel && sv.worldmodel->brush.FindBoxClusters) + { + i = sv.worldmodel->brush.FindBoxClusters(sv.worldmodel, cullmins, cullmaxs, MAX_ENTITYCLUSTERS, ent->priv.server->pvs_clusterlist); + if (i <= MAX_ENTITYCLUSTERS) + ent->priv.server->pvs_numclusters = i; + } + } + + // we need to do some csqc entity upkeep here + // get self.SendFlags and clear them + // (to let the QC know that they've been read) + if (sendentity) + { + sendflags = (unsigned int)PRVM_serveredictfloat(ent, SendFlags); + PRVM_serveredictfloat(ent, SendFlags) = 0; + // legacy self.Version system + if ((version = (unsigned int)PRVM_serveredictfloat(ent, Version))) + { + if (sv.csqcentityversion[enumber] != version) + sendflags = 0xFFFFFF; + sv.csqcentityversion[enumber] = version; + } + // move sendflags into the per-client sendflags + if (sendflags) + for (i = 0;i < svs.maxclients;i++) + svs.clients[i].csqcentitysendflags[enumber] |= sendflags; + // mark it as inactive for non-csqc networking + cs->active = ACTIVE_SHARED; + } + + return true; +} + +void SV_PrepareEntitiesForSending(void) +{ + int e; + prvm_edict_t *ent; + // send all entities that touch the pvs + sv.numsendentities = 0; + sv.sendentitiesindex[0] = NULL; + memset(sv.sendentitiesindex, 0, prog->num_edicts * sizeof(*sv.sendentitiesindex)); + for (e = 1, ent = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ent = PRVM_NEXT_EDICT(ent)) + { + if (!ent->priv.server->free && SV_PrepareEntityForSending(ent, sv.sendentities + sv.numsendentities, e)) + { + sv.sendentitiesindex[e] = sv.sendentities + sv.numsendentities; + sv.numsendentities++; + } + } +} + +#define MAX_LINEOFSIGHTTRACES 64 + +qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs) +{ + float pitchsign; + float alpha; + float starttransformed[3], endtransformed[3]; + int blocked = 0; + int traceindex; + int originalnumtouchedicts; + int numtouchedicts = 0; + int touchindex; + matrix4x4_t matrix, imatrix; + dp_model_t *model; + prvm_edict_t *touch; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + vec3_t boxmins, boxmaxs; + vec3_t clipboxmins, clipboxmaxs; + vec3_t endpoints[MAX_LINEOFSIGHTTRACES]; + + numtraces = min(numtraces, MAX_LINEOFSIGHTTRACES); + + // expand the box a little + boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0]; + boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0]; + boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1]; + boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1]; + boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2]; + boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2]; + + VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, endpoints[0]); + for (traceindex = 1;traceindex < numtraces;traceindex++) + VectorSet(endpoints[traceindex], lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2])); + + // calculate sweep box for the entire swarm of traces + VectorCopy(eye, clipboxmins); + VectorCopy(eye, clipboxmaxs); + for (traceindex = 0;traceindex < numtraces;traceindex++) + { + clipboxmins[0] = min(clipboxmins[0], endpoints[traceindex][0]); + clipboxmins[1] = min(clipboxmins[1], endpoints[traceindex][1]); + clipboxmins[2] = min(clipboxmins[2], endpoints[traceindex][2]); + clipboxmaxs[0] = max(clipboxmaxs[0], endpoints[traceindex][0]); + clipboxmaxs[1] = max(clipboxmaxs[1], endpoints[traceindex][1]); + clipboxmaxs[2] = max(clipboxmaxs[2], endpoints[traceindex][2]); + } + + // get the list of entities in the sweep box + if (sv_cullentities_trace_entityocclusion.integer) + numtouchedicts = World_EntitiesInBox(&sv.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + // iterate the entities found in the sweep box and filter them + originalnumtouchedicts = numtouchedicts; + numtouchedicts = 0; + for (touchindex = 0;touchindex < originalnumtouchedicts;touchindex++) + { + touch = touchedicts[touchindex]; + if (PRVM_serveredictfloat(touch, solid) != SOLID_BSP) + continue; + model = SV_GetModelFromEdict(touch); + if (!model || !model->brush.TraceLineOfSight) + continue; + // skip obviously transparent entities + alpha = PRVM_serveredictfloat(touch, alpha); + if (alpha && alpha < 1) + continue; + if ((int)PRVM_serveredictfloat(touch, effects) & EF_ADDITIVE) + continue; + touchedicts[numtouchedicts++] = touch; + } + + // now that we have a filtered list of "interesting" entities, fire each + // ray against all of them, this gives us an early-out case when something + // is visible (which it often is) + + for (traceindex = 0;traceindex < numtraces;traceindex++) + { + // check world occlusion + if (sv.worldmodel && sv.worldmodel->brush.TraceLineOfSight) + if (!sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, eye, endpoints[traceindex])) + continue; + for (touchindex = 0;touchindex < numtouchedicts;touchindex++) + { + touch = touchedicts[touchindex]; + model = SV_GetModelFromEdict(touch); + if(model && model->brush.TraceLineOfSight) + { + // get the entity matrix + pitchsign = SV_GetPitchSign(touch); + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + // see if the ray hits this entity + Matrix4x4_Transform(&imatrix, eye, starttransformed); + Matrix4x4_Transform(&imatrix, endpoints[traceindex], endtransformed); + if (!model->brush.TraceLineOfSight(model, starttransformed, endtransformed)) + { + blocked++; + break; + } + } + } + // check if the ray was blocked + if (touchindex < numtouchedicts) + continue; + // return if the ray was not blocked + return true; + } + + // no rays survived + return false; +} + +void SV_MarkWriteEntityStateToClient(entity_state_t *s) +{ + int isbmodel; + dp_model_t *model; + prvm_edict_t *ed; + if (sv.sententitiesconsideration[s->number] == sv.sententitiesmark) + return; + sv.sententitiesconsideration[s->number] = sv.sententitiesmark; + sv.writeentitiestoclient_stats_totalentities++; + + if (s->customizeentityforclient) + { + PRVM_serverglobaledict(self) = s->number; + PRVM_serverglobaledict(other) = sv.writeentitiestoclient_cliententitynumber; + PRVM_ExecuteProgram(s->customizeentityforclient, "customizeentityforclient: NULL function"); + if(!PRVM_G_FLOAT(OFS_RETURN) || !SV_PrepareEntityForSending(PRVM_EDICT_NUM(s->number), s, s->number)) + return; + } + + // never reject player + if (s->number != sv.writeentitiestoclient_cliententitynumber) + { + // check various rejection conditions + if (s->nodrawtoclient == sv.writeentitiestoclient_cliententitynumber) + return; + if (s->drawonlytoclient && s->drawonlytoclient != sv.writeentitiestoclient_cliententitynumber) + return; + if (s->effects & EF_NODRAW) + return; + // LordHavoc: only send entities with a model or important effects + if (!s->modelindex && s->specialvisibilityradius == 0) + return; + + isbmodel = (model = SV_GetModelByIndex(s->modelindex)) != NULL && model->name[0] == '*'; + // viewmodels don't have visibility checking + if (s->viewmodelforclient) + { + if (s->viewmodelforclient != sv.writeentitiestoclient_cliententitynumber) + return; + } + else if (s->tagentity) + { + // tag attached entities simply check their parent + if (!sv.sendentitiesindex[s->tagentity]) + return; + SV_MarkWriteEntityStateToClient(sv.sendentitiesindex[s->tagentity]); + if (sv.sententities[s->tagentity] != sv.sententitiesmark) + return; + } + // always send world submodels in newer protocols because they don't + // generate much traffic (in old protocols they hog bandwidth) + // but only if sv_cullentities_nevercullbmodels is off + else if (!(s->effects & EF_NODEPTHTEST) && (!isbmodel || !sv_cullentities_nevercullbmodels.integer || sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE)) + { + // entity has survived every check so far, check if visible + ed = PRVM_EDICT_NUM(s->number); + + // if not touching a visible leaf + if (sv_cullentities_pvs.integer && !r_novis.integer && !r_trippy.integer && sv.writeentitiestoclient_pvsbytes) + { + if (ed->priv.server->pvs_numclusters < 0) + { + // entity too big for clusters list + if (sv.worldmodel && sv.worldmodel->brush.BoxTouchingPVS && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, sv.writeentitiestoclient_pvs, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) + { + sv.writeentitiestoclient_stats_culled_pvs++; + return; + } + } + else + { + int i; + // check cached clusters list + for (i = 0;i < ed->priv.server->pvs_numclusters;i++) + if (CHECKPVSBIT(sv.writeentitiestoclient_pvs, ed->priv.server->pvs_clusterlist[i])) + break; + if (i == ed->priv.server->pvs_numclusters) + { + sv.writeentitiestoclient_stats_culled_pvs++; + return; + } + } + } + + // or not seen by random tracelines + if (sv_cullentities_trace.integer && !isbmodel && sv.worldmodel->brush.TraceLineOfSight && !r_trippy.integer) + { + int samples = + s->number <= svs.maxclients + ? sv_cullentities_trace_samples_players.integer + : + s->specialvisibilityradius + ? sv_cullentities_trace_samples_extra.integer + : sv_cullentities_trace_samples.integer; + float enlarge = sv_cullentities_trace_enlarge.value; + + if(samples > 0) + { + int eyeindex; + for (eyeindex = 0;eyeindex < sv.writeentitiestoclient_numeyes;eyeindex++) + if(SV_CanSeeBox(samples, enlarge, sv.writeentitiestoclient_eyes[eyeindex], ed->priv.server->cullmins, ed->priv.server->cullmaxs)) + break; + if(eyeindex < sv.writeentitiestoclient_numeyes) + svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] = + realtime + ( + s->number <= svs.maxclients + ? sv_cullentities_trace_delay_players.value + : sv_cullentities_trace_delay.value + ); + else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number]) + { + sv.writeentitiestoclient_stats_culled_trace++; + return; + } + } + } + } + } + + // this just marks it for sending + // FIXME: it would be more efficient to send here, but the entity + // compressor isn't that flexible + sv.writeentitiestoclient_stats_visibleentities++; + sv.sententities[s->number] = sv.sententitiesmark; +} + +#if MAX_LEVELNETWORKEYES > 0 +#define MAX_EYE_RECURSION 1 // increase if recursion gets supported by portals +void SV_AddCameraEyes(void) +{ + int e, i, j, k; + prvm_edict_t *ed; + static int cameras[MAX_LEVELNETWORKEYES]; + static vec3_t camera_origins[MAX_LEVELNETWORKEYES]; + static int eye_levels[MAX_CLIENTNETWORKEYES]; + int n_cameras = 0; + vec3_t mi, ma; + + for(i = 0; i < sv.writeentitiestoclient_numeyes; ++i) + eye_levels[i] = 0; + + // check line of sight to portal entities and add them to PVS + for (e = 1, ed = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ed = PRVM_NEXT_EDICT(ed)) + { + if (!ed->priv.server->free) + { + if(PRVM_serveredictfunction(ed, camera_transform)) + { + PRVM_serverglobaledict(self) = e; + PRVM_serverglobaledict(other) = sv.writeentitiestoclient_cliententitynumber; + VectorCopy(sv.writeentitiestoclient_eyes[0], PRVM_serverglobalvector(trace_endpos)); + VectorCopy(sv.writeentitiestoclient_eyes[0], PRVM_G_VECTOR(OFS_PARM0)); + VectorClear(PRVM_G_VECTOR(OFS_PARM1)); + PRVM_ExecuteProgram(PRVM_serveredictfunction(ed, camera_transform), "QC function e.camera_transform is missing"); + if(!VectorCompare(PRVM_serverglobalvector(trace_endpos), sv.writeentitiestoclient_eyes[0])) + { + VectorCopy(PRVM_serverglobalvector(trace_endpos), camera_origins[n_cameras]); + cameras[n_cameras] = e; + ++n_cameras; + if(n_cameras >= MAX_LEVELNETWORKEYES) + break; + } + } + } + } + + if(!n_cameras) + return; + + // i is loop counter, is reset to 0 when an eye got added + // j is camera index to check + for(i = 0, j = 0; sv.writeentitiestoclient_numeyes < MAX_CLIENTNETWORKEYES && i < n_cameras; ++i, ++j, j %= n_cameras) + { + if(!cameras[j]) + continue; + ed = PRVM_EDICT_NUM(cameras[j]); + VectorAdd(PRVM_serveredictvector(ed, origin), PRVM_serveredictvector(ed, mins), mi); + VectorAdd(PRVM_serveredictvector(ed, origin), PRVM_serveredictvector(ed, maxs), ma); + for(k = 0; k < sv.writeentitiestoclient_numeyes; ++k) + if(eye_levels[k] <= MAX_EYE_RECURSION) + { + if(SV_CanSeeBox(sv_cullentities_trace_samples.integer, sv_cullentities_trace_enlarge.value, sv.writeentitiestoclient_eyes[k], mi, ma)) + { + eye_levels[sv.writeentitiestoclient_numeyes] = eye_levels[k] + 1; + VectorCopy(camera_origins[j], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); + // Con_Printf("added eye %d: %f %f %f because we can see %f %f %f .. %f %f %f from eye %d\n", j, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][0], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][1], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][2], mi[0], mi[1], mi[2], ma[0], ma[1], ma[2], k); + sv.writeentitiestoclient_numeyes++; + cameras[j] = 0; + i = 0; + break; + } + } + } +} +#else +void SV_AddCameraEyes(void) +{ +} +#endif + +void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int maxsize) +{ + qboolean need_empty = false; + int i, numsendstates, numcsqcsendstates; + entity_state_t *s; + prvm_edict_t *camera; + qboolean success; + vec3_t eye; + + // if there isn't enough space to accomplish anything, skip it + if (msg->cursize + 25 > maxsize) + return; + + sv.writeentitiestoclient_msg = msg; + sv.writeentitiestoclient_clientnumber = client - svs.clients; + + sv.writeentitiestoclient_stats_culled_pvs = 0; + sv.writeentitiestoclient_stats_culled_trace = 0; + sv.writeentitiestoclient_stats_visibleentities = 0; + sv.writeentitiestoclient_stats_totalentities = 0; + sv.writeentitiestoclient_numeyes = 0; + + // get eye location + sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes + camera = PRVM_EDICT_NUM( client->clientcamera ); + VectorAdd(PRVM_serveredictvector(camera, origin), PRVM_serveredictvector(clent, view_ofs), eye); + sv.writeentitiestoclient_pvsbytes = 0; + // get the PVS values for the eye location, later FatPVS calls will merge + if (sv.worldmodel && sv.worldmodel->brush.FatPVS) + sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, eye, 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), sv.writeentitiestoclient_pvsbytes != 0); + + // add the eye to a list for SV_CanSeeBox tests + VectorCopy(eye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); + sv.writeentitiestoclient_numeyes++; + + // calculate predicted eye origin for SV_CanSeeBox tests + if (sv_cullentities_trace_prediction.integer) + { + vec_t predtime = bound(0, host_client->ping, sv_cullentities_trace_prediction_time.value); + vec3_t predeye; + VectorMA(eye, predtime, PRVM_serveredictvector(camera, velocity), predeye); + if (SV_CanSeeBox(1, 0, eye, predeye, predeye)) + { + VectorCopy(predeye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); + sv.writeentitiestoclient_numeyes++; + } + //if (!sv.writeentitiestoclient_useprediction) + // Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n"); + } + + SV_AddCameraEyes(); + + // build PVS from the new eyes + if (sv.worldmodel && sv.worldmodel->brush.FatPVS) + for(i = 1; i < sv.writeentitiestoclient_numeyes; ++i) + sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, sv.writeentitiestoclient_eyes[i], 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), sv.writeentitiestoclient_pvsbytes != 0); + + sv.sententitiesmark++; + + for (i = 0;i < sv.numsendentities;i++) + SV_MarkWriteEntityStateToClient(sv.sendentities + i); + + numsendstates = 0; + numcsqcsendstates = 0; + for (i = 0;i < sv.numsendentities;i++) + { + s = &sv.sendentities[i]; + if (sv.sententities[s->number] == sv.sententitiesmark) + { + if(s->active == ACTIVE_NETWORK) + { + if (s->exteriormodelforclient) + { + if (s->exteriormodelforclient == sv.writeentitiestoclient_cliententitynumber) + s->flags |= RENDER_EXTERIORMODEL; + else + s->flags &= ~RENDER_EXTERIORMODEL; + } + sv.writeentitiestoclient_sendstates[numsendstates++] = s; + } + else if(sv.sendentities[i].active == ACTIVE_SHARED) + sv.writeentitiestoclient_csqcsendstates[numcsqcsendstates++] = s->number; + else + Con_Printf("entity %d is in sv.sendentities and marked, but not active, please breakpoint me\n", s->number); + } + } + + if (sv_cullentities_stats.integer) + Con_Printf("client \"%s\" entities: %d total, %d visible, %d culled by: %d pvs %d trace\n", client->name, sv.writeentitiestoclient_stats_totalentities, sv.writeentitiestoclient_stats_visibleentities, sv.writeentitiestoclient_stats_culled_pvs + sv.writeentitiestoclient_stats_culled_trace, sv.writeentitiestoclient_stats_culled_pvs, sv.writeentitiestoclient_stats_culled_trace); + + if(client->entitydatabase5) + need_empty = EntityFrameCSQC_WriteFrame(msg, maxsize, numcsqcsendstates, sv.writeentitiestoclient_csqcsendstates, client->entitydatabase5->latestframenum + 1); + else + EntityFrameCSQC_WriteFrame(msg, maxsize, numcsqcsendstates, sv.writeentitiestoclient_csqcsendstates, 0); + + if(client->num_skippedentityframes >= 10) + need_empty = true; // force every 10th frame to be not empty (or cl_movement replay takes too long) + + if (client->entitydatabase5) + success = EntityFrame5_WriteFrame(msg, maxsize, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence, need_empty); + else if (client->entitydatabase4) + { + success = EntityFrame4_WriteFrame(msg, maxsize, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates); + Protocol_WriteStatsReliable(); + } + else if (client->entitydatabase) + { + success = EntityFrame_WriteFrame(msg, maxsize, client->entitydatabase, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1); + Protocol_WriteStatsReliable(); + } + else + { + success = EntityFrameQuake_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates); + Protocol_WriteStatsReliable(); + } + + if(success) + client->num_skippedentityframes = 0; + else + ++client->num_skippedentityframes; +} + +/* +============= +SV_CleanupEnts + +============= +*/ +static void SV_CleanupEnts (void) +{ + int e; + prvm_edict_t *ent; + + ent = PRVM_NEXT_EDICT(prog->edicts); + for (e=1 ; enum_edicts ; e++, ent = PRVM_NEXT_EDICT(ent)) + PRVM_serveredictfloat(ent, effects) = (int)PRVM_serveredictfloat(ent, effects) & ~EF_MUZZLEFLASH; +} + +/* +================== +SV_WriteClientdataToMessage + +================== +*/ +void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats) +{ + int bits; + int i; + prvm_edict_t *other; + int items; + vec3_t punchvector; + int viewzoom; + const char *s; + float *statsf = (float *)stats; + float gravity; + +// +// send a damage message +// + if (PRVM_serveredictfloat(ent, dmg_take) || PRVM_serveredictfloat(ent, dmg_save)) + { + other = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, dmg_inflictor)); + MSG_WriteByte (msg, svc_damage); + MSG_WriteByte (msg, (int)PRVM_serveredictfloat(ent, dmg_save)); + MSG_WriteByte (msg, (int)PRVM_serveredictfloat(ent, dmg_take)); + for (i=0 ; i<3 ; i++) + MSG_WriteCoord (msg, PRVM_serveredictvector(other, origin)[i] + 0.5*(PRVM_serveredictvector(other, mins)[i] + PRVM_serveredictvector(other, maxs)[i]), sv.protocol); + + PRVM_serveredictfloat(ent, dmg_take) = 0; + PRVM_serveredictfloat(ent, dmg_save) = 0; + } + +// +// send the current viewpos offset from the view entity +// + SV_SetIdealPitch (); // how much to look up / down ideally + +// a fixangle might get lost in a dropped packet. Oh well. + if(PRVM_serveredictfloat(ent, fixangle)) + { + // angle fixing was requested by global thinking code... + // so store the current angles for later use + memcpy(host_client->fixangle_angles, PRVM_serveredictvector(ent, angles), sizeof(host_client->fixangle_angles)); + host_client->fixangle_angles_set = TRUE; + + // and clear fixangle for the next frame + PRVM_serveredictfloat(ent, fixangle) = 0; + } + + if (host_client->fixangle_angles_set) + { + MSG_WriteByte (msg, svc_setangle); + for (i=0 ; i < 3 ; i++) + MSG_WriteAngle (msg, host_client->fixangle_angles[i], sv.protocol); + host_client->fixangle_angles_set = FALSE; + } + + // stuff the sigil bits into the high bits of items for sbar, or else + // mix in items2 + if (gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) + items = (int)PRVM_serveredictfloat(ent, items) | ((int)PRVM_serveredictfloat(ent, items2) << 23); + else + items = (int)PRVM_serveredictfloat(ent, items) | ((int)PRVM_serverglobalfloat(serverflags) << 28); + + VectorCopy(PRVM_serveredictvector(ent, punchvector), punchvector); + + // cache weapon model name and index in client struct to save time + // (this search can be almost 1% of cpu time!) + s = PRVM_GetString(PRVM_serveredictstring(ent, weaponmodel)); + if (strcmp(s, client->weaponmodel)) + { + strlcpy(client->weaponmodel, s, sizeof(client->weaponmodel)); + client->weaponmodelindex = SV_ModelIndex(s, 1); + } + + viewzoom = (int)(PRVM_serveredictfloat(ent, viewzoom) * 255.0f); + if (viewzoom == 0) + viewzoom = 255; + + bits = 0; + + if ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) + bits |= SU_ONGROUND; + if (PRVM_serveredictfloat(ent, waterlevel) >= 2) + bits |= SU_INWATER; + if (PRVM_serveredictfloat(ent, idealpitch)) + bits |= SU_IDEALPITCH; + + for (i=0 ; i<3 ; i++) + { + if (PRVM_serveredictvector(ent, punchangle)[i]) + bits |= (SU_PUNCH1<weaponmodelindex; + stats[STAT_HEALTH] = (int)PRVM_serveredictfloat(ent, health); + stats[STAT_AMMO] = (int)PRVM_serveredictfloat(ent, currentammo); + stats[STAT_SHELLS] = (int)PRVM_serveredictfloat(ent, ammo_shells); + stats[STAT_NAILS] = (int)PRVM_serveredictfloat(ent, ammo_nails); + stats[STAT_ROCKETS] = (int)PRVM_serveredictfloat(ent, ammo_rockets); + stats[STAT_CELLS] = (int)PRVM_serveredictfloat(ent, ammo_cells); + stats[STAT_ACTIVEWEAPON] = (int)PRVM_serveredictfloat(ent, weapon); + stats[STAT_VIEWZOOM] = viewzoom; + stats[STAT_TOTALSECRETS] = (int)PRVM_serverglobalfloat(total_secrets); + stats[STAT_TOTALMONSTERS] = (int)PRVM_serverglobalfloat(total_monsters); + // the QC bumps these itself by sending svc_'s, so we have to keep them + // zero or they'll be corrected by the engine + //stats[STAT_SECRETS] = PRVM_serverglobalfloat(found_secrets); + //stats[STAT_MONSTERS] = PRVM_serverglobalfloat(killed_monsters); + + // movement settings for prediction + // note: these are not sent in protocols with lower MAX_CL_STATS limits + stats[STAT_MOVEFLAGS] = MOVEFLAG_VALID + | (sv_gameplayfix_q2airaccelerate.integer ? MOVEFLAG_Q2AIRACCELERATE : 0) + | (sv_gameplayfix_nogravityonground.integer ? MOVEFLAG_NOGRAVITYONGROUND : 0) + | (sv_gameplayfix_gravityunaffectedbyticrate.integer ? MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE : 0) + ; + statsf[STAT_MOVEVARS_TICRATE] = sys_ticrate.value; + statsf[STAT_MOVEVARS_TIMESCALE] = slowmo.value; + statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value; + statsf[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value; + statsf[STAT_MOVEVARS_MAXSPEED] = sv_maxspeed.value; + statsf[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_maxspeed.value; // FIXME: QW has a separate cvar for this + statsf[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value; + statsf[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value >= 0 ? sv_airaccelerate.value : sv_accelerate.value; + statsf[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value >= 0 ? sv_wateraccelerate.value : sv_accelerate.value; + statsf[STAT_MOVEVARS_ENTGRAVITY] = gravity; + statsf[STAT_MOVEVARS_JUMPVELOCITY] = sv_jumpvelocity.value; + statsf[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value; + statsf[STAT_MOVEVARS_MAXAIRSPEED] = sv_maxairspeed.value; + statsf[STAT_MOVEVARS_STEPHEIGHT] = sv_stepheight.value; + statsf[STAT_MOVEVARS_AIRACCEL_QW] = sv_airaccel_qw.value; + statsf[STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR] = sv_airaccel_qw_stretchfactor.value; + statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_airaccel_sideways_friction.value; + statsf[STAT_MOVEVARS_FRICTION] = sv_friction.value; + statsf[STAT_MOVEVARS_WATERFRICTION] = sv_waterfriction.value >= 0 ? sv_waterfriction.value : sv_friction.value; + statsf[STAT_MOVEVARS_AIRSTOPACCELERATE] = sv_airstopaccelerate.value; + statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE] = sv_airstrafeaccelerate.value; + statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED] = sv_maxairstrafespeed.value; + statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW] = sv_airstrafeaccel_qw.value; + statsf[STAT_MOVEVARS_AIRCONTROL] = sv_aircontrol.value; + statsf[STAT_MOVEVARS_AIRCONTROL_POWER] = sv_aircontrol_power.value; + statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY] = sv_aircontrol_penalty.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL] = sv_warsowbunny_airforwardaccel.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL] = sv_warsowbunny_accel.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED] = sv_warsowbunny_topspeed.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL] = sv_warsowbunny_turnaccel.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO] = sv_warsowbunny_backtosideratio.value; + statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW] = sv_airspeedlimit_nonqw.value; + statsf[STAT_FRAGLIMIT] = fraglimit.value; + statsf[STAT_TIMELIMIT] = timelimit.value; + + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) + { + if (stats[STAT_VIEWHEIGHT] != DEFAULT_VIEWHEIGHT) bits |= SU_VIEWHEIGHT; + bits |= SU_ITEMS; + if (stats[STAT_WEAPONFRAME]) bits |= SU_WEAPONFRAME; + if (stats[STAT_ARMOR]) bits |= SU_ARMOR; + bits |= SU_WEAPON; + // FIXME: which protocols support this? does PROTOCOL_DARKPLACES3 support viewzoom? + if (sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) + if (viewzoom != 255) + bits |= SU_VIEWZOOM; + } + + if (bits >= 65536) + bits |= SU_EXTEND1; + if (bits >= 16777216) + bits |= SU_EXTEND2; + + // send the data + MSG_WriteByte (msg, svc_clientdata); + MSG_WriteShort (msg, bits); + if (bits & SU_EXTEND1) + MSG_WriteByte(msg, bits >> 16); + if (bits & SU_EXTEND2) + MSG_WriteByte(msg, bits >> 24); + + if (bits & SU_VIEWHEIGHT) + MSG_WriteChar (msg, stats[STAT_VIEWHEIGHT]); + + if (bits & SU_IDEALPITCH) + MSG_WriteChar (msg, (int)PRVM_serveredictfloat(ent, idealpitch)); + + for (i=0 ; i<3 ; i++) + { + if (bits & (SU_PUNCH1<spawned || !client->netconnection || client->unreliablemsg.cursize + sv.datagram.cursize > client->unreliablemsg.maxsize || client->unreliablemsg_splitpoints >= (int)(sizeof(client->unreliablemsg_splitpoint)/sizeof(client->unreliablemsg_splitpoint[0]))) + continue; + SZ_Write(&client->unreliablemsg, sv.datagram.data, sv.datagram.cursize); + client->unreliablemsg_splitpoint[client->unreliablemsg_splitpoints++] = client->unreliablemsg.cursize; + } + SZ_Clear(&sv.datagram); +} + +static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg, int maxsize, int maxsize2) +{ + // scan the splitpoints to find out how many we can fit in + int numsegments, j, split; + if (!client->unreliablemsg_splitpoints) + return; + // always accept the first one if it's within 1024 bytes, this ensures + // that very big datagrams which are over the rate limit still get + // through, just to keep it working + for (numsegments = 1;numsegments < client->unreliablemsg_splitpoints;numsegments++) + if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > maxsize) + break; + // the first segment gets an exemption from the rate limiting, otherwise + // it could get dropped consistently due to a low rate limit + if (numsegments == 1) + maxsize = maxsize2; + // some will fit, so add the ones that will fit + split = client->unreliablemsg_splitpoint[numsegments-1]; + // note this discards ones that were accepted by the segments scan but + // can not fit, such as a really huge first one that will never ever + // fit in a packet... + if (msg->cursize + split <= maxsize) + SZ_Write(msg, client->unreliablemsg.data, split); + // remove the part we sent, keeping any remaining data + client->unreliablemsg.cursize -= split; + if (client->unreliablemsg.cursize > 0) + memmove(client->unreliablemsg.data, client->unreliablemsg.data + split, client->unreliablemsg.cursize); + // adjust remaining splitpoints + client->unreliablemsg_splitpoints -= numsegments; + for (j = 0;j < client->unreliablemsg_splitpoints;j++) + client->unreliablemsg_splitpoint[j] = client->unreliablemsg_splitpoint[numsegments + j] - split; +} + +/* +======================= +SV_SendClientDatagram +======================= +*/ +static void SV_SendClientDatagram (client_t *client) +{ + int clientrate, maxrate, maxsize, maxsize2, downloadsize; + sizebuf_t msg; + int stats[MAX_CL_STATS]; + static unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; + + // obey rate limit by limiting packet frequency if the packet size + // limiting fails + // (usually this is caused by reliable messages) + if (!NetConn_CanSend(client->netconnection)) + return; + + // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates + maxrate = max(NET_MINRATE, sv_maxrate.integer); + if (sv_maxrate.integer != maxrate) + Cvar_SetValueQuick(&sv_maxrate, maxrate); + + // clientrate determines the 'cleartime' of a packet + // (how long to wait before sending another, based on this packet's size) + clientrate = bound(NET_MINRATE, client->rate, maxrate); + + switch (sv.protocol) + { + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + case PROTOCOL_NEHAHRAMOVIE: + case PROTOCOL_NEHAHRABJP: + case PROTOCOL_NEHAHRABJP2: + case PROTOCOL_NEHAHRABJP3: + case PROTOCOL_QUAKEWORLD: + // no packet size limit support on Quake protocols because it just + // causes missing entities/effects + // packets are simply sent less often to obey the rate limit + maxsize = 1024; + maxsize2 = 1024; + break; + case PROTOCOL_DARKPLACES1: + case PROTOCOL_DARKPLACES2: + case PROTOCOL_DARKPLACES3: + case PROTOCOL_DARKPLACES4: + // no packet size limit support on DP1-4 protocols because they kick + // the client off if they overflow, and miss effects + // packets are simply sent less often to obey the rate limit + maxsize = sizeof(sv_sendclientdatagram_buf); + maxsize2 = sizeof(sv_sendclientdatagram_buf); + break; + default: + // DP5 and later protocols support packet size limiting which is a + // better method than limiting packet frequency as QW does + // + // at very low rates (or very small sys_ticrate) the packet size is + // not reduced below 128, but packets may be sent less often + maxsize = (int)(clientrate * sys_ticrate.value); + maxsize = bound(128, maxsize, 1400); + maxsize2 = 1400; + // csqc entities can easily exceed 128 bytes, so disable throttling in + // mods that use csqc (they are likely to use less bandwidth anyway) + if (sv.csqc_progsize > 0) + maxsize = maxsize2; + break; + } + + if (LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP && !sv_ratelimitlocalplayer.integer) + { + // for good singleplayer, send huge packets + maxsize = sizeof(sv_sendclientdatagram_buf); + maxsize2 = sizeof(sv_sendclientdatagram_buf); + // never limit frequency in singleplayer + clientrate = 1000000000; + } + + // while downloading, limit entity updates to half the packet + // (any leftover space will be used for downloading) + if (host_client->download_file) + maxsize /= 2; + + msg.data = sv_sendclientdatagram_buf; + msg.maxsize = sizeof(sv_sendclientdatagram_buf); + msg.cursize = 0; + msg.allowoverflow = false; + + if (host_client->spawned) + { + // the player is in the game + MSG_WriteByte (&msg, svc_time); + MSG_WriteFloat (&msg, sv.time); + + // add the client specific data to the datagram + SV_WriteClientdataToMessage (client, client->edict, &msg, stats); + // now update the stats[] array using any registered custom fields + VM_SV_UpdateCustomStats (client, client->edict, &msg, stats); + // set host_client->statsdeltabits + Protocol_UpdateClientStats (stats); + + // add as many queued unreliable messages (effects) as we can fit + // limit effects to half of the remaining space + if (client->unreliablemsg.cursize) + SV_WriteUnreliableMessages (client, &msg, maxsize/2, maxsize2); + + // now write as many entities as we can fit, and also sends stats + SV_WriteEntitiesToClient (client, client->edict, &msg, maxsize); + } + else if (realtime > client->keepalivetime) + { + // the player isn't totally in the game yet + // send small keepalive messages if too much time has passed + // (may also be sending downloads) + client->keepalivetime = realtime + 5; + MSG_WriteChar (&msg, svc_nop); + } + + // if a download is active, see if there is room to fit some download data + // in this packet + downloadsize = min(maxsize*2,maxsize2) - msg.cursize - 7; + if (host_client->download_file && host_client->download_started && downloadsize > 0) + { + fs_offset_t downloadstart; + unsigned char data[1400]; + downloadstart = FS_Tell(host_client->download_file); + downloadsize = min(downloadsize, (int)sizeof(data)); + downloadsize = FS_Read(host_client->download_file, data, downloadsize); + // note this sends empty messages if at the end of the file, which is + // necessary to keep the packet loss logic working + // (the last blocks may be lost and need to be re-sent, and that will + // only occur if the client acks the empty end messages, revealing + // a gap in the download progress, causing the last blocks to be + // sent again) + MSG_WriteChar (&msg, svc_downloaddata); + MSG_WriteLong (&msg, downloadstart); + MSG_WriteShort (&msg, downloadsize); + if (downloadsize > 0) + SZ_Write (&msg, data, downloadsize); + } + + // reliable only if none is in progress + if(client->sendsignon != 2 && !client->netconnection->sendMessageLength) + SV_WriteDemoMessage(client, &(client->netconnection->message), false); + // unreliable + SV_WriteDemoMessage(client, &msg, false); + +// send the datagram + NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate, client->sendsignon == 2); + if (client->sendsignon == 1 && !client->netconnection->message.cursize) + client->sendsignon = 2; // prevent reliable until client sends prespawn (this is the keepalive phase) +} + +/* +======================= +SV_UpdateToReliableMessages +======================= +*/ +static void SV_UpdateToReliableMessages (void) +{ + int i, j; + client_t *client; + const char *name; + const char *model; + const char *skin; + int clientcamera; + +// check for changes to be sent over the reliable streams + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + // update the host_client fields we care about according to the entity fields + host_client->edict = PRVM_EDICT_NUM(i+1); + + // DP_SV_CLIENTNAME + name = PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)); + if (name == NULL) + name = ""; + // always point the string back at host_client->name to keep it safe + strlcpy (host_client->name, name, sizeof (host_client->name)); + PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(host_client->name); + if (strcmp(host_client->old_name, host_client->name)) + { + if (host_client->spawned) + SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name); + strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name)); + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatename); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteString (&sv.reliable_datagram, host_client->name); + SV_WriteNetnameIntoDemo(host_client); + } + + // DP_SV_CLIENTCOLORS + host_client->colors = (int)PRVM_serveredictfloat(host_client->edict, clientcolors); + if (host_client->old_colors != host_client->colors) + { + host_client->old_colors = host_client->colors; + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteByte (&sv.reliable_datagram, host_client->colors); + } + + // NEXUIZ_PLAYERMODEL + model = PRVM_GetString(PRVM_serveredictstring(host_client->edict, playermodel)); + if (model == NULL) + model = ""; + // always point the string back at host_client->name to keep it safe + strlcpy (host_client->playermodel, model, sizeof (host_client->playermodel)); + PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(host_client->playermodel); + + // NEXUIZ_PLAYERSKIN + skin = PRVM_GetString(PRVM_serveredictstring(host_client->edict, playerskin)); + if (skin == NULL) + skin = ""; + // always point the string back at host_client->name to keep it safe + strlcpy (host_client->playerskin, skin, sizeof (host_client->playerskin)); + PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(host_client->playerskin); + + // TODO: add an extension name for this [1/17/2008 Black] + clientcamera = PRVM_serveredictedict(host_client->edict, clientcamera); + if (clientcamera > 0) + { + int oldclientcamera = host_client->clientcamera; + if (clientcamera >= prog->max_edicts || PRVM_EDICT_NUM(clientcamera)->priv.required->free) + clientcamera = PRVM_NUM_FOR_EDICT(host_client->edict); + host_client->clientcamera = clientcamera; + + if (oldclientcamera != host_client->clientcamera) + { + MSG_WriteByte(&host_client->netconnection->message, svc_setview); + MSG_WriteShort(&host_client->netconnection->message, host_client->clientcamera); + } + } + + // frags + host_client->frags = (int)PRVM_serveredictfloat(host_client->edict, frags); + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + if(!host_client->spawned && host_client->netconnection) + host_client->frags = -666; + if (host_client->old_frags != host_client->frags) + { + host_client->old_frags = host_client->frags; + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatefrags); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteShort (&sv.reliable_datagram, host_client->frags); + } + } + + for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++) + if (client->netconnection && (client->spawned || client->clientconnectcalled)) // also send MSG_ALL to people who are past ClientConnect, but not spawned yet + SZ_Write (&client->netconnection->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize); + + SZ_Clear (&sv.reliable_datagram); +} + + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages (void) +{ + int i, prepared = false; + + if (sv.protocol == PROTOCOL_QUAKEWORLD) + Sys_Error("SV_SendClientMessages: no quakeworld support\n"); + + SV_FlushBroadcastMessages(); + +// update frags, names, etc + SV_UpdateToReliableMessages(); + +// build individual updates + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + if (!host_client->active) + continue; + if (!host_client->netconnection) + continue; + + if (host_client->netconnection->message.overflowed) + { + SV_DropClient (true); // if the message couldn't send, kick off + continue; + } + + if (!prepared) + { + prepared = true; + // only prepare entities once per frame + SV_PrepareEntitiesForSending(); + } + SV_SendClientDatagram (host_client); + } + +// clear muzzle flashes + SV_CleanupEnts(); +} + +static void SV_StartDownload_f(void) +{ + if (host_client->download_file) + host_client->download_started = true; +} + +/* + * Compression extension negotiation: + * + * Server to client: + * cl_serverextension_download 2 + * + * Client to server: + * download + * e.g. + * download maps/map1.bsp lzo deflate huffman + * + * Server to client: + * cl_downloadbegin + * e.g. + * cl_downloadbegin 123456 maps/map1.bsp deflate + * + * The server may choose not to compress the file by sending no compression name, like: + * cl_downloadbegin 345678 maps/map1.bsp + * + * NOTE: the "download" command may only specify compression algorithms if + * cl_serverextension_download is 2! + * If cl_serverextension_download has a different value, the client must + * assume this extension is not supported! + */ + +static void Download_CheckExtensions(void) +{ + int i; + int argc = Cmd_Argc(); + + // first reset them all + host_client->download_deflate = false; + + for(i = 2; i < argc; ++i) + { + if(!strcmp(Cmd_Argv(i), "deflate")) + { + host_client->download_deflate = true; + break; + } + } +} + +static void SV_Download_f(void) +{ + const char *whichpack, *whichpack2, *extension; + qboolean is_csqc; // so we need to check only once + + if (Cmd_Argc() < 2) + { + SV_ClientPrintf("usage: download {}*\n"); + SV_ClientPrintf(" supported extensions: deflate\n"); + return; + } + + if (FS_CheckNastyPath(Cmd_Argv(1), false)) + { + SV_ClientPrintf("Download rejected: nasty filename \"%s\"\n", Cmd_Argv(1)); + return; + } + + if (host_client->download_file) + { + // at this point we'll assume the previous download should be aborted + Con_DPrintf("Download of %s aborted by %s starting a new download\n", host_client->download_name, host_client->name); + Host_ClientCommands("\nstopdownload\n"); + + // close the file and reset variables + FS_Close(host_client->download_file); + host_client->download_file = NULL; + host_client->download_name[0] = 0; + host_client->download_expectedposition = 0; + host_client->download_started = false; + } + + is_csqc = (sv.csqc_progname[0] && strcmp(Cmd_Argv(1), sv.csqc_progname) == 0); + + if (!sv_allowdownloads.integer && !is_csqc) + { + SV_ClientPrintf("Downloads are disabled on this server\n"); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + Download_CheckExtensions(); + + strlcpy(host_client->download_name, Cmd_Argv(1), sizeof(host_client->download_name)); + extension = FS_FileExtension(host_client->download_name); + + // host_client is asking to download a specified file + if (developer_extra.integer) + Con_DPrintf("Download request for %s by %s\n", host_client->download_name, host_client->name); + + if(is_csqc) + { + char extensions[MAX_QPATH]; // make sure this can hold all extensions + extensions[0] = '\0'; + + if(host_client->download_deflate) + strlcat(extensions, " deflate", sizeof(extensions)); + + Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name); + + if(host_client->download_deflate && svs.csqc_progdata_deflated) + host_client->download_file = FS_FileFromData(svs.csqc_progdata_deflated, svs.csqc_progsize_deflated, true); + else + host_client->download_file = FS_FileFromData(svs.csqc_progdata, sv.csqc_progsize, true); + + // no, no space is needed between %s and %s :P + Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions); + + host_client->download_expectedposition = 0; + host_client->download_started = false; + host_client->sendsignon = true; // make sure this message is sent + return; + } + + if (!FS_FileExists(host_client->download_name)) + { + SV_ClientPrintf("Download rejected: server does not have the file \"%s\"\nYou may need to separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + // check if the user is trying to download part of registered Quake(r) + whichpack = FS_WhichPack(host_client->download_name); + whichpack2 = FS_WhichPack("gfx/pop.lmp"); + if ((whichpack && whichpack2 && !strcasecmp(whichpack, whichpack2)) || FS_IsRegisteredQuakePack(host_client->download_name)) + { + SV_ClientPrintf("Download rejected: file \"%s\" is part of registered Quake(r)\nYou must purchase Quake(r) from id Software or a retailer to get this file\nPlease go to http://www.idsoftware.com/games/quake/quake/index.php?game_section=buy\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + // check if the server has forbidden archive downloads entirely + if (!sv_allowdownloads_inarchive.integer) + { + whichpack = FS_WhichPack(host_client->download_name); + if (whichpack) + { + SV_ClientPrintf("Download rejected: file \"%s\" is in an archive (\"%s\")\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name, whichpack); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_config.integer) + { + if (!strcasecmp(extension, "cfg")) + { + SV_ClientPrintf("Download rejected: file \"%s\" is a .cfg file which is forbidden for security reasons\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_dlcache.integer) + { + if (!strncasecmp(host_client->download_name, "dlcache/", 8)) + { + SV_ClientPrintf("Download rejected: file \"%s\" is in the dlcache/ directory which is forbidden for security reasons\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_archive.integer) + { + if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) + { + SV_ClientPrintf("Download rejected: file \"%s\" is an archive\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + host_client->download_file = FS_OpenVirtualFile(host_client->download_name, true); + if (!host_client->download_file) + { + SV_ClientPrintf("Download rejected: server could not open the file \"%s\"\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + if (FS_FileSize(host_client->download_file) > 1<<30) + { + SV_ClientPrintf("Download rejected: file \"%s\" is very large\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + return; + } + + if (FS_FileSize(host_client->download_file) < 0) + { + SV_ClientPrintf("Download rejected: file \"%s\" is not a regular file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + return; + } + + Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name); + + /* + * we can only do this if we would actually deflate on the fly + * which we do not (yet)! + { + char extensions[MAX_QPATH]; // make sure this can hold all extensions + extensions[0] = '\0'; + + if(host_client->download_deflate) + strlcat(extensions, " deflate", sizeof(extensions)); + + // no, no space is needed between %s and %s :P + Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions); + } + */ + Host_ClientCommands("\ncl_downloadbegin %i %s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name); + + host_client->download_expectedposition = 0; + host_client->download_started = false; + host_client->sendsignon = true; // make sure this message is sent + + // the rest of the download process is handled in SV_SendClientDatagram + // and other code dealing with svc_downloaddata and clc_ackdownloaddata + // + // no svc_downloaddata messages will be sent until sv_startdownload is + // sent by the client +} + +/* +============================================================================== + +SERVER SPAWNING + +============================================================================== +*/ + +/* +================ +SV_ModelIndex + +================ +*/ +int SV_ModelIndex(const char *s, int precachemode) +{ + int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) ? 256 : MAX_MODELS); + char filename[MAX_QPATH]; + if (!s || !*s) + return 0; + // testing + //if (precachemode == 2) + // return 0; + strlcpy(filename, s, sizeof(filename)); + for (i = 2;i < limit;i++) + { + if (!sv.model_precache[i][0]) + { + if (precachemode) + { + if (sv.state != ss_loading && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5)) + { + Con_Printf("SV_ModelIndex(\"%s\"): precache_model can only be done in spawn functions\n", filename); + return 0; + } + if (precachemode == 1) + Con_Printf("SV_ModelIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename); + strlcpy(sv.model_precache[i], filename, sizeof(sv.model_precache[i])); + sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL); + if (sv.state != ss_loading) + { + MSG_WriteByte(&sv.reliable_datagram, svc_precache); + MSG_WriteShort(&sv.reliable_datagram, i); + MSG_WriteString(&sv.reliable_datagram, filename); + } + return i; + } + Con_Printf("SV_ModelIndex(\"%s\"): not precached\n", filename); + return 0; + } + if (!strcmp(sv.model_precache[i], filename)) + return i; + } + Con_Printf("SV_ModelIndex(\"%s\"): i (%i) == MAX_MODELS (%i)\n", filename, i, MAX_MODELS); + return 0; +} + +/* +================ +SV_SoundIndex + +================ +*/ +int SV_SoundIndex(const char *s, int precachemode) +{ + int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) ? 256 : MAX_SOUNDS); + char filename[MAX_QPATH]; + if (!s || !*s) + return 0; + // testing + //if (precachemode == 2) + // return 0; + strlcpy(filename, s, sizeof(filename)); + for (i = 1;i < limit;i++) + { + if (!sv.sound_precache[i][0]) + { + if (precachemode) + { + if (sv.state != ss_loading && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5)) + { + Con_Printf("SV_SoundIndex(\"%s\"): precache_sound can only be done in spawn functions\n", filename); + return 0; + } + if (precachemode == 1) + Con_Printf("SV_SoundIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename); + strlcpy(sv.sound_precache[i], filename, sizeof(sv.sound_precache[i])); + if (sv.state != ss_loading) + { + MSG_WriteByte(&sv.reliable_datagram, svc_precache); + MSG_WriteShort(&sv.reliable_datagram, i + 32768); + MSG_WriteString(&sv.reliable_datagram, filename); + } + return i; + } + Con_Printf("SV_SoundIndex(\"%s\"): not precached\n", filename); + return 0; + } + if (!strcmp(sv.sound_precache[i], filename)) + return i; + } + Con_Printf("SV_SoundIndex(\"%s\"): i (%i) == MAX_SOUNDS (%i)\n", filename, i, MAX_SOUNDS); + return 0; +} + +/* +================ +SV_ParticleEffectIndex + +================ +*/ +int SV_ParticleEffectIndex(const char *name) +{ + int i, argc, linenumber, effectnameindex; + int filepass; + fs_offset_t filesize; + unsigned char *filedata; + const char *text; + const char *textstart; + //const char *textend; + char argv[16][1024]; + char filename[MAX_QPATH]; + if (!sv.particleeffectnamesloaded) + { + sv.particleeffectnamesloaded = true; + memset(sv.particleeffectname, 0, sizeof(sv.particleeffectname)); + for (i = 0;i < EFFECT_TOTAL;i++) + strlcpy(sv.particleeffectname[i], standardeffectnames[i], sizeof(sv.particleeffectname[i])); + for (filepass = 0;;filepass++) + { + if (filepass == 0) + dpsnprintf(filename, sizeof(filename), "effectinfo.txt"); + else if (filepass == 1) + dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", sv.worldnamenoextension); + else + break; + filedata = FS_LoadFile(filename, tempmempool, true, &filesize); + if (!filedata) + continue; + textstart = (const char *)filedata; + //textend = (const char *)filedata + filesize; + text = textstart; + for (linenumber = 1;;linenumber++) + { + argc = 0; + for (;;) + { + if (!COM_ParseToken_Simple(&text, true, false) || !strcmp(com_token, "\n")) + break; + if (argc < 16) + { + strlcpy(argv[argc], com_token, sizeof(argv[argc])); + argc++; + } + } + if (com_token[0] == 0) + break; // if the loop exited and it's not a \n, it's EOF + if (argc < 1) + continue; + if (!strcmp(argv[0], "effect")) + { + if (argc == 2) + { + for (effectnameindex = 1;effectnameindex < SV_MAX_PARTICLEEFFECTNAME;effectnameindex++) + { + if (sv.particleeffectname[effectnameindex][0]) + { + if (!strcmp(sv.particleeffectname[effectnameindex], argv[1])) + break; + } + else + { + strlcpy(sv.particleeffectname[effectnameindex], argv[1], sizeof(sv.particleeffectname[effectnameindex])); + break; + } + } + // if we run out of names, abort + if (effectnameindex == SV_MAX_PARTICLEEFFECTNAME) + { + Con_Printf("%s:%i: too many effects!\n", filename, linenumber); + break; + } + } + } + } + Mem_Free(filedata); + } + } + // search for the name + for (effectnameindex = 1;effectnameindex < SV_MAX_PARTICLEEFFECTNAME && sv.particleeffectname[effectnameindex][0];effectnameindex++) + if (!strcmp(sv.particleeffectname[effectnameindex], name)) + return effectnameindex; + // return 0 if we couldn't find it + return 0; +} + +dp_model_t *SV_GetModelByIndex(int modelindex) +{ + return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL; +} + +dp_model_t *SV_GetModelFromEdict(prvm_edict_t *ed) +{ + int modelindex; + if (!ed || ed->priv.server->free) + return NULL; + modelindex = (int)PRVM_serveredictfloat(ed, modelindex); + return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL; +} + +/* +================ +SV_CreateBaseline + +================ +*/ +static void SV_CreateBaseline (void) +{ + int i, entnum, large; + prvm_edict_t *svent; + + // LordHavoc: clear *all* states (note just active ones) + for (entnum = 0;entnum < prog->max_edicts;entnum++) + { + // get the current server version + svent = PRVM_EDICT_NUM(entnum); + + // LordHavoc: always clear state values, whether the entity is in use or not + svent->priv.server->baseline = defaultstate; + + if (svent->priv.server->free) + continue; + if (entnum > svs.maxclients && !PRVM_serveredictfloat(svent, modelindex)) + continue; + + // create entity baseline + VectorCopy (PRVM_serveredictvector(svent, origin), svent->priv.server->baseline.origin); + VectorCopy (PRVM_serveredictvector(svent, angles), svent->priv.server->baseline.angles); + svent->priv.server->baseline.frame = (int)PRVM_serveredictfloat(svent, frame); + svent->priv.server->baseline.skin = (int)PRVM_serveredictfloat(svent, skin); + if (entnum > 0 && entnum <= svs.maxclients) + { + svent->priv.server->baseline.colormap = entnum; + svent->priv.server->baseline.modelindex = SV_ModelIndex("progs/player.mdl", 1); + } + else + { + svent->priv.server->baseline.colormap = 0; + svent->priv.server->baseline.modelindex = (int)PRVM_serveredictfloat(svent, modelindex); + } + + large = false; + if (svent->priv.server->baseline.modelindex & 0xFF00 || svent->priv.server->baseline.frame & 0xFF00) + { + large = true; + if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + large = false; + } + + // add to the message + if (large) + MSG_WriteByte (&sv.signon, svc_spawnbaseline2); + else + MSG_WriteByte (&sv.signon, svc_spawnbaseline); + MSG_WriteShort (&sv.signon, entnum); + + if (large) + { + MSG_WriteShort (&sv.signon, svent->priv.server->baseline.modelindex); + MSG_WriteShort (&sv.signon, svent->priv.server->baseline.frame); + } + else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + { + MSG_WriteShort (&sv.signon, svent->priv.server->baseline.modelindex); + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.frame); + } + else + { + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.modelindex); + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.frame); + } + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.colormap); + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.skin); + for (i=0 ; i<3 ; i++) + { + MSG_WriteCoord(&sv.signon, svent->priv.server->baseline.origin[i], sv.protocol); + MSG_WriteAngle(&sv.signon, svent->priv.server->baseline.angles[i], sv.protocol); + } + } +} + +/* +================ +SV_Prepare_CSQC + +Load csprogs.dat and comperss it so it doesn't need to be +reloaded on request. +================ +*/ +void SV_Prepare_CSQC(void) +{ + fs_offset_t progsize; + + if(svs.csqc_progdata) + { + Con_DPrintf("Unloading old CSQC data.\n"); + Mem_Free(svs.csqc_progdata); + if(svs.csqc_progdata_deflated) + Mem_Free(svs.csqc_progdata_deflated); + } + + svs.csqc_progdata = NULL; + svs.csqc_progdata_deflated = NULL; + + sv.csqc_progname[0] = 0; + svs.csqc_progdata = FS_LoadFile(csqc_progname.string, sv_mempool, false, &progsize); + + if(progsize > 0) + { + size_t deflated_size; + + sv.csqc_progsize = (int)progsize; + sv.csqc_progcrc = CRC_Block(svs.csqc_progdata, progsize); + strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname)); + Con_DPrintf("server detected csqc progs file \"%s\" with size %i and crc %i\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); + + Con_DPrint("Compressing csprogs.dat\n"); + //unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool); + svs.csqc_progdata_deflated = FS_Deflate(svs.csqc_progdata, progsize, &deflated_size, -1, sv_mempool); + svs.csqc_progsize_deflated = (int)deflated_size; + if(svs.csqc_progdata_deflated) + { + Con_DPrintf("Deflated: %g%%\n", 100.0 - 100.0 * (deflated_size / (float)progsize)); + Con_DPrintf("Uncompressed: %u\nCompressed: %u\n", (unsigned)sv.csqc_progsize, (unsigned)svs.csqc_progsize_deflated); + } + else + Con_DPrintf("Cannot compress - need zlib for this. Using uncompressed progs only.\n"); + } +} + +/* +================ +SV_SaveSpawnparms + +Grabs the current state of each client for saving across the +transition to another level +================ +*/ +void SV_SaveSpawnparms (void) +{ + int i, j; + + svs.serverflags = (int)PRVM_serverglobalfloat(serverflags); + + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + if (!host_client->active) + continue; + + // call the progs to get default spawn parms for the new client + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram (PRVM_serverfunction(SetChangeParms), "QC function SetChangeParms is missing"); + for (j=0 ; jspawn_parms[j] = (&PRVM_serverglobalfloat(parm1))[j]; + } +} + +/* +================ +SV_SpawnServer + +This is called at the start of each level +================ +*/ + +void SV_SpawnServer (const char *server) +{ + prvm_edict_t *ent; + int i; + char *entities; + dp_model_t *worldmodel; + char modelname[sizeof(sv.worldname)]; + + Con_DPrintf("SpawnServer: %s\n", server); + + dpsnprintf (modelname, sizeof(modelname), "maps/%s.bsp", server); + + if (!FS_FileExists(modelname)) + { + dpsnprintf (modelname, sizeof(modelname), "maps/%s", server); + if (!FS_FileExists(modelname)) + { + Con_Printf("SpawnServer: no map file named maps/%s.bsp\n", server); + return; + } + } + + if (cls.state != ca_dedicated) + { + SCR_BeginLoadingPlaque(); + S_StopAllSounds(); + } + + if(sv.active) + { + SV_VM_Begin(); + World_End(&sv.world); + if(PRVM_serverfunction(SV_Shutdown)) + { + func_t s = PRVM_serverfunction(SV_Shutdown); + PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again + PRVM_ExecuteProgram(s,"SV_Shutdown() required"); + } + SV_VM_End(); + } + + // free q3 shaders so that any newly downloaded shaders will be active + Mod_FreeQ3Shaders(); + + worldmodel = Mod_ForName(modelname, false, developer.integer > 0, NULL); + if (!worldmodel || !worldmodel->TraceBox) + { + Con_Printf("Couldn't load map %s\n", modelname); + return; + } + + Collision_Cache_Reset(true); + + // let's not have any servers with no name + if (hostname.string[0] == 0) + Cvar_Set ("hostname", "UNNAMED"); + scr_centertime_off = 0; + + svs.changelevel_issued = false; // now safe to issue another + + // make the map a required file for clients + Curl_ClearRequirements(); + Curl_RequireFile(modelname); + +// +// tell all connected clients that we are going to a new level +// + if (sv.active) + { + client_t *client; + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (client->netconnection) + { + MSG_WriteByte(&client->netconnection->message, svc_stufftext); + MSG_WriteString(&client->netconnection->message, "reconnect\n"); + } + } + } + else + { + // open server port + NetConn_OpenServerPorts(true); + } + +// +// make cvars consistant +// + if (coop.integer) + Cvar_SetValue ("deathmatch", 0); + // LordHavoc: it can be useful to have skills outside the range 0-3... + //current_skill = bound(0, (int)(skill.value + 0.5), 3); + //Cvar_SetValue ("skill", (float)current_skill); + current_skill = (int)(skill.value + 0.5); + +// +// set up the new server +// + memset (&sv, 0, sizeof(sv)); + // if running a local client, make sure it doesn't try to access the last + // level's data which is no longer valiud + cls.signon = 0; + + Cvar_SetValue("halflifebsp", worldmodel->brush.ishlbsp); + + if(*sv_random_seed.string) + { + srand(sv_random_seed.integer); + Con_Printf("NOTE: random seed is %d; use for debugging/benchmarking only!\nUnset sv_random_seed to get real random numbers again.\n", sv_random_seed.integer); + } + + SV_VM_Setup(); + + sv.active = true; + + // set level base name variables for later use + strlcpy (sv.name, server, sizeof (sv.name)); + strlcpy(sv.worldname, modelname, sizeof(sv.worldname)); + FS_StripExtension(sv.worldname, sv.worldnamenoextension, sizeof(sv.worldnamenoextension)); + strlcpy(sv.worldbasename, !strncmp(sv.worldnamenoextension, "maps/", 5) ? sv.worldnamenoextension + 5 : sv.worldnamenoextension, sizeof(sv.worldbasename)); + //Cvar_SetQuick(&sv_worldmessage, sv.worldmessage); // set later after QC is spawned + Cvar_SetQuick(&sv_worldname, sv.worldname); + Cvar_SetQuick(&sv_worldnamenoextension, sv.worldnamenoextension); + Cvar_SetQuick(&sv_worldbasename, sv.worldbasename); + + sv.protocol = Protocol_EnumForName(sv_protocolname.string); + if (sv.protocol == PROTOCOL_UNKNOWN) + { + char buffer[1024]; + Protocol_Names(buffer, sizeof(buffer)); + Con_Printf("Unknown sv_protocolname \"%s\", valid values are:\n%s\n", sv_protocolname.string, buffer); + sv.protocol = PROTOCOL_QUAKE; + } + + SV_VM_Begin(); + +// load progs to get entity field count + //PR_LoadProgs ( sv_progs.string ); + + sv.datagram.maxsize = sizeof(sv.datagram_buf); + sv.datagram.cursize = 0; + sv.datagram.data = sv.datagram_buf; + + sv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf); + sv.reliable_datagram.cursize = 0; + sv.reliable_datagram.data = sv.reliable_datagram_buf; + + sv.signon.maxsize = sizeof(sv.signon_buf); + sv.signon.cursize = 0; + sv.signon.data = sv.signon_buf; + +// leave slots at start for clients only + //prog->num_edicts = svs.maxclients+1; + + sv.state = ss_loading; + prog->allowworldwrites = true; + sv.paused = false; + + PRVM_serverglobalfloat(time) = sv.time = 1.0; + + Mod_ClearUsed(); + worldmodel->used = true; + + sv.worldmodel = worldmodel; + sv.models[1] = sv.worldmodel; + +// +// clear world interaction links +// + World_SetSize(&sv.world, sv.worldname, sv.worldmodel->normalmins, sv.worldmodel->normalmaxs); + World_Start(&sv.world); + + strlcpy(sv.sound_precache[0], "", sizeof(sv.sound_precache[0])); + + strlcpy(sv.model_precache[0], "", sizeof(sv.model_precache[0])); + strlcpy(sv.model_precache[1], sv.worldname, sizeof(sv.model_precache[1])); + for (i = 1;i < sv.worldmodel->brush.numsubmodels && i+1 < MAX_MODELS;i++) + { + dpsnprintf(sv.model_precache[i+1], sizeof(sv.model_precache[i+1]), "*%i", i); + sv.models[i+1] = Mod_ForName (sv.model_precache[i+1], false, false, sv.worldname); + } + if(i < sv.worldmodel->brush.numsubmodels) + Con_Printf("Too many submodels (MAX_MODELS is %i)\n", MAX_MODELS); + +// +// load the rest of the entities +// + // AK possible hack since num_edicts is still 0 + ent = PRVM_EDICT_NUM(0); + memset (ent->fields.vp, 0, prog->entityfields * 4); + ent->priv.server->free = false; + PRVM_serveredictstring(ent, model) = PRVM_SetEngineString(sv.worldname); + PRVM_serveredictfloat(ent, modelindex) = 1; // world model + PRVM_serveredictfloat(ent, solid) = SOLID_BSP; + PRVM_serveredictfloat(ent, movetype) = MOVETYPE_PUSH; + VectorCopy(sv.world.mins, PRVM_serveredictvector(ent, mins)); + VectorCopy(sv.world.maxs, PRVM_serveredictvector(ent, maxs)); + VectorCopy(sv.world.mins, PRVM_serveredictvector(ent, absmin)); + VectorCopy(sv.world.maxs, PRVM_serveredictvector(ent, absmax)); + + if (coop.value) + PRVM_serverglobalfloat(coop) = coop.integer; + else + PRVM_serverglobalfloat(deathmatch) = deathmatch.integer; + + PRVM_serverglobalstring(mapname) = PRVM_SetEngineString(sv.name); + +// serverflags are for cross level information (sigils) + PRVM_serverglobalfloat(serverflags) = svs.serverflags; + + // we need to reset the spawned flag on all connected clients here so that + // their thinks don't run during startup (before PutClientInServer) + // we also need to set up the client entities now + // and we need to set the ->edict pointers to point into the progs edicts + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + host_client->spawned = false; + host_client->edict = PRVM_EDICT_NUM(i + 1); + PRVM_ED_ClearEdict(host_client->edict); + } + + // load replacement entity file if found + if (sv_entpatch.integer && (entities = (char *)FS_LoadFile(va("%s.ent", sv.worldnamenoextension), tempmempool, true, NULL))) + { + Con_Printf("Loaded %s.ent\n", sv.worldnamenoextension); + PRVM_ED_LoadFromFile (entities); + Mem_Free(entities); + } + else + PRVM_ED_LoadFromFile (sv.worldmodel->brush.entities); + + + // LordHavoc: clear world angles (to fix e3m3.bsp) + VectorClear(PRVM_serveredictvector(prog->edicts, angles)); + +// all setup is completed, any further precache statements are errors +// sv.state = ss_active; // LordHavoc: workaround for svc_precache bug + prog->allowworldwrites = false; + +// run two frames to allow everything to settle + PRVM_serverglobalfloat(time) = sv.time = 1.0001; + for (i = 0;i < 2;i++) + { + sv.frametime = 0.1; + SV_Physics (); + } + + if (cls.state == ca_dedicated) + Mod_PurgeUnused(); + +// create a baseline for more efficient communications + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + SV_CreateBaseline (); + + sv.state = ss_active; // LordHavoc: workaround for svc_precache bug + + // to prevent network timeouts + realtime = Sys_DoubleTime(); + +// send serverinfo to all connected clients, and set up botclients coming back from a level change + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + host_client->clientconnectcalled = false; // do NOT call ClientDisconnect if he drops before ClientConnect! + if (!host_client->active) + continue; + if (host_client->netconnection) + SV_SendServerinfo(host_client); + else + { + int j; + // if client is a botclient coming from a level change, we need to + // set up client info that normally requires networking + + // copy spawn parms out of the client_t + for (j=0 ; j< NUM_SPAWN_PARMS ; j++) + (&PRVM_serverglobalfloat(parm1))[j] = host_client->spawn_parms[j]; + + // call the spawn function + host_client->clientconnectcalled = true; + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram (PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing"); + PRVM_ExecuteProgram (PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing"); + host_client->spawned = true; + } + } + + // update the map title cvar + strlcpy(sv.worldmessage, PRVM_GetString(PRVM_serveredictstring(prog->edicts, message)), sizeof(sv.worldmessage)); // map title (not related to filename) + Cvar_SetQuick(&sv_worldmessage, sv.worldmessage); + + Con_DPrint("Server spawned.\n"); + NetConn_Heartbeat (2); + + SV_VM_End(); +} + +///////////////////////////////////////////////////// +// SV VM stuff + +static void SV_VM_CB_BeginIncreaseEdicts(void) +{ + // links don't survive the transition, so unlink everything + World_UnlinkAll(&sv.world); +} + +static void SV_VM_CB_EndIncreaseEdicts(void) +{ + int i; + prvm_edict_t *ent; + + // link every entity except world + for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++) + if (!ent->priv.server->free) + SV_LinkEdict(ent); +} + +static void SV_VM_CB_InitEdict(prvm_edict_t *e) +{ + // LordHavoc: for consistency set these here + int num = PRVM_NUM_FOR_EDICT(e) - 1; + + e->priv.server->move = false; // don't move on first frame + + if (num >= 0 && num < svs.maxclients) + { + // set colormap and team on newly created player entity + PRVM_serveredictfloat(e, colormap) = num + 1; + PRVM_serveredictfloat(e, team) = (svs.clients[num].colors & 15) + 1; + // set netname/clientcolors back to client values so that + // DP_SV_CLIENTNAME and DP_SV_CLIENTCOLORS will not immediately + // reset them + PRVM_serveredictstring(e, netname) = PRVM_SetEngineString(svs.clients[num].name); + PRVM_serveredictfloat(e, clientcolors) = svs.clients[num].colors; + // NEXUIZ_PLAYERMODEL and NEXUIZ_PLAYERSKIN + PRVM_serveredictstring(e, playermodel) = PRVM_SetEngineString(svs.clients[num].playermodel); + PRVM_serveredictstring(e, playerskin) = PRVM_SetEngineString(svs.clients[num].playerskin); + // Assign netaddress (IP Address, etc) + if(svs.clients[num].netconnection != NULL) + { + // Acquire Readable Address + LHNETADDRESS_ToString(&svs.clients[num].netconnection->peeraddress, svs.clients[num].netaddress, sizeof(svs.clients[num].netaddress), false); + PRVM_serveredictstring(e, netaddress) = PRVM_SetEngineString(svs.clients[num].netaddress); + } + else + PRVM_serveredictstring(e, netaddress) = PRVM_SetEngineString("null/botclient"); + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_idfp[0]) + PRVM_serveredictstring(e, crypto_idfp) = PRVM_SetEngineString(svs.clients[num].netconnection->crypto.client_idfp); + else + PRVM_serveredictstring(e, crypto_idfp) = 0; + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_keyfp[0]) + PRVM_serveredictstring(e, crypto_keyfp) = PRVM_SetEngineString(svs.clients[num].netconnection->crypto.client_keyfp); + else + PRVM_serveredictstring(e, crypto_keyfp) = 0; + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.server_keyfp[0]) + PRVM_serveredictstring(e, crypto_mykeyfp) = PRVM_SetEngineString(svs.clients[num].netconnection->crypto.server_keyfp); + else + PRVM_serveredictstring(e, crypto_mykeyfp) = 0; + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.use_aes) + PRVM_serveredictstring(e, crypto_encryptmethod) = PRVM_SetEngineString("AES128"); + else + PRVM_serveredictstring(e, crypto_encryptmethod) = 0; + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated) + PRVM_serveredictstring(e, crypto_signmethod) = PRVM_SetEngineString("HMAC-SHA256"); + else + PRVM_serveredictstring(e, crypto_signmethod) = 0; + } +} + +static void SV_VM_CB_FreeEdict(prvm_edict_t *ed) +{ + int i; + int e; + + World_UnlinkEdict(ed); // unlink from world bsp + + PRVM_serveredictstring(ed, model) = 0; + PRVM_serveredictfloat(ed, takedamage) = 0; + PRVM_serveredictfloat(ed, modelindex) = 0; + PRVM_serveredictfloat(ed, colormap) = 0; + PRVM_serveredictfloat(ed, skin) = 0; + PRVM_serveredictfloat(ed, frame) = 0; + VectorClear(PRVM_serveredictvector(ed, origin)); + VectorClear(PRVM_serveredictvector(ed, angles)); + PRVM_serveredictfloat(ed, nextthink) = -1; + PRVM_serveredictfloat(ed, solid) = 0; + + VM_RemoveEdictSkeleton(ed); + World_Physics_RemoveFromEntity(&sv.world, ed); + World_Physics_RemoveJointFromEntity(&sv.world, ed); + + // make sure csqc networking is aware of the removed entity + e = PRVM_NUM_FOR_EDICT(ed); + sv.csqcentityversion[e] = 0; + for (i = 0;i < svs.maxclients;i++) + { + if (svs.clients[i].csqcentityscope[e]) + svs.clients[i].csqcentityscope[e] = 1; // removed, awaiting send + svs.clients[i].csqcentitysendflags[e] = 0xFFFFFF; + } +} + +static void SV_VM_CB_CountEdicts(void) +{ + int i; + prvm_edict_t *ent; + int active, models, solid, step; + + active = models = solid = step = 0; + for (i=0 ; inum_edicts ; i++) + { + ent = PRVM_EDICT_NUM(i); + if (ent->priv.server->free) + continue; + active++; + if (PRVM_serveredictfloat(ent, solid)) + solid++; + if (PRVM_serveredictstring(ent, model)) + models++; + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_STEP) + step++; + } + + Con_Printf("num_edicts:%3i\n", prog->num_edicts); + Con_Printf("active :%3i\n", active); + Con_Printf("view :%3i\n", models); + Con_Printf("touch :%3i\n", solid); + Con_Printf("step :%3i\n", step); +} + +static qboolean SV_VM_CB_LoadEdict(prvm_edict_t *ent) +{ + // remove things from different skill levels or deathmatch + if (gamemode != GAME_TRANSFUSION) //Transfusion does this in QC + { + if (deathmatch.integer) + { + if (((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_DEATHMATCH)) + { + return false; + } + } + else if ((current_skill <= 0 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_EASY )) + || (current_skill == 1 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_MEDIUM)) + || (current_skill >= 2 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_HARD ))) + { + return false; + } + } + return true; +} + +static void SV_VM_Setup(void) +{ + PRVM_Begin; + PRVM_InitProg( PRVM_SERVERPROG ); + + // allocate the mempools + // TODO: move the magic numbers/constants into #defines [9/13/2006 Black] + prog->progs_mempool = Mem_AllocPool("Server Progs", 0, NULL); + prog->builtins = vm_sv_builtins; + prog->numbuiltins = vm_sv_numbuiltins; + prog->max_edicts = 512; + if (sv.protocol == PROTOCOL_QUAKE) + prog->limit_edicts = 640; // before quake mission pack 1 this was 512 + else if (sv.protocol == PROTOCOL_QUAKEDP) + prog->limit_edicts = 2048; // guessing + else if (sv.protocol == PROTOCOL_NEHAHRAMOVIE) + prog->limit_edicts = 2048; // guessing! + else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + prog->limit_edicts = 4096; // guessing! + else + prog->limit_edicts = MAX_EDICTS; + prog->reserved_edicts = svs.maxclients; + prog->edictprivate_size = sizeof(edict_engineprivate_t); + prog->name = "server"; + prog->extensionstring = vm_sv_extensions; + prog->loadintoworld = true; + + prog->begin_increase_edicts = SV_VM_CB_BeginIncreaseEdicts; + prog->end_increase_edicts = SV_VM_CB_EndIncreaseEdicts; + prog->init_edict = SV_VM_CB_InitEdict; + prog->free_edict = SV_VM_CB_FreeEdict; + prog->count_edicts = SV_VM_CB_CountEdicts; + prog->load_edict = SV_VM_CB_LoadEdict; + prog->init_cmd = VM_SV_Cmd_Init; + prog->reset_cmd = VM_SV_Cmd_Reset; + prog->error_cmd = Host_Error; + prog->ExecuteProgram = SVVM_ExecuteProgram; + + PRVM_LoadProgs( sv_progs.string, SV_REQFUNCS, sv_reqfuncs, SV_REQFIELDS, sv_reqfields, SV_REQGLOBALS, sv_reqglobals); + + // some mods compiled with scrambling compilers lack certain critical + // global names and field names such as "self" and "time" and "nextthink" + // so we have to set these offsets manually, matching the entvars_t + // but we only do this if the prog header crc matches, otherwise it's totally freeform + if (prog->progs_crc == PROGHEADER_CRC || prog->progs_crc == PROGHEADER_CRC_TENEBRAE) + { + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, modelindex); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, absmin); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, absmax); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ltime); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, movetype); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, solid); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, origin); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, oldorigin); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, velocity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, angles); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, avelocity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, punchangle); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, classname); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, model); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, frame); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, skin); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, effects); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, mins); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, maxs); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, size); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, touch); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, use); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, think); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, blocked); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, nextthink); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, groundentity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, health); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, frags); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weapon); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weaponmodel); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weaponframe); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, currentammo); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_shells); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_nails); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_rockets); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_cells); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, items); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, takedamage); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, chain); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, deadflag); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, view_ofs); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button0); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button1); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button2); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, impulse); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, fixangle); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, v_angle); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, idealpitch); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, netname); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, enemy); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, flags); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, colormap); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, team); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, max_health); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, teleport_time); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, armortype); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, armorvalue); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, waterlevel); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, watertype); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ideal_yaw); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, yaw_speed); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, aiment); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, goalentity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, spawnflags); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, target); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, targetname); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_take); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_save); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_inflictor); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, owner); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, movedir); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, message); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, sounds); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise1); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise2); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise3); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, self); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, other); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, world); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, time); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, frametime); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, force_retouch); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, mapname); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, deathmatch); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, coop); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, teamplay); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, serverflags); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, total_secrets); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, total_monsters); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, found_secrets); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, killed_monsters); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm1); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm2); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm3); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm4); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm5); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm6); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm7); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm8); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm9); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm10); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm11); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm12); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm13); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm14); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm15); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm16); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_forward); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_up); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_right); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_allsolid); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_startsolid); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_fraction); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_endpos); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_plane_normal); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_plane_dist); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_ent); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_inopen); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_inwater); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, msg_entity); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, main); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, StartFrame); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PlayerPreThink); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PlayerPostThink); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientKill); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientConnect); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PutClientInServer); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientDisconnect); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, SetNewParms); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, SetChangeParms); + } + else + Con_DPrintf("%s: %s system vars have been modified (CRC %i != engine %i), will not load in other engines", PRVM_NAME, sv_progs.string, prog->progs_crc, PROGHEADER_CRC); + + // OP_STATE is always supported on server because we add fields/globals for it + prog->flag |= PRVM_OP_STATE; + + VM_CustomStats_Clear();//[515]: csqc + + PRVM_End; + + SV_Prepare_CSQC(); +} + +void SV_VM_Begin(void) +{ + PRVM_Begin; + PRVM_SetProg( PRVM_SERVERPROG ); + + PRVM_serverglobalfloat(time) = (float) sv.time; +} + +void SV_VM_End(void) +{ + PRVM_End; +} diff --git a/misc/source/darkplaces-src/sv_move.c b/misc/source/darkplaces-src/sv_move.c new file mode 100644 index 00000000..d93d1f8b --- /dev/null +++ b/misc/source/darkplaces-src/sv_move.c @@ -0,0 +1,441 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_move.c -- monster movement + +#include "quakedef.h" +#include "prvm_cmds.h" + +/* +============= +SV_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean SV_CheckBottom (prvm_edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); + VectorAdd (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (!(SV_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*sv_stepheight.value; + trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent)); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent)); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) + return false; + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done and false is returned +============= +*/ +qboolean SV_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace) +{ + float dz; + vec3_t oldorg, neworg, end, traceendpos; + trace_t trace; + int i; + prvm_edict_t *enemy; + +// try the move + VectorCopy (PRVM_serveredictvector(ent, origin), oldorg); + VectorAdd (PRVM_serveredictvector(ent, origin), move, neworg); + +// flying monsters don't step up + if ( (int)PRVM_serveredictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (PRVM_serveredictvector(ent, origin), move, neworg); + if (noenemy) + enemy = prog->edicts; + else + { + enemy = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, enemy)); + if (i == 0 && enemy != prog->edicts) + { + dz = PRVM_serveredictvector(ent, origin)[2] - PRVM_serveredictvector(enemy, origin)[2]; + if (dz > 40) + neworg[2] -= 8; + if (dz < 30) + neworg[2] += 8; + } + } + trace = SV_TraceBox(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), neworg, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + + if (trace.fraction == 1) + { + VectorCopy(trace.endpos, traceendpos); + if (((int)PRVM_serveredictfloat(ent, flags) & FL_SWIM) && !(SV_PointSuperContents(traceendpos) & SUPERCONTENTS_LIQUIDSMASK)) + return false; // swim monster left water + + VectorCopy (traceendpos, PRVM_serveredictvector(ent, origin)); + if (relink) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + return true; + } + + if (enemy == prog->edicts) + break; + } + + return false; + } + +// push down from a step height above the wished position + neworg[2] += sv_stepheight.value; + VectorCopy (neworg, end); + end[2] -= sv_stepheight.value*2; + + trace = SV_TraceBox(neworg, PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + + if (trace.startsolid) + { + neworg[2] -= sv_stepheight.value; + trace = SV_TraceBox(neworg, PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + if (trace.startsolid) + return false; + } + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) + { + VectorAdd (PRVM_serveredictvector(ent, origin), move, PRVM_serveredictvector(ent, origin)); + if (relink) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); + + if (!SV_CheckBottom (ent)) + { + if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + return true; + } + VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); + return false; + } + + if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_PARTIALGROUND; + +// gameplayfix: check if reached pretty steep plane and bail + if ( ! ( (int)PRVM_serveredictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) && sv_gameplayfix_nostepmoveonsteepslopes.integer ) + { + if (trace.plane.normal[ 2 ] < 0.5) + { + VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); + return false; + } + } + + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + +// the move is ok + if (relink) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + return true; +} + + +//============================================================================ + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +void VM_changeyaw (void); +qboolean SV_StepDirection (prvm_edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + PRVM_serveredictfloat(ent, ideal_yaw) = yaw; + VM_changeyaw(); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (PRVM_serveredictvector(ent, origin), oldorigin); + if (SV_movestep (ent, move, false, false, false)) + { + delta = PRVM_serveredictvector(ent, angles)[YAW] - PRVM_serveredictfloat(ent, ideal_yaw); + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, PRVM_serveredictvector(ent, origin)); + } + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + return true; + } + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (prvm_edict_t *ent) +{ + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (prvm_edict_t *actor, prvm_edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + olddir = ANGLEMOD((int)(PRVM_serveredictfloat(actor, ideal_yaw)/45)*45); + turnaround = ANGLEMOD(olddir - 180); + + deltax = PRVM_serveredictvector(enemy, origin)[0] - PRVM_serveredictvector(actor, origin)[0]; + deltay = PRVM_serveredictvector(enemy, origin)[1] - PRVM_serveredictvector(actor, origin)[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || fabs(deltay)>fabs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + PRVM_serveredictfloat(actor, ideal_yaw) = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!SV_CheckBottom (actor)) + SV_FixCheckBottom (actor); + +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +qboolean SV_CloseEnough (prvm_edict_t *ent, prvm_edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->priv.server->areamins[i] > ent->priv.server->areamaxs[i] + dist) + return false; + if (goal->priv.server->areamaxs[i] < ent->priv.server->areamins[i] - dist) + return false; + } + return true; +} + +/* +====================== +SV_MoveToGoal + +====================== +*/ +void SV_MoveToGoal (void) +{ + prvm_edict_t *ent, *goal; + float dist; + + VM_SAFEPARMCOUNT(1, SV_MoveToGoal); + + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + goal = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, goalentity)); + dist = PRVM_G_FLOAT(OFS_PARM0); + + if ( !( (int)PRVM_serveredictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + +// if the next step hits the enemy, return immediately + if ( PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, enemy)) != prog->edicts && SV_CloseEnough (ent, goal, dist) ) + return; + +// bump around... + if ( (rand()&3)==1 || + !SV_StepDirection (ent, PRVM_serveredictfloat(ent, ideal_yaw), dist)) + { + SV_NewChaseDir (ent, goal, dist); + } +} + diff --git a/misc/source/darkplaces-src/sv_phys.c b/misc/source/darkplaces-src/sv_phys.c new file mode 100644 index 00000000..0e3ac330 --- /dev/null +++ b/misc/source/darkplaces-src/sv_phys.c @@ -0,0 +1,3035 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_phys.c + +#include "quakedef.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + +#define MOVE_EPSILON 0.01 + +void SV_Physics_Toss (prvm_edict_t *ent); + +int SV_GetPitchSign(prvm_edict_t *ent) +{ + dp_model_t *model; + if ( + (model = SV_GetModelFromEdict(ent)) + ? + model->type == mod_alias + : + ( + (((unsigned char)PRVM_serveredictfloat(ent, pflags)) & PFLAGS_FULLDYNAMIC) + || + ((gamemode == GAME_TENEBRAE) && ((unsigned int)PRVM_serveredictfloat(ent, effects) & (16 | 32))) + ) + ) + return -1; + return 1; +} + +/* +=============================================================================== + +LINE TESTING IN HULLS + +=============================================================================== +*/ + +int SV_GenericHitSuperContentsMask(const prvm_edict_t *passedict) +{ + if (passedict) + { + int dphitcontentsmask = (int)PRVM_serveredictfloat(passedict, dphitcontentsmask); + if (dphitcontentsmask) + return dphitcontentsmask; + else if (PRVM_serveredictfloat(passedict, solid) == SOLID_SLIDEBOX) + { + if ((int)PRVM_serveredictfloat(passedict, flags) & FL_MONSTER) + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_MONSTERCLIP; + else + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP; + } + else if (PRVM_serveredictfloat(passedict, solid) == SOLID_CORPSE) + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; + else if (PRVM_serveredictfloat(passedict, solid) == SOLID_TRIGGER) + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; + else + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE; + } + else + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE; +} + +/* +================== +SV_TracePoint +================== +*/ +trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +{ + int i, bodysupercontents; + int passedictprog; + float pitchsign = 1; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // bounding box of entire move area + vec3_t clipboxmins, clipboxmaxs; + // size when clipping against monsters + vec3_t clipmins2, clipmaxs2; + // start and end origin of move + vec3_t clipstart; + // trace results + trace_t cliptrace; + // matrices to transform into/out of other entity's space + matrix4x4_t matrix, imatrix; + // model of other entity + dp_model_t *model; + // list of entities to test for collisions + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + + //return SV_TraceBox(start, vec3_origin, vec3_origin, end, type, passedict, hitsupercontentsmask); + + VectorCopy(start, clipstart); + VectorClear(clipmins2); + VectorClear(clipmaxs2); +#if COLLISIONPARANOID >= 3 + Con_Printf("move(%f %f %f)", clipstart[0], clipstart[1], clipstart[2]); +#endif + + // clip to world + Collision_ClipPointToWorld(&cliptrace, sv.worldmodel, clipstart, hitsupercontentsmask); + cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog->edicts; + if (type == MOVE_WORLDONLY) + goto finished; + + if (type == MOVE_MISSILE) + { + // LordHavoc: modified this, was = -15, now -= 15 + for (i = 0;i < 3;i++) + { + clipmins2[i] -= 15; + clipmaxs2[i] += 15; + } + } + + // create the bounding box of the entire move + for (i = 0;i < 3;i++) + { + clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + clipmins2[i] - 1; + clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + clipmaxs2[i] + 1; + } + + // debug override to test against everything + if (sv_debugmove.integer) + { + clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; + clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; + } + + // if the passedict is world, make it NULL (to avoid two checks each time) + if (passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = PRVM_EDICT_TO_PROG(passedict); + // precalculate passedict's owner edict pointer for comparisons + traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_serveredictedict(passedict, owner)) : 0; + + // clip to entities + // because this uses World_EntitiestoBox, we know all entity boxes overlap + // the clip region, so we can skip culling checks in the loop below + numtouchedicts = World_EntitiesInBox(&sv.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + + if (PRVM_serveredictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(touch, solid) != SOLID_BSP) + continue; + + if (passedict) + { + // don't clip against self + if (passedict == touch) + continue; + // don't clip owned entities against owner + if (traceowner == touch) + continue; + // don't clip owner against owned entities + if (passedictprog == PRVM_serveredictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + { + model = SV_GetModelFromEdict(touch); + pitchsign = SV_GetPitchSign(touch); + } + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + VM_GenerateFrameGroupBlend(touch->priv.server->framegroupblend, touch); + VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(touch, model, touch->priv.server->frameblend); + if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER) + Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipstart, hitsupercontentsmask); + else + Collision_ClipPointToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, hitsupercontentsmask); + + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(touch, solid) == SOLID_BSP); + } + +finished: + return cliptrace; +} + +/* +================== +SV_TraceLine +================== +*/ +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +trace_t SV_TraceLine(const vec3_t start, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +#else +trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +#endif +{ + int i, bodysupercontents; + int passedictprog; + float pitchsign = 1; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // bounding box of entire move area + vec3_t clipboxmins, clipboxmaxs; + // size when clipping against monsters + vec3_t clipmins2, clipmaxs2; + // start and end origin of move + vec3_t clipstart, clipend; + // trace results + trace_t cliptrace; + // matrices to transform into/out of other entity's space + matrix4x4_t matrix, imatrix; + // model of other entity + dp_model_t *model; + // list of entities to test for collisions + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + vec3_t end; + vec_t len = 0; + + if (VectorCompare(start, pEnd)) + return SV_TracePoint(start, type, passedict, hitsupercontentsmask); + + if(collision_endposnudge.value > 0) + { + // TRICK: make the trace 1 qu longer! + VectorSubtract(pEnd, start, end); + len = VectorNormalizeLength(end); + VectorMA(pEnd, collision_endposnudge.value, end, end); + } + else + VectorCopy(pEnd, end); +#else + if (VectorCompare(start, end)) + return SV_TracePoint(start, type, passedict, hitsupercontentsmask); +#endif + + //return SV_TraceBox(start, vec3_origin, vec3_origin, end, type, passedict, hitsupercontentsmask); + + VectorCopy(start, clipstart); + VectorCopy(end, clipend); + VectorClear(clipmins2); + VectorClear(clipmaxs2); +#if COLLISIONPARANOID >= 3 + Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); +#endif + + // clip to world + Collision_ClipLineToWorld(&cliptrace, sv.worldmodel, clipstart, clipend, hitsupercontentsmask, false); + cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog->edicts; + if (type == MOVE_WORLDONLY) + goto finished; + + if (type == MOVE_MISSILE) + { + // LordHavoc: modified this, was = -15, now -= 15 + for (i = 0;i < 3;i++) + { + clipmins2[i] -= 15; + clipmaxs2[i] += 15; + } + } + + // create the bounding box of the entire move + for (i = 0;i < 3;i++) + { + clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + clipmins2[i] - 1; + clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + clipmaxs2[i] + 1; + } + + // debug override to test against everything + if (sv_debugmove.integer) + { + clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; + clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; + } + + // if the passedict is world, make it NULL (to avoid two checks each time) + if (passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = PRVM_EDICT_TO_PROG(passedict); + // precalculate passedict's owner edict pointer for comparisons + traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_serveredictedict(passedict, owner)) : 0; + + // clip to entities + // because this uses World_EntitiestoBox, we know all entity boxes overlap + // the clip region, so we can skip culling checks in the loop below + numtouchedicts = World_EntitiesInBox(&sv.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + + if (PRVM_serveredictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(touch, solid) != SOLID_BSP) + continue; + + if (passedict) + { + // don't clip against self + if (passedict == touch) + continue; + // don't clip owned entities against owner + if (traceowner == touch) + continue; + // don't clip owner against owned entities + if (passedictprog == PRVM_serveredictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + { + model = SV_GetModelFromEdict(touch); + pitchsign = SV_GetPitchSign(touch); + } + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + VM_GenerateFrameGroupBlend(touch->priv.server->framegroupblend, touch); + VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(touch, model, touch->priv.server->frameblend); + if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER) + Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask); + else + Collision_ClipLineToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipend, hitsupercontentsmask, false); + + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(touch, solid) == SOLID_BSP); + } + +finished: +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) + Collision_ShortenTrace(&cliptrace, len / (len + collision_endposnudge.value), pEnd); +#endif + return cliptrace; +} + +/* +================== +SV_Move +================== +*/ +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +#if COLLISIONPARANOID >= 1 +trace_t SV_TraceBox_(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +#else +trace_t SV_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +#endif +#else +#if COLLISIONPARANOID >= 1 +trace_t SV_TraceBox_(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +#else +trace_t SV_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +#endif +#endif +{ + vec3_t hullmins, hullmaxs; + int i, bodysupercontents; + int passedictprog; + float pitchsign = 1; + qboolean pointtrace; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // bounding box of entire move area + vec3_t clipboxmins, clipboxmaxs; + // size of the moving object + vec3_t clipmins, clipmaxs; + // size when clipping against monsters + vec3_t clipmins2, clipmaxs2; + // start and end origin of move + vec3_t clipstart, clipend; + // trace results + trace_t cliptrace; + // matrices to transform into/out of other entity's space + matrix4x4_t matrix, imatrix; + // model of other entity + dp_model_t *model; + // list of entities to test for collisions + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + vec3_t end; + vec_t len = 0; + + if (VectorCompare(mins, maxs)) + { + vec3_t shiftstart, shiftend; + VectorAdd(start, mins, shiftstart); + VectorAdd(pEnd, mins, shiftend); + if (VectorCompare(start, pEnd)) + trace = SV_TracePoint(shiftstart, type, passedict, hitsupercontentsmask); + else + trace = SV_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask); + VectorSubtract(trace.endpos, mins, trace.endpos); + return trace; + } + + if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) + { + // TRICK: make the trace 1 qu longer! + VectorSubtract(pEnd, start, end); + len = VectorNormalizeLength(end); + VectorMA(pEnd, collision_endposnudge.value, end, end); + } + else + VectorCopy(pEnd, end); +#else + if (VectorCompare(mins, maxs)) + { + vec3_t shiftstart, shiftend; + VectorAdd(start, mins, shiftstart); + VectorAdd(end, mins, shiftend); + if (VectorCompare(start, end)) + trace = SV_TracePoint(shiftstart, type, passedict, hitsupercontentsmask); + else + trace = SV_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask); + VectorSubtract(trace.endpos, mins, trace.endpos); + return trace; + } +#endif + + VectorCopy(start, clipstart); + VectorCopy(end, clipend); + VectorCopy(mins, clipmins); + VectorCopy(maxs, clipmaxs); + VectorCopy(mins, clipmins2); + VectorCopy(maxs, clipmaxs2); +#if COLLISIONPARANOID >= 3 + Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); +#endif + + // clip to world + Collision_ClipToWorld(&cliptrace, sv.worldmodel, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask); + cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog->edicts; + if (type == MOVE_WORLDONLY) + goto finished; + + if (type == MOVE_MISSILE) + { + // LordHavoc: modified this, was = -15, now -= 15 + for (i = 0;i < 3;i++) + { + clipmins2[i] -= 15; + clipmaxs2[i] += 15; + } + } + + // get adjusted box for bmodel collisions if the world is q1bsp or hlbsp + if (sv.worldmodel && sv.worldmodel->brush.RoundUpToHullSize) + sv.worldmodel->brush.RoundUpToHullSize(sv.worldmodel, clipmins, clipmaxs, hullmins, hullmaxs); + else + { + VectorCopy(clipmins, hullmins); + VectorCopy(clipmaxs, hullmaxs); + } + + // create the bounding box of the entire move + for (i = 0;i < 3;i++) + { + clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + min(hullmins[i], clipmins2[i]) - 1; + clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + max(hullmaxs[i], clipmaxs2[i]) + 1; + } + + // debug override to test against everything + if (sv_debugmove.integer) + { + clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; + clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; + } + + // if the passedict is world, make it NULL (to avoid two checks each time) + if (passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = PRVM_EDICT_TO_PROG(passedict); + // figure out whether this is a point trace for comparisons + pointtrace = VectorCompare(clipmins, clipmaxs); + // precalculate passedict's owner edict pointer for comparisons + traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_serveredictedict(passedict, owner)) : 0; + + // clip to entities + // because this uses World_EntitiestoBox, we know all entity boxes overlap + // the clip region, so we can skip culling checks in the loop below + numtouchedicts = World_EntitiesInBox(&sv.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + + if (PRVM_serveredictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(touch, solid) != SOLID_BSP) + continue; + + if (passedict) + { + // don't clip against self + if (passedict == touch) + continue; + // don't clip owned entities against owner + if (traceowner == touch) + continue; + // don't clip owner against owned entities + if (passedictprog == PRVM_serveredictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (pointtrace && VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + { + model = SV_GetModelFromEdict(touch); + pitchsign = SV_GetPitchSign(touch); + } + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + VM_GenerateFrameGroupBlend(touch->priv.server->framegroupblend, touch); + VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(touch, model, touch->priv.server->frameblend); + if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER) + Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask); + else + Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs), bodysupercontents, &matrix, &imatrix, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask); + + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(touch, solid) == SOLID_BSP); + } + +finished: +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) + Collision_ShortenTrace(&cliptrace, len / (len + collision_endposnudge.value), pEnd); +#endif + return cliptrace; +} + +#if COLLISIONPARANOID >= 1 +trace_t SV_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +{ + int endstuck; + trace_t trace; + vec3_t temp; + trace = SV_TraceBox_(start, mins, maxs, end, type, passedict, hitsupercontentsmask); + if (passedict) + { + VectorCopy(trace.endpos, temp); + endstuck = SV_TraceBox_(temp, mins, maxs, temp, type, passedict, hitsupercontentsmask).startsolid; +#if COLLISIONPARANOID < 3 + if (trace.startsolid || endstuck) +#endif + Con_Printf("%s{e%i:%f %f %f:%f %f %f:%f:%f %f %f%s%s}\n", (trace.startsolid || endstuck) ? "^3" : "", passedict ? (int)(passedict - prog->edicts) : -1, PRVM_serveredictvector(passedict, origin)[0], PRVM_serveredictvector(passedict, origin)[1], PRVM_serveredictvector(passedict, origin)[2], end[0] - PRVM_serveredictvector(passedict, origin)[0], end[1] - PRVM_serveredictvector(passedict, origin)[1], end[2] - PRVM_serveredictvector(passedict, origin)[2], trace.fraction, trace.endpos[0] - PRVM_serveredictvector(passedict, origin)[0], trace.endpos[1] - PRVM_serveredictvector(passedict, origin)[1], trace.endpos[2] - PRVM_serveredictvector(passedict, origin)[2], trace.startsolid ? " startstuck" : "", endstuck ? " endstuck" : ""); + } + return trace; +} +#endif + +int SV_PointSuperContents(const vec3_t point) +{ + int supercontents = 0; + int i; + prvm_edict_t *touch; + vec3_t transformed; + // matrices to transform into/out of other entity's space + matrix4x4_t matrix, imatrix; + // model of other entity + dp_model_t *model; + int frame; + // list of entities to test for collisions + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + + // get world supercontents at this point + if (sv.worldmodel && sv.worldmodel->PointSuperContents) + supercontents = sv.worldmodel->PointSuperContents(sv.worldmodel, 0, point); + + // if sv_gameplayfix_swiminbmodels is off we're done + if (!sv_gameplayfix_swiminbmodels.integer) + return supercontents; + + // get list of entities at this point + numtouchedicts = World_EntitiesInBox(&sv.world, point, point, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + + // we only care about SOLID_BSP for pointcontents + if (PRVM_serveredictfloat(touch, solid) != SOLID_BSP) + continue; + + // might interact, so do an exact clip + model = SV_GetModelFromEdict(touch); + if (!model || !model->PointSuperContents) + continue; + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + Matrix4x4_Transform(&imatrix, point, transformed); + frame = (int)PRVM_serveredictfloat(touch, frame); + supercontents |= model->PointSuperContents(model, bound(0, frame, (model->numframes - 1)), transformed); + } + + return supercontents; +} + +/* +=============================================================================== + +Linking entities into the world culling system + +=============================================================================== +*/ + +void SV_LinkEdict_TouchAreaGrid_Call(prvm_edict_t *touch, prvm_edict_t *ent) +{ + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(touch); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(ent); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobalfloat(trace_allsolid) = false; + PRVM_serverglobalfloat(trace_startsolid) = false; + PRVM_serverglobalfloat(trace_fraction) = 1; + PRVM_serverglobalfloat(trace_inwater) = false; + PRVM_serverglobalfloat(trace_inopen) = true; + VectorCopy (PRVM_serveredictvector(touch, origin), PRVM_serverglobalvector(trace_endpos)); + VectorSet (PRVM_serverglobalvector(trace_plane_normal), 0, 0, 1); + PRVM_serverglobalfloat(trace_plane_dist) = 0; + PRVM_serverglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(ent); + PRVM_serverglobalfloat(trace_dpstartcontents) = 0; + PRVM_serverglobalfloat(trace_dphitcontents) = 0; + PRVM_serverglobalfloat(trace_dphitq3surfaceflags) = 0; + PRVM_serverglobalstring(trace_dphittexturename) = 0; + PRVM_ExecuteProgram (PRVM_serveredictfunction(touch, touch), "QC function self.touch is missing"); +} + +void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent) +{ + int i, numtouchedicts, old_self, old_other; + prvm_edict_t *touch; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + + if (ent == prog->edicts) + return; // don't add the world + + if (ent->priv.server->free) + return; + + if (PRVM_serveredictfloat(ent, solid) == SOLID_NOT) + return; + + // build a list of edicts to touch, because the link loop can be corrupted + // by IncreaseEdicts called during touch functions + numtouchedicts = World_EntitiesInBox(&sv.world, ent->priv.server->areamins, ent->priv.server->areamaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + + old_self = PRVM_serverglobaledict(self); + old_other = PRVM_serverglobaledict(other); + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + if (touch != ent && (int)PRVM_serveredictfloat(touch, solid) == SOLID_TRIGGER && PRVM_serveredictfunction(touch, touch)) + { + SV_LinkEdict_TouchAreaGrid_Call(touch, ent); + } + } + PRVM_serverglobaledict(self) = old_self; + PRVM_serverglobaledict(other) = old_other; +} + +static void RotateBBox(const vec3_t mins, const vec3_t maxs, const vec3_t angles, vec3_t rotatedmins, vec3_t rotatedmaxs) +{ + vec3_t v, u; + matrix4x4_t m; + Matrix4x4_CreateFromQuakeEntity(&m, 0, 0, 0, angles[PITCH], angles[YAW], angles[ROLL], 1.0); + + v[0] = mins[0]; v[1] = mins[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); + VectorCopy(u, rotatedmins); VectorCopy(u, rotatedmaxs); + v[0] = maxs[0]; v[1] = mins[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = mins[0]; v[1] = maxs[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = maxs[0]; v[1] = maxs[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = mins[0]; v[1] = mins[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = maxs[0]; v[1] = mins[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = mins[0]; v[1] = maxs[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = maxs[0]; v[1] = maxs[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; +} + +/* +=============== +SV_LinkEdict + +=============== +*/ +void SV_LinkEdict (prvm_edict_t *ent) +{ + dp_model_t *model; + vec3_t mins, maxs; + int modelindex; + + if (ent == prog->edicts) + return; // don't add the world + + if (ent->priv.server->free) + return; + + modelindex = (int)PRVM_serveredictfloat(ent, modelindex); + if (modelindex < 0 || modelindex >= MAX_MODELS) + { + Con_Printf("edict %i: SOLID_BSP with invalid modelindex!\n", PRVM_NUM_FOR_EDICT(ent)); + modelindex = 0; + } + model = SV_GetModelByIndex(modelindex); + + VM_GenerateFrameGroupBlend(ent->priv.server->framegroupblend, ent); + VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(ent, model, ent->priv.server->frameblend); + +// set the abs box + + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_PHYSICS) + { + // TODO maybe should do this for rotating SOLID_BSP too? Would behave better with rotating doors + // TODO special handling for spheres? + RotateBBox(PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), PRVM_serveredictvector(ent, angles), mins, maxs); + VectorAdd(PRVM_serveredictvector(ent, origin), mins, mins); + VectorAdd(PRVM_serveredictvector(ent, origin), maxs, maxs); + } + else if (PRVM_serveredictfloat(ent, solid) == SOLID_BSP) + { + if (model != NULL) + { + if (!model->TraceBox) + Con_DPrintf("edict %i: SOLID_BSP with non-collidable model\n", PRVM_NUM_FOR_EDICT(ent)); + + if (PRVM_serveredictvector(ent, angles)[0] || PRVM_serveredictvector(ent, angles)[2] || PRVM_serveredictvector(ent, avelocity)[0] || PRVM_serveredictvector(ent, avelocity)[2]) + { + VectorAdd(PRVM_serveredictvector(ent, origin), model->rotatedmins, mins); + VectorAdd(PRVM_serveredictvector(ent, origin), model->rotatedmaxs, maxs); + } + else if (PRVM_serveredictvector(ent, angles)[1] || PRVM_serveredictvector(ent, avelocity)[1]) + { + VectorAdd(PRVM_serveredictvector(ent, origin), model->yawmins, mins); + VectorAdd(PRVM_serveredictvector(ent, origin), model->yawmaxs, maxs); + } + else + { + VectorAdd(PRVM_serveredictvector(ent, origin), model->normalmins, mins); + VectorAdd(PRVM_serveredictvector(ent, origin), model->normalmaxs, maxs); + } + } + else + { + // SOLID_BSP with no model is valid, mainly because some QC setup code does so temporarily + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); + } + } + else + { + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); + } + +// +// to make items easier to pick up and allow them to be grabbed off +// of shelves, the abs sizes are expanded +// + if ((int)PRVM_serveredictfloat(ent, flags) & FL_ITEM) + { + mins[0] -= 15; + mins[1] -= 15; + mins[2] -= 1; + maxs[0] += 15; + maxs[1] += 15; + maxs[2] += 1; + } + else + { + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + mins[0] -= 1; + mins[1] -= 1; + mins[2] -= 1; + maxs[0] += 1; + maxs[1] += 1; + maxs[2] += 1; + } + + VectorCopy(mins, PRVM_serveredictvector(ent, absmin)); + VectorCopy(maxs, PRVM_serveredictvector(ent, absmax)); + + World_LinkEdict(&sv.world, ent, mins, maxs); +} + +/* +=============================================================================== + +Utility functions + +=============================================================================== +*/ + +/* +============ +SV_TestEntityPosition + +returns true if the entity is in solid currently +============ +*/ +static int SV_TestEntityPosition (prvm_edict_t *ent, vec3_t offset) +{ + int contents; + vec3_t org; + trace_t trace; + contents = SV_GenericHitSuperContentsMask(ent); + VectorAdd(PRVM_serveredictvector(ent, origin), offset, org); + trace = SV_TraceBox(org, PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), PRVM_serveredictvector(ent, origin), MOVE_NOMONSTERS, ent, contents); + if (trace.startsupercontents & contents) + return true; + else + { + if (sv.worldmodel->brushq1.numclipnodes && !VectorCompare(PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs))) + { + // q1bsp/hlbsp use hulls and if the entity does not exactly match + // a hull size it is incorrectly tested, so this code tries to + // 'fix' it slightly... + // FIXME: this breaks entities larger than the hull size + int i; + vec3_t v, m1, m2, s; + VectorAdd(org, PRVM_serveredictvector(ent, mins), m1); + VectorAdd(org, PRVM_serveredictvector(ent, maxs), m2); + VectorSubtract(m2, m1, s); +#define EPSILON (1.0f / 32.0f) + if (s[0] >= EPSILON*2) {m1[0] += EPSILON;m2[0] -= EPSILON;} + if (s[1] >= EPSILON*2) {m1[1] += EPSILON;m2[1] -= EPSILON;} + if (s[2] >= EPSILON*2) {m1[2] += EPSILON;m2[2] -= EPSILON;} + for (i = 0;i < 8;i++) + { + v[0] = (i & 1) ? m2[0] : m1[0]; + v[1] = (i & 2) ? m2[1] : m1[1]; + v[2] = (i & 4) ? m2[2] : m1[2]; + if (SV_PointSuperContents(v) & contents) + return true; + } + } + } + // if the trace found a better position for the entity, move it there + if (VectorDistance2(trace.endpos, PRVM_serveredictvector(ent, origin)) >= 0.0001) + { +#if 0 + // please switch back to this code when trace.endpos sometimes being in solid bug is fixed + VectorCopy(trace.endpos, PRVM_serveredictvector(ent, origin)); +#else + // verify if the endpos is REALLY outside solid + VectorCopy(trace.endpos, org); + trace = SV_TraceBox(org, PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), org, MOVE_NOMONSTERS, ent, contents); + if(trace.startsolid) + Con_Printf("SV_TestEntityPosition: trace.endpos detected to be in solid. NOT using it.\n"); + else + VectorCopy(org, PRVM_serveredictvector(ent, origin)); +#endif + } + return false; +} + +/* +================ +SV_CheckAllEnts +================ +*/ +void SV_CheckAllEnts (void) +{ + int e; + prvm_edict_t *check; + + // see if any solid entities are inside the final position + check = PRVM_NEXT_EDICT(prog->edicts); + for (e = 1;e < prog->num_edicts;e++, check = PRVM_NEXT_EDICT(check)) + { + if (check->priv.server->free) + continue; + if (PRVM_serveredictfloat(check, movetype) == MOVETYPE_PUSH + || PRVM_serveredictfloat(check, movetype) == MOVETYPE_NONE + || PRVM_serveredictfloat(check, movetype) == MOVETYPE_FOLLOW + || PRVM_serveredictfloat(check, movetype) == MOVETYPE_NOCLIP) + continue; + + if (SV_TestEntityPosition (check, vec3_origin)) + Con_Print("entity in invalid position\n"); + } +} + +// DRESK - Support for Entity Contents Transition Event +/* +================ +SV_CheckContentsTransition + +returns true if entity had a valid contentstransition function call +================ +*/ +int SV_CheckContentsTransition(prvm_edict_t *ent, const int nContents) +{ + int bValidFunctionCall; + + // Default Valid Function Call to False + bValidFunctionCall = false; + + if(PRVM_serveredictfloat(ent, watertype) != nContents) + { // Changed Contents + // Acquire Contents Transition Function from QC + if(PRVM_serveredictfunction(ent, contentstransition)) + { // Valid Function; Execute + // Assign Valid Function + bValidFunctionCall = true; + // Prepare Parameters (Original Contents, New Contents) + // Original Contents + PRVM_G_FLOAT(OFS_PARM0) = PRVM_serveredictfloat(ent, watertype); + // New Contents + PRVM_G_FLOAT(OFS_PARM1) = nContents; + // Assign Self + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + // Execute VM Function + PRVM_ExecuteProgram(PRVM_serveredictfunction(ent, contentstransition), "contentstransition: NULL function"); + } + } + + // Return if Function Call was Valid + return bValidFunctionCall; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (prvm_edict_t *ent) +{ + int i; + float wishspeed; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (IS_NAN(PRVM_serveredictvector(ent, velocity)[i])) + { + Con_Printf("Got a NaN velocity on entity #%i (%s)\n", PRVM_NUM_FOR_EDICT(ent), PRVM_GetString(PRVM_serveredictstring(ent, classname))); + PRVM_serveredictvector(ent, velocity)[i] = 0; + } + if (IS_NAN(PRVM_serveredictvector(ent, origin)[i])) + { + Con_Printf("Got a NaN origin on entity #%i (%s)\n", PRVM_NUM_FOR_EDICT(ent), PRVM_GetString(PRVM_serveredictstring(ent, classname))); + PRVM_serveredictvector(ent, origin)[i] = 0; + } + } + + // LordHavoc: a hack to ensure that the (rather silly) id1 quakec + // player_run/player_stand1 does not horribly malfunction if the + // velocity becomes a denormalized float + if (VectorLength2(PRVM_serveredictvector(ent, velocity)) < 0.0001) + VectorClear(PRVM_serveredictvector(ent, velocity)); + + // LordHavoc: max velocity fix, inspired by Maddes's source fixes, but this is faster + wishspeed = DotProduct(PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, velocity)); + if (wishspeed > sv_maxvelocity.value * sv_maxvelocity.value) + { + wishspeed = sv_maxvelocity.value / sqrt(wishspeed); + PRVM_serveredictvector(ent, velocity)[0] *= wishspeed; + PRVM_serveredictvector(ent, velocity)[1] *= wishspeed; + PRVM_serveredictvector(ent, velocity)[2] *= wishspeed; + } +} + +/* +============= +SV_RunThink + +Runs thinking code if time. There is some play in the exact time the think +function will be called, because it is called before any movement is done +in a frame. Not used for pushmove objects, because they must be exact. +Returns false if the entity removed itself. +============= +*/ +qboolean SV_RunThink (prvm_edict_t *ent) +{ + int iterations; + + // don't let things stay in the past. + // it is possible to start that way by a trigger with a local time. + if (PRVM_serveredictfloat(ent, nextthink) <= 0 || PRVM_serveredictfloat(ent, nextthink) > sv.time + sv.frametime) + return true; + + for (iterations = 0;iterations < 128 && !ent->priv.server->free;iterations++) + { + PRVM_serverglobalfloat(time) = max(sv.time, PRVM_serveredictfloat(ent, nextthink)); + PRVM_serveredictfloat(ent, nextthink) = 0; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_ExecuteProgram (PRVM_serveredictfunction(ent, think), "QC function self.think is missing"); + // mods often set nextthink to time to cause a think every frame, + // we don't want to loop in that case, so exit if the new nextthink is + // <= the time the qc was told, also exit if it is past the end of the + // frame + if (PRVM_serveredictfloat(ent, nextthink) <= PRVM_serverglobalfloat(time) || PRVM_serveredictfloat(ent, nextthink) > sv.time + sv.frametime || !sv_gameplayfix_multiplethinksperframe.integer) + break; + } + return !ent->priv.server->free; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +extern void VM_SetTraceGlobals(const trace_t *trace); +extern sizebuf_t vm_tempstringsbuf; +void SV_Impact (prvm_edict_t *e1, trace_t *trace) +{ + int restorevm_tempstringsbuf_cursize; + int old_self, old_other; + prvm_edict_t *e2 = (prvm_edict_t *)trace->ent; + + old_self = PRVM_serverglobaledict(self); + old_other = PRVM_serverglobaledict(other); + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + + VM_SetTraceGlobals(trace); + + PRVM_serverglobalfloat(time) = sv.time; + if (!e1->priv.server->free && !e2->priv.server->free && PRVM_serveredictfunction(e1, touch) && PRVM_serveredictfloat(e1, solid) != SOLID_NOT) + { + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(e1); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(e2); + PRVM_ExecuteProgram (PRVM_serveredictfunction(e1, touch), "QC function self.touch is missing"); + } + + if (!e1->priv.server->free && !e2->priv.server->free && PRVM_serveredictfunction(e2, touch) && PRVM_serveredictfloat(e2, solid) != SOLID_NOT) + { + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(e2); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(e1); + VectorCopy(PRVM_serveredictvector(e2, origin), PRVM_serverglobalvector(trace_endpos)); + VectorNegate(trace->plane.normal, PRVM_serverglobalvector(trace_plane_normal)); + PRVM_serverglobalfloat(trace_plane_dist) = -trace->plane.dist; + PRVM_serverglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(e1); + PRVM_serverglobalfloat(trace_dpstartcontents) = 0; + PRVM_serverglobalfloat(trace_dphitcontents) = 0; + PRVM_serverglobalfloat(trace_dphitq3surfaceflags) = 0; + PRVM_serverglobalstring(trace_dphittexturename) = 0; + PRVM_ExecuteProgram (PRVM_serveredictfunction(e2, touch), "QC function self.touch is missing"); + } + + PRVM_serverglobaledict(self) = old_self; + PRVM_serverglobaledict(other) = old_other; + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 +void ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + int i; + float backoff; + + backoff = -DotProduct (in, normal) * overbounce; + VectorMA(in, backoff, normal, out); + + for (i = 0;i < 3;i++) + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +8 = teleported by touch method +If stepnormal is not NULL, the plane normal of any vertical wall hit will be stored +============ +*/ +static float SV_Gravity (prvm_edict_t *ent); +static qboolean SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, qboolean failonbmodelstartsolid, qboolean dolink); +#define MAX_CLIP_PLANES 5 +static int SV_FlyMove (prvm_edict_t *ent, float time, qboolean applygravity, float *stepnormal, int hitsupercontentsmask, float stepheight) +{ + int blocked, bumpcount; + int i, j, numplanes; + float d, time_left, gravity; + vec3_t dir, push, planes[MAX_CLIP_PLANES], primal_velocity, original_velocity, new_velocity; +#if 0 + vec3_t end; +#endif + trace_t trace; + if (time <= 0) + return 0; + gravity = 0; + + if(sv_gameplayfix_nogravityonground.integer) + if((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) + applygravity = false; + + if (applygravity) + { + if (sv_gameplayfix_gravityunaffectedbyticrate.integer) + { + gravity = SV_Gravity(ent) * 0.5f; + PRVM_serveredictvector(ent, velocity)[2] -= gravity; + } + else + { + applygravity = false; + PRVM_serveredictvector(ent, velocity)[2] -= SV_Gravity(ent); + } + } + blocked = 0; + VectorCopy(PRVM_serveredictvector(ent, velocity), original_velocity); + VectorCopy(PRVM_serveredictvector(ent, velocity), primal_velocity); + numplanes = 0; + time_left = time; + for (bumpcount = 0;bumpcount < MAX_CLIP_PLANES;bumpcount++) + { + if (!PRVM_serveredictvector(ent, velocity)[0] && !PRVM_serveredictvector(ent, velocity)[1] && !PRVM_serveredictvector(ent, velocity)[2]) + break; + + VectorScale(PRVM_serveredictvector(ent, velocity), time_left, push); + if(!SV_PushEntity(&trace, ent, push, false, false)) + { + // we got teleported by a touch function + // let's abort the move + blocked |= 8; + break; + } + + if (trace.fraction == 1) + break; + if (trace.plane.normal[2]) + { + if (trace.plane.normal[2] > 0.7) + { + // floor + blocked |= 1; + + if (!trace.ent) + { + Con_Printf ("SV_FlyMove: !trace.ent"); + trace.ent = prog->edicts; + } + + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + } + } + else if (stepheight) + { + // step - handle it immediately + vec3_t org; + vec3_t steppush; + trace_t steptrace; + trace_t steptrace2; + trace_t steptrace3; + //Con_Printf("step %f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + VectorSet(steppush, 0, 0, stepheight); + VectorCopy(PRVM_serveredictvector(ent, origin), org); + if(!SV_PushEntity(&steptrace, ent, steppush, false, false)) + { + blocked |= 8; + break; + } + //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + if(!SV_PushEntity(&steptrace2, ent, push, false, false)) + { + blocked |= 8; + break; + } + //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + VectorSet(steppush, 0, 0, org[2] - PRVM_serveredictvector(ent, origin)[2]); + if(!SV_PushEntity(&steptrace3, ent, steppush, false, false)) + { + blocked |= 8; + break; + } + //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + // accept the new position if it made some progress... + if (fabs(PRVM_serveredictvector(ent, origin)[0] - org[0]) >= 0.03125 || fabs(PRVM_serveredictvector(ent, origin)[1] - org[1]) >= 0.03125) + { + //Con_Printf("accepted (delta %f %f %f)\n", PRVM_serveredictvector(ent, origin)[0] - org[0], PRVM_serveredictvector(ent, origin)[1] - org[1], PRVM_serveredictvector(ent, origin)[2] - org[2]); + trace = steptrace2; + VectorCopy(PRVM_serveredictvector(ent, origin), trace.endpos); + time_left *= 1 - trace.fraction; + numplanes = 0; + continue; + } + else + { + //Con_Printf("REJECTED (delta %f %f %f)\n", PRVM_serveredictvector(ent, origin)[0] - org[0], PRVM_serveredictvector(ent, origin)[1] - org[1], PRVM_serveredictvector(ent, origin)[2] - org[2]); + VectorCopy(org, PRVM_serveredictvector(ent, origin)); + } + } + else + { + // step - return it to caller + blocked |= 2; + // save the trace for player extrafriction + if (stepnormal) + VectorCopy(trace.plane.normal, stepnormal); + } + if (trace.fraction >= 0.001) + { + // actually covered some distance + VectorCopy(PRVM_serveredictvector(ent, velocity), original_velocity); + numplanes = 0; + } + + time_left *= 1 - trace.fraction; + + // clipped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { + // this shouldn't really happen + VectorClear(PRVM_serveredictvector(ent, velocity)); + blocked = 3; + break; + } + + /* + for (i = 0;i < numplanes;i++) + if (DotProduct(trace.plane.normal, planes[i]) > 0.99) + break; + if (i < numplanes) + { + VectorAdd(PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity)); + continue; + } + */ + + VectorCopy(trace.plane.normal, planes[numplanes]); + numplanes++; + + // modify original_velocity so it parallels all of the clip planes + for (i = 0;i < numplanes;i++) + { + ClipVelocity(original_velocity, planes[i], new_velocity, 1); + for (j = 0;j < numplanes;j++) + { + if (j != i) + { + // not ok + if (DotProduct(new_velocity, planes[j]) < 0) + break; + } + } + if (j == numplanes) + break; + } + + if (i != numplanes) + { + // go along this plane + VectorCopy(new_velocity, PRVM_serveredictvector(ent, velocity)); + } + else + { + // go along the crease + if (numplanes != 2) + { + VectorClear(PRVM_serveredictvector(ent, velocity)); + blocked = 7; + break; + } + CrossProduct(planes[0], planes[1], dir); + // LordHavoc: thanks to taniwha of QuakeForge for pointing out this fix for slowed falling in corners + VectorNormalize(dir); + d = DotProduct(dir, PRVM_serveredictvector(ent, velocity)); + VectorScale(dir, d, PRVM_serveredictvector(ent, velocity)); + } + + // if current velocity is against the original velocity, + // stop dead to avoid tiny occilations in sloping corners + if (DotProduct(PRVM_serveredictvector(ent, velocity), primal_velocity) <= 0) + { + VectorClear(PRVM_serveredictvector(ent, velocity)); + break; + } + } + + //Con_Printf("entity %i final: blocked %i velocity %f %f %f\n", ent - prog->edicts, blocked, PRVM_serveredictvector(ent, velocity)[0], PRVM_serveredictvector(ent, velocity)[1], PRVM_serveredictvector(ent, velocity)[2]); + + /* + if ((blocked & 1) == 0 && bumpcount > 1) + { + // LordHavoc: fix the 'fall to your death in a wedge corner' glitch + // flag ONGROUND if there's ground under it + trace = SV_TraceBox(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), end, MOVE_NORMAL, ent, hitsupercontentsmask); + } + */ + + // LordHavoc: this came from QW and allows you to get out of water more easily + if (sv_gameplayfix_easierwaterjump.integer && ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP) && !(blocked & 8)) + VectorCopy(primal_velocity, PRVM_serveredictvector(ent, velocity)); + if (applygravity && !((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) + PRVM_serveredictvector(ent, velocity)[2] -= gravity; + return blocked; +} + +/* +============ +SV_Gravity + +============ +*/ +static float SV_Gravity (prvm_edict_t *ent) +{ + float ent_gravity; + + ent_gravity = PRVM_serveredictfloat(ent, gravity); + if (!ent_gravity) + ent_gravity = 1.0f; + return ent_gravity * sv_gravity.value * sv.frametime; +} + + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +The trace struct is filled with the trace that has been done. +Returns true if the push did not result in the entity being teleported by QC code. +============ +*/ +static qboolean SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, qboolean failonbmodelstartsolid, qboolean dolink) +{ + int solid; + int movetype; + int type; + int bump; + vec3_t mins, maxs; + vec3_t original, original_velocity; + vec3_t start; + vec3_t end; + + solid = (int)PRVM_serveredictfloat(ent, solid); + movetype = (int)PRVM_serveredictfloat(ent, movetype); + VectorCopy(PRVM_serveredictvector(ent, mins), mins); + VectorCopy(PRVM_serveredictvector(ent, maxs), maxs); + + // move start position out of solids + if (sv_gameplayfix_nudgeoutofsolid.integer && sv_gameplayfix_nudgeoutofsolid_separation.value >= 0) + { + trace_t stucktrace; + vec3_t stuckorigin; + vec3_t stuckmins, stuckmaxs; + vec_t nudge; + vec_t separation = sv_gameplayfix_nudgeoutofsolid_separation.value; + if (sv.worldmodel && sv.worldmodel->brushq1.numclipnodes) + separation = 0.0f; // when using hulls, it can not be enlarged + VectorCopy(PRVM_serveredictvector(ent, origin), stuckorigin); + VectorCopy(mins, stuckmins); + VectorCopy(maxs, stuckmaxs); + stuckmins[0] -= separation; + stuckmins[1] -= separation; + stuckmins[2] -= separation; + stuckmaxs[0] += separation; + stuckmaxs[1] += separation; + stuckmaxs[2] += separation; + for (bump = 0;bump < 10;bump++) + { + stucktrace = SV_TraceBox(stuckorigin, stuckmins, stuckmaxs, stuckorigin, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent)); + if (!stucktrace.bmodelstartsolid || stucktrace.startdepth >= 0) + { + // found a good location, use it + VectorCopy(stuckorigin, PRVM_serveredictvector(ent, origin)); + break; + } + nudge = -stucktrace.startdepth; + VectorMA(stuckorigin, nudge, stucktrace.startdepthnormal, stuckorigin); + } + } + + VectorCopy(PRVM_serveredictvector(ent, origin), start); + VectorAdd(start, push, end); + + if (movetype == MOVETYPE_FLYMISSILE) + type = MOVE_MISSILE; + else if (solid == SOLID_TRIGGER || solid == SOLID_NOT) + type = MOVE_NOMONSTERS; // only clip against bmodels + else + type = MOVE_NORMAL; + + *trace = SV_TraceBox(start, mins, maxs, end, type, ent, SV_GenericHitSuperContentsMask(ent)); + if (trace->bmodelstartsolid && failonbmodelstartsolid) + return true; + + VectorCopy(trace->endpos, PRVM_serveredictvector(ent, origin)); + + VectorCopy(PRVM_serveredictvector(ent, origin), original); + VectorCopy(PRVM_serveredictvector(ent, velocity), original_velocity); + + SV_LinkEdict(ent); + +#if 0 + if(!trace->startsolid) + if(SV_TraceBox(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), PRVM_serveredictvector(ent, origin), type, ent, SV_GenericHitSuperContentsMask(ent)).startsolid) + { + Con_Printf("something eeeeevil happened\n"); + } +#endif + + if (dolink) + SV_LinkEdict_TouchAreaGrid(ent); + + if((PRVM_serveredictfloat(ent, solid) >= SOLID_TRIGGER && trace->ent && (!((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) || PRVM_serveredictedict(ent, groundentity) != PRVM_EDICT_TO_PROG(trace->ent)))) + SV_Impact (ent, trace); + + return VectorCompare(PRVM_serveredictvector(ent, origin), original) && VectorCompare(PRVM_serveredictvector(ent, velocity), original_velocity); +} + + +/* +============ +SV_PushMove + +============ +*/ +void SV_PushMove (prvm_edict_t *pusher, float movetime) +{ + int i, e, index; + int pusherowner, pusherprog; + int checkcontents; + qboolean rotated; + float savesolid, movetime2, pushltime; + vec3_t mins, maxs, move, move1, moveangle, pushorig, pushang, a, forward, left, up, org; + int num_moved; + int numcheckentities; + static prvm_edict_t *checkentities[MAX_EDICTS]; + dp_model_t *pushermodel; + trace_t trace, trace2; + matrix4x4_t pusherfinalmatrix, pusherfinalimatrix; + static unsigned short moved_edicts[MAX_EDICTS]; + + if (!PRVM_serveredictvector(pusher, velocity)[0] && !PRVM_serveredictvector(pusher, velocity)[1] && !PRVM_serveredictvector(pusher, velocity)[2] && !PRVM_serveredictvector(pusher, avelocity)[0] && !PRVM_serveredictvector(pusher, avelocity)[1] && !PRVM_serveredictvector(pusher, avelocity)[2]) + { + PRVM_serveredictfloat(pusher, ltime) += movetime; + return; + } + + switch ((int) PRVM_serveredictfloat(pusher, solid)) + { + // LordHavoc: valid pusher types + case SOLID_BSP: + case SOLID_BBOX: + case SOLID_SLIDEBOX: + case SOLID_CORPSE: // LordHavoc: this would be weird... + break; + // LordHavoc: no collisions + case SOLID_NOT: + case SOLID_TRIGGER: + VectorMA (PRVM_serveredictvector(pusher, origin), movetime, PRVM_serveredictvector(pusher, velocity), PRVM_serveredictvector(pusher, origin)); + VectorMA (PRVM_serveredictvector(pusher, angles), movetime, PRVM_serveredictvector(pusher, avelocity), PRVM_serveredictvector(pusher, angles)); + PRVM_serveredictvector(pusher, angles)[0] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[0] * (1.0 / 360.0)); + PRVM_serveredictvector(pusher, angles)[1] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[1] * (1.0 / 360.0)); + PRVM_serveredictvector(pusher, angles)[2] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[2] * (1.0 / 360.0)); + PRVM_serveredictfloat(pusher, ltime) += movetime; + SV_LinkEdict(pusher); + return; + default: + Con_Printf("SV_PushMove: entity #%i, unrecognized solid type %f\n", PRVM_NUM_FOR_EDICT(pusher), PRVM_serveredictfloat(pusher, solid)); + return; + } + index = (int) PRVM_serveredictfloat(pusher, modelindex); + if (index < 1 || index >= MAX_MODELS) + { + Con_Printf("SV_PushMove: entity #%i has an invalid modelindex %f\n", PRVM_NUM_FOR_EDICT(pusher), PRVM_serveredictfloat(pusher, modelindex)); + return; + } + pushermodel = SV_GetModelByIndex(index); + pusherowner = PRVM_serveredictedict(pusher, owner); + pusherprog = PRVM_EDICT_TO_PROG(pusher); + + rotated = VectorLength2(PRVM_serveredictvector(pusher, angles)) + VectorLength2(PRVM_serveredictvector(pusher, avelocity)) > 0; + + movetime2 = movetime; + VectorScale(PRVM_serveredictvector(pusher, velocity), movetime2, move1); + VectorScale(PRVM_serveredictvector(pusher, avelocity), movetime2, moveangle); + if (moveangle[0] || moveangle[2]) + { + for (i = 0;i < 3;i++) + { + if (move1[i] > 0) + { + mins[i] = pushermodel->rotatedmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->rotatedmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + else + { + mins[i] = pushermodel->rotatedmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->rotatedmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + } + } + else if (moveangle[1]) + { + for (i = 0;i < 3;i++) + { + if (move1[i] > 0) + { + mins[i] = pushermodel->yawmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->yawmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + else + { + mins[i] = pushermodel->yawmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->yawmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + } + } + else + { + for (i = 0;i < 3;i++) + { + if (move1[i] > 0) + { + mins[i] = pushermodel->normalmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->normalmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + else + { + mins[i] = pushermodel->normalmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->normalmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + } + } + + VectorNegate (moveangle, a); + AngleVectorsFLU (a, forward, left, up); + + VectorCopy (PRVM_serveredictvector(pusher, origin), pushorig); + VectorCopy (PRVM_serveredictvector(pusher, angles), pushang); + pushltime = PRVM_serveredictfloat(pusher, ltime); + +// move the pusher to its final position + + VectorMA (PRVM_serveredictvector(pusher, origin), movetime, PRVM_serveredictvector(pusher, velocity), PRVM_serveredictvector(pusher, origin)); + VectorMA (PRVM_serveredictvector(pusher, angles), movetime, PRVM_serveredictvector(pusher, avelocity), PRVM_serveredictvector(pusher, angles)); + PRVM_serveredictfloat(pusher, ltime) += movetime; + SV_LinkEdict(pusher); + + pushermodel = SV_GetModelFromEdict(pusher); + Matrix4x4_CreateFromQuakeEntity(&pusherfinalmatrix, PRVM_serveredictvector(pusher, origin)[0], PRVM_serveredictvector(pusher, origin)[1], PRVM_serveredictvector(pusher, origin)[2], PRVM_serveredictvector(pusher, angles)[0], PRVM_serveredictvector(pusher, angles)[1], PRVM_serveredictvector(pusher, angles)[2], 1); + Matrix4x4_Invert_Simple(&pusherfinalimatrix, &pusherfinalmatrix); + + savesolid = PRVM_serveredictfloat(pusher, solid); + +// see if any solid entities are inside the final position + num_moved = 0; + + numcheckentities = World_EntitiesInBox(&sv.world, mins, maxs, MAX_EDICTS, checkentities); + for (e = 0;e < numcheckentities;e++) + { + prvm_edict_t *check = checkentities[e]; + int movetype = (int)PRVM_serveredictfloat(check, movetype); + switch(movetype) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_FOLLOW: + case MOVETYPE_NOCLIP: + case MOVETYPE_FAKEPUSH: + continue; + default: + break; + } + + if (PRVM_serveredictedict(check, owner) == pusherprog) + continue; + + if (pusherowner == PRVM_EDICT_TO_PROG(check)) + continue; + + //Con_Printf("%i %s ", PRVM_NUM_FOR_EDICT(check), PRVM_GetString(PRVM_serveredictstring(check, classname))); + + // tell any MOVETYPE_STEP entity that it may need to check for water transitions + check->priv.server->waterposition_forceupdate = true; + + checkcontents = SV_GenericHitSuperContentsMask(check); + + // if the entity is standing on the pusher, it will definitely be moved + // if the entity is not standing on the pusher, but is in the pusher's + // final position, move it + if (!((int)PRVM_serveredictfloat(check, flags) & FL_ONGROUND) || PRVM_PROG_TO_EDICT(PRVM_serveredictedict(check, groundentity)) != pusher) + { + Collision_ClipToGenericEntity(&trace, pushermodel, pusher->priv.server->frameblend, &pusher->priv.server->skeleton, PRVM_serveredictvector(pusher, mins), PRVM_serveredictvector(pusher, maxs), SUPERCONTENTS_BODY, &pusherfinalmatrix, &pusherfinalimatrix, PRVM_serveredictvector(check, origin), PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs), PRVM_serveredictvector(check, origin), checkcontents); + //trace = SV_TraceBox(PRVM_serveredictvector(check, origin), PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs), PRVM_serveredictvector(check, origin), MOVE_NOMONSTERS, check, checkcontents); + if (!trace.startsolid) + { + //Con_Printf("- not in solid\n"); + continue; + } + } + + if (rotated) + { + vec3_t org2; + VectorSubtract (PRVM_serveredictvector(check, origin), PRVM_serveredictvector(pusher, origin), org); + org2[0] = DotProduct (org, forward); + org2[1] = DotProduct (org, left); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move); + VectorAdd (move, move1, move); + } + else + VectorCopy (move1, move); + + //Con_Printf("- pushing %f %f %f\n", move[0], move[1], move[2]); + + VectorCopy (PRVM_serveredictvector(check, origin), check->priv.server->moved_from); + VectorCopy (PRVM_serveredictvector(check, angles), check->priv.server->moved_fromangles); + moved_edicts[num_moved++] = PRVM_NUM_FOR_EDICT(check); + + // physics objects need better collisions than this code can do + if (movetype == MOVETYPE_PHYSICS) + { + VectorAdd(PRVM_serveredictvector(check, origin), move, PRVM_serveredictvector(check, origin)); + SV_LinkEdict(check); + SV_LinkEdict_TouchAreaGrid(check); + continue; + } + + // try moving the contacted entity + PRVM_serveredictfloat(pusher, solid) = SOLID_NOT; + if(!SV_PushEntity (&trace, check, move, true, true)) + { + // entity "check" got teleported + PRVM_serveredictvector(check, angles)[1] += trace.fraction * moveangle[1]; + PRVM_serveredictfloat(pusher, solid) = savesolid; // was SOLID_BSP + continue; // pushed enough + } + // FIXME: turn players specially + PRVM_serveredictvector(check, angles)[1] += trace.fraction * moveangle[1]; + PRVM_serveredictfloat(pusher, solid) = savesolid; // was SOLID_BSP + //Con_Printf("%s:%d frac %f startsolid %d bmodelstartsolid %d allsolid %d\n", __FILE__, __LINE__, trace.fraction, trace.startsolid, trace.bmodelstartsolid, trace.allsolid); + + // this trace.fraction < 1 check causes items to fall off of pushers + // if they pass under or through a wall + // the groundentity check causes items to fall off of ledges + if (PRVM_serveredictfloat(check, movetype) != MOVETYPE_WALK && (trace.fraction < 1 || PRVM_PROG_TO_EDICT(PRVM_serveredictedict(check, groundentity)) != pusher)) + PRVM_serveredictfloat(check, flags) = (int)PRVM_serveredictfloat(check, flags) & ~FL_ONGROUND; + + // if it is still inside the pusher, block + Collision_ClipToGenericEntity(&trace, pushermodel, pusher->priv.server->frameblend, &pusher->priv.server->skeleton, PRVM_serveredictvector(pusher, mins), PRVM_serveredictvector(pusher, maxs), SUPERCONTENTS_BODY, &pusherfinalmatrix, &pusherfinalimatrix, PRVM_serveredictvector(check, origin), PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs), PRVM_serveredictvector(check, origin), checkcontents); + if (trace.startsolid) + { + // try moving the contacted entity a tiny bit further to account for precision errors + vec3_t move2; + PRVM_serveredictfloat(pusher, solid) = SOLID_NOT; + VectorScale(move, 1.1, move2); + VectorCopy (check->priv.server->moved_from, PRVM_serveredictvector(check, origin)); + VectorCopy (check->priv.server->moved_fromangles, PRVM_serveredictvector(check, angles)); + if(!SV_PushEntity (&trace2, check, move2, true, true)) + { + // entity "check" got teleported + continue; + } + PRVM_serveredictfloat(pusher, solid) = savesolid; + Collision_ClipToGenericEntity(&trace, pushermodel, pusher->priv.server->frameblend, &pusher->priv.server->skeleton, PRVM_serveredictvector(pusher, mins), PRVM_serveredictvector(pusher, maxs), SUPERCONTENTS_BODY, &pusherfinalmatrix, &pusherfinalimatrix, PRVM_serveredictvector(check, origin), PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs), PRVM_serveredictvector(check, origin), checkcontents); + if (trace.startsolid) + { + // try moving the contacted entity a tiny bit less to account for precision errors + PRVM_serveredictfloat(pusher, solid) = SOLID_NOT; + VectorScale(move, 0.9, move2); + VectorCopy (check->priv.server->moved_from, PRVM_serveredictvector(check, origin)); + VectorCopy (check->priv.server->moved_fromangles, PRVM_serveredictvector(check, angles)); + if(!SV_PushEntity (&trace2, check, move2, true, true)) + { + // entity "check" got teleported + continue; + } + PRVM_serveredictfloat(pusher, solid) = savesolid; + Collision_ClipToGenericEntity(&trace, pushermodel, pusher->priv.server->frameblend, &pusher->priv.server->skeleton, PRVM_serveredictvector(pusher, mins), PRVM_serveredictvector(pusher, maxs), SUPERCONTENTS_BODY, &pusherfinalmatrix, &pusherfinalimatrix, PRVM_serveredictvector(check, origin), PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs), PRVM_serveredictvector(check, origin), checkcontents); + if (trace.startsolid) + { + // still inside pusher, so it's really blocked + + // fail the move + if (PRVM_serveredictvector(check, mins)[0] == PRVM_serveredictvector(check, maxs)[0]) + continue; + if (PRVM_serveredictfloat(check, solid) == SOLID_NOT || PRVM_serveredictfloat(check, solid) == SOLID_TRIGGER) + { + // corpse + PRVM_serveredictvector(check, mins)[0] = PRVM_serveredictvector(check, mins)[1] = 0; + VectorCopy (PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs)); + continue; + } + + VectorCopy (pushorig, PRVM_serveredictvector(pusher, origin)); + VectorCopy (pushang, PRVM_serveredictvector(pusher, angles)); + PRVM_serveredictfloat(pusher, ltime) = pushltime; + SV_LinkEdict(pusher); + + // move back any entities we already moved + for (i = 0;i < num_moved;i++) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(moved_edicts[i]); + VectorCopy (ed->priv.server->moved_from, PRVM_serveredictvector(ed, origin)); + VectorCopy (ed->priv.server->moved_fromangles, PRVM_serveredictvector(ed, angles)); + SV_LinkEdict(ed); + } + + // if the pusher has a "blocked" function, call it, otherwise just stay in place until the obstacle is gone + if (PRVM_serveredictfunction(pusher, blocked)) + { + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(pusher); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(check); + PRVM_ExecuteProgram (PRVM_serveredictfunction(pusher, blocked), "QC function self.blocked is missing"); + } + break; + } + } + } + } + PRVM_serveredictvector(pusher, angles)[0] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[0] * (1.0 / 360.0)); + PRVM_serveredictvector(pusher, angles)[1] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[1] * (1.0 / 360.0)); + PRVM_serveredictvector(pusher, angles)[2] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[2] * (1.0 / 360.0)); +} + +/* +================ +SV_Physics_Pusher + +================ +*/ +void SV_Physics_Pusher (prvm_edict_t *ent) +{ + float thinktime, oldltime, movetime; + + oldltime = PRVM_serveredictfloat(ent, ltime); + + thinktime = PRVM_serveredictfloat(ent, nextthink); + if (thinktime < PRVM_serveredictfloat(ent, ltime) + sv.frametime) + { + movetime = thinktime - PRVM_serveredictfloat(ent, ltime); + if (movetime < 0) + movetime = 0; + } + else + movetime = sv.frametime; + + if (movetime) + // advances PRVM_serveredictfloat(ent, ltime) if not blocked + SV_PushMove (ent, movetime); + + if (thinktime > oldltime && thinktime <= PRVM_serveredictfloat(ent, ltime)) + { + PRVM_serveredictfloat(ent, nextthink) = 0; + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_ExecuteProgram (PRVM_serveredictfunction(ent, think), "QC function self.think is missing"); + } +} + + +/* +=============================================================================== + +CLIENT MOVEMENT + +=============================================================================== +*/ + +static float unstickoffsets[] = +{ + // poutting -/+z changes first as they are least weird + 0, 0, -1, + 0, 0, 1, + // x or y changes + -1, 0, 0, + 1, 0, 0, + 0, -1, 0, + 0, 1, 0, + // x and y changes + -1, -1, 0, + 1, -1, 0, + -1, 1, 0, + 1, 1, 0, +}; + +typedef enum unstickresult_e +{ + UNSTICK_STUCK = 0, + UNSTICK_GOOD = 1, + UNSTICK_UNSTUCK = 2 +} +unstickresult_t; + +unstickresult_t SV_UnstickEntityReturnOffset (prvm_edict_t *ent, vec3_t offset) +{ + int i, maxunstick; + + // if not stuck in a bmodel, just return + if (!SV_TestEntityPosition(ent, vec3_origin)) + return UNSTICK_GOOD; + + for (i = 0;i < (int)(sizeof(unstickoffsets) / sizeof(unstickoffsets[0]));i += 3) + { + if (!SV_TestEntityPosition(ent, unstickoffsets + i)) + { + VectorCopy(unstickoffsets + i, offset); + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); + return UNSTICK_UNSTUCK; + } + } + + maxunstick = (int) ((PRVM_serveredictvector(ent, maxs)[2] - PRVM_serveredictvector(ent, mins)[2]) * 0.36); + // magic number 0.36 allows unsticking by up to 17 units with the largest supported bbox + + for(i = 2; i <= maxunstick; ++i) + { + VectorClear(offset); + offset[2] = -i; + if (!SV_TestEntityPosition(ent, offset)) + { + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); + return UNSTICK_UNSTUCK; + } + offset[2] = i; + if (!SV_TestEntityPosition(ent, offset)) + { + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); + return UNSTICK_UNSTUCK; + } + } + + return UNSTICK_STUCK; +} + +qboolean SV_UnstickEntity (prvm_edict_t *ent) +{ + vec3_t offset; + switch(SV_UnstickEntityReturnOffset(ent, offset)) + { + case UNSTICK_GOOD: + return true; + case UNSTICK_UNSTUCK: + Con_DPrintf("Unstuck entity %i (classname \"%s\") with offset %f %f %f.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(PRVM_serveredictstring(ent, classname)), offset[0], offset[1], offset[2]); + return true; + case UNSTICK_STUCK: + if (developer_extra.integer) + Con_DPrintf("Stuck entity %i (classname \"%s\").\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(PRVM_serveredictstring(ent, classname))); + return false; + default: + Con_Printf("SV_UnstickEntityReturnOffset returned a value outside its enum.\n"); + return false; + } +} + +/* +============= +SV_CheckStuck + +This is a big hack to try and fix the rare case of getting stuck in the world +clipping hull. +============= +*/ +void SV_CheckStuck (prvm_edict_t *ent) +{ + vec3_t offset; + + switch(SV_UnstickEntityReturnOffset(ent, offset)) + { + case UNSTICK_GOOD: + VectorCopy (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, oldorigin)); + break; + case UNSTICK_UNSTUCK: + Con_DPrintf("Unstuck player entity %i (classname \"%s\") with offset %f %f %f.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(PRVM_serveredictstring(ent, classname)), offset[0], offset[1], offset[2]); + break; + case UNSTICK_STUCK: + VectorSubtract(PRVM_serveredictvector(ent, oldorigin), PRVM_serveredictvector(ent, origin), offset); + if (!SV_TestEntityPosition(ent, offset)) + { + Con_DPrintf("Unstuck player entity %i (classname \"%s\") by restoring oldorigin.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(PRVM_serveredictstring(ent, classname))); + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); + } + else + Con_DPrintf("Stuck player entity %i (classname \"%s\").\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(PRVM_serveredictstring(ent, classname))); + break; + default: + Con_Printf("SV_UnstickEntityReturnOffset returned a value outside its enum.\n"); + } +} + + +/* +============= +SV_CheckWater +============= +*/ +qboolean SV_CheckWater (prvm_edict_t *ent) +{ + int cont; + int nNativeContents; + vec3_t point; + + point[0] = PRVM_serveredictvector(ent, origin)[0]; + point[1] = PRVM_serveredictvector(ent, origin)[1]; + point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, mins)[2] + 1; + + // DRESK - Support for Entity Contents Transition Event + // NOTE: Some logic needed to be slightly re-ordered + // to not affect performance and allow for the feature. + + // Acquire Super Contents Prior to Resets + cont = SV_PointSuperContents(point); + // Acquire Native Contents Here + nNativeContents = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, cont); + + // DRESK - Support for Entity Contents Transition Event + if(PRVM_serveredictfloat(ent, watertype)) + // Entity did NOT Spawn; Check + SV_CheckContentsTransition(ent, nNativeContents); + + + PRVM_serveredictfloat(ent, waterlevel) = 0; + PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY; + cont = SV_PointSuperContents(point); + if (cont & (SUPERCONTENTS_LIQUIDSMASK)) + { + PRVM_serveredictfloat(ent, watertype) = nNativeContents; + PRVM_serveredictfloat(ent, waterlevel) = 1; + point[2] = PRVM_serveredictvector(ent, origin)[2] + (PRVM_serveredictvector(ent, mins)[2] + PRVM_serveredictvector(ent, maxs)[2])*0.5; + if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK)) + { + PRVM_serveredictfloat(ent, waterlevel) = 2; + point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, view_ofs)[2]; + if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK)) + PRVM_serveredictfloat(ent, waterlevel) = 3; + } + } + + return PRVM_serveredictfloat(ent, waterlevel) > 1; +} + +/* +============ +SV_WallFriction + +============ +*/ +void SV_WallFriction (prvm_edict_t *ent, float *stepnormal) +{ + float d, i; + vec3_t forward, into, side; + + AngleVectors (PRVM_serveredictvector(ent, v_angle), forward, NULL, NULL); + if ((d = DotProduct (stepnormal, forward) + 0.5) < 0) + { + // cut the tangential velocity + i = DotProduct (stepnormal, PRVM_serveredictvector(ent, velocity)); + VectorScale (stepnormal, i, into); + VectorSubtract (PRVM_serveredictvector(ent, velocity), into, side); + PRVM_serveredictvector(ent, velocity)[0] = side[0] * (1 + d); + PRVM_serveredictvector(ent, velocity)[1] = side[1] * (1 + d); + } +} + +#if 0 +/* +===================== +SV_TryUnstick + +Player has come to a dead stop, possibly due to the problem with limited +float precision at some angle joins in the BSP hull. + +Try fixing by pushing one pixel in each direction. + +This is a hack, but in the interest of good gameplay... +====================== +*/ +int SV_TryUnstick (prvm_edict_t *ent, vec3_t oldvel) +{ + int i, clip; + vec3_t oldorg, dir; + + VectorCopy (PRVM_serveredictvector(ent, origin), oldorg); + VectorClear (dir); + + for (i=0 ; i<8 ; i++) + { + // try pushing a little in an axial direction + switch (i) + { + case 0: dir[0] = 2; dir[1] = 0; break; + case 1: dir[0] = 0; dir[1] = 2; break; + case 2: dir[0] = -2; dir[1] = 0; break; + case 3: dir[0] = 0; dir[1] = -2; break; + case 4: dir[0] = 2; dir[1] = 2; break; + case 5: dir[0] = -2; dir[1] = 2; break; + case 6: dir[0] = 2; dir[1] = -2; break; + case 7: dir[0] = -2; dir[1] = -2; break; + } + + SV_PushEntity (&trace, ent, dir, false, true); + + // retry the original move + PRVM_serveredictvector(ent, velocity)[0] = oldvel[0]; + PRVM_serveredictvector(ent, velocity)[1] = oldvel[1]; + PRVM_serveredictvector(ent, velocity)[2] = 0; + clip = SV_FlyMove (ent, 0.1, NULL, SV_GenericHitSuperContentsMask(ent)); + + if (fabs(oldorg[1] - PRVM_serveredictvector(ent, origin)[1]) > 4 + || fabs(oldorg[0] - PRVM_serveredictvector(ent, origin)[0]) > 4) + { + Con_DPrint("TryUnstick - success.\n"); + return clip; + } + + // go back to the original pos and try again + VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); + } + + // still not moving + VectorClear (PRVM_serveredictvector(ent, velocity)); + Con_DPrint("TryUnstick - failure.\n"); + return 7; +} +#endif + +/* +===================== +SV_WalkMove + +Only used by players +====================== +*/ +void SV_WalkMove (prvm_edict_t *ent) +{ + int clip; + int oldonground; + //int originalmove_clip; + int originalmove_flags; + int originalmove_groundentity; + int hitsupercontentsmask; + int type; + vec3_t upmove, downmove, start_origin, start_velocity, stepnormal, originalmove_origin, originalmove_velocity; + trace_t downtrace, trace; + qboolean applygravity; + + // if frametime is 0 (due to client sending the same timestamp twice), + // don't move + if (sv.frametime <= 0) + return; + + SV_CheckStuck (ent); + + applygravity = !SV_CheckWater (ent) && PRVM_serveredictfloat(ent, movetype) == MOVETYPE_WALK && ! ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP); + + hitsupercontentsmask = SV_GenericHitSuperContentsMask(ent); + + SV_CheckVelocity(ent); + + // do a regular slide move unless it looks like you ran into a step + oldonground = (int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND; + + VectorCopy (PRVM_serveredictvector(ent, origin), start_origin); + VectorCopy (PRVM_serveredictvector(ent, velocity), start_velocity); + + clip = SV_FlyMove (ent, sv.frametime, applygravity, NULL, hitsupercontentsmask, sv_gameplayfix_stepmultipletimes.integer ? sv_stepheight.value : 0); + + if(sv_gameplayfix_downtracesupportsongroundflag.integer) + if(!(clip & 1)) + { + // only try this if there was no floor in the way in the trace (no, + // this check seems to be not REALLY necessary, because if clip & 1, + // our trace will hit that thing too) + VectorSet(upmove, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] + 1); + VectorSet(downmove, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] - 1); + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLYMISSILE) + type = MOVE_MISSILE; + else if (PRVM_serveredictfloat(ent, solid) == SOLID_TRIGGER || PRVM_serveredictfloat(ent, solid) == SOLID_NOT) + type = MOVE_NOMONSTERS; // only clip against bmodels + else + type = MOVE_NORMAL; + trace = SV_TraceBox(upmove, PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), downmove, type, ent, SV_GenericHitSuperContentsMask(ent)); + if(trace.fraction < 1 && trace.plane.normal[2] > 0.7) + clip |= 1; // but we HAVE found a floor + } + + // if the move did not hit the ground at any point, we're not on ground + if(!(clip & 1)) + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + + SV_CheckVelocity(ent); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + if(clip & 8) // teleport + return; + + if ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP) + return; + + if (sv_nostep.integer) + return; + + VectorCopy(PRVM_serveredictvector(ent, origin), originalmove_origin); + VectorCopy(PRVM_serveredictvector(ent, velocity), originalmove_velocity); + //originalmove_clip = clip; + originalmove_flags = (int)PRVM_serveredictfloat(ent, flags); + originalmove_groundentity = PRVM_serveredictedict(ent, groundentity); + + // if move didn't block on a step, return + if (clip & 2) + { + // if move was not trying to move into the step, return + if (fabs(start_velocity[0]) < 0.03125 && fabs(start_velocity[1]) < 0.03125) + return; + + if (PRVM_serveredictfloat(ent, movetype) != MOVETYPE_FLY) + { + // return if gibbed by a trigger + if (PRVM_serveredictfloat(ent, movetype) != MOVETYPE_WALK) + return; + + // only step up while jumping if that is enabled + if (!(sv_jumpstep.integer && sv_gameplayfix_stepwhilejumping.integer)) + if (!oldonground && PRVM_serveredictfloat(ent, waterlevel) == 0) + return; + } + + // try moving up and forward to go up a step + // back to start pos + VectorCopy (start_origin, PRVM_serveredictvector(ent, origin)); + VectorCopy (start_velocity, PRVM_serveredictvector(ent, velocity)); + + // move up + VectorClear (upmove); + upmove[2] = sv_stepheight.value; + if(!SV_PushEntity(&trace, ent, upmove, false, true)) + { + // we got teleported when upstepping... must abort the move + return; + } + + // move forward + PRVM_serveredictvector(ent, velocity)[2] = 0; + clip = SV_FlyMove (ent, sv.frametime, applygravity, stepnormal, hitsupercontentsmask, 0); + PRVM_serveredictvector(ent, velocity)[2] += start_velocity[2]; + if(clip & 8) + { + // we got teleported when upstepping... must abort the move + // note that z velocity handling may not be what QC expects here, but we cannot help it + return; + } + + SV_CheckVelocity(ent); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + // check for stuckness, possibly due to the limited precision of floats + // in the clipping hulls + if (clip + && fabs(originalmove_origin[1] - PRVM_serveredictvector(ent, origin)[1]) < 0.03125 + && fabs(originalmove_origin[0] - PRVM_serveredictvector(ent, origin)[0]) < 0.03125) + { + //Con_Printf("wall\n"); + // stepping up didn't make any progress, revert to original move + VectorCopy(originalmove_origin, PRVM_serveredictvector(ent, origin)); + VectorCopy(originalmove_velocity, PRVM_serveredictvector(ent, velocity)); + //clip = originalmove_clip; + PRVM_serveredictfloat(ent, flags) = originalmove_flags; + PRVM_serveredictedict(ent, groundentity) = originalmove_groundentity; + // now try to unstick if needed + //clip = SV_TryUnstick (ent, oldvel); + return; + } + + //Con_Printf("step - "); + + // extra friction based on view angle + if (clip & 2 && sv_wallfriction.integer) + SV_WallFriction (ent, stepnormal); + } + // don't do the down move if stepdown is disabled, moving upward, not in water, or the move started offground or ended onground + else if (!sv_gameplayfix_stepdown.integer || PRVM_serveredictfloat(ent, waterlevel) >= 3 || start_velocity[2] >= (1.0 / 32.0) || !oldonground || ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) + return; + + // move down + VectorClear (downmove); + downmove[2] = -sv_stepheight.value + start_velocity[2]*sv.frametime; + if(!SV_PushEntity (&downtrace, ent, downmove, false, true)) + { + // we got teleported when downstepping... must abort the move + return; + } + + if (downtrace.fraction < 1 && downtrace.plane.normal[2] > 0.7) + { + // this has been disabled so that you can't jump when you are stepping + // up while already jumping (also known as the Quake2 double jump bug) +#if 0 + // LordHavoc: disabled this check so you can walk on monsters/players + //if (PRVM_serveredictfloat(ent, solid) == SOLID_BSP) + { + //Con_Printf("onground\n"); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(downtrace.ent); + } +#endif + } + else + { + //Con_Printf("slope\n"); + // if the push down didn't end up on good ground, use the move without + // the step up. This happens near wall / slope combinations, and can + // cause the player to hop up higher on a slope too steep to climb + VectorCopy(originalmove_origin, PRVM_serveredictvector(ent, origin)); + VectorCopy(originalmove_velocity, PRVM_serveredictvector(ent, velocity)); + //clip = originalmove_clip; + PRVM_serveredictfloat(ent, flags) = originalmove_flags; + PRVM_serveredictedict(ent, groundentity) = originalmove_groundentity; + } + + SV_CheckVelocity(ent); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); +} + +//============================================================================ + +/* +============= +SV_Physics_Follow + +Entities that are "stuck" to another entity +============= +*/ +void SV_Physics_Follow (prvm_edict_t *ent) +{ + vec3_t vf, vr, vu, angles, v; + prvm_edict_t *e; + + // regular thinking + if (!SV_RunThink (ent)) + return; + + // LordHavoc: implemented rotation on MOVETYPE_FOLLOW objects + e = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, aiment)); + if (PRVM_serveredictvector(e, angles)[0] == PRVM_serveredictvector(ent, punchangle)[0] && PRVM_serveredictvector(e, angles)[1] == PRVM_serveredictvector(ent, punchangle)[1] && PRVM_serveredictvector(e, angles)[2] == PRVM_serveredictvector(ent, punchangle)[2]) + { + // quick case for no rotation + VectorAdd(PRVM_serveredictvector(e, origin), PRVM_serveredictvector(ent, view_ofs), PRVM_serveredictvector(ent, origin)); + } + else + { + angles[0] = -PRVM_serveredictvector(ent, punchangle)[0]; + angles[1] = PRVM_serveredictvector(ent, punchangle)[1]; + angles[2] = PRVM_serveredictvector(ent, punchangle)[2]; + AngleVectors (angles, vf, vr, vu); + v[0] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[0] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[0] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[0]; + v[1] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[1] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[1] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[1]; + v[2] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[2] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[2] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[2]; + angles[0] = -PRVM_serveredictvector(e, angles)[0]; + angles[1] = PRVM_serveredictvector(e, angles)[1]; + angles[2] = PRVM_serveredictvector(e, angles)[2]; + AngleVectors (angles, vf, vr, vu); + PRVM_serveredictvector(ent, origin)[0] = v[0] * vf[0] + v[1] * vf[1] + v[2] * vf[2] + PRVM_serveredictvector(e, origin)[0]; + PRVM_serveredictvector(ent, origin)[1] = v[0] * vr[0] + v[1] * vr[1] + v[2] * vr[2] + PRVM_serveredictvector(e, origin)[1]; + PRVM_serveredictvector(ent, origin)[2] = v[0] * vu[0] + v[1] * vu[1] + v[2] * vu[2] + PRVM_serveredictvector(e, origin)[2]; + } + VectorAdd (PRVM_serveredictvector(e, angles), PRVM_serveredictvector(ent, v_angle), PRVM_serveredictvector(ent, angles)); + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_CheckWaterTransition + +============= +*/ +void SV_CheckWaterTransition (prvm_edict_t *ent) +{ + int cont; + cont = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(PRVM_serveredictvector(ent, origin))); + if (!PRVM_serveredictfloat(ent, watertype)) + { + // just spawned here + PRVM_serveredictfloat(ent, watertype) = cont; + PRVM_serveredictfloat(ent, waterlevel) = 1; + return; + } + + // DRESK - Support for Entity Contents Transition Event + // NOTE: Call here BEFORE updating the watertype below, + // and suppress watersplash sound if a valid function + // call was made to allow for custom "splash" sounds. + if( !SV_CheckContentsTransition(ent, cont) ) + { // Contents Transition Function Invalid; Potentially Play Water Sound + // check if the entity crossed into or out of water + if (sv_sound_watersplash.string && ((PRVM_serveredictfloat(ent, watertype) == CONTENTS_WATER || PRVM_serveredictfloat(ent, watertype) == CONTENTS_SLIME) != (cont == CONTENTS_WATER || cont == CONTENTS_SLIME))) + SV_StartSound (ent, 0, sv_sound_watersplash.string, 255, 1, false); + } + + if (cont <= CONTENTS_WATER) + { + PRVM_serveredictfloat(ent, watertype) = cont; + PRVM_serveredictfloat(ent, waterlevel) = 1; + } + else + { + PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY; + PRVM_serveredictfloat(ent, waterlevel) = 0; + } +} + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (prvm_edict_t *ent) +{ + trace_t trace; + vec3_t move; + vec_t movetime; + int bump; + prvm_edict_t *groundentity; + +// if onground, return without moving + if ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) + { + groundentity = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, groundentity)); + if (PRVM_serveredictvector(ent, velocity)[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer) + { + // don't stick to ground if onground and moving upward + PRVM_serveredictfloat(ent, flags) -= FL_ONGROUND; + } + else if (!PRVM_serveredictedict(ent, groundentity) || !sv_gameplayfix_noairborncorpse.integer) + { + // we can trust FL_ONGROUND if groundentity is world because it never moves + return; + } + else if (ent->priv.server->suspendedinairflag && groundentity->priv.server->free) + { + // if ent was supported by a brush model on previous frame, + // and groundentity is now freed, set groundentity to 0 (world) + // which leaves it suspended in the air + PRVM_serveredictedict(ent, groundentity) = 0; + if (sv_gameplayfix_noairborncorpse_allowsuspendeditems.integer) + return; + } + else if (BoxesOverlap(ent->priv.server->cullmins, ent->priv.server->cullmaxs, groundentity->priv.server->cullmins, groundentity->priv.server->cullmaxs)) + { + // don't slide if still touching the groundentity + return; + } + } + ent->priv.server->suspendedinairflag = false; + + SV_CheckVelocity (ent); + +// add gravity + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_TOSS || PRVM_serveredictfloat(ent, movetype) == MOVETYPE_BOUNCE) + PRVM_serveredictvector(ent, velocity)[2] -= SV_Gravity(ent); + +// move angles + VectorMA (PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); + + movetime = sv.frametime; + for (bump = 0;bump < MAX_CLIP_PLANES && movetime > 0;bump++) + { + // move origin + VectorScale (PRVM_serveredictvector(ent, velocity), movetime, move); + if(!SV_PushEntity (&trace, ent, move, true, true)) + return; // teleported + if (ent->priv.server->free) + return; + if (trace.bmodelstartsolid) + { + // try to unstick the entity + SV_UnstickEntity(ent); + if(!SV_PushEntity (&trace, ent, move, false, true)) + return; // teleported + if (ent->priv.server->free) + return; + } + if (trace.fraction == 1) + break; + movetime *= 1 - min(1, trace.fraction); + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_BOUNCEMISSILE) + { + float bouncefactor; + bouncefactor = PRVM_serveredictfloat(ent, bouncefactor); + if (!bouncefactor) + bouncefactor = 1.0f; + + ClipVelocity (PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1 + bouncefactor); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + } + else if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_BOUNCE) + { + float d, ent_gravity; + float bouncefactor; + float bouncestop; + + bouncefactor = PRVM_serveredictfloat(ent, bouncefactor); + if (!bouncefactor) + bouncefactor = 0.5f; + + bouncestop = PRVM_serveredictfloat(ent, bouncestop); + if (!bouncestop) + bouncestop = 60.0f / 800.0f; + + ClipVelocity (PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1 + bouncefactor); + ent_gravity = PRVM_serveredictfloat(ent, gravity); + if (!ent_gravity) + ent_gravity = 1.0f; + // LordHavoc: fixed grenades not bouncing when fired down a slope + if (sv_gameplayfix_grenadebouncedownslopes.integer) + { + d = DotProduct(trace.plane.normal, PRVM_serveredictvector(ent, velocity)); + if (trace.plane.normal[2] > 0.7 && fabs(d) < sv_gravity.value * bouncestop * ent_gravity) + { + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + VectorClear (PRVM_serveredictvector(ent, velocity)); + VectorClear (PRVM_serveredictvector(ent, avelocity)); + } + else + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + } + else + { + if (trace.plane.normal[2] > 0.7 && PRVM_serveredictvector(ent, velocity)[2] < sv_gravity.value * bouncestop * ent_gravity) + { + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + VectorClear (PRVM_serveredictvector(ent, velocity)); + VectorClear (PRVM_serveredictvector(ent, avelocity)); + } + else + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + } + } + else + { + ClipVelocity (PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1.0); + if (trace.plane.normal[2] > 0.7) + { + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + if (PRVM_serveredictfloat(((prvm_edict_t *)trace.ent), solid) == SOLID_BSP) + ent->priv.server->suspendedinairflag = true; + VectorClear (PRVM_serveredictvector(ent, velocity)); + VectorClear (PRVM_serveredictvector(ent, avelocity)); + } + else + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + } + if (!sv_gameplayfix_slidemoveprojectiles.integer || (PRVM_serveredictfloat(ent, movetype) != MOVETYPE_BOUNCE && PRVM_serveredictfloat(ent, movetype) == MOVETYPE_BOUNCEMISSILE) || ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) + break; + } + +// check for in water + SV_CheckWaterTransition (ent); +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +============= +*/ +void SV_Physics_Step (prvm_edict_t *ent) +{ + int flags = (int)PRVM_serveredictfloat(ent, flags); + + // DRESK + // Backup Velocity in the event that movetypesteplandevent is called, + // to provide a parameter with the entity's velocity at impact. + vec3_t backupVelocity; + VectorCopy(PRVM_serveredictvector(ent, velocity), backupVelocity); + // don't fall at all if fly/swim + if (!(flags & (FL_FLY | FL_SWIM))) + { + if (flags & FL_ONGROUND) + { + // freefall if onground and moving upward + // freefall if not standing on a world surface (it may be a lift or trap door) + if (PRVM_serveredictvector(ent, velocity)[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer) + { + PRVM_serveredictfloat(ent, flags) -= FL_ONGROUND; + SV_CheckVelocity(ent); + SV_FlyMove(ent, sv.frametime, true, NULL, SV_GenericHitSuperContentsMask(ent), 0); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + ent->priv.server->waterposition_forceupdate = true; + } + } + else + { + // freefall if not onground + int hitsound = PRVM_serveredictvector(ent, velocity)[2] < sv_gravity.value * -0.1; + + SV_CheckVelocity(ent); + SV_FlyMove(ent, sv.frametime, true, NULL, SV_GenericHitSuperContentsMask(ent), 0); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + // just hit ground + if (hitsound && (int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) + { + // DRESK - Check for Entity Land Event Function + if(PRVM_serveredictfunction(ent, movetypesteplandevent)) + { // Valid Function; Execute + // Prepare Parameters + // Assign Velocity at Impact + PRVM_G_VECTOR(OFS_PARM0)[0] = backupVelocity[0]; + PRVM_G_VECTOR(OFS_PARM0)[1] = backupVelocity[1]; + PRVM_G_VECTOR(OFS_PARM0)[2] = backupVelocity[2]; + // Assign Self + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + // Execute VM Function + PRVM_ExecuteProgram(PRVM_serveredictfunction(ent, movetypesteplandevent), "movetypesteplandevent: NULL function"); + } + else + // Check for Engine Landing Sound + if(sv_sound_land.string) + SV_StartSound(ent, 0, sv_sound_land.string, 255, 1, false); + } + ent->priv.server->waterposition_forceupdate = true; + } + } + +// regular thinking + if (!SV_RunThink(ent)) + return; + + if (ent->priv.server->waterposition_forceupdate || !VectorCompare(PRVM_serveredictvector(ent, origin), ent->priv.server->waterposition_origin)) + { + ent->priv.server->waterposition_forceupdate = false; + VectorCopy(PRVM_serveredictvector(ent, origin), ent->priv.server->waterposition_origin); + SV_CheckWaterTransition(ent); + } +} + +//============================================================================ + +static void SV_Physics_Entity (prvm_edict_t *ent) +{ + // don't run think/move on newly spawned projectiles as it messes up + // movement interpolation and rocket trails, and is inconsistent with + // respect to entities spawned in the same frame + // (if an ent spawns a higher numbered ent, it moves in the same frame, + // but if it spawns a lower numbered ent, it doesn't - this never moves + // ents in the first frame regardless) + qboolean runmove = ent->priv.server->move; + ent->priv.server->move = true; + if (!runmove && sv_gameplayfix_delayprojectiles.integer > 0) + return; + switch ((int) PRVM_serveredictfloat(ent, movetype)) + { + case MOVETYPE_PUSH: + case MOVETYPE_FAKEPUSH: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + // LordHavoc: manually inlined the thinktime check here because MOVETYPE_NONE is used on so many objects + if (PRVM_serveredictfloat(ent, nextthink) > 0 && PRVM_serveredictfloat(ent, nextthink) <= sv.time + sv.frametime) + SV_RunThink (ent); + break; + case MOVETYPE_FOLLOW: + SV_Physics_Follow (ent); + break; + case MOVETYPE_NOCLIP: + if (SV_RunThink(ent)) + { + SV_CheckWater(ent); + VectorMA(PRVM_serveredictvector(ent, origin), sv.frametime, PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, origin)); + VectorMA(PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); + } + SV_LinkEdict(ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_WALK: + if (SV_RunThink (ent)) + SV_WalkMove (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_BOUNCEMISSILE: + case MOVETYPE_FLYMISSILE: + case MOVETYPE_FLY: + // regular thinking + if (SV_RunThink (ent)) + SV_Physics_Toss (ent); + break; + case MOVETYPE_PHYSICS: + if (SV_RunThink(ent)) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + break; + default: + Con_Printf ("SV_Physics: bad movetype %i\n", (int)PRVM_serveredictfloat(ent, movetype)); + break; + } +} + +void SV_Physics_ClientMove(void) +{ + prvm_edict_t *ent; + ent = host_client->edict; + + // call player physics, this needs the proper frametime + PRVM_serverglobalfloat(frametime) = sv.frametime; + SV_ClientThink(); + + // call standard client pre-think, with frametime = 0 + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobalfloat(frametime) = 0; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram (PRVM_serverfunction(PlayerPreThink), "QC function PlayerPreThink is missing"); + PRVM_serverglobalfloat(frametime) = sv.frametime; + + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + + // perform MOVETYPE_WALK behavior + SV_WalkMove (ent); + + // call standard player post-think, with frametime = 0 + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobalfloat(frametime) = 0; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram (PRVM_serverfunction(PlayerPostThink), "QC function PlayerPostThink is missing"); + PRVM_serverglobalfloat(frametime) = sv.frametime; + + if(PRVM_serveredictfloat(ent, fixangle)) + { + // angle fixing was requested by physics code... + // so store the current angles for later use + memcpy(host_client->fixangle_angles, PRVM_serveredictvector(ent, angles), sizeof(host_client->fixangle_angles)); + host_client->fixangle_angles_set = TRUE; + + // and clear fixangle for the next frame + PRVM_serveredictfloat(ent, fixangle) = 0; + } +} + +static void SV_Physics_ClientEntity_PreThink(prvm_edict_t *ent) +{ + // don't do physics on disconnected clients, FrikBot relies on this + if (!host_client->spawned) + return; + + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + + // don't run physics here if running asynchronously + if (host_client->clmovement_inputtimeout <= 0) + { + SV_ClientThink(); + //host_client->cmd.time = max(host_client->cmd.time, sv.time); + } + + // make sure the velocity is still sane (not a NaN) + SV_CheckVelocity(ent); + + // call standard client pre-think + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram(PRVM_serverfunction(PlayerPreThink), "QC function PlayerPreThink is missing"); + + // make sure the velocity is still sane (not a NaN) + SV_CheckVelocity(ent); +} + +static void SV_Physics_ClientEntity_PostThink(prvm_edict_t *ent) +{ + // don't do physics on disconnected clients, FrikBot relies on this + if (!host_client->spawned) + return; + + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + + // call standard player post-think + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram(PRVM_serverfunction(PlayerPostThink), "QC function PlayerPostThink is missing"); + + // make sure the velocity is still sane (not a NaN) + SV_CheckVelocity(ent); + + if(PRVM_serveredictfloat(ent, fixangle)) + { + // angle fixing was requested by physics code... + // so store the current angles for later use + memcpy(host_client->fixangle_angles, PRVM_serveredictvector(ent, angles), sizeof(host_client->fixangle_angles)); + host_client->fixangle_angles_set = TRUE; + + // and clear fixangle for the next frame + PRVM_serveredictfloat(ent, fixangle) = 0; + } + + // decrement the countdown variable used to decide when to go back to + // synchronous physics + if (host_client->clmovement_inputtimeout > sv.frametime) + host_client->clmovement_inputtimeout -= sv.frametime; + else + host_client->clmovement_inputtimeout = 0; +} + +static void SV_Physics_ClientEntity(prvm_edict_t *ent) +{ + // don't do physics on disconnected clients, FrikBot relies on this + if (!host_client->spawned) + { + memset(&host_client->cmd, 0, sizeof(host_client->cmd)); + return; + } + + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + + switch ((int) PRVM_serveredictfloat(ent, movetype)) + { + case MOVETYPE_PUSH: + case MOVETYPE_FAKEPUSH: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + // LordHavoc: manually inlined the thinktime check here because MOVETYPE_NONE is used on so many objects + if (PRVM_serveredictfloat(ent, nextthink) > 0 && PRVM_serveredictfloat(ent, nextthink) <= sv.time + sv.frametime) + SV_RunThink (ent); + break; + case MOVETYPE_FOLLOW: + SV_Physics_Follow (ent); + break; + case MOVETYPE_NOCLIP: + SV_RunThink(ent); + SV_CheckWater(ent); + VectorMA(PRVM_serveredictvector(ent, origin), sv.frametime, PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, origin)); + VectorMA(PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_WALK: + SV_RunThink (ent); + // don't run physics here if running asynchronously + if (host_client->clmovement_inputtimeout <= 0) + SV_WalkMove (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_BOUNCEMISSILE: + case MOVETYPE_FLYMISSILE: + // regular thinking + SV_RunThink (ent); + SV_Physics_Toss (ent); + break; + case MOVETYPE_FLY: + SV_RunThink (ent); + SV_WalkMove (ent); + break; + case MOVETYPE_PHYSICS: + SV_RunThink (ent); + break; + default: + Con_Printf ("SV_Physics_ClientEntity: bad movetype %i\n", (int)PRVM_serveredictfloat(ent, movetype)); + break; + } + + SV_CheckVelocity (ent); + + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + SV_CheckVelocity (ent); +} + +/* +================ +SV_Physics + +================ +*/ +void SV_Physics (void) +{ + int i; + prvm_edict_t *ent; + +// let the progs know that a new frame has started + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobalfloat(frametime) = sv.frametime; + PRVM_ExecuteProgram (PRVM_serverfunction(StartFrame), "QC function StartFrame is missing"); + + // run physics engine + World_Physics_Frame(&sv.world, sv.frametime, sv_gravity.value); + +// +// treat each object in turn +// + + // if force_retouch, relink all the entities + if (PRVM_serverglobalfloat(force_retouch) > 0) + for (i = 1, ent = PRVM_EDICT_NUM(i);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->free) + SV_LinkEdict_TouchAreaGrid(ent); // force retouch even for stationary + + if (sv_gameplayfix_consistentplayerprethink.integer) + { + // run physics on the client entities in 3 stages + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + if (!ent->priv.server->free) + SV_Physics_ClientEntity_PreThink(ent); + + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + if (!ent->priv.server->free) + SV_Physics_ClientEntity(ent); + + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + if (!ent->priv.server->free) + SV_Physics_ClientEntity_PostThink(ent); + } + else + { + // run physics on the client entities + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + { + if (!ent->priv.server->free) + { + SV_Physics_ClientEntity_PreThink(ent); + SV_Physics_ClientEntity(ent); + SV_Physics_ClientEntity_PostThink(ent); + } + } + } + + // run physics on all the non-client entities + if (!sv_freezenonclients.integer) + { + for (;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->free) + SV_Physics_Entity(ent); + // make a second pass to see if any ents spawned this frame and make + // sure they run their move/think + if (sv_gameplayfix_delayprojectiles.integer < 0) + for (i = svs.maxclients + 1, ent = PRVM_EDICT_NUM(i);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->move && !ent->priv.server->free) + SV_Physics_Entity(ent); + } + + if (PRVM_serverglobalfloat(force_retouch) > 0) + PRVM_serverglobalfloat(force_retouch) = max(0, PRVM_serverglobalfloat(force_retouch) - 1); + + // LordHavoc: endframe support + if (PRVM_serverfunction(EndFrame)) + { + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_ExecuteProgram (PRVM_serverfunction(EndFrame), "QC function EndFrame is missing"); + } + + // decrement prog->num_edicts if the highest number entities died + for (;PRVM_ED_CanAlloc(PRVM_EDICT_NUM(prog->num_edicts - 1));prog->num_edicts--); + + if (!sv_freezenonclients.integer) + sv.time += sv.frametime; +} diff --git a/misc/source/darkplaces-src/sv_user.c b/misc/source/darkplaces-src/sv_user.c new file mode 100644 index 00000000..7b59de99 --- /dev/null +++ b/misc/source/darkplaces-src/sv_user.c @@ -0,0 +1,966 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_user.c -- server code for moving users + +#include "quakedef.h" +#include "sv_demo.h" +#define DEBUGMOVES 0 + +static usercmd_t cmd; +extern cvar_t sv_autodemo_perclient; + +/* +=============== +SV_SetIdealPitch +=============== +*/ +#define MAX_FORWARD 6 +void SV_SetIdealPitch (void) +{ + float angleval, sinval, cosval, step, dir; + trace_t tr; + vec3_t top, bottom; + float z[MAX_FORWARD]; + int i, j; + int steps; + + if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_ONGROUND)) + return; + + angleval = PRVM_serveredictvector(host_client->edict, angles)[YAW] * M_PI*2 / 360; + sinval = sin(angleval); + cosval = cos(angleval); + + for (i=0 ; iedict, origin)[0] + cosval*(i+3)*12; + top[1] = PRVM_serveredictvector(host_client->edict, origin)[1] + sinval*(i+3)*12; + top[2] = PRVM_serveredictvector(host_client->edict, origin)[2] + PRVM_serveredictvector(host_client->edict, view_ofs)[2]; + + bottom[0] = top[0]; + bottom[1] = top[1]; + bottom[2] = top[2] - 160; + + tr = SV_TraceLine(top, bottom, MOVE_NOMONSTERS, host_client->edict, SUPERCONTENTS_SOLID); + // if looking at a wall, leave ideal the way is was + if (tr.startsolid) + return; + + // near a dropoff + if (tr.fraction == 1) + return; + + z[i] = top[2] + tr.fraction*(bottom[2]-top[2]); + } + + dir = 0; + steps = 0; + for (j=1 ; j -ON_EPSILON && step < ON_EPSILON) + continue; + + // mixed changes + if (dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ) ) + return; + + steps++; + dir = step; + } + + if (!dir) + { + PRVM_serveredictfloat(host_client->edict, idealpitch) = 0; + return; + } + + if (steps < 2) + return; + PRVM_serveredictfloat(host_client->edict, idealpitch) = -dir * sv_idealpitchscale.value; +} + +static vec3_t wishdir, forward, right, up; +static float wishspeed; + +static qboolean onground; + +/* +================== +SV_UserFriction + +================== +*/ +void SV_UserFriction (void) +{ + float speed, newspeed, control, friction; + vec3_t start, stop; + trace_t trace; + + speed = sqrt(PRVM_serveredictvector(host_client->edict, velocity)[0]*PRVM_serveredictvector(host_client->edict, velocity)[0]+PRVM_serveredictvector(host_client->edict, velocity)[1]*PRVM_serveredictvector(host_client->edict, velocity)[1]); + if (!speed) + return; + + // if the leading edge is over a dropoff, increase friction + start[0] = stop[0] = PRVM_serveredictvector(host_client->edict, origin)[0] + PRVM_serveredictvector(host_client->edict, velocity)[0]/speed*16; + start[1] = stop[1] = PRVM_serveredictvector(host_client->edict, origin)[1] + PRVM_serveredictvector(host_client->edict, velocity)[1]/speed*16; + start[2] = PRVM_serveredictvector(host_client->edict, origin)[2] + PRVM_serveredictvector(host_client->edict, mins)[2]; + stop[2] = start[2] - 34; + + trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, host_client->edict, SV_GenericHitSuperContentsMask(host_client->edict)); + + if (trace.fraction == 1.0) + friction = sv_friction.value*sv_edgefriction.value; + else + friction = sv_friction.value; + + // apply friction + control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed; + newspeed = speed - sv.frametime*control*friction; + + if (newspeed < 0) + newspeed = 0; + else + newspeed /= speed; + + VectorScale(PRVM_serveredictvector(host_client->edict, velocity), newspeed, PRVM_serveredictvector(host_client->edict, velocity)); +} + +/* +============== +SV_Accelerate +============== +*/ +void SV_Accelerate (void) +{ + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (PRVM_serveredictvector(host_client->edict, velocity), wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + accelspeed = sv_accelerate.value*sv.frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed*wishdir[i]; +} + +extern cvar_t sv_gameplayfix_q2airaccelerate; +void SV_AirAccelerate (vec3_t wishveloc) +{ + int i; + float addspeed, wishspd, accelspeed, currentspeed; + + wishspd = VectorNormalizeLength (wishveloc); + if (wishspd > sv_maxairspeed.value) + wishspd = sv_maxairspeed.value; + currentspeed = DotProduct (PRVM_serveredictvector(host_client->edict, velocity), wishveloc); + addspeed = wishspd - currentspeed; + if (addspeed <= 0) + return; + accelspeed = (sv_airaccelerate.value < 0 ? sv_accelerate.value : sv_airaccelerate.value)*(sv_gameplayfix_q2airaccelerate.integer ? wishspd : wishspeed) * sv.frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed*wishveloc[i]; +} + + +void DropPunchAngle (void) +{ + float len; + vec3_t v; + + len = VectorNormalizeLength (PRVM_serveredictvector(host_client->edict, punchangle)); + + len -= 10*sv.frametime; + if (len < 0) + len = 0; + VectorScale (PRVM_serveredictvector(host_client->edict, punchangle), len, PRVM_serveredictvector(host_client->edict, punchangle)); + + VectorCopy(PRVM_serveredictvector(host_client->edict, punchvector), v); + len = VectorNormalizeLength(v); + if (len > 0) + { + len -= 20*sv.frametime; + if (len < 0) + len = 0; + VectorScale(v, len, v); + } + VectorCopy(v, PRVM_serveredictvector(host_client->edict, punchvector)); +} + +/* +=================== +SV_FreeMove +=================== +*/ +void SV_FreeMove (void) +{ + int i; + float wishspeed; + + AngleVectors (PRVM_serveredictvector(host_client->edict, v_angle), forward, right, up); + + for (i = 0; i < 3; i++) + PRVM_serveredictvector(host_client->edict, velocity)[i] = forward[i] * cmd.forwardmove + right[i] * cmd.sidemove; + + PRVM_serveredictvector(host_client->edict, velocity)[2] += cmd.upmove; + + wishspeed = VectorLength(PRVM_serveredictvector(host_client->edict, velocity)); + if (wishspeed > sv_maxspeed.value) + VectorScale(PRVM_serveredictvector(host_client->edict, velocity), sv_maxspeed.value / wishspeed, PRVM_serveredictvector(host_client->edict, velocity)); +} + +/* +=================== +SV_WaterMove + +=================== +*/ +void SV_WaterMove (void) +{ + int i; + vec3_t wishvel; + float speed, newspeed, wishspeed, addspeed, accelspeed, temp; + + // user intentions + AngleVectors (PRVM_serveredictvector(host_client->edict, v_angle), forward, right, up); + + for (i=0 ; i<3 ; i++) + wishvel[i] = forward[i]*cmd.forwardmove + right[i]*cmd.sidemove; + + if (!cmd.forwardmove && !cmd.sidemove && !cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else + wishvel[2] += cmd.upmove; + + wishspeed = VectorLength(wishvel); + if (wishspeed > sv_maxspeed.value) + { + temp = sv_maxspeed.value/wishspeed; + VectorScale (wishvel, temp, wishvel); + wishspeed = sv_maxspeed.value; + } + wishspeed *= 0.7; + + // water friction + speed = VectorLength(PRVM_serveredictvector(host_client->edict, velocity)); + if (speed) + { + newspeed = speed - sv.frametime * speed * (sv_waterfriction.value < 0 ? sv_friction.value : sv_waterfriction.value); + if (newspeed < 0) + newspeed = 0; + temp = newspeed/speed; + VectorScale(PRVM_serveredictvector(host_client->edict, velocity), temp, PRVM_serveredictvector(host_client->edict, velocity)); + } + else + newspeed = 0; + + // water acceleration + if (!wishspeed) + return; + + addspeed = wishspeed - newspeed; + if (addspeed <= 0) + return; + + VectorNormalize (wishvel); + accelspeed = (sv_wateraccelerate.value < 0 ? sv_accelerate.value : sv_wateraccelerate.value) * wishspeed * sv.frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed * wishvel[i]; +} + +void SV_WaterJump (void) +{ + if (sv.time > PRVM_serveredictfloat(host_client->edict, teleport_time) || !PRVM_serveredictfloat(host_client->edict, waterlevel)) + { + PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) & ~FL_WATERJUMP; + PRVM_serveredictfloat(host_client->edict, teleport_time) = 0; + } + PRVM_serveredictvector(host_client->edict, velocity)[0] = PRVM_serveredictvector(host_client->edict, movedir)[0]; + PRVM_serveredictvector(host_client->edict, velocity)[1] = PRVM_serveredictvector(host_client->edict, movedir)[1]; +} + + +/* +=================== +SV_AirMove + +=================== +*/ +void SV_AirMove (void) +{ + int i; + vec3_t wishvel; + float fmove, smove, temp; + + // LordHavoc: correct quake movement speed bug when looking up/down + wishvel[0] = wishvel[2] = 0; + wishvel[1] = PRVM_serveredictvector(host_client->edict, angles)[1]; + AngleVectors (wishvel, forward, right, up); + + fmove = cmd.forwardmove; + smove = cmd.sidemove; + +// hack to not let you back into teleporter + if (sv.time < PRVM_serveredictfloat(host_client->edict, teleport_time) && fmove < 0) + fmove = 0; + + for (i=0 ; i<3 ; i++) + wishvel[i] = forward[i]*fmove + right[i]*smove; + + if ((int)PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_WALK) + wishvel[2] += cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalizeLength(wishdir); + if (wishspeed > sv_maxspeed.value) + { + temp = sv_maxspeed.value/wishspeed; + VectorScale (wishvel, temp, wishvel); + wishspeed = sv_maxspeed.value; + } + + if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NOCLIP) + { + // noclip + VectorCopy (wishvel, PRVM_serveredictvector(host_client->edict, velocity)); + } + else if (onground) + { + SV_UserFriction (); + SV_Accelerate (); + } + else + { + // not on ground, so little effect on velocity + SV_AirAccelerate (wishvel); + } +} + +/* +=================== +SV_ClientThink + +the move fields specify an intended velocity in pix/sec +the angle fields specify an exact angular motion in degrees +=================== +*/ +void SV_ClientThink (void) +{ + vec3_t v_angle; + + //Con_Printf("clientthink for %ims\n", (int) (sv.frametime * 1000)); + + SV_ApplyClientMove(); + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(host_client->edict); + + // LordHavoc: QuakeC replacement for SV_ClientThink (player movement) + if (PRVM_serverfunction(SV_PlayerPhysics) && sv_playerphysicsqc.integer) + { + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram (PRVM_serverfunction(SV_PlayerPhysics), "QC function SV_PlayerPhysics is missing"); + SV_CheckVelocity(host_client->edict); + return; + } + + if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NONE) + return; + + onground = ((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_ONGROUND) != 0; + + DropPunchAngle (); + + // if dead, behave differently + if (PRVM_serveredictfloat(host_client->edict, health) <= 0) + return; + + cmd = host_client->cmd; + + // angles + // show 1/3 the pitch angle and all the roll angle + VectorAdd (PRVM_serveredictvector(host_client->edict, v_angle), PRVM_serveredictvector(host_client->edict, punchangle), v_angle); + PRVM_serveredictvector(host_client->edict, angles)[ROLL] = V_CalcRoll (PRVM_serveredictvector(host_client->edict, angles), PRVM_serveredictvector(host_client->edict, velocity))*4; + if (!PRVM_serveredictfloat(host_client->edict, fixangle)) + { + PRVM_serveredictvector(host_client->edict, angles)[PITCH] = -v_angle[PITCH]/3; + PRVM_serveredictvector(host_client->edict, angles)[YAW] = v_angle[YAW]; + } + + if ( (int)PRVM_serveredictfloat(host_client->edict, flags) & FL_WATERJUMP ) + { + SV_WaterJump (); + SV_CheckVelocity(host_client->edict); + return; + } + + /* + // Player is (somehow) outside of the map, or flying, or noclipping + if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP && (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_FLY || SV_TestEntityPosition (host_client->edict))) + //if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NOCLIP || PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_FLY || SV_TestEntityPosition (host_client->edict)) + { + SV_FreeMove (); + return; + } + */ + + // walk + if ((PRVM_serveredictfloat(host_client->edict, waterlevel) >= 2) && (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)) + { + SV_WaterMove (); + SV_CheckVelocity(host_client->edict); + return; + } + + SV_AirMove (); + SV_CheckVelocity(host_client->edict); +} + +/* +=================== +SV_ReadClientMove +=================== +*/ +int sv_numreadmoves = 0; +usercmd_t sv_readmoves[CL_MAX_USERCMDS]; +void SV_ReadClientMove (void) +{ + int i; + usercmd_t newmove; + usercmd_t *move = &newmove; + + memset(move, 0, sizeof(*move)); + + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // read ping time + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5 && sv.protocol != PROTOCOL_DARKPLACES6) + move->sequence = MSG_ReadLong (); + move->time = move->clienttime = MSG_ReadFloat (); + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + move->receivetime = (float)sv.time; + +#if DEBUGMOVES + Con_Printf("%s move%i #%i %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", move->time > move->receivetime ? "^3read future" : "^4read normal", sv_numreadmoves + 1, move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove); +#endif + // limit reported time to current time + // (incase the client is trying to cheat) + move->time = min(move->time, move->receivetime + sv.frametime); + + // read current angles + for (i = 0;i < 3;i++) + { + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + move->viewangles[i] = MSG_ReadAngle8i(); + else if (sv.protocol == PROTOCOL_DARKPLACES1) + move->viewangles[i] = MSG_ReadAngle16i(); + else if (sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) + move->viewangles[i] = MSG_ReadAngle32f(); + else + move->viewangles[i] = MSG_ReadAngle16i(); + } + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // read movement + move->forwardmove = MSG_ReadCoord16i (); + move->sidemove = MSG_ReadCoord16i (); + move->upmove = MSG_ReadCoord16i (); + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // read buttons + // be sure to bitwise OR them into the move->buttons because we want to + // accumulate button presses from multiple packets per actual move + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) + move->buttons = MSG_ReadByte (); + else + move->buttons = MSG_ReadLong (); + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // read impulse + move->impulse = MSG_ReadByte (); + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // PRYDON_CLIENTCURSOR + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5) + { + // 30 bytes + move->cursor_screen[0] = MSG_ReadShort() * (1.0f / 32767.0f); + move->cursor_screen[1] = MSG_ReadShort() * (1.0f / 32767.0f); + move->cursor_start[0] = MSG_ReadFloat(); + move->cursor_start[1] = MSG_ReadFloat(); + move->cursor_start[2] = MSG_ReadFloat(); + move->cursor_impact[0] = MSG_ReadFloat(); + move->cursor_impact[1] = MSG_ReadFloat(); + move->cursor_impact[2] = MSG_ReadFloat(); + move->cursor_entitynumber = (unsigned short)MSG_ReadShort(); + if (move->cursor_entitynumber >= prog->max_edicts) + { + Con_DPrintf("SV_ReadClientMessage: client send bad cursor_entitynumber\n"); + move->cursor_entitynumber = 0; + } + // as requested by FrikaC, cursor_trace_ent is reset to world if the + // entity is free at time of receipt + if (PRVM_EDICT_NUM(move->cursor_entitynumber)->priv.server->free) + move->cursor_entitynumber = 0; + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + } + + // if the previous move has not been applied yet, we need to accumulate + // the impulse/buttons from it + if (!host_client->cmd.applied) + { + if (!move->impulse) + move->impulse = host_client->cmd.impulse; + move->buttons |= host_client->cmd.buttons; + } + + // now store this move for later execution + // (we have to buffer the moves because of old ones being repeated) + if (sv_numreadmoves < CL_MAX_USERCMDS) + sv_readmoves[sv_numreadmoves++] = *move; + + // movement packet loss tracking + if(move->sequence) + { + if(move->sequence > host_client->movement_highestsequence_seen) + { + if(host_client->movement_highestsequence_seen) + { + // mark moves in between as lost + if(move->sequence - host_client->movement_highestsequence_seen - 1 < NETGRAPH_PACKETS) + for(i = host_client->movement_highestsequence_seen + 1; i < move->sequence; ++i) + host_client->movement_count[i % NETGRAPH_PACKETS] = -1; + else + memset(host_client->movement_count, -1, sizeof(host_client->movement_count)); + } + // mark THIS move as seen for the first time + host_client->movement_count[move->sequence % NETGRAPH_PACKETS] = 1; + // update highest sequence seen + host_client->movement_highestsequence_seen = move->sequence; + } + else + if(host_client->movement_count[move->sequence % NETGRAPH_PACKETS] >= 0) + ++host_client->movement_count[move->sequence % NETGRAPH_PACKETS]; + } + else + { + host_client->movement_highestsequence_seen = 0; + memset(host_client->movement_count, 0, sizeof(host_client->movement_count)); + } +} + +void SV_ExecuteClientMoves(void) +{ + int moveindex; + float moveframetime; + double oldframetime; + double oldframetime2; +#ifdef NUM_PING_TIMES + double total; +#endif + if (sv_numreadmoves < 1) + return; + // only start accepting input once the player is spawned + if (!host_client->spawned) + return; +#if DEBUGMOVES + Con_Printf("SV_ExecuteClientMoves: read %i moves at sv.time %f\n", sv_numreadmoves, (float)sv.time); +#endif + // disable clientside movement prediction in some cases + if (ceil(max(sv_readmoves[sv_numreadmoves-1].receivetime - sv_readmoves[sv_numreadmoves-1].time, 0) * 1000.0) < sv_clmovement_minping.integer) + host_client->clmovement_disabletimeout = realtime + sv_clmovement_minping_disabletime.value / 1000.0; + // several conditions govern whether clientside movement prediction is allowed + if (sv_readmoves[sv_numreadmoves-1].sequence && sv_clmovement_enable.integer && sv_clmovement_inputtimeout.value > 0 && host_client->clmovement_disabletimeout <= realtime && PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_WALK && (!PRVM_serveredictfloat(host_client->edict, disableclientprediction))) + { + // process the moves in order and ignore old ones + // but always trust the latest move + // (this deals with bogus initial move sequences after level change, + // where the client will eventually catch up with the level change + // and reset its move sequence) + for (moveindex = 0;moveindex < sv_numreadmoves;moveindex++) + { + usercmd_t *move = sv_readmoves + moveindex; + if (host_client->movesequence < move->sequence || moveindex == sv_numreadmoves - 1) + { +#if DEBUGMOVES + Con_Printf("%smove #%i %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", (move->time - host_client->cmd.time) > sv.frametime * 1.01 ? "^1" : "^2", move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove); +#endif + // this is a new move + move->time = bound(sv.time - 1, move->time, sv.time); // prevent slowhack/speedhack combos + move->time = max(move->time, host_client->cmd.time); // prevent backstepping of time + moveframetime = bound(0, move->time - host_client->cmd.time, min(0.1, sv_clmovement_inputtimeout.value)); + + // discard (treat like lost) moves with too low distance from + // the previous one to prevent hacks using float inaccuracy + // clients will see this as packet loss in the netgraph + // this should also apply if a move cannot get + // executed because it came too late and + // already was performed serverside + if(moveframetime < 0.0005) + { + // count the move as LOST if we don't + // execute it but it has higher + // sequence count + if(host_client->movesequence) + if(move->sequence > host_client->movesequence) + host_client->movement_count[(move->sequence) % NETGRAPH_PACKETS] = -1; + continue; + } + + //Con_Printf("movesequence = %i (%i lost), moveframetime = %f\n", move->sequence, move->sequence ? move->sequence - host_client->movesequence - 1 : 0, moveframetime); + host_client->cmd = *move; + host_client->movesequence = move->sequence; + + // if using prediction, we need to perform moves when packets are + // received, even if multiple occur in one frame + // (they can't go beyond the current time so there is no cheat issue + // with this approach, and if they don't send input for a while they + // start moving anyway, so the longest 'lagaport' possible is + // determined by the sv_clmovement_inputtimeout cvar) + if (moveframetime <= 0) + continue; + oldframetime = PRVM_serverglobalfloat(frametime); + oldframetime2 = sv.frametime; + // update ping time for qc to see while executing this move + host_client->ping = host_client->cmd.receivetime - host_client->cmd.time; + // the server and qc frametime values must be changed temporarily + PRVM_serverglobalfloat(frametime) = sv.frametime = moveframetime; + // if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction) + if (sv.frametime > 0.05) + { + PRVM_serverglobalfloat(frametime) = sv.frametime = moveframetime * 0.5f; + SV_Physics_ClientMove(); + } + SV_Physics_ClientMove(); + sv.frametime = oldframetime2; + PRVM_serverglobalfloat(frametime) = oldframetime; + host_client->clmovement_inputtimeout = sv_clmovement_inputtimeout.value; + } + } + } + else + { + // try to gather button bits from old moves, but only if their time is + // advancing (ones with the same timestamp can't be trusted) + for (moveindex = 0;moveindex < sv_numreadmoves-1;moveindex++) + { + usercmd_t *move = sv_readmoves + moveindex; + if (host_client->cmd.time < move->time) + { + sv_readmoves[sv_numreadmoves-1].buttons |= move->buttons; + if (move->impulse) + sv_readmoves[sv_numreadmoves-1].impulse = move->impulse; + } + } + // now copy the new move + host_client->cmd = sv_readmoves[sv_numreadmoves-1]; + host_client->cmd.time = max(host_client->cmd.time, sv.time); + // physics will run up to sv.time, so allow no predicted moves + // before that otherwise, there is a speedhack by turning + // prediction on and off repeatedly on client side because the + // engine would run BOTH client and server physics for the same + // time + host_client->movesequence = 0; + // make sure that normal physics takes over immediately + host_client->clmovement_inputtimeout = 0; + } + + // calculate average ping time + host_client->ping = host_client->cmd.receivetime - host_client->cmd.clienttime; +#ifdef NUM_PING_TIMES + host_client->ping_times[host_client->num_pings % NUM_PING_TIMES] = host_client->cmd.receivetime - host_client->cmd.clienttime; + host_client->num_pings++; + for (i=0, total = 0;i < NUM_PING_TIMES;i++) + total += host_client->ping_times[i]; + host_client->ping = total / NUM_PING_TIMES; +#endif +} + +void SV_ApplyClientMove (void) +{ + usercmd_t *move = &host_client->cmd; + int j, movementloss, packetloss; + + if (!move->receivetime) + return; + + // note: a move can be applied multiple times if the client packets are + // not coming as often as the physics is executed, and the move must be + // applied before running qc each time because the id1 qc had a bug where + // it clears self.button2 in PlayerJump, causing pogostick behavior if + // moves are not applied every time before calling qc + move->applied = true; + + // set the edict fields + PRVM_serveredictfloat(host_client->edict, button0) = move->buttons & 1; + PRVM_serveredictfloat(host_client->edict, button2) = (move->buttons & 2)>>1; + if (move->impulse) + PRVM_serveredictfloat(host_client->edict, impulse) = move->impulse; + // only send the impulse to qc once + move->impulse = 0; + + movementloss = packetloss = 0; + if(host_client->netconnection) + { + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (host_client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) + packetloss++; + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (host_client->movement_count[j] < 0) + movementloss++; + } + + VectorCopy(move->viewangles, PRVM_serveredictvector(host_client->edict, v_angle)); + PRVM_serveredictfloat(host_client->edict, button3) = ((move->buttons >> 2) & 1); + PRVM_serveredictfloat(host_client->edict, button4) = ((move->buttons >> 3) & 1); + PRVM_serveredictfloat(host_client->edict, button5) = ((move->buttons >> 4) & 1); + PRVM_serveredictfloat(host_client->edict, button6) = ((move->buttons >> 5) & 1); + PRVM_serveredictfloat(host_client->edict, button7) = ((move->buttons >> 6) & 1); + PRVM_serveredictfloat(host_client->edict, button8) = ((move->buttons >> 7) & 1); + PRVM_serveredictfloat(host_client->edict, button9) = ((move->buttons >> 11) & 1); + PRVM_serveredictfloat(host_client->edict, button10) = ((move->buttons >> 12) & 1); + PRVM_serveredictfloat(host_client->edict, button11) = ((move->buttons >> 13) & 1); + PRVM_serveredictfloat(host_client->edict, button12) = ((move->buttons >> 14) & 1); + PRVM_serveredictfloat(host_client->edict, button13) = ((move->buttons >> 15) & 1); + PRVM_serveredictfloat(host_client->edict, button14) = ((move->buttons >> 16) & 1); + PRVM_serveredictfloat(host_client->edict, button15) = ((move->buttons >> 17) & 1); + PRVM_serveredictfloat(host_client->edict, button16) = ((move->buttons >> 18) & 1); + PRVM_serveredictfloat(host_client->edict, buttonuse) = ((move->buttons >> 8) & 1); + PRVM_serveredictfloat(host_client->edict, buttonchat) = ((move->buttons >> 9) & 1); + PRVM_serveredictfloat(host_client->edict, cursor_active) = ((move->buttons >> 10) & 1); + VectorSet(PRVM_serveredictvector(host_client->edict, movement), move->forwardmove, move->sidemove, move->upmove); + VectorCopy(move->cursor_screen, PRVM_serveredictvector(host_client->edict, cursor_screen)); + VectorCopy(move->cursor_start, PRVM_serveredictvector(host_client->edict, cursor_trace_start)); + VectorCopy(move->cursor_impact, PRVM_serveredictvector(host_client->edict, cursor_trace_endpos)); + PRVM_serveredictedict(host_client->edict, cursor_trace_ent) = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM(move->cursor_entitynumber)); + PRVM_serveredictfloat(host_client->edict, ping) = host_client->ping * 1000.0; + PRVM_serveredictfloat(host_client->edict, ping_packetloss) = packetloss / (float) NETGRAPH_PACKETS; + PRVM_serveredictfloat(host_client->edict, ping_movementloss) = movementloss / (float) NETGRAPH_PACKETS; +} + +void SV_FrameLost(int framenum) +{ + if (host_client->entitydatabase5) + { + EntityFrame5_LostFrame(host_client->entitydatabase5, framenum); + EntityFrameCSQC_LostFrame(host_client, framenum); + } +} + +void SV_FrameAck(int framenum) +{ + if (host_client->entitydatabase) + EntityFrame_AckFrame(host_client->entitydatabase, framenum); + else if (host_client->entitydatabase4) + EntityFrame4_AckFrame(host_client->entitydatabase4, framenum, true); + else if (host_client->entitydatabase5) + EntityFrame5_AckFrame(host_client->entitydatabase5, framenum); +} + +/* +=================== +SV_ReadClientMessage +=================== +*/ +extern void SV_SendServerinfo(client_t *client); +extern sizebuf_t vm_tempstringsbuf; +void SV_ReadClientMessage(void) +{ + int cmd, num, start; + char *s, *p, *q; + + if(sv_autodemo_perclient.integer >= 2) + SV_WriteDemoMessage(host_client, &(host_client->netconnection->message), true); + + //MSG_BeginReading (); + sv_numreadmoves = 0; + + for(;;) + { + if (!host_client->active) + { + // a command caused an error + SV_DropClient (false); + return; + } + + if (msg_badread) + { + Con_Print("SV_ReadClientMessage: badread\n"); + SV_DropClient (false); + return; + } + + cmd = MSG_ReadByte (); + if (cmd == -1) + { + // end of message + // apply the moves that were read this frame + SV_ExecuteClientMoves(); + break; + } + + switch (cmd) + { + default: + Con_Printf("SV_ReadClientMessage: unknown command char %i (at offset 0x%x)\n", cmd, msg_readcount); + if (developer_networking.integer) + Com_HexDumpToConsole(net_message.data, net_message.cursize); + SV_DropClient (false); + return; + + case clc_nop: + break; + + case clc_stringcmd: + // allow reliable messages now as the client is done with initial loading + if (host_client->sendsignon == 2) + host_client->sendsignon = 0; + s = MSG_ReadString (); + q = NULL; + for(p = s; *p; ++p) switch(*p) + { + case 10: + case 13: + if(!q) + q = p; + break; + default: + if(q) + goto clc_stringcmd_invalid; // newline seen, THEN something else -> possible exploit + break; + } + if(q) + *q = 0; + if (strncasecmp(s, "spawn", 5) == 0 + || strncasecmp(s, "begin", 5) == 0 + || strncasecmp(s, "prespawn", 8) == 0) + Cmd_ExecuteString (s, src_client); + else if (PRVM_serverfunction(SV_ParseClientCommand)) + { + int restorevm_tempstringsbuf_cursize; + restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(s); + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + PRVM_ExecuteProgram (PRVM_serverfunction(SV_ParseClientCommand), "QC function SV_ParseClientCommand is missing"); + vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + } + else + Cmd_ExecuteString (s, src_client); + break; + +clc_stringcmd_invalid: + Con_Printf("Received invalid stringcmd from %s\n", host_client->name); + if(developer.integer > 0) + Com_HexDumpToConsole((unsigned char *) s, strlen(s)); + break; + + case clc_disconnect: + SV_DropClient (false); // client wants to disconnect + return; + + case clc_move: + SV_ReadClientMove(); + break; + + case clc_ackdownloaddata: + start = MSG_ReadLong(); + num = MSG_ReadShort(); + if (host_client->download_file && host_client->download_started) + { + if (host_client->download_expectedposition == start) + { + int size = (int)FS_FileSize(host_client->download_file); + // a data block was successfully received by the client, + // update the expected position on the next data block + host_client->download_expectedposition = start + num; + // if this was the last data block of the file, it's done + if (host_client->download_expectedposition >= FS_FileSize(host_client->download_file)) + { + // tell the client that the download finished + // we need to calculate the crc now + // + // note: at this point the OS probably has the file + // entirely in memory, so this is a faster operation + // now than it was when the download started. + // + // it is also preferable to do this at the end of the + // download rather than the start because it reduces + // potential for Denial Of Service attacks against the + // server. + int crc; + unsigned char *temp; + FS_Seek(host_client->download_file, 0, SEEK_SET); + temp = (unsigned char *) Mem_Alloc(tempmempool, size); + FS_Read(host_client->download_file, temp, size); + crc = CRC_Block(temp, size); + Mem_Free(temp); + // calculated crc, send the file info to the client + // (so that it can verify the data) + Host_ClientCommands("\ncl_downloadfinished %i %i %s\n", size, crc, host_client->download_name); + Con_DPrintf("Download of %s by %s has finished\n", host_client->download_name, host_client->name); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + host_client->download_name[0] = 0; + host_client->download_expectedposition = 0; + host_client->download_started = false; + } + } + else + { + // a data block was lost, reset to the expected position + // and resume sending from there + FS_Seek(host_client->download_file, host_client->download_expectedposition, SEEK_SET); + } + } + break; + + case clc_ackframe: + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + num = MSG_ReadLong(); + if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + if (developer_networkentities.integer >= 10) + Con_Printf("recv clc_ackframe %i\n", num); + // if the client hasn't progressed through signons yet, + // ignore any clc_ackframes we get (they're probably from the + // previous level) + if (host_client->spawned && host_client->latestframenum < num) + { + int i; + for (i = host_client->latestframenum + 1;i < num;i++) + SV_FrameLost(i); + SV_FrameAck(num); + host_client->latestframenum = num; + } + break; + } + } +} + diff --git a/misc/source/darkplaces-src/svbsp.c b/misc/source/darkplaces-src/svbsp.c new file mode 100644 index 00000000..437d82a3 --- /dev/null +++ b/misc/source/darkplaces-src/svbsp.c @@ -0,0 +1,453 @@ + +// Shadow Volume BSP code written by Forest "LordHavoc" Hale on 2003-11-06 and placed into public domain. +// Modified by LordHavoc (to make it work and other nice things like that) on 2007-01-24 and 2007-01-25 +// Optimized by LordHavoc on 2009-12-24 and 2009-12-25 + +#include +#include +#include "svbsp.h" +#include "polygon.h" + +#define MAX_SVBSP_POLYGONPOINTS 64 +#define SVBSP_CLIP_EPSILON (1.0f / 1024.0f) + +#define SVBSP_DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) + +typedef struct svbsp_polygon_s +{ + float points[MAX_SVBSP_POLYGONPOINTS][3]; + //unsigned char splitflags[MAX_SVBSP_POLYGONPOINTS]; + int facesplitflag; + int numpoints; +} +svbsp_polygon_t; + +static void SVBSP_PlaneFromPoints(float *plane4f, const float *p1, const float *p2, const float *p3) +{ + float ilength; + // calculate unnormalized plane + plane4f[0] = (p1[1] - p2[1]) * (p3[2] - p2[2]) - (p1[2] - p2[2]) * (p3[1] - p2[1]); + plane4f[1] = (p1[2] - p2[2]) * (p3[0] - p2[0]) - (p1[0] - p2[0]) * (p3[2] - p2[2]); + plane4f[2] = (p1[0] - p2[0]) * (p3[1] - p2[1]) - (p1[1] - p2[1]) * (p3[0] - p2[0]); + plane4f[3] = SVBSP_DotProduct(plane4f, p1); + // normalize the plane normal and adjust distance accordingly + ilength = (float)sqrt(SVBSP_DotProduct(plane4f, plane4f)); + if (ilength) + ilength = 1.0f / ilength; + plane4f[0] *= ilength; + plane4f[1] *= ilength; + plane4f[2] *= ilength; + plane4f[3] *= ilength; +} + +static void SVBSP_DividePolygon(const svbsp_polygon_t *poly, const float *plane, svbsp_polygon_t *front, svbsp_polygon_t *back, const float *dists, const int *sides) +{ + int i, j, count = poly->numpoints, frontcount = 0, backcount = 0; + float frac, ifrac, c[3], pdist, ndist; + const float *nextpoint; + const float *points = poly->points[0]; + float *outfront = front->points[0]; + float *outback = back->points[0]; + for(i = 0;i < count;i++, points += 3) + { + j = i + 1; + if (j >= count) + j = 0; + if (!(sides[i] & 2)) + { + outfront[frontcount*3+0] = points[0]; + outfront[frontcount*3+1] = points[1]; + outfront[frontcount*3+2] = points[2]; + frontcount++; + } + if (!(sides[i] & 1)) + { + outback[backcount*3+0] = points[0]; + outback[backcount*3+1] = points[1]; + outback[backcount*3+2] = points[2]; + backcount++; + } + if ((sides[i] | sides[j]) == 3) + { + // don't allow splits if remaining points would overflow point buffer + if (frontcount + (count - i) > MAX_SVBSP_POLYGONPOINTS - 1) + continue; + if (backcount + (count - i) > MAX_SVBSP_POLYGONPOINTS - 1) + continue; + nextpoint = poly->points[j]; + pdist = dists[i]; + ndist = dists[j]; + frac = pdist / (pdist - ndist); + ifrac = 1.0f - frac; + c[0] = points[0] * ifrac + frac * nextpoint[0]; + c[1] = points[1] * ifrac + frac * nextpoint[1]; + c[2] = points[2] * ifrac + frac * nextpoint[2]; + outfront[frontcount*3+0] = c[0]; + outfront[frontcount*3+1] = c[1]; + outfront[frontcount*3+2] = c[2]; + frontcount++; + outback[backcount*3+0] = c[0]; + outback[backcount*3+1] = c[1]; + outback[backcount*3+2] = c[2]; + backcount++; + } + } + front->numpoints = frontcount; + back->numpoints = backcount; +} + +void SVBSP_Init(svbsp_t *b, const float *origin, int maxnodes, svbsp_node_t *nodes) +{ + memset(b, 0, sizeof(*b)); + b->origin[0] = origin[0]; + b->origin[1] = origin[1]; + b->origin[2] = origin[2]; + b->numnodes = 3; + b->maxnodes = maxnodes; + b->nodes = nodes; + b->ranoutofnodes = 0; + b->stat_occluders_rejected = 0; + b->stat_occluders_accepted = 0; + b->stat_occluders_fragments_accepted = 0; + b->stat_occluders_fragments_rejected = 0; + b->stat_queries_rejected = 0; + b->stat_queries_accepted = 0; + b->stat_queries_fragments_accepted = 0; + b->stat_queries_fragments_rejected = 0; + + // the bsp tree must be initialized to have two perpendicular splits axes + // through origin, otherwise the polygon insertions would affect the + // opposite side of the tree, which would be disasterous. + // + // so this code has to make 3 nodes and 4 leafs, and since the leafs are + // represented by empty/solid state numbers in this system rather than + // actual structs, we only need to make the 3 nodes. + + // root node + // this one splits the world into +X and -X sides + b->nodes[0].plane[0] = 1; + b->nodes[0].plane[1] = 0; + b->nodes[0].plane[2] = 0; + b->nodes[0].plane[3] = origin[0]; + b->nodes[0].parent = -1; + b->nodes[0].children[0] = 1; + b->nodes[0].children[1] = 2; + + // +X side node + // this one splits the +X half of the world into +Y and -Y + b->nodes[1].plane[0] = 0; + b->nodes[1].plane[1] = 1; + b->nodes[1].plane[2] = 0; + b->nodes[1].plane[3] = origin[1]; + b->nodes[1].parent = 0; + b->nodes[1].children[0] = -1; + b->nodes[1].children[1] = -1; + + // -X side node + // this one splits the -X half of the world into +Y and -Y + b->nodes[2].plane[0] = 0; + b->nodes[2].plane[1] = 1; + b->nodes[2].plane[2] = 0; + b->nodes[2].plane[3] = origin[1]; + b->nodes[2].parent = 0; + b->nodes[2].children[0] = -1; + b->nodes[2].children[1] = -1; +} + +static void SVBSP_InsertOccluderPolygonNodes(svbsp_t *b, int *parentnodenumpointer, int parentnodenum, const svbsp_polygon_t *poly, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) +{ + // now we need to create up to numpoints + 1 new nodes, forming a BSP tree + // describing the occluder polygon's shadow volume + int i, j, p; + svbsp_node_t *node; + + // points and lines are valid testers but not occluders + if (poly->numpoints < 3) + return; + + // if there aren't enough nodes remaining, skip it + if (b->numnodes + poly->numpoints + 1 >= b->maxnodes) + { + b->ranoutofnodes = 1; + return; + } + + // add one node per side, then the actual occluding face node + + // thread safety notes: + // DO NOT multithread insertion, it could be made 'safe' but the results + // would be inconsistent. + // + // it is completely safe to multithread queries in all cases. + // + // if an insertion is occurring the query will give intermediate results, + // being blocked by some volumes but not others, which is perfectly okay + // for visibility culling intended only to reduce rendering work + + // note down the first available nodenum for the *parentnodenumpointer + // line which is done last to allow multithreaded queries during an + // insertion + for (i = 0, p = poly->numpoints - 1;i < poly->numpoints;p = i, i++) + { +#if 1 + // see if a parent plane describes this side + for (j = parentnodenum;j >= 0;j = b->nodes[j].parent) + { + float *parentnodeplane = b->nodes[j].plane; + if (fabs(SVBSP_DotProduct(poly->points[p], parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON + && fabs(SVBSP_DotProduct(poly->points[i], parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON + && fabs(SVBSP_DotProduct(b->origin , parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON) + break; + } + if (j >= 0) + continue; // already have a matching parent plane +#endif +#if 0 + // skip any sides that were classified as belonging to a parent plane + if (poly->splitflags[i]) + continue; +#endif + // create a side plane + // anything infront of this is not inside the shadow volume + node = b->nodes + b->numnodes++; + SVBSP_PlaneFromPoints(node->plane, b->origin, poly->points[p], poly->points[i]); + // we need to flip the plane if it puts any part of the polygon on the + // wrong side + // (in this way this code treats all polygons as float sided) + // + // because speed is important this stops as soon as it finds proof + // that the orientation is right or wrong + // (we know that the plane is on one edge of the polygon, so there is + // never a case where points lie on both sides, so the first hint is + // sufficient) + for (j = 0;j < poly->numpoints;j++) + { + float d = SVBSP_DotProduct(poly->points[j], node->plane) - node->plane[3]; + if (d < -SVBSP_CLIP_EPSILON) + break; + if (d > SVBSP_CLIP_EPSILON) + { + node->plane[0] *= -1; + node->plane[1] *= -1; + node->plane[2] *= -1; + node->plane[3] *= -1; + break; + } + } + node->parent = parentnodenum; + node->children[0] = -1; // empty + node->children[1] = -1; // empty + // link this child into the tree + *parentnodenumpointer = parentnodenum = (int)(node - b->nodes); + // now point to the child pointer for the next node to update later + parentnodenumpointer = &node->children[1]; + } + +#if 1 + // skip the face plane if it lies on a parent plane + if (!poly->facesplitflag) +#endif + { + // add the face-plane node + // infront is empty, behind is shadow + node = b->nodes + b->numnodes++; + SVBSP_PlaneFromPoints(node->plane, poly->points[0], poly->points[1], poly->points[2]); + // this is a flip check similar to the one above + // this one checks if the plane faces the origin, if not, flip it + if (SVBSP_DotProduct(b->origin, node->plane) - node->plane[3] < -SVBSP_CLIP_EPSILON) + { + node->plane[0] *= -1; + node->plane[1] *= -1; + node->plane[2] *= -1; + node->plane[3] *= -1; + } + node->parent = parentnodenum; + node->children[0] = -1; // empty + node->children[1] = -2; // shadow + // link this child into the tree + // (with the addition of this node, queries will now be culled by it) + *parentnodenumpointer = (int)(node - b->nodes); + } +} + +static int SVBSP_AddPolygonNode(svbsp_t *b, int *parentnodenumpointer, int parentnodenum, const svbsp_polygon_t *poly, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) +{ + int i; + int s; + int facesplitflag = poly->facesplitflag; + int bothsides; + float plane[4]; + float d; + svbsp_polygon_t front; + svbsp_polygon_t back; + svbsp_node_t *node; + int sides[MAX_SVBSP_POLYGONPOINTS]; + float dists[MAX_SVBSP_POLYGONPOINTS]; + if (poly->numpoints < 1) + return 0; + // recurse through plane nodes + while (*parentnodenumpointer >= 0) + { + // get node info + parentnodenum = *parentnodenumpointer; + node = b->nodes + parentnodenum; + plane[0] = node->plane[0]; + plane[1] = node->plane[1]; + plane[2] = node->plane[2]; + plane[3] = node->plane[3]; + // calculate point dists for clipping + bothsides = 0; + for (i = 0;i < poly->numpoints;i++) + { + d = SVBSP_DotProduct(poly->points[i], plane) - plane[3]; + s = 0; + if (d > SVBSP_CLIP_EPSILON) + s = 1; + if (d < -SVBSP_CLIP_EPSILON) + s = 2; + bothsides |= s; + dists[i] = d; + sides[i] = s; + } + // see which side the polygon is on + switch(bothsides) + { + default: + case 0: + // no need to split, this polygon is on the plane + // this case only occurs for polygons on the face plane, usually + // the same polygon (inserted twice - once as occluder, once as + // tester) + // if this is an occluder, it is redundant + if (insertoccluder) + return 1; // occluded + // if this is a tester, test the front side, because it is + // probably the same polygon that created this node... + facesplitflag = 1; + parentnodenumpointer = &node->children[0]; + continue; + case 1: + // no need to split, just go to one side + parentnodenumpointer = &node->children[0]; + continue; + case 2: + // no need to split, just go to one side + parentnodenumpointer = &node->children[1]; + continue; + case 3: + // lies on both sides of the plane, we need to split it +#if 1 + SVBSP_DividePolygon(poly, plane, &front, &back, dists, sides); +#else + PolygonF_Divide(poly->numpoints, poly->points[0], plane[0], plane[1], plane[2], plane[3], SVBSP_CLIP_EPSILON, MAX_SVBSP_POLYGONPOINTS, front.points[0], &front.numpoints, MAX_SVBSP_POLYGONPOINTS, back.points[0], &back.numpoints, NULL); + if (front.numpoints > MAX_SVBSP_POLYGONPOINTS) + front.numpoints = MAX_SVBSP_POLYGONPOINTS; + if (back.numpoints > MAX_SVBSP_POLYGONPOINTS) + back.numpoints = MAX_SVBSP_POLYGONPOINTS; +#endif + front.facesplitflag = facesplitflag; + back.facesplitflag = facesplitflag; + // recurse the sides and return the resulting occlusion flags + i = SVBSP_AddPolygonNode(b, &node->children[0], *parentnodenumpointer, &front, insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); + i |= SVBSP_AddPolygonNode(b, &node->children[1], *parentnodenumpointer, &back , insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); + return i; + } + } + // leaf node + if (*parentnodenumpointer == -1) + { + // empty leaf node; and some geometry survived + // if inserting an occluder, replace this empty leaf with a shadow volume +#if 0 + for (i = 0;i < poly->numpoints-2;i++) + { + Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); + Debug_PolygonVertex(poly->points[ 0][0], poly->points[ 0][1], poly->points[ 0][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); + Debug_PolygonVertex(poly->points[i+1][0], poly->points[i+1][1], poly->points[i+1][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); + Debug_PolygonVertex(poly->points[i+2][0], poly->points[i+2][1], poly->points[i+2][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); + Debug_PolygonEnd(); + } +#endif + if (insertoccluder) + { + b->stat_occluders_fragments_accepted++; + SVBSP_InsertOccluderPolygonNodes(b, parentnodenumpointer, parentnodenum, poly, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); + } + else + b->stat_queries_fragments_accepted++; + if (fragmentcallback) + fragmentcallback(fragmentcallback_pointer1, fragmentcallback_number1, b, poly->numpoints, poly->points[0]); + return 2; + } + else + { + // otherwise it's a solid leaf which destroys all polygons inside it + if (insertoccluder) + b->stat_occluders_fragments_rejected++; + else + b->stat_queries_fragments_rejected++; +#if 0 + for (i = 0;i < poly->numpoints-2;i++) + { + Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); + Debug_PolygonVertex(poly->points[ 0][0], poly->points[ 0][1], poly->points[ 0][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); + Debug_PolygonVertex(poly->points[i+1][0], poly->points[i+1][1], poly->points[i+1][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); + Debug_PolygonVertex(poly->points[i+2][0], poly->points[i+2][1], poly->points[i+2][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); + Debug_PolygonEnd(); + } +#endif + } + return 1; +} + +int SVBSP_AddPolygon(svbsp_t *b, int numpoints, const float *points, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) +{ + int i; + int nodenum; + svbsp_polygon_t poly; + // don't even consider an empty polygon + // note we still allow points and lines to be tested... + if (numpoints < 1) + return 0; + // if the polygon has too many points, we would crash + if (numpoints > MAX_SVBSP_POLYGONPOINTS) + return 0; + poly.numpoints = numpoints; + for (i = 0;i < numpoints;i++) + { + poly.points[i][0] = points[i*3+0]; + poly.points[i][1] = points[i*3+1]; + poly.points[i][2] = points[i*3+2]; + //poly.splitflags[i] = 0; // this edge is a valid BSP splitter - clipped edges are not (because they lie on a bsp plane) + poly.facesplitflag = 0; // this face is a valid BSP Splitter - if it lies on a bsp plane it is not + } +#if 0 +//if (insertoccluder) + for (i = 0;i < poly.numpoints-2;i++) + { + Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); + Debug_PolygonVertex(poly.points[ 0][0], poly.points[ 0][1], poly.points[ 0][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); + Debug_PolygonVertex(poly.points[i+1][0], poly.points[i+1][1], poly.points[i+1][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); + Debug_PolygonVertex(poly.points[i+2][0], poly.points[i+2][1], poly.points[i+2][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); + Debug_PolygonEnd(); + } +#endif + nodenum = 0; + i = SVBSP_AddPolygonNode(b, &nodenum, -1, &poly, insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); + if (insertoccluder) + { + if (i & 2) + b->stat_occluders_accepted++; + else + b->stat_occluders_rejected++; + } + else + { + if (i & 2) + b->stat_queries_accepted++; + else + b->stat_queries_rejected++; + } + return i; +} + diff --git a/misc/source/darkplaces-src/svbsp.h b/misc/source/darkplaces-src/svbsp.h new file mode 100644 index 00000000..ca87f0d9 --- /dev/null +++ b/misc/source/darkplaces-src/svbsp.h @@ -0,0 +1,75 @@ + +// Shadow Volume BSP code written by Forest "LordHavoc" Hale on 2003-11-06 and placed into public domain. +// Modified by LordHavoc (to make it work and other nice things like that) on 2007-01-24 and 2007-01-25 + +#ifndef SVBSP_H +#define SVBSP_H + +typedef struct svbsp_node_s +{ + // notes: + // leaf nodes are not stored, these are always structural nodes + // (they always have a plane and two children) + // children[] can be -1 for empty leaf or -2 for shadow leaf, >= 0 is node + // parent can be -1 if this is the root node, >= 0 is a node + int parent, children[2], padding; + // node plane, splits space + float plane[4]; +} +svbsp_node_t; + +typedef struct svbsp_s +{ + // lightsource or view origin + float origin[3]; + // current number of nodes in the svbsp + int numnodes; + // how big the nodes array is + int maxnodes; + // first node is the root node + svbsp_node_t *nodes; + // non-zero indicates that an insertion failed because of lack of nodes + int ranoutofnodes; + // tree statistics + // note: do not use multithreading when gathering statistics! + // (the code updating these counters is not thread-safe, increments may + // sometimes fail when multithreaded) + int stat_occluders_rejected; + int stat_occluders_accepted; + int stat_occluders_fragments_rejected; + int stat_occluders_fragments_accepted; + int stat_queries_rejected; + int stat_queries_accepted; + int stat_queries_fragments_rejected; + int stat_queries_fragments_accepted; +} +svbsp_t; + +// this function initializes a tree to prepare for polygon insertions +// +// the maxnodes needed for a given polygon set can vary wildly, if there are +// not enough maxnodes then later polygons will not be inserted and the field +// svbsp_t->ranoutofnodes will be non-zero +// +// as a rule of thumb the minimum nodes needed for a polygon set is +// numpolygons * (averagepolygonvertices + 1) +void SVBSP_Init(svbsp_t *b, const float *origin, int maxnodes, svbsp_node_t *nodes); + +// this function tests if any part of a polygon is not in shadow, and returns +// non-zero if the polygon is not completely shadowed +// +// returns 0 if the polygon was rejected (not facing origin or no points) +// returns 1 if all of the polygon is in shadow +// returns 2 if all of the polygon is unshadowed +// returns 3 if some of the polygon is shadowed and some unshadowed +// +// it also can add a new shadow volume (insertoccluder parameter) +// +// additionally it calls your fragmentcallback on each unshadowed clipped +// part of the polygon +// (beware that polygons often get split heavily, even if entirely unshadowed) +// +// thread-safety notes: do not multi-thread insertions! +int SVBSP_AddPolygon(svbsp_t *b, int numpoints, const float *points, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1); + +#endif diff --git a/misc/source/darkplaces-src/svvm_cmds.c b/misc/source/darkplaces-src/svvm_cmds.c new file mode 100644 index 00000000..f252ade1 --- /dev/null +++ b/misc/source/darkplaces-src/svvm_cmds.c @@ -0,0 +1,3802 @@ +#include "quakedef.h" + +#include "prvm_cmds.h" +#include "jpeg.h" + +//============================================================================ +// Server + + + +const char *vm_sv_extensions = +"BX_WAL_SUPPORT " +"DP_BUTTONCHAT " +"DP_BUTTONUSE " +"DP_CL_LOADSKY " +"DP_CON_ALIASPARAMETERS " +"DP_CON_BESTWEAPON " +"DP_CON_EXPANDCVAR " +"DP_CON_SET " +"DP_CON_SETA " +"DP_CON_STARTMAP " +"DP_CRYPTO " +"DP_CSQC_BINDMAPS " +"DP_CSQC_ENTITYNOCULL " +"DP_CSQC_ENTITYTRANSPARENTSORTING_OFFSET " +"DP_CSQC_MULTIFRAME_INTERPOLATION " +"DP_CSQC_BOXPARTICLES " +"DP_CSQC_SPAWNPARTICLE " +"DP_CSQC_QUERYRENDERENTITY " +"DP_CSQC_ROTATEMOVES " +"DP_CSQC_SETPAUSE " +"DP_EF_ADDITIVE " +"DP_EF_BLUE " +"DP_EF_DOUBLESIDED " +"DP_EF_FLAME " +"DP_EF_FULLBRIGHT " +"DP_EF_NODEPTHTEST " +"DP_EF_NODRAW " +"DP_EF_NOGUNBOB " +"DP_EF_NOSELFSHADOW " +"DP_EF_NOSHADOW " +"DP_EF_RED " +"DP_EF_RESTARTANIM_BIT " +"DP_EF_STARDUST " +"DP_EF_TELEPORT_BIT " +"DP_ENT_ALPHA " +"DP_ENT_COLORMOD " +"DP_ENT_CUSTOMCOLORMAP " +"DP_ENT_EXTERIORMODELTOCLIENT " +"DP_ENT_GLOW " +"DP_ENT_GLOWMOD " +"DP_ENT_LOWPRECISION " +"DP_ENT_SCALE " +"DP_ENT_TRAILEFFECTNUM " +"DP_ENT_VIEWMODEL " +"DP_GECKO_SUPPORT " +"DP_GFX_EXTERNALTEXTURES " +"DP_GFX_EXTERNALTEXTURES_PERMAP " +"DP_GFX_FOG " +"DP_GFX_MODEL_INTERPOLATION " +"DP_GFX_QUAKE3MODELTAGS " +"DP_GFX_SKINFILES " +"DP_GFX_SKYBOX " +"DP_GFX_FONTS " +"DP_GFX_FONTS_FREETYPE " +"DP_UTF8 " +"DP_FONT_VARIABLEWIDTH " +"DP_HALFLIFE_MAP " +"DP_HALFLIFE_MAP_CVAR " +"DP_HALFLIFE_SPRITE " +"DP_INPUTBUTTONS " +"DP_LIGHTSTYLE_STATICVALUE " +"DP_LITSPRITES " +"DP_LITSUPPORT " +"DP_MONSTERWALK " +"DP_MOVETYPEBOUNCEMISSILE " +"DP_MOVETYPEFOLLOW " +"DP_NULL_MODEL " +"DP_QC_ASINACOSATANATAN2TAN " +"DP_QC_AUTOCVARS " +"DP_QC_CHANGEPITCH " +"DP_QC_CMD " +"DP_QC_COPYENTITY " +"DP_QC_CRC16 " +"DP_QC_CVAR_DEFSTRING " +"DP_QC_CVAR_DESCRIPTION " +"DP_QC_CVAR_STRING " +"DP_QC_CVAR_TYPE " +"DP_QC_EDICT_NUM " +"DP_QC_ENTITYDATA " +"DP_QC_ENTITYSTRING " +"DP_QC_ETOS " +"DP_QC_EXTRESPONSEPACKET " +"DP_QC_FINDCHAIN " +"DP_QC_FINDCHAINFLAGS " +"DP_QC_FINDCHAINFLOAT " +"DP_QC_FINDCHAIN_TOFIELD " +"DP_QC_FINDFLAGS " +"DP_QC_FINDFLOAT " +"DP_QC_FS_SEARCH " +"DP_QC_GETLIGHT " +"DP_QC_GETSURFACE " +"DP_QC_GETSURFACETRIANGLE " +"DP_QC_GETSURFACEPOINTATTRIBUTE " +"DP_QC_GETTAGINFO " +"DP_QC_GETTAGINFO_BONEPROPERTIES " +"DP_QC_GETTIME " +"DP_QC_GETTIME_CDTRACK " +"DP_QC_LOG " +"DP_QC_MINMAXBOUND " +"DP_QC_MULTIPLETEMPSTRINGS " +"DP_QC_NUM_FOR_EDICT " +"DP_QC_RANDOMVEC " +"DP_QC_SINCOSSQRTPOW " +"DP_QC_SPRINTF " +"DP_QC_STRFTIME " +"DP_QC_STRINGBUFFERS " +"DP_QC_STRINGBUFFERS_CVARLIST " +"DP_QC_STRINGCOLORFUNCTIONS " +"DP_QC_STRING_CASE_FUNCTIONS " +"DP_QC_STRREPLACE " +"DP_QC_TOKENIZEBYSEPARATOR " +"DP_QC_TOKENIZE_CONSOLE " +"DP_QC_TRACEBOX " +"DP_QC_TRACETOSS " +"DP_QC_TRACE_MOVETYPE_HITMODEL " +"DP_QC_TRACE_MOVETYPE_WORLDONLY " +"DP_QC_UNLIMITEDTEMPSTRINGS " +"DP_QC_URI_ESCAPE " +"DP_QC_URI_GET " +"DP_QC_URI_POST " +"DP_QC_VECTOANGLES_WITH_ROLL " +"DP_QC_VECTORVECTORS " +"DP_QC_WHICHPACK " +"DP_QUAKE2_MODEL " +"DP_QUAKE2_SPRITE " +"DP_QUAKE3_MAP " +"DP_QUAKE3_MODEL " +"DP_REGISTERCVAR " +"DP_SKELETONOBJECTS " +"DP_SND_DIRECTIONLESSATTNNONE " +"DP_SND_FAKETRACKS " +"DP_SND_SOUND7_WIP1 " +"DP_SND_OGGVORBIS " +"DP_SND_SETPARAMS " +"DP_SND_STEREOWAV " +"DP_SND_GETSOUNDTIME " +"DP_VIDEO_DPV " +"DP_VIDEO_SUBTITLES " +"DP_SOLIDCORPSE " +"DP_SPRITE32 " +"DP_SV_BOTCLIENT " +"DP_SV_BOUNCEFACTOR " +"DP_SV_CLIENTCAMERA " +"DP_SV_CLIENTCOLORS " +"DP_SV_CLIENTNAME " +"DP_SV_CMD " +"DP_SV_CUSTOMIZEENTITYFORCLIENT " +"DP_SV_DISCARDABLEDEMO " +"DP_SV_DRAWONLYTOCLIENT " +"DP_SV_DROPCLIENT " +"DP_SV_EFFECT " +"DP_SV_ENTITYCONTENTSTRANSITION " +"DP_SV_MODELFLAGS_AS_EFFECTS " +"DP_SV_MOVETYPESTEP_LANDEVENT " +"DP_SV_NETADDRESS " +"DP_SV_NODRAWTOCLIENT " +"DP_SV_ONENTITYNOSPAWNFUNCTION " +"DP_SV_ONENTITYPREPOSTSPAWNFUNCTION " +"DP_SV_PING " +"DP_SV_PING_PACKETLOSS " +"DP_SV_PLAYERPHYSICS " +"DP_PHYSICS_ODE " +"DP_SV_POINTPARTICLES " +"DP_SV_POINTSOUND " +"DP_SV_PRECACHEANYTIME " +"DP_SV_PRINT " +"DP_SV_PUNCHVECTOR " +"DP_SV_QCSTATUS " +"DP_SV_ROTATINGBMODEL " +"DP_SV_SETCOLOR " +"DP_SV_SHUTDOWN " +"DP_SV_SLOWMO " +"DP_SV_SPAWNFUNC_PREFIX " +"DP_SV_WRITEPICTURE " +"DP_SV_WRITEUNTERMINATEDSTRING " +"DP_TE_BLOOD " +"DP_TE_BLOODSHOWER " +"DP_TE_CUSTOMFLASH " +"DP_TE_EXPLOSIONRGB " +"DP_TE_FLAMEJET " +"DP_TE_PARTICLECUBE " +"DP_TE_PARTICLERAIN " +"DP_TE_PARTICLESNOW " +"DP_TE_PLASMABURN " +"DP_TE_QUADEFFECTS1 " +"DP_TE_SMALLFLASH " +"DP_TE_SPARK " +"DP_TE_STANDARDEFFECTBUILTINS " +"DP_TRACE_HITCONTENTSMASK_SURFACEINFO " +"DP_VIEWZOOM " +"EXT_BITSHIFT " +"FRIK_FILE " +"FTE_CSQC_SKELETONOBJECTS " +"FTE_QC_CHECKPVS " +"FTE_STRINGS " +"KRIMZON_SV_PARSECLIENTCOMMAND " +"NEH_CMD_PLAY2 " +"NEH_RESTOREGAME " +"NEXUIZ_PLAYERMODEL " +"NXQ_GFX_LETTERBOX " +"PRYDON_CLIENTCURSOR " +"TENEBRAE_GFX_DLIGHTS " +"TW_SV_STEPCONTROL " +"ZQ_PAUSE " +//"EXT_CSQC " // not ready yet +; + +/* +================= +VM_SV_setorigin + +This is the only valid way to move an object without using the physics of the world (setting velocity and waiting). Directly changing origin will not set internal links correctly, so clipping would be messed up. This should be called when an object is spawned, and then only if it is teleported. + +setorigin (entity, origin) +================= +*/ +static void VM_SV_setorigin (void) +{ + prvm_edict_t *e; + float *org; + + VM_SAFEPARMCOUNT(2, VM_setorigin); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning("setorigin: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning("setorigin: can not modify free entity\n"); + return; + } + org = PRVM_G_VECTOR(OFS_PARM1); + VectorCopy (org, PRVM_serveredictvector(e, origin)); + SV_LinkEdict(e); +} + +// TODO: rotate param isnt used.. could be a bug. please check this and remove it if possible [1/10/2008 Black] +static void SetMinMaxSize (prvm_edict_t *e, float *min, float *max, qboolean rotate) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (min[i] > max[i]) + PRVM_ERROR("SetMinMaxSize: backwards mins/maxs"); + +// set derived values + VectorCopy (min, PRVM_serveredictvector(e, mins)); + VectorCopy (max, PRVM_serveredictvector(e, maxs)); + VectorSubtract (max, min, PRVM_serveredictvector(e, size)); + + SV_LinkEdict(e); +} + +/* +================= +VM_SV_setsize + +the size box is rotated by the current angle +LordHavoc: no it isn't... + +setsize (entity, minvector, maxvector) +================= +*/ +static void VM_SV_setsize (void) +{ + prvm_edict_t *e; + float *min, *max; + + VM_SAFEPARMCOUNT(3, VM_setsize); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning("setsize: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning("setsize: can not modify free entity\n"); + return; + } + min = PRVM_G_VECTOR(OFS_PARM1); + max = PRVM_G_VECTOR(OFS_PARM2); + SetMinMaxSize (e, min, max, false); +} + + +/* +================= +VM_SV_setmodel + +setmodel(entity, model) +================= +*/ +static vec3_t quakemins = {-16, -16, -16}, quakemaxs = {16, 16, 16}; +static void VM_SV_setmodel (void) +{ + prvm_edict_t *e; + dp_model_t *mod; + int i; + + VM_SAFEPARMCOUNT(2, VM_setmodel); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning("setmodel: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning("setmodel: can not modify free entity\n"); + return; + } + i = SV_ModelIndex(PRVM_G_STRING(OFS_PARM1), 1); + PRVM_serveredictstring(e, model) = PRVM_SetEngineString(sv.model_precache[i]); + PRVM_serveredictfloat(e, modelindex) = i; + + mod = SV_GetModelByIndex(i); + + if (mod) + { + if (mod->type != mod_alias || sv_gameplayfix_setmodelrealbox.integer) + SetMinMaxSize (e, mod->normalmins, mod->normalmaxs, true); + else + SetMinMaxSize (e, quakemins, quakemaxs, true); + } + else + SetMinMaxSize (e, vec3_origin, vec3_origin, true); +} + +/* +================= +VM_SV_sprint + +single print to a specific client + +sprint(clientent, value) +================= +*/ +static void VM_SV_sprint (void) +{ + client_t *client; + int entnum; + char string[VM_STRINGTEMP_LENGTH]; + + VM_VarString(1, string, sizeof(string)); + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_sprint); + + entnum = PRVM_G_EDICTNUM(OFS_PARM0); + // LordHavoc: div0 requested that sprintto world operate like print + if (entnum == 0) + { + Con_Print(string); + return; + } + + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) + { + VM_Warning("tried to centerprint to a non-client\n"); + return; + } + + client = svs.clients + entnum-1; + if (!client->netconnection) + return; + + MSG_WriteChar(&client->netconnection->message,svc_print); + MSG_WriteString(&client->netconnection->message, string); +} + + +/* +================= +VM_SV_centerprint + +single print to a specific client + +centerprint(clientent, value) +================= +*/ +static void VM_SV_centerprint (void) +{ + client_t *client; + int entnum; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_centerprint); + + entnum = PRVM_G_EDICTNUM(OFS_PARM0); + + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) + { + VM_Warning("tried to centerprint to a non-client\n"); + return; + } + + client = svs.clients + entnum-1; + if (!client->netconnection) + return; + + VM_VarString(1, string, sizeof(string)); + MSG_WriteChar(&client->netconnection->message,svc_centerprint); + MSG_WriteString(&client->netconnection->message, string); +} + +/* +================= +VM_SV_particle + +particle(origin, color, count) +================= +*/ +static void VM_SV_particle (void) +{ + float *org, *dir; + float color; + float count; + + VM_SAFEPARMCOUNT(4, VM_SV_particle); + + org = PRVM_G_VECTOR(OFS_PARM0); + dir = PRVM_G_VECTOR(OFS_PARM1); + color = PRVM_G_FLOAT(OFS_PARM2); + count = PRVM_G_FLOAT(OFS_PARM3); + SV_StartParticle (org, dir, (int)color, (int)count); +} + + +/* +================= +VM_SV_ambientsound + +================= +*/ +static void VM_SV_ambientsound (void) +{ + const char *samp; + float *pos; + float vol, attenuation; + int soundnum, large; + + VM_SAFEPARMCOUNT(4, VM_SV_ambientsound); + + pos = PRVM_G_VECTOR (OFS_PARM0); + samp = PRVM_G_STRING(OFS_PARM1); + vol = PRVM_G_FLOAT(OFS_PARM2); + attenuation = PRVM_G_FLOAT(OFS_PARM3); + +// check to see if samp was properly precached + soundnum = SV_SoundIndex(samp, 1); + if (!soundnum) + return; + + large = false; + if (soundnum >= 256) + large = true; + + // add an svc_spawnambient command to the level signon packet + + if (large) + MSG_WriteByte (&sv.signon, svc_spawnstaticsound2); + else + MSG_WriteByte (&sv.signon, svc_spawnstaticsound); + + MSG_WriteVector(&sv.signon, pos, sv.protocol); + + if (large || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + MSG_WriteShort (&sv.signon, soundnum); + else + MSG_WriteByte (&sv.signon, soundnum); + + MSG_WriteByte (&sv.signon, (int)(vol*255)); + MSG_WriteByte (&sv.signon, (int)(attenuation*64)); + +} + +/* +================= +VM_SV_sound + +Each entity can have eight independant sound sources, like voice, +weapon, feet, etc. + +Channel 0 is an auto-allocate channel, the others override anything +already running on that entity/channel pair. + +An attenuation of 0 will play full volume everywhere in the level. +Larger attenuations will drop off. + +================= +*/ +static void VM_SV_sound (void) +{ + const char *sample; + int channel; + prvm_edict_t *entity; + int volume; + int flags; + float attenuation; + float pitchchange; + + VM_SAFEPARMCOUNTRANGE(4, 7, VM_SV_sound); + + entity = PRVM_G_EDICT(OFS_PARM0); + channel = (int)PRVM_G_FLOAT(OFS_PARM1); + sample = PRVM_G_STRING(OFS_PARM2); + volume = (int)(PRVM_G_FLOAT(OFS_PARM3) * 255); + if (prog->argc < 5) + { + Con_DPrintf("VM_SV_sound: given only 4 parameters, expected 5, assuming attenuation = ATTN_NORMAL\n"); + attenuation = 1; + } + else + attenuation = PRVM_G_FLOAT(OFS_PARM4); + if (prog->argc < 6) + pitchchange = 0; + else + pitchchange = PRVM_G_FLOAT(OFS_PARM5); + + if (prog->argc < 7) + { + flags = 0; + if(channel >= 8 && channel <= 15) // weird QW feature + { + flags |= CHANFLAG_RELIABLE; + channel -= 8; + } + } + else + flags = PRVM_G_FLOAT(OFS_PARM6); + + if (volume < 0 || volume > 255) + { + VM_Warning("SV_StartSound: volume must be in range 0-1\n"); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + VM_Warning("SV_StartSound: attenuation must be in range 0-4\n"); + return; + } + + channel = CHAN_USER2ENGINE(channel); + + if (!IS_CHAN(channel)) + { + VM_Warning("SV_StartSound: channel must be in range 0-127\n"); + return; + } + + SV_StartSound (entity, channel, sample, volume, attenuation, flags & CHANFLAG_RELIABLE); +} + +/* +================= +VM_SV_pointsound + +Follows the same logic as VM_SV_sound, except instead of +an entity, an origin for the sound is provided, and channel +is omitted (since no entity is being tracked). + +================= +*/ +static void VM_SV_pointsound(void) +{ + const char *sample; + int volume; + float attenuation; + vec3_t org; + + VM_SAFEPARMCOUNT(4, VM_SV_pointsound); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + sample = PRVM_G_STRING(OFS_PARM1); + volume = (int)(PRVM_G_FLOAT(OFS_PARM2) * 255); + attenuation = PRVM_G_FLOAT(OFS_PARM3); + + if (volume < 0 || volume > 255) + { + VM_Warning("SV_StartPointSound: volume must be in range 0-1\n"); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + VM_Warning("SV_StartPointSound: attenuation must be in range 0-4\n"); + return; + } + + SV_StartPointSound (org, sample, volume, attenuation); +} + +/* +================= +VM_SV_traceline + +Used for use tracing and shot targeting +Traces are blocked by bbox and exact bsp entityes, and also slide box entities +if the tryents flag is set. + +traceline (vector1, vector2, movetype, ignore) +================= +*/ +static void VM_SV_traceline (void) +{ + float *v1, *v2; + trace_t trace; + int move; + prvm_edict_t *ent; + + VM_SAFEPARMCOUNTRANGE(4, 8, VM_SV_traceline); // allow more parameters for future expansion + + prog->xfunction->builtinsprofile += 30; + + v1 = PRVM_G_VECTOR(OFS_PARM0); + v2 = PRVM_G_VECTOR(OFS_PARM1); + move = (int)PRVM_G_FLOAT(OFS_PARM2); + ent = PRVM_G_EDICT(OFS_PARM3); + + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) + PRVM_ERROR("%s: NAN errors detected in traceline('%f %f %f', '%f %f %f', %i, entity %i)\n", PRVM_NAME, v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); + + trace = SV_TraceLine(v1, v2, move, ent, SV_GenericHitSuperContentsMask(ent)); + + VM_SetTraceGlobals(&trace); +} + + +/* +================= +VM_SV_tracebox + +Used for use tracing and shot targeting +Traces are blocked by bbox and exact bsp entityes, and also slide box entities +if the tryents flag is set. + +tracebox (vector1, vector mins, vector maxs, vector2, tryents) +================= +*/ +// LordHavoc: added this for my own use, VERY useful, similar to traceline +static void VM_SV_tracebox (void) +{ + float *v1, *v2, *m1, *m2; + trace_t trace; + int move; + prvm_edict_t *ent; + + VM_SAFEPARMCOUNTRANGE(6, 8, VM_SV_tracebox); // allow more parameters for future expansion + + prog->xfunction->builtinsprofile += 30; + + v1 = PRVM_G_VECTOR(OFS_PARM0); + m1 = PRVM_G_VECTOR(OFS_PARM1); + m2 = PRVM_G_VECTOR(OFS_PARM2); + v2 = PRVM_G_VECTOR(OFS_PARM3); + move = (int)PRVM_G_FLOAT(OFS_PARM4); + ent = PRVM_G_EDICT(OFS_PARM5); + + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) + PRVM_ERROR("%s: NAN errors detected in tracebox('%f %f %f', '%f %f %f', '%f %f %f', '%f %f %f', %i, entity %i)\n", PRVM_NAME, v1[0], v1[1], v1[2], m1[0], m1[1], m1[2], m2[0], m2[1], m2[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); + + trace = SV_TraceBox(v1, m1, m2, v2, move, ent, SV_GenericHitSuperContentsMask(ent)); + + VM_SetTraceGlobals(&trace); +} + +static trace_t SV_Trace_Toss (prvm_edict_t *tossent, prvm_edict_t *ignore) +{ + int i; + float gravity; + vec3_t move, end; + vec3_t original_origin; + vec3_t original_velocity; + vec3_t original_angles; + vec3_t original_avelocity; + trace_t trace; + + VectorCopy(PRVM_serveredictvector(tossent, origin) , original_origin ); + VectorCopy(PRVM_serveredictvector(tossent, velocity) , original_velocity ); + VectorCopy(PRVM_serveredictvector(tossent, angles) , original_angles ); + VectorCopy(PRVM_serveredictvector(tossent, avelocity), original_avelocity); + + gravity = PRVM_serveredictfloat(tossent, gravity); + if (!gravity) + gravity = 1.0f; + gravity *= sv_gravity.value * 0.025; + + for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds + { + SV_CheckVelocity (tossent); + PRVM_serveredictvector(tossent, velocity)[2] -= gravity; + VectorMA (PRVM_serveredictvector(tossent, angles), 0.05, PRVM_serveredictvector(tossent, avelocity), PRVM_serveredictvector(tossent, angles)); + VectorScale (PRVM_serveredictvector(tossent, velocity), 0.05, move); + VectorAdd (PRVM_serveredictvector(tossent, origin), move, end); + trace = SV_TraceBox(PRVM_serveredictvector(tossent, origin), PRVM_serveredictvector(tossent, mins), PRVM_serveredictvector(tossent, maxs), end, MOVE_NORMAL, tossent, SV_GenericHitSuperContentsMask(tossent)); + VectorCopy (trace.endpos, PRVM_serveredictvector(tossent, origin)); + PRVM_serveredictvector(tossent, velocity)[2] -= gravity; + + if (trace.fraction < 1) + break; + } + + VectorCopy(original_origin , PRVM_serveredictvector(tossent, origin) ); + VectorCopy(original_velocity , PRVM_serveredictvector(tossent, velocity) ); + VectorCopy(original_angles , PRVM_serveredictvector(tossent, angles) ); + VectorCopy(original_avelocity, PRVM_serveredictvector(tossent, avelocity)); + + return trace; +} + +static void VM_SV_tracetoss (void) +{ + trace_t trace; + prvm_edict_t *ent; + prvm_edict_t *ignore; + + VM_SAFEPARMCOUNT(2, VM_SV_tracetoss); + + prog->xfunction->builtinsprofile += 600; + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning("tracetoss: can not use world entity\n"); + return; + } + ignore = PRVM_G_EDICT(OFS_PARM1); + + trace = SV_Trace_Toss (ent, ignore); + + VM_SetTraceGlobals(&trace); +} + +//============================================================================ + +static int checkpvsbytes; +static unsigned char checkpvs[MAX_MAP_LEAFS/8]; + +static int VM_SV_newcheckclient (int check) +{ + int i; + prvm_edict_t *ent; + vec3_t org; + +// cycle to the next one + + check = bound(1, check, svs.maxclients); + if (check == svs.maxclients) + i = 1; + else + i = check + 1; + + for ( ; ; i++) + { + // count the cost + prog->xfunction->builtinsprofile++; + // wrap around + if (i == svs.maxclients+1) + i = 1; + // look up the client's edict + ent = PRVM_EDICT_NUM(i); + // check if it is to be ignored, but never ignore the one we started on (prevent infinite loop) + if (i != check && (ent->priv.server->free || PRVM_serveredictfloat(ent, health) <= 0 || ((int)PRVM_serveredictfloat(ent, flags) & FL_NOTARGET))) + continue; + // found a valid client (possibly the same one again) + break; + } + +// get the PVS for the entity + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, view_ofs), org); + checkpvsbytes = 0; + if (sv.worldmodel && sv.worldmodel->brush.FatPVS) + checkpvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, org, 0, checkpvs, sizeof(checkpvs), false); + + return i; +} + +/* +================= +VM_SV_checkclient + +Returns a client (or object that has a client enemy) that would be a +valid target. + +If there is more than one valid option, they are cycled each frame + +If (self.origin + self.viewofs) is not in the PVS of the current target, +it is not returned at all. + +name checkclient () +================= +*/ +int c_invis, c_notvis; +static void VM_SV_checkclient (void) +{ + prvm_edict_t *ent, *self; + vec3_t view; + + VM_SAFEPARMCOUNT(0, VM_SV_checkclient); + + // find a new check if on a new frame + if (sv.time - sv.lastchecktime >= 0.1) + { + sv.lastcheck = VM_SV_newcheckclient (sv.lastcheck); + sv.lastchecktime = sv.time; + } + + // return check if it might be visible + ent = PRVM_EDICT_NUM(sv.lastcheck); + if (ent->priv.server->free || PRVM_serveredictfloat(ent, health) <= 0) + { + VM_RETURN_EDICT(prog->edicts); + return; + } + + // if current entity can't possibly see the check entity, return 0 + self = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + VectorAdd(PRVM_serveredictvector(self, origin), PRVM_serveredictvector(self, view_ofs), view); + if (sv.worldmodel && checkpvsbytes && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, checkpvs, view, view)) + { + c_notvis++; + VM_RETURN_EDICT(prog->edicts); + return; + } + + // might be able to see it + c_invis++; + VM_RETURN_EDICT(ent); +} + +//============================================================================ + +/* +================= +VM_SV_checkpvs + +Checks if an entity is in a point's PVS. +Should be fast but can be inexact. + +float checkpvs(vector viewpos, entity viewee) = #240; +================= +*/ +static void VM_SV_checkpvs (void) +{ + vec3_t viewpos; + prvm_edict_t *viewee; +#if 1 + unsigned char *pvs; +#else + int fatpvsbytes; + unsigned char fatpvs[MAX_MAP_LEAFS/8]; +#endif + + VM_SAFEPARMCOUNT(2, VM_SV_checkpvs); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), viewpos); + viewee = PRVM_G_EDICT(OFS_PARM1); + + if(viewee->priv.server->free) + { + VM_Warning("checkpvs: can not check free entity\n"); + PRVM_G_FLOAT(OFS_RETURN) = 4; + return; + } + +#if 1 + if(!sv.worldmodel->brush.GetPVS || !sv.worldmodel->brush.BoxTouchingPVS) + { + // no PVS support on this worldmodel... darn + PRVM_G_FLOAT(OFS_RETURN) = 3; + return; + } + pvs = sv.worldmodel->brush.GetPVS(sv.worldmodel, viewpos); + if(!pvs) + { + // viewpos isn't in any PVS... darn + PRVM_G_FLOAT(OFS_RETURN) = 2; + return; + } + PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, pvs, PRVM_serveredictvector(viewee, absmin), PRVM_serveredictvector(viewee, absmax)); +#else + // using fat PVS like FTEQW does (slow) + if(!sv.worldmodel->brush.FatPVS || !sv.worldmodel->brush.BoxTouchingPVS) + { + // no PVS support on this worldmodel... darn + PRVM_G_FLOAT(OFS_RETURN) = 3; + return; + } + fatpvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, viewpos, 8, fatpvs, sizeof(fatpvs), false); + if(!fatpvsbytes) + { + // viewpos isn't in any PVS... darn + PRVM_G_FLOAT(OFS_RETURN) = 2; + return; + } + PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, fatpvs, PRVM_serveredictvector(viewee, absmin), PRVM_serveredictvector(viewee, absmax)); +#endif +} + + +/* +================= +VM_SV_stuffcmd + +Sends text over to the client's execution buffer + +stuffcmd (clientent, value, ...) +================= +*/ +static void VM_SV_stuffcmd (void) +{ + int entnum; + client_t *old; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_stuffcmd); + + entnum = PRVM_G_EDICTNUM(OFS_PARM0); + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) + { + VM_Warning("Can't stuffcmd to a non-client\n"); + return; + } + + VM_VarString(1, string, sizeof(string)); + + old = host_client; + host_client = svs.clients + entnum-1; + Host_ClientCommands ("%s", string); + host_client = old; +} + +/* +================= +VM_SV_findradius + +Returns a chain of entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static void VM_SV_findradius (void) +{ + prvm_edict_t *ent, *chain; + vec_t radius, radius2; + vec3_t org, eorg, mins, maxs; + int i; + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_SV_findradius); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if (chainfield < 0) + PRVM_ERROR("VM_findchain: %s doesnt have the specified chain field !", PRVM_NAME); + + chain = (prvm_edict_t *)prog->edicts; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + radius = PRVM_G_FLOAT(OFS_PARM1); + radius2 = radius * radius; + + mins[0] = org[0] - (radius + 1); + mins[1] = org[1] - (radius + 1); + mins[2] = org[2] - (radius + 1); + maxs[0] = org[0] + (radius + 1); + maxs[1] = org[1] + (radius + 1); + maxs[2] = org[2] + (radius + 1); + numtouchedicts = World_EntitiesInBox(&sv.world, mins, maxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + ent = touchedicts[i]; + prog->xfunction->builtinsprofile++; + // Quake did not return non-solid entities but darkplaces does + // (note: this is the reason you can't blow up fallen zombies) + if (PRVM_serveredictfloat(ent, solid) == SOLID_NOT && !sv_gameplayfix_blowupfallenzombies.integer) + continue; + // LordHavoc: compare against bounding box rather than center so it + // doesn't miss large objects, and use DotProduct instead of Length + // for a major speedup + VectorSubtract(org, PRVM_serveredictvector(ent, origin), eorg); + if (sv_gameplayfix_findradiusdistancetobox.integer) + { + eorg[0] -= bound(PRVM_serveredictvector(ent, mins)[0], eorg[0], PRVM_serveredictvector(ent, maxs)[0]); + eorg[1] -= bound(PRVM_serveredictvector(ent, mins)[1], eorg[1], PRVM_serveredictvector(ent, maxs)[1]); + eorg[2] -= bound(PRVM_serveredictvector(ent, mins)[2], eorg[2], PRVM_serveredictvector(ent, maxs)[2]); + } + else + VectorMAMAM(1, eorg, -0.5f, PRVM_serveredictvector(ent, mins), -0.5f, PRVM_serveredictvector(ent, maxs), eorg); + if (DotProduct(eorg, eorg) < radius2) + { + PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); + chain = ent; + } + } + + VM_RETURN_EDICT(chain); +} + +static void VM_SV_precache_sound (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_precache_sound); + PRVM_G_FLOAT(OFS_RETURN) = SV_SoundIndex(PRVM_G_STRING(OFS_PARM0), 2); +} + +static void VM_SV_precache_model (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_precache_model); + SV_ModelIndex(PRVM_G_STRING(OFS_PARM0), 2); + PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); +} + +/* +=============== +VM_SV_walkmove + +float(float yaw, float dist[, settrace]) walkmove +=============== +*/ +static void VM_SV_walkmove (void) +{ + prvm_edict_t *ent; + float yaw, dist; + vec3_t move; + mfunction_t *oldf; + int oldself; + qboolean settrace; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_SV_walkmove); + + // assume failure if it returns early + PRVM_G_FLOAT(OFS_RETURN) = 0; + + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning("walkmove: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("walkmove: can not modify free entity\n"); + return; + } + yaw = PRVM_G_FLOAT(OFS_PARM0); + dist = PRVM_G_FLOAT(OFS_PARM1); + settrace = prog->argc >= 3 && PRVM_G_FLOAT(OFS_PARM2); + + if ( !( (int)PRVM_serveredictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + return; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + +// save program state, because SV_movestep may call other progs + oldf = prog->xfunction; + oldself = PRVM_serverglobaledict(self); + + PRVM_G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true, false, settrace); + + +// restore program state + prog->xfunction = oldf; + PRVM_serverglobaledict(self) = oldself; +} + +/* +=============== +VM_SV_droptofloor + +void() droptofloor +=============== +*/ +static void VM_SV_droptofloor (void) +{ + prvm_edict_t *ent; + vec3_t end; + trace_t trace; + + VM_SAFEPARMCOUNTRANGE(0, 2, VM_SV_droptofloor); // allow 2 parameters because the id1 defs.qc had an incorrect prototype + + // assume failure if it returns early + PRVM_G_FLOAT(OFS_RETURN) = 0; + + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning("droptofloor: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("droptofloor: can not modify free entity\n"); + return; + } + + VectorCopy (PRVM_serveredictvector(ent, origin), end); + end[2] -= 256; + + if (sv_gameplayfix_droptofloorstartsolid_nudgetocorrect.integer) + SV_UnstickEntity(ent); + + trace = SV_TraceBox(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + if (trace.startsolid && sv_gameplayfix_droptofloorstartsolid.integer) + { + vec3_t offset, org; + VectorSet(offset, 0.5f * (PRVM_serveredictvector(ent, mins)[0] + PRVM_serveredictvector(ent, maxs)[0]), 0.5f * (PRVM_serveredictvector(ent, mins)[1] + PRVM_serveredictvector(ent, maxs)[1]), PRVM_serveredictvector(ent, mins)[2]); + VectorAdd(PRVM_serveredictvector(ent, origin), offset, org); + trace = SV_TraceLine(org, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + VectorSubtract(trace.endpos, offset, trace.endpos); + if (trace.startsolid) + { + Con_DPrintf("droptofloor at %f %f %f - COULD NOT FIX BADLY PLACED ENTITY\n", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + SV_UnstickEntity(ent); + SV_LinkEdict(ent); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = 0; + PRVM_G_FLOAT(OFS_RETURN) = 1; + } + else if (trace.fraction < 1) + { + Con_DPrintf("droptofloor at %f %f %f - FIXED BADLY PLACED ENTITY\n", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); + SV_UnstickEntity(ent); + SV_LinkEdict(ent); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + PRVM_G_FLOAT(OFS_RETURN) = 1; + // if support is destroyed, keep suspended (gross hack for floating items in various maps) + ent->priv.server->suspendedinairflag = true; + } + } + else + { + if (trace.fraction != 1) + { + if (trace.fraction < 1) + VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); + SV_LinkEdict(ent); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + PRVM_G_FLOAT(OFS_RETURN) = 1; + // if support is destroyed, keep suspended (gross hack for floating items in various maps) + ent->priv.server->suspendedinairflag = true; + } + } +} + +/* +=============== +VM_SV_lightstyle + +void(float style, string value) lightstyle +=============== +*/ +static void VM_SV_lightstyle (void) +{ + int style; + const char *val; + client_t *client; + int j; + + VM_SAFEPARMCOUNT(2, VM_SV_lightstyle); + + style = (int)PRVM_G_FLOAT(OFS_PARM0); + val = PRVM_G_STRING(OFS_PARM1); + + if( (unsigned) style >= MAX_LIGHTSTYLES ) { + PRVM_ERROR( "PF_lightstyle: style: %i >= 64", style ); + } + +// change the string in sv + strlcpy(sv.lightstyles[style], val, sizeof(sv.lightstyles[style])); + +// send message to all clients on this server + if (sv.state != ss_active) + return; + + for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++) + { + if (client->active && client->netconnection) + { + MSG_WriteChar (&client->netconnection->message, svc_lightstyle); + MSG_WriteChar (&client->netconnection->message,style); + MSG_WriteString (&client->netconnection->message, val); + } + } +} + +/* +============= +VM_SV_checkbottom +============= +*/ +static void VM_SV_checkbottom (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_checkbottom); + PRVM_G_FLOAT(OFS_RETURN) = SV_CheckBottom (PRVM_G_EDICT(OFS_PARM0)); +} + +/* +============= +VM_SV_pointcontents +============= +*/ +static void VM_SV_pointcontents (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_pointcontents); + PRVM_G_FLOAT(OFS_RETURN) = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(PRVM_G_VECTOR(OFS_PARM0))); +} + +/* +============= +VM_SV_aim + +Pick a vector for the player to shoot along +vector aim(entity, missilespeed) +============= +*/ +static void VM_SV_aim (void) +{ + prvm_edict_t *ent, *check, *bestent; + vec3_t start, dir, end, bestdir; + int i, j; + trace_t tr; + float dist, bestdist; + //float speed; + + VM_SAFEPARMCOUNT(2, VM_SV_aim); + + // assume failure if it returns early + VectorCopy(PRVM_serverglobalvector(v_forward), PRVM_G_VECTOR(OFS_RETURN)); + // if sv_aim is so high it can't possibly accept anything, skip out early + if (sv_aim.value >= 1) + return; + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning("aim: can not use world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("aim: can not use free entity\n"); + return; + } + //speed = PRVM_G_FLOAT(OFS_PARM1); + + VectorCopy (PRVM_serveredictvector(ent, origin), start); + start[2] += 20; + +// try sending a trace straight + VectorCopy (PRVM_serverglobalvector(v_forward), dir); + VectorMA (start, 2048, dir, end); + tr = SV_TraceLine(start, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY); + if (tr.ent && PRVM_serveredictfloat(((prvm_edict_t *)tr.ent), takedamage) == DAMAGE_AIM + && (!teamplay.integer || PRVM_serveredictfloat(ent, team) <=0 || PRVM_serveredictfloat(ent, team) != PRVM_serveredictfloat(((prvm_edict_t *)tr.ent), team)) ) + { + VectorCopy (PRVM_serverglobalvector(v_forward), PRVM_G_VECTOR(OFS_RETURN)); + return; + } + + +// try all possible entities + VectorCopy (dir, bestdir); + bestdist = sv_aim.value; + bestent = NULL; + + check = PRVM_NEXT_EDICT(prog->edicts); + for (i=1 ; inum_edicts ; i++, check = PRVM_NEXT_EDICT(check) ) + { + prog->xfunction->builtinsprofile++; + if (PRVM_serveredictfloat(check, takedamage) != DAMAGE_AIM) + continue; + if (check == ent) + continue; + if (teamplay.integer && PRVM_serveredictfloat(ent, team) > 0 && PRVM_serveredictfloat(ent, team) == PRVM_serveredictfloat(check, team)) + continue; // don't aim at teammate + for (j=0 ; j<3 ; j++) + end[j] = PRVM_serveredictvector(check, origin)[j] + + 0.5*(PRVM_serveredictvector(check, mins)[j] + PRVM_serveredictvector(check, maxs)[j]); + VectorSubtract (end, start, dir); + VectorNormalize (dir); + dist = DotProduct (dir, PRVM_serverglobalvector(v_forward)); + if (dist < bestdist) + continue; // to far to turn + tr = SV_TraceLine(start, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY); + if (tr.ent == check) + { // can shoot at this one + bestdist = dist; + bestent = check; + } + } + + if (bestent) + { + VectorSubtract (PRVM_serveredictvector(bestent, origin), PRVM_serveredictvector(ent, origin), dir); + dist = DotProduct (dir, PRVM_serverglobalvector(v_forward)); + VectorScale (PRVM_serverglobalvector(v_forward), dist, end); + end[2] = dir[2]; + VectorNormalize (end); + VectorCopy (end, PRVM_G_VECTOR(OFS_RETURN)); + } + else + { + VectorCopy (bestdir, PRVM_G_VECTOR(OFS_RETURN)); + } +} + +/* +=============================================================================== + +MESSAGE WRITING + +=============================================================================== +*/ + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_ENTITY 5 + +sizebuf_t *WriteDest (void) +{ + int entnum; + int dest; + prvm_edict_t *ent; + + dest = (int)PRVM_G_FLOAT(OFS_PARM0); + switch (dest) + { + case MSG_BROADCAST: + return &sv.datagram; + + case MSG_ONE: + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(msg_entity)); + entnum = PRVM_NUM_FOR_EDICT(ent); + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active || !svs.clients[entnum-1].netconnection) + { + VM_Warning ("WriteDest: tried to write to non-client\n"); + return &sv.reliable_datagram; + } + else + return &svs.clients[entnum-1].netconnection->message; + + default: + VM_Warning ("WriteDest: bad destination\n"); + case MSG_ALL: + return &sv.reliable_datagram; + + case MSG_INIT: + return &sv.signon; + + case MSG_ENTITY: + return sv.writeentitiestoclient_msg; + } + + //return NULL; +} + +static void VM_SV_WriteByte (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteByte); + MSG_WriteByte (WriteDest(), (int)PRVM_G_FLOAT(OFS_PARM1)); +} + +static void VM_SV_WriteChar (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteChar); + MSG_WriteChar (WriteDest(), (int)PRVM_G_FLOAT(OFS_PARM1)); +} + +static void VM_SV_WriteShort (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteShort); + MSG_WriteShort (WriteDest(), (int)PRVM_G_FLOAT(OFS_PARM1)); +} + +static void VM_SV_WriteLong (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteLong); + MSG_WriteLong (WriteDest(), (int)PRVM_G_FLOAT(OFS_PARM1)); +} + +static void VM_SV_WriteAngle (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteAngle); + MSG_WriteAngle (WriteDest(), PRVM_G_FLOAT(OFS_PARM1), sv.protocol); +} + +static void VM_SV_WriteCoord (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteCoord); + MSG_WriteCoord (WriteDest(), PRVM_G_FLOAT(OFS_PARM1), sv.protocol); +} + +static void VM_SV_WriteString (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteString); + MSG_WriteString (WriteDest(), PRVM_G_STRING(OFS_PARM1)); +} + +static void VM_SV_WriteUnterminatedString (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteUnterminatedString); + MSG_WriteUnterminatedString (WriteDest(), PRVM_G_STRING(OFS_PARM1)); +} + + +static void VM_SV_WriteEntity (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteEntity); + MSG_WriteShort (WriteDest(), PRVM_G_EDICTNUM(OFS_PARM1)); +} + +// writes a picture as at most size bytes of data +// message: +// IMGNAME \0 SIZE(short) IMGDATA +// if failed to read/compress: +// IMGNAME \0 \0 \0 +//#501 void(float dest, string name, float maxsize) WritePicture (DP_SV_WRITEPICTURE)) +static void VM_SV_WritePicture (void) +{ + const char *imgname; + void *buf; + size_t size; + + VM_SAFEPARMCOUNT(3, VM_SV_WritePicture); + + imgname = PRVM_G_STRING(OFS_PARM1); + size = (int) PRVM_G_FLOAT(OFS_PARM2); + if(size > 65535) + size = 65535; + + MSG_WriteString(WriteDest(), imgname); + if(Image_Compress(imgname, size, &buf, &size)) + { + // actual picture + MSG_WriteShort(WriteDest(), size); + SZ_Write(WriteDest(), (unsigned char *) buf, size); + } + else + { + // placeholder + MSG_WriteShort(WriteDest(), 0); + } +} + +////////////////////////////////////////////////////////// + +static void VM_SV_makestatic (void) +{ + prvm_edict_t *ent; + int i, large; + + // allow 0 parameters due to an id1 qc bug in which this function is used + // with no parameters (but directly after setmodel with self in OFS_PARM0) + VM_SAFEPARMCOUNTRANGE(0, 1, VM_SV_makestatic); + + if (prog->argc >= 1) + ent = PRVM_G_EDICT(OFS_PARM0); + else + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning("makestatic: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning("makestatic: can not modify free entity\n"); + return; + } + + large = false; + if (PRVM_serveredictfloat(ent, modelindex) >= 256 || PRVM_serveredictfloat(ent, frame) >= 256) + large = true; + + if (large) + { + MSG_WriteByte (&sv.signon,svc_spawnstatic2); + MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); + MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); + } + else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + { + MSG_WriteByte (&sv.signon,svc_spawnstatic); + MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); + } + else + { + MSG_WriteByte (&sv.signon,svc_spawnstatic); + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); + } + + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, colormap)); + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, skin)); + for (i=0 ; i<3 ; i++) + { + MSG_WriteCoord(&sv.signon, PRVM_serveredictvector(ent, origin)[i], sv.protocol); + MSG_WriteAngle(&sv.signon, PRVM_serveredictvector(ent, angles)[i], sv.protocol); + } + +// throw the entity away now + PRVM_ED_Free (ent); +} + +//============================================================================= + +/* +============== +VM_SV_setspawnparms +============== +*/ +static void VM_SV_setspawnparms (void) +{ + prvm_edict_t *ent; + int i; + client_t *client; + + VM_SAFEPARMCOUNT(1, VM_SV_setspawnparms); + + ent = PRVM_G_EDICT(OFS_PARM0); + i = PRVM_NUM_FOR_EDICT(ent); + if (i < 1 || i > svs.maxclients || !svs.clients[i-1].active) + { + Con_Print("tried to setspawnparms on a non-client\n"); + return; + } + + // copy spawn parms out of the client_t + client = svs.clients + i-1; + for (i=0 ; i< NUM_SPAWN_PARMS ; i++) + (&PRVM_serverglobalfloat(parm1))[i] = client->spawn_parms[i]; +} + +/* +================= +VM_SV_getlight + +Returns a color vector indicating the lighting at the requested point. + +(Internal Operation note: actually measures the light beneath the point, just like + the model lighting on the client) + +getlight(vector) +================= +*/ +static void VM_SV_getlight (void) +{ + vec3_t ambientcolor, diffusecolor, diffusenormal; + vec_t *p; + VM_SAFEPARMCOUNT(1, VM_SV_getlight); + p = PRVM_G_VECTOR(OFS_PARM0); + VectorClear(ambientcolor); + VectorClear(diffusecolor); + VectorClear(diffusenormal); + if (sv.worldmodel && sv.worldmodel->brush.LightPoint) + sv.worldmodel->brush.LightPoint(sv.worldmodel, p, ambientcolor, diffusecolor, diffusenormal); + VectorMA(ambientcolor, 0.5, diffusecolor, PRVM_G_VECTOR(OFS_RETURN)); +} + +typedef struct +{ + unsigned char type; // 1/2/8 or other value if isn't used + int fieldoffset; +}customstat_t; + +static customstat_t *vm_customstats = NULL; //[515]: it starts from 0, not 32 +static int vm_customstats_last; + +void VM_CustomStats_Clear (void) +{ + if(vm_customstats) + { + Z_Free(vm_customstats); + vm_customstats = NULL; + vm_customstats_last = -1; + } +} + +void VM_SV_UpdateCustomStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats) +{ + int i; + char s[17]; + + if(!vm_customstats) + return; + + for(i=0; i= (MAX_CL_STATS-32)) + { + VM_Warning("PF_SV_AddStat: index >= MAX_CL_STATS\n"); + return; + } + if(i > (MAX_CL_STATS-32-4) && type == 1) + { + VM_Warning("PF_SV_AddStat: index > (MAX_CL_STATS-4) with string\n"); + return; + } + vm_customstats[i].type = type; + vm_customstats[i].fieldoffset = off; + if(vm_customstats_last < i) + vm_customstats_last = i; +} + +/* +================= +VM_SV_copyentity + +copies data from one entity to another + +copyentity(src, dst) +================= +*/ +static void VM_SV_copyentity (void) +{ + prvm_edict_t *in, *out; + VM_SAFEPARMCOUNT(2, VM_SV_copyentity); + in = PRVM_G_EDICT(OFS_PARM0); + if (in == prog->edicts) + { + VM_Warning("copyentity: can not read world entity\n"); + return; + } + if (in->priv.server->free) + { + VM_Warning("copyentity: can not read free entity\n"); + return; + } + out = PRVM_G_EDICT(OFS_PARM1); + if (out == prog->edicts) + { + VM_Warning("copyentity: can not modify world entity\n"); + return; + } + if (out->priv.server->free) + { + VM_Warning("copyentity: can not modify free entity\n"); + return; + } + memcpy(out->fields.vp, in->fields.vp, prog->entityfields * 4); + SV_LinkEdict(out); +} + + +/* +================= +VM_SV_setcolor + +sets the color of a client and broadcasts the update to all connected clients + +setcolor(clientent, value) +================= +*/ +static void VM_SV_setcolor (void) +{ + client_t *client; + int entnum, i; + + VM_SAFEPARMCOUNT(2, VM_SV_setcolor); + entnum = PRVM_G_EDICTNUM(OFS_PARM0); + i = (int)PRVM_G_FLOAT(OFS_PARM1); + + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) + { + Con_Print("tried to setcolor a non-client\n"); + return; + } + + client = svs.clients + entnum-1; + if (client->edict) + { + PRVM_serveredictfloat(client->edict, clientcolors) = i; + PRVM_serveredictfloat(client->edict, team) = (i & 15) + 1; + } + client->colors = i; + if (client->old_colors != client->colors) + { + client->old_colors = client->colors; + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, client - svs.clients); + MSG_WriteByte (&sv.reliable_datagram, client->colors); + } +} + +/* +================= +VM_SV_effect + +effect(origin, modelname, startframe, framecount, framerate) +================= +*/ +static void VM_SV_effect (void) +{ + int i; + const char *s; + VM_SAFEPARMCOUNT(5, VM_SV_effect); + s = PRVM_G_STRING(OFS_PARM1); + if (!s[0]) + { + VM_Warning("effect: no model specified\n"); + return; + } + + i = SV_ModelIndex(s, 1); + if (!i) + { + VM_Warning("effect: model not precached\n"); + return; + } + + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + { + VM_Warning("effect: framecount < 1\n"); + return; + } + + if (PRVM_G_FLOAT(OFS_PARM4) < 1) + { + VM_Warning("effect: framerate < 1\n"); + return; + } + + SV_StartEffect(PRVM_G_VECTOR(OFS_PARM0), i, (int)PRVM_G_FLOAT(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4)); +} + +static void VM_SV_te_blood (void) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_blood); + if (PRVM_G_FLOAT(OFS_PARM2) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_BLOOD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // velocity + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[0], 127)); + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[1], 127)); + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[2], 127)); + // count + MSG_WriteByte(&sv.datagram, bound(0, (int) PRVM_G_FLOAT(OFS_PARM2), 255)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_bloodshower (void) +{ + VM_SAFEPARMCOUNT(4, VM_SV_te_bloodshower); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_BLOODSHOWER); + // min + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // max + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // speed + MSG_WriteCoord(&sv.datagram, PRVM_G_FLOAT(OFS_PARM2), sv.protocol); + // count + MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_explosionrgb (void) +{ + VM_SAFEPARMCOUNT(2, VM_SV_te_explosionrgb); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSIONRGB); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // color + MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[0] * 255), 255)); + MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[1] * 255), 255)); + MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[2] * 255), 255)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_particlecube (void) +{ + VM_SAFEPARMCOUNT(7, VM_SV_te_particlecube); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_PARTICLECUBE); + // min + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // max + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // velocity + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + // count + MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); + // color + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); + // gravity true/false + MSG_WriteByte(&sv.datagram, ((int) PRVM_G_FLOAT(OFS_PARM5)) != 0); + // randomvel + MSG_WriteCoord(&sv.datagram, PRVM_G_FLOAT(OFS_PARM6), sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_particlerain (void) +{ + VM_SAFEPARMCOUNT(5, VM_SV_te_particlerain); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_PARTICLERAIN); + // min + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // max + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // velocity + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + // count + MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); + // color + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_particlesnow (void) +{ + VM_SAFEPARMCOUNT(5, VM_SV_te_particlesnow); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_PARTICLESNOW); + // min + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // max + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // velocity + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + // count + MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); + // color + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_spark (void) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_spark); + if (PRVM_G_FLOAT(OFS_PARM2) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SPARK); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // velocity + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[0], 127)); + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[1], 127)); + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[2], 127)); + // count + MSG_WriteByte(&sv.datagram, bound(0, (int) PRVM_G_FLOAT(OFS_PARM2), 255)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_gunshotquad (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_gunshotquad); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_GUNSHOTQUAD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_spikequad (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_spikequad); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SPIKEQUAD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_superspikequad (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_superspikequad); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SUPERSPIKEQUAD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_explosionquad (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_explosionquad); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSIONQUAD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_smallflash (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_smallflash); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SMALLFLASH); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_customflash (void) +{ + VM_SAFEPARMCOUNT(4, VM_SV_te_customflash); + if (PRVM_G_FLOAT(OFS_PARM1) < 8 || PRVM_G_FLOAT(OFS_PARM2) < (1.0 / 256.0)) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_CUSTOMFLASH); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // radius + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM1) / 8 - 1, 255)); + // lifetime + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM2) * 256 - 1, 255)); + // color + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[0] * 255, 255)); + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[1] * 255, 255)); + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[2] * 255, 255)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_gunshot (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_gunshot); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_GUNSHOT); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_spike (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_spike); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SPIKE); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_superspike (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_superspike); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SUPERSPIKE); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_explosion (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_explosion); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSION); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_tarexplosion (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_tarexplosion); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_TAREXPLOSION); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_wizspike (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_wizspike); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_WIZSPIKE); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_knightspike (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_knightspike); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_KNIGHTSPIKE); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_lavasplash (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_lavasplash); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LAVASPLASH); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_teleport (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_teleport); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_TELEPORT); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_explosion2 (void) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_explosion2); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSION2); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // color + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM1)); + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM2)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_lightning1 (void) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_lightning1); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING1); + // owner entity + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + // start + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // end + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_lightning2 (void) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_lightning2); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING2); + // owner entity + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + // start + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // end + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_lightning3 (void) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_lightning3); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING3); + // owner entity + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + // start + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // end + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_beam (void) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_beam); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_BEAM); + // owner entity + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + // start + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // end + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_plasmaburn (void) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_plasmaburn); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_PLASMABURN); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_flamejet (void) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_flamejet); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_FLAMEJET); + // org + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // vel + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // count + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM2)); + SV_FlushBroadcastMessages(); +} + +//void(entity e, string s) clientcommand = #440; // executes a command string as if it came from the specified client +//this function originally written by KrimZon, made shorter by LordHavoc +static void VM_SV_clientcommand (void) +{ + client_t *temp_client; + int i; + VM_SAFEPARMCOUNT(2, VM_SV_clientcommand); + + //find client for this entity + i = (PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0)) - 1); + if (i < 0 || i >= svs.maxclients || !svs.clients[i].active) + { + Con_Print("PF_clientcommand: entity is not a client\n"); + return; + } + + temp_client = host_client; + host_client = svs.clients + i; + Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client); + host_client = temp_client; +} + +//void(entity e, entity tagentity, string tagname) setattachment = #443; // attachs e to a tag on tagentity (note: use "" to attach to entity origin/angles instead of a tag) +static void VM_SV_setattachment (void) +{ + prvm_edict_t *e = PRVM_G_EDICT(OFS_PARM0); + prvm_edict_t *tagentity = PRVM_G_EDICT(OFS_PARM1); + const char *tagname = PRVM_G_STRING(OFS_PARM2); + dp_model_t *model; + int tagindex; + VM_SAFEPARMCOUNT(3, VM_SV_setattachment); + + if (e == prog->edicts) + { + VM_Warning("setattachment: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning("setattachment: can not modify free entity\n"); + return; + } + + if (tagentity == NULL) + tagentity = prog->edicts; + + tagindex = 0; + + if (tagentity != NULL && tagentity != prog->edicts && tagname && tagname[0]) + { + model = SV_GetModelFromEdict(tagentity); + if (model) + { + tagindex = Mod_Alias_GetTagIndexForName(model, (int)PRVM_serveredictfloat(tagentity, skin), tagname); + if (tagindex == 0) + Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i (model \"%s\") but could not find it\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity), model->name); + } + else + Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i but it has no model\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity)); + } + + PRVM_serveredictedict(e, tag_entity) = PRVM_EDICT_TO_PROG(tagentity); + PRVM_serveredictfloat(e, tag_index) = tagindex; +} + +///////////////////////////////////////// +// DP_MD3_TAGINFO extension coded by VorteX + +int SV_GetTagIndex (prvm_edict_t *e, const char *tagname) +{ + int i; + + i = (int)PRVM_serveredictfloat(e, modelindex); + if (i < 1 || i >= MAX_MODELS) + return -1; + + return Mod_Alias_GetTagIndexForName(SV_GetModelByIndex(i), (int)PRVM_serveredictfloat(e, skin), tagname); +} + +int SV_GetExtendedTagInfo (prvm_edict_t *e, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) +{ + int r; + dp_model_t *model; + + *tagname = NULL; + *parentindex = 0; + Matrix4x4_CreateIdentity(tag_localmatrix); + + if (tagindex >= 0 && (model = SV_GetModelFromEdict(e)) && model->num_bones) + { + r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)PRVM_serveredictfloat(e, skin), e->priv.server->frameblend, &e->priv.server->skeleton, tagindex - 1, parentindex, tagname, tag_localmatrix); + + if(!r) // success? + *parentindex += 1; + + return r; + } + + return 1; +} + +void SV_GetEntityMatrix (prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix) +{ + float scale; + float pitchsign = 1; + + scale = PRVM_serveredictfloat(ent, scale); + if (!scale) + scale = 1.0f; + + if (viewmatrix) + Matrix4x4_CreateFromQuakeEntity(out, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, view_ofs)[2], PRVM_serveredictvector(ent, v_angle)[0], PRVM_serveredictvector(ent, v_angle)[1], PRVM_serveredictvector(ent, v_angle)[2], scale * cl_viewmodel_scale.value); + else + { + pitchsign = SV_GetPitchSign(ent); + Matrix4x4_CreateFromQuakeEntity(out, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2], pitchsign * PRVM_serveredictvector(ent, angles)[0], PRVM_serveredictvector(ent, angles)[1], PRVM_serveredictvector(ent, angles)[2], scale); + } +} + +int SV_GetEntityLocalTagMatrix(prvm_edict_t *ent, int tagindex, matrix4x4_t *out) +{ + dp_model_t *model; + if (tagindex >= 0 && (model = SV_GetModelFromEdict(ent)) && model->animscenes) + { + VM_GenerateFrameGroupBlend(ent->priv.server->framegroupblend, ent); + VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(ent, model, ent->priv.server->frameblend); + return Mod_Alias_GetTagMatrix(model, ent->priv.server->frameblend, &ent->priv.server->skeleton, tagindex, out); + } + *out = identitymatrix; + return 0; +} + +// Warnings/errors code: +// 0 - normal (everything all-right) +// 1 - world entity +// 2 - free entity +// 3 - null or non-precached model +// 4 - no tags with requested index +// 5 - runaway loop at attachment chain +extern cvar_t cl_bob; +extern cvar_t cl_bobcycle; +extern cvar_t cl_bobup; +int SV_GetTagMatrix (matrix4x4_t *out, prvm_edict_t *ent, int tagindex) +{ + int ret; + int modelindex, attachloop; + matrix4x4_t entitymatrix, tagmatrix, attachmatrix; + dp_model_t *model; + + *out = identitymatrix; // warnings and errors return identical matrix + + if (ent == prog->edicts) + return 1; + if (ent->priv.server->free) + return 2; + + modelindex = (int)PRVM_serveredictfloat(ent, modelindex); + if (modelindex <= 0 || modelindex >= MAX_MODELS) + return 3; + + model = SV_GetModelByIndex(modelindex); + + VM_GenerateFrameGroupBlend(ent->priv.server->framegroupblend, ent); + VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(ent, model, ent->priv.server->frameblend); + + tagmatrix = identitymatrix; + // DP_GFX_QUAKE3MODELTAGS, scan all chain and stop on unattached entity + attachloop = 0; + for (;;) + { + if (attachloop >= 256) // prevent runaway looping + return 5; + // apply transformation by child's tagindex on parent entity and then + // by parent entity itself + ret = SV_GetEntityLocalTagMatrix(ent, tagindex - 1, &attachmatrix); + if (ret && attachloop == 0) + return ret; + SV_GetEntityMatrix(ent, &entitymatrix, false); + Matrix4x4_Concat(&tagmatrix, &attachmatrix, out); + Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); + // next iteration we process the parent entity + if (PRVM_serveredictedict(ent, tag_entity)) + { + tagindex = (int)PRVM_serveredictfloat(ent, tag_index); + ent = PRVM_EDICT_NUM(PRVM_serveredictedict(ent, tag_entity)); + } + else + break; + attachloop++; + } + + // RENDER_VIEWMODEL magic + if (PRVM_serveredictedict(ent, viewmodelforclient)) + { + Matrix4x4_Copy(&tagmatrix, out); + ent = PRVM_EDICT_NUM(PRVM_serveredictedict(ent, viewmodelforclient)); + + SV_GetEntityMatrix(ent, &entitymatrix, true); + Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); + + /* + // Cl_bob, ported from rendering code + if (PRVM_serveredictfloat(ent, health) > 0 && cl_bob.value && cl_bobcycle.value) + { + double bob, cycle; + // LordHavoc: this code is *weird*, but not replacable (I think it + // should be done in QC on the server, but oh well, quake is quake) + // LordHavoc: figured out bobup: the time at which the sin is at 180 + // degrees (which allows lengthening or squishing the peak or valley) + cycle = sv.time/cl_bobcycle.value; + cycle -= (int)cycle; + if (cycle < cl_bobup.value) + cycle = sin(M_PI * cycle / cl_bobup.value); + else + cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); + // bob is proportional to velocity in the xy plane + // (don't count Z, or jumping messes it up) + bob = sqrt(PRVM_serveredictvector(ent, velocity)[0]*PRVM_serveredictvector(ent, velocity)[0] + PRVM_serveredictvector(ent, velocity)[1]*PRVM_serveredictvector(ent, velocity)[1])*cl_bob.value; + bob = bob*0.3 + bob*0.7*cycle; + Matrix4x4_AdjustOrigin(out, 0, 0, bound(-7, bob, 4)); + } + */ + } + return 0; +} + +//float(entity ent, string tagname) gettagindex; + +static void VM_SV_gettagindex (void) +{ + prvm_edict_t *ent; + const char *tag_name; + int tag_index; + + VM_SAFEPARMCOUNT(2, VM_SV_gettagindex); + + ent = PRVM_G_EDICT(OFS_PARM0); + tag_name = PRVM_G_STRING(OFS_PARM1); + + if (ent == prog->edicts) + { + VM_Warning("VM_SV_gettagindex(entity #%i): can't affect world entity\n", PRVM_NUM_FOR_EDICT(ent)); + return; + } + if (ent->priv.server->free) + { + VM_Warning("VM_SV_gettagindex(entity #%i): can't affect free entity\n", PRVM_NUM_FOR_EDICT(ent)); + return; + } + + tag_index = 0; + if (!SV_GetModelFromEdict(ent)) + Con_DPrintf("VM_SV_gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent)); + else + { + tag_index = SV_GetTagIndex(ent, tag_name); + if (tag_index == 0) + if(developer_extra.integer) + Con_DPrintf("VM_SV_gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name); + } + PRVM_G_FLOAT(OFS_RETURN) = tag_index; +} + +//vector(entity ent, float tagindex) gettaginfo; +static void VM_SV_gettaginfo (void) +{ + prvm_edict_t *e; + int tagindex; + matrix4x4_t tag_matrix; + matrix4x4_t tag_localmatrix; + int parentindex; + const char *tagname; + int returncode; + vec3_t fo, le, up, trans; + const dp_model_t *model; + + VM_SAFEPARMCOUNT(2, VM_SV_gettaginfo); + + e = PRVM_G_EDICT(OFS_PARM0); + tagindex = (int)PRVM_G_FLOAT(OFS_PARM1); + + returncode = SV_GetTagMatrix(&tag_matrix, e, tagindex); + Matrix4x4_ToVectors(&tag_matrix, PRVM_serverglobalvector(v_forward), le, PRVM_serverglobalvector(v_up), PRVM_G_VECTOR(OFS_RETURN)); + VectorScale(le, -1, PRVM_serverglobalvector(v_right)); + model = SV_GetModelFromEdict(e); + VM_GenerateFrameGroupBlend(e->priv.server->framegroupblend, e); + VM_FrameBlendFromFrameGroupBlend(e->priv.server->frameblend, e->priv.server->framegroupblend, model); + VM_UpdateEdictSkeleton(e, model, e->priv.server->frameblend); + SV_GetExtendedTagInfo(e, tagindex, &parentindex, &tagname, &tag_localmatrix); + Matrix4x4_ToVectors(&tag_localmatrix, fo, le, up, trans); + + PRVM_serverglobalfloat(gettaginfo_parent) = parentindex; + PRVM_serverglobalstring(gettaginfo_name) = tagname ? PRVM_SetTempString(tagname) : 0; + VectorCopy(trans, PRVM_serverglobalvector(gettaginfo_offset)); + VectorCopy(fo, PRVM_serverglobalvector(gettaginfo_forward)); + VectorScale(le, -1, PRVM_serverglobalvector(gettaginfo_right)); + VectorCopy(up, PRVM_serverglobalvector(gettaginfo_up)); + + switch(returncode) + { + case 1: + VM_Warning("gettagindex: can't affect world entity\n"); + break; + case 2: + VM_Warning("gettagindex: can't affect free entity\n"); + break; + case 3: + Con_DPrintf("SV_GetTagMatrix(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(e)); + break; + case 4: + Con_DPrintf("SV_GetTagMatrix(entity #%i): model has no tag with requested index %i\n", PRVM_NUM_FOR_EDICT(e), tagindex); + break; + case 5: + Con_DPrintf("SV_GetTagMatrix(entity #%i): runaway loop at attachment chain\n", PRVM_NUM_FOR_EDICT(e)); + break; + } +} + +//void(entity clent) dropclient (DP_SV_DROPCLIENT) +static void VM_SV_dropclient (void) +{ + int clientnum; + client_t *oldhostclient; + VM_SAFEPARMCOUNT(1, VM_SV_dropclient); + clientnum = PRVM_G_EDICTNUM(OFS_PARM0) - 1; + if (clientnum < 0 || clientnum >= svs.maxclients) + { + VM_Warning("dropclient: not a client\n"); + return; + } + if (!svs.clients[clientnum].active) + { + VM_Warning("dropclient: that client slot is not connected\n"); + return; + } + oldhostclient = host_client; + host_client = svs.clients + clientnum; + SV_DropClient(false); + host_client = oldhostclient; +} + +//entity() spawnclient (DP_SV_BOTCLIENT) +static void VM_SV_spawnclient (void) +{ + int i; + prvm_edict_t *ed; + VM_SAFEPARMCOUNT(0, VM_SV_spawnclient); + prog->xfunction->builtinsprofile += 2; + ed = prog->edicts; + for (i = 0;i < svs.maxclients;i++) + { + if (!svs.clients[i].active) + { + prog->xfunction->builtinsprofile += 100; + SV_ConnectClient (i, NULL); + // this has to be set or else ClientDisconnect won't be called + // we assume the qc will call ClientConnect... + svs.clients[i].clientconnectcalled = true; + ed = PRVM_EDICT_NUM(i + 1); + break; + } + } + VM_RETURN_EDICT(ed); +} + +//float(entity clent) clienttype (DP_SV_BOTCLIENT) +static void VM_SV_clienttype (void) +{ + int clientnum; + VM_SAFEPARMCOUNT(1, VM_SV_clienttype); + clientnum = PRVM_G_EDICTNUM(OFS_PARM0) - 1; + if (clientnum < 0 || clientnum >= svs.maxclients) + PRVM_G_FLOAT(OFS_RETURN) = 3; + else if (!svs.clients[clientnum].active) + PRVM_G_FLOAT(OFS_RETURN) = 0; + else if (svs.clients[clientnum].netconnection) + PRVM_G_FLOAT(OFS_RETURN) = 1; + else + PRVM_G_FLOAT(OFS_RETURN) = 2; +} + +/* +=============== +VM_SV_serverkey + +string(string key) serverkey +=============== +*/ +void VM_SV_serverkey(void) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNT(1, VM_SV_serverkey); + InfoString_GetValue(svs.serverinfo, PRVM_G_STRING(OFS_PARM0), string, sizeof(string)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); +} + +//#333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) +static void VM_SV_setmodelindex (void) +{ + prvm_edict_t *e; + dp_model_t *mod; + int i; + VM_SAFEPARMCOUNT(2, VM_SV_setmodelindex); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning("setmodelindex: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning("setmodelindex: can not modify free entity\n"); + return; + } + i = (int)PRVM_G_FLOAT(OFS_PARM1); + if (i <= 0 || i >= MAX_MODELS) + { + VM_Warning("setmodelindex: invalid modelindex\n"); + return; + } + if (!sv.model_precache[i][0]) + { + VM_Warning("setmodelindex: model not precached\n"); + return; + } + + PRVM_serveredictstring(e, model) = PRVM_SetEngineString(sv.model_precache[i]); + PRVM_serveredictfloat(e, modelindex) = i; + + mod = SV_GetModelByIndex(i); + + if (mod) + { + if (mod->type != mod_alias || sv_gameplayfix_setmodelrealbox.integer) + SetMinMaxSize (e, mod->normalmins, mod->normalmaxs, true); + else + SetMinMaxSize (e, quakemins, quakemaxs, true); + } + else + SetMinMaxSize (e, vec3_origin, vec3_origin, true); +} + +//#334 string(float mdlindex) modelnameforindex (EXT_CSQC) +static void VM_SV_modelnameforindex (void) +{ + int i; + VM_SAFEPARMCOUNT(1, VM_SV_modelnameforindex); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + + i = (int)PRVM_G_FLOAT(OFS_PARM0); + if (i <= 0 || i >= MAX_MODELS) + { + VM_Warning("modelnameforindex: invalid modelindex\n"); + return; + } + if (!sv.model_precache[i][0]) + { + VM_Warning("modelnameforindex: model not precached\n"); + return; + } + + PRVM_G_INT(OFS_RETURN) = PRVM_SetEngineString(sv.model_precache[i]); +} + +//#335 float(string effectname) particleeffectnum (EXT_CSQC) +static void VM_SV_particleeffectnum (void) +{ + int i; + VM_SAFEPARMCOUNT(1, VM_SV_particleeffectnum); + i = SV_ParticleEffectIndex(PRVM_G_STRING(OFS_PARM0)); + if (i == 0) + i = -1; + PRVM_G_FLOAT(OFS_RETURN) = i; +} + +// #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) +static void VM_SV_trailparticles (void) +{ + VM_SAFEPARMCOUNT(4, VM_SV_trailparticles); + + if ((int)PRVM_G_FLOAT(OFS_PARM0) < 0) + return; + + MSG_WriteByte(&sv.datagram, svc_trailparticles); + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + MSG_WriteShort(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM1)); + MSG_WriteVector(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2), sv.protocol); + MSG_WriteVector(&sv.datagram, PRVM_G_VECTOR(OFS_PARM3), sv.protocol); + SV_FlushBroadcastMessages(); +} + +//#337 void(float effectnum, vector origin, vector dir, float count) pointparticles (EXT_CSQC) +static void VM_SV_pointparticles (void) +{ + int effectnum, count; + vec3_t org, vel; + VM_SAFEPARMCOUNTRANGE(4, 8, VM_SV_pointparticles); + + if ((int)PRVM_G_FLOAT(OFS_PARM0) < 0) + return; + + effectnum = (int)PRVM_G_FLOAT(OFS_PARM0); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), org); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); + count = bound(0, (int)PRVM_G_FLOAT(OFS_PARM3), 65535); + if (count == 1 && !VectorLength2(vel)) + { + // 1+2+12=15 bytes + MSG_WriteByte(&sv.datagram, svc_pointparticles1); + MSG_WriteShort(&sv.datagram, effectnum); + MSG_WriteVector(&sv.datagram, org, sv.protocol); + } + else + { + // 1+2+12+12+2=29 bytes + MSG_WriteByte(&sv.datagram, svc_pointparticles); + MSG_WriteShort(&sv.datagram, effectnum); + MSG_WriteVector(&sv.datagram, org, sv.protocol); + MSG_WriteVector(&sv.datagram, vel, sv.protocol); + MSG_WriteShort(&sv.datagram, count); + } + + SV_FlushBroadcastMessages(); +} + +//PF_setpause, // void(float pause) setpause = #531; +static void VM_SV_setpause(void) { + int pauseValue; + pauseValue = (int)PRVM_G_FLOAT(OFS_PARM0); + if (pauseValue != 0) { //pause the game + sv.paused = 1; + sv.pausedstart = Sys_DoubleTime(); + } else { //disable pause, in case it was enabled + if (sv.paused != 0) { + sv.paused = 0; + sv.pausedstart = 0; + } + } + // send notification to all clients + MSG_WriteByte(&sv.reliable_datagram, svc_setpause); + MSG_WriteByte(&sv.reliable_datagram, sv.paused); +} + +// #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. +static void VM_SV_skel_create(void) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = SV_GetModelByIndex(modelindex); + skeleton_t *skeleton; + int i; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (!model || !model->num_bones) + return; + for (i = 0;i < MAX_EDICTS;i++) + if (!prog->skeletons[i]) + break; + if (i == MAX_EDICTS) + return; + prog->skeletons[i] = skeleton = (skeleton_t *)Mem_Alloc(cls.levelmempool, sizeof(skeleton_t) + model->num_bones * sizeof(matrix4x4_t)); + PRVM_G_FLOAT(OFS_RETURN) = i + 1; + skeleton->model = model; + skeleton->relativetransforms = (matrix4x4_t *)(skeleton+1); + // initialize to identity matrices + for (i = 0;i < skeleton->model->num_bones;i++) + skeleton->relativetransforms[i] = identitymatrix; +} + +// #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +static void VM_SV_skel_build(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + prvm_edict_t *ed = PRVM_G_EDICT(OFS_PARM1); + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM2); + float retainfrac = PRVM_G_FLOAT(OFS_PARM3); + int firstbone = PRVM_G_FLOAT(OFS_PARM4) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM5) - 1; + dp_model_t *model = SV_GetModelByIndex(modelindex); + float blendfrac; + int numblends; + int bonenum; + int blendindex; + framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + matrix4x4_t blendedmatrix; + matrix4x4_t matrix; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + firstbone = max(0, firstbone); + lastbone = min(lastbone, model->num_bones - 1); + lastbone = min(lastbone, skeleton->model->num_bones - 1); + VM_GenerateFrameGroupBlend(framegroupblend, ed); + VM_FrameBlendFromFrameGroupBlend(frameblend, framegroupblend, model); + blendfrac = 1.0f - retainfrac; + for (numblends = 0;numblends < MAX_FRAMEBLENDS && frameblend[numblends].lerp;numblends++) + frameblend[numblends].lerp *= blendfrac; + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + { + memset(&blendedmatrix, 0, sizeof(blendedmatrix)); + Matrix4x4_Accumulate(&blendedmatrix, &skeleton->relativetransforms[bonenum], retainfrac); + for (blendindex = 0;blendindex < numblends;blendindex++) + { + Matrix4x4_FromBonePose6s(&matrix, model->num_posescale, model->data_poses6s + 6 * (frameblend[blendindex].subframe * model->num_bones + bonenum)); + Matrix4x4_Accumulate(&blendedmatrix, &matrix, frameblend[blendindex].lerp); + } + skeleton->relativetransforms[bonenum] = blendedmatrix; + } + PRVM_G_FLOAT(OFS_RETURN) = skeletonindex + 1; +} + +// #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton +static void VM_SV_skel_get_numbones(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->num_bones; +} + +// #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) +static void VM_SV_skel_get_bonename(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + PRVM_G_INT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(skeleton->model->data_bones[bonenum].name); +} + +// #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +static void VM_SV_skel_get_boneparent(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->data_bones[bonenum].parent + 1; +} + +// #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex +static void VM_SV_skel_find_bone(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + const char *tagname = PRVM_G_STRING(OFS_PARM1); + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + PRVM_G_FLOAT(OFS_RETURN) = Mod_Alias_GetTagIndexForName(skeleton->model, 0, tagname) + 1; +} + +// #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +static void VM_SV_skel_get_bonerel(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + matrix4x4_t matrix; + vec3_t forward, left, up, origin; + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + VectorClear(PRVM_clientglobalvector(v_forward)); + VectorClear(PRVM_clientglobalvector(v_right)); + VectorClear(PRVM_clientglobalvector(v_up)); + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + matrix = skeleton->relativetransforms[bonenum]; + Matrix4x4_ToVectors(&matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); +} + +// #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +static void VM_SV_skel_get_boneabs(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + vec3_t forward, left, up, origin; + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + VectorClear(PRVM_clientglobalvector(v_forward)); + VectorClear(PRVM_clientglobalvector(v_right)); + VectorClear(PRVM_clientglobalvector(v_up)); + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + matrix = skeleton->relativetransforms[bonenum]; + // convert to absolute + while ((bonenum = skeleton->model->data_bones[bonenum].parent) >= 0) + { + temp = matrix; + Matrix4x4_Concat(&matrix, &skeleton->relativetransforms[bonenum], &temp); + } + Matrix4x4_ToVectors(&matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); +} + +// #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +static void VM_SV_skel_set_bone(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + skeleton->relativetransforms[bonenum] = matrix; +} + +// #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +static void VM_SV_skel_mul_bone(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + temp = skeleton->relativetransforms[bonenum]; + Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); +} + +// #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +static void VM_SV_skel_mul_bones(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int firstbone = PRVM_G_FLOAT(OFS_PARM1) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM2) - 1; + int bonenum; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + firstbone = max(0, firstbone); + lastbone = min(lastbone, skeleton->model->num_bones - 1); + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + { + temp = skeleton->relativetransforms[bonenum]; + Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); + } +} + +// #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +static void VM_SV_skel_copybones(void) +{ + int skeletonindexdst = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int skeletonindexsrc = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + int firstbone = PRVM_G_FLOAT(OFS_PARM2) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM3) - 1; + int bonenum; + skeleton_t *skeletondst; + skeleton_t *skeletonsrc; + if (skeletonindexdst < 0 || skeletonindexdst >= MAX_EDICTS || !(skeletondst = prog->skeletons[skeletonindexdst])) + return; + if (skeletonindexsrc < 0 || skeletonindexsrc >= MAX_EDICTS || !(skeletonsrc = prog->skeletons[skeletonindexsrc])) + return; + firstbone = max(0, firstbone); + lastbone = min(lastbone, skeletondst->model->num_bones - 1); + lastbone = min(lastbone, skeletonsrc->model->num_bones - 1); + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + skeletondst->relativetransforms[bonenum] = skeletonsrc->relativetransforms[bonenum]; +} + +// #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +static void VM_SV_skel_delete(void) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + Mem_Free(skeleton); + prog->skeletons[skeletonindex] = NULL; +} + +// #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found +static void VM_SV_frameforname(void) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = SV_GetModelByIndex(modelindex); + const char *name = PRVM_G_STRING(OFS_PARM1); + int i; + PRVM_G_FLOAT(OFS_RETURN) = -1; + if (!model || !model->animscenes) + return; + for (i = 0;i < model->numframes;i++) + { + if (!strcasecmp(model->animscenes[i].name, name)) + { + PRVM_G_FLOAT(OFS_RETURN) = i; + break; + } + } +} + +// #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +static void VM_SV_frameduration(void) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = SV_GetModelByIndex(modelindex); + int framenum = (int)PRVM_G_FLOAT(OFS_PARM1); + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes) + return; + if (model->animscenes[framenum].framerate) + PRVM_G_FLOAT(OFS_RETURN) = model->animscenes[framenum].framecount / model->animscenes[framenum].framerate; +} + + +prvm_builtin_t vm_sv_builtins[] = { +NULL, // #0 NULL function (not callable) (QUAKE) +VM_makevectors, // #1 void(vector ang) makevectors (QUAKE) +VM_SV_setorigin, // #2 void(entity e, vector o) setorigin (QUAKE) +VM_SV_setmodel, // #3 void(entity e, string m) setmodel (QUAKE) +VM_SV_setsize, // #4 void(entity e, vector min, vector max) setsize (QUAKE) +NULL, // #5 void(entity e, vector min, vector max) setabssize (QUAKE) +VM_break, // #6 void() break (QUAKE) +VM_random, // #7 float() random (QUAKE) +VM_SV_sound, // #8 void(entity e, float chan, string samp) sound (QUAKE) +VM_normalize, // #9 vector(vector v) normalize (QUAKE) +VM_error, // #10 void(string e) error (QUAKE) +VM_objerror, // #11 void(string e) objerror (QUAKE) +VM_vlen, // #12 float(vector v) vlen (QUAKE) +VM_vectoyaw, // #13 float(vector v) vectoyaw (QUAKE) +VM_spawn, // #14 entity() spawn (QUAKE) +VM_remove, // #15 void(entity e) remove (QUAKE) +VM_SV_traceline, // #16 void(vector v1, vector v2, float tryents) traceline (QUAKE) +VM_SV_checkclient, // #17 entity() checkclient (QUAKE) +VM_find, // #18 entity(entity start, .string fld, string match) find (QUAKE) +VM_SV_precache_sound, // #19 void(string s) precache_sound (QUAKE) +VM_SV_precache_model, // #20 void(string s) precache_model (QUAKE) +VM_SV_stuffcmd, // #21 void(entity client, string s, ...) stuffcmd (QUAKE) +VM_SV_findradius, // #22 entity(vector org, float rad) findradius (QUAKE) +VM_bprint, // #23 void(string s, ...) bprint (QUAKE) +VM_SV_sprint, // #24 void(entity client, string s, ...) sprint (QUAKE) +VM_dprint, // #25 void(string s, ...) dprint (QUAKE) +VM_ftos, // #26 string(float f) ftos (QUAKE) +VM_vtos, // #27 string(vector v) vtos (QUAKE) +VM_coredump, // #28 void() coredump (QUAKE) +VM_traceon, // #29 void() traceon (QUAKE) +VM_traceoff, // #30 void() traceoff (QUAKE) +VM_eprint, // #31 void(entity e) eprint (QUAKE) +VM_SV_walkmove, // #32 float(float yaw, float dist) walkmove (QUAKE) +NULL, // #33 (QUAKE) +VM_SV_droptofloor, // #34 float() droptofloor (QUAKE) +VM_SV_lightstyle, // #35 void(float style, string value) lightstyle (QUAKE) +VM_rint, // #36 float(float v) rint (QUAKE) +VM_floor, // #37 float(float v) floor (QUAKE) +VM_ceil, // #38 float(float v) ceil (QUAKE) +NULL, // #39 (QUAKE) +VM_SV_checkbottom, // #40 float(entity e) checkbottom (QUAKE) +VM_SV_pointcontents, // #41 float(vector v) pointcontents (QUAKE) +NULL, // #42 (QUAKE) +VM_fabs, // #43 float(float f) fabs (QUAKE) +VM_SV_aim, // #44 vector(entity e, float speed) aim (QUAKE) +VM_cvar, // #45 float(string s) cvar (QUAKE) +VM_localcmd, // #46 void(string s) localcmd (QUAKE) +VM_nextent, // #47 entity(entity e) nextent (QUAKE) +VM_SV_particle, // #48 void(vector o, vector d, float color, float count) particle (QUAKE) +VM_changeyaw, // #49 void() ChangeYaw (QUAKE) +NULL, // #50 (QUAKE) +VM_vectoangles, // #51 vector(vector v) vectoangles (QUAKE) +VM_SV_WriteByte, // #52 void(float to, float f) WriteByte (QUAKE) +VM_SV_WriteChar, // #53 void(float to, float f) WriteChar (QUAKE) +VM_SV_WriteShort, // #54 void(float to, float f) WriteShort (QUAKE) +VM_SV_WriteLong, // #55 void(float to, float f) WriteLong (QUAKE) +VM_SV_WriteCoord, // #56 void(float to, float f) WriteCoord (QUAKE) +VM_SV_WriteAngle, // #57 void(float to, float f) WriteAngle (QUAKE) +VM_SV_WriteString, // #58 void(float to, string s) WriteString (QUAKE) +VM_SV_WriteEntity, // #59 void(float to, entity e) WriteEntity (QUAKE) +VM_sin, // #60 float(float f) sin (DP_QC_SINCOSSQRTPOW) (QUAKE) +VM_cos, // #61 float(float f) cos (DP_QC_SINCOSSQRTPOW) (QUAKE) +VM_sqrt, // #62 float(float f) sqrt (DP_QC_SINCOSSQRTPOW) (QUAKE) +VM_changepitch, // #63 void(entity ent) changepitch (DP_QC_CHANGEPITCH) (QUAKE) +VM_SV_tracetoss, // #64 void(entity e, entity ignore) tracetoss (DP_QC_TRACETOSS) (QUAKE) +VM_etos, // #65 string(entity ent) etos (DP_QC_ETOS) (QUAKE) +NULL, // #66 (QUAKE) +SV_MoveToGoal, // #67 void(float step) movetogoal (QUAKE) +VM_precache_file, // #68 string(string s) precache_file (QUAKE) +VM_SV_makestatic, // #69 void(entity e) makestatic (QUAKE) +VM_changelevel, // #70 void(string s) changelevel (QUAKE) +NULL, // #71 (QUAKE) +VM_cvar_set, // #72 void(string var, string val) cvar_set (QUAKE) +VM_SV_centerprint, // #73 void(entity client, strings) centerprint (QUAKE) +VM_SV_ambientsound, // #74 void(vector pos, string samp, float vol, float atten) ambientsound (QUAKE) +VM_SV_precache_model, // #75 string(string s) precache_model2 (QUAKE) +VM_SV_precache_sound, // #76 string(string s) precache_sound2 (QUAKE) +VM_precache_file, // #77 string(string s) precache_file2 (QUAKE) +VM_SV_setspawnparms, // #78 void(entity e) setspawnparms (QUAKE) +NULL, // #79 void(entity killer, entity killee) logfrag (QUAKEWORLD) +NULL, // #80 string(entity e, string keyname) infokey (QUAKEWORLD) +VM_stof, // #81 float(string s) stof (FRIK_FILE) +NULL, // #82 void(vector where, float set) multicast (QUAKEWORLD) +NULL, // #83 (QUAKE) +NULL, // #84 (QUAKE) +NULL, // #85 (QUAKE) +NULL, // #86 (QUAKE) +NULL, // #87 (QUAKE) +NULL, // #88 (QUAKE) +NULL, // #89 (QUAKE) +VM_SV_tracebox, // #90 void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox (DP_QC_TRACEBOX) +VM_randomvec, // #91 vector() randomvec (DP_QC_RANDOMVEC) +VM_SV_getlight, // #92 vector(vector org) getlight (DP_QC_GETLIGHT) +VM_registercvar, // #93 float(string name, string value) registercvar (DP_REGISTERCVAR) +VM_min, // #94 float(float a, floats) min (DP_QC_MINMAXBOUND) +VM_max, // #95 float(float a, floats) max (DP_QC_MINMAXBOUND) +VM_bound, // #96 float(float minimum, float val, float maximum) bound (DP_QC_MINMAXBOUND) +VM_pow, // #97 float(float f, float f) pow (DP_QC_SINCOSSQRTPOW) +VM_findfloat, // #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT) +VM_checkextension, // #99 float(string s) checkextension (the basis of the extension system) +// FrikaC and Telejano range #100-#199 +NULL, // #100 +NULL, // #101 +NULL, // #102 +NULL, // #103 +NULL, // #104 +NULL, // #105 +NULL, // #106 +NULL, // #107 +NULL, // #108 +NULL, // #109 +VM_fopen, // #110 float(string filename, float mode) fopen (FRIK_FILE) +VM_fclose, // #111 void(float fhandle) fclose (FRIK_FILE) +VM_fgets, // #112 string(float fhandle) fgets (FRIK_FILE) +VM_fputs, // #113 void(float fhandle, string s) fputs (FRIK_FILE) +VM_strlen, // #114 float(string s) strlen (FRIK_FILE) +VM_strcat, // #115 string(string s1, string s2, ...) strcat (FRIK_FILE) +VM_substring, // #116 string(string s, float start, float length) substring (FRIK_FILE) +VM_stov, // #117 vector(string) stov (FRIK_FILE) +VM_strzone, // #118 string(string s) strzone (FRIK_FILE) +VM_strunzone, // #119 void(string s) strunzone (FRIK_FILE) +NULL, // #120 +NULL, // #121 +NULL, // #122 +NULL, // #123 +NULL, // #124 +NULL, // #125 +NULL, // #126 +NULL, // #127 +NULL, // #128 +NULL, // #129 +NULL, // #130 +NULL, // #131 +NULL, // #132 +NULL, // #133 +NULL, // #134 +NULL, // #135 +NULL, // #136 +NULL, // #137 +NULL, // #138 +NULL, // #139 +NULL, // #140 +NULL, // #141 +NULL, // #142 +NULL, // #143 +NULL, // #144 +NULL, // #145 +NULL, // #146 +NULL, // #147 +NULL, // #148 +NULL, // #149 +NULL, // #150 +NULL, // #151 +NULL, // #152 +NULL, // #153 +NULL, // #154 +NULL, // #155 +NULL, // #156 +NULL, // #157 +NULL, // #158 +NULL, // #159 +NULL, // #160 +NULL, // #161 +NULL, // #162 +NULL, // #163 +NULL, // #164 +NULL, // #165 +NULL, // #166 +NULL, // #167 +NULL, // #168 +NULL, // #169 +NULL, // #170 +NULL, // #171 +NULL, // #172 +NULL, // #173 +NULL, // #174 +NULL, // #175 +NULL, // #176 +NULL, // #177 +NULL, // #178 +NULL, // #179 +NULL, // #180 +NULL, // #181 +NULL, // #182 +NULL, // #183 +NULL, // #184 +NULL, // #185 +NULL, // #186 +NULL, // #187 +NULL, // #188 +NULL, // #189 +NULL, // #190 +NULL, // #191 +NULL, // #192 +NULL, // #193 +NULL, // #194 +NULL, // #195 +NULL, // #196 +NULL, // #197 +NULL, // #198 +NULL, // #199 +// FTEQW range #200-#299 +NULL, // #200 +NULL, // #201 +NULL, // #202 +NULL, // #203 +NULL, // #204 +NULL, // #205 +NULL, // #206 +NULL, // #207 +NULL, // #208 +NULL, // #209 +NULL, // #210 +NULL, // #211 +NULL, // #212 +NULL, // #213 +NULL, // #214 +NULL, // #215 +NULL, // #216 +NULL, // #217 +VM_bitshift, // #218 float(float number, float quantity) bitshift (EXT_BITSHIFT) +NULL, // #219 +NULL, // #220 +VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) +VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) +VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) +VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) +VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) +VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) +VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) +VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) +VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) +VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) +NULL, // #231 +VM_SV_AddStat, // #232 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) +NULL, // #233 +NULL, // #234 +NULL, // #235 +NULL, // #236 +NULL, // #237 +NULL, // #238 +NULL, // #239 +VM_SV_checkpvs, // #240 float(vector viewpos, entity viewee) checkpvs; +NULL, // #241 +NULL, // #242 +NULL, // #243 +NULL, // #244 +NULL, // #245 +NULL, // #246 +NULL, // #247 +NULL, // #248 +NULL, // #249 +NULL, // #250 +NULL, // #251 +NULL, // #252 +NULL, // #253 +NULL, // #254 +NULL, // #255 +NULL, // #256 +NULL, // #257 +NULL, // #258 +NULL, // #259 +NULL, // #260 +NULL, // #261 +NULL, // #262 +VM_SV_skel_create, // #263 float(float modlindex) skel_create = #263; // (DP_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. +VM_SV_skel_build, // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (DP_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +VM_SV_skel_get_numbones, // #265 float(float skel) skel_get_numbones = #265; // (DP_SKELETONOBJECTS) returns how many bones exist in the created skeleton +VM_SV_skel_get_bonename, // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (DP_SKELETONOBJECTS) returns name of bone (as a tempstring) +VM_SV_skel_get_boneparent, // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (DP_SKELETONOBJECTS) returns parent num for supplied bonenum, -1 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +VM_SV_skel_find_bone, // #268 float(float skel, string tagname) skel_find_bone = #268; // (DP_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex +VM_SV_skel_get_bonerel, // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (DP_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +VM_SV_skel_get_boneabs, // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (DP_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +VM_SV_skel_set_bone, // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (DP_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +VM_SV_skel_mul_bone, // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (DP_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +VM_SV_skel_mul_bones, // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (DP_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +VM_SV_skel_copybones, // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (DP_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +VM_SV_skel_delete, // #275 void(float skel) skel_delete = #275; // (DP_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +VM_SV_frameforname, // #276 float(float modlindex, string framename) frameforname = #276; // (DP_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found +VM_SV_frameduration, // #277 float(float modlindex, float framenum) frameduration = #277; // (DP_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +NULL, // #278 +NULL, // #279 +NULL, // #280 +NULL, // #281 +NULL, // #282 +NULL, // #283 +NULL, // #284 +NULL, // #285 +NULL, // #286 +NULL, // #287 +NULL, // #288 +NULL, // #289 +NULL, // #290 +NULL, // #291 +NULL, // #292 +NULL, // #293 +NULL, // #294 +NULL, // #295 +NULL, // #296 +NULL, // #297 +NULL, // #298 +NULL, // #299 +// CSQC range #300-#399 +NULL, // #300 void() clearscene (EXT_CSQC) +NULL, // #301 void(float mask) addentities (EXT_CSQC) +NULL, // #302 void(entity ent) addentity (EXT_CSQC) +NULL, // #303 float(float property, ...) setproperty (EXT_CSQC) +NULL, // #304 void() renderscene (EXT_CSQC) +NULL, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (EXT_CSQC) +NULL, // #306 void(string texturename, float flag[, float is2d, float lines]) R_BeginPolygon +NULL, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex +NULL, // #308 void() R_EndPolygon +NULL, // #309 +NULL, // #310 vector (vector v) cs_unproject (EXT_CSQC) +NULL, // #311 vector (vector v) cs_project (EXT_CSQC) +NULL, // #312 +NULL, // #313 +NULL, // #314 +NULL, // #315 void(float width, vector pos1, vector pos2, float flag) drawline (EXT_CSQC) +NULL, // #316 float(string name) iscachedpic (EXT_CSQC) +NULL, // #317 string(string name, float trywad) precache_pic (EXT_CSQC) +NULL, // #318 vector(string picname) draw_getimagesize (EXT_CSQC) +NULL, // #319 void(string name) freepic (EXT_CSQC) +NULL, // #320 float(vector position, float character, vector scale, vector rgb, float alpha, float flag) drawcharacter (EXT_CSQC) +NULL, // #321 float(vector position, string text, vector scale, vector rgb, float alpha, float flag) drawstring (EXT_CSQC) +NULL, // #322 float(vector position, string pic, vector size, vector rgb, float alpha, float flag) drawpic (EXT_CSQC) +NULL, // #323 float(vector position, vector size, vector rgb, float alpha, float flag) drawfill (EXT_CSQC) +NULL, // #324 void(float x, float y, float width, float height) drawsetcliparea +NULL, // #325 void(void) drawresetcliparea +NULL, // #326 +NULL, // #327 +NULL, // #328 +NULL, // #329 +NULL, // #330 float(float stnum) getstatf (EXT_CSQC) +NULL, // #331 float(float stnum) getstati (EXT_CSQC) +NULL, // #332 string(float firststnum) getstats (EXT_CSQC) +VM_SV_setmodelindex, // #333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) +VM_SV_modelnameforindex, // #334 string(float mdlindex) modelnameforindex (EXT_CSQC) +VM_SV_particleeffectnum, // #335 float(string effectname) particleeffectnum (EXT_CSQC) +VM_SV_trailparticles, // #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) +VM_SV_pointparticles, // #337 void(float effectnum, vector origin [, vector dir, float count]) pointparticles (EXT_CSQC) +NULL, // #338 void(string s, ...) centerprint (EXT_CSQC) +VM_print, // #339 void(string s, ...) print (EXT_CSQC, DP_SV_PRINT) +NULL, // #340 string(float keynum) keynumtostring (EXT_CSQC) +NULL, // #341 float(string keyname) stringtokeynum (EXT_CSQC) +NULL, // #342 string(float keynum) getkeybind (EXT_CSQC) +NULL, // #343 void(float usecursor) setcursormode (EXT_CSQC) +NULL, // #344 vector() getmousepos (EXT_CSQC) +NULL, // #345 float(float framenum) getinputstate (EXT_CSQC) +NULL, // #346 void(float sens) setsensitivityscaler (EXT_CSQC) +NULL, // #347 void() runstandardplayerphysics (EXT_CSQC) +NULL, // #348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) +NULL, // #349 float() isdemo (EXT_CSQC) +VM_isserver, // #350 float() isserver (EXT_CSQC) +NULL, // #351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) +NULL, // #352 void(string cmdname) registercommand (EXT_CSQC) +VM_wasfreed, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too) +VM_SV_serverkey, // #354 string(string key) serverkey (EXT_CSQC) +NULL, // #355 +NULL, // #356 +NULL, // #357 +NULL, // #358 +NULL, // #359 +NULL, // #360 float() readbyte (EXT_CSQC) +NULL, // #361 float() readchar (EXT_CSQC) +NULL, // #362 float() readshort (EXT_CSQC) +NULL, // #363 float() readlong (EXT_CSQC) +NULL, // #364 float() readcoord (EXT_CSQC) +NULL, // #365 float() readangle (EXT_CSQC) +NULL, // #366 string() readstring (EXT_CSQC) +NULL, // #367 float() readfloat (EXT_CSQC) +NULL, // #368 +NULL, // #369 +NULL, // #370 +NULL, // #371 +NULL, // #372 +NULL, // #373 +NULL, // #374 +NULL, // #375 +NULL, // #376 +NULL, // #377 +NULL, // #378 +NULL, // #379 +NULL, // #380 +NULL, // #381 +NULL, // #382 +NULL, // #383 +NULL, // #384 +NULL, // #385 +NULL, // #386 +NULL, // #387 +NULL, // #388 +NULL, // #389 +NULL, // #390 +NULL, // #391 +NULL, // #392 +NULL, // #393 +NULL, // #394 +NULL, // #395 +NULL, // #396 +NULL, // #397 +NULL, // #398 +NULL, // #399 +// LordHavoc's range #400-#499 +VM_SV_copyentity, // #400 void(entity from, entity to) copyentity (DP_QC_COPYENTITY) +VM_SV_setcolor, // #401 void(entity ent, float colors) setcolor (DP_QC_SETCOLOR) +VM_findchain, // #402 entity(.string fld, string match) findchain (DP_QC_FINDCHAIN) +VM_findchainfloat, // #403 entity(.float fld, float match) findchainfloat (DP_QC_FINDCHAINFLOAT) +VM_SV_effect, // #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) +VM_SV_te_blood, // #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) +VM_SV_te_bloodshower, // #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) +VM_SV_te_explosionrgb, // #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) +VM_SV_te_particlecube, // #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) +VM_SV_te_particlerain, // #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) +VM_SV_te_particlesnow, // #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) +VM_SV_te_spark, // #411 void(vector org, vector vel, float howmany) te_spark (DP_TE_SPARK) +VM_SV_te_gunshotquad, // #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) +VM_SV_te_spikequad, // #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) +VM_SV_te_superspikequad, // #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) +VM_SV_te_explosionquad, // #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) +VM_SV_te_smallflash, // #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) +VM_SV_te_customflash, // #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) +VM_SV_te_gunshot, // #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_spike, // #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_superspike, // #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_explosion, // #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_tarexplosion, // #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_wizspike, // #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_knightspike, // #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_lavasplash, // #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_teleport, // #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_explosion2, // #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_lightning1, // #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_lightning2, // #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_lightning3, // #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_beam, // #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) +VM_vectorvectors, // #432 void(vector dir) vectorvectors (DP_QC_VECTORVECTORS) +VM_SV_te_plasmaburn, // #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) +VM_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE) +VM_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE) +VM_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE) +VM_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE) +VM_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE) +VM_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE) +VM_SV_clientcommand, // #440 void(entity e, string s) clientcommand (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_tokenize, // #441 float(string s) tokenize (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_argv, // #442 string(float n) argv (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_SV_setattachment, // #443 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) +VM_search_begin, // #444 float(string pattern, float caseinsensitive, float quiet) search_begin (DP_QC_FS_SEARCH) +VM_search_end, // #445 void(float handle) search_end (DP_QC_FS_SEARCH) +VM_search_getsize, // #446 float(float handle) search_getsize (DP_QC_FS_SEARCH) +VM_search_getfilename, // #447 string(float handle, float num) search_getfilename (DP_QC_FS_SEARCH) +VM_cvar_string, // #448 string(string s) cvar_string (DP_QC_CVAR_STRING) +VM_findflags, // #449 entity(entity start, .float fld, float match) findflags (DP_QC_FINDFLAGS) +VM_findchainflags, // #450 entity(.float fld, float match) findchainflags (DP_QC_FINDCHAINFLAGS) +VM_SV_gettagindex, // #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) +VM_SV_gettaginfo, // #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) +VM_SV_dropclient, // #453 void(entity clent) dropclient (DP_SV_DROPCLIENT) +VM_SV_spawnclient, // #454 entity() spawnclient (DP_SV_BOTCLIENT) +VM_SV_clienttype, // #455 float(entity clent) clienttype (DP_SV_BOTCLIENT) +VM_SV_WriteUnterminatedString, // #456 void(float to, string s) WriteUnterminatedString (DP_SV_WRITEUNTERMINATEDSTRING) +VM_SV_te_flamejet, // #457 void(vector org, vector vel, float howmany) te_flamejet = #457 (DP_TE_FLAMEJET) +NULL, // #458 +VM_ftoe, // #459 entity(float num) entitybyindex (DP_QC_EDICT_NUM) +VM_buf_create, // #460 float() buf_create (DP_QC_STRINGBUFFERS) +VM_buf_del, // #461 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) +VM_buf_getsize, // #462 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) +VM_buf_copy, // #463 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) +VM_buf_sort, // #464 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) +VM_buf_implode, // #465 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) +VM_bufstr_get, // #466 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) +VM_bufstr_set, // #467 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) +VM_bufstr_add, // #468 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) +VM_bufstr_free, // #469 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) +NULL, // #470 +VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) +VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) +VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) +VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) +VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) +VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) +VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_SV_STRINGCOLORFUNCTIONS) +VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) +VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) +VM_strtolower, // #480 string(string s) VM_strtolower (DP_QC_STRING_CASE_FUNCTIONS) +VM_strtoupper, // #481 string(string s) VM_strtoupper (DP_QC_STRING_CASE_FUNCTIONS) +VM_cvar_defstring, // #482 string(string s) cvar_defstring (DP_QC_CVAR_DEFSTRING) +VM_SV_pointsound, // #483 void(vector origin, string sample, float volume, float attenuation) (DP_SV_POINTSOUND) +VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) +VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) +VM_getsurfacepointattribute,// #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; +NULL, // #487 +NULL, // #488 +NULL, // #489 +NULL, // #490 +NULL, // #491 +NULL, // #492 +NULL, // #493 +VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) +VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) +VM_numentityfields, // #496 float() numentityfields = #496; (DP_QC_ENTITYDATA) +VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (DP_QC_ENTITYDATA) +VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) +VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) +VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) +VM_SV_WritePicture, // #501 +NULL, // #502 +VM_whichpack, // #503 string(string) whichpack = #503; +NULL, // #504 +NULL, // #505 +NULL, // #506 +NULL, // #507 +NULL, // #508 +NULL, // #509 +VM_uri_escape, // #510 string(string in) uri_escape = #510; +VM_uri_unescape, // #511 string(string in) uri_unescape = #511; +VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) +VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) +VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) +VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) +VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) +VM_gettime, // #519 float(float timer) gettime = #519; (DP_QC_GETTIME) +NULL, // #520 +NULL, // #521 +NULL, // #522 +NULL, // #523 +NULL, // #524 +NULL, // #525 +NULL, // #526 +NULL, // #527 +NULL, // #528 +VM_loadfromdata, // #529 +VM_loadfromfile, // #530 +VM_SV_setpause, // #531 void(float pause) setpause = #531; +VM_log, // #532 +VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) +VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) +NULL, // #535 +NULL, // #536 +NULL, // #537 +NULL, // #538 +NULL, // #539 +VM_physics_enable, // #540 void(entity e, float physics_enabled) physics_enable = #540; (DP_PHYSICS_ODE) +VM_physics_addforce, // #541 void(entity e, vector force, vector relative_ofs) physics_addforce = #541; (DP_PHYSICS_ODE) +VM_physics_addtorque, // #542 void(entity e, vector torque) physics_addtorque = #542; (DP_PHYSICS_ODE) +NULL, // #543 +NULL, // #544 +NULL, // #545 +NULL, // #546 +NULL, // #547 +NULL, // #548 +NULL, // #549 +NULL, // #550 +NULL, // #551 +NULL, // #552 +NULL, // #553 +NULL, // #554 +NULL, // #555 +NULL, // #556 +NULL, // #557 +NULL, // #558 +NULL, // #559 +NULL, // #560 +NULL, // #561 +NULL, // #562 +NULL, // #563 +NULL, // #564 +NULL, // #565 +NULL, // #566 +NULL, // #567 +NULL, // #568 +NULL, // #569 +NULL, // #570 +NULL, // #571 +NULL, // #572 +NULL, // #573 +NULL, // #574 +NULL, // #575 +NULL, // #576 +NULL, // #577 +NULL, // #578 +NULL, // #579 +NULL, // #580 +NULL, // #581 +NULL, // #582 +NULL, // #583 +NULL, // #584 +NULL, // #585 +NULL, // #586 +NULL, // #587 +NULL, // #588 +NULL, // #589 +NULL, // #590 +NULL, // #591 +NULL, // #592 +NULL, // #593 +NULL, // #594 +NULL, // #595 +NULL, // #596 +NULL, // #597 +NULL, // #598 +NULL, // #599 +NULL, // #600 +NULL, // #601 +NULL, // #602 +NULL, // #603 +NULL, // #604 +VM_callfunction, // #605 +VM_writetofile, // #606 +VM_isfunction, // #607 +NULL, // #608 +NULL, // #609 +NULL, // #610 +NULL, // #611 +NULL, // #612 +VM_parseentitydata, // #613 +NULL, // #614 +NULL, // #615 +NULL, // #616 +NULL, // #617 +NULL, // #618 +NULL, // #619 +NULL, // #620 +NULL, // #621 +NULL, // #622 +NULL, // #623 +VM_SV_getextresponse, // #624 string getextresponse(void) +NULL, // #625 +NULL, // #626 +VM_sprintf, // #627 string sprintf(string format, ...) +VM_getsurfacenumtriangles, // #628 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACETRIANGLE) +VM_getsurfacetriangle, // #629 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACETRIANGLE) +NULL, // #630 +}; + +const int vm_sv_numbuiltins = sizeof(vm_sv_builtins) / sizeof(prvm_builtin_t); + +void VM_SV_Cmd_Init(void) +{ + VM_Cmd_Init(); +} + +void VM_SV_Cmd_Reset(void) +{ + World_End(&sv.world); + if(PRVM_serverfunction(SV_Shutdown)) + { + func_t s = PRVM_serverfunction(SV_Shutdown); + PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again + PRVM_ExecuteProgram(s,"SV_Shutdown() required"); + } + + VM_Cmd_Reset(); +} + diff --git a/misc/source/darkplaces-src/sys.h b/misc/source/darkplaces-src/sys.h new file mode 100644 index 00000000..37fcc804 --- /dev/null +++ b/misc/source/darkplaces-src/sys.h @@ -0,0 +1,106 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sys.h -- non-portable functions + +#ifndef SYS_H +#define SYS_H + +extern cvar_t sys_usenoclockbutbenchmark; + +// +// DLL management +// + +// Win32 specific +#ifdef WIN32 +# include +typedef HMODULE dllhandle_t; + +// Other platforms +#else + typedef void* dllhandle_t; +#endif + +typedef struct dllfunction_s +{ + const char *name; + void **funcvariable; +} +dllfunction_t; + +/*! Loads a library. + * \param dllnames a NULL terminated array of possible names for the DLL you want to load. + * \param handle + * \param fcts + */ +qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts); +void Sys_UnloadLibrary (dllhandle_t* handle); +void* Sys_GetProcAddress (dllhandle_t handle, const char* name); + +/// called early in Host_Init +void Sys_InitConsole (void); +/// called after command system is initialized but before first Con_Print +void Sys_Init_Commands (void); + + +/// \returns current timestamp +char *Sys_TimeString(const char *timeformat); + +// +// system IO interface (these are the sys functions that need to be implemented in a new driver atm) +// + +/// an error will cause the entire program to exit +void Sys_Error (const char *error, ...) DP_FUNC_PRINTF(1); + +/// (may) output text to terminal which launched program +void Sys_PrintToTerminal(const char *text); + +/// INFO: This is only called by Host_Shutdown so we dont need testing for recursion +void Sys_Shutdown (void); +void Sys_Quit (int returnvalue); + +/*! on some build/platform combinations (such as Linux gcc with the -pg + * profiling option) this can turn on/off profiling, used primarily to limit + * profiling to certain areas of the code, such as ingame performance without + * regard for loading/shutdown performance (-profilegameonly on commandline) + */ +void Sys_AllowProfiling (qboolean enable); + +double Sys_DoubleTime (void); + +void Sys_ProvideSelfFD (void); + +char *Sys_ConsoleInput (void); + +/// called to yield for a little bit so as not to hog cpu when paused or debugging +void Sys_Sleep(int microseconds); + +/// Perform Key_Event () callbacks until the input que is empty +void Sys_SendKeyEvents (void); + +char *Sys_GetClipboardData (void); + +extern qboolean sys_supportsdlgetticks; +unsigned int Sys_SDL_GetTicks (void); // wrapper to call SDL_GetTicks +void Sys_SDL_Delay (unsigned int milliseconds); // wrapper to call SDL_Delay + +#endif + diff --git a/misc/source/darkplaces-src/sys_linux.c b/misc/source/darkplaces-src/sys_linux.c new file mode 100644 index 00000000..0b954301 --- /dev/null +++ b/misc/source/darkplaces-src/sys_linux.c @@ -0,0 +1,184 @@ + +#ifdef WIN32 +#include +#include +#include +#include "conio.h" +#else +#include +#include +#include +#endif + +#include + +#include "quakedef.h" + +// ======================================================================= +// General routines +// ======================================================================= +void Sys_Shutdown (void) +{ +#ifdef FNDELAY + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); +#endif + fflush(stdout); +} + +void Sys_Error (const char *error, ...) +{ + va_list argptr; + char string[MAX_INPUTLINE]; + +// change stdin to non blocking +#ifdef FNDELAY + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); +#endif + + va_start (argptr,error); + dpvsnprintf (string, sizeof (string), error, argptr); + va_end (argptr); + + Con_Printf ("Quake Error: %s\n", string); + + Host_Shutdown (); + exit (1); +} + +static int outfd = 1; +void Sys_PrintToTerminal(const char *text) +{ + if(outfd < 0) + return; +#ifdef FNDELAY + // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0). + // this is because both go to /dev/tty by default! + { + int origflags = fcntl (outfd, F_GETFL, 0); + fcntl (outfd, F_SETFL, origflags & ~FNDELAY); +#endif +#ifdef WIN32 +#define write _write +#endif + while(*text) + { + fs_offset_t written = (fs_offset_t)write(outfd, text, strlen(text)); + if(written <= 0) + break; // sorry, I cannot do anything about this error - without an output + text += written; + } +#ifdef FNDELAY + fcntl (outfd, F_SETFL, origflags); + } +#endif + //fprintf(stdout, "%s", text); +} + +char *Sys_ConsoleInput(void) +{ + //if (cls.state == ca_dedicated) + { + static char text[MAX_INPUTLINE]; + static unsigned int len = 0; +#ifdef WIN32 + int c; + + // read a line out + while (_kbhit ()) + { + c = _getch (); + if (c == '\r') + { + text[len] = '\0'; + _putch ('\n'); + len = 0; + return text; + } + if (c == '\b') + { + if (len) + { + _putch (c); + _putch (' '); + _putch (c); + len--; + } + continue; + } + if (len < sizeof (text) - 1) + { + _putch (c); + text[len] = c; + len++; + } + } +#else + fd_set fdset; + struct timeval timeout; + FD_ZERO(&fdset); + FD_SET(0, &fdset); // stdin + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select (1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(0, &fdset)) + { + len = read (0, text, sizeof(text) - 1); + if (len >= 1) + { + // rip off the \n and terminate + // div0: WHY? console code can deal with \n just fine + // this caused problems with pasting stuff into a terminal window + // so, not ripping off the \n, but STILL keeping a NUL terminator + text[len] = 0; + return text; + } + } +#endif + } + return NULL; +} + +char *Sys_GetClipboardData (void) +{ + return NULL; +} + +void Sys_InitConsole (void) +{ +} + +int main (int argc, char **argv) +{ + signal(SIGFPE, SIG_IGN); + + com_argc = argc; + com_argv = (const char **)argv; + Sys_ProvideSelfFD(); + + // COMMANDLINEOPTION: sdl: -noterminal disables console output on stdout + if(COM_CheckParm("-noterminal")) + outfd = -1; + // COMMANDLINEOPTION: sdl: -stderr moves console output to stderr + else if(COM_CheckParm("-stderr")) + outfd = 2; + else + outfd = 1; + +#ifdef FNDELAY + fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY); +#endif + + Host_Main(); + + return 0; +} + +qboolean sys_supportsdlgetticks = false; +unsigned int Sys_SDL_GetTicks (void) +{ + Sys_Error("Called Sys_SDL_GetTicks on non-SDL target"); + return 0; +} +void Sys_SDL_Delay (unsigned int milliseconds) +{ + Sys_Error("Called Sys_SDL_Delay on non-SDL target"); +} diff --git a/misc/source/darkplaces-src/sys_sdl.c b/misc/source/darkplaces-src/sys_sdl.c new file mode 100644 index 00000000..6175b4f5 --- /dev/null +++ b/misc/source/darkplaces-src/sys_sdl.c @@ -0,0 +1,214 @@ + +#ifdef WIN32 +#ifdef _MSC_VER +#pragma comment(lib, "sdl.lib") +#pragma comment(lib, "sdlmain.lib") +#endif +#include +#include "conio.h" +#else +#include +#include +#include +#endif + +#include + +#include + +#include "quakedef.h" + +// ======================================================================= +// General routines +// ======================================================================= + +void Sys_Shutdown (void) +{ +#ifndef WIN32 + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); +#endif + fflush(stdout); + SDL_Quit(); +} + + +void Sys_Error (const char *error, ...) +{ + va_list argptr; + char string[MAX_INPUTLINE]; + +// change stdin to non blocking +#ifndef WIN32 + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); +#endif + + va_start (argptr,error); + dpvsnprintf (string, sizeof (string), error, argptr); + va_end (argptr); + + Con_Printf ("Quake Error: %s\n", string); + + Host_Shutdown (); + exit (1); +} + +static int outfd = 1; +void Sys_PrintToTerminal(const char *text) +{ + if(outfd < 0) + return; +#ifdef FNDELAY + // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0). + // this is because both go to /dev/tty by default! + { + int origflags = fcntl (outfd, F_GETFL, 0); + fcntl (outfd, F_SETFL, origflags & ~FNDELAY); +#endif +#ifdef WIN32 +#define write _write +#endif + while(*text) + { + fs_offset_t written = (fs_offset_t)write(outfd, text, strlen(text)); + if(written <= 0) + break; // sorry, I cannot do anything about this error - without an output + text += written; + } +#ifdef FNDELAY + fcntl (outfd, F_SETFL, origflags); + } +#endif + //fprintf(stdout, "%s", text); +} + +char *Sys_ConsoleInput(void) +{ + if (cls.state == ca_dedicated) + { + static char text[MAX_INPUTLINE]; + int len = 0; +#ifdef WIN32 + int c; + + // read a line out + while (_kbhit ()) + { + c = _getch (); + _putch (c); + if (c == '\r') + { + text[len] = 0; + _putch ('\n'); + len = 0; + return text; + } + if (c == 8) + { + if (len) + { + _putch (' '); + _putch (c); + len--; + text[len] = 0; + } + continue; + } + text[len] = c; + len++; + text[len] = 0; + if (len == sizeof (text)) + len = 0; + } +#else + fd_set fdset; + struct timeval timeout; + FD_ZERO(&fdset); + FD_SET(0, &fdset); // stdin + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select (1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(0, &fdset)) + { + len = read (0, text, sizeof(text)); + if (len >= 1) + { + // rip off the \n and terminate + text[len-1] = 0; + return text; + } + } +#endif + } + return NULL; +} + +char *Sys_GetClipboardData (void) +{ +#ifdef WIN32 + char *data = NULL; + char *cliptext; + + if (OpenClipboard (NULL) != 0) + { + HANDLE hClipboardData; + + if ((hClipboardData = GetClipboardData (CF_TEXT)) != 0) + { + if ((cliptext = (char *)GlobalLock (hClipboardData)) != 0) + { + size_t allocsize; + allocsize = GlobalSize (hClipboardData) + 1; + data = (char *)Z_Malloc (allocsize); + strlcpy (data, cliptext, allocsize); + GlobalUnlock (hClipboardData); + } + } + CloseClipboard (); + } + return data; +#else + return NULL; +#endif +} + +void Sys_InitConsole (void) +{ +} + +int main (int argc, char *argv[]) +{ + signal(SIGFPE, SIG_IGN); + + com_argc = argc; + com_argv = (const char **)argv; + Sys_ProvideSelfFD(); + + // COMMANDLINEOPTION: sdl: -noterminal disables console output on stdout + if(COM_CheckParm("-noterminal")) + outfd = -1; + // COMMANDLINEOPTION: sdl: -stderr moves console output to stderr + else if(COM_CheckParm("-stderr")) + outfd = 2; + else + outfd = 1; + +#ifndef WIN32 + fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY); +#endif + + // we don't know which systems we'll want to init, yet... + SDL_Init(0); + + Host_Main(); + + return 0; +} + +qboolean sys_supportsdlgetticks = true; +unsigned int Sys_SDL_GetTicks (void) +{ + return SDL_GetTicks(); +} +void Sys_SDL_Delay (unsigned int milliseconds) +{ + SDL_Delay(milliseconds); +} diff --git a/misc/source/darkplaces-src/sys_shared.c b/misc/source/darkplaces-src/sys_shared.c new file mode 100644 index 00000000..298de87d --- /dev/null +++ b/misc/source/darkplaces-src/sys_shared.c @@ -0,0 +1,572 @@ +#ifdef WIN32 +# ifndef DONT_USE_SETDLLDIRECTORY +# define _WIN32_WINNT 0x0502 +# endif +#endif + +#include "quakedef.h" + +#define SUPPORTDLL + +#ifdef WIN32 +# include +# include // timeGetTime +# include // localtime +#ifdef _MSC_VER +#pragma comment(lib, "winmm.lib") +#endif +#else +# include +# include +# include +# include +# ifdef SUPPORTDLL +# include +# endif +#endif + +static char sys_timestring[128]; +char *Sys_TimeString(const char *timeformat) +{ + time_t mytime = time(NULL); +#if _MSC_VER >= 1400 + struct tm mytm; + localtime_s(&mytm, &mytime); + strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm); +#else + strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime)); +#endif + return sys_timestring; +} + + +extern qboolean host_shuttingdown; +void Sys_Quit (int returnvalue) +{ + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(false); + host_shuttingdown = true; + Host_Shutdown(); + exit(returnvalue); +} + +#if defined(__linux__) || defined(__FreeBSD__) +#ifdef __cplusplus +extern "C" +#endif +int moncontrol(int); +#endif + +void Sys_AllowProfiling(qboolean enable) +{ +#if defined(__linux__) || defined(__FreeBSD__) + moncontrol(enable); +#endif +} + + +/* +=============================================================================== + +DLL MANAGEMENT + +=============================================================================== +*/ + +static qboolean Sys_LoadLibraryFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qboolean complain, qboolean has_next) +{ + const dllfunction_t *func; + if(dllhandle) + { + for (func = fcts; func && func->name != NULL; func++) + if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name))) + { + if(complain) + { + Con_DPrintf (" - missing function \"%s\" - broken library!", func->name); + if(has_next) + Con_DPrintf("\nContinuing with"); + } + goto notfound; + } + return true; + + notfound: + for (func = fcts; func && func->name != NULL; func++) + *func->funcvariable = NULL; + } + return false; +} + +qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts) +{ +#ifdef SUPPORTDLL + const dllfunction_t *func; + dllhandle_t dllhandle = 0; + unsigned int i; + + if (handle == NULL) + return false; + +#ifndef WIN32 +#ifdef PREFER_PRELOAD + dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL); + if(Sys_LoadLibraryFunctions(dllhandle, fcts, false, false)) + { + Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]); + *handle = dllhandle; + return true; + } + else + Sys_UnloadLibrary(&dllhandle); +notfound: +#endif +#endif + + // Initializations + for (func = fcts; func && func->name != NULL; func++) + *func->funcvariable = NULL; + + // Try every possible name + Con_DPrintf ("Trying to load library..."); + for (i = 0; dllnames[i] != NULL; i++) + { + Con_DPrintf (" \"%s\"", dllnames[i]); +#ifdef WIN32 +# ifndef DONT_USE_SETDLLDIRECTORY +# ifdef _WIN64 + SetDllDirectory("bin64"); +# else + SetDllDirectory("bin32"); +# endif +# endif + dllhandle = LoadLibrary (dllnames[i]); + // no need to unset this - we want ALL dlls to be loaded from there, anyway +#else + dllhandle = dlopen (dllnames[i], RTLD_LAZY | RTLD_GLOBAL); +#endif + if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(com_argv[0], '/')))) + break; + else + Sys_UnloadLibrary (&dllhandle); + } + + // see if the names can be loaded relative to the executable path + // (this is for Mac OSX which does not check next to the executable) + if (!dllhandle && strrchr(com_argv[0], '/')) + { + char path[MAX_OSPATH]; + strlcpy(path, com_argv[0], sizeof(path)); + strrchr(path, '/')[1] = 0; + for (i = 0; dllnames[i] != NULL; i++) + { + char temp[MAX_OSPATH]; + strlcpy(temp, path, sizeof(temp)); + strlcat(temp, dllnames[i], sizeof(temp)); + Con_DPrintf (" \"%s\"", temp); +#ifdef WIN32 + dllhandle = LoadLibrary (temp); +#else + dllhandle = dlopen (temp, RTLD_LAZY | RTLD_GLOBAL); +#endif + if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, dllnames[i+1] != NULL)) + break; + else + Sys_UnloadLibrary (&dllhandle); + } + } + + // No DLL found + if (! dllhandle) + { + Con_DPrintf(" - failed.\n"); + return false; + } + + Con_DPrintf(" - loaded.\n"); + + *handle = dllhandle; + return true; +#else + return false; +#endif +} + +void Sys_UnloadLibrary (dllhandle_t* handle) +{ +#ifdef SUPPORTDLL + if (handle == NULL || *handle == NULL) + return; + +#ifdef WIN32 + FreeLibrary (*handle); +#else + dlclose (*handle); +#endif + + *handle = NULL; +#endif +} + +void* Sys_GetProcAddress (dllhandle_t handle, const char* name) +{ +#ifdef SUPPORTDLL +#ifdef WIN32 + return (void *)GetProcAddress (handle, name); +#else + return (void *)dlsym (handle, name); +#endif +#else + return NULL; +#endif +} + +#ifdef WIN32 +# define HAVE_TIMEGETTIME 1 +# define HAVE_QUERYPERFORMANCECOUNTER 1 +# define HAVE_Sleep 1 +#endif + +#if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES) +# define HAVE_CLOCKGETTIME 1 +#endif + +#ifndef WIN32 +// FIXME improve this check, manpage hints to DST_NONE +# define HAVE_GETTIMEOFDAY 1 +#endif + +#ifndef WIN32 +// on Win32, select() cannot be used with all three FD list args being NULL according to MSDN +// (so much for POSIX...) +# ifdef FD_SET +# define HAVE_SELECT 1 +# endif +#endif + +#ifndef WIN32 +// FIXME improve this check +# define HAVE_USLEEP 1 +#endif + +// this one is referenced elsewhere +cvar_t sys_usenoclockbutbenchmark = {CVAR_SAVE, "sys_usenoclockbutbenchmark", "0", "don't use ANY real timing, and simulate a clock (for benchmarking); the game then runs as fast as possible. Run a QC mod with bots that does some stuff, then does a quit at the end, to benchmark a server. NEVER do this on a public server."}; + +// these are not +static cvar_t sys_debugsleep = {0, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"}; +static cvar_t sys_usesdlgetticks = {CVAR_SAVE, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"}; +static cvar_t sys_usesdldelay = {CVAR_SAVE, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"}; +#if HAVE_QUERYPERFORMANCECOUNTER +static cvar_t sys_usequeryperformancecounter = {CVAR_SAVE, "sys_usequeryperformancecounter", "0", "use windows QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) for timing rather than timeGetTime function (which has issues on some motherboards)"}; +#endif +#if HAVE_CLOCKGETTIME +static cvar_t sys_useclockgettime = {CVAR_SAVE, "sys_useclockgettime", "0", "use POSIX clock_gettime function (which has issues if the system clock speed is far off, as it can't get fixed by NTP) for timing rather than gettimeofday (which has issues if the system time is stepped by ntpdate, or apparently on some Xen installations)"}; +#endif + +static unsigned long benchmark_time; + +void Sys_Init_Commands (void) +{ + Cvar_RegisterVariable(&sys_debugsleep); + Cvar_RegisterVariable(&sys_usenoclockbutbenchmark); +#if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY + if(sys_supportsdlgetticks) + { + Cvar_RegisterVariable(&sys_usesdlgetticks); + Cvar_RegisterVariable(&sys_usesdldelay); + } +#endif +#if HAVE_QUERYPERFORMANCECOUNTER + Cvar_RegisterVariable(&sys_usequeryperformancecounter); +#endif +#if HAVE_CLOCKGETTIME + Cvar_RegisterVariable(&sys_useclockgettime); +#endif +} + +double Sys_DoubleTime(void) +{ + static int first = true; + static double oldtime = 0.0, curtime = 0.0; + double newtime; + if(sys_usenoclockbutbenchmark.integer) + { + benchmark_time += 1; + return ((double) benchmark_time) / 1e6; + } + + // first all the OPTIONAL timers + +#if HAVE_QUERYPERFORMANCECOUNTER + else if (sys_usequeryperformancecounter.integer) + { + // LordHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine. + // QueryPerformanceCounter + // platform: + // Windows 95/98/ME/NT/2000/XP + // features: + // very accurate (CPU cycles) + // known issues: + // does not necessarily match realtime too well (tends to get faster and faster in win98) + // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors) + double timescale; + LARGE_INTEGER PerformanceFreq; + LARGE_INTEGER PerformanceCount; + + if (!QueryPerformanceFrequency (&PerformanceFreq)) + { + Con_Printf ("No hardware timer available\n"); + // fall back to timeGetTime + Cvar_SetValueQuick(&sys_usequeryperformancecounter, false); + return Sys_DoubleTime(); + } + QueryPerformanceCounter (&PerformanceCount); + + #ifdef __BORLANDC__ + timescale = 1.0 / ((double) PerformanceFreq.u.LowPart + (double) PerformanceFreq.u.HighPart * 65536.0 * 65536.0); + newtime = ((double) PerformanceCount.u.LowPart + (double) PerformanceCount.u.HighPart * 65536.0 * 65536.0) * timescale; + #else + timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0); + newtime = ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale; + #endif + } +#endif + +#if HAVE_CLOCKGETTIME + else if (sys_useclockgettime.integer) + { + struct timespec ts; +# ifdef CLOCK_MONOTONIC + // linux + clock_gettime(CLOCK_MONOTONIC, &ts); +# else + // sunos + clock_gettime(CLOCK_HIGHRES, &ts); +# endif + newtime = (double) ts.tv_sec + ts.tv_nsec / 1000000000.0; + } +#endif + + // now all the FALLBACK timers + else if(sys_supportsdlgetticks && sys_usesdlgetticks.integer) + { + newtime = (double) Sys_SDL_GetTicks() / 1000.0; + } +#if HAVE_GETTIMEOFDAY + else + { + struct timeval tp; + gettimeofday(&tp, NULL); + newtime = (double) tp.tv_sec + tp.tv_usec / 1000000.0; + } +#elif HAVE_TIMEGETTIME + else + { + static int firsttimegettime = true; + // timeGetTime + // platform: + // Windows 95/98/ME/NT/2000/XP + // features: + // reasonable accuracy (millisecond) + // issues: + // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter) + + // make sure the timer is high precision, otherwise different versions of windows have varying accuracy + if (firsttimegettime) + { + timeBeginPeriod (1); + firsttimegettime = false; + } + + newtime = (double) timeGetTime () / 1000.0; + } +#else + // fallback for using the SDL timer if no other timer is available + else + { + newtime = (double) Sys_SDL_GetTicks() / 1000.0; + // this calls Sys_Error() if not linking against SDL + } +#endif + + if (first) + { + first = false; + oldtime = newtime; + } + + if (newtime < oldtime) + { + // warn if it's significant + if (newtime - oldtime < -0.01) + Con_Printf("Sys_DoubleTime: time stepped backwards (went from %f to %f, difference %f)\n", oldtime, newtime, newtime - oldtime); + } + else if (newtime > oldtime + 1800) + { + Con_Printf("Sys_DoubleTime: time stepped forward (went from %f to %f, difference %f)\n", oldtime, newtime, newtime - oldtime); + } + else + curtime += newtime - oldtime; + oldtime = newtime; + + return curtime; +} + +void Sys_Sleep(int microseconds) +{ + double t = 0; + if(sys_usenoclockbutbenchmark.integer) + { + benchmark_time += microseconds; + return; + } + if(sys_debugsleep.integer) + { + t = Sys_DoubleTime(); + } + if(sys_supportsdlgetticks && sys_usesdldelay.integer) + { + Sys_SDL_Delay(microseconds / 1000); + } +#if HAVE_SELECT + else + { + struct timeval tv; + tv.tv_sec = microseconds / 1000000; + tv.tv_usec = microseconds % 1000000; + select(0, NULL, NULL, NULL, &tv); + } +#elif HAVE_USLEEP + else + { + usleep(microseconds); + } +#elif HAVE_Sleep + else + { + Sleep(microseconds / 1000); + } +#else + else + { + Sys_SDL_Delay(microseconds / 1000); + } +#endif + if(sys_debugsleep.integer) + { + t = Sys_DoubleTime() - t; + printf("%d %d # debugsleep\n", microseconds, (unsigned int)(t * 1000000)); + } +} + +const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize) +{ + const char *p = PATH; + const char *q; + if(p && name) + { + while((q = strchr(p, ':'))) + { + dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name); + if(FS_SysFileExists(buf)) + return buf; + p = q + 1; + } + if(!q) // none found - try the last item + { + dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name); + if(FS_SysFileExists(buf)) + return buf; + } + } + return name; +} + +const char *Sys_FindExecutableName(void) +{ +#if defined(WIN32) + return com_argv[0]; +#else + static char exenamebuf[MAX_OSPATH+1]; + ssize_t n = -1; +#if defined(__FreeBSD__) + n = readlink("/proc/curproc/file", exenamebuf, sizeof(exenamebuf)-1); +#elif defined(__linux__) + n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1); +#endif + if(n > 0 && (size_t)(n) < sizeof(exenamebuf)) + { + exenamebuf[n] = 0; + return exenamebuf; + } + if(strchr(com_argv[0], '/')) + return com_argv[0]; // possibly a relative path + else + return Sys_FindInPATH(com_argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf)); +#endif +} + +void Sys_ProvideSelfFD(void) +{ + if(com_selffd != -1) + return; + com_selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false); +} + +// for x86 cpus only... (x64 has SSE2_PRESENT) +#if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT) +// code from SDL, shortened as we can expect CPUID to work +static int CPUID_Features(void) +{ + int features = 0; +# if defined(__GNUC__) && defined(__i386__) + __asm__ ( +" movl %%ebx,%%edi\n" +" xorl %%eax,%%eax \n" +" incl %%eax \n" +" cpuid # Get family/model/stepping/features\n" +" movl %%edx,%0 \n" +" movl %%edi,%%ebx\n" + : "=m" (features) + : + : "%eax", "%ecx", "%edx", "%edi" + ); +# elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) + __asm { + xor eax, eax + inc eax + cpuid ; Get family/model/stepping/features + mov features, edx + } +# else +# error SSE_POSSIBLE set but no CPUID implementation +# endif + return features; +} + +qboolean Sys_HaveSSE(void) +{ + // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection + if(COM_CheckParm("-nosse")) + return false; + // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection + if(COM_CheckParm("-forcesse") || COM_CheckParm("-forcesse2")) + return true; + if(CPUID_Features() & (1 << 25)) + return true; + return false; +} + +qboolean Sys_HaveSSE2(void) +{ + // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection + if(COM_CheckParm("-nosse") || COM_CheckParm("-nosse2")) + return false; + // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection + if(COM_CheckParm("-forcesse2")) + return true; + if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26 + return true; + return false; +} +#endif diff --git a/misc/source/darkplaces-src/sys_win.c b/misc/source/darkplaces-src/sys_win.c new file mode 100644 index 00000000..3b48f18d --- /dev/null +++ b/misc/source/darkplaces-src/sys_win.c @@ -0,0 +1,401 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sys_win.c -- Win32 system interface code + +#include +#include +#include +#ifdef SUPPORTDIRECTX +#include +#endif + +#include "qtypes.h" + +#include "quakedef.h" +#include "errno.h" +#include "resource.h" +#include "conproc.h" + +HANDLE hinput, houtput; + +#ifdef QHOST +static HANDLE tevent; +static HANDLE hFile; +static HANDLE heventParent; +static HANDLE heventChild; +#endif + + +/* +=============================================================================== + +SYSTEM IO + +=============================================================================== +*/ + +void Sys_Error (const char *error, ...) +{ + va_list argptr; + char text[MAX_INPUTLINE]; + static int in_sys_error0 = 0; + static int in_sys_error1 = 0; + static int in_sys_error2 = 0; + static int in_sys_error3 = 0; + + va_start (argptr, error); + dpvsnprintf (text, sizeof (text), error, argptr); + va_end (argptr); + + Con_Printf ("Quake Error: %s\n", text); + + // close video so the message box is visible, unless we already tried that + if (!in_sys_error0 && cls.state != ca_dedicated) + { + in_sys_error0 = 1; + VID_Shutdown(); + } + + if (!in_sys_error3 && cls.state != ca_dedicated) + { + in_sys_error3 = true; + MessageBox(NULL, text, "Quake Error", MB_OK | MB_SETFOREGROUND | MB_ICONSTOP); + } + + if (!in_sys_error1) + { + in_sys_error1 = 1; + Host_Shutdown (); + } + +// shut down QHOST hooks if necessary + if (!in_sys_error2) + { + in_sys_error2 = 1; + Sys_Shutdown (); + } + + exit (1); +} + +void Sys_Shutdown (void) +{ +#ifdef QHOST + if (tevent) + CloseHandle (tevent); +#endif + + if (cls.state == ca_dedicated) + FreeConsole (); + +#ifdef QHOST +// shut down QHOST hooks if necessary + DeinitConProc (); +#endif +} + +void Sys_PrintToTerminal(const char *text) +{ + DWORD dummy; + extern HANDLE houtput; + + if ((houtput != 0) && (houtput != INVALID_HANDLE_VALUE)) + WriteFile(houtput, text, (DWORD) strlen(text), &dummy, NULL); +} + +char *Sys_ConsoleInput (void) +{ + static char text[MAX_INPUTLINE]; + static int len; + INPUT_RECORD recs[1024]; + int ch; + DWORD numread, numevents, dummy; + + if (cls.state != ca_dedicated) + return NULL; + + for ( ;; ) + { + if (!GetNumberOfConsoleInputEvents (hinput, &numevents)) + { + cls.state = ca_disconnected; + Sys_Error ("Error getting # of console events (error code %x)", (unsigned int)GetLastError()); + } + + if (numevents <= 0) + break; + + if (!ReadConsoleInput(hinput, recs, 1, &numread)) + { + cls.state = ca_disconnected; + Sys_Error ("Error reading console input (error code %x)", (unsigned int)GetLastError()); + } + + if (numread != 1) + { + cls.state = ca_disconnected; + Sys_Error ("Couldn't read console input (error code %x)", (unsigned int)GetLastError()); + } + + if (recs[0].EventType == KEY_EVENT) + { + if (!recs[0].Event.KeyEvent.bKeyDown) + { + ch = recs[0].Event.KeyEvent.uChar.AsciiChar; + + switch (ch) + { + case '\r': + WriteFile(houtput, "\r\n", 2, &dummy, NULL); + + if (len) + { + text[len] = 0; + len = 0; + return text; + } + + break; + + case '\b': + WriteFile(houtput, "\b \b", 3, &dummy, NULL); + if (len) + { + len--; + } + break; + + default: + if (ch >= (int) (unsigned char) ' ') + { + WriteFile(houtput, &ch, 1, &dummy, NULL); + text[len] = ch; + len = (len + 1) & 0xff; + } + + break; + + } + } + } + } + + return NULL; +} + +char *Sys_GetClipboardData (void) +{ + char *data = NULL; + char *cliptext; + + if (OpenClipboard (NULL) != 0) + { + HANDLE hClipboardData; + + if ((hClipboardData = GetClipboardData (CF_TEXT)) != 0) + { + if ((cliptext = (char *)GlobalLock (hClipboardData)) != 0) + { + size_t allocsize; + allocsize = GlobalSize (hClipboardData) + 1; + data = (char *)Z_Malloc (allocsize); + strlcpy (data, cliptext, allocsize); + GlobalUnlock (hClipboardData); + } + } + CloseClipboard (); + } + return data; +} + +void Sys_InitConsole (void) +{ +#ifdef QHOST + int t; + + // initialize the windows dedicated server console if needed + tevent = CreateEvent(NULL, false, false, NULL); + + if (!tevent) + Sys_Error ("Couldn't create event"); +#endif + + houtput = GetStdHandle (STD_OUTPUT_HANDLE); + hinput = GetStdHandle (STD_INPUT_HANDLE); + + // LordHavoc: can't check cls.state because it hasn't been initialized yet + // if (cls.state == ca_dedicated) + if (COM_CheckParm("-dedicated")) + { + //if ((houtput == 0) || (houtput == INVALID_HANDLE_VALUE)) // LordHavoc: on Windows XP this is never 0 or invalid, but hinput is invalid + { + if (!AllocConsole ()) + Sys_Error ("Couldn't create dedicated server console (error code %x)", (unsigned int)GetLastError()); + houtput = GetStdHandle (STD_OUTPUT_HANDLE); + hinput = GetStdHandle (STD_INPUT_HANDLE); + } + if ((houtput == 0) || (houtput == INVALID_HANDLE_VALUE)) + Sys_Error ("Couldn't create dedicated server console"); + + +#ifdef QHOST +#ifdef _WIN64 +#define atoi _atoi64 +#endif + // give QHOST a chance to hook into the console + if ((t = COM_CheckParm ("-HFILE")) > 0) + { + if (t < com_argc) + hFile = (HANDLE)atoi (com_argv[t+1]); + } + + if ((t = COM_CheckParm ("-HPARENT")) > 0) + { + if (t < com_argc) + heventParent = (HANDLE)atoi (com_argv[t+1]); + } + + if ((t = COM_CheckParm ("-HCHILD")) > 0) + { + if (t < com_argc) + heventChild = (HANDLE)atoi (com_argv[t+1]); + } + + InitConProc (hFile, heventParent, heventChild); +#endif + } + +// because sound is off until we become active + S_BlockSound (); +} + +/* +============================================================================== + +WINDOWS CRAP + +============================================================================== +*/ + + +/* +================== +WinMain +================== +*/ +HINSTANCE global_hInstance; +const char *argv[MAX_NUM_ARGVS]; +char program_name[MAX_OSPATH]; + +int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + MEMORYSTATUS lpBuffer; + + /* previous instances do not exist in Win32 */ + if (hPrevInstance) + return 0; + + global_hInstance = hInstance; + + lpBuffer.dwLength = sizeof(MEMORYSTATUS); + GlobalMemoryStatus (&lpBuffer); + + program_name[sizeof(program_name)-1] = 0; + GetModuleFileNameA(NULL, program_name, sizeof(program_name) - 1); + + com_argc = 1; + com_argv = argv; + argv[0] = program_name; + + // FIXME: this tokenizer is rather redundent, call a more general one + while (*lpCmdLine && (com_argc < MAX_NUM_ARGVS)) + { + while (*lpCmdLine && ISWHITESPACE(*lpCmdLine)) + lpCmdLine++; + + if (!*lpCmdLine) + break; + + if (*lpCmdLine == '\"') + { + // quoted string + lpCmdLine++; + argv[com_argc] = lpCmdLine; + com_argc++; + while (*lpCmdLine && (*lpCmdLine != '\"')) + lpCmdLine++; + } + else + { + // unquoted word + argv[com_argc] = lpCmdLine; + com_argc++; + while (*lpCmdLine && !ISWHITESPACE(*lpCmdLine)) + lpCmdLine++; + } + + if (*lpCmdLine) + { + *lpCmdLine = 0; + lpCmdLine++; + } + } + + Sys_ProvideSelfFD(); + + Host_Main(); + + /* return success of application */ + return true; +} + +#if 0 +// unused, this file is only used when building windows client and vid_wgl provides WinMain() instead +int main (int argc, const char* argv[]) +{ + MEMORYSTATUS lpBuffer; + + global_hInstance = GetModuleHandle (0); + + lpBuffer.dwLength = sizeof(MEMORYSTATUS); + GlobalMemoryStatus (&lpBuffer); + + program_name[sizeof(program_name)-1] = 0; + GetModuleFileNameA(NULL, program_name, sizeof(program_name) - 1); + + com_argc = argc; + com_argv = argv; + + Host_Main(); + + return true; +} +#endif + +qboolean sys_supportsdlgetticks = false; +unsigned int Sys_SDL_GetTicks (void) +{ + Sys_Error("Called Sys_SDL_GetTicks on non-SDL target"); + return 0; +} +void Sys_SDL_Delay (unsigned int milliseconds) +{ + Sys_Error("Called Sys_SDL_Delay on non-SDL target"); +} diff --git a/misc/source/darkplaces-src/thread.h b/misc/source/darkplaces-src/thread.h new file mode 100644 index 00000000..590da977 --- /dev/null +++ b/misc/source/darkplaces-src/thread.h @@ -0,0 +1,19 @@ +#ifndef THREAD_H + +int Thread_Init(void); +void Thread_Shutdown(void); +qboolean Thread_HasThreads(void); +void *Thread_CreateMutex(void); +void Thread_DestroyMutex(void *mutex); +int Thread_LockMutex(void *mutex); +int Thread_UnlockMutex(void *mutex); +void *Thread_CreateCond(void); +void Thread_DestroyCond(void *cond); +int Thread_CondSignal(void *cond); +int Thread_CondBroadcast(void *cond); +int Thread_CondWait(void *cond, void *mutex); +void *Thread_CreateThread(int (*fn)(void *), void *data); +int Thread_WaitThread(void *thread, int retval); + +#endif + diff --git a/misc/source/darkplaces-src/thread_null.c b/misc/source/darkplaces-src/thread_null.c new file mode 100644 index 00000000..5d788a49 --- /dev/null +++ b/misc/source/darkplaces-src/thread_null.c @@ -0,0 +1,70 @@ +#include "quakedef.h" +#include "thread.h" + +int Thread_Init(void) +{ + return 0; +} + +void Thread_Shutdown(void) +{ +} + +qboolean Thread_HasThreads(void) +{ + return false; +} + +void *Thread_CreateMutex(void) +{ + return NULL; +} + +void Thread_DestroyMutex(void *mutex) +{ +} + +int Thread_LockMutex(void *mutex) +{ + return -1; +} + +int Thread_UnlockMutex(void *mutex) +{ + return -1; +} + +void *Thread_CreateCond(void) +{ + return NULL; +} + +void Thread_DestroyCond(void *cond) +{ +} + +int Thread_CondSignal(void *cond) +{ + return -1; +} + +int Thread_CondBroadcast(void *cond) +{ + return -1; +} + +int Thread_CondWait(void *cond, void *mutex) +{ + return -1; +} + +void *Thread_CreateThread(int (*fn)(void *), void *data) +{ + return NULL; +} + +int Thread_WaitThread(void *thread, int retval) +{ + return retval; +} + diff --git a/misc/source/darkplaces-src/thread_pthread.c b/misc/source/darkplaces-src/thread_pthread.c new file mode 100644 index 00000000..d394b6db --- /dev/null +++ b/misc/source/darkplaces-src/thread_pthread.c @@ -0,0 +1,100 @@ +#include "quakedef.h" +#include "thread.h" +#include +#include + +int Thread_Init(void) +{ + return 0; +} + +void Thread_Shutdown(void) +{ +} + +qboolean Thread_HasThreads(void) +{ + return true; +} + +void *Thread_CreateMutex(void) +{ + pthread_mutex_t *mutexp = (pthread_mutex_t *) Z_Malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(mutexp, NULL); + return mutexp; +} + +void Thread_DestroyMutex(void *mutex) +{ + pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; + pthread_mutex_destroy(mutexp); + Z_Free(mutexp); +} + +int Thread_LockMutex(void *mutex) +{ + pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; + return pthread_mutex_lock(mutexp); +} + +int Thread_UnlockMutex(void *mutex) +{ + pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; + return pthread_mutex_unlock(mutexp); +} + +void *Thread_CreateCond(void) +{ + pthread_cond_t *condp = (pthread_cond_t *) Z_Malloc(sizeof(pthread_cond_t)); + pthread_cond_init(condp, NULL); + return condp; +} + +void Thread_DestroyCond(void *cond) +{ + pthread_cond_t *condp = (pthread_cond_t *) cond; + pthread_cond_destroy(condp); + Z_Free(condp); +} + +int Thread_CondSignal(void *cond) +{ + pthread_cond_t *condp = (pthread_cond_t *) cond; + return pthread_cond_signal(condp); +} + +int Thread_CondBroadcast(void *cond) +{ + pthread_cond_t *condp = (pthread_cond_t *) cond; + return pthread_cond_broadcast(condp); +} + +int Thread_CondWait(void *cond, void *mutex) +{ + pthread_cond_t *condp = (pthread_cond_t *) cond; + pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; + return pthread_cond_wait(condp, mutexp); +} + +void *Thread_CreateThread(int (*fn)(void *), void *data) +{ + pthread_t *threadp = (pthread_t *) Z_Malloc(sizeof(pthread_t)); + int r = pthread_create(threadp, NULL, (void * (*) (void *)) fn, data); + if(r) + { + Z_Free(threadp); + return NULL; + } + return threadp; +} + +int Thread_WaitThread(void *thread, int retval) +{ + pthread_t *threadp = (pthread_t *) thread; + void *status = (void *) (intptr_t) retval; + pthread_join(*threadp, &status); + Z_Free(threadp); + return (int) (intptr_t) status; +} + + diff --git a/misc/source/darkplaces-src/thread_sdl.c b/misc/source/darkplaces-src/thread_sdl.c new file mode 100644 index 00000000..5272221e --- /dev/null +++ b/misc/source/darkplaces-src/thread_sdl.c @@ -0,0 +1,77 @@ +#include "quakedef.h" +#include "thread.h" +#include +#include + +int Thread_Init(void) +{ + return 0; +} + +void Thread_Shutdown(void) +{ +} + +qboolean Thread_HasThreads(void) +{ + return true; +} + +void *Thread_CreateMutex(void) +{ + return SDL_CreateMutex(); +} + +void Thread_DestroyMutex(void *mutex) +{ + SDL_DestroyMutex((SDL_mutex *)mutex); +} + +int Thread_LockMutex(void *mutex) +{ + return SDL_LockMutex((SDL_mutex *)mutex); +} + +int Thread_UnlockMutex(void *mutex) +{ + return SDL_UnlockMutex((SDL_mutex *)mutex); +} + +void *Thread_CreateCond(void) +{ + return SDL_CreateCond(); +} + +void Thread_DestroyCond(void *cond) +{ + SDL_DestroyCond((SDL_cond *)cond); +} + +int Thread_CondSignal(void *cond) +{ + return SDL_CondSignal((SDL_cond *)cond); +} + +int Thread_CondBroadcast(void *cond) +{ + return SDL_CondBroadcast((SDL_cond *)cond); +} + +int Thread_CondWait(void *cond, void *mutex) +{ + return SDL_CondWait((SDL_cond *)cond, (SDL_mutex *)mutex); +} + +void *Thread_CreateThread(int (*fn)(void *), void *data) +{ + return SDL_CreateThread(fn, data); +} + +int Thread_WaitThread(void *thread, int retval) +{ + int status = retval; + SDL_WaitThread((SDL_Thread *)thread, &status); + return status; +} + + diff --git a/misc/source/darkplaces-src/thread_win.c b/misc/source/darkplaces-src/thread_win.c new file mode 100644 index 00000000..a55e6b4c --- /dev/null +++ b/misc/source/darkplaces-src/thread_win.c @@ -0,0 +1,211 @@ +#include "quakedef.h" +#include "thread.h" +#include + +int Thread_Init(void) +{ + return 0; +} + +void Thread_Shutdown(void) +{ +} + +qboolean Thread_HasThreads(void) +{ + return true; +} + +void *Thread_CreateMutex(void) +{ + return (void *)CreateMutex(NULL, FALSE, NULL); +} + +void Thread_DestroyMutex(void *mutex) +{ + CloseHandle(mutex); +} + +int Thread_LockMutex(void *mutex) +{ + return (WaitForSingleObject(mutex, INFINITE) == WAIT_FAILED) ? -1 : 0; +} + +int Thread_UnlockMutex(void *mutex) +{ + return (ReleaseMutex(mutex) == FALSE) ? -1 : 0; +} + +typedef struct thread_semaphore_s +{ + HANDLE semaphore; + volatile LONG value; +} +thread_semaphore_t; + +static thread_semaphore_t *Thread_CreateSemaphore(unsigned int v) +{ + thread_semaphore_t *s = (thread_semaphore_t *)calloc(sizeof(*s), 1); + s->semaphore = CreateSemaphore(NULL, v, 32768, NULL); + s->value = v; + return s; +} + +static void Thread_DestroySemaphore(thread_semaphore_t *s) +{ + CloseHandle(s->semaphore); + free(s); +} + +static int Thread_WaitSemaphore(thread_semaphore_t *s, unsigned int msec) +{ + int r = WaitForSingleObject(s->semaphore, msec); + if (r == WAIT_OBJECT_0) + { + InterlockedDecrement(&s->value); + return 0; + } + if (r == WAIT_TIMEOUT) + return 1; + return -1; +} + +static int Thread_PostSemaphore(thread_semaphore_t *s) +{ + InterlockedIncrement(&s->value); + if (ReleaseSemaphore(s->semaphore, 1, NULL)) + return 0; + InterlockedDecrement(&s->value); + return -1; +} + +typedef struct thread_cond_s +{ + HANDLE mutex; + int waiting; + int signals; + thread_semaphore_t *sem; + thread_semaphore_t *done; +} +thread_cond_t; + +void *Thread_CreateCond(void) +{ + thread_cond_t *c = (thread_cond_t *)calloc(sizeof(*c), 1); + c->mutex = CreateMutex(NULL, FALSE, NULL); + c->sem = Thread_CreateSemaphore(0); + c->done = Thread_CreateSemaphore(0); + c->waiting = 0; + c->signals = 0; + return c; +} + +void Thread_DestroyCond(void *cond) +{ + thread_cond_t *c = (thread_cond_t *)cond; + Thread_DestroySemaphore(c->sem); + Thread_DestroySemaphore(c->done); + CloseHandle(c->mutex); +} + +int Thread_CondSignal(void *cond) +{ + thread_cond_t *c = (thread_cond_t *)cond; + int n; + WaitForSingleObject(c->mutex, INFINITE); + n = c->waiting - c->signals; + if (n > 0) + { + c->signals++; + Thread_PostSemaphore(c->sem); + } + ReleaseMutex(c->mutex); + if (n > 0) + Thread_WaitSemaphore(c->done, INFINITE); + return 0; +} + +int Thread_CondBroadcast(void *cond) +{ + thread_cond_t *c = (thread_cond_t *)cond; + int i = 0; + int n = 0; + WaitForSingleObject(c->mutex, INFINITE); + n = c->waiting - c->signals; + if (n > 0) + { + c->signals += n; + for (i = 0;i < n;i++) + Thread_PostSemaphore(c->sem); + } + ReleaseMutex(c->mutex); + for (i = 0;i < n;i++) + Thread_WaitSemaphore(c->done, INFINITE); + return 0; +} + +int Thread_CondWait(void *cond, void *mutex) +{ + thread_cond_t *c = (thread_cond_t *)cond; + int waitresult; + + WaitForSingleObject(c->mutex, INFINITE); + c->waiting++; + ReleaseMutex(c->mutex); + + ReleaseMutex(mutex); + + waitresult = Thread_WaitSemaphore(c->sem, INFINITE); + WaitForSingleObject(c->mutex, INFINITE); + if (c->signals > 0) + { + if (waitresult > 0) + Thread_WaitSemaphore(c->sem, INFINITE); + Thread_PostSemaphore(c->done); + c->signals--; + } + c->waiting--; + ReleaseMutex(c->mutex); + + WaitForSingleObject(mutex, INFINITE); + return waitresult; +} + +typedef struct threadwrapper_s +{ + HANDLE handle; + unsigned int threadid; + int result; + int (*fn)(void *); + void *data; +} +threadwrapper_t; + +unsigned int __stdcall Thread_WrapperFunc(void *d) +{ + threadwrapper_t *w = (threadwrapper_t *)d; + w->result = w->fn(w->data); + _endthreadex(w->result); + return w->result; +} + +void *Thread_CreateThread(int (*fn)(void *), void *data) +{ + threadwrapper_t *w = (threadwrapper_t *)calloc(sizeof(*w), 1); + w->fn = fn; + w->data = data; + w->threadid = 0; + w->result = 0; + w->handle = (HANDLE)_beginthreadex(NULL, 0, Thread_WrapperFunc, (void *)w, 0, &w->threadid); + return (void *)w; +} + +int Thread_WaitThread(void *d, int retval) +{ + threadwrapper_t *w = (threadwrapper_t *)d; + WaitForSingleObject(w->handle, INFINITE); + CloseHandle(w->handle); + retval = w->result; + free(w); + return retval; +} diff --git a/misc/source/darkplaces-src/timing.h b/misc/source/darkplaces-src/timing.h new file mode 100644 index 00000000..310f4e8f --- /dev/null +++ b/misc/source/darkplaces-src/timing.h @@ -0,0 +1,58 @@ +/* +Simple helper macros to time blocks or statements. + +Copyright (C) 2007 Frank Richter + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __TIMING_H__ +#define __TIMING_H__ + +#if defined(DO_TIMING) + +#define TIMING_BEGIN double _timing_end_, _timing_start_ = Sys_DoubleTime(); +#define TIMING_END_STR(S) \ + _timing_end_ = Sys_DoubleTime(); \ + Con_Printf ("%s: %.3g s\n", S, _timing_end_ - _timing_start_); +#define TIMING_END TIMING_END_STR(__FUNCTION__) + +#define TIMING_INTERMEDIATE(S) \ + { \ + double currentTime = Sys_DoubleTime(); \ + Con_Printf ("%s: %.3g s\n", S, currentTime - _timing_start_); \ + } + +#define TIMING_TIMESTATEMENT(Stmt) \ + { \ + TIMING_BEGIN \ + Stmt; \ + TIMING_END_STR(#Stmt); \ + } + +#else + +#define TIMING_BEGIN +#define TIMING_END_STR(S) +#define TIMING_END +#define TIMING_INTERMEDIATE(S) +#define TIMING_TIMESTATEMENT(Stmt) Stmt + +#endif + +#endif // __TIMING_H__ + diff --git a/misc/source/darkplaces-src/todo b/misc/source/darkplaces-src/todo new file mode 100644 index 00000000..adfa4ebe --- /dev/null +++ b/misc/source/darkplaces-src/todo @@ -0,0 +1,1440 @@ +- todo: difficulty ratings are: 0 = trivial, 1 = easy, 2 = easy-moderate, 3 = moderate, 4 = moderate-hard, 5 = hard, 6 = hard++, 7 = nightmare, d = done, -d = done but have not notified the people who asked for it, f = failed, -f = failed but have not notified the people who asked for it +-d bug darkplaces d3d9: drawsetcliparea not working right - seems to be Y-flipped, this also affects menus in Steelstorm (VorteX) +-d bug darkplaces d3d9: overbright particles get weird colors (VorteX) +0 bug darkplaces client csqc: CSQC_InputEvent is supposed to handle mouse movement, compare to FTEQW code (avirox) +0 bug darkplaces client csqc: engine prediction function is not implemented - could just return the engine's current cl.movement_origin (Spike) +0 bug darkplaces client csqc: entities not being drawn with VF_PERSPECTIVE 0? (daemon) +0 bug darkplaces client csqc: input queue functions needed for csqc prediction aren't implemented (Spike) +0 bug darkplaces client csqc: precaches on client don't work, have to precache on server - what's going wrong here? (daemon, Urre) +0 bug darkplaces client csqc: string stats should be sent as a single stat with WriteString (Spike) +0 bug darkplaces client csqc: there is no WriteFloat, making ReadFloat useless (Urre) +0 bug darkplaces client csqc: unproject Z handling differs in DP and FTEQW, project also must be directly compatible with unproject (avirox) +0 bug darkplaces client csqc: world not being drawn with VF_PERSPECTIVE 0? (VorteX) +0 bug darkplaces client net: proquake server compatibility broke between the last build of 2008 and the first of 2009 (Tomas Vredeveld) +0 bug darkplaces client qc: trace_dphittexturename should be a static buffer rather than a tempstring, it produces unnecessary warnings currently (div0) +0 bug darkplaces client qw: add .mvd demo support +0 bug darkplaces client qw: add .qwd demo support +0 bug darkplaces client qw: add spectator cvar (Plague Monkey Rat) +0 bug darkplaces client qw: add spectator prediction +0 bug darkplaces client qw: inactive player entities are showing up at 0 0 0 (Plague Monkey Rat) +0 bug darkplaces client qw: qw skins should only be active on progs/player.mdl (Plague Monkey Rat) +0 bug darkplaces client qw: qw/skins/*.pcx need to be cropped to 296x194 and loaded as internal textures so they are split into multiple layers (Plague Monkey Rat) +0 bug darkplaces client qw: restrict wateralpha and such cvars according to what is permitted in qw serverinfo? +0 bug darkplaces client sound: -nocdaudio disables emulated music tracks (Tomas Vredeveld) +0 bug darkplaces client timedemo: investigate bogus results of one-second min/avg/max, clearly not working properly (Willis) +0 bug darkplaces client win32: add some kind of workaround for Windows Firewall prompt killing the OpenGL context (motorsep) +0 bug darkplaces client: can't move mouse around in nexuiz menu if vid_mouse is 0 +0 bug darkplaces client: if you press 1 during the demo loop when quake starts, escape doesn't do anything until you hit some other key (daemon) +0 bug darkplaces d3d9: alphatest not working (VorteX) +0 bug darkplaces d3d9: gamma not working (VorteX) +0 bug darkplaces d3d9: shadowmaps are clipped - does this mean r_shadows 2 or light shadowmaps? (VorteX) +0 bug darkplaces d3d9: water/refraction/reflection/warp render scissor is wrong (SavageX) +0 bug darkplaces effects: add a cvar to disable colored dlights (leileilol) +0 bug darkplaces effects: dlights don't look anything like quake ones, bring back lightmap dlights as a cvar - with both classic and old dp falloff modes (leileilol) +0 bug darkplaces effects: quake mode blood trails don't have the appropriate slight gravity - there may be other effects missing this also (leileilol) +0 bug darkplaces filesystem: -game nehahra does not load properly; menu does not work properly - probably not activating gamemode +0 bug darkplaces loader: crash when a mdl model has more replacement skins than the model contains (Lardarse) +0 bug darkplaces loader: make rtlight entity loader support q3map/q3map2 lights properly, they use a spawnflag for LINEAR mode, by default they use 1/(x*x) falloff (Carni, motorsep) +0 bug darkplaces loader: mcbsp hull selection is ignoring the custom hulls supported by mcbsp (div0) +0 bug darkplaces loader: png loading crashes if the image is transparent (Urre) +0 bug darkplaces loader: q1bsp loader computes wrong submodel size for submodels with no surfaces, such as a func_wall comprised entirely of SKIP or CAULK brushes (neg|ke) +0 bug darkplaces memory: memstats doesn't account for memory used by VBO/EBO buffers in models +0 bug darkplaces mobile: add a command to lock in current accelerometer axes as default orientation (calibrate), which the menuqc/csqc can call +0 bug darkplaces mobile: add a landscape mode for mobile devices where width is > height, which changes 2D and 3D rendering orientation +0 bug darkplaces qw: tf skins not working (xavior) +0 bug darkplaces readme: it would be a very good idea to add documentation of sv_gameplayfix_* cvars in the readme as a means to run broken mods (xaGe) +0 bug darkplaces readme: readme says that q3 shaders are not supported, this is not true, describe the working features in detail (qqshka) +0 bug darkplaces renderer deferred: scissoring artifacts are very obvious on shadowless lights, turn off scissor (Rock) +0 bug darkplaces renderer: GL13 path has broken handling of unlit surfaces in Nexuiz toxic.bsp - the small red light surfaces are black in GL13 path (m0rfar) +0 bug darkplaces renderer: coronas are not affected by fog (div0) +0 bug darkplaces renderer: if an animated model has transparent surfaces, each one calls RSurf_ActiveModelEntity, recomputing all vertices +0 bug darkplaces renderer: if an animated model is entirely transparent, the RSurf_ActiveModelEntity call updating vertices is completely wasted +0 bug darkplaces renderer: r_speeds counts entities twice with r_hdr 1, the whole counting code probably needs auditing (Morphed, and jim on inside3d) +0 bug darkplaces renderer: rtworld seems to mess up player pants/shirt colors +0 bug darkplaces renderer: sprites with ! in the filename are supposed to receive lighting (Asaki) +0 bug darkplaces server csqc networking: csqc entity sending code does not currently detect packet loss and repeat lost entities (FrikaC, Chris Page, div0) +0 bug darkplaces server qc: trace_dphittexturename should be a static buffer rather than a tempstring, it produces unnecessary warnings currently (div0) +0 bug darkplaces server: SV_PushMove is ignoring model type in its angles_x handling, where as the renderer checks only model type to determine angles_x handling (Urre) +0 bug darkplaces server: SV_PushMove's call to SV_ClipMoveToEntity should do a trace, not just a point test, to support hollow pusher models (Urre) +0 bug darkplaces server: X-Men mod has flying enemies that tend to go through the floor, sometimes preventing a level from being completed. x1m1 has two alcoves that open to reveal Storm and Archangel but often Storm disappears through the ground of hear alcove and then the level can't be completed. A similar problem with Storm apears in x1m3 in an alcove. (ewwfq yahoo com) +0 bug darkplaces server: savegames do not contain parms for multiple players, restoring a savegame leaves parms for other players as they were at the time of loading the savegame, clearly wrong (daemon) +0 bug darkplaces server: savegames do not save precaches, which means that automatic precaching frequently results in invalid modelindex values when reloading the savegame, and this bug also exists in many quake mods that randomly choose multiple variants of a monster, each with separate precaches, resulting in a different precache order when reloading the savegame +0 bug darkplaces wgl client: during video mode setup, sometimes another application's window becomes permanently top most, not darkplaces' fullscreen window, why? (tZork) +0 bug darkplaces windows sound: freezing on exit sometimes when freeing sound buffer during sound shutdown (Black) +0 bug darkplaces windows sound: surround sound fails in windows client, falls back to stereo (greenmarine) +0 bug dpmaster: if server does not reply to queries in a reasonable period of time it should be removed from the list, this is necessary for the server to be removed when it shuts down and sends out two heartbeats to let the master know that it is shutting down, and isn't there to reply to queries (jitspoe, div0) +0 bug dpmaster: if server does not reply within 5 seconds to a query it should be removed (jitspoe, div0) +0 bug dpmod: LinkDoors seems to ignore .origin on door entities when comparing if they overlap +0 bug dpmod: allow selection of weapons with secondary ammo but no primary ammo, and switch away if trying to fire primary ammo you don't have (romi) +0 bug dpmod: chthon stops attacking in coop if shot enough +0 bug dpmod: crash when dog attacks you in dpdm2 deathmatch 7 with bots present (Zombie13) +0 bug dpmod: figure out what's wrong with the bots +0 bug dpmod: go through http://www.inside3d.com/qip/q1/bugsmap.htm and fix map bugs by using replacement .ent files (Lardarse) +0 bug dpmod: identify what could cause huge angles values (1187488512.0000) on a dog entity, may be related to anglemod, FacingIdeal, ai_run, or dog_run2 (Zombie13) +0 bug dpmod: impulse 102 isn't removing the bots +0 bug dpmod: in dpmod_qcphysics_casings the two main particles have the same mass, realistically speaking the rear one should have more mass than the front one +0 bug dpmod: it is (rarely) possible to die and be unable to respawn +0 bug dpmod: monsters don't properly chase you around corners, this is not an engine bug because they work fine in id1 but not dpmod, the knight at the start of e2m2 makes for good testing by shooting him and then hiding behind the crates (StrangerThanYou) +0 bug dpmod: monsters falling out of level? +0 bug dpmod: monsters shouldn't constantly do sightsounds on a slain player, it's annoying and silly +0 bug dpmod: monsters that spawn underwater shouldn't drown, just because it somewhat breaks the intended behavior of maps +0 bug dpmod: test for any unnamed death messages that might be happening +0 bug dpmod: the id1 map e2m4 has a bug where 4 scrags don't spawn in normal/hard skills because of a mislinked trigger, the Quake Done Quick team figured this out and a trigger_relay with target "t58" should be changed to target "t66" (Lardarse) +0 bug hmap2 -qbsp: errors still occur in mrinsane's newmap.map file, tyrqbsp does not have these problems (mrinsane) +0 bug hmap2 -qbsp: portal clipped away errors and a crash occur in vaccui (Epicenter) +0 bug hmap2 -qbsp: various errors still occur for Predator's Transfusion maps (Predator) +0 bug hmap2 -vis: test that bitlongs code works properly on big endian systems such as Mac (LordHavoc) +0 bug hmap2: figure out why there is a subtle difference between bmodel lighting and wall lighting, something to do with nudges causing different attenuation? (Urre) +0 bug hmap2: handle \" properly in hmap2 cmdlib.c COM_Parse (sort) +0 bug hmap2: the plane-distance based projection size for polygons doesn't work in certain wedge cases where two sides of a wedge brush are very near origin but the entirety of the wedge brush is much larger +0 bug hmap: strip .map extension from filename if present +0 change csqc client: add float in_mouse_centered which indicates the engine should lock the mouse at the center of the window, only gathering relative motion (in_mouse_window_position stops being modified - it is not set to the center as that would be useless) +0 change csqc client: add float in_mouse_keep_in_window which indicates whether engine should be currently restricting the mouse pointer to the window (useful in point and click games such as RTS games) +0 change csqc client: add float in_mouse_show_system_cursor which indicates whether the system mouse pointer should be visible while over the window +0 change csqc client: add svc_updatestatstring, svc_updatestatfloat, and keep 3 cl.stats arrays, of int, float, and string types respectively, store svc_updatestatstring in the string one, store all others in both int and float formats (KrimZon, Chris, Spike) +0 change csqc client: add vector in_mouse_motion which indicates mouse movement since previous frame (even if in_mouse_window_position did not change, such as when in_mouse_centered is on) +0 change csqc client: add vector in_mouse_window_position which indicates cursor position over the game window (real position of mouse in the window) - not updated if in_mouse_centered is true +0 change csqc client: replace input_buttons with in_button0 and similar (matching server qc fields), auto-detect these at load +0 change csqc server: change SendEntity to take the client as "other" rather than a parameter +0 change csqc server: implement packet logging of csqc entities to deal with packet loss +0 change csqc server: replace AddStat with RegisterStat and RegisterStatString, make them send float or string only, with string taking up only one stat slot and being sent using svc_updatestatstring, make it automatically send integer values as svc_updatestat/svc_updatestatubyte and non-integer values as svc_updatestatfloat (KrimZon, Chris, Spike) +0 change csqc server: replace per-entity SendEntity function with a global SV_SendEntity function, because it does not make it sufficiently clear to modders that the .SendEntity function has no special meaning, that an ENT_ constant must be set up, and so on, it would be more clear if there was only a single global SV_SendEntity function, which can still call out to a .SendEntity or whatever if it wishes, but is more likely to do an if else on a .net_type field or similar (Urre) +0 change darkplaces client: disable all possible 'cheat' things unless -developer is given on commandline, this includes r_show*, r_test, gl_lightmaps, r_fullbright, and would require changing -developer to only set developer to 1 rather than 100, as 100 is too annoying +0 change darkplaces client: modify cl_particles_quake to make all the engine dlights be white and look as much like quake as possible (Jago) +0 change darkplaces client: particles shouldn't be using contents checks to decide whether to die, they should use movement traces +0 change darkplaces client: turn off coronas on dlights (Jago) +0 change darkplaces extensions: edit FRIK_FILE documentation to mention that fgets uses its own separate buffer, so only one fgets can be done at a time without uzing strzone, but that this is not true with DP_QC_UNLIMITEDTEMPSTRINGS (FrikaC) +0 change darkplaces extensions: edit FRIK_FILE documentation to mention that strcat uses its own separate buffer, and that a = strcat(a, b);a = strcat(a, c); works correctly despite this, but that strcat is far more flexible with DP_QC_UNLIMITEDTEMPSTRINGS (FrikaC) +0 change darkplaces general: make r_speeds 2 show timings for other subsystems such as client, sound, server, network (Carni) +0 change darkplaces loader: load *lava and *teleport and *rift textures as a black diffuse texture with all the color put into a glow layer, and remove the MATERIALFLAG_FULLBRIGHT accordingly, this way replacement textures can make it not glow (Kedhrin) +0 change darkplaces memory: optimize model loaders to use less individual allocations, especially the q1bsp loader, it is responsible for a lot of very small (1-8 byte) allocations in the memlist all report +0 change darkplaces menu: move all options into a submenu so that people won't keep ignoring the other submenus +0 change darkplaces networking: make darkplaces detect its *public* client port from master server and send that in nq connect messages (wallace) +0 change darkplaces protocol: PRYDON_CLIENTCURSOR should use a stat and .prydoncursor field instead of the cl_prydoncursor cvar, because stuffcmd is a bit icky (FrikaC) +0 change darkplaces protocol: in dp8 protocol change clc_move to include a separate want-prediction flag rather than sending 0 movesequence for unpredicted moves, this will allow the client to know its own ping regardless of whether prediction is used. +0 change darkplaces protocol: increase maxplayers limit to 65535, send maxplayers as an int in the serverinfo packet, send colormap indices as a short, send svc_update* using short player indices (discoloda) +0 change darkplaces protocol: make svc_cdtrack use strings rather than a numbers in next DP protocol, so that named tracks can be used by maps +0 change darkplaces protocol: send origin and angle updates as individual components in entities like Quake did, rather than combined ORIGIN/ANGLE bits like DP5/6/7 do +0 change darkplaces protocol: use q3 print "print message" command packet instead of qw print 'nmessage' command packet? (div0, KadaverJack) +0 change darkplaces prvm: prvm_globals should print values of globals like entity fields do, not just names +0 change darkplaces readme: make sure that cl_capturevideo is documented +0 change darkplaces renderer: add a polygonoffset for rtlight passes to try to work around zfighting issues on mac, this would also change the depthfunc to LEQUAL rather than EQUAL (div0) +0 change darkplaces renderer: rename r_drawportals to r_showportals, and move its declaration to gl_rmain.c, and make it work with r_showdisabledepthtest +0 change darkplaces server: make "viewmodel" command code precache a new model and set it, rather than changing the meaning of the player model +0 change darkplaces server: support sys_ticrate 0 as variable framerate mode +0 change darkplaces: make vid_mouse 0 mouse movement work with menu.dat +0 change dpmod: add a ghostly cvar and change skill 5 code to check it +0 change dpmod: make shamblers be worth 5 frags, shalrath be worth 3 frags, and fiends be worth 2 frags (Zenex) +0 change dpmod: stop using playernogun/playergun models, go back to ordinary player.mdl, this saves a bit of memory +0 change hmap2: qbsp should do tjunc fixing on leaky maps +0 change revelation: change the wabbit kill message to " was hunting wabbit but shot " " instead" +0 change zmodel: include the example script in the build zips, not just in the files directory +0 feature darkplaces client csqc: DP_CSQC_SOUNDLENGTH extension which defines a builtin float(float f) soundlength = #??; which returns the sound length in seconds, or 0 if the sound is not loaded, or the engine was started with -nosound (ChrisP) +0 feature darkplaces client particles: effectinfo.txt should have a "particlefont" command specifying a filename, number of cells per row, number of rows, number of bottom rows that are beams, this would allow more particle images to be used (ChrisP) +0 feature darkplaces client rtlights: ChrisP has a suggestion of selecting rtlight properties using the number keys, and increasing/decreasing their value using the mouse wheel (cvars needed for amounts to adjust by), and ability to right click a light to delete it, left click to select a light, left drag to move a light on the XY plane it is on (ChrisP) +0 feature darkplaces client text: support for font rasterizing at pixel-accurate sizes and caching them to disk, support freetype2 for this purpose if it is present (ChrisP) +0 feature darkplaces client: add DP_GFX_EFFECTINFO_TXT to extensions and document it, the feature has been in for a long time, also update wiki.quakesrc.org accordingly +0 feature darkplaces client: add a cl_showspeed cvar to display a hud overlay of your current velocity, speed as length of velocity, and speed along forward vector (Spike) +0 feature darkplaces client: add a cvar to make the renderer use a different entity for pvs than for viewing, this might be useful for a third person camera that should only see what the player sees (Urre) +0 feature darkplaces client: add an alias to control whether the nexuiz logo.dpv splash video plays at startup and whether the menu opens, this way the config can change it before it happens, for instance to disable it, or to set up a similar thing in other games (green) +0 feature darkplaces client: add cvar r_fonttexturenearest to force font textures to be GL_NEAREST sampled and disable the squished-character boxes, although that should be a different cvar (leilei) +0 feature darkplaces client: add ezQuake-style hud layout as an option - http://quake.tek.pl/_sshots/candy/redfog.png +0 feature darkplaces client: add test and test2 commands, test queries a server and prints player list, (Yellow No. 5) +0 feature darkplaces client: somehow handle alt-enter on windows, and any other equivilant toggle-maximize shortcut on other platforms (Spirit) +0 feature darkplaces console: centerprint and notify text printing should not count color codes in their length estimation (Dresk) +0 feature darkplaces dpv playback: when video ends, execute a console command, perhaps "endvideo", this could be an alias set by a mod before it began playing the video (SavageX, motorsep) +0 feature darkplaces editlights: add r_editlights_edit commands for turn/turnx/turny/turnz to allow angle adjustments using binds (Kedhrin) +0 feature darkplaces editlights: split rtlight drawshadows option into drawworldshadows and drawentityshadows options, this allows combinations like no world shadows (for speed) but still having entity shadows (Mitchell, romi, Kedhrin) +0 feature darkplaces loader: add hud_clearprecache and hud_precachepic commands to preload pics by name, these get reloaded by r_restart as well, mods can put a lot of these commands in their default.cfg to precache needed hud art (Tomaz) +0 feature darkplaces loader: load .vis files produced by hmap2 +0 feature darkplaces menu: add r_shadow_glsl_offsetmapping cvars to menu (Kedhrin) +0 feature darkplaces menu: map selection menu for singleplayer +0 feature darkplaces networking: download updated csprogs.dat if crc mismatches (div0, Dresk, Chris) +0 feature darkplaces protocol: add DP_GFX_QUAKE3MODELTAGS_ATTACHMENTTYPE extension to allow attachments to affect only origin or only orientation, and enable/disable client camera turning while attached (Urre) +0 feature darkplaces protocol: add DP_SENSITIVITYSCALE extension which scales sensitivity on client like viewzoom does, but without affecting fov, note if this is non-zero it overrides viewzoom sensitivity entirely, it does not scale it (Urre) +0 feature darkplaces protocol: add DP_TE_BUBBLES to make a burst of bubbles underwater (Supa, shadowalker) +0 feature darkplaces protocol: add DP_WEAPONSKIN extension which would add a .float weaponskin field (protoplasmatic, Kazashi) +0 feature darkplaces protocol: add EF_NOLERP effect for mods to use to prevent interpolation across teleports (Kinn, CuBe0wL) +0 feature darkplaces protocol: add TE_LASER effect with values Entity owner Vector start Vector end Byte palettecolor Byte startalpha Byte endalpha Byte beam width (in quake units) Byte lifetime (0-255 for 0-1 seconds), skin index (looking up a gfx/particles/beam%03i.tga), it fades from startalpha to endalpha over time (Wazat, Vermeulen) +0 feature darkplaces protocol: add a .fatness field which enlarges models along their vertex normals, this won't work on bsp models or sprites, label this as FTE_PEXT_FATNESS in the extensions string (Dresk) +0 feature darkplaces protocol: add a .scalexyz field which would allow non-uniform scaling of models, and may requires upgrading some lighting code but I am not sure (Dresk) +0 feature darkplaces protocol: add a new TE_ explosion effect with RGB color choice for particle color and another choice for light color (VorteX) +0 feature darkplaces protocol: add an EF_BULLETTRACER trail effect which can be used to make an invisible entity into a tracer, a bright streak leaving a faint smoke trail (Carni) +0 feature darkplaces protocol: add lava-steam particle puff effect for bursting lava bubbles (Zombie) +0 feature darkplaces protocol: add support for .float corona and corona_radius to control corona intensity and radius on dlights +0 feature darkplaces prvm: if developer is >= 100, list unfreed strzone strings when level ends (div0) +0 feature darkplaces prvm: modify PRVM_ExecuteProgram to be able to call builtins directly, and add prog->argc = before every PRVM_ExecuteProgram call (div0) +0 feature darkplaces qc: add FTE_STRINGS extension, and specifically support -1 for strncmp/strncasecmp to compare whole string (div0) +0 feature darkplaces qc: add support for ZQ_QC_TOKENIZE so that csqc can use tokenize too (KrimZon) +0 feature darkplaces quakec: DP_QC_STRFTIME extension providing strftime function to find out what the current time is with a format string (FrikaC) +0 feature darkplaces quakec: add a DP_QC_STRCATREPEAT extension providing string(float atimes, string a[, float btimes, string b, [float ctimes, string c, [float dtimes, string d]]]) strcatrepeat = #???; which repeats the given strings a given number of times and concatenates them together (like many strcat calls), can be given 2, 4, 6, or 8 parameters, stores it into a temp buffer, and returns the temp buffer (FA-Zalon) +0 feature darkplaces quakec: add crossproduct builtin, as DP_QC_CROSSPRODUCT, and suggest that this is a viable alternative to vectorvectors when you have two vectors already +0 feature darkplaces readme: add documentation about r_lockpvs, r_lockvisibility, r_useportalculling, r_drawportals, r_drawcollisionbrushes, r_showtris, r_speeds, r_shadow_visiblevolumes, and r_shadow_visiblelighting. +0 feature darkplaces readme: add log_file and log_sync documentation (Edward Holness) +0 feature darkplaces readme: document the ctrl-escape hotkey for toggleconsole (LordHavoc) +0 feature darkplaces renderer: add a rtlight flag to disable vis culling, for ambient area lights and such (Kaz) +0 feature darkplaces renderer: add a water caustics lightstyle for use on rtlights and to someday be supported on multi-pass lightmapped rendering (skite2001) +0 feature darkplaces renderer: add cubemap support to low quality rtlighting path for cards that support >= 2 TMUs and cubemap +0 feature darkplaces renderer: add optional filename parameter to screenshot command (Chris) +0 feature darkplaces renderer: add per-entity PolygonOffset to renderer, to allow zfighting bmodel/world glitches to be fixed, this has to affect all rendering involving the entity, including light/shadow (Tomaz) +0 feature darkplaces renderer: add procedural ripple distortion texture of some sort for use with envmap reflections (FrikaC) +0 feature darkplaces renderer: add r_shadow_light_polygonoffset and polygonfactor cvars for lighting polygons (Diablo-D3) +0 feature darkplaces renderer: add r_showsurfaces cvar which adds an extra texture layer of additive flat color, based on the surface number, similar to r_drawflat in software quake (div0) +0 feature darkplaces renderer: add rtlight "avelocity" parameter to make lights that spin, useful with cubemaps (romi) +0 feature darkplaces renderer: document dp_water, dp_reflect, dp_refract .shader commands, add extension indicating this feature and document it in dpextensions.qc (leileilol) +0 feature darkplaces renderer: gl_picmip -1 and -2 settings based on twilight code (Harout Darmanchyan) +0 feature darkplaces renderer: make showfps display GL renderer string and CPU - figure out how to detect this from the OS +0 feature darkplaces renderer: save r_shadow_glsl* cvars (and possibly a few others) to config because they are useful user settings (SavageX) +0 feature darkplaces renderer: support tcgen in q3 shaders (ob3lisk) +0 feature darkplaces server: DP_SV_FINDPVS +0 feature darkplaces server: add .maxspeed field to control player movement speed in engine code, call it QW_SV_MAXSPEED (Carni) +0 feature darkplaces server: add DP_QC_STRTOKEN extension with these functions: float strtokens(string s, string separator) = #;string strtoken(string s, string separator, float index) = #; (FrikaC) +0 feature darkplaces server: add DP_SV_DRAWONLYTOTEAM extension (Supajoe) +0 feature darkplaces server: add DP_SV_TRAIL_EFFECT extension - .float trail_effect; field which can be networked as an automatic trailparticles() associated with an entity +0 feature darkplaces server: add PF_tokenizeseparator function and DP_QC_TOKENIZESEPARATOR extension +0 feature darkplaces server: add a .collision_cancollide QC function call to decide if an entity should collide with another, or pass through it (Uffe) +0 feature darkplaces server: add a DP_QC_WARNING extension which has a "warning" builtin that does a PF_WARNING just to print the requested message, opcode dump, and stack trace (FrikaC) +0 feature darkplaces server: add a clipmask thingy to allow QC to mask off collisions as it wishes (Uffe) +0 feature darkplaces server: add a different progs.dat defs for darkplaces-based games to use instead of the quake defs, which would force use of csqc entity networking and disable legacy stuff like quake physics, and enable use of ODE physics perhaps +0 feature darkplaces server: add a sv_gameplayfix_slidewhenstandingonmonster cvar to allow the FL_ONGROUND when ontop of a SOLID_BBOX/SOLID_SLIDEBOX to be disabled +0 feature darkplaces server: add a sv_netticrate cvar which allows less frequent network updates (Urre) +0 feature darkplaces server: add back edict, edicts and edictset commands (just as stubs that call the prvm_edict/edicts/edictset server commands) for convenience and compatibility with quake modding practices +0 feature darkplaces server: add cl_prydoncursor_centeredcursor cvar and PRYDON_CLIENTCURSOR_CENTEREDCURSOR extension (Wazat) +0 feature darkplaces server: add sv_antilag cvar which would upgrade the aim() builtin to aim at the creature the player's prydon cursor trace was hitting (Spike) +0 feature darkplaces server: float(vector viewpos, entity viewee) checkpvs = #240; //(FTE_QC_CHECKPVS) (Urre, Spike) +0 feature darkplaces server: make SV_PushMove check .owner on pusher and pushee to ignore related entities, needed for Urre's stick physics code, make sure this is cvar controlled (Urre) +0 feature darkplaces server: make fopen builtin have the ability to disable fopen builtin access to read /, read data/, write data/, or disable fopen builtin entirely +0 feature darkplaces server: make noclip/fly cheats use MOVETYPE_CHEATNOCLIP/MOVETYPE_CHEATFLY which would have the nicer movement interface (Spikester) +0 feature darkplaces server: when "exec config.cfg" is encountered, add on an automatic "exec server.cfg" if running a dedicated server (mortenoesterlundjoer) +0 feature darkplaces sound: Lordhavoc needs to talk to fuh about snd_macos.c (fuh) +0 feature darkplaces sound: add extension info for mod music playback (leileilol) +0 feature darkplaces sound: the new sound engine should have a cvar for random variations of pitch on sounds like in doom (RenegadeC) +0 feature darkplaces website: add download link for deluxemaps_id1.pk3 +0 feature darkplaces website: add q1source.zip to downloads page and suggest that mingw/Dev-C++ users may need the dx headers from it (cpuforbrain) +0 feature darkplaces windows: include P3, P4, Xeon, AthlonXP, and Athlon64-32bit optimized windows dedicated server binaries for windows server admins to use, http://www.alientrap.org/forum/viewtopic.php?t=522 (PoWaZ) +0 feature darkplaces: .vis files - like .lit but replacement vis data, note this also requires .leaf files (knghtbrd) +0 feature darkplaces: add DP_GFX_EFFECTINFO extension to indicate that effectinfo.txt is supported, and thoroughly document its syntax (Morphed) +0 feature darkplaces: add a progress bar to the loading screen, this would be updated by the model/sound loading process +0 feature dpmaster: add back qw and q2 server support, with records not matching for different protocols to prevent qw/q2 protocols being used to poison q3 protocol records +0 feature dpmaster: don't filter by protocol version if query is for protocol version 0, this way server browsers can see multiple versions of a single game, should probably also add a 'any' gamename that disables gamename filtering (Angst, Elric) +0 feature dpmaster: release an example /etc/init.d/dpdmasterd script based on the one LordHavoc is using +0 feature dpmod: add a g_spawnprotectiontime cvar which would default to 1 (invulnerable for 1 second after spawn) to prevent mining the spawn points +0 feature dpmod: add a gravity gun weapon, able to grab and launch monsters +0 feature dpmod: add a summon command using KRIMZON_SV_PARSECLIENTCOMMAND which would summon the specified monster infront of you (Kedhrin) +0 feature dpmod: add higher quality replacement sbar images +0 feature dpmod: add knight/hell knight swords as player weapons (TimeServ) +0 feature dpmod: add mode with respawning monsters +0 feature dpmod: change kill awards to use DP_SV_CLIENTFLASHPIC (Tomaz) +0 feature dpmod: find a way to make deathmatch 7 get more difficult as kills increase? (Zombie13) +0 feature dpmod: make a editlights.cfg containing a lot of light editing binds as bindmap 5, also aliases to turn on/off that bindmap, so users can bind one key to the switch command to switch to a light editing mode and back again any time they want (HReaper) +0 feature dpmod: make rocket launcher ai code use altfire to detonate rockets near players (Vermeulen) +0 feature dpmod: make run animation play back according to movement speed (along v_forward), instead of just playing a continuous loop based on time (Urre) +0 feature dpmod: make spawning use viewzoom to start zoomed out 2.0 and then zoom in to 1.0 (Urre) +0 feature dpmod: make teleport leave an EF_ADDITIVE clone of the player which fades out +0 feature dpmod: merge alieninfestation speedmod into dpmod, especially the map +0 feature dpmod: replacement flame models using md3 and q3 shaders with animmap (skite2001) +0 feature dpmod: try making ball lightning mortar shamblers (scar3crow) +0 feature dpmod: try not adding gravity when onground to see if it gets rid of ramp sliding (Midgar) +0 feature dpmod: update stats to count monster kills in dm 7 and such +0 feature dpmodel: add support for compiling a model relative to a bone, for exporting head/upper/legs md3 player models more easily (Morphed) +0 feature dpzoo.map: flame jet +0 feature dpzoo.map: func_train with sky brushes +0 feature dpzoo.map: particlecube +0 feature dpzoo.map: rotating fan bmodels +0 feature dpzoo.map: rtlights in normal mode (when that extension is made) +0 feature dpzoo.map: thief-like area to sneak past a guard who can easily kill you (shambler?) to demonstrate lightlevel checking +0 feature hmap2 -light: add a new light type which matches the old hlight behavior, and a -hlight option to make lights default to this type (Crusader, Urre) +0 feature hmap2 -qbsp: add ORIGIN brush support in bmodel compilation (Carni) +0 feature hmap2 -visdump: save bsp vis lump to .vis and .leaf files (knghtbrd) +0 feature hmap2 -vispatch: replace bsp vis lump with .vis and .leaf file contents (knghtbrd) +0 feature hmap2: add CAULK texture support - delete surfaces using this texture, or at least don't link them (Tomaz) +0 feature hmap2: produce .vis files which contain a numvisleafs, an array of visofs values for all the leafs of the bsp, and the new data for the visibility lump, this would avoid the need for vispatch +0 feature lhfire: make a lhfire.txt and move the scripting info to it, add some more general explanation and tips +0 optimization darkplaces client: add cl_null.c so that dedicated server doesn't need client +0 optimization darkplaces collision: Collision_TraceBrushBrush should compare enterfrac changes to realfraction and skip out if further, also leavefrac (Vic) +0 optimization darkplaces collision: Mod_Q3BSP_TraceBrush_RecursiveBSPNode can be optimized to take a clipflags parameter like R_Q3BSP_RecursiveWorldNode (Vic) +0 optimization darkplaces collision: put patches on a delayed queue in q3bsp collision code so the trace is first clipped by brushes +0 optimization darkplaces collisions: modify q3bsp tracebox to scan the cluster bsp first (which should cut short a lot of traces), and make a list of brushes/curves encountered along the way, and then check them in roughly sorted order +0 optimization darkplaces loader: make loadimagepixels take a string of space separated path patterns to check for a file in, to cut down on the number of wasted file checks for things that never occur (Spike, Up2nOgOoD) +0 optimization darkplaces loader: remove the loop unrolling in Image_Resample32LerpLine and Image_Resample24LerpLine and related functions, as the loop is really too long to get much benefit, and it might even not fit in the L1 instruction cache on Pentium1 (fuh) +0 optimization darkplaces particles: make pt_rain spawning do a single downward trace and set a timer for the impact, to reduce collision load (LordHavoc) +0 optimization darkplaces renderer: add r_null.c so that dedicated server doesn't need renderer +0 optimization darkplaces renderer: store p->past->clusterindex into p->clusterindex to speed up R_WorldVisibility, also store p->past - data_leafs index into p->leafindex, and move the CullBox check to last (Vic) +0 optimization darkplaces renderer: support GL_ATI_separate_stencil since ATI does not support GL_EXT_stencil_two_side and GL_ATI_separate_stencil was integrated in OpenGL2.0 (romi) +0 optimization darkplaces server: implement first unused/last used entity range optimization on entity spawn/remove similar to the client particles (LordHavoc) +0 optimization darkplaces visibility: R_Q1BSP_BoxTouchingPVS and R_Q3BSP_BoxTouchingPVS should check pvsframe on nodes as well as leafs (Vic) +0 optimization darkplaces: calculate worldmodel farclip (corner to corner radius) at load +0 optimize darkplaces renderer: get rid of attenuation texture on lights because math is faster, add fastpath for no normalmap (Lava_Croft) +1 bug darkplaces WGL client: figure out why for some people GDI input has stuttering problems with gl_finish 0 mode (Kinn, Urre, romi, Spike, Black) +1 bug darkplaces WGL/GLX/SDL client bug: if sound is unavailable (causing a freeze waiting for it to become available), the config is reset (SavageX) +1 bug darkplaces bsd filesystem: read() is failing (not returning the requested amount) on freebsd when reading files, whether actual files or in a pk3 - somehow it is still able to read the pk3 zip directory though (suminigashi, Elric) +1 bug darkplaces collisions: curve collisions sometimes catch on the lip of the edge, pushing into the curved back wall around certain jumppads in Nexuiz for example consistently gets stuck just below the ledge (HReaper) +1 bug darkplaces command: "rate", "playermodel", "playerskin", "pmodel" commands can spam server console with usage statements (Spike) +1 bug darkplaces console: when logging using log_file and log_sync 0, setting log_file back to "" does not close the file until another message is posted? +1 bug darkplaces fs: invalid pk3 archives prevent engine from starting (Willis) +1 bug darkplaces input: fix stuck buttons during a level change (mercury82, tkimmet@ezworks.net) (further note: this is from the console becoming active temporarily and catching the key release when the player lets go during the loading stage, make it possible to release a button that was pressed before the console was activated, or make it execute -commands for all pressed binds when level starts) +1 bug darkplaces linux client: when clicked in a file manager it does not find the data directories as the working directory is not set, do a proper search for valid data directories in multiple paths, such as working directory, executable path, etc (Randy) +1 bug darkplaces loader: check for out of bounds lump data ranges in maps (FrikaC) +1 bug darkplaces loader: check for truncated sound files (FrikaC) +1 bug darkplaces makefile: add icon to windows sdl builds (RenegadeC) +1 bug darkplaces nexuiz: can't open console when in nexuiz menu (stahl) +1 bug darkplaces protocol: GAME_NEXUIZ: view sometimes stuck on its side while playing (update: this is not related to cl.viewangles, it's something else): http://www.digitalfunk.org/brewdles/Nexuiz/nexuiz000001.jpg (Brewdles) +1 bug darkplaces prvm: add back the leak checking http://cvs.icculus.org/cvs/twilight/darkplaces/prvm_cmds.c?r1=1.67&r2=1.68 (Black) +1 bug darkplaces prvm: bounds check function statement values to prevent qc from jumping to arbitrary memory (Spike) +1 bug darkplaces renderer or server: EF_NODEPTHTEST sometimes disappears when not in pvs (Urre) +1 bug darkplaces renderer: VP oriented sprites are not using the left/right vectors correctly as demonstrated in dpspbug.avi (Cheapy) +1 bug darkplaces renderer: add r_shadow_light_polygonoffset and r_shadow_light_polygonfactor variables to work around multitexture depth issues on TNT cards (Urre) +1 bug darkplaces renderer: do bloom effect before world crosshair and coronas and things (KrimZon) +1 bug darkplaces renderer: lit sprites (which use R_CompleteLightPoint) are being lit blue by glow_color 108 dlights (Cheapy) +1 bug darkplaces renderer: make sure that the texture fragment allocator can upload a full size block that uses the entire image, this may involve width/height comparisons needing a + 1 (fuh) +1 bug darkplaces server: PF_vectorvectors is broken, given a v_forward from makevectors (not using roll) it does not give the same v_right and v_up vectors (VorteX) +1 bug darkplaces server: add DP_QC_ENDFRAME extension/documentation and post it on wiki (tell Uffe) +1 bug darkplaces server: apparently MOVETYPE_WALK on non-players is frequently resetting origin to oldorigin, why does it think it's in solid? (Wazat) +1 bug darkplaces server: change maxplayers to be a cvar, and post a warning if it changes during a game saying it won't take effect until the next map command +1 bug darkplaces server: decide on an extension name for .ent loading and report it, also document in dpextensions (FrikaC, Gleeb, wiki) +1 bug darkplaces server: figure out what's breaking RenegadeC's TAOV monster jump code (RenegadeC) +1 bug darkplaces server: silver key missing in the map Menkalinan at http://quakemaps.nm.ru/maps2.html (zarquon) +1 bug darkplaces: if progdefs.h is out of date it causes a Sys_Error, after clicking OK on the message box the engine crashes, probably something to do with partially loaded progs +1 bug hmap: qbsp dies from runaway allocations if a duplicate plane is found on a brush (Tomaz) +1 change darkplaces protocol: document the TEI stuff used in Nexuiz +1 change darkplaces protocol: send additional stats for movement parameters and slowmo and gravity, replacing the rather silly cl_movement and cl_slowmo and cl_gravity cvars (Vermeulen) +1 change darkplaces renderer: add gunshot (shotgun pellet) and nail (spike) decals to the particlefont so that these can look different from explosions (Morphed) +1 change darkplaces renderer: add r_shadow_realtime_world 2 mode (and update menu), make 1 only render rtlights if they came from a .rtlights file and mode 2 render them even if they were imported from the map, always render imported ones if editing mode is turned on (Willis) +1 change darkplaces renderer: make "sky" keys in worldspawn override q3 sky shaders (Vermeulen) +1 change darkplaces renderer: make attachments use their parent's origin for model lighting +1 change darkplaces renderer: use 16th power specular instead of 8th power (LordHavoc) +1 cleanup darkplaces loader: add palette conversion capabilities to Image_CopyMux +1 cleanup darkplaces memory: add Mem_AllocNoClear function, and use it where possible, if developer is on it should clear with random garbage +1 cleanup darkplaces renderer: make Host_Error call error reset functions on renderer subsystems? (models are already flushed) +1 feature darkplaces client: add cl_particles_blood_color_r and g and b cvars to control blood color (Asaki) +1 feature darkplaces client: add cvars to control lighting quality to allow performance tradeoffs; r_shadow_ options for use of dot3 shading, etc +1 feature darkplaces client: change sort key in server browser using left/right arrows +1 feature darkplaces docs: fix lots of bugs and then retitle the website to get more publicity: DarkPlaces: Re-live Quake again... +1 feature darkplaces docs: write docs about in_bind/in_bindmap in readme (shadowalker) +1 feature darkplaces input: finish porting Quake2 keyboard stuff (Rick, FrikaC) +1 feature darkplaces loader: load separate _lower.tga and _upper.tga sky textures to allow resolutions other than 128x128 per layer (Idot) +1 feature darkplaces loader: make the rtlight entity loader support a rtlight entity to be used for lightstyles in q3bsp, these would render in both normalmode and realtime mode, but not actually be dlights (Kazashi) +1 feature darkplaces menu: add in_bindmap support to bind menu; a selector for which bindmap is actively being shown and bound in the menu, and add bind entries for some bindmap commands +1 feature darkplaces networking: hexdump the replies to the packet command? (Spike) +1 feature darkplaces particles: add a vertical splash effect to raindrop splashes, not just the ring (Stribbs) +1 feature darkplaces protocol: add DP_EF_CLIENTLOCKANGLES extension (prevents client from turning view, takes angles from entity) (Wazat for Battlemech) +1 feature darkplaces protocol: add DP_SV_CLIENTFLASHPIC extension: a function void svc_clientflashpic(entity cl, float alpha, float fadetime, string picname), to flash a pic in the center of the client's screen, useful for 'Double Kill' awards and such (Tomaz) +1 feature darkplaces protocol: add DP_SV_READCLIENTINPUT extension (.vector clientinput; works like .movement but for mouse or any other similar controllers) (Wazat for Battlemech, FrikaC, Urre) +1 feature darkplaces protocol: add a .modelflags variable which if non-zero overrides model flags - note, LordHavoc would prefer just adding more EF_ values (Arwing, frightfan) +1 feature darkplaces renderer: check out QMB lightning and lava effects (jeremy janzen) +1 feature darkplaces renderer: make r_fogsky cvar to control how much fog is rendered infront of the sky (Deej, C0burn) +1 feature darkplaces server: add DP_CLIENTCAMERA extension (.entity clientcamera; sets which entity the client views from) (Wazat for Battlemech, [TACO]) +1 feature darkplaces server: add EndGame function (called on server shutdown or level change) (Vermeulen) +1 feature darkplaces server: add a string function that returns a character value from a string, mainly for csqc printing its own text +1 feature darkplaces server: add contents reporting to qc somehow when traceline does model tracing and hits the model, this could also fix q3bsp sky collisions? +1 feature darkplaces server: add gettimestamp builtin (returns a string) for logging purposes +1 feature darkplaces server: add md3 mesh name reporting to qc somehow when traceline does model tracing and hits the model +1 feature darkplaces video: add r_displayrefresh cvar for windows video refresh settings (Willis, Judas Judas, Michael Miller, Vondur) +1 feature dpmod: add a Treasure Hunt mode (inspired by preview of Will Rock) - a team wins when they hold all the artifacts +1 feature dpmod: add func_crate (NotoriousRay) +1 feature dpmod: dm 7 monster spawns should occasionally be a crowd of Diablo2 style powered up monsters (Rick) +1 feature dpmod: dm 7 monster spawns should occasionally fail in a shower of gibs (Rick) +1 feature dpmod: dm 7 super monsters should glow and have a name which shows up when in crosshairs (Rick) +1 feature dpmod: dm 7 super scrag should fire spiral acid (Rick) +1 feature dpmod: make ogres start up their chainsaw when first seeing an enemy (scar3crow) +1 feature hmap2: add .mip loading support +1 feature lhfire: add percentage and estimated time reporting to console output (daniel_hansson@telia.com) +1 feature lhfire: post lhfire_gui build +1 feature lhfire: prepare example scripts for release +2 bug darkplaces collision: bmodel bounding boxes need to be calculated to account for clip brushes, which are not in the drawing hull (metlslime) +2 bug darkplaces collision: modify Collision_ClipTrace_Line_Sphere to have a slight backoff on impact, like all other collision functions (tell TheBurningRed) +2 bug darkplaces collision: use larger of model box or collision box for linking into areagrid so that bullet tracing can use the model bounding box instead of the collision one? (Urre) +2 bug darkplaces console: figure out how to prevent "alias a a" - infinite loop when executed, this should be detected when executing it (Vicious) +2 bug darkplaces console: review the whole set of console commands and cvars carefully and identify interactions, known interactions include sequences such as +sv_cheats 1 +map e1m1, or +maxplayers 8 +deathmatch 7 +map dpdm2, but for some reason this works with the cvar after the map command, and also if you do -window it does not affect the value saved to config, because the configs are executed again after the -window, perhaps it is not executing the commandline a second time? apparently also -dedicated without +map does not load a map automatically in transfusion (Wazat, Willis) +2 bug darkplaces csqc: does csqc entity player movement prediction work? what does the EXT_CSQC spec say about retrieving move history for a replay in csqc code? (Spike) +2 bug darkplaces loader: implement r_shadow_bumpscale_basetexture support in hl maps (CheapAlert) +2 bug darkplaces renderer: some polygons are not being lit by compiled rtlights in start.bsp, uncompiled rtlights work fine +2 bug darkplaces rtlights: light entity import should support spotlights and generate cubemaps for their cone angles as needed +2 bug darkplaces server: getlight builtin should consider lightstyles +2 bug darkplaces testing: make sure Zerstoerer works (Chris Kemp, Kaotic)) +2 change darkplaces client: PRYDON_CLIENTCURSOR should be able to click on sprites, make sure it collides with the polygons in the current orientation (FrikaC) +2 change darkplaces renderer: use GLSL for bloom to improve color quality (Justin Thyme) +2 change darkplaces renderer: use GLSL for gamma correction when hardware gamma is not working +2 cleanup darkplaces console: add cvar callbacks and make net cvars have callbacks +2 cleanup darkplaces console: make commandline parsing use COM_ParseToken - why? +2 cleanup darkplaces console: make sure the engine uses only the first 32 special chars, so the high set can be replaced, this means player messages should not be shifted up, and the 'shift down' printing in dedicated server consoles should be removed, etc (Urre) +2 cleanup darkplaces filesystem: add fs_datapath and fs_userpath cvars to better support Linux, this can be done by making each gamedir add both the basepath and userpath variants of the gamedir, and making sure the userpath one is last so it is used for writing (Mercury) +2 cleanup darkplaces memory: memory pools should be able to be nested multiple levels (Vic) +2 cleanup darkplaces menuvm: change menu qc key input to using string key names instead of numbers (the bind alias names should be able to do this) (Mercury, Black, Vermeulen) +2 feature darkplaces client font: cvar for console text size (Vermeulen) +2 feature darkplaces client font: variable width font support using a character width file (FrikaC) +2 feature darkplaces client: 'status' command player ip logging by nickname (sublim3) +2 feature darkplaces client: add a cl_identifyplayer cvar to show the scoreboard name for the current cursor trace entity's .colormap (green) +2 feature darkplaces client: add a message history for messagemode to allow you to retrieve old ones (green) +2 feature darkplaces client: add a net_graph cvar which would show incoming and outgoing packet ping times, packet sizes, dropped packets, etc (avirox) +2 feature darkplaces client: add cl_censor cvar which would replace 'swearing' with humorous messages (Deej) +2 feature darkplaces client: add support for stereo shutter glasses +2 feature darkplaces client: cl_particles_maximum cvar (default 32768) which would change number of particles allowed at once (TheBeast) +2 feature darkplaces client: decal clipping (romi) +2 feature darkplaces client: http download and parse http://www.gameaholic.com/servers/qspy-quake for nq servers (Spike) +2 feature darkplaces client: interpolate scale and alpha changes (Cheapy) +2 feature darkplaces client: make CL_Video use TEXF_FRAGMENT again by adding general, transparent support for it in all drawqueue functions (so you dont need to call FragmentLocation) (Black) +2 feature darkplaces image: add scaling capabilities to Image_CopyMux +2 feature darkplaces loader: add support for fuhquake naming of map textures (textures/start/quake.tga style) +2 feature darkplaces loader: implement vertex cache optimization of models during loading, see this paper: http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html (Dresk) +2 feature darkplaces menu: implement menu_clearkeyconfig and menu_keyconfig and the corresponding menu (diGGer) +2 feature darkplaces menu: new game custom level/mod menu, which allows you to choose a mod and browse through maps and choose starting skill, by default it would have the current mod selected and start selected (Jago) +2 feature darkplaces model: add model_exportobj console command to allow exporting a specified model as .obj (Randy) +2 feature darkplaces physics: DP_SV_QCPHYSICS extension, calls SV_PhysicsQC function, which replaces the entire SV_Physics C function, calling all thinks and physics and everything (Urre) +2 feature darkplaces protocol: .float bulge; field which makes an entity larger by moving vertices along their normals, well known as the fatboy mutator in Unreal Tournament (Wazat) +2 feature darkplaces protocol: add effects.txt file which would describe a bunch of numbered effects usable with a .effectindex field on entities, these would range from point effects, to continuous emitters, to beams with jitter and other properties, each effect would have various info like dlight and particle spawning and beam rendering (CheapAlert, Supa, FrikaC, [TACO], Urre, Vermeulen) +2 feature darkplaces protocol: add rcon_password system similar to quakeworld server (II`cyan) +2 feature darkplaces protocol: svc_spawnstatic should use a delta from defaultstate, instead of its outdated custom protocol (Spike, Dresk) +2 feature darkplaces release: add KDE/gnome icons somehow using darkplaces72x72.png (de-we) +2 feature darkplaces renderer: dpshader should support corona-model shaders somehow (equation: pow(normalizationcubemap(transform(eye, vertexmatrix)) dot3 '0 0 1', 8)), which are normally used around unusually shaped lights instead of flat coronas (Mitchell) +2 feature darkplaces renderer: q3 fog brush shaders (tZork) +2 feature darkplaces renderer: use occlusion query extension (if supported) for testing corona visibility instead of traceline - curve collisions are dragging down corona performance in some maps (Vermeulen, Riot) +2 feature darkplaces sdl: add joystick support +2 feature darkplaces server: add DP_SV_COLLISIONCONTENTMASK extension (Urre, Spike) +2 feature darkplaces server: add EXT_BITSHIFT extension, documented at http://sourceforge.net/docman/display_doc.php?docid=24607&group_id=116842 (Spike) +2 feature darkplaces server: add EXT_DIMENSION_GHOST extension, documented at http://sourceforge.net/docman/display_doc.php?docid=24607&group_id=116842 (Spike) +2 feature darkplaces server: add EXT_DIMENSION_HITMODEL extension (Urre, Spike) +2 feature darkplaces server: add EXT_DIMENSION_PHYSICS extension, documented at http://sourceforge.net/docman/display_doc.php?docid=24607&group_id=116842 (Spike) +2 feature darkplaces server: add EXT_DIMENSION_VISIBLE extension, documented at http://sourceforge.net/docman/display_doc.php?docid=24607&group_id=116842 (Spike) +2 feature darkplaces server: add the ability to minimize the windows dedicated server console to a tray icon (div0) +2 feature darkplaces sound: add FLAC sound support (Anton Romanov) +2 feature darkplaces sound: add mapmusic command ( perhaps, with a blank music name it would simply remove the map from the list of overrides) to manipulate a list of per-map music overrides, mapmusic alone should print the list (Joseph Caporale, tell Static_Fiend) +2 feature dpmod: add observer mode and a best N of (N-1)*teams+1 match system (carni) +2 feature dpmod: write a readme for the menu progs code to get people started with it, and know what is and is not possible, what builtins do, etc (Urre) +2 feature dpzoo.map: swinging doors +2 feature hmap2: add "_minlight" "red green blue" and "_ambientlight" "red green blue" fields to worldspawn parsing (Harb) +2 feature hmap2: add .hlwad and .hlw support to hqbsp (convert to quake palette, and check for colormap.lmp to see how many fullbrights are involved, also add -fullbrights option to control this) (Todd, FrikaC) +2 optimization darkplaces collision: do trace against patch bbox before testing triangles +2 optimization darkplaces renderer: move skybox/skysphere render to after sky polygons, so that they can do a depth comparison to draw only on the sky polygons, using DepthRange(1,1) and DepthFunc(GL_GREATER), note though that portal renders will need half depth range (Mercury) +3 bug darkplaces collision: add edge bevels in collision code, by trying all crossproduct combinations of an edge with planes from the other brush, and choosing only the ones which are valid +3 bug darkplaces compatibility: quakerally does not work, figure out why (Spike) +3 bug darkplaces renderer: add stainmaps to realtime lighting mode +3 bug darkplaces renderer: crash when using rtworld mode and reloading a map that has been modified and now has less surfaces, this means the map name is the same, but the surface indices/pointers are no longer valid +3 bug dpmodel: add support for unnamed bones (Mitchell) +3 bug dpmodel: fix dpmodel to compile v_HKmp5-sd (tell Riot) +3 change darkplaces client: GAME_NEXUIZ: implement new hud and scoreboard based on http://www.quirkybastards.net/qmods/scoreboard.jpg except with deaths instead of lives, and map name instead of "be the last one alive" and remove the time string and map string at the bottom, instead showing the hud (Vermeulen) +3 change darkplaces renderer: change q3 shader system to use fingerprinting of shader passes to identify what kind of shader it is (for example "OPAQUE reflection ALPHA texture MODULATE $lightmap" metal shaders, and "OPAQUE texture1 ALPHA texture2 MODULATE $lightmap" terrain blending shaders), this would allow any identifiable q3 shader to have per pixel lighting, with full rendering capability +3 change darkplaces renderer: change texture manager to use a flat array with sequence purging on level change after stale models are unloaded, sequence marking and a flat array would allow reuse of textures by multiple models, multiple skins within a model, or even multiple texinfo structures in q3bsp +3 change darkplaces renderer: load q3 shader information at level load, and allow all models to use them +3 change darkplaces sound/render: change r_refdef calculations to happen before CL_UpdateScreen, and then move S_Update before CL_UpdateScreen to slightly improve sound latency issues, and then eliminate sound_spatialized variable +3 cleanup darkplaces loader: make q1bsp surfaces have vertex color arrays like q3bsp to make things more consistent, note these need light styles +3 cleanup darkplaces menu: rearrange menus - make Graphics Options submenu and move video and renderer stuff there, add Apply button to video section (tell Elric) +3 feature darkplaces client: .loc support and other team messaging capabilities (sublim3) +3 feature darkplaces client: add an httpdownload command which can download pk3 archives (Paul Gagnon) +3 feature darkplaces client: add back r_waterripple (Vermeulen) +3 feature darkplaces client: add direct xvid recording using the xvid library (Error, Vermeulen) +3 feature darkplaces docs: add short and long documentation string to each cvar/command (QorpsE) +3 feature darkplaces docs: write a documentation string in engine, and a command to dump documentation to a darkplaces.txt file (QorpsE) +3 feature darkplaces editlights: add a custom light style string to rtlights, if empty it uses a normal server controlled light style (Stribbs) +3 feature darkplaces filesystem: add fs_reload command to allow restarting the filesystem module, this would mean that it could check for new paks and such (Mercury) +3 feature darkplaces loader: add _color.tga support (realtime lighting would use this instead of the .tga for diffuse layer if available) (Urre) +3 feature darkplaces loader: load .skin files for bsp files to allow per-map texture overrides (Spike) +3 feature darkplaces loader: load .skin files for sprite files to allow per-frame texture overrides (Spike) +3 feature darkplaces loader: support md5mesh/md5anim model files (Supa, Kazashi, Swifty) +3 feature darkplaces menu: add OpenGL Extensions menu to enable/disable various features (zombie_13) +3 feature darkplaces physics: DP_SV_TRACEMOVE extension, adds a qc builtin which traces an entity through the world (using origin/angles/mins/maxs/velocity/avelocity) for a specified amount of time (frametime typically), and sets trace results accordingly, this would greatly help out QC physics (Urre) +3 feature darkplaces physics: add DP_SV_CRATEPHYSICS (NotoriousRay) +3 feature darkplaces protocol: .string drawtext; displays the specified message (up to 31 characters) centered at the origin of this entity, for qc based menus and titles on things inside the world, also .float drawtextscale which defines character height and .float drawtextflags with DRAWTEXTFLAG_ORIENTED DRAWTEXTFLAG_UPRIGHTFACING DRAWTEXTFLAG_ALIGNTOP DRAWTEXTFLAG_ALIGNBOTTOM DRAWTEXTFLAG_ALIGNLEFT DRAWTEXTFLAG_ALIGNRIGHT, defaults to view parallel (like typical sprites) and centered alignment horizontally and vertically, also note colormod, and the effects flags EF_ADDITIVE EF_FULLBRIGHT (it is lit by default) EF_NODEPTHTEST EF_SELECTABLE apply to this (Wazat) +3 feature darkplaces protocol: add "GetPK3URLList" and "PK3URLListResponse" messages to allow a client to query a server for what pk3 archives to httpdownload before joining (Paul Gagnon) +3 feature darkplaces protocol: add DP_ENT_COLORSHELL which puts a Q2-style colored shell on a model (Supajoe) +3 feature darkplaces protocol: add a "box" effect controllable by QC somehow, for highlighting usable items (buttons and such) like in Red Faction, preferably with multiple colors supported (Mitchell) +3 feature darkplaces protocol: make server send ping time to client for prediction +3 feature darkplaces renderer/loader: add RBSP map support (modified q3bsp with lightstyles) (Kazashi) +3 feature darkplaces renderer: add Draw2D function to model struct to make it easy to draw models without an entity (Tomaz) +3 feature darkplaces renderer: add a command to replace a texture in the running map, should only work in singleplayer; for testing only (Randi) +3 feature darkplaces renderer: add antialiasing options (Zombie_13) +3 feature darkplaces renderer: add support for Color Code 3D anaglyph stereo glasses which are purple-blue/amber, this would require rendering the two views to a texture and then blending them with color modulation, some research will be needed to find out the specific colors to use, which should probably be cvar controlled (Francis Siefken) +3 feature darkplaces renderer: create collision brushes from q1bsp clip hulls, and make them display when using r_drawcollisionbrushes (Aardappel) +3 feature darkplaces renderer: directional lighting from the q3bsp lightgrid should use diffuse and specular lighting if available (Vermeulen) +3 feature darkplaces renderer: dpshaders (when supported) should have support for envmaps, and should support being lit by diffuse lighting as a fake gloss effect for normal mode (Vermeulen) +3 feature darkplaces renderer: implement steep parallax mapping with self shadowing and depth options: http://graphics.cs.brown.edu/games/SteepParallax/ (Kazashi, Randy) +3 feature darkplaces renderer: need to make a standalone minimod to test darkplaces rtlights code, which Diablo-D3 can throw at the ATI driver team to test with, 3D txtures with GL_CLAMP_TO_EDGE wrapping are broken (Diablo-D3) +3 feature darkplaces renderer: skyroom needs to be added ("info_skyroom" entity sets view origin, scanned by client at load, and by server to send all entities in skyroom) +3 feature darkplaces renderer: try to achieve perfect transparency sorting by encoding them as a BSP tree to cause all to be split by eachothers' planes, or at least try recursively splitting each one by all of the previous accepted transparent polygons, the BSP encode method is still probably too slow to use but is worth trying, and the other method is even slower (Kazashi) +3 feature darkplaces renderer: try two-cubemap approach to specular lighting math on GF3 path (Black) +3 feature darkplaces sdl: make and submit a patch to add vsync control to libSDL +3 feature darkplaces server: add DP_GFX_QUAKE3MODELTAGS, DP_GFX_SKINFILES, and any other new extensions to the wiki (wiki) +3 feature darkplaces server: add a DP_SV_PUSHMOVE extension with a pushmove builtin that does basically what MOVETYPE_PUSH does, but with controllable end position, not time based (Zombie) +3 feature darkplaces server: add traceboxwithcontents function (same as tracebox but adds the startcontents parameter) (LTH, http://forums.inside3d.com/showflat.pl?Board=Engine&Number=909 ) +3 feature darkplaces server: hub save support, one file indicating active map, and then for each map it saves a quake savegame +3 feature darkplaces server: make an event message queue for each client, so TE_ effects and sounds and can be stuffed into successive packets if they don't all fit at once, currently a large number of explosions at once are never sent because they don't fit in one size limited packet (romi) +3 feature darkplaces sound: add snd_rate cvar and make it changable during game (RenegadeC) +3 feature darkplaces sound: sound shaders; mindistance, maxdistance, list of sounds to choose from (Supa) +3 feature dpmod: code a func_swinging entity which takes a starting angle and swing time and swings back and forth, each time reaching that angle, and swinging through '0 0 0' (Zombie) +3 feature dpmod: code an alternate ending for shub being killed by normal weapons (scar3crow) +3 feature dpmod: use FRIK_FILE extension to allow passing objects between levels, such as crates (NotoriousRay) +3 feature dpzoo.map: remote cameras (to demonstrate DP_CLIENTCAMERA, DP_EF_CLIENTLOCKANGLES, and precise angles) +3 feature hmap2: add support for shadow casting bmodels (Urre, Arwing) +3 feature hmap2: add tga support to qbsp (load base texture and _glow/_luma) +3 feature nexuiz qc: add clanring-style menus and match settings +3 optimization darkplaces client: make a new caching system with handles (which can be purged) and give every entity a cache handle to a model instance, which contains cache handles for each mesh/array +3 optimization darkplaces renderer: implement some sort of areaportals system to let doors shut off portals, this needs to clip the portals by all bmodels in the area because in e1m1 the doors are two part and can't hide the portal individually (Vic) +3 optimization darkplaces renderer: use zpass (without front/back caps) shadowing for a speed gain if entity box is outside a frustum made from the light to the view's nearclip polygon edges, which determines whether the shadow would be casting directly onto the view surface (Tomaz) +3 optimization darkplaces server: make SV_ClipMoveToWorld cache trace results somehow for a speed gain in masque.bsp +4 bug darkplaces client: figure out why intermission camera pitch changes after a moment (Tomaz) +4 bug darkplaces networking: use getaddrinfo to support ipv6, add support for winsock2 (or require it), check if winsock2 has ipv6 functions (getaddrinfo)... (|Rain|) +4 bug darkplaces physics: rotating MOVETYPE_PUSH code calls blocked when it's just a touch, it isn't even trying to push +4 cleanup darkplaces memory: use the memory pool nesting feature ! (Black[,Vicious]) +4 feature darkplaces client: add decals on models (Urre) +4 feature darkplaces client: proquake secure protocol support for playing on proquake servers (sublim3) +4 feature darkplaces console: add setlock command which marks a cvar as locked, and sends it over network to connected clients as a setlock command, the clients will not allow the user to modify the cvars while locked (and will only accept setlock commands over the network), and cvars are unlocked when level ends - the server will send the locks again on next level (VorteX) +4 feature darkplaces csqc: add builtin to clientside qc for reading triangles of model meshes (useful to orient a ui along a triangle of a model mesh) +4 feature darkplaces demo: ability to record demos while already connected (green) +4 feature darkplaces loader: add SKM model support (Vermeulen, Vic) +4 feature darkplaces loader: load .map file if present to get collision brushes for q1bsp (Transfusion) +4 feature darkplaces protocol: add capability for qc entities to act as bones in a model, and send them as compressed origins in the parent entity's updates, with perhaps a limit of 16 bones, this would allow some simple serverside ragdoll (Mitchell, Deej) +4 feature darkplaces protocol: multianimation support using multiple .frame fields on the entities for model formats with multianim capability such as SKM (Vermeulen) +4 feature darkplaces renderer: add _reflect textures which filter use of skybox as a cubemap reflection (FrikaC) +4 feature darkplaces rtlights: add a directional flag which makes the light cast along X only, rather than spherically, angles can be used to make it point at things, this will also affect shadow volumes, so portal clipping enhancements are needed, and non-uniform scaling is needed (Zombie) +4 feature darkplaces server: add DP_QC_MODELINFO extension to allow QC to ask about the range of frames owned by an animation base name, and other things like how many skins it has (Vermeulen, Supajoe) +4 feature darkplaces sound: rewrite sound system! (FrikaC, Static_Fiend) +4 feature dpzoo.map: a drivable vehicle (using same technique as remote cameras, plus DP_SV_READCLIENTINPUT) +4 feature hmap2: add hint brushes (HINT texture) +4 optimization darkplaces renderer: add cubemap light filter texture culling based on how much of the cubemap is used somehow (Vermeulen) +5 feature darkplaces client: add a "edictedit" command to open up a dialog to edit an edict (allow multiple dialogs to be open at once) +5 feature darkplaces client: add qc debugger, which would have its own very simple fullscreen console, this would be called directly from the qc interpreter, not from client (painQuin) +5 feature darkplaces console: r_textutf8 cvar (to parse UTF-8 codes), should affect all text rendering, using multiple conchar images for different groups of 256 characters (VorteX) +5 feature darkplaces renderer: add dpshader support +5 feature darkplaces renderer: add some kind of sun flare support, possibly stored in a dpshader (CheapAlert) +5 feature darkplaces renderer: do a minimap that works by simply using nearclip to sheer off anything above the eye, and draws anything below normally, or via a cvar as height coloring (Supajoe) +5 feature darkplaces renderer: implement transparent triangle sorting within each entity (FrikaC) +5 feature darkplaces renderer: lightshader files (probably loaded by the cubemap field already present in rtlights handling), these would indicate what attenuation textures to use for the light, what cubemap filter, which corona texture and size and so on, and all textures can be animated (romi, Urre) +5 feature darkplaces server: split server into a separate thread when running listen mode so that a host running too slow won't spoil the game (Toddd) +5 feature dpzoo.map: make some things that make the player bigger/smaller to demonstrate scaling and better viewheight handling and brush collisions (depends on brush collisions) +5 feature hmap2: fix tjunctions on leaky maps +6 feature darkplaces client: add a "edit" command that can edit text files (I.E. .qc and progs.src) using a dialog window (allow multiple), and then add a "frikqcc" command to run it on the current mod (if it's in the command path at least) +6 feature darkplaces renderer: figure out an LOD scheme for really large outdoor environments (Uffe) +7 feature darkplaces protocol,renderer: add DP_EF_REFRACT which turns an entity into a refraction effect similar to doom3 shockwave/heathaze (Supajoe) +7 feature darkplaces renderer: make it work on Savage4 again (Ender) +7 feature darkplaces renderer: mirrors +7 feature darkplaces renderer: shadow volume clipping (romi) +d bug darkplaces Mac filesystem: on Mac dlopen is not finding dylib files alongside the executable, do a more thorough search (Zenex) +d bug darkplaces Mac filesystem: on Mac init the basedir to argv[0] truncated to not include the *.app/ part of the path onward (Zenex) +d bug darkplaces SDL input: changing video mode causes it to ignore all character events from then on +d bug darkplaces WGL client: default WGL input back to GDI, the DirectInput driver is malfunctioning, losing key release messages, stuttering mouse input, and lacks mouse wheel support (Wazat, Kinn) +d bug darkplaces WGL client: fix GDI input init/shutdown, it is using weird mouse acceleration and not restoring it on exit (innovati) +d bug darkplaces X11 keyboard: make sure that the XLookupString code is not little endian specific (Elric, jitspoe) +d bug darkplaces client qw: cl_netinputpacketspersecond 20 is too low for QW physics to behave properly, add a separate cl_netinputpacketspersecond_qw cvar which defaults to 72 (Plague Monkey Rat) +d bug darkplaces client sbar: when playing demos recorded from singleplayer, the deathmatch hud seems to be used, perhaps we have to force it off for maxplayers 1? (Spirit) +d bug darkplaces client win64: crash in R_DrawRTLight due to stack overflow, change the pointer arrays to indexes into r_refdef.scene.entities, or increase projects to build with 4MB stack instead of 2MB - also clean up these warnings: http://dp.deathmask.net/log/dp_x64_buildlog_r8078.txt (Willis) +d bug darkplaces client/server: unable to control player in TAoV multiplayer (RenegadeC) +d bug darkplaces client/server: viewzoom values above 1 are not working properly +d bug darkplaces client: GAME_NEHAHRA: make sure cutscenes and movies work, got a report of seeing a black screen (NightFright) +d bug darkplaces client: GAME_NEXUIZ spews a number of warnings about gfx/ images not being found (Vermeulen) +d bug darkplaces client: cl.sfx sounds aren't playing (romi) +d bug darkplaces client: cl_beams_relative is behaving really badly with cl_movement prediction +d bug darkplaces client: cl_beams_relative is sometimes drawing beams from '0 0 0' (VorteX) +d bug darkplaces client: cl_movement 0 shouldn't be doing an input replay (SavageX) +d bug darkplaces client: cl_movement_airaccelerate missing? +d bug darkplaces client: color codes are not supported in centerprint messages (Wazat) +d bug darkplaces client: corona on your own muzzleflash is annoying when looking down because it can be seen, disable corona on all muzzleflashes (flum) +d bug darkplaces client: crosshair_static 0 breaks if self is EF_NODRAW (NecroPhil) +d bug darkplaces client: disable vsync when doing a timedemo (Randy) +d bug darkplaces client: do replay cl_movement queue each time a move is added, as this is called from the server packet parser, which can overwhelm the client with several packets in one frame, leading to a complete lockup until the level changes (Black) +d bug darkplaces client: figure out why multimap demos are skipping the later portions, it's probably related to the time seeking, probably not being reset (Urre) +d bug darkplaces client: finale text during episode-end intermissions shows briefly in its entirety and all as one line (going off the screen), then disappears and begins typing slowly as it should (Sajt) +d bug darkplaces client: fix cl_bobmodel bug which momentarily jolts the gun when you pass through a trigger, pick up an item, etc, Sajt thinks this is related to console prints as well as centerprint (Sajt) +d bug darkplaces client: fix gl_flashblend, it's still drawing rtdlights even when gl_flashblend is on (Toddd) +d bug darkplaces client: hipnotic: health is one character to the right on the sbar, covering up the key icons (M`Shacron) +d bug darkplaces client: loading savegames while a demo is playing does not stop the demo, causing a broken state and ultimately a disconnect before the client enters the loaded game (Bill Pickett) +d bug darkplaces client: make "wait" command wait fornext network frame somehow when connected, to make frikbot .way files load properly (Transfusion, FrikaC) +d bug darkplaces client: make envmap command work with the corrected layout +d bug darkplaces client: make server queries use a queue to avoid flooding out queries too fast (Willis) +d bug darkplaces client: missing bolt/beam models should not produce warnings +d bug darkplaces client: name (and probably other userinfo properties) are not being set when entering a qw server? +d bug darkplaces client: on crctf proquake servers the scoreboard does not contain exactly matching player names (READY is sometimes appended), the ping report and status parsing should ignore text after the player name +d bug darkplaces client: prydon cursor highlighting of EF_SELECTABLE entities flickers with lower server framerate than client framerate (carni) +d bug darkplaces client: quakeworld servers often stuffcmd the cvars topcolor, bottomcolor, pants, team, skin, noaim, so commands for these need to be added (topcolor/bottomcolor will modify _cl_color, the others can be real cvars) +d bug darkplaces client: seta commands create cvars that are not saved to config because they match their 'default' value +d bug darkplaces client: svc_effect should post a warning and do nothing if given a framerate below 1 (Willis) +d bug darkplaces client: te_customflash isn't working? (Wazat) +d bug darkplaces client: userinfo strings are not being updated by name/color commands +d bug darkplaces client: when playing back a demo in slow motion it is very noticable that the camera rotates into position on the first frame (Stribbs) +d bug darkplaces client: when playing back nehahra demos they often contain large time skips at scene cuts which are interpolating slowly over several seconds (Stribbs) +d bug darkplaces collision: check Urre's sltest.bsp and slopestuck.dem and fix the sticking bug, which only happens with sv_newflymove 1 (Urre) +d bug darkplaces collision: frikbots are falling through the map (Sajt) +d bug darkplaces commands: say command is not posting to server console (Vermeulen) +d bug darkplaces compatibility: targetquake does not work, figure out why +d bug darkplaces console: $* expansion should not include $0 (Black) +d bug darkplaces console: $variable expansion is not working on forwarded commands like "say I'm $_cl_name", it does work on local commands like set (esteel, Black) +d bug darkplaces console: alias test "echo 1";test;echo 2 should print 1 then 2, not 2 then 1 or an error (div0, FrikaC) +d bug darkplaces console: chat messages are showing up in brown quake characters and having ^7 and such printed literally +d bug darkplaces console: commandline history won't scroll back past a blank line - the blank line should not be entered into history (Elric) +d bug darkplaces console: console script lines that are too long (1024+ characters) crash (NecroPhil, Black) +d bug darkplaces console: don't save cvars to config.cfg if their current value matches their default value (div0) +d bug darkplaces console: first character is missing on quake brown-text lines, but not consistently, resolved: stripping off the chat prefix character on prints was stripping other characters sometimes due to signed comparison (Spirit) +d bug darkplaces console: inserting characters in the commandline is not adding a nul terminator to the commandline, resulting in lots of trash from older commandlines suddenly showing up (Spike) +d bug darkplaces console: make map listing read the .ent files for map names +d bug darkplaces console: rapid printing (like cvarlist) is somehow being truncated when printing to the terminal (div0) +d bug darkplaces console: when cursoring up and down through command history, shorter lines sometimes contain some text from the previous line +d bug darkplaces csqc: after the drawqueue was eliminated, the CSQC probably can't draw 2D polygons the same way, so it may need fixing ([515]) +d bug darkplaces csqc: engine-based rocket entities have a trail but they don't glow if csqc is used +d bug darkplaces csqc: implement lerpfrac/frame2 fields (Spike) +d bug darkplaces csqc: it's broken! +d bug darkplaces csqc: network entity positions seem to be incorrectly updated while csqc is active, this is best tested with cl_nolerp 1 on a sys_ticrate 0.1 server, which makes the jumps in rocket movement quite noticable +d bug darkplaces csqc: when playing back a demo, the csqc does not seem to be getting the cl.viewangles right +d bug darkplaces docs: host_maxfps is gone, correct the darkplaces.txt and host.c cvar description for host_framerate +d bug darkplaces general: make all text parsing routines support Mac newlines; \r with no \n (Zenex) +d bug darkplaces gl: turning r_shadow_bouncegrid off and back on left a messed up texture binding state +d bug darkplaces hud: sometimes texture borders wrap, causing annoying seams at the edges of pics, use TEXF_CLAMP +d bug darkplaces init: only print "Playing shareware version." notice if running GAME_QUAKE (MrBIOS) +d bug darkplaces input: buttons 4 and 5 on a mouse are acting like mwheel (Kedhrin) +d bug darkplaces input: centerview command isn't doing anything until console is activated, it should begin the pitch drift immediately as in quake (Sajt) +d bug darkplaces input: figure out what's wrong with ctrl key in Linux, hitting character keys tends to do nothing, and holding a character key and then hitting ctrl tends to leave the character key stuck on, this sounds like a window manager issue, but somehow quake3 works around it (Baalz) +d bug darkplaces input: fix the mouse move when console is raised in glx, probably by ignoring the first move after console raise (mashakos) +d bug darkplaces input: ignore first mouse move in windows fullscreen when coming back from an alt-tab (sublim3) +d bug darkplaces loader: AliasSkinFiles stuff is crashing because of the changed skin indexing, it needs to step in multiples of num_surfaces not 1 (Willis) +d bug darkplaces loader: fix hlbsp transparent surface support (mrinsane) +d bug darkplaces loader: halflife wad loading is unable to seek to lump table (ryan[sg], Elric) +d bug darkplaces loader: nexuiz loading a level often loops part of the map's music during loading, this is probably an extra Host_Frame being executed during loading, where it shouldn't be (Vermeulen) +d bug darkplaces loader: only load .lit if the file size matches lumpsize * 3 + 8, as a rough check that the lit is for the correct bsp file (Spike, Urre) +d bug darkplaces loader: q3bsp deluxemap detection can fail on some files, thinking they have deluxemaps even though they don't? (jimmmy) +d bug darkplaces loader: q3bsp lightgrid loading seems to be ignoring the "gridsize" key of worldspawn, but how? +d bug darkplaces loader: unlit q1bsp maps are showing as black rather than fullbright... again. +d bug darkplaces loader: zym models are not loading some of their meshes? this is causing the striped part of the nexuiz RL to disappear (div0, SavageX) +d bug darkplaces loading: test zlib support with entirely pk3'd id1 data (should crash because of zlib not being setup early enough - fix this) (Mabus) +d bug darkplaces loading: when gamedir (or -game) contains a directory which listdirectory() fails on, do a Host_Error with an appropriate message, rather than running with a non-existent directory +d bug darkplaces makefile: build nexuiz.exe using nexuiz.rc (Vermeulen) +d bug darkplaces menu: if no data is found, the menu should be text +d bug darkplaces menu: if no data is found, there are only 3 menu options selectable, even though that is not enough to be able to select quit... perhaps some nexuiz-specific logic is malfunctioning? +d bug darkplaces menu: load/save game menus show files that are not in the highest priority gamedir, such as id1 saves showing up when playing dpmod (Dooomer, WodahsEht) +d bug darkplaces menuvm: menu input focus is lost if a map command occurs while in menu, even if it fails the menu still lost focus and is unusable until closed and reopened with escape key (Black, Vermeulen) +d bug darkplaces model: don't Host_Error when a model is unknown/unsupported type (SavageX, Vermeulen) +d bug darkplaces model: ignore attempts to load "" (SavageX, Vermeulen) +d bug darkplaces particles: cl_particles_quality is affecting lifetime of decals, it should not +d bug darkplaces physics: corpses/gibs are not riding down e1m1 lift (scar3crow) +d bug darkplaces physics: in Prydon Gate the func_door2 entities are stuck in eachother, causing a continuous spew of warnings and causing one of them to be teleported slightly upward which looks bad (FrikaC) +d bug darkplaces physics: q3bsp collisions are still glitchy, particularly gunshots hitting thin air near brushes, even with mod_q3bsp_optimizedtraceline 0, test in dpdm2r by shooting down through gaps in the architecture around the top platform (Vermeulen) +d bug darkplaces physics: test TecnoX and find the frikbot crash in SV_Physics (KrimZon) +d bug darkplaces physics: the zombie lift in e3m2 before the gold key is not working (scar3crow) +d bug darkplaces physics: when riding a lift down (such as near the start of e1m1), the player is not being pulled down, unlike in quake, this can cause repeated fall damage on very fast lifts (scar3crow) +d bug darkplaces protocol: fix cl_nodelta 1, it's halting updates after you move away from an area (Tomaz, sublim3) +d bug darkplaces protocol: fix signon error when starting prydon without +map curig2 (FrikaC) +d bug darkplaces protocol: getting packetlog overflow warnings again, but WHY? (daemon, SavageX) +d bug darkplaces protocol: it's possible to get a "received signon 1 when at 1" error in singleplayer when restarting level, perhaps this relates to very low framerate +d bug darkplaces protocol: models sometimes staying in nexuiz after a big battle, entities that don't exist on the server anymore (Spike) +d bug darkplaces protocol: something is causing things like tracers to sometimes stay indefinitely (Vermeulen) +d bug darkplaces protocol: sometimes players are invisible in nexuiz, showing only their gun model, this may be related to svc_precache messages not being sent during signon (Vermeulen) +d bug darkplaces prvm: VM_remove does not print a stack trace (RenegadeC) +d bug darkplaces prvm: all warnings should print a stack trace (RenegadeC) +d bug darkplaces prvm: assignment to world is not producing an error after world spawn stage (Spike) +d bug darkplaces prvm: engine calls to qc functions are not counting as function calls in the prvm_profile results, so for example SV_PlayerPhysics is being shown with a 0 call count +d bug darkplaces prvm: findchain/findchainfloat are corrupting things when trying to write to the .chain field (Kedhrin) +d bug darkplaces prvm: findflags/findchainflags are server-specific, these should be moved into the generic progs commands +d bug darkplaces prvm: the merged remove is causing a Host_Error on already removed entities, which happens in id1 start.bsp (RenegadeC) +d bug darkplaces prvm: unknown opcode warnings are missing a \n +d bug darkplaces qc FRIK_FILE: when opening a file for writing that already has the data/ prefix in its path, it should not add another data/ prefix (daemon) +d bug darkplaces qc: document the wasfreed() builtin from EXT_CSQC and entitybyindex() builtins, the latter is DP_QC_EDICT_NUM (Urre) +d bug darkplaces quakec: to stop crashing on 64bit the quakec vm needs a string manager that can allocate/free negative integer indices to the strzone strings, and also automatically add engine strings +d bug darkplaces readme: commandline options are slightly out of date, update them (Baker) +d bug darkplaces renderer/server: scaled sprites (or possibly all models) are getting culled as if they were not scaled (KrimZon) +d bug darkplaces renderer: Morphed's colormapping experiments in nexuiz show a difference in gloss color with GLSL vs dot3 path, http://img494.imageshack.us/img494/8745/nexuiz0000258lf.jpg http://www.nexuiz.com/forums/index.php?showtopic=1531 - and apparently it looks right or wrong depending on view point, suddenly switching (Morphed) +d bug darkplaces renderer: add r_shadow_glsl_geforcefxlowquality cvar to make make GLSL shaders use "half" data type, automatically set this if on GFFX (MauveBib, SavageX) +d bug darkplaces renderer: alternate anims are not showing up in wall renderer - they are working for rtlights however (LordHavoc) +d bug darkplaces renderer: animated textures are not being lit by static rtlights +d bug darkplaces renderer: audit all text drawing to make color codes work properly everywhere, right now they are even managing to mess up death message printing if someone has a color code in their name (Vermeulen) +d bug darkplaces renderer: audit rtlight ambient rendering, apparently scissor is clipping away parts of lights that have ambientscale but not ones that have diffusescale or specularscale (Zenex) +d bug darkplaces renderer: colormap rendering not working on rtlighting passes, resulting in black pants/shirt +d bug darkplaces renderer: colormod is not affecting bmodels (Urre) +d bug darkplaces renderer: compiled rtlights aren't working in modeltest.bsp which is a one cluster map (LordHavoc) +d bug darkplaces renderer: deluxemaps are not detected in some maps that do have them? (SavageX) +d bug darkplaces renderer: don't shut off gl_combine when r_textureunits goes below 2, and don't save gl_combine either +d bug darkplaces renderer: entity culling is ignoring entity scale (daemon) +d bug darkplaces renderer: envmap command includes the hud in the screenshots, bad! +d bug darkplaces renderer: fix fogging in realtime lighting mode, need to split the shaders into two stages, this will also fix decal bugs with fog (Mitchell) +d bug darkplaces renderer: fix q3bsp fogging (Sajt) +d bug darkplaces renderer: fix rtlighting of viewmodel, it should not be performing lighting on a model outside the light radius (LordHavoc) +d bug darkplaces renderer: fix the delayed lightmap updates on bmodels, they're lagging behind one frame, very noticable on flickering light +d bug darkplaces renderer: fix vis problems when outside the level in q1bsp +d bug darkplaces renderer: gl_max_size is affecting bloom (causing a black screen when gl_max_size is less than the screen dimensions) (Willis) +d bug darkplaces renderer: glsl lighting path is not using GL_SRC_ALPHA, GL_ONE +d bug darkplaces renderer: if a texture has the NOLIGHTMAP flag set, disable deluxemapping on the batch, this is needed to fix the glowing stuff in nexuiz maps like the stairs in Glow Arena or the slime pipes in Slime Pit (SavageX) +d bug darkplaces renderer: in full rtlighting mode, deluxemapping gloss still shows up (the diffuse and ambient does not) +d bug darkplaces renderer: make rtlights properly affect transparent models (romi) +d bug darkplaces renderer: make sure that unlit maps show up fullbright (Wazat) +d bug darkplaces renderer: monsters teleporting in really slow down rendering, perhaps the teleport light is casting huge shadows? new information suggests it is the particles. (romi, lcatlnx) +d bug darkplaces renderer: opaque water (r_wateralpha 1) is not being lit by rtlights (Sajt) +d bug darkplaces renderer: q3bsp alpha shaders are not being lit? (Cheapy) +d bug darkplaces renderer: q3bsp ignoring EF_ADDITIVE on opaque surfaces such as Nexuiz teleporters? (Vermeulen) +d bug darkplaces renderer: r_drawcollisionbrushes 2 is broken (LordHavoc) +d bug darkplaces renderer: r_glsl 1 mode has black grapple beam in nexuiz (SavageX) +d bug darkplaces renderer: r_wateralpha 0.9 is invisible on r_glsl 0;gl_combine 0 path (Lardarse] +d bug darkplaces renderer: r_wateralpha 1 water that has lightmapping is black in r_shadow_realtime_world 1 mode, but only if the map was loaded in r_shadow_realtime_world 1 mode, if started in 0 and then going to 1 it does not have black water, this is probably lightmap updates not occurring in rtworld mode (mrinsane) +d bug darkplaces renderer: r_wateralpha on maps that are not watervised shows sky, this is a known glquake bug but it is fixable in darkplaces at load time by marking opposite-content (water-empty, empty-water) leafs as visible in their pvs sets, this involves checking the portal flow... (knghtbrd) +d bug darkplaces renderer: reverse corona traceline direction so that a player in solid can see coronas (Urre) +d bug darkplaces renderer: shadow volumes from q3bsp brush models are broken, maybe inverted or something (Vermeulen) +d bug darkplaces renderer: text coloring is only affecting the first line of messagemode text (LordHavoc) +d bug darkplaces renderer: there's some sort of bug with GL_CullFace, it is sometimes rendering the map using GL_CullFace(GL_NONE) depending on viewpoint +d bug darkplaces renderer: transparent entities are not being lit by rtlights, where as transparent water belonging to an opaque entity (world) is being lit by rtlights (SavageX) +d bug darkplaces renderer: transparent surfaces are not being lit by rtlights (Vermeulen) +d bug darkplaces renderer: vertex normals seem to be generated backwards +d bug darkplaces renderer: vid_restart and r_restart are both crashing (Tomaz) +d bug darkplaces rtlights: light entity import should spawn lights at torch origin so that it does not cast a shadow +d bug darkplaces sdl client: gamma is being lost after a vid_restart +d bug darkplaces server: .colormap is not being set on DP_SV_BOTCLIENT entities the first time, but if removed and spawned again it is set (Urre) +d bug darkplaces server: Blood Mage monsters are stuck in place apparently (steven a) +d bug darkplaces server: SV_SpawnServer should send reconnect command using per-client reliable messages, because sv.reliable_datagram is being cleared +d bug darkplaces server: add TE_FLAMEJET builtin and add extension (Supajoe) +d bug darkplaces server: add \" support to Com_ParseTokenConsole (div0) +d bug darkplaces server: add color code to start of chat message to prevent nick colors from messing up the text color +d bug darkplaces server: call checkvelocity (to clear NaNs) every time velocity is set in physics, to fix frikbot (tell FrikaC) +d bug darkplaces server: cl_movement 0 clients can pogostick jump and do quake2 style double jumps... why? answer: id1 qc move was clearing self.button2 each time it jumped, which causes it to pogostick when client input rate is lower than server framerate, fixed. (div) +d bug darkplaces server: client ping times are often negative after a level change, which shows up in the console on the client because the ping report parser doesn't like negative pings +d bug darkplaces server: don't clear player entity when loading a savegame +d bug darkplaces server: dropclient() is not calling ClientDisconnect on bots during the first level they exist in, it is called on later levels (Urre) +d bug darkplaces server: effect() builtin should post a warning and do nothing if given a framerate below 1 (Willis) +d bug darkplaces server: entity unsticking code should try 1 unit horizontal offsets, then diagonals, then vertical, not diagonal + vertical +d bug darkplaces server: error() qc builtin does not print error message, just Host_Error: Program error or something similar (evilfrog) +d bug darkplaces server: having a csprogs.dat file installed can crash dedicated servers (esteel) +d bug darkplaces server: if sv_fixedframeratesingleplayer is 0 and cl_maxfps is something like 10, the server still runs every frame, consuming massive amounts of cpu and resulting in very small frametime values +d bug darkplaces server: in X-Men: Ravages of Apocalypse the weapon part in x1m3 fails to follow the platform it is on, it is probably spawning inside the ceiling and for some reason not associating with the platform as its groundentity? (qwerasdf) +d bug darkplaces server: in X-Men: Ravages of Apocalypse the weapon part in x2m4 falls out of the level, along with a few other items in the same secret (qwerasdf) +d bug darkplaces server: inconsistent packet timing produces jerky movement (constantly pausing every other frame or so), this is probably the dedicated server's sleep pattern, fixable by using the client's sleep pattern which wastes more cpu time but is more accurate (green`marine) +d bug darkplaces server: local server is not being killed when you join another server (Vermeulen, suminigashi, Willis) +d bug darkplaces server: losing clientcolors somehow during connect in dpmod +d bug darkplaces server: ping should work from server console +d bug darkplaces server: projectiles spawned during client physics called by SV_ReadClientMove are moved on the same server frame, causing them to appear in midair, unlike the normal physics which refuses to move projectiles on their first frame (m0rfar) +d bug darkplaces server: running only one server frame per host frame is really bad in listen servers where the client may be too slow to keep up the server framerate +d bug darkplaces server: self.fixangle is being cleared each frame even if no client move has been performed this frame, which means that predicted clients often see fixangle = 0 due to irregular moves causing PlayerPreThink/PlayerPostThink to not be called every frame +d bug darkplaces server: sending unused lightstyles in serverinfo packet is silly (Spike) +d bug darkplaces server: stats[TOTAL_MONSTERS] should be networked as a stat +d bug darkplaces server: stepping while jumping is setting FL_GROUND (allowing the quake2 doublejump bug) +d bug darkplaces server: sv_jumpstep should be defaulted to off because it makes it possible to reach places one should not be able to reach in quake, it can be turned on by particular games if desired (div0, SavageX, Kazashi) +d bug darkplaces server: the lava+func_trains room of r1m5 is leaving items floating in the air - r1m5 is Towers of Wrath, in episode of Dissolution of Eternity, aka rogue (maichal) +d bug darkplaces server: when server quits, it does not notify the master that it is quitting, it should send out a couple forced heartbeats and assume the master server will remove it because it does not respond (jitspoe, div0) +d bug darkplaces server: when trying to load a map that is missing the model is still precached permanently, causing 'not found' warnings every time r_restart/vid_restart are used +d bug darkplaces sound: sound is sometimes coming from the wrong side apparently (lcatlnx) +d bug darkplaces sound: spatialization bug occurs in The Ascension of Vigil, making all player and monster sounds far to the right (RenegadeC) +d bug darkplaces sound: svc_staticsound messages are being received before sounds are precached, causing no ambient sounds to work (esteel) +d bug darkplaces video: generate 1024 color gamma ramps for glx on Quadro, right now hardware gamma is being disabled on these cards because they use 1024 color ramps, not 256 (div0) +d bug darkplaces wgl client: hardware gamma is being retried every frame for unknown reasons, this is SEVERELY impacting framerates on win2k/xp (Jago) +d bug darkplaces windows general: include libcurl dll from Nexuiz 2.0 in future releases (Baker) +d bug dpmod: air control doesn't seem to be working (Kedhrin) +d bug dpmod: fix sv_user.qc noclip movement when looking straight up/down (Electro) +d bug dpmod: fix the 'shell casing spawning at wrong player' bug somehow +d bug dpmod: items aren't respawning in coop, they should +d bug dpmod: nailgun mine launching doesn't trigger a player animation (sng one does) +d bug dpmod: respawning still on fire (innovati) +d bug dpmod: shouldn't get double kill for killing something and its corpse (Sajt) +d bug dpmodel: md3 exporting is broken on complex models (Morphed) +d bug dpmodel: scale parameter isn't affecting animations (Ghostface) +d bug hmap2: make sure seconds reports in all tools don't print secondssss when they're printing shorter and shorter updates (FrikaC) +d bug hmap2: strip .map extension from filename if present +d bug zmodel: makefile should support mingw +d change darkplaces client: add a swinging weapon motion to replace the removed forward/back movement of the weapon, should be controllable with cl_bob_* cvars (Joel Murdoch) +d change darkplaces client: add some particles to teleportsplash (Uffe) +d change darkplaces client: change timedemo minfps/maxfps to be the lowest and highest fps in one second segments, similar to the showfps display, this should solve the precision problems resulting in stupidly high/low fps reports (m0rfar) +d change darkplaces client: get image sizes from .lmp files if present +d change darkplaces client: implement inversion of non-uniform scaling in Matrix4x4_Invert_Simple or replace it with a full featured matrix inverter +d change darkplaces client: reduce number of particles used, and particle limit, to save some memory (LordHavoc) +d change darkplaces client: tone down scrag and hell knight shot trails +d change darkplaces extensions: add DP_QUAKE3MAP extension to indicate that the engine supports Q3BSP files +d change darkplaces menu: remove gl_combine from menu as it's not saved to config and really shouldn't be changed except when debugging drivers (QuakeMatt) +d change darkplaces model system: change model animations back to their original compressed format (not float[3]), decode them as needed +d change darkplaces prvm: disable the unknown opcode error in progs loading so that fteqcc fastarrays progs will load (Spike) +d change darkplaces prvm: make strzone able to take multiple varargs strings like strcat does (KrimZon) +d change darkplaces renderer: add a r_show_disabledepthtest cvar which defaults to 0 (and could be considered a cheat), and r_show_polygonoffset cvars, rename r_shadow_visible* cvars to r_show*, rename r_drawcollisionbrushes to r_showbrushes, and make all the r_show* cvars control brightness +d change darkplaces renderer: build a temporary msurface_t struct in model renderer and call map surface list renderer, eliminating model surface renderer +d change darkplaces renderer: get rid of DSDT texture support in texture manager, it was only used for the geforce3 water shader which has been removed +d change darkplaces renderer: make meshqueue transparent sorting take a farclip instead of using 4096 +d change darkplaces renderer: make r_showtris only affect ingame view +d change darkplaces renderer: make sprites use skinframe_t instead of their own texture/fogtexture pointers +d change darkplaces renderer: remove GL_NV_texture_shader detection +d change darkplaces renderer: write rendering functions that take msurface_t * lists and migrate most of renderer to them (LordHavoc) +d change darkplaces server: make dedicated server not load images (maybe all fail?) +d change darkplaces server: remove upper limit on sv_maxrate, there's no reason to limit it +d change darkplaces: enable deathmatch scoreboard stuff in coop! (Monster) +d change dpmod: make cells only 30 damage, they're too powerful now (hyenur) +d change dpmod: use sv_maxairspeed cvar (engine) rather than sv_airmaxspeed (qc) cvar in playermovement.qc and default.cfg +d change dpmodel: include the example script in the build zips, not just in the files directory +d change dpmodel: keep all bones instead of removing unused ones (Ghostface) +d change hmap2: increase MAXTOKEN from 1024 to 16384 (FrikaC) +d cleanup darkplaces cleanup: remove cgame* files and any references +d cleanup darkplaces cleanup: remove ui.* files and any references +d cleanup darkplaces console: look at Black's recent console args changes and clean it up as he requested, particularly removing a commented block (Black) +d cleanup darkplaces csqc: cl.csqcentities/cl.num_csqcentities/cl.max_csqcentities are probably entirely unnecessary +d cleanup darkplaces general: get rid of fs_filesize, use parameters/local variables instead (Randy) +d cleanup darkplaces general: replace qbyte with unsigned char (Randy) +d cleanup darkplaces loader: merge msurface_t/q3mface_t, mleaf_t/q3mleaf_t, and mnode_t/q3mnode_t +d cleanup darkplaces prvm: move vm_files and vm_fssearchlist arrays in prvm_cmds.c to prvm_prog_t struct +d cleanup darkplaces renderer: split GLSL program compilation code into shaderobject and programobject functions to reduce code +d cleanup darkplaces server: modify q3bsp traceline code to eliminate unused cases like point testing in traceline +d darkplaces GLX client: make sure that vid_vsync is taking effect immediately +d darkplaces SDL client: add key repeat +d darkplaces WGL client: if gamma setting fails, restore system gamma (RenegadeC) +d darkplaces cleanup: clean up Collision_TraceBrushBrush to have another temp variable besides f and clean up the enterfrac2 handling (Vic) +d darkplaces cleanup: make memory pools have a flag to print them as temporary pools (I.E. consider them leaks if anything is in them) (Vicious) +d darkplaces cleanup: make sure engine dumps log file to disk if there is a Sys_Error (VorteX) +d darkplaces cleanup: merge model format handling (mdl/md2/md3/zym) +d darkplaces cleanup: nodestack[nodestackindex++] = node->children[0]; and similar things should skip the node if stack is full (Vic) +d darkplaces cleanup: port DarkWar polygon.c to darkplaces, as it is more optimized than winding.c +d darkplaces cleanup: rename QuadraticSpline code in curves.c to QuadraticBSpline +d darkplaces client bug: CL_Video_Init is allocating a texture pool, this is not allowed in init functions, it must be moved to cl_video_start and the corresponding free must be moved to cl_video_shutdown, and an empty cl_video_newmap added, these need to be registered with an R_RegisterModule call (Black, Randy) +d darkplaces client bug: ignore modelflags on view weaponmodel - in Malice the double barreled shotgun leaves a smoke trail, and the hellfire rotates, also in Zerstorer the riot shotgun rotates (Hidayat) +d darkplaces client bug: make sure QuakeDoneQuick works (Chris Kemp) +d darkplaces client: (goodvsbad2) increase chase_stevie height to 2048 (yummyluv) +d darkplaces client: Draw_CachePic: failed to load gfx/net.lmp (LordHavoc) +d darkplaces client: GAME_NEXUIZ: don't show monsters/secrets on scores sbar (Vermeulen) +d darkplaces client: GAME_NEXUIZ: horizontally center notify lines (Vermeulen) +d darkplaces client: ValidateState should not error out about colormap > maxclients, only warn (Static_Fiend) +d darkplaces client: add DP_LITSPRITES extension to document the fact that any sprite with a ! in its filename is lit rather than fullbright +d darkplaces client: add GAME_PRYDON mode which would make vore spike trails blue as they're used for ice (Urre, Harb, FrikaC) +d darkplaces client: add a config saving command (Speeds) +d darkplaces client: add ability to load gfx/particlefont.tga (Vermeulen, frightfan, Error) +d darkplaces client: add back random framegroup animation sync for sprites and models so torch flames don't play in sync (Elric) +d darkplaces client: add cl_netlag (ping, like cl_netlocalping_* but no range) and cl_netpacketloss/sv_netpacketloss cvars (packetloss percentage, half of this each way) +d darkplaces client: add cl_particles_particleffect_bloodhack cvar to enable converting id1 blood effects to TE_BLOOD style (Alex Boveri) +d darkplaces client: add color code support to console printing (Vermeulen) +d darkplaces client: add color codes to console, but first need to decide on a prefix character, this can be used to color code stuff in the engine too, but the prefix must be chosen carefully not to mess up most text (Up2nOgOoD) +d darkplaces client: add cvars for sbar alpha (background and foreground) (Throvold@uboot.com) +d darkplaces client: add gl_polyblend cvar to control amount of viewblend effect (Andrew A. Gilevsky) +d darkplaces client: add r_waterwarp cvar to control amount of viewwarping underwater (Andrew A. Gilevsky) +d darkplaces client: add to .rtlights these fields: flags (ambient, diffuse, specular, normalmode, realtimemode), coronascale (0-1), the normalmode flag allows rtlights to forcibly exist in normal mode which is mainly useful for decorative coronas in nexuiz maps (Vermeulen) +d darkplaces client: add two cvars to replace sbar_alpha, one would control background as 0-1, and one would control everything else as 0-1 +d darkplaces client: add video playback handles to the cl_video code so that other systems can use streaming video textures, and allow the menu qc to use these (Black) +d darkplaces client: all glow trails are bright blue (Kinn) +d darkplaces client: change server browser listing structures to store the real data returned from the server, rather than the current processed strings suited only to the menu, menu qc needs to look at the original data (Black) +d darkplaces client: cl_particles_size isn't working, it should affect rendering (Chillo) +d darkplaces client: crosshair traceline should not hit your own model (Willis) +d darkplaces client: don't disconnect before attempting to connect to another server, so if it fails you remain on the current one (RenegadeC, Urre) +d darkplaces client: don't draw entities which are tagged to the camera entity; exterior view models and such +d darkplaces client: don't query servers twice in slist, only add new unique servers when parsing a server list (Willis) +d darkplaces client: dynamic allocation of entity_t structures to save memory (LordHavoc) +d darkplaces client: entities not being removed in quake protocol demos? (MoALTz) +d darkplaces client: figure out why r_editlights_edit cubemap is not setting the light's cubemap name +d darkplaces client: figure out why startdemos is listed twice (once as 0 demos and once as 3 demos) in startup of id1 (Kazashi) +d darkplaces client: figure out why tenebrae style dlights are brilliantly blue, PLUS their color +d darkplaces client: fix 'no such frame 0' warnings in prydon when examining wands in stores +d darkplaces client: fix disappearing decals bug, it seems that when the smoke disappears so do the decals (Urre) +d darkplaces client: got an error: Protocol: Runaway loop recursing tagentity links on entity -7763899 (LordHavoc) +d darkplaces client: increase resolution of particlefont to 512x512 (Chillo) +d darkplaces client: interpolate punchangle and punchvector from network (Sajt) +d darkplaces client: lerp lightstyles (Mitchell) +d darkplaces client: locked console scrollback (sublim3) +d darkplaces client: make CL_RocketTrail2 use the entity to keep track of trail spacing like CL_RocketTrail does (Vic) +d darkplaces client: make blood decals a bit lighter as they're nearly black (ashridah) +d darkplaces client: make cl_beams_relative only affect view-attached beams +d darkplaces client: make colormap > cl.maxclients error be only a warning, to play QDDQ demo of end map (Stribbs) +d darkplaces client: make sure that menu sounds are ATTN_NONE (DarkSnow) +d darkplaces client: make sure the join game menu shows status on a connection attempt, such as connecting or failed (RenegadeC) +d darkplaces client: mini scoreboard (the deathmatch overlay) shows player names multiple times in some cases, fix this! +d darkplaces client: rain drops should splash when they hit (Tomaz, Carni) +d darkplaces client: remove GAME_NETHERWORLD cvars, as they have been added to the default.cfg (VorteX) +d darkplaces client: reset cl.viewzoom on connect (Rick) +d darkplaces client: send one input message per server message instead of using sys_ticrate (LordHavoc) +d darkplaces client: skybox should not be reset by r_restart (Stribbs) +d darkplaces client: when video restarts, set the vsync status variable back to false so it will set it on the first VID_Finish if it is supposed to be on (Tomaz) +d darkplaces collision: mod_q3bsp_optimizedtraceline going through brushes? (Vermeulen) +d darkplaces commandline: make commandline parser ignore + and - if they were not directly following a space, so that + and - can be used in map names and such, also ignore if - or + is start of a number (Urre) +d darkplaces compatibility: add a sv_gameplayfix_setmodeluserealbox to allow people to use the +-16 box hack for running some mods (Spike) +d darkplaces console: add "set" and "seta" commands (DP_CONSOLE_SET and DP_CONSOLE_SETA extensions) to create a cvar and set its value (seta makes a saved cvar) (VorteX) +d darkplaces console: add DP_CON_SET and DP_CON_SETA extensions to describe the set and seta commands +d darkplaces console: add a cvar which sets the start map name so that mods can set their own instead of using "start" or needing to modify the engine (Urre, Elric, Vermeulen) +d darkplaces console: allow typing characters > 126 (LordHavoc) +d darkplaces console: default to insert mode (LordHavoc) +d darkplaces console: exec is broken, it requires two newlines around it, which is breaking the prydon quake.rc (FrikaC) +d darkplaces console: get rid of stupid cursor right at end of line behavior in console, repeating characters from the previous edit line is just annoying (LordHavoc) +d darkplaces console: make typing "; quit " in messagemode NOT quit the game (jitspoe) +d darkplaces console: redesign startup script handling to scan scripts for cvars (ignoring commands) and then init video and then run the scripts for real +d darkplaces docs: add de-we to credits page for the great icons (de-we) +d darkplaces docs: write a readme (Antti) +d darkplaces editlights: add r_editlights_copyinfo and r_editlights_pasteinfo commands to clone the properties of a light, all except for origin (Stribbs) +d darkplaces editlights: add r_editlights_editall command, same as _edit but affects all lights (mashakos) +d darkplaces editlights: fix positioning of light editing display, it's not following the console properly +d darkplaces editlights: light entity loader is broken, it ends up with scrambled colors (avirox, Tomaz) +d darkplaces editlights: r_shadow should load .ent when importing light entities +d darkplaces filesystem: darkplaces-glx -path transfusion crashes, fix the crash even though it's not going to work anyway (Todd) +d darkplaces filesystem: make FS_Open fail to open paths that contain parent directory links (/../ and such, depending on platform) to prevent console commands from doing damage, this is similar to the FRIK_FILE qc extension checking the path (FrikaC, Spike) +d darkplaces game: GAME_FNIGGIUM: "data" directory (not "id1" at all) +d darkplaces game: GAME_FNIGGIUM: 22050/44100 khz sound default +d darkplaces game: GAME_FNIGGIUM: console doesn't show unless you manually pull it down (Sajt) +d darkplaces game: GAME_FNIGGIUM: minimum resolution: 640x480 +d darkplaces general: reduce cl_entities memory usage (LordHavoc) +d darkplaces general: reduce entityframe5_database_t memory usage to reduce per-client memory (LordHavoc) +d darkplaces general: reduce particle_t struct size (LordHavoc) +d darkplaces init: if quake.rc is missing, run the commandline options anyway (LordHavoc) +d darkplaces input: CTRL-V clipboard paste feature in windows (Rick, FrikaC) +d darkplaces input: add more joystick buttons, 3 isn't enough (Static_Fiend) +d darkplaces input: allow typing characters > 128 into console to allow Latin1 fonts to be used properly, already works in text messages (Urre) +d darkplaces input: key repeat should work in menus, for example scrolling quickly through options (Up2nOgOoD) +d darkplaces loader q3bsp: remove snapping to integer on patch vertices (HReaper) +d darkplaces loader: LoadTGA needs to read a palette whether the image uses it or not, and should ignore alpha if there are no attribute bits, to comply with the TGA spec http://netghost.narod.ru/gff/graphics/summary/tga.htm (LordHavoc) +d darkplaces loader: add q2 sprite support sometime +d darkplaces loader: don't report texture loading failure warnings when in dedicated server (Biomass) +d darkplaces loading: clear stainmaps on map restart/change based on cl_stainmapsclearonload cvar (John Truex) +d darkplaces loading: fix bumpmapping, there's something quite mixed up about the svectors and tvectors (Randi) +d darkplaces loading: make files override pak and pk3 archives, as it's really what people expect, hopefully this won't break any broken mods (Stribbs) +d darkplaces loading: make hl map loading halve the lightmap samples, to fit hl's 0-1 range into quake's 0-2 range (KrimZon) +d darkplaces loading: make it only reload rtlights when current map changes, not when restarting renderer or reloading same map (Stribbs) +d darkplaces loading: make sky work without a valid size (just treat it as single layer clouds or non-animated) (tell Vermeulen) +d darkplaces loading: make sure .skin files work on md3 models that have no default shaders but do have mesh names (VorteX) +d darkplaces loading: make sure startup script code executes aliases when doing the cvar scan +d darkplaces loading: missing triangles in q3bsp patches, appears only one of the two triangles per cell is being rendered (Zombie) +d darkplaces makefile: enable sdl builds by default (Spirit_of_85) +d darkplaces math: make portals.c use Polygon_Divide to reduce redundent code (LordHavoc) +d darkplaces menu: add cl_particles_particleffect_bloodhack cvar to menu (Alex Boveri) +d darkplaces menu: add confirm question to "Reset to Defaults" option, with No selected (Sajt) +d darkplaces menu: add graphics options menu and put realtime lighting stuff in it (Antti) +d darkplaces menu: add slowmo to options menu (Cristian Beltramo) +d darkplaces menu: add sv_maxrate cvar to server setup menu +d darkplaces menu: player setup menu network speed is never applying to rate (Mitchell) +d darkplaces menu: r_shadow_realtime_world_lightmaps cvar should be a slider, not a checkbox (Diablo-D3) +d darkplaces parse: support " as an end token for words in Com_Parse +d darkplaces parse: support ' quoted strings +d darkplaces physics: bmodels (doors, etc) hurt player if player pushes against it, and sometimes gets stuck for a frame when falling onto it (Andrew A. Gilevsky) +d darkplaces physics: disable sv_gameplayfix_stepdown while underwater (Sajt) +d darkplaces physics: make players step down stairs rather than just flying off (Riot) +d darkplaces physics: repeatedly jumping against a wall can cause a fall to your death (MoALTz) +d darkplaces physics: some pushers aren't affecting entities standing on them, such as the e3m2 key on a falling platform, monsters on moving floors, and players riding down the e1m1 lift (scar3crow) +d darkplaces physics: standing on a slope that slopes into an obstacle causes a 'falling' condition, velocity keeps increasing (VorteX) +d darkplaces protocol bug: model colormap is showing white on client, is it even being sent? +d darkplaces protocol: GAME_NEXUIZ: add a NEXUIZ_PLAYERMODEL and NEXUIZ_PLAYERSKIN extension which would add playermodel and playerskin commands and set them up like the Host_Name_f stuff works, this means a command for each, saved _cl_playermodel and _cl_playerskin cvars, playermodel and playerskin fields in the client_t struct, and quakec fields to allow access to these similarly to .clientcolors (Vermeulen, Black, VorteX) +d darkplaces protocol: MSG_ReadAngle functions should return +-180 range, not 0-360 (Carni) +d darkplaces protocol: PROTOCOL_DARKPLACES4 malfunctioning after a few seconds, probably not acknowledging packets properly (Sajt) +d darkplaces protocol: add DP_SV_BUTTONCHAT extension to document the addition of .buttonchat to indicate if someone is not in key_game mode, so mods can show a talk bubble if they wish (Vermeulen) +d darkplaces protocol: add DP_SV_BUTTONUSE extension to document the addition of .buttonuse and +use/-use commands (Kazashi) +d darkplaces protocol: add rate command and sv_maxrate cvar (and _cl_rate cvar to save to config) to control client rate (rate is sent to server on connect as a command, like other properties) (protoplasmatic) +d darkplaces protocol: expand viewzoom to two bytes (8bit.8bit fixedpoint instead of 0.8bit like it is now) (Urre) +d darkplaces protocol: make a DP_EF_NODEPTHTEST extension which causes an entity to show through walls, useful for navigation markers (Urre, CheapAlert, Supajoe) +d darkplaces protocol: rename PreciseAngle stuff to Angle16, add Angle8 functions (for EF_LOWPRECISION code), upgrade Angle functions to use Angle16 or Angle8 depending on protocol version, upgrade ammo/armor stats to 16bit (Urre, FrikaC, Sajt, mashakos, RenegadeC, scar3crow) +d darkplaces release: add windres stuff to makefile to compile darkplaces icon into win32 builds (tell de-we) +d darkplaces renderer: .skin loading for models (override skins - not exactly shaders, but adequate, missing replacements are nodraw, this allows q3 player models with optional accessories) (Electro) +d darkplaces renderer: 12bit color textures in 16bit mode?? (Tomaz) +d darkplaces renderer: bloom effect (Vermeulen) +d darkplaces renderer: bmodels all rendering as water in malefic (Harbish) +d darkplaces renderer: check out qe1 textures and make sure they load in all the e1 maps, report of crashing in most but not all maps (Linny Amore) +d darkplaces renderer: check that surface has triangles before adding it to the texturesurfacelist (LordHavoc) +d darkplaces renderer: examine the surface rendering code to make sure it has no bugs regarding texture selection for any of the passes (sublim3) +d darkplaces renderer: figure out the 'inverted bumps' bug on some texture orientations (see crate tops at end of e1m1, tenebrae1 does not suffer this problem somehow) (U8Poo) +d darkplaces renderer: figure out what's wrong with gloss rendering vertex calculations, which may be GF2 related (QorpsE) +d darkplaces renderer: figure out why glow rendering on models seems to get brighter/darker when gl_combine is turned on/off (LordHavoc) +d darkplaces renderer: fix EF_ADDITIVE alias model entities not appearing in realtime lighting mode (VorteX) +d darkplaces renderer: fix a crash when changing level while using qe1 textures (Todd) +d darkplaces renderer: fix loadsky;r_restart;r_restart crashing or showing random textures (Sajt, Randy) +d darkplaces renderer: fix model lighting with r_shadow_realtime_world_lightmaps mode, it seems to be adding dlights to vertices? (Mitchell) +d darkplaces renderer: fix r_shadow_glsl 1 mode on textureless maps (LordHavoc) +d darkplaces renderer: fix rtdlights not rendering in q3bsp (Vermeulen) +d darkplaces renderer: fix the bug causing models in an unlit map to be black when they should be fullbright (Sajt) +d darkplaces renderer: fix the sometimes non-animating framegroups on sprites (Kinn) +d darkplaces renderer: implement PXQ_GFX_LETTERBOX extension (RenegadeC) +d darkplaces renderer: make gl_picmip affect only maps, models, and sprites by setting their TEXF_PICMIP flag (Zenex, Urre) +d darkplaces renderer: make gl_texture_anisotropy take effect immediately like gl_texturemode rather than needing an r_restart (metlslime, zinx) +d darkplaces renderer: make static entities work in realtime lighting mode, like func_illusionary for example, they're currently black (Urre) +d darkplaces renderer: make sure r_novis works (Carni) +d darkplaces renderer: make sure zym code is rendering at correct brightness, it's too dark in nexuiz (Vermeulen) +d darkplaces renderer: r_shadow_glsl 1 mode has inverted normalmaps - r_shadow_glsl 0 is correct (LordHavoc) +d darkplaces renderer: skybox textures are not being freed when changed! (LordHavoc) +d darkplaces renderer: water shader not working with fog (Tomaz) +d darkplaces renderer: zym model rtlight support (Vermeulen) +d darkplaces server: "edict -1" and other invalid numbers cause an error, should just complain (Supajoe) +d darkplaces server: add DP_HALFLIFE_SPRITE extension (Urre) +d darkplaces server: add findflag and findchainflag builtins (Sajt) +d darkplaces server: add sv_gameplayfix_blowupfallenzombies and sv_gameplayfix_findradiusdistancetobox (LordHavoc) +d darkplaces server: add sv_progs cvar, defaulted to "progs.dat", can be set from console or by menu to choose a mod (Black) +d darkplaces server: client colors are being reset to "15 15" each level in prydon gate and dpmod (FrikaC, LordHavoc) +d darkplaces server: don't use popup error window in windows dedicated server crashes (FrikaC) +d darkplaces server: figure out what is wrong with dedicated server console on win32 and fix it (and tell Willis) +d darkplaces server: figure out what's making monsters act like notarget is on while underwater (romi) +d darkplaces server: figure out why zombies are disappearing when not entirely submerged in some hipnotic maps (romi) +d darkplaces server: fix SV_SoundIndex warnings in sv_protocolname DARKPLACES5 (Spike) +d darkplaces server: fix the sys_ticrate bounds checking, it's constantly setting it to 0.1 when it is already 0.1, don't set it if the change is insignificant +d darkplaces server: for some clients PROTOCOL_DARKPLACES5 stops updating after a short while after a reconnect... why? +d darkplaces server: implement q3bsp playerclip support with SOLID_SLIDEBOX (Vermeulen) +d darkplaces server: make a getattachmentvectors qc builtin (Supajoe, Urre) +d darkplaces server: make findradius use areagrid scans to speed up searching (Urre, Sajt) +d darkplaces server: make qc profile command post an error message instead of crashing when used during demo playback (Sajt) +d darkplaces server: make server able to work without models, just for sake of completeness +d darkplaces server: make sys_ticrate impose a maximum frame time so that it calls SV_Physics multiple times in one frame to avoid slowing down +d darkplaces server: prevent player name changes faster than once every 5 seconds (sublim3) +d darkplaces server: still says " disconnected" in dpmod, figure out why and fix it +d darkplaces server: stop sound before loading a level to get rid of looping noise (Edward Holness) +d darkplaces sound: add a sound unloader of some sort, to allow music and other one-level stuff to be unloaded +d darkplaces sound: clear sound buffer at startup so it doesn't play static during startup on windows (FrikaC) +d darkplaces sound: dsound broken, needs to be managed as part of video system (jeremy janzen) +d darkplaces sound: make sound engine restart ambients after a restart (RenegadeC) +d darkplaces sound: make sound loader check both sound/%s and %s, incase a sound (like music/something.wav in q3 maps) is not in the sound directory (Static_Fiend) +d darkplaces sound: make sound precaching not allocate an sfx if the sound is not found, so it complains only once about missing sounds when you connect, rather than constantly, and also so using "play" commands for non-existent files won't eat up sfx slots (fuh) +d darkplaces sound: non-cd music tracks should not be affected by sound volume setting (Urre) +d darkplaces video: add vid_vsync cvar and also to options menu (metlslime) +d darkplaces: Host_Name_f validate player names, stripping \r and \n +d darkplaces: PF_traceline/PF_tracebox now work with world as the edict +d darkplaces: Quake3 bsp support (Vermeulen, Mitchell, Sajt) +d darkplaces: TEXF_CLAMP needs to use GL_CLAMP_TO_EDGE (if not supported just use REPEAT as a fallback, not aware of any cards that lack this) +d darkplaces: Zerstorer: riot shotgun rotates even as a view model: need to ignore that model flag when a view model +d darkplaces: adaptive patch subdivision levels on X and Y based on r_subdivisions cvar +d darkplaces: add "showdate" cvar +d darkplaces: add "showtime" cvar +d darkplaces: add "skin" and "pflags" parsing to light entity loader in rtlights mode (Electro) +d darkplaces: add -benchmark commandline option which plays a demo, appends the resulting min/max/avg fps to gamedir/benchmark.log with commandline so people know what settings were used, like +exec realtimelow.cfg, +exec realtimemed.cfg, etc (romi) +d darkplaces: add 66.28.32.64 to master server list (Willis) +d darkplaces: add DP_EF_NOSHADOW extension (Urre) +d darkplaces: add DP_GFX_EXTERNALTEXTURES extension (Electro) +d darkplaces: add DP_LITSUPPORT extension and document it +d darkplaces: add DP_SND_OGGVORBIS extension which can be checked by mods to know they can intentionally load .ogg instead of .wav, since the engine prefers wav over ogg normally (CheapAlert) +d darkplaces: add DP_SV_ROTATINGBMODEL extension to explain that MOVETYPE_PUSH/SOLID_BSP support rotation in darkplaces and a demonstration of how to use it without qc modifications (Uffe, Supajoe) +d darkplaces: add GAME_NEXUIZ mode +d darkplaces: add GL_EXT_stencil_two_side support to shadow rendering - note: this got a 77% speedup! (fuh) +d darkplaces: add PF_copyentity error checking for copying to world (yummyluv) +d darkplaces: add a "cmd" command to the client for sending arbitrary commands to the server, mainly for use with KRIMZON_SV_PARSECLIENTCOMMAND +d darkplaces: add a "edictset" command to console to set a single field of an edict to the specified value +d darkplaces: add a newline to map name printing +d darkplaces: add a scr_screenshot_jpeg_quality cvar (Electro) +d darkplaces: add airborn blood images to the particlefont which would look like a cloud of droplets (Vermeulen) +d darkplaces: add an extension for EF_RED and EF_BLUE (FrikaC) +d darkplaces: add an optimized special case to AngleVectors for roll == 0, thanks to fuh for the idea +d darkplaces: add and document DP_HALFLIFE_MAP_CVAR extension (the cvar which has existed for a long time) +d darkplaces: add and document DP_SND_DIRECTIONLESSATTNNONE extension +d darkplaces: add and document DP_SND_STEREOWAV extension +d darkplaces: add anisotropic filtering options (Zombie_13, zinx) +d darkplaces: add bullet hole decals to the particlefont (Vermeulen) +d darkplaces: add cl_decals to effects options menu +d darkplaces: add cl_particles_quality cvar (1-10) which would scale count of particles and inversely scale alpha of particles (TheBeast) +d darkplaces: add constant insertion capabilities to Image_CopyMux +d darkplaces: add cubemap px/nx/py/ny/pz/nz loading in skybox loader +d darkplaces: add cvar_string builtin (Paul Timofeyev) +d darkplaces: add display of current cursor coordinates in realtime lighting mode (Stribbs) +d darkplaces: add error messages to LHNET_OpenSocket_Connectionless or its callers (Zombie13) +d darkplaces: add extension for tenebrae dlight entities +d darkplaces: add file access functions and string handling (diGGer) +d darkplaces: add fov to menu +d darkplaces: add gl_lightmaps cvar to disable texturing except lightmaps for testing (Vic) +d darkplaces: add gl_texture_anisotropy to menu (Static_Fiend) +d darkplaces: add lightning beam settings to menu (romi) +d darkplaces: add log cvar to set console logging target (default "", or default "qconsole.log" if -condebug is used) +d darkplaces: add multiple skin support to md2/md3 (Vermeuln) +d darkplaces: add ogg music playback using optional library after adding wav music playback (Joseph Caporale, Static_Fiend, Akuma) +d darkplaces: add r_shadow_realtime_world_lightmaps cvar to control lightmap brightness (Mitchell) +d darkplaces: add r_showtris cvar (Riot) +d darkplaces: add scr_conbrightness cvar (0-1) to control brightness of conback (0 = black and does not load conback, resets back to 0 if conback fails to load) +d darkplaces: add skin and pflags support to light entity loader +d darkplaces: add some cl_explosions_ cvars to control settings - start alpha, end alpha, start size, end size, life time (Supajoe, Mercury) +d darkplaces: add stats to slist menu displaying how many masters/servers have been queried and replied (tell yummyluv) +d darkplaces: add support for multiple -game's (note: this needs an enhanced COM_CheckParm to find the multiple matches) (Static_Fiend) +d darkplaces: add support for red/cyan and red/green and red/blue anaglyph stereo glasses +d darkplaces: add sv_freenonclients cvar (Vermeulen) +d darkplaces: add sv_gameplayfix_grenadebouncedownslopes cvar (default 1) +d darkplaces: add sv_gameplayfix_noairborncorpse cvar (default 1) +d darkplaces: add sv_gameplayfix_stepwhilejumping cvar (default 1), note that sv_jumpstep must also be on to enable this +d darkplaces: add sv_gameplayfix_swiminbmodels cvar (default 1) +d darkplaces: add tenebrae light entity properties, like cubemap and style and such +d darkplaces: add vid_pixelaspect patch from Grisha Spivak's email +d darkplaces: add view height to chase_active again (yummyluv) +d darkplaces: add wav music playback (Joseph Caporale, Static_Fiend) +d darkplaces: add wgl support for mouse buttons 4 and 5 (Intellimouse Explorer) from Q2 (source supplied in email from joseph caporale@sbcglobal.net) +d darkplaces: added RENDER_LIGHT flag to entity_render_t to make rtlighting optional per entity +d darkplaces: added silly scr_zoomwindow as an experiment, turned out mostly useless +d darkplaces: cap packet size at 1k for non-local connections, regardless of their rate setting +d darkplaces: change R_Shadow_VertexShading functions to use sqrt and VectorLength2 instead of two VectorLength calls (Vic) +d darkplaces: change cl_fakelocalping_min and _max to only lag by half each way, as currently it results in 2x ping +d darkplaces: change particle engine to not compact particles array, but keep track of highest used number, update it each frame (Tomaz) +d darkplaces: check if nodrawtoclient works and if not, fix it (Uffe) +d darkplaces: cleaned up rtlight handling, merging most code between world rtlights and dlights +d darkplaces: collision: 'wall stuttering' collision bugs: getting stuck and nudged out constantly when sliding along certain walls +d darkplaces: collision: client getting fraction out of bounds errors when in a map the client does not have +d darkplaces: collision: q3bsp curve problems: comparing nudged impacts causes player to hit edges of triangles in a q3bsp curve closer than the surface +d darkplaces: colors of player in demos seems to alter player config (this is clearly a more severe problem than just demos) (tkimmet@ezworks.net) +d darkplaces: console scrolling should not reset when new messages appear +d darkplaces: crashes if you type too long a command line in the console (Sajt) +d darkplaces: debug server crash +d darkplaces: dedicated server hosting prydon with multiple players exhibited severe networking bugs in tests, including failure to find acked network frames, and a segfault (Supajoe, Uffe, FrikaC, Harb) +d darkplaces: dedicated server should error out if it has no sockets (yummyluv) +d darkplaces: dedicated server should not bother allocating a loopback socket (yummyluv) +d darkplaces: default a few cvars accordingly for GAME_TENEBRAE mode +d darkplaces: default deathmatch 1 in multiplayer games like Nexuiz incase someone starts a game from console (Vermeulen) +d darkplaces: default to 32bit color +d darkplaces: default to sv_cullentities_pvs mode again... trace is too slow in q3bsp and unreliable by nature anyway +d darkplaces: delay "connect" and "playdemo" and "timedemo" until after video init to cause quicker video startup (KrimZon) +d darkplaces: disable mod_q3bsp_optimizedtraceline by default until it works +d darkplaces: display "No servers found" instead of a cursor when there are none (yummyluv) +d darkplaces: don't accept connect packets after first one (tell Willis) +d darkplaces: don't complain if lightning bolt models are missing in client (Electro, Sajt) +d darkplaces: don't crash if SOLID_BSP is used with modelindex 0 - TargetQuake does this... +d darkplaces: embed a fallback conchars.tga so it can load in an empty directory with a visible console (right now it uses the checkerboard texture) +d darkplaces: figure out and fix vis problems when noclipping out of world in q1bsp and q3bsp +d darkplaces: figure out and fix win32 networking problems +d darkplaces: figure out how monster models are disappearing in waistdeep water in e1m3 (scar3crow) +d darkplaces: figure out random crashes on map changes (Uffe, QorpsE) +d darkplaces: figure out what is breaking with rate limited (partial) entity updates that is losing entities (Urre, FrikaC, Harb) +d darkplaces: figure out what is broken about the shadow volumes or stencil comparisons +d darkplaces: figure out what is causing invalid entity numbers in TouchAreaGrid in world.c - suspicion: problem with reallocation of edicts? +d darkplaces: figure out what is wrong with loading _glow/_luma textures on md3 models (not bsp textures) (kd23 Nexuiz) +d darkplaces: figure out why -sndspeed 22050, 44100 and 16000 are choppy in windows? (cheapalert) +d darkplaces: figure out why disconnections are showing up as " disconnected" +d darkplaces: figure out why dlights are apparently disappearing in nexuiz when far away (Vermeulen) +d darkplaces: figure out why fullbrights are black on models (romi) +d darkplaces: figure out why quad is creating two coronas, one at player and one at 0 0 0 - answer: viewmodel dlight (Tomaz) +d darkplaces: finish new udp networking code (yummyluv) +d darkplaces: finish the partial update support in protocol.[ch] and reduce packet size to 1k to fix NAT routers (yummyluv, Vermeulen, Elric) +d darkplaces: fix "game speed" menu option, it's too far left (Tomaz) +d darkplaces: fix 'fall to death in wedge corner' glitch from quake (Zombie) +d darkplaces: fix 2D attenuation texturing which is all black +d darkplaces: fix PF_substring's mishandling of the end variable (Fuh) +d darkplaces: fix Short format entity origins to fix shell casings sitting in floor/above floor (Tomaz) +d darkplaces: fix black models bug with unlit maps (CheapAlert) +d darkplaces: fix bounding box bugs with viewmodelforclient (diGGer) +d darkplaces: fix broken key repeat on backspace key in console (Mercury, CheapAlert) +d darkplaces: fix broken mouse button display in key binding menu, it shows ??? for mouse buttons (Mercury, Tomaz) +d darkplaces: fix con_notify (should control number of lines) +d darkplaces: fix cubemap upload scaling crashes (Urre) +d darkplaces: fix cursor being flipped in Prydon (FrikaC) +d darkplaces: fix curve collision bugs, catching on edges of triangles +d darkplaces: fix entity glows to use Mod_FindNonSolidLocation... maybe all dlights should? (CTF has this problem with flags) +d darkplaces: fix envmap command, it's saving black again, and is the wrong arrangement (Tomaz) +d darkplaces: fix gl_texturemode change errors (Vic) +d darkplaces: fix intermission failing to move view to intermission camera (romi, Zombie_13) +d darkplaces: fix invisible md3 bug +d darkplaces: fix invisible md3 models when missing textures (John Truex) +d darkplaces: fix key based turning being affected by slowmo - it should not be +d darkplaces: fix logging, setting log_file during the game doesn't open a log apparently (FrikaC) +d darkplaces: fix md3 shadow volumes +d darkplaces: fix network timeouts +d darkplaces: fix non-polygonal lightning beam model pitch (it was backwards) (thanks Eksess for reporting this) +d darkplaces: fix particle trails (I think trail start is identical to trail end) (Supajoe, Sajt) +d darkplaces: fix q3bsp static shadow volumes (currently they are calculated as if novis) +d darkplaces: fix r_drawentities view problem (stops updating r_refdef.vieworg?) (Vic) +d darkplaces: fix r_editlights_edit origin not working (romi) +d darkplaces: fix r_novis +d darkplaces: fix r_shadow_portallight 1 (default) mode (Vermeulen) +d darkplaces: fix server crashing from client timeouts (Moz) +d darkplaces: fix server list not working until you set maxplayers above 1 (Rick) +d darkplaces: fix server list only querying the master to reply (Rick) +d darkplaces: fix skybox geometry (Sajt) +d darkplaces: fix skybox orientation to match glquake/quake2/quake3, it needs to be rotated 90 degrees; +X should be rt (metlslime) +d darkplaces: fix sounds not following entities (yummyluv, Sajt) +d darkplaces: fix starting non-existent maps. (drops to console with an error like it should) +d darkplaces: fix startup on multiplayer games so they don't start a game when executing startdemos unless -listen or -dedicated was used (yummyluv) +d darkplaces: fix suffix table used by cubemap loader to load skyboxes in the correct arrangement, matching the sky (Tomaz) +d darkplaces: fix the dedicated server timing, seems to be using host_maxfps instead of sys_ticrate +d darkplaces: fix the fact singleplayer is using maxplayers 8 +d darkplaces: fix the weird broken config parsing at startup +d darkplaces: fix toggling decals in menu +d darkplaces: fix up comments on USETEXMATRIX stuff a little in r_shadow.c (Vic) +d darkplaces: fix video modes menu as you can select 2048x1536 and then go right to get 0x0 (Sajt) +d darkplaces: fix vis decompression underrun/overrun warnings as the problem appears to be more visleafs than the data contains (Vic) +d darkplaces: fix whatever is breaking in prydon gate town curig (Uffe) +d darkplaces: fix win32 bug where shift key types a character (Black, Sajt) +d darkplaces: fix wrapping textures on sprites/models (Elric) +d darkplaces: fixed SV_TouchAreaGrid to not crash if SV_IncreaseEdicts is called during a touch function, by making a list of edicts to touch and then running through the list afterward (romi) +d darkplaces: fov limit now 1-170, was 10-170 +d darkplaces: frikbot scores don't update - discovered this is because of the fact they have no client (Todd) +d darkplaces: generate tvectors the same as svectors in bumpvector calculations (Riot) +d darkplaces: get rid of frags per hour rating in deathmatch scoreboard and mini scoreboard +d darkplaces: get rid of stencil options and make 32bit color automatically use stencil +d darkplaces: give each gamemode a default screenshot name pattern, and make -game override the name pattern to match the mod dir (Rick) +d darkplaces: gl_flashblend 1 should disable dlighting of models (Tomaz) +d darkplaces: have a look at CFQ and figure out why its b0rked (it assumed nq noclip movement) +d darkplaces: heartbeat should print an error message if used with no server running (yummyluv) +d darkplaces: identify weird lightmap texturing bug on TNT cards - goes away in r_textureunits 1 (NotoriousRay, Uffe) +d darkplaces: implement cubemap support on rtlights (romi, Vermeulen, Mitchell) +d darkplaces: improve framerate limiting to sleep until next frame, instead of just sleeping a little +d darkplaces: improve tenebrae compatibility by handling EF_FULLDYNAMIC flag in tenebrae mode, also make all sprites render additive +d darkplaces: integrate zinx's psycho.c gamma hack as an easteregg (zinx) +d darkplaces: intermission: origin and angles are wrong: probably not getting them from entity correctly (resolved: rewrote view setup and fixed timerefresh and envmap command bugs in the process, and also fixed listener positioning during intermissions) +d darkplaces: intermission: statusbar disappears (resolved: not fixed, people seem to kind of prefer it this way) +d darkplaces: intermission: view model isn't disappearing (resolved: fixed) +d darkplaces: keep track of min and max fps (based on single frame frametime) during timedemo and print these stats (romi) +d darkplaces: limit maximum lerp time on animations to .1 seconds (Vermeulen) +d darkplaces: loadgame broken (Linny Amore) +d darkplaces: make 22khz ogg files not crash (CheapAlert) +d darkplaces: make 48khz ogg files load (CheapAlert) +d darkplaces: make Com_HexDumpToConsole not use color +d darkplaces: make DP_EF_FULLBRIGHT extension (FrikaC) +d darkplaces: make LHNET_OpenSocket_Connectionless call getsockname to find out the address/port of the socket +d darkplaces: make LHNET_Read print out the names of read errors (yummyluv) +d darkplaces: make MAX_PACKETFRAGMENT a property of each net connection, so memory loopbacks could use huge limits (Sajt) +d darkplaces: make Mem_Free function clear memory only if developer is on +d darkplaces: make S_Update take a matrix4x4_t * +d darkplaces: make TE_EXPLOSION2 use a spherical spawn pattern rather than cube shape (VorteX) +d darkplaces: make bounce check for fabs(dotproduct)<60 velocity, not dotproduct<60, so now an explosion above gibs will cause them to bounce up into the air +d darkplaces: make client load .ent files +d darkplaces: make console editing allow cursoring left/right on the line and insert and delete, etc (Vic) +d darkplaces: make envmap also save px/nx/py/ny/pz/nz images, in addition to the ft/bk/lf/rt/up/dn skybox arrangement (Tomaz) +d darkplaces: make fopen builtin check / as well as data/ when reading (writing would always go in data/) +d darkplaces: make light_lev dlights from qc require PFLAGS_FULLDYNAMIC flag +d darkplaces: make lightning work without bolt models persent (Vermeulen) +d darkplaces: make missing skins show as white on models (Electro) +d darkplaces: make model lerping optional +d darkplaces: make most QC builtin give warnings instead of errors, so broken mods still run +d darkplaces: make notify lines show based on cl.time, not realtime, so they last the proper length when using cl_avidemo (Urre) +d darkplaces: make r_fullbrights affect model skins, not just map textures +d darkplaces: make reliable message splitting use a different limit than unreliable message size, to fix NAT routers (yummyluv) +d darkplaces: make rocket trail have an orange glow +d darkplaces: make screenshots save to screenshots directory (Sajt) +d darkplaces: make screenshots save to screenshots/fniggium%04i.tga in GAME_FNIGGIUM (Sajt) +d darkplaces: make sprite lerping optional (yummyluv) +d darkplaces: make sure 24bit sky textures work (Static_Fiend) +d darkplaces: make sure EF_FULLBRIGHT works on bmodels (FrikaC) +d darkplaces: make sure EF_FULLBRIGHT works on models (FrikaC) +d darkplaces: make sure EF_FULLBRIGHT works on sprites (FrikaC) +d darkplaces: make sure PR_SetString points NULL strings at pr_strings (which would be an offset of 0) (Fuh) +d darkplaces: make sure r_drawportals works +d darkplaces: make sure r_fullbright works +d darkplaces: make sure that disappearing entities are removed on the client in quake demos +d darkplaces: make sure that sound engine does not remove sounds when volume drops to 0 due to going out of range - now spawns sounds even if out of range (Sajt) +d darkplaces: make sure that textureless models are white and not invisible, apparently creating a .bmp texture (not supported) made the models black, even more odd... (McKilled, QorpsE) +d darkplaces: make the WriteEntitiesToClient code call TraceBox directly instead of SV_Move because checking all the entities is far too slow in helm18 (banshee21) +d darkplaces: make the reply receive code drop packets from servers not in the list (Willis) +d darkplaces: make the static light built messages be developer prints (Tomaz) +d darkplaces: make the world lights check pvs bits instead of recursing a box which would tend to touch solids +d darkplaces: make v_cshift affect view even if in a liquid, by adding another cshift slot for it +d darkplaces: make water and sky never cast shadows +d darkplaces: make water scrolling optional +d darkplaces: may be reading md3 tag matrices wrong (Electro) +d darkplaces: memory pool nesting, allowing pools of pools to be batch freed (Vicious) +d darkplaces: merge pvs info for all brush model formats +d darkplaces: moved R_ShadowVolumeLighting to r_shadow.c +d darkplaces: net_slist and the server browser should show servers when they are queried, not just when they reply; which would replace the matching entry (yummyluv) +d darkplaces: net_slist should print out "No network." if networking is not initialized (yummyluv) +d darkplaces: new dpmaster release (Elric, Vic) +d darkplaces: noclipping out the ceiling of q3dm17 crashes (Static_Fiend) +d darkplaces: optimized ray-triangle collision code +d darkplaces: oriented sprites are not responding to angles properly (yummyluv) +d darkplaces: physics bug: fiends can leap through the player (thanks to Tomaz for massive assistance in tracking down this longstanding bug) +d darkplaces: physics bug: rotating bmodels stop when the player blocks them instead of pushing the player +d darkplaces: playerprethink being called before clientconnect? (Electro) +d darkplaces: post new darkplaces build. (email FrikaC) +d darkplaces: post new dpmaster build. +d darkplaces: put new shell casings in dpmod (Tomaz) +d darkplaces: q1bsp trace bug: 'wall hugging' stuttering, also stuttering movement when walking over steps or monsters and causes block on moving doors (Urre, romi, Static_Fiend) +d darkplaces: q1bsp trace bug: bullets don't hit walls at extremely steep angles, especially at very high framerates... +d darkplaces: q1bsp trace bug: movetogoal is broken - monsters are not going around corners, just running into walls (scar3crow) +d darkplaces: q1bsp trace bug: scrags frequently fly through ceilings - this needs to be fixed +d darkplaces: q1bsp: parse submodels before leafs, so that the pvs can be allocated smaller (only enough for the world model's visleafs count) (Vic) +d darkplaces: r_skyscroll1 and r_skyscroll2 cvars (Sajt) +d darkplaces: reduce r_lightningbeam_repeatdistance to 128, 1024 is way too long +d darkplaces: release new hmap (fixes compilation of TF entities for one person, adds support for GTKRadiant Q1Pack by adding -wadpath option) +d darkplaces: release new hqbsp with -wadpath support (also searchs in map's directory and map's parent directory) +d darkplaces: remove dead master server from default masters list (yummyluv) +d darkplaces: remove frags per hour rating from scoreboard because it depends on cl.scores[i]->entertime (which is never set) +d darkplaces: rename R_Model_Brush_ functions to R_Q1BSP_ +d darkplaces: rename cl_fakelocalping_* to cl_netlocalping_* and *_fakepacketloss_* to *_netpacketloss_* +d darkplaces: rename r_picmip and r_max_size and such to glquake names +d darkplaces: rename r_shadow_polygonoffset and r_shadow_polygonfactor to r_shadow_shadow_polygonoffset and r_shadow_shadow_polygonfactor (Urre) +d darkplaces: rename r_shadow_shadows to r_shadow_dlightshadows and add r_shadow_worldshadows (mashakos) +d darkplaces: replace key system with twilight key system, note that this breaks existing mouse4 and mouse5 binds, and adds in_bindmap capability +d darkplaces: restarting server with two people on it, hits the name change timer and thus people rejoin with blank names (romi) +d darkplaces: revert noclip movement to match nq for compatibility with mods that trap movement as input (MauveBib) +d darkplaces: safety checked lightmap access in Mod_Q1BSP_RecursiveLightPoint as one map Sajt uses was crashing (Sajt) +d darkplaces: segfault reading memory in windows when starting a new server from menu (yummyluv) +d darkplaces: server is starting before the "port" cvar is set by commandline and scripts? (yummyluv) +d darkplaces: shadow volume rendering should not unlock the arrays between renders (Mercury) +d darkplaces: support water lightmaps for use with hmap2 water lighting +d darkplaces: tags support on md3 (Electro needs this urgently) +d darkplaces: te_explosion2 builtin needs to be fixed, it is missing the colorlength parameter, update pr_cmds.c and dpextensions.qc (VorteX) +d darkplaces: tenebrae dlights have reversed pitch (like v_angle, not model angles), make DP match this +d darkplaces: tweak the blood decals in the particlefont to make them look more like the q2e_blood.avi video (Vermeulen) +d darkplaces: typing ip in join game menu should show 'trying' and 'no response' after a while, or 'no network' if networking is not initialized (yummyluv) +d darkplaces: update darkplaces readme +d darkplaces: upgrade network protocol to send precise angles for entities, and make EF_LOWPRECISION downgrade both origin and angles, note this does not cover svc_setangle (Urre, Wazat for Battlemech, FrikaC, mashakos, RenegadeC, Sajt) +d darkplaces: upgrade punchangle protocol to 16bit angles for smoother motion (Urre) +d darkplaces: worked around Intel precision bug with view blends (they were not covering one line of the screen, due to being so huge that it had precision problems, on ATI and NVIDIA) (Sajt) +d dpmaster: add a commandline option to dpmaster that remaps a server ip to another ip, so LordHavoc can make his server show up on his dpmaster +d dpmaster: rename 'interface' variable so it compiles in MSVC, interface is a compiler keyword (Vic) +d dpmod: add a "monsterwander" cvar and default it off, this would enable the spawnwanderpath code (Zombie13) +d dpmod: add back charge-up on plasma rifle alt-fire and increase the max charge to 50 cells +d dpmod: add back nails in walls, even if only in singleplayer (Zenex) +d dpmod: add back tarbaby gibs (scar3crow) +d dpmod: add combo kill detection; rapid burst of kills (Sajt) +d dpmod: add flame thrower enforcers back (scar3crow) +d dpmod: add flame thrower weapon, and make its altfire drop a canister of fuel (10 fuel units?), which can be ignited to set off as a bomb about the size of a rocket blast, plus some fireballs raining down (scar3crow) +d dpmod: add frags for killing monsters in dpmod (scar3crow) +d dpmod: add killing spree reporting; how many kills since spawn when you die, as well as announcing when you hit certain numbers of kills (Sajt) +d dpmod: add q3bsp teleport target entity +d dpmod: add rotfish to spawnmonsters code (only spawn if they land in water) (Zombie) +d dpmod: add support for info_player_deathmatch in singleplayer for q3 compatibility (Static_Fiend) +d dpmod: add target_position entity for a touch of q3 compatibility on jumppads (Static_Fiend) +d dpmod: apparently can't fire in start.bsp? (scar3crow) +d dpmod: change weapons 8-10 to lightning, plasma, plasma wave (joe hill) +d dpmod: fix backpacks (giving no ammo) +d dpmod: fix the plasma wave doing excessive damage at low framerates +d dpmod: impulse 154 should cycle to deathmatch 7 (Rick) +d dpmod: make a skill 4 mode where monsters are nearly invisible (alpha 0.2?) except when attacking or in pain +d dpmod: make enforcers drop more cells for plasma gun (Sajt) +d dpmod: make grapple off-hand (joe hill) +d dpmod: make grunts reload less often, like every 10 shotgun shells (scar3crow) +d dpmod: make tarbabies easier to kill? (Sajt) +d dpmod: make tarbabies explode larger (Sajt) +d dpmod: make the in-wall spikeballs only appear in developer 1 mode (Tomaz) +d dpmod: modify anglemod to be able to recover from extremely large angles numbers (Zombie13) +d dpmod: post new dpmod build. +d dpmod: revert back to id1 weapons +d dpmod: set oldorigin when spawning to prevent being stuck at the spawn from causing an instant teleport back to where you died (Sajt) +d dpmod: switch to new Tomaz weapon models +d dpmod: up nail limit to 500 (scar3crow) +d dpmod: use Tomaz's ammo box models (Tomaz) +d dpmod: why can't I pick up nails when I have no nailguns? and other similar pickup problems with weapons +d dpzoo.map: colored lighting +d dpzoo.map: rain +d dpzoo.map: skybox +d dpzoo.map: snow +d dpzoo.map: transparent glass bmodels (DP_ENT_ALPHA) +d feature darkplaces client extensions: DP_EF_NOSELFSHADOW extension (ChrisP) +d feature darkplaces client: add .loc file support and say macros +d feature darkplaces client: add BX_WAL_SUPPORT to extensions and document it, the feature has been in for a long time, also update wiki.quakesrc.org accordingly +d feature darkplaces client: add a dot crosshair texture (HellToupee) +d feature darkplaces client: add a sv_fixedframeratesingleplayer cvar (default off), to allow fixed framerate singleplayer mods, mainly useful for physics (Urre) +d feature darkplaces client: add qw protocol support (making darkplaces work as a qwcl client) (Amish, Fuh) +d feature darkplaces client: add showbrand cvar which would show gfx/brand.tga in the left/right top/bottom corner (depending on value of scr_showbrand) all the time, this would be useful for screenshots (Spirit_of_85) +d feature darkplaces client: cl_capture_video avi support would be nice, the Intel(r) 4:2:0 codec seems to be standard on Windows XP so this should be easy +d feature darkplaces client: make tab completion able to complete map names when using a map or changelevel command (Zenex, Eksess) +d feature darkplaces client: mod browser (and ability to switch mods), this depends on "gamedir" cvar todo item (mashakos, FrikaC) +d feature darkplaces client: play sound/misc/talk2.wav instead of sound/misc/talk.wav for team chat messages, and indicate team chat messages with () around the playre name, which is compatible with other engines (Yellow No. 5) +d feature darkplaces client: query qw masters for server browser +d feature darkplaces client: v_deathtilt cvar (Sajt, MauveBib) +d feature darkplaces console: "toggle" console command present in doom3: toggle , and toggle (Dresk) +d feature darkplaces console: add a "maps" command which takes the list from "dir maps/*.bsp" and prints the actual names of all the levels according to their worldspawn.message keys (RPG, Zenex, Eksess) +d feature darkplaces console: add condump command to output recent console history (note: wordwrap will remain, trailing spaces will be stripped though), and add it to the readme (Edward Holness) +d feature darkplaces console: change commandline history to clear the commandline when cursoring below the most recent history, and not allow cursoring back more than the oldest history (up2nogood) +d feature darkplaces console: expand parameters such as $cvar to use the value of the cvar, DP_CON_EXPANDCVAR (up2nogood) +d feature darkplaces console: make aliases given parameters insert the parameters in place of $1, $2, $* macros in the alias string, add this as DP_CON_ALIASPARAMETERS (up2nogood) +d feature darkplaces csqc: add "pl" support in getplayerkey function (Dresk) +d feature darkplaces csqc: add builtins to clientside qc for rendering arbitrary polygon meshes +d feature darkplaces csqc: add clientside quakec (KrimZon, FrikaC) +d feature darkplaces cvars: sort cvars and commands by name so that when saved to config they are sorted (might also be able to remove sorting from cvar/command listing) +d feature darkplaces editlights: add coronasize setting to rtlights (romi) +d feature darkplaces extensions: document DP_QC_UNLIMITEDTEMPSTRINGS extension explaining the new tempstring system and the prvm_tempstringmemory cvar, add a note to DP_QC_MULTIPLETEMPSTRINGS that it is superceded by DP_QC_UNLIMITEDTEMPSTRINGS when present +d feature darkplaces filesystem: gamedir command to switch between mods, should be able to take multiple parameters to load multiple mods ontop of eachother like the -game commandline option can (FrikaC) +d feature darkplaces init: add -demo option like -benchmark except playdemo instead of timedemo +d feature darkplaces init: add -demolooponly option which makes escape key quit, and disables all other keys (Speedy) +d feature darkplaces loader: support dpm models (Vermeulen) +d feature darkplaces mac osx: add mac osx builds to build script (inertia, mwh) +d feature darkplaces menu: add gl_picmip setting to graphics options menu, and an r_restart button (LordHavoc) +d feature darkplaces menu: add lan searching to the server browser and related code (Vermeulen) +d feature darkplaces menu: add some basic graphics/effects options profiles so that people can choose profiles like "Classic", "Modern", "Excessive", "Realistic", or any other profiles that make sense, may also need to reorganize the graphics/effects options menus to be a bit less confusing (Tron) +d feature darkplaces networking: add "packet serverip:port command" command to send out of band packets, and hexdump the replies (Spike) +d feature darkplaces networking: download individual files on demand from the server (Baker, CanadianSniper, Zop, Dresk, Chris) +d feature darkplaces particles: reimplement quake effects for a cl_particles_quake mode (Mr Fribbles, metlslime) +d feature darkplaces physics: add a sv_ cvar to disable demonland.wav when monsters fall, this would allow getting rid of the GAME_NEXUIZ check in that code +d feature darkplaces playerphysics: add sv_maxairspeed cvar and use it in sv_user.c, default 30 to match quake player physics (Vermeulen) +d feature darkplaces protocol: add "sendcvar " command which executes on clients and forwards a "sentcvar " to the server, which the qc can catch (Urre) +d feature darkplaces protocol: add EF_DOUBLESIDED for double sided entity rendering (disable cull face for this entity) (yummyluv) +d feature darkplaces protocol: add PRYDON_CLIENTCURSOR extension - clientside mouse with highlighting of selected entities with the EF_SELECTABLE flag, and qc fields on the client entity on the server would indicate which entity the cursor is highlighting as well as where it is (Urre, Harb, FrikaC) +d feature darkplaces protocol: add back colormod extension (FrikaC, Uffe, Gilgamesh, Wazat) +d feature darkplaces protocol: add buttons 9-16 (yummyluv) +d feature darkplaces protocol: allow sending of additional precaches during game, this needs to send a reliable message to all connected clients stating the new filename to load, and also to be sent to new connections (VorteX, Vermeulen) +d feature darkplaces renderer: add HalfLife2 style water rendering (Mitchell, Stribbs, Jimmy Freestone) +d feature darkplaces renderer: add a nearclip cvar (Tomaz) +d feature darkplaces renderer: add q3bsp water rendering, both scrolling and watershader (Zombie) +d feature darkplaces renderer: add r_shadow_visiblelighting cvar which draws redish orange polygons similar to visiblevolumes for measuring number of light passes per pixel (Harbish) +d feature darkplaces renderer: v_hwgamma 2 should force use of hardware gamma, ignoring failure return values, this might make fancy gamma ramps work on windows if the driver can bypass windows limitations +d feature darkplaces server: add DP_QC_WRITEUNTERMINATEDSTRING extension (shadowalker) +d feature darkplaces server: add DP_SV_PRINT extension +d feature darkplaces server: add filename/line number reporting to progs stack and opcode printouts (Spike) +d feature darkplaces server: add sv_playerphysicsqc cvar to allow engine to ignore SV_PlayerPhysics function, this would also have to change the reported extensions (Gleeb) +d feature darkplaces server: automatically choose a server port if the bind fails, just keep incrementing the port until it finds an available port (tell Spike) +d feature darkplaces server: finish DP_QC_BOTCLIENT extension docs and implement it (MauveBib, Supajoe) +d feature darkplaces server: make a DP_SV_CUSTOMIZEENTITYFORCLIENT extension which calls a .float customizeentityforclient() function for each client that may see the entity, the function returns TRUE if it should send, FALSE if it should not, and is fully capable of editing the entity's fields, this allows cloaked players to appear less transparent to their teammates, navigation markers to only show to their team, etc (Urre, Supa, Wazat, SavageX, Vermeulen, Spike) +d feature darkplaces sound: add a snd_soundradius cvar, default 1000 (Urre) +d feature darkplaces sound: add snd_speed and snd_channels cvars (hyenur) +d feature darkplaces sound: make Host_Shutdown clear sound buffer to avoid looping while quitting (up2nogood) +d feature darkplaces video: add widescreen mode support, with 3 lists of resolutions in the menu based on aspect ratio setting, using this list http://www.deathmask.net/misc/widescreen.txt and figure out how to bias the fov based on aspect (Willis) +d feature darkplaces: showfps should show spf when below 1fps (Sajt) +d feature dpmodel: merge in jalisk0's patches for halflife2 smd import: http://www.quakesrc.org/forums/viewtopic.php?t=4731 +d feature hmap2: make water have lightmaps (unless -nowaterlightmaps is specified) +d feature lhfire: get lhfire_gui build from Tomaz +d feature modeltools: add a makesp2 tool to make a very simple .sp2 sprite given a base name and frame size, the format is IDS2{} (Morphed) +d feature zmodel: add "rotate" command to rotate around yaw (Vermeulen) +d hmap2 -qbsp: degenerate edge error that occurs in mrinsane's newmap.map file, tyrqbsp does not have this problem (mrinsane) +d hmap2 -vis: fix CompressVis bitbytes to be correct (Transfusion) +d hmap2: add -ambientlight option, with warning that it does not produce a .light file (Harb) +d hmap2: add -minlight option, with warning that it does not produce a .lights file (Harb) +d hmap2: add a -harshshade option which would not have the 90 degrees incidence = grey hack seen in light.exe (Urre) +d hmap2: add tyrlite compatible "delay" settings, with the interpretation of no specified delay being dependent on a -tyrlite option, and add a new type which is a sun light; light cast in a direction, from sky polygons or the void, these light types would warn that they disable .lights files +d hmap2: light not properly figuring out the origin of rotating objects - it should take the "origin" key (FrikaC) +d hmap2: make LoadBrush reject incomplete brushes - they produce bogus polygon boundaries (Tomaz) +d hmap2: release hmap2 (Vic, Supajoe, Urre) +d hmap2: report locations of lights which can not be vis optimized (Urre, FrikaC) +d hmap2: tweak the light point generation a bit more to try to solve the 'corner light' glitch (Urre) +d hmap2: update .bat files to use hmap2 name and remove -noreuse from revis.bat (Vic) +d hmap: add support for GTKRadiant stuff +d lhfire: post lhfire build with example scripts. +d litsupport: fix the one COM_HunkFile call that uses two parameters (glquake took one) and fix the few "//lit support begin" messages at the end of code blocks (metlslime) +d lmp2pcx: post new lmp2pcx build. +d optimization darkplaces renderer: cache collision trace results for more performance in r_shadow_bouncegrid +d optimization darkplaces renderer: initialize more lighting state in R_Shadow_Stage_Light to reduce per-surface overhead (LordHavoc) +d optimization darkplaces renderer: rename r_shadow_glsl_geforcefxlowprecision to r_shadow_glsl_usehalffloat and enable it by default if the extension is present, it's about a 20% speed gain on GF6 compared to 5% on GFFX (SavageX) +d optimization darkplaces server: optimize pvs checking by caching pvs cluster indices corresponding to entity box (Sajt) +d revelation: fix bodies, they're standing due to invalid frame mappings (romi) +d revelation: fix lingering glow on lightning deaths (romi) +d revelation: reduce damage from weapons (romi) +d sv_user.qc: figure out why looking up/down slows movement and fix it (Vermeulen) +d zmodel: fix scale and origin commands (Vermeulen) +f LordHavoc: examine .mb (Maya Binary) file from Electro and learn its format (Electro) +f bug darkplaces capturevideo: cl_capturevideo 1 with sound off is not locking the framerate of a server (Vermeulen) +f bug darkplaces client: decals are not sticking to submodels +f bug darkplaces client: it has been reported that sometimes level changes on quakeworld servers don't load a map, this may be related to downloading? (Baker) +f bug darkplaces client: occasionally when level changes on remote server, Host_Error occurs (LordHavoc) +f bug darkplaces client: occasionally when level changes on remote server, connection stops and console scrolls wildly without user intervention, and it does not print any kind of error to the terminal, vid_restart in this state causes a crash (LordHavoc) +f bug darkplaces client: pain flash seems to be framerate dependent? (Urre) +f bug darkplaces client: when going through a teleporter the cl_movement prediction still interpolates the move (div0) +f bug darkplaces crash: q3dm2 and q3dm11 crash (Stribbs) +f bug darkplaces loader: occasional crash due to memory corruption when doing "deathmatch 1;map start" during demo loop (Willis) +f bug darkplaces model loader: a q1 mdl file with a _1.tga but no _0.tga crashes at load (daemon) +f bug darkplaces physics: GAME_TAOV: Vigil's movement isn't working properly, the qc uses MOVETYPE_STEP and clears FL_ONGROUND every frame and moves using velocity, this is causing a landing sound every frame and causing the player to slide down minor slopes very quickly, this did not occur in Quake, and seems that it must be related to a velocity_z check or FL_ONGROUND check in the MOVETYPE_STEP physics code (RenegadeC, xaGe) +f bug darkplaces physics: figure out why monsters keep making fall pain sound after they've landed in dpmod (Cruaich) +f bug darkplaces renderer: alias layers should have a shadow volume pass so that nodraw textures don't cast a shadow +f bug darkplaces renderer: fix disappearing viewmodel (and other models) when in an unvised q3bsp, or partially inside a wall in q3bsp +f bug darkplaces renderer: modify r_showtris_polygonoffset to push back all filled geometry, not lines, because polygonoffset will not affect GL_LINES at all +f bug darkplaces renderer: r_editlights 1 causes crashes on level change 40% of the time? (romi) +f bug darkplaces renderer: rtlight "style" values are broken, e1m6 trap hall for example (romi) +f bug darkplaces renderer: showfps values 2 and 3 are printing bogus numbers like -2 billion (Willis) +f bug darkplaces renderer: the quake logo shadow is missing in e1m5 rtlights, too much vis optimization... (romi) +f bug darkplaces server: items still falling through the floor in nexuiz, and they seem to fall through more often at smaller sys_ticrate values such as 0.02 rather than 0.05 (GreEn`mArine) +f bug darkplaces server: losing runes on episode completion, completing episode 1 then 2 then 3 causes it to forget 1, then 4 causes it to forget 2 and 3, making it impossible to open the boss gate (James D) +f bug darkplaces server: player entered the game is printed twice, test with +map start +f bug darkplaces sound: remove looping sounds when their owner entity has been removed by network code, this would mean that Nexuiz could have rocket/electro noise again - thought about this a bit more and can't do this (Qantourisc) +f bug darkplaces: client's slowmo detection (measuring packet times and comparing to game time changes) may be making the game unpleasant (Wazat) +f change darkplaces client: hardcode sbar image sizes so they can be replaced with higher quality images +f darkplaces client: add chase_pitch cvar to control pitch angle of chase camera, and chase_angle cvar to control yaw angle of chase camera, and add back chase_right cvar (Electro) +f darkplaces client: figure out why dlights are flashing on/off in TEU, particularly test the flashlight (Electro) +f darkplaces client: fix view blends slightly lingering as time goes on, they should go away completely (Cruaich) +f darkplaces loading: crash when progs/k_spike.mdl isn't found? (CheapAlert) +f darkplaces physics: can't move when stuck in a monster (Sajt) +f darkplaces physics: walking backward toward the cage in e4m2, it's 'sticky' (MoALTz) +f darkplaces protocol: PROTOCOL_DARKPLACES5 not sending skin? (Sajt) +f darkplaces protocol: add DP_EF_HIGHPRECISION to send float origins instead of shorts (VorteX) +f darkplaces protocol: add EF_PARTICLESPAWNER extension (FrikaC, [TACO]) +f darkplaces server: Mem_Alloc crash when entities are spawning, sv_main line 1760 (VorteX) +f darkplaces server: add an extension to indicate that MOVETYPE_WALK works on non-clients (tell FrikaC) +f darkplaces server: add automatic binding to whatever address the machine's hostname resolves to (in addition to 0.0.0.0); see original quake code for examples (yummyluv) +f darkplaces testing: figure out BoxOnPlaneSide crash that happens in dpmod dpdm2 deathmatch 7 occasionally +f darkplaces testing: figure out a workaround for broken gcc optimizers on BoxOnPlaneSide? (Diablo-D3) +f darkplaces video: detect 5x4 video aspect ratio modes (such as 1280x1024) and set a 0.9375 pixel aspect (which is (1280/1024)/(1280/960)) when these modes are active? +f darkplaces: add DP_EF_PRECISEANGLES extension (sends short angles instead of byte), failed because network protocol was upgraded by default (Wazat for Battlemech, FrikaC, mashakos, RenegadeC, Sajt) +f darkplaces: add _0.tga support (per texture) to bsp/md2/md3 loaders +f darkplaces: add a loading screen (gfx/loadback.tga or the loading plaque if that's not found) before loading commences so that people have something to look at when the engine starts... (Sajt) +f darkplaces: add a new TE_TELEPORTSHELL effect which would take an entity and create a fading plasma shell of its model at the moment of teleportation (tell fuh and Mercury about this) +f darkplaces: add another TE_TELEPORT effect that spawns particles at a model's vertices (Urre) +f darkplaces: add crude DML model loading with animation list (ask Riot for dml library) (Mitchell) +f darkplaces: change particle() macro in cl_particles.c to have a do{}while(0) to eat the ; +f darkplaces: client crashes on +button8? (Static_Fiend) +f darkplaces: crashes on radeon in rare situations that seem to occur in dpmod dm 7 mode? (Option42) +f darkplaces: crosshair_size 0 draws incorrectly (Sajt) +f darkplaces: document how polygon collision works in the code (KrimZon) +f darkplaces: examine proquake code to find nat fix and implement similar in darkplaces +f darkplaces: figure out and fix network entity protocol bugs (sublim3) +f darkplaces: figure out what crashes when this sequence is done: r_speeds 1;map anything, crash (Stribbs) +f darkplaces: figure out why bmodels aren't receiving lightmap dlights +f darkplaces: fix colormapping (Demonix) +f darkplaces: fix connecting to proquake servers through routers (Demonix) +f darkplaces: fix sound resampling to not assume sound ends with value 0, and add support for passing in start and end times, as doubles, so that it can handle arbitrary mixing alignments +f darkplaces: hack PF_nextent to skip inaccessible client slots depending on maxplayers - but always report ones that are active at the time (FrikaC) +f darkplaces: hitting capslock and tab at the same time segfaults +f darkplaces: look at and integrate Vic's updated zone.[ch] (Vic) +f darkplaces: make a flag for rtlights that makes them appear in normal mode (not just r_shadow_realtime_world mode) (Vermeulen) +f darkplaces: model interpolation off crashes? (Sajt) +f darkplaces: pointcontents crash when building harvester in gvb2? (yummyluv) +f darkplaces: r_shadow_showtris messes up r_shadow_visiblevolumes color (jitspoe) +f darkplaces: send bmodels even if alpha is 0 or EF_NODRAW is on +f darkplaces: shadows are not working with model tag attachments (Electro) +f darkplaces: should add quake3 shader support even though the language is utterly insane +f dpmod: figure out why the dbsg isn't selectable in deathmatch 7 mode +f dpmod: make tarbabies have a self.resist_explosive = 3; like zombies (Sajt) +f feature darkplaces client: add back cl_particles_lighting cvar and add back the particle lighting (romi) +f feature darkplaces renderer: add cubemap reflections like UT2003 somehow (perhaps entities would define the reflection maps for rooms, and a water entity would take care of the rest?) +f feature darkplaces server: add an extension to check if a file exists outside the data directory, FRIK_FILE can do this but only inside data directory (Error) +f feature dpmod: include .lit and .dlit files for all id1 maps - this idea was rejected due to download size +f feature dpmod: include .vis files for all id1 maps - this idea rejected due to lack of .vis support and download size +f hqbsp: CreateBrushFaces should use RadiusFromBounds for its rotation box code, but hmap is obsolete (Vic) +f optimization darkplaces renderer: change water distortion textures from multiple 2D textures to one 3D texture for smoother animation (Tomaz) +f optimization darkplaces visibility: R_Q3BSP_RecursiveWorldNode should take clipflags parameter and do not cull a node against a plane if the parent node is totally on one side of the plane (Vic) \ No newline at end of file diff --git a/misc/source/darkplaces-src/utf8lib.c b/misc/source/darkplaces-src/utf8lib.c new file mode 100644 index 00000000..b726e220 --- /dev/null +++ b/misc/source/darkplaces-src/utf8lib.c @@ -0,0 +1,2939 @@ +#include "quakedef.h" +#include "utf8lib.h" + +/* +================================================================================ +Initialization of UTF-8 support and new cvars. +================================================================================ +*/ +// for compatibility this defaults to 0 +cvar_t utf8_enable = {CVAR_SAVE, "utf8_enable", "0", "Enable UTF-8 support. For compatibility, this is disabled by default in most games."}; + +void u8_Init(void) +{ + Cvar_RegisterVariable(&utf8_enable); +} + +/* +================================================================================ +UTF-8 encoding and decoding functions follow. +================================================================================ +*/ + +unsigned char utf8_lengths[256] = { // 0 = invalid + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // ascii characters + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 - 0xBF are within multibyte sequences + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // they could be interpreted as 2-byte starts but + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // the codepoint would be < 127 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0 and C1 would also result in overlong encodings + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + // with F5 the codepoint is above 0x10FFFF, + // F8-FB would start 5-byte sequences + // FC-FD would start 6-byte sequences + // ... +}; +Uchar utf8_range[5] = { + 1, // invalid - let's not allow the creation of 0-bytes :P + 1, // ascii minimum + 0x80, // 2-byte minimum + 0x800, // 3-byte minimum + 0x10000, // 4-byte minimum +}; + +/** Analyze the next character and return various information if requested. + * @param _s An utf-8 string. + * @param _start Filled with the start byte-offset of the next valid character + * @param _len Fileed with the length of the next valid character + * @param _ch Filled with the unicode value of the next character + * @param _maxlen Maximum number of bytes to read from _s + * @return Whether or not another valid character is in the string + */ +#define U8_ANALYZE_INFINITY 7 +static qboolean u8_analyze(const char *_s, size_t *_start, size_t *_len, Uchar *_ch, size_t _maxlen) +{ + const unsigned char *s = (const unsigned char*)_s; + size_t i, j; + size_t bits = 0; + Uchar ch; + + i = 0; +findchar: + while (i < _maxlen && s[i] && (bits = utf8_lengths[s[i]]) == 0) + ++i; + + if (i >= _maxlen || !s[i]) { + if (_start) *_start = i; + if (_len) *_len = 0; + return false; + } + + if (bits == 1) { // ascii + if (_start) *_start = i; + if (_len) *_len = 1; + if (_ch) *_ch = (Uchar)s[i]; + return true; + } + + ch = (s[i] & (0xFF >> bits)); + for (j = 1; j < bits; ++j) + { + if ( (s[i+j] & 0xC0) != 0x80 ) + { + i += j; + goto findchar; + } + ch = (ch << 6) | (s[i+j] & 0x3F); + } + if (ch < utf8_range[bits] || ch >= 0x10FFFF) + { + i += bits; + goto findchar; + } +#if 0 + // <0xC2 is always an overlong encoding, they're invalid, thus skipped + while (i < _maxlen && s[i] && s[i] >= 0x80 && s[i] < 0xC2) { + //fprintf(stderr, "skipping\n"); + ++i; + } + + // If we hit the end, well, we're out and invalid + if(i >= _maxlen || !s[i]) { + if (_start) *_start = i; + if (_len) *_len = 0; + return false; + } + + // I'll leave that in - if you remove it, also change the part below + // to support 1-byte chars correctly + if (s[i] < 0x80) + { + if (_start) *_start = i; + if (_len) *_len = 1; + if (_ch) *_ch = (Uchar)s[i]; + //fprintf(stderr, "valid ascii\n"); + return true; + } + + // Figure out the next char's length + bc = s[i]; + bits = 1; + // count the 1 bits, they're the # of bytes + for (bt = 0x40; bt && (bc & bt); bt >>= 1, ++bits); + if (!bt) + { + //fprintf(stderr, "superlong\n"); + ++i; + goto findchar; + } + if(i + bits > _maxlen) { + /* + if (_start) *_start = i; + if (_len) *_len = 0; + return false; + */ + ++i; + goto findchar; + } + // turn bt into a mask and give ch a starting value + --bt; + ch = (s[i] & bt); + // check the byte sequence for invalid bytes + for (j = 1; j < bits; ++j) + { + // valid bit value: 10xx xxxx + //if (s[i+j] < 0x80 || s[i+j] >= 0xC0) + if ( (s[i+j] & 0xC0) != 0x80 ) + { + //fprintf(stderr, "sequence of %i f'd at %i by %x\n", bits, j, (unsigned int)s[i+j]); + // this byte sequence is invalid, skip it + i += j; + // find a character after it + goto findchar; + } + // at the same time, decode the character + ch = (ch << 6) | (s[i+j] & 0x3F); + } + + // Now check the decoded byte for an overlong encoding + if ( (bits >= 2 && ch < 0x80) || + (bits >= 3 && ch < 0x800) || + (bits >= 4 && ch < 0x10000) || + ch >= 0x10FFFF // RFC 3629 + ) + { + i += bits; + //fprintf(stderr, "overlong: %i bytes for %x\n", bits, ch); + goto findchar; + } +#endif + + if (_start) + *_start = i; + if (_len) + *_len = bits; + if (_ch) + *_ch = ch; + //fprintf(stderr, "valid utf8\n"); + return true; +} + +/** Get the number of characters in an UTF-8 string. + * @param _s An utf-8 encoded null-terminated string. + * @return The number of unicode characters in the string. + */ +size_t u8_strlen(const char *_s) +{ + size_t st, ln; + size_t len = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) + return strlen(_s); + + while (*s) + { + // ascii char, skip u8_analyze + if (*s < 0x80) + { + ++len; + ++s; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + break; + // valid character, skip after it + s += st + ln; + ++len; + } + return len; +} + +/** Get the number of characters in a part of an UTF-8 string. + * @param _s An utf-8 encoded null-terminated string. + * @param n The maximum number of bytes. + * @return The number of unicode characters in the string. + */ +size_t u8_strnlen(const char *_s, size_t n) +{ + size_t st, ln; + size_t len = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) + { + len = strlen(_s); + return (len < n) ? len : n; + } + + while (*s && n) + { + // ascii char, skip u8_analyze + if (*s < 0x80) + { + ++len; + ++s; + --n; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + --n; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, n)) + break; + // valid character, see if it's still inside the range specified by n: + if (n < st + ln) + return len; + ++len; + n -= st + ln; + s += st + ln; + } + return len; +} + +/** Get the number of bytes used in a string to represent an amount of characters. + * @param _s An utf-8 encoded null-terminated string. + * @param n The number of characters we want to know the byte-size for. + * @return The number of bytes used to represent n characters. + */ +size_t u8_bytelen(const char *_s, size_t n) +{ + size_t st, ln; + size_t len = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) { + len = strlen(_s); + return (len < n) ? len : n; + } + + while (*s && n) + { + // ascii char, skip u8_analyze + if (*s < 0x80) + { + ++len; + ++s; + --n; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + ++len; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + break; + --n; + s += st + ln; + len += st + ln; + } + return len; +} + +/** Get the byte-index for a character-index. + * @param _s An utf-8 encoded string. + * @param i The character-index for which you want the byte offset. + * @param len If not null, character's length will be stored in there. + * @return The byte-index at which the character begins, or -1 if the string is too short. + */ +int u8_byteofs(const char *_s, size_t i, size_t *len) +{ + size_t st, ln; + size_t ofs = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) + { + if (strlen(_s) < i) + { + if (len) *len = 0; + return -1; + } + + if (len) *len = 1; + return i; + } + + st = ln = 0; + do + { + ofs += ln; + if (!u8_analyze((const char*)s + ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + return -1; + ofs += st; + } while(i-- > 0); + if (len) + *len = ln; + return ofs; +} + +/** Get the char-index for a byte-index. + * @param _s An utf-8 encoded string. + * @param i The byte offset for which you want the character index. + * @param len If not null, the offset within the character is stored here. + * @return The character-index, or -1 if the string is too short. + */ +int u8_charidx(const char *_s, size_t i, size_t *len) +{ + size_t st, ln; + size_t ofs = 0; + size_t pofs = 0; + int idx = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) + { + if (len) *len = 0; + return i; + } + + while (ofs < i && s[ofs]) + { + // ascii character, skip u8_analyze + if (s[ofs] < 0x80) + { + pofs = ofs; + ++idx; + ++ofs; + continue; + } + + // invalid, skip u8_analyze + if (s[ofs] < 0xC2) + { + ++ofs; + continue; + } + + if (!u8_analyze((const char*)s+ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + return -1; + // see if next char is after the bytemark + if (ofs + st > i) + { + if (len) + *len = i - pofs; + return idx; + } + ++idx; + pofs = ofs + st; + ofs += st + ln; + // see if bytemark is within the char + if (ofs > i) + { + if (len) + *len = i - pofs; + return idx; + } + } + if (len) *len = 0; + return idx; +} + +/** Get the byte offset of the previous byte. + * The result equals: + * prevchar_pos = u8_byteofs(text, u8_charidx(text, thischar_pos, NULL) - 1, NULL) + * @param _s An utf-8 encoded string. + * @param i The current byte offset. + * @return The byte offset of the previous character + */ +size_t u8_prevbyte(const char *_s, size_t i) +{ + size_t st, ln; + const unsigned char *s = (const unsigned char*)_s; + size_t lastofs = 0; + size_t ofs = 0; + + if (!utf8_enable.integer) + { + if (i > 0) + return i-1; + return 0; + } + + while (ofs < i && s[ofs]) + { + // ascii character, skip u8_analyze + if (s[ofs] < 0x80) + { + lastofs = ofs++; + continue; + } + + // invalid, skip u8_analyze + if (s[ofs] < 0xC2) + { + ++ofs; + continue; + } + + if (!u8_analyze((const char*)s+ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + return lastofs; + if (ofs + st > i) + return lastofs; + if (ofs + st + ln >= i) + return ofs + st; + + lastofs = ofs; + ofs += st + ln; + } + return lastofs; +} + +Uchar u8_quake2utf8map[256] = { + 0xE000, 0xE001, 0xE002, 0xE003, 0xE004, 0xE005, 0xE006, 0xE007, 0xE008, 0xE009, 0xE00A, 0xE00B, 0xE00C, 0xE00D, 0xE00E, 0xE00F, // specials + 0xE010, 0xE011, 0xE012, 0xE013, 0xE014, 0xE015, 0xE016, 0xE017, 0xE018, 0xE019, 0xE01A, 0xE01B, 0xE01C, 0xE01D, 0xE01E, 0xE01F, // specials + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, // shift+digit line + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, // digits + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, // caps + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, // caps + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, // small + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, // small + 0xE080, 0xE081, 0xE082, 0xE083, 0xE084, 0xE085, 0xE086, 0xE087, 0xE088, 0xE089, 0xE08A, 0xE08B, 0xE08C, 0xE08D, 0xE08E, 0xE08F, // specials + 0xE090, 0xE091, 0xE092, 0xE093, 0xE094, 0xE095, 0xE096, 0xE097, 0xE098, 0xE099, 0xE09A, 0xE09B, 0xE09C, 0xE09D, 0xE09E, 0xE09F, // faces + 0xE0A0, 0xE0A1, 0xE0A2, 0xE0A3, 0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, 0xE0A8, 0xE0A9, 0xE0AA, 0xE0AB, 0xE0AC, 0xE0AD, 0xE0AE, 0xE0AF, + 0xE0B0, 0xE0B1, 0xE0B2, 0xE0B3, 0xE0B4, 0xE0B5, 0xE0B6, 0xE0B7, 0xE0B8, 0xE0B9, 0xE0BA, 0xE0BB, 0xE0BC, 0xE0BD, 0xE0BE, 0xE0BF, + 0xE0C0, 0xE0C1, 0xE0C2, 0xE0C3, 0xE0C4, 0xE0C5, 0xE0C6, 0xE0C7, 0xE0C8, 0xE0C9, 0xE0CA, 0xE0CB, 0xE0CC, 0xE0CD, 0xE0CE, 0xE0CF, + 0xE0D0, 0xE0D1, 0xE0D2, 0xE0D3, 0xE0D4, 0xE0D5, 0xE0D6, 0xE0D7, 0xE0D8, 0xE0D9, 0xE0DA, 0xE0DB, 0xE0DC, 0xE0DD, 0xE0DE, 0xE0DF, + 0xE0E0, 0xE0E1, 0xE0E2, 0xE0E3, 0xE0E4, 0xE0E5, 0xE0E6, 0xE0E7, 0xE0E8, 0xE0E9, 0xE0EA, 0xE0EB, 0xE0EC, 0xE0ED, 0xE0EE, 0xE0EF, + 0xE0F0, 0xE0F1, 0xE0F2, 0xE0F3, 0xE0F4, 0xE0F5, 0xE0F6, 0xE0F7, 0xE0F8, 0xE0F9, 0xE0FA, 0xE0FB, 0xE0FC, 0xE0FD, 0xE0FE, 0xE0FF, +}; + +/** Fetch a character from an utf-8 encoded string. + * @param _s The start of an utf-8 encoded multi-byte character. + * @param _end Will point to after the first multi-byte character. + * @return The 32-bit integer representation of the first multi-byte character or 0 for invalid characters. + */ +Uchar u8_getchar_utf8_enabled(const char *_s, const char **_end) +{ + size_t st, ln; + Uchar ch; + + if (!u8_analyze(_s, &st, &ln, &ch, U8_ANALYZE_INFINITY)) + ch = 0; + if (_end) + *_end = _s + st + ln; + return ch; +} + +/** Fetch a character from an utf-8 encoded string. + * @param _s The start of an utf-8 encoded multi-byte character. + * @param _end Will point to after the first multi-byte character. + * @return The 32-bit integer representation of the first multi-byte character or 0 for invalid characters. + */ +Uchar u8_getnchar_utf8_enabled(const char *_s, const char **_end, size_t _maxlen) +{ + size_t st, ln; + Uchar ch; + + if (!u8_analyze(_s, &st, &ln, &ch, _maxlen)) + ch = 0; + if (_end) + *_end = _s + st + ln; + return ch; +} + +/** Encode a wide-character into utf-8. + * @param w The wide character to encode. + * @param to The target buffer the utf-8 encoded string is stored to. + * @param maxlen The maximum number of bytes that fit into the target buffer. + * @return Number of bytes written to the buffer not including the terminating null. + * Less or equal to 0 if the buffer is too small. + */ +int u8_fromchar(Uchar w, char *to, size_t maxlen) +{ + if (maxlen < 1) + return 0; + + if (!w) + return 0; + + if (w >= 0xE000 && !utf8_enable.integer) + w -= 0xE000; + + if (w < 0x80 || !utf8_enable.integer) + { + to[0] = (char)w; + if (maxlen < 2) + return -1; + to[1] = 0; + return 1; + } + // for a little speedup + if (w < 0x800) + { + if (maxlen < 3) + { + to[0] = 0; + return -1; + } + to[2] = 0; + to[1] = 0x80 | (w & 0x3F); w >>= 6; + to[0] = 0xC0 | w; + return 2; + } + if (w < 0x10000) + { + if (maxlen < 4) + { + to[0] = 0; + return -1; + } + to[3] = 0; + to[2] = 0x80 | (w & 0x3F); w >>= 6; + to[1] = 0x80 | (w & 0x3F); w >>= 6; + to[0] = 0xE0 | w; + return 3; + } + + // RFC 3629 + if (w <= 0x10FFFF) + { + if (maxlen < 5) + { + to[0] = 0; + return -1; + } + to[4] = 0; + to[3] = 0x80 | (w & 0x3F); w >>= 6; + to[2] = 0x80 | (w & 0x3F); w >>= 6; + to[1] = 0x80 | (w & 0x3F); w >>= 6; + to[0] = 0xF0 | w; + return 4; + } + return 0; +} + +/** uses u8_fromchar on a static buffer + * @param ch The unicode character to convert to encode + * @param l The number of bytes without the terminating null. + * @return A statically allocated buffer containing the character's utf8 representation, or NULL if it fails. + */ +char *u8_encodech(Uchar ch, size_t *l) +{ + static char buf[16]; + size_t len; + len = u8_fromchar(ch, buf, sizeof(buf)); + if (len > 0) + { + if (l) *l = len; + return buf; + } + return NULL; +} + +/** Convert a utf-8 multibyte string to a wide character string. + * @param wcs The target wide-character buffer. + * @param mb The utf-8 encoded multibyte string to convert. + * @param maxlen The maximum number of wide-characters that fit into the target buffer. + * @return The number of characters written to the target buffer. + */ +size_t u8_mbstowcs(Uchar *wcs, const char *mb, size_t maxlen) +{ + size_t i; + Uchar ch; + if (maxlen < 1) + return 0; + for (i = 0; *mb && i < maxlen-1; ++i) + { + ch = u8_getchar(mb, &mb); + if (!ch) + break; + wcs[i] = ch; + } + wcs[i] = 0; + return i; +} + +/** Convert a wide-character string to a utf-8 multibyte string. + * @param mb The target buffer the utf-8 string is written to. + * @param wcs The wide-character string to convert. + * @param maxlen The number bytes that fit into the multibyte target buffer. + * @return The number of bytes written, not including the terminating \0 + */ +size_t u8_wcstombs(char *mb, const Uchar *wcs, size_t maxlen) +{ + size_t i; + const char *start = mb; + if (maxlen < 2) + return 0; + for (i = 0; wcs[i] && i < maxlen-1; ++i) + { + /* + int len; + if ( (len = u8_fromchar(wcs[i], mb, maxlen - i)) < 0) + return (mb - start); + mb += len; + */ + mb += u8_fromchar(wcs[i], mb, maxlen - i); + } + *mb = 0; + return (mb - start); +} + +/* +============ +UTF-8 aware COM_StringLengthNoColors + +calculates the visible width of a color coded string. + +*valid is filled with TRUE if the string is a valid colored string (that is, if +it does not end with an unfinished color code). If it gets filled with FALSE, a +fix would be adding a STRING_COLOR_TAG at the end of the string. + +valid can be set to NULL if the caller doesn't care. + +For size_s, specify the maximum number of characters from s to use, or 0 to use +all characters until the zero terminator. +============ +*/ +size_t +COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); +size_t +u8_COM_StringLengthNoColors(const char *_s, size_t size_s, qboolean *valid) +{ + const unsigned char *s = (const unsigned char*)_s; + const unsigned char *end; + size_t len = 0; + size_t st, ln; + + if (!utf8_enable.integer) + return COM_StringLengthNoColors(_s, size_s, valid); + + end = size_s ? (s + size_s) : NULL; + + for(;;) + { + switch((s == end) ? 0 : *s) + { + case 0: + if(valid) + *valid = TRUE; + return len; + case STRING_COLOR_TAG: + ++s; + switch((s == end) ? 0 : *s) + { + case STRING_COLOR_RGB_TAG_CHAR: + if (s+1 != end && isxdigit(s[1]) && + s+2 != end && isxdigit(s[2]) && + s+3 != end && isxdigit(s[3]) ) + { + s+=3; + break; + } + ++len; // STRING_COLOR_TAG + ++len; // STRING_COLOR_RGB_TAG_CHAR + break; + case 0: // ends with unfinished color code! + ++len; + if(valid) + *valid = FALSE; + return len; + case STRING_COLOR_TAG: // escaped ^ + ++len; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': // color code + break; + default: // not a color code + ++len; // STRING_COLOR_TAG + ++len; // the character + break; + } + ++s; + continue; + default: + break; + } + + // ascii char, skip u8_analyze + if (*s < 0x80) + { + ++len; + ++s; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + { + // we CAN end up here, if an invalid char is between this one and the end of the string + if(valid) + *valid = TRUE; + return len; + } + + if(end && s + st + ln > end) + { + // string length exceeded by new character + if(valid) + *valid = TRUE; + return len; + } + + // valid character, skip after it + s += st + ln; + ++len; + } + // never get here +} + +/** Pads a utf-8 string + * @param out The target buffer the utf-8 string is written to. + * @param outsize The size of the target buffer, including the final NUL + * @param in The input utf-8 buffer + * @param leftalign Left align the output string (by default right alignment is done) + * @param minwidth The minimum output width + * @param maxwidth The maximum output width + * @return The number of bytes written, not including the terminating \0 + */ +size_t u8_strpad(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth) +{ + if(!utf8_enable.integer) + { + return dpsnprintf(out, outsize, "%*.*s", leftalign ? -(int) minwidth : (int) minwidth, (int) maxwidth, in); + } + else + { + size_t l = u8_bytelen(in, maxwidth); + size_t actual_width = u8_strnlen(in, l); + int pad = (actual_width >= minwidth) ? 0 : (minwidth - actual_width); + int prec = l; + int lpad = leftalign ? 0 : pad; + int rpad = leftalign ? pad : 0; + return dpsnprintf(out, outsize, "%*s%.*s%*s", lpad, "", prec, in, rpad, ""); + } +} + + +/* +The two following functions (u8_toupper, u8_tolower) are derived from +ftp://ftp.unicode.org/Public/UNIDATA/UnicodeData.txt and the following license +holds for these: + +Copyright © 1991-2011 Unicode, Inc. All rights reserved. Distributed under the +Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +the Unicode data files and any associated documentation (the "Data Files") or +Unicode software and any associated documentation (the "Software") to deal in +the Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell copies +of the Data Files or Software, and to permit persons to whom the Data Files or +Software are furnished to do so, provided that (a) the above copyright +notice(s) and this permission notice appear with all copies of the Data Files +or Software, (b) both the above copyright notice(s) and this permission notice +appear in associated documentation, and (c) there is clear notice in each +modified Data File or in the Software as well as in the documentation +associated with the Data File(s) or Software that the data or software has been +modified. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD +PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN +THIS NOTICE BE LIABLE FOR ANY CLAIM, OR 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 THE DATA FILES OR +SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +these Data Files or Software without prior written authorization of the +copyright holder. +*/ + +Uchar u8_toupper(Uchar ch) +{ + switch(ch) + { + case 0x0061: return 0x0041; + case 0x0062: return 0x0042; + case 0x0063: return 0x0043; + case 0x0064: return 0x0044; + case 0x0065: return 0x0045; + case 0x0066: return 0x0046; + case 0x0067: return 0x0047; + case 0x0068: return 0x0048; + case 0x0069: return 0x0049; + case 0x006A: return 0x004A; + case 0x006B: return 0x004B; + case 0x006C: return 0x004C; + case 0x006D: return 0x004D; + case 0x006E: return 0x004E; + case 0x006F: return 0x004F; + case 0x0070: return 0x0050; + case 0x0071: return 0x0051; + case 0x0072: return 0x0052; + case 0x0073: return 0x0053; + case 0x0074: return 0x0054; + case 0x0075: return 0x0055; + case 0x0076: return 0x0056; + case 0x0077: return 0x0057; + case 0x0078: return 0x0058; + case 0x0079: return 0x0059; + case 0x007A: return 0x005A; + case 0x00B5: return 0x039C; + case 0x00E0: return 0x00C0; + case 0x00E1: return 0x00C1; + case 0x00E2: return 0x00C2; + case 0x00E3: return 0x00C3; + case 0x00E4: return 0x00C4; + case 0x00E5: return 0x00C5; + case 0x00E6: return 0x00C6; + case 0x00E7: return 0x00C7; + case 0x00E8: return 0x00C8; + case 0x00E9: return 0x00C9; + case 0x00EA: return 0x00CA; + case 0x00EB: return 0x00CB; + case 0x00EC: return 0x00CC; + case 0x00ED: return 0x00CD; + case 0x00EE: return 0x00CE; + case 0x00EF: return 0x00CF; + case 0x00F0: return 0x00D0; + case 0x00F1: return 0x00D1; + case 0x00F2: return 0x00D2; + case 0x00F3: return 0x00D3; + case 0x00F4: return 0x00D4; + case 0x00F5: return 0x00D5; + case 0x00F6: return 0x00D6; + case 0x00F8: return 0x00D8; + case 0x00F9: return 0x00D9; + case 0x00FA: return 0x00DA; + case 0x00FB: return 0x00DB; + case 0x00FC: return 0x00DC; + case 0x00FD: return 0x00DD; + case 0x00FE: return 0x00DE; + case 0x00FF: return 0x0178; + case 0x0101: return 0x0100; + case 0x0103: return 0x0102; + case 0x0105: return 0x0104; + case 0x0107: return 0x0106; + case 0x0109: return 0x0108; + case 0x010B: return 0x010A; + case 0x010D: return 0x010C; + case 0x010F: return 0x010E; + case 0x0111: return 0x0110; + case 0x0113: return 0x0112; + case 0x0115: return 0x0114; + case 0x0117: return 0x0116; + case 0x0119: return 0x0118; + case 0x011B: return 0x011A; + case 0x011D: return 0x011C; + case 0x011F: return 0x011E; + case 0x0121: return 0x0120; + case 0x0123: return 0x0122; + case 0x0125: return 0x0124; + case 0x0127: return 0x0126; + case 0x0129: return 0x0128; + case 0x012B: return 0x012A; + case 0x012D: return 0x012C; + case 0x012F: return 0x012E; + case 0x0131: return 0x0049; + case 0x0133: return 0x0132; + case 0x0135: return 0x0134; + case 0x0137: return 0x0136; + case 0x013A: return 0x0139; + case 0x013C: return 0x013B; + case 0x013E: return 0x013D; + case 0x0140: return 0x013F; + case 0x0142: return 0x0141; + case 0x0144: return 0x0143; + case 0x0146: return 0x0145; + case 0x0148: return 0x0147; + case 0x014B: return 0x014A; + case 0x014D: return 0x014C; + case 0x014F: return 0x014E; + case 0x0151: return 0x0150; + case 0x0153: return 0x0152; + case 0x0155: return 0x0154; + case 0x0157: return 0x0156; + case 0x0159: return 0x0158; + case 0x015B: return 0x015A; + case 0x015D: return 0x015C; + case 0x015F: return 0x015E; + case 0x0161: return 0x0160; + case 0x0163: return 0x0162; + case 0x0165: return 0x0164; + case 0x0167: return 0x0166; + case 0x0169: return 0x0168; + case 0x016B: return 0x016A; + case 0x016D: return 0x016C; + case 0x016F: return 0x016E; + case 0x0171: return 0x0170; + case 0x0173: return 0x0172; + case 0x0175: return 0x0174; + case 0x0177: return 0x0176; + case 0x017A: return 0x0179; + case 0x017C: return 0x017B; + case 0x017E: return 0x017D; + case 0x017F: return 0x0053; + case 0x0180: return 0x0243; + case 0x0183: return 0x0182; + case 0x0185: return 0x0184; + case 0x0188: return 0x0187; + case 0x018C: return 0x018B; + case 0x0192: return 0x0191; + case 0x0195: return 0x01F6; + case 0x0199: return 0x0198; + case 0x019A: return 0x023D; + case 0x019E: return 0x0220; + case 0x01A1: return 0x01A0; + case 0x01A3: return 0x01A2; + case 0x01A5: return 0x01A4; + case 0x01A8: return 0x01A7; + case 0x01AD: return 0x01AC; + case 0x01B0: return 0x01AF; + case 0x01B4: return 0x01B3; + case 0x01B6: return 0x01B5; + case 0x01B9: return 0x01B8; + case 0x01BD: return 0x01BC; + case 0x01BF: return 0x01F7; + case 0x01C5: return 0x01C4; + case 0x01C6: return 0x01C4; + case 0x01C8: return 0x01C7; + case 0x01C9: return 0x01C7; + case 0x01CB: return 0x01CA; + case 0x01CC: return 0x01CA; + case 0x01CE: return 0x01CD; + case 0x01D0: return 0x01CF; + case 0x01D2: return 0x01D1; + case 0x01D4: return 0x01D3; + case 0x01D6: return 0x01D5; + case 0x01D8: return 0x01D7; + case 0x01DA: return 0x01D9; + case 0x01DC: return 0x01DB; + case 0x01DD: return 0x018E; + case 0x01DF: return 0x01DE; + case 0x01E1: return 0x01E0; + case 0x01E3: return 0x01E2; + case 0x01E5: return 0x01E4; + case 0x01E7: return 0x01E6; + case 0x01E9: return 0x01E8; + case 0x01EB: return 0x01EA; + case 0x01ED: return 0x01EC; + case 0x01EF: return 0x01EE; + case 0x01F2: return 0x01F1; + case 0x01F3: return 0x01F1; + case 0x01F5: return 0x01F4; + case 0x01F9: return 0x01F8; + case 0x01FB: return 0x01FA; + case 0x01FD: return 0x01FC; + case 0x01FF: return 0x01FE; + case 0x0201: return 0x0200; + case 0x0203: return 0x0202; + case 0x0205: return 0x0204; + case 0x0207: return 0x0206; + case 0x0209: return 0x0208; + case 0x020B: return 0x020A; + case 0x020D: return 0x020C; + case 0x020F: return 0x020E; + case 0x0211: return 0x0210; + case 0x0213: return 0x0212; + case 0x0215: return 0x0214; + case 0x0217: return 0x0216; + case 0x0219: return 0x0218; + case 0x021B: return 0x021A; + case 0x021D: return 0x021C; + case 0x021F: return 0x021E; + case 0x0223: return 0x0222; + case 0x0225: return 0x0224; + case 0x0227: return 0x0226; + case 0x0229: return 0x0228; + case 0x022B: return 0x022A; + case 0x022D: return 0x022C; + case 0x022F: return 0x022E; + case 0x0231: return 0x0230; + case 0x0233: return 0x0232; + case 0x023C: return 0x023B; + case 0x023F: return 0x2C7E; + case 0x0240: return 0x2C7F; + case 0x0242: return 0x0241; + case 0x0247: return 0x0246; + case 0x0249: return 0x0248; + case 0x024B: return 0x024A; + case 0x024D: return 0x024C; + case 0x024F: return 0x024E; + case 0x0250: return 0x2C6F; + case 0x0251: return 0x2C6D; + case 0x0252: return 0x2C70; + case 0x0253: return 0x0181; + case 0x0254: return 0x0186; + case 0x0256: return 0x0189; + case 0x0257: return 0x018A; + case 0x0259: return 0x018F; + case 0x025B: return 0x0190; + case 0x0260: return 0x0193; + case 0x0263: return 0x0194; + case 0x0265: return 0xA78D; + case 0x0268: return 0x0197; + case 0x0269: return 0x0196; + case 0x026B: return 0x2C62; + case 0x026F: return 0x019C; + case 0x0271: return 0x2C6E; + case 0x0272: return 0x019D; + case 0x0275: return 0x019F; + case 0x027D: return 0x2C64; + case 0x0280: return 0x01A6; + case 0x0283: return 0x01A9; + case 0x0288: return 0x01AE; + case 0x0289: return 0x0244; + case 0x028A: return 0x01B1; + case 0x028B: return 0x01B2; + case 0x028C: return 0x0245; + case 0x0292: return 0x01B7; + case 0x0345: return 0x0399; + case 0x0371: return 0x0370; + case 0x0373: return 0x0372; + case 0x0377: return 0x0376; + case 0x037B: return 0x03FD; + case 0x037C: return 0x03FE; + case 0x037D: return 0x03FF; + case 0x03AC: return 0x0386; + case 0x03AD: return 0x0388; + case 0x03AE: return 0x0389; + case 0x03AF: return 0x038A; + case 0x03B1: return 0x0391; + case 0x03B2: return 0x0392; + case 0x03B3: return 0x0393; + case 0x03B4: return 0x0394; + case 0x03B5: return 0x0395; + case 0x03B6: return 0x0396; + case 0x03B7: return 0x0397; + case 0x03B8: return 0x0398; + case 0x03B9: return 0x0399; + case 0x03BA: return 0x039A; + case 0x03BB: return 0x039B; + case 0x03BC: return 0x039C; + case 0x03BD: return 0x039D; + case 0x03BE: return 0x039E; + case 0x03BF: return 0x039F; + case 0x03C0: return 0x03A0; + case 0x03C1: return 0x03A1; + case 0x03C2: return 0x03A3; + case 0x03C3: return 0x03A3; + case 0x03C4: return 0x03A4; + case 0x03C5: return 0x03A5; + case 0x03C6: return 0x03A6; + case 0x03C7: return 0x03A7; + case 0x03C8: return 0x03A8; + case 0x03C9: return 0x03A9; + case 0x03CA: return 0x03AA; + case 0x03CB: return 0x03AB; + case 0x03CC: return 0x038C; + case 0x03CD: return 0x038E; + case 0x03CE: return 0x038F; + case 0x03D0: return 0x0392; + case 0x03D1: return 0x0398; + case 0x03D5: return 0x03A6; + case 0x03D6: return 0x03A0; + case 0x03D7: return 0x03CF; + case 0x03D9: return 0x03D8; + case 0x03DB: return 0x03DA; + case 0x03DD: return 0x03DC; + case 0x03DF: return 0x03DE; + case 0x03E1: return 0x03E0; + case 0x03E3: return 0x03E2; + case 0x03E5: return 0x03E4; + case 0x03E7: return 0x03E6; + case 0x03E9: return 0x03E8; + case 0x03EB: return 0x03EA; + case 0x03ED: return 0x03EC; + case 0x03EF: return 0x03EE; + case 0x03F0: return 0x039A; + case 0x03F1: return 0x03A1; + case 0x03F2: return 0x03F9; + case 0x03F5: return 0x0395; + case 0x03F8: return 0x03F7; + case 0x03FB: return 0x03FA; + case 0x0430: return 0x0410; + case 0x0431: return 0x0411; + case 0x0432: return 0x0412; + case 0x0433: return 0x0413; + case 0x0434: return 0x0414; + case 0x0435: return 0x0415; + case 0x0436: return 0x0416; + case 0x0437: return 0x0417; + case 0x0438: return 0x0418; + case 0x0439: return 0x0419; + case 0x043A: return 0x041A; + case 0x043B: return 0x041B; + case 0x043C: return 0x041C; + case 0x043D: return 0x041D; + case 0x043E: return 0x041E; + case 0x043F: return 0x041F; + case 0x0440: return 0x0420; + case 0x0441: return 0x0421; + case 0x0442: return 0x0422; + case 0x0443: return 0x0423; + case 0x0444: return 0x0424; + case 0x0445: return 0x0425; + case 0x0446: return 0x0426; + case 0x0447: return 0x0427; + case 0x0448: return 0x0428; + case 0x0449: return 0x0429; + case 0x044A: return 0x042A; + case 0x044B: return 0x042B; + case 0x044C: return 0x042C; + case 0x044D: return 0x042D; + case 0x044E: return 0x042E; + case 0x044F: return 0x042F; + case 0x0450: return 0x0400; + case 0x0451: return 0x0401; + case 0x0452: return 0x0402; + case 0x0453: return 0x0403; + case 0x0454: return 0x0404; + case 0x0455: return 0x0405; + case 0x0456: return 0x0406; + case 0x0457: return 0x0407; + case 0x0458: return 0x0408; + case 0x0459: return 0x0409; + case 0x045A: return 0x040A; + case 0x045B: return 0x040B; + case 0x045C: return 0x040C; + case 0x045D: return 0x040D; + case 0x045E: return 0x040E; + case 0x045F: return 0x040F; + case 0x0461: return 0x0460; + case 0x0463: return 0x0462; + case 0x0465: return 0x0464; + case 0x0467: return 0x0466; + case 0x0469: return 0x0468; + case 0x046B: return 0x046A; + case 0x046D: return 0x046C; + case 0x046F: return 0x046E; + case 0x0471: return 0x0470; + case 0x0473: return 0x0472; + case 0x0475: return 0x0474; + case 0x0477: return 0x0476; + case 0x0479: return 0x0478; + case 0x047B: return 0x047A; + case 0x047D: return 0x047C; + case 0x047F: return 0x047E; + case 0x0481: return 0x0480; + case 0x048B: return 0x048A; + case 0x048D: return 0x048C; + case 0x048F: return 0x048E; + case 0x0491: return 0x0490; + case 0x0493: return 0x0492; + case 0x0495: return 0x0494; + case 0x0497: return 0x0496; + case 0x0499: return 0x0498; + case 0x049B: return 0x049A; + case 0x049D: return 0x049C; + case 0x049F: return 0x049E; + case 0x04A1: return 0x04A0; + case 0x04A3: return 0x04A2; + case 0x04A5: return 0x04A4; + case 0x04A7: return 0x04A6; + case 0x04A9: return 0x04A8; + case 0x04AB: return 0x04AA; + case 0x04AD: return 0x04AC; + case 0x04AF: return 0x04AE; + case 0x04B1: return 0x04B0; + case 0x04B3: return 0x04B2; + case 0x04B5: return 0x04B4; + case 0x04B7: return 0x04B6; + case 0x04B9: return 0x04B8; + case 0x04BB: return 0x04BA; + case 0x04BD: return 0x04BC; + case 0x04BF: return 0x04BE; + case 0x04C2: return 0x04C1; + case 0x04C4: return 0x04C3; + case 0x04C6: return 0x04C5; + case 0x04C8: return 0x04C7; + case 0x04CA: return 0x04C9; + case 0x04CC: return 0x04CB; + case 0x04CE: return 0x04CD; + case 0x04CF: return 0x04C0; + case 0x04D1: return 0x04D0; + case 0x04D3: return 0x04D2; + case 0x04D5: return 0x04D4; + case 0x04D7: return 0x04D6; + case 0x04D9: return 0x04D8; + case 0x04DB: return 0x04DA; + case 0x04DD: return 0x04DC; + case 0x04DF: return 0x04DE; + case 0x04E1: return 0x04E0; + case 0x04E3: return 0x04E2; + case 0x04E5: return 0x04E4; + case 0x04E7: return 0x04E6; + case 0x04E9: return 0x04E8; + case 0x04EB: return 0x04EA; + case 0x04ED: return 0x04EC; + case 0x04EF: return 0x04EE; + case 0x04F1: return 0x04F0; + case 0x04F3: return 0x04F2; + case 0x04F5: return 0x04F4; + case 0x04F7: return 0x04F6; + case 0x04F9: return 0x04F8; + case 0x04FB: return 0x04FA; + case 0x04FD: return 0x04FC; + case 0x04FF: return 0x04FE; + case 0x0501: return 0x0500; + case 0x0503: return 0x0502; + case 0x0505: return 0x0504; + case 0x0507: return 0x0506; + case 0x0509: return 0x0508; + case 0x050B: return 0x050A; + case 0x050D: return 0x050C; + case 0x050F: return 0x050E; + case 0x0511: return 0x0510; + case 0x0513: return 0x0512; + case 0x0515: return 0x0514; + case 0x0517: return 0x0516; + case 0x0519: return 0x0518; + case 0x051B: return 0x051A; + case 0x051D: return 0x051C; + case 0x051F: return 0x051E; + case 0x0521: return 0x0520; + case 0x0523: return 0x0522; + case 0x0525: return 0x0524; + case 0x0527: return 0x0526; + case 0x0561: return 0x0531; + case 0x0562: return 0x0532; + case 0x0563: return 0x0533; + case 0x0564: return 0x0534; + case 0x0565: return 0x0535; + case 0x0566: return 0x0536; + case 0x0567: return 0x0537; + case 0x0568: return 0x0538; + case 0x0569: return 0x0539; + case 0x056A: return 0x053A; + case 0x056B: return 0x053B; + case 0x056C: return 0x053C; + case 0x056D: return 0x053D; + case 0x056E: return 0x053E; + case 0x056F: return 0x053F; + case 0x0570: return 0x0540; + case 0x0571: return 0x0541; + case 0x0572: return 0x0542; + case 0x0573: return 0x0543; + case 0x0574: return 0x0544; + case 0x0575: return 0x0545; + case 0x0576: return 0x0546; + case 0x0577: return 0x0547; + case 0x0578: return 0x0548; + case 0x0579: return 0x0549; + case 0x057A: return 0x054A; + case 0x057B: return 0x054B; + case 0x057C: return 0x054C; + case 0x057D: return 0x054D; + case 0x057E: return 0x054E; + case 0x057F: return 0x054F; + case 0x0580: return 0x0550; + case 0x0581: return 0x0551; + case 0x0582: return 0x0552; + case 0x0583: return 0x0553; + case 0x0584: return 0x0554; + case 0x0585: return 0x0555; + case 0x0586: return 0x0556; + case 0x1D79: return 0xA77D; + case 0x1D7D: return 0x2C63; + case 0x1E01: return 0x1E00; + case 0x1E03: return 0x1E02; + case 0x1E05: return 0x1E04; + case 0x1E07: return 0x1E06; + case 0x1E09: return 0x1E08; + case 0x1E0B: return 0x1E0A; + case 0x1E0D: return 0x1E0C; + case 0x1E0F: return 0x1E0E; + case 0x1E11: return 0x1E10; + case 0x1E13: return 0x1E12; + case 0x1E15: return 0x1E14; + case 0x1E17: return 0x1E16; + case 0x1E19: return 0x1E18; + case 0x1E1B: return 0x1E1A; + case 0x1E1D: return 0x1E1C; + case 0x1E1F: return 0x1E1E; + case 0x1E21: return 0x1E20; + case 0x1E23: return 0x1E22; + case 0x1E25: return 0x1E24; + case 0x1E27: return 0x1E26; + case 0x1E29: return 0x1E28; + case 0x1E2B: return 0x1E2A; + case 0x1E2D: return 0x1E2C; + case 0x1E2F: return 0x1E2E; + case 0x1E31: return 0x1E30; + case 0x1E33: return 0x1E32; + case 0x1E35: return 0x1E34; + case 0x1E37: return 0x1E36; + case 0x1E39: return 0x1E38; + case 0x1E3B: return 0x1E3A; + case 0x1E3D: return 0x1E3C; + case 0x1E3F: return 0x1E3E; + case 0x1E41: return 0x1E40; + case 0x1E43: return 0x1E42; + case 0x1E45: return 0x1E44; + case 0x1E47: return 0x1E46; + case 0x1E49: return 0x1E48; + case 0x1E4B: return 0x1E4A; + case 0x1E4D: return 0x1E4C; + case 0x1E4F: return 0x1E4E; + case 0x1E51: return 0x1E50; + case 0x1E53: return 0x1E52; + case 0x1E55: return 0x1E54; + case 0x1E57: return 0x1E56; + case 0x1E59: return 0x1E58; + case 0x1E5B: return 0x1E5A; + case 0x1E5D: return 0x1E5C; + case 0x1E5F: return 0x1E5E; + case 0x1E61: return 0x1E60; + case 0x1E63: return 0x1E62; + case 0x1E65: return 0x1E64; + case 0x1E67: return 0x1E66; + case 0x1E69: return 0x1E68; + case 0x1E6B: return 0x1E6A; + case 0x1E6D: return 0x1E6C; + case 0x1E6F: return 0x1E6E; + case 0x1E71: return 0x1E70; + case 0x1E73: return 0x1E72; + case 0x1E75: return 0x1E74; + case 0x1E77: return 0x1E76; + case 0x1E79: return 0x1E78; + case 0x1E7B: return 0x1E7A; + case 0x1E7D: return 0x1E7C; + case 0x1E7F: return 0x1E7E; + case 0x1E81: return 0x1E80; + case 0x1E83: return 0x1E82; + case 0x1E85: return 0x1E84; + case 0x1E87: return 0x1E86; + case 0x1E89: return 0x1E88; + case 0x1E8B: return 0x1E8A; + case 0x1E8D: return 0x1E8C; + case 0x1E8F: return 0x1E8E; + case 0x1E91: return 0x1E90; + case 0x1E93: return 0x1E92; + case 0x1E95: return 0x1E94; + case 0x1E9B: return 0x1E60; + case 0x1EA1: return 0x1EA0; + case 0x1EA3: return 0x1EA2; + case 0x1EA5: return 0x1EA4; + case 0x1EA7: return 0x1EA6; + case 0x1EA9: return 0x1EA8; + case 0x1EAB: return 0x1EAA; + case 0x1EAD: return 0x1EAC; + case 0x1EAF: return 0x1EAE; + case 0x1EB1: return 0x1EB0; + case 0x1EB3: return 0x1EB2; + case 0x1EB5: return 0x1EB4; + case 0x1EB7: return 0x1EB6; + case 0x1EB9: return 0x1EB8; + case 0x1EBB: return 0x1EBA; + case 0x1EBD: return 0x1EBC; + case 0x1EBF: return 0x1EBE; + case 0x1EC1: return 0x1EC0; + case 0x1EC3: return 0x1EC2; + case 0x1EC5: return 0x1EC4; + case 0x1EC7: return 0x1EC6; + case 0x1EC9: return 0x1EC8; + case 0x1ECB: return 0x1ECA; + case 0x1ECD: return 0x1ECC; + case 0x1ECF: return 0x1ECE; + case 0x1ED1: return 0x1ED0; + case 0x1ED3: return 0x1ED2; + case 0x1ED5: return 0x1ED4; + case 0x1ED7: return 0x1ED6; + case 0x1ED9: return 0x1ED8; + case 0x1EDB: return 0x1EDA; + case 0x1EDD: return 0x1EDC; + case 0x1EDF: return 0x1EDE; + case 0x1EE1: return 0x1EE0; + case 0x1EE3: return 0x1EE2; + case 0x1EE5: return 0x1EE4; + case 0x1EE7: return 0x1EE6; + case 0x1EE9: return 0x1EE8; + case 0x1EEB: return 0x1EEA; + case 0x1EED: return 0x1EEC; + case 0x1EEF: return 0x1EEE; + case 0x1EF1: return 0x1EF0; + case 0x1EF3: return 0x1EF2; + case 0x1EF5: return 0x1EF4; + case 0x1EF7: return 0x1EF6; + case 0x1EF9: return 0x1EF8; + case 0x1EFB: return 0x1EFA; + case 0x1EFD: return 0x1EFC; + case 0x1EFF: return 0x1EFE; + case 0x1F00: return 0x1F08; + case 0x1F01: return 0x1F09; + case 0x1F02: return 0x1F0A; + case 0x1F03: return 0x1F0B; + case 0x1F04: return 0x1F0C; + case 0x1F05: return 0x1F0D; + case 0x1F06: return 0x1F0E; + case 0x1F07: return 0x1F0F; + case 0x1F10: return 0x1F18; + case 0x1F11: return 0x1F19; + case 0x1F12: return 0x1F1A; + case 0x1F13: return 0x1F1B; + case 0x1F14: return 0x1F1C; + case 0x1F15: return 0x1F1D; + case 0x1F20: return 0x1F28; + case 0x1F21: return 0x1F29; + case 0x1F22: return 0x1F2A; + case 0x1F23: return 0x1F2B; + case 0x1F24: return 0x1F2C; + case 0x1F25: return 0x1F2D; + case 0x1F26: return 0x1F2E; + case 0x1F27: return 0x1F2F; + case 0x1F30: return 0x1F38; + case 0x1F31: return 0x1F39; + case 0x1F32: return 0x1F3A; + case 0x1F33: return 0x1F3B; + case 0x1F34: return 0x1F3C; + case 0x1F35: return 0x1F3D; + case 0x1F36: return 0x1F3E; + case 0x1F37: return 0x1F3F; + case 0x1F40: return 0x1F48; + case 0x1F41: return 0x1F49; + case 0x1F42: return 0x1F4A; + case 0x1F43: return 0x1F4B; + case 0x1F44: return 0x1F4C; + case 0x1F45: return 0x1F4D; + case 0x1F51: return 0x1F59; + case 0x1F53: return 0x1F5B; + case 0x1F55: return 0x1F5D; + case 0x1F57: return 0x1F5F; + case 0x1F60: return 0x1F68; + case 0x1F61: return 0x1F69; + case 0x1F62: return 0x1F6A; + case 0x1F63: return 0x1F6B; + case 0x1F64: return 0x1F6C; + case 0x1F65: return 0x1F6D; + case 0x1F66: return 0x1F6E; + case 0x1F67: return 0x1F6F; + case 0x1F70: return 0x1FBA; + case 0x1F71: return 0x1FBB; + case 0x1F72: return 0x1FC8; + case 0x1F73: return 0x1FC9; + case 0x1F74: return 0x1FCA; + case 0x1F75: return 0x1FCB; + case 0x1F76: return 0x1FDA; + case 0x1F77: return 0x1FDB; + case 0x1F78: return 0x1FF8; + case 0x1F79: return 0x1FF9; + case 0x1F7A: return 0x1FEA; + case 0x1F7B: return 0x1FEB; + case 0x1F7C: return 0x1FFA; + case 0x1F7D: return 0x1FFB; + case 0x1F80: return 0x1F88; + case 0x1F81: return 0x1F89; + case 0x1F82: return 0x1F8A; + case 0x1F83: return 0x1F8B; + case 0x1F84: return 0x1F8C; + case 0x1F85: return 0x1F8D; + case 0x1F86: return 0x1F8E; + case 0x1F87: return 0x1F8F; + case 0x1F90: return 0x1F98; + case 0x1F91: return 0x1F99; + case 0x1F92: return 0x1F9A; + case 0x1F93: return 0x1F9B; + case 0x1F94: return 0x1F9C; + case 0x1F95: return 0x1F9D; + case 0x1F96: return 0x1F9E; + case 0x1F97: return 0x1F9F; + case 0x1FA0: return 0x1FA8; + case 0x1FA1: return 0x1FA9; + case 0x1FA2: return 0x1FAA; + case 0x1FA3: return 0x1FAB; + case 0x1FA4: return 0x1FAC; + case 0x1FA5: return 0x1FAD; + case 0x1FA6: return 0x1FAE; + case 0x1FA7: return 0x1FAF; + case 0x1FB0: return 0x1FB8; + case 0x1FB1: return 0x1FB9; + case 0x1FB3: return 0x1FBC; + case 0x1FBE: return 0x0399; + case 0x1FC3: return 0x1FCC; + case 0x1FD0: return 0x1FD8; + case 0x1FD1: return 0x1FD9; + case 0x1FE0: return 0x1FE8; + case 0x1FE1: return 0x1FE9; + case 0x1FE5: return 0x1FEC; + case 0x1FF3: return 0x1FFC; + case 0x214E: return 0x2132; + case 0x2170: return 0x2160; + case 0x2171: return 0x2161; + case 0x2172: return 0x2162; + case 0x2173: return 0x2163; + case 0x2174: return 0x2164; + case 0x2175: return 0x2165; + case 0x2176: return 0x2166; + case 0x2177: return 0x2167; + case 0x2178: return 0x2168; + case 0x2179: return 0x2169; + case 0x217A: return 0x216A; + case 0x217B: return 0x216B; + case 0x217C: return 0x216C; + case 0x217D: return 0x216D; + case 0x217E: return 0x216E; + case 0x217F: return 0x216F; + case 0x2184: return 0x2183; + case 0x24D0: return 0x24B6; + case 0x24D1: return 0x24B7; + case 0x24D2: return 0x24B8; + case 0x24D3: return 0x24B9; + case 0x24D4: return 0x24BA; + case 0x24D5: return 0x24BB; + case 0x24D6: return 0x24BC; + case 0x24D7: return 0x24BD; + case 0x24D8: return 0x24BE; + case 0x24D9: return 0x24BF; + case 0x24DA: return 0x24C0; + case 0x24DB: return 0x24C1; + case 0x24DC: return 0x24C2; + case 0x24DD: return 0x24C3; + case 0x24DE: return 0x24C4; + case 0x24DF: return 0x24C5; + case 0x24E0: return 0x24C6; + case 0x24E1: return 0x24C7; + case 0x24E2: return 0x24C8; + case 0x24E3: return 0x24C9; + case 0x24E4: return 0x24CA; + case 0x24E5: return 0x24CB; + case 0x24E6: return 0x24CC; + case 0x24E7: return 0x24CD; + case 0x24E8: return 0x24CE; + case 0x24E9: return 0x24CF; + case 0x2C30: return 0x2C00; + case 0x2C31: return 0x2C01; + case 0x2C32: return 0x2C02; + case 0x2C33: return 0x2C03; + case 0x2C34: return 0x2C04; + case 0x2C35: return 0x2C05; + case 0x2C36: return 0x2C06; + case 0x2C37: return 0x2C07; + case 0x2C38: return 0x2C08; + case 0x2C39: return 0x2C09; + case 0x2C3A: return 0x2C0A; + case 0x2C3B: return 0x2C0B; + case 0x2C3C: return 0x2C0C; + case 0x2C3D: return 0x2C0D; + case 0x2C3E: return 0x2C0E; + case 0x2C3F: return 0x2C0F; + case 0x2C40: return 0x2C10; + case 0x2C41: return 0x2C11; + case 0x2C42: return 0x2C12; + case 0x2C43: return 0x2C13; + case 0x2C44: return 0x2C14; + case 0x2C45: return 0x2C15; + case 0x2C46: return 0x2C16; + case 0x2C47: return 0x2C17; + case 0x2C48: return 0x2C18; + case 0x2C49: return 0x2C19; + case 0x2C4A: return 0x2C1A; + case 0x2C4B: return 0x2C1B; + case 0x2C4C: return 0x2C1C; + case 0x2C4D: return 0x2C1D; + case 0x2C4E: return 0x2C1E; + case 0x2C4F: return 0x2C1F; + case 0x2C50: return 0x2C20; + case 0x2C51: return 0x2C21; + case 0x2C52: return 0x2C22; + case 0x2C53: return 0x2C23; + case 0x2C54: return 0x2C24; + case 0x2C55: return 0x2C25; + case 0x2C56: return 0x2C26; + case 0x2C57: return 0x2C27; + case 0x2C58: return 0x2C28; + case 0x2C59: return 0x2C29; + case 0x2C5A: return 0x2C2A; + case 0x2C5B: return 0x2C2B; + case 0x2C5C: return 0x2C2C; + case 0x2C5D: return 0x2C2D; + case 0x2C5E: return 0x2C2E; + case 0x2C61: return 0x2C60; + case 0x2C65: return 0x023A; + case 0x2C66: return 0x023E; + case 0x2C68: return 0x2C67; + case 0x2C6A: return 0x2C69; + case 0x2C6C: return 0x2C6B; + case 0x2C73: return 0x2C72; + case 0x2C76: return 0x2C75; + case 0x2C81: return 0x2C80; + case 0x2C83: return 0x2C82; + case 0x2C85: return 0x2C84; + case 0x2C87: return 0x2C86; + case 0x2C89: return 0x2C88; + case 0x2C8B: return 0x2C8A; + case 0x2C8D: return 0x2C8C; + case 0x2C8F: return 0x2C8E; + case 0x2C91: return 0x2C90; + case 0x2C93: return 0x2C92; + case 0x2C95: return 0x2C94; + case 0x2C97: return 0x2C96; + case 0x2C99: return 0x2C98; + case 0x2C9B: return 0x2C9A; + case 0x2C9D: return 0x2C9C; + case 0x2C9F: return 0x2C9E; + case 0x2CA1: return 0x2CA0; + case 0x2CA3: return 0x2CA2; + case 0x2CA5: return 0x2CA4; + case 0x2CA7: return 0x2CA6; + case 0x2CA9: return 0x2CA8; + case 0x2CAB: return 0x2CAA; + case 0x2CAD: return 0x2CAC; + case 0x2CAF: return 0x2CAE; + case 0x2CB1: return 0x2CB0; + case 0x2CB3: return 0x2CB2; + case 0x2CB5: return 0x2CB4; + case 0x2CB7: return 0x2CB6; + case 0x2CB9: return 0x2CB8; + case 0x2CBB: return 0x2CBA; + case 0x2CBD: return 0x2CBC; + case 0x2CBF: return 0x2CBE; + case 0x2CC1: return 0x2CC0; + case 0x2CC3: return 0x2CC2; + case 0x2CC5: return 0x2CC4; + case 0x2CC7: return 0x2CC6; + case 0x2CC9: return 0x2CC8; + case 0x2CCB: return 0x2CCA; + case 0x2CCD: return 0x2CCC; + case 0x2CCF: return 0x2CCE; + case 0x2CD1: return 0x2CD0; + case 0x2CD3: return 0x2CD2; + case 0x2CD5: return 0x2CD4; + case 0x2CD7: return 0x2CD6; + case 0x2CD9: return 0x2CD8; + case 0x2CDB: return 0x2CDA; + case 0x2CDD: return 0x2CDC; + case 0x2CDF: return 0x2CDE; + case 0x2CE1: return 0x2CE0; + case 0x2CE3: return 0x2CE2; + case 0x2CEC: return 0x2CEB; + case 0x2CEE: return 0x2CED; + case 0x2D00: return 0x10A0; + case 0x2D01: return 0x10A1; + case 0x2D02: return 0x10A2; + case 0x2D03: return 0x10A3; + case 0x2D04: return 0x10A4; + case 0x2D05: return 0x10A5; + case 0x2D06: return 0x10A6; + case 0x2D07: return 0x10A7; + case 0x2D08: return 0x10A8; + case 0x2D09: return 0x10A9; + case 0x2D0A: return 0x10AA; + case 0x2D0B: return 0x10AB; + case 0x2D0C: return 0x10AC; + case 0x2D0D: return 0x10AD; + case 0x2D0E: return 0x10AE; + case 0x2D0F: return 0x10AF; + case 0x2D10: return 0x10B0; + case 0x2D11: return 0x10B1; + case 0x2D12: return 0x10B2; + case 0x2D13: return 0x10B3; + case 0x2D14: return 0x10B4; + case 0x2D15: return 0x10B5; + case 0x2D16: return 0x10B6; + case 0x2D17: return 0x10B7; + case 0x2D18: return 0x10B8; + case 0x2D19: return 0x10B9; + case 0x2D1A: return 0x10BA; + case 0x2D1B: return 0x10BB; + case 0x2D1C: return 0x10BC; + case 0x2D1D: return 0x10BD; + case 0x2D1E: return 0x10BE; + case 0x2D1F: return 0x10BF; + case 0x2D20: return 0x10C0; + case 0x2D21: return 0x10C1; + case 0x2D22: return 0x10C2; + case 0x2D23: return 0x10C3; + case 0x2D24: return 0x10C4; + case 0x2D25: return 0x10C5; + case 0xA641: return 0xA640; + case 0xA643: return 0xA642; + case 0xA645: return 0xA644; + case 0xA647: return 0xA646; + case 0xA649: return 0xA648; + case 0xA64B: return 0xA64A; + case 0xA64D: return 0xA64C; + case 0xA64F: return 0xA64E; + case 0xA651: return 0xA650; + case 0xA653: return 0xA652; + case 0xA655: return 0xA654; + case 0xA657: return 0xA656; + case 0xA659: return 0xA658; + case 0xA65B: return 0xA65A; + case 0xA65D: return 0xA65C; + case 0xA65F: return 0xA65E; + case 0xA661: return 0xA660; + case 0xA663: return 0xA662; + case 0xA665: return 0xA664; + case 0xA667: return 0xA666; + case 0xA669: return 0xA668; + case 0xA66B: return 0xA66A; + case 0xA66D: return 0xA66C; + case 0xA681: return 0xA680; + case 0xA683: return 0xA682; + case 0xA685: return 0xA684; + case 0xA687: return 0xA686; + case 0xA689: return 0xA688; + case 0xA68B: return 0xA68A; + case 0xA68D: return 0xA68C; + case 0xA68F: return 0xA68E; + case 0xA691: return 0xA690; + case 0xA693: return 0xA692; + case 0xA695: return 0xA694; + case 0xA697: return 0xA696; + case 0xA723: return 0xA722; + case 0xA725: return 0xA724; + case 0xA727: return 0xA726; + case 0xA729: return 0xA728; + case 0xA72B: return 0xA72A; + case 0xA72D: return 0xA72C; + case 0xA72F: return 0xA72E; + case 0xA733: return 0xA732; + case 0xA735: return 0xA734; + case 0xA737: return 0xA736; + case 0xA739: return 0xA738; + case 0xA73B: return 0xA73A; + case 0xA73D: return 0xA73C; + case 0xA73F: return 0xA73E; + case 0xA741: return 0xA740; + case 0xA743: return 0xA742; + case 0xA745: return 0xA744; + case 0xA747: return 0xA746; + case 0xA749: return 0xA748; + case 0xA74B: return 0xA74A; + case 0xA74D: return 0xA74C; + case 0xA74F: return 0xA74E; + case 0xA751: return 0xA750; + case 0xA753: return 0xA752; + case 0xA755: return 0xA754; + case 0xA757: return 0xA756; + case 0xA759: return 0xA758; + case 0xA75B: return 0xA75A; + case 0xA75D: return 0xA75C; + case 0xA75F: return 0xA75E; + case 0xA761: return 0xA760; + case 0xA763: return 0xA762; + case 0xA765: return 0xA764; + case 0xA767: return 0xA766; + case 0xA769: return 0xA768; + case 0xA76B: return 0xA76A; + case 0xA76D: return 0xA76C; + case 0xA76F: return 0xA76E; + case 0xA77A: return 0xA779; + case 0xA77C: return 0xA77B; + case 0xA77F: return 0xA77E; + case 0xA781: return 0xA780; + case 0xA783: return 0xA782; + case 0xA785: return 0xA784; + case 0xA787: return 0xA786; + case 0xA78C: return 0xA78B; + case 0xA791: return 0xA790; + case 0xA7A1: return 0xA7A0; + case 0xA7A3: return 0xA7A2; + case 0xA7A5: return 0xA7A4; + case 0xA7A7: return 0xA7A6; + case 0xA7A9: return 0xA7A8; + case 0xFF41: return 0xFF21; + case 0xFF42: return 0xFF22; + case 0xFF43: return 0xFF23; + case 0xFF44: return 0xFF24; + case 0xFF45: return 0xFF25; + case 0xFF46: return 0xFF26; + case 0xFF47: return 0xFF27; + case 0xFF48: return 0xFF28; + case 0xFF49: return 0xFF29; + case 0xFF4A: return 0xFF2A; + case 0xFF4B: return 0xFF2B; + case 0xFF4C: return 0xFF2C; + case 0xFF4D: return 0xFF2D; + case 0xFF4E: return 0xFF2E; + case 0xFF4F: return 0xFF2F; + case 0xFF50: return 0xFF30; + case 0xFF51: return 0xFF31; + case 0xFF52: return 0xFF32; + case 0xFF53: return 0xFF33; + case 0xFF54: return 0xFF34; + case 0xFF55: return 0xFF35; + case 0xFF56: return 0xFF36; + case 0xFF57: return 0xFF37; + case 0xFF58: return 0xFF38; + case 0xFF59: return 0xFF39; + case 0xFF5A: return 0xFF3A; + case 0x10428: return 0x10400; + case 0x10429: return 0x10401; + case 0x1042A: return 0x10402; + case 0x1042B: return 0x10403; + case 0x1042C: return 0x10404; + case 0x1042D: return 0x10405; + case 0x1042E: return 0x10406; + case 0x1042F: return 0x10407; + case 0x10430: return 0x10408; + case 0x10431: return 0x10409; + case 0x10432: return 0x1040A; + case 0x10433: return 0x1040B; + case 0x10434: return 0x1040C; + case 0x10435: return 0x1040D; + case 0x10436: return 0x1040E; + case 0x10437: return 0x1040F; + case 0x10438: return 0x10410; + case 0x10439: return 0x10411; + case 0x1043A: return 0x10412; + case 0x1043B: return 0x10413; + case 0x1043C: return 0x10414; + case 0x1043D: return 0x10415; + case 0x1043E: return 0x10416; + case 0x1043F: return 0x10417; + case 0x10440: return 0x10418; + case 0x10441: return 0x10419; + case 0x10442: return 0x1041A; + case 0x10443: return 0x1041B; + case 0x10444: return 0x1041C; + case 0x10445: return 0x1041D; + case 0x10446: return 0x1041E; + case 0x10447: return 0x1041F; + case 0x10448: return 0x10420; + case 0x10449: return 0x10421; + case 0x1044A: return 0x10422; + case 0x1044B: return 0x10423; + case 0x1044C: return 0x10424; + case 0x1044D: return 0x10425; + case 0x1044E: return 0x10426; + case 0x1044F: return 0x10427; + default: return ch; + } +} + +Uchar u8_tolower(Uchar ch) +{ + switch(ch) + { + case 0x0041: return 0x0061; + case 0x0042: return 0x0062; + case 0x0043: return 0x0063; + case 0x0044: return 0x0064; + case 0x0045: return 0x0065; + case 0x0046: return 0x0066; + case 0x0047: return 0x0067; + case 0x0048: return 0x0068; + case 0x0049: return 0x0069; + case 0x004A: return 0x006A; + case 0x004B: return 0x006B; + case 0x004C: return 0x006C; + case 0x004D: return 0x006D; + case 0x004E: return 0x006E; + case 0x004F: return 0x006F; + case 0x0050: return 0x0070; + case 0x0051: return 0x0071; + case 0x0052: return 0x0072; + case 0x0053: return 0x0073; + case 0x0054: return 0x0074; + case 0x0055: return 0x0075; + case 0x0056: return 0x0076; + case 0x0057: return 0x0077; + case 0x0058: return 0x0078; + case 0x0059: return 0x0079; + case 0x005A: return 0x007A; + case 0x00C0: return 0x00E0; + case 0x00C1: return 0x00E1; + case 0x00C2: return 0x00E2; + case 0x00C3: return 0x00E3; + case 0x00C4: return 0x00E4; + case 0x00C5: return 0x00E5; + case 0x00C6: return 0x00E6; + case 0x00C7: return 0x00E7; + case 0x00C8: return 0x00E8; + case 0x00C9: return 0x00E9; + case 0x00CA: return 0x00EA; + case 0x00CB: return 0x00EB; + case 0x00CC: return 0x00EC; + case 0x00CD: return 0x00ED; + case 0x00CE: return 0x00EE; + case 0x00CF: return 0x00EF; + case 0x00D0: return 0x00F0; + case 0x00D1: return 0x00F1; + case 0x00D2: return 0x00F2; + case 0x00D3: return 0x00F3; + case 0x00D4: return 0x00F4; + case 0x00D5: return 0x00F5; + case 0x00D6: return 0x00F6; + case 0x00D8: return 0x00F8; + case 0x00D9: return 0x00F9; + case 0x00DA: return 0x00FA; + case 0x00DB: return 0x00FB; + case 0x00DC: return 0x00FC; + case 0x00DD: return 0x00FD; + case 0x00DE: return 0x00FE; + case 0x0100: return 0x0101; + case 0x0102: return 0x0103; + case 0x0104: return 0x0105; + case 0x0106: return 0x0107; + case 0x0108: return 0x0109; + case 0x010A: return 0x010B; + case 0x010C: return 0x010D; + case 0x010E: return 0x010F; + case 0x0110: return 0x0111; + case 0x0112: return 0x0113; + case 0x0114: return 0x0115; + case 0x0116: return 0x0117; + case 0x0118: return 0x0119; + case 0x011A: return 0x011B; + case 0x011C: return 0x011D; + case 0x011E: return 0x011F; + case 0x0120: return 0x0121; + case 0x0122: return 0x0123; + case 0x0124: return 0x0125; + case 0x0126: return 0x0127; + case 0x0128: return 0x0129; + case 0x012A: return 0x012B; + case 0x012C: return 0x012D; + case 0x012E: return 0x012F; + case 0x0130: return 0x0069; + case 0x0132: return 0x0133; + case 0x0134: return 0x0135; + case 0x0136: return 0x0137; + case 0x0139: return 0x013A; + case 0x013B: return 0x013C; + case 0x013D: return 0x013E; + case 0x013F: return 0x0140; + case 0x0141: return 0x0142; + case 0x0143: return 0x0144; + case 0x0145: return 0x0146; + case 0x0147: return 0x0148; + case 0x014A: return 0x014B; + case 0x014C: return 0x014D; + case 0x014E: return 0x014F; + case 0x0150: return 0x0151; + case 0x0152: return 0x0153; + case 0x0154: return 0x0155; + case 0x0156: return 0x0157; + case 0x0158: return 0x0159; + case 0x015A: return 0x015B; + case 0x015C: return 0x015D; + case 0x015E: return 0x015F; + case 0x0160: return 0x0161; + case 0x0162: return 0x0163; + case 0x0164: return 0x0165; + case 0x0166: return 0x0167; + case 0x0168: return 0x0169; + case 0x016A: return 0x016B; + case 0x016C: return 0x016D; + case 0x016E: return 0x016F; + case 0x0170: return 0x0171; + case 0x0172: return 0x0173; + case 0x0174: return 0x0175; + case 0x0176: return 0x0177; + case 0x0178: return 0x00FF; + case 0x0179: return 0x017A; + case 0x017B: return 0x017C; + case 0x017D: return 0x017E; + case 0x0181: return 0x0253; + case 0x0182: return 0x0183; + case 0x0184: return 0x0185; + case 0x0186: return 0x0254; + case 0x0187: return 0x0188; + case 0x0189: return 0x0256; + case 0x018A: return 0x0257; + case 0x018B: return 0x018C; + case 0x018E: return 0x01DD; + case 0x018F: return 0x0259; + case 0x0190: return 0x025B; + case 0x0191: return 0x0192; + case 0x0193: return 0x0260; + case 0x0194: return 0x0263; + case 0x0196: return 0x0269; + case 0x0197: return 0x0268; + case 0x0198: return 0x0199; + case 0x019C: return 0x026F; + case 0x019D: return 0x0272; + case 0x019F: return 0x0275; + case 0x01A0: return 0x01A1; + case 0x01A2: return 0x01A3; + case 0x01A4: return 0x01A5; + case 0x01A6: return 0x0280; + case 0x01A7: return 0x01A8; + case 0x01A9: return 0x0283; + case 0x01AC: return 0x01AD; + case 0x01AE: return 0x0288; + case 0x01AF: return 0x01B0; + case 0x01B1: return 0x028A; + case 0x01B2: return 0x028B; + case 0x01B3: return 0x01B4; + case 0x01B5: return 0x01B6; + case 0x01B7: return 0x0292; + case 0x01B8: return 0x01B9; + case 0x01BC: return 0x01BD; + case 0x01C4: return 0x01C6; + case 0x01C5: return 0x01C6; + case 0x01C7: return 0x01C9; + case 0x01C8: return 0x01C9; + case 0x01CA: return 0x01CC; + case 0x01CB: return 0x01CC; + case 0x01CD: return 0x01CE; + case 0x01CF: return 0x01D0; + case 0x01D1: return 0x01D2; + case 0x01D3: return 0x01D4; + case 0x01D5: return 0x01D6; + case 0x01D7: return 0x01D8; + case 0x01D9: return 0x01DA; + case 0x01DB: return 0x01DC; + case 0x01DE: return 0x01DF; + case 0x01E0: return 0x01E1; + case 0x01E2: return 0x01E3; + case 0x01E4: return 0x01E5; + case 0x01E6: return 0x01E7; + case 0x01E8: return 0x01E9; + case 0x01EA: return 0x01EB; + case 0x01EC: return 0x01ED; + case 0x01EE: return 0x01EF; + case 0x01F1: return 0x01F3; + case 0x01F2: return 0x01F3; + case 0x01F4: return 0x01F5; + case 0x01F6: return 0x0195; + case 0x01F7: return 0x01BF; + case 0x01F8: return 0x01F9; + case 0x01FA: return 0x01FB; + case 0x01FC: return 0x01FD; + case 0x01FE: return 0x01FF; + case 0x0200: return 0x0201; + case 0x0202: return 0x0203; + case 0x0204: return 0x0205; + case 0x0206: return 0x0207; + case 0x0208: return 0x0209; + case 0x020A: return 0x020B; + case 0x020C: return 0x020D; + case 0x020E: return 0x020F; + case 0x0210: return 0x0211; + case 0x0212: return 0x0213; + case 0x0214: return 0x0215; + case 0x0216: return 0x0217; + case 0x0218: return 0x0219; + case 0x021A: return 0x021B; + case 0x021C: return 0x021D; + case 0x021E: return 0x021F; + case 0x0220: return 0x019E; + case 0x0222: return 0x0223; + case 0x0224: return 0x0225; + case 0x0226: return 0x0227; + case 0x0228: return 0x0229; + case 0x022A: return 0x022B; + case 0x022C: return 0x022D; + case 0x022E: return 0x022F; + case 0x0230: return 0x0231; + case 0x0232: return 0x0233; + case 0x023A: return 0x2C65; + case 0x023B: return 0x023C; + case 0x023D: return 0x019A; + case 0x023E: return 0x2C66; + case 0x0241: return 0x0242; + case 0x0243: return 0x0180; + case 0x0244: return 0x0289; + case 0x0245: return 0x028C; + case 0x0246: return 0x0247; + case 0x0248: return 0x0249; + case 0x024A: return 0x024B; + case 0x024C: return 0x024D; + case 0x024E: return 0x024F; + case 0x0370: return 0x0371; + case 0x0372: return 0x0373; + case 0x0376: return 0x0377; + case 0x0386: return 0x03AC; + case 0x0388: return 0x03AD; + case 0x0389: return 0x03AE; + case 0x038A: return 0x03AF; + case 0x038C: return 0x03CC; + case 0x038E: return 0x03CD; + case 0x038F: return 0x03CE; + case 0x0391: return 0x03B1; + case 0x0392: return 0x03B2; + case 0x0393: return 0x03B3; + case 0x0394: return 0x03B4; + case 0x0395: return 0x03B5; + case 0x0396: return 0x03B6; + case 0x0397: return 0x03B7; + case 0x0398: return 0x03B8; + case 0x0399: return 0x03B9; + case 0x039A: return 0x03BA; + case 0x039B: return 0x03BB; + case 0x039C: return 0x03BC; + case 0x039D: return 0x03BD; + case 0x039E: return 0x03BE; + case 0x039F: return 0x03BF; + case 0x03A0: return 0x03C0; + case 0x03A1: return 0x03C1; + case 0x03A3: return 0x03C3; + case 0x03A4: return 0x03C4; + case 0x03A5: return 0x03C5; + case 0x03A6: return 0x03C6; + case 0x03A7: return 0x03C7; + case 0x03A8: return 0x03C8; + case 0x03A9: return 0x03C9; + case 0x03AA: return 0x03CA; + case 0x03AB: return 0x03CB; + case 0x03CF: return 0x03D7; + case 0x03D8: return 0x03D9; + case 0x03DA: return 0x03DB; + case 0x03DC: return 0x03DD; + case 0x03DE: return 0x03DF; + case 0x03E0: return 0x03E1; + case 0x03E2: return 0x03E3; + case 0x03E4: return 0x03E5; + case 0x03E6: return 0x03E7; + case 0x03E8: return 0x03E9; + case 0x03EA: return 0x03EB; + case 0x03EC: return 0x03ED; + case 0x03EE: return 0x03EF; + case 0x03F4: return 0x03B8; + case 0x03F7: return 0x03F8; + case 0x03F9: return 0x03F2; + case 0x03FA: return 0x03FB; + case 0x03FD: return 0x037B; + case 0x03FE: return 0x037C; + case 0x03FF: return 0x037D; + case 0x0400: return 0x0450; + case 0x0401: return 0x0451; + case 0x0402: return 0x0452; + case 0x0403: return 0x0453; + case 0x0404: return 0x0454; + case 0x0405: return 0x0455; + case 0x0406: return 0x0456; + case 0x0407: return 0x0457; + case 0x0408: return 0x0458; + case 0x0409: return 0x0459; + case 0x040A: return 0x045A; + case 0x040B: return 0x045B; + case 0x040C: return 0x045C; + case 0x040D: return 0x045D; + case 0x040E: return 0x045E; + case 0x040F: return 0x045F; + case 0x0410: return 0x0430; + case 0x0411: return 0x0431; + case 0x0412: return 0x0432; + case 0x0413: return 0x0433; + case 0x0414: return 0x0434; + case 0x0415: return 0x0435; + case 0x0416: return 0x0436; + case 0x0417: return 0x0437; + case 0x0418: return 0x0438; + case 0x0419: return 0x0439; + case 0x041A: return 0x043A; + case 0x041B: return 0x043B; + case 0x041C: return 0x043C; + case 0x041D: return 0x043D; + case 0x041E: return 0x043E; + case 0x041F: return 0x043F; + case 0x0420: return 0x0440; + case 0x0421: return 0x0441; + case 0x0422: return 0x0442; + case 0x0423: return 0x0443; + case 0x0424: return 0x0444; + case 0x0425: return 0x0445; + case 0x0426: return 0x0446; + case 0x0427: return 0x0447; + case 0x0428: return 0x0448; + case 0x0429: return 0x0449; + case 0x042A: return 0x044A; + case 0x042B: return 0x044B; + case 0x042C: return 0x044C; + case 0x042D: return 0x044D; + case 0x042E: return 0x044E; + case 0x042F: return 0x044F; + case 0x0460: return 0x0461; + case 0x0462: return 0x0463; + case 0x0464: return 0x0465; + case 0x0466: return 0x0467; + case 0x0468: return 0x0469; + case 0x046A: return 0x046B; + case 0x046C: return 0x046D; + case 0x046E: return 0x046F; + case 0x0470: return 0x0471; + case 0x0472: return 0x0473; + case 0x0474: return 0x0475; + case 0x0476: return 0x0477; + case 0x0478: return 0x0479; + case 0x047A: return 0x047B; + case 0x047C: return 0x047D; + case 0x047E: return 0x047F; + case 0x0480: return 0x0481; + case 0x048A: return 0x048B; + case 0x048C: return 0x048D; + case 0x048E: return 0x048F; + case 0x0490: return 0x0491; + case 0x0492: return 0x0493; + case 0x0494: return 0x0495; + case 0x0496: return 0x0497; + case 0x0498: return 0x0499; + case 0x049A: return 0x049B; + case 0x049C: return 0x049D; + case 0x049E: return 0x049F; + case 0x04A0: return 0x04A1; + case 0x04A2: return 0x04A3; + case 0x04A4: return 0x04A5; + case 0x04A6: return 0x04A7; + case 0x04A8: return 0x04A9; + case 0x04AA: return 0x04AB; + case 0x04AC: return 0x04AD; + case 0x04AE: return 0x04AF; + case 0x04B0: return 0x04B1; + case 0x04B2: return 0x04B3; + case 0x04B4: return 0x04B5; + case 0x04B6: return 0x04B7; + case 0x04B8: return 0x04B9; + case 0x04BA: return 0x04BB; + case 0x04BC: return 0x04BD; + case 0x04BE: return 0x04BF; + case 0x04C0: return 0x04CF; + case 0x04C1: return 0x04C2; + case 0x04C3: return 0x04C4; + case 0x04C5: return 0x04C6; + case 0x04C7: return 0x04C8; + case 0x04C9: return 0x04CA; + case 0x04CB: return 0x04CC; + case 0x04CD: return 0x04CE; + case 0x04D0: return 0x04D1; + case 0x04D2: return 0x04D3; + case 0x04D4: return 0x04D5; + case 0x04D6: return 0x04D7; + case 0x04D8: return 0x04D9; + case 0x04DA: return 0x04DB; + case 0x04DC: return 0x04DD; + case 0x04DE: return 0x04DF; + case 0x04E0: return 0x04E1; + case 0x04E2: return 0x04E3; + case 0x04E4: return 0x04E5; + case 0x04E6: return 0x04E7; + case 0x04E8: return 0x04E9; + case 0x04EA: return 0x04EB; + case 0x04EC: return 0x04ED; + case 0x04EE: return 0x04EF; + case 0x04F0: return 0x04F1; + case 0x04F2: return 0x04F3; + case 0x04F4: return 0x04F5; + case 0x04F6: return 0x04F7; + case 0x04F8: return 0x04F9; + case 0x04FA: return 0x04FB; + case 0x04FC: return 0x04FD; + case 0x04FE: return 0x04FF; + case 0x0500: return 0x0501; + case 0x0502: return 0x0503; + case 0x0504: return 0x0505; + case 0x0506: return 0x0507; + case 0x0508: return 0x0509; + case 0x050A: return 0x050B; + case 0x050C: return 0x050D; + case 0x050E: return 0x050F; + case 0x0510: return 0x0511; + case 0x0512: return 0x0513; + case 0x0514: return 0x0515; + case 0x0516: return 0x0517; + case 0x0518: return 0x0519; + case 0x051A: return 0x051B; + case 0x051C: return 0x051D; + case 0x051E: return 0x051F; + case 0x0520: return 0x0521; + case 0x0522: return 0x0523; + case 0x0524: return 0x0525; + case 0x0526: return 0x0527; + case 0x0531: return 0x0561; + case 0x0532: return 0x0562; + case 0x0533: return 0x0563; + case 0x0534: return 0x0564; + case 0x0535: return 0x0565; + case 0x0536: return 0x0566; + case 0x0537: return 0x0567; + case 0x0538: return 0x0568; + case 0x0539: return 0x0569; + case 0x053A: return 0x056A; + case 0x053B: return 0x056B; + case 0x053C: return 0x056C; + case 0x053D: return 0x056D; + case 0x053E: return 0x056E; + case 0x053F: return 0x056F; + case 0x0540: return 0x0570; + case 0x0541: return 0x0571; + case 0x0542: return 0x0572; + case 0x0543: return 0x0573; + case 0x0544: return 0x0574; + case 0x0545: return 0x0575; + case 0x0546: return 0x0576; + case 0x0547: return 0x0577; + case 0x0548: return 0x0578; + case 0x0549: return 0x0579; + case 0x054A: return 0x057A; + case 0x054B: return 0x057B; + case 0x054C: return 0x057C; + case 0x054D: return 0x057D; + case 0x054E: return 0x057E; + case 0x054F: return 0x057F; + case 0x0550: return 0x0580; + case 0x0551: return 0x0581; + case 0x0552: return 0x0582; + case 0x0553: return 0x0583; + case 0x0554: return 0x0584; + case 0x0555: return 0x0585; + case 0x0556: return 0x0586; + case 0x10A0: return 0x2D00; + case 0x10A1: return 0x2D01; + case 0x10A2: return 0x2D02; + case 0x10A3: return 0x2D03; + case 0x10A4: return 0x2D04; + case 0x10A5: return 0x2D05; + case 0x10A6: return 0x2D06; + case 0x10A7: return 0x2D07; + case 0x10A8: return 0x2D08; + case 0x10A9: return 0x2D09; + case 0x10AA: return 0x2D0A; + case 0x10AB: return 0x2D0B; + case 0x10AC: return 0x2D0C; + case 0x10AD: return 0x2D0D; + case 0x10AE: return 0x2D0E; + case 0x10AF: return 0x2D0F; + case 0x10B0: return 0x2D10; + case 0x10B1: return 0x2D11; + case 0x10B2: return 0x2D12; + case 0x10B3: return 0x2D13; + case 0x10B4: return 0x2D14; + case 0x10B5: return 0x2D15; + case 0x10B6: return 0x2D16; + case 0x10B7: return 0x2D17; + case 0x10B8: return 0x2D18; + case 0x10B9: return 0x2D19; + case 0x10BA: return 0x2D1A; + case 0x10BB: return 0x2D1B; + case 0x10BC: return 0x2D1C; + case 0x10BD: return 0x2D1D; + case 0x10BE: return 0x2D1E; + case 0x10BF: return 0x2D1F; + case 0x10C0: return 0x2D20; + case 0x10C1: return 0x2D21; + case 0x10C2: return 0x2D22; + case 0x10C3: return 0x2D23; + case 0x10C4: return 0x2D24; + case 0x10C5: return 0x2D25; + case 0x1E00: return 0x1E01; + case 0x1E02: return 0x1E03; + case 0x1E04: return 0x1E05; + case 0x1E06: return 0x1E07; + case 0x1E08: return 0x1E09; + case 0x1E0A: return 0x1E0B; + case 0x1E0C: return 0x1E0D; + case 0x1E0E: return 0x1E0F; + case 0x1E10: return 0x1E11; + case 0x1E12: return 0x1E13; + case 0x1E14: return 0x1E15; + case 0x1E16: return 0x1E17; + case 0x1E18: return 0x1E19; + case 0x1E1A: return 0x1E1B; + case 0x1E1C: return 0x1E1D; + case 0x1E1E: return 0x1E1F; + case 0x1E20: return 0x1E21; + case 0x1E22: return 0x1E23; + case 0x1E24: return 0x1E25; + case 0x1E26: return 0x1E27; + case 0x1E28: return 0x1E29; + case 0x1E2A: return 0x1E2B; + case 0x1E2C: return 0x1E2D; + case 0x1E2E: return 0x1E2F; + case 0x1E30: return 0x1E31; + case 0x1E32: return 0x1E33; + case 0x1E34: return 0x1E35; + case 0x1E36: return 0x1E37; + case 0x1E38: return 0x1E39; + case 0x1E3A: return 0x1E3B; + case 0x1E3C: return 0x1E3D; + case 0x1E3E: return 0x1E3F; + case 0x1E40: return 0x1E41; + case 0x1E42: return 0x1E43; + case 0x1E44: return 0x1E45; + case 0x1E46: return 0x1E47; + case 0x1E48: return 0x1E49; + case 0x1E4A: return 0x1E4B; + case 0x1E4C: return 0x1E4D; + case 0x1E4E: return 0x1E4F; + case 0x1E50: return 0x1E51; + case 0x1E52: return 0x1E53; + case 0x1E54: return 0x1E55; + case 0x1E56: return 0x1E57; + case 0x1E58: return 0x1E59; + case 0x1E5A: return 0x1E5B; + case 0x1E5C: return 0x1E5D; + case 0x1E5E: return 0x1E5F; + case 0x1E60: return 0x1E61; + case 0x1E62: return 0x1E63; + case 0x1E64: return 0x1E65; + case 0x1E66: return 0x1E67; + case 0x1E68: return 0x1E69; + case 0x1E6A: return 0x1E6B; + case 0x1E6C: return 0x1E6D; + case 0x1E6E: return 0x1E6F; + case 0x1E70: return 0x1E71; + case 0x1E72: return 0x1E73; + case 0x1E74: return 0x1E75; + case 0x1E76: return 0x1E77; + case 0x1E78: return 0x1E79; + case 0x1E7A: return 0x1E7B; + case 0x1E7C: return 0x1E7D; + case 0x1E7E: return 0x1E7F; + case 0x1E80: return 0x1E81; + case 0x1E82: return 0x1E83; + case 0x1E84: return 0x1E85; + case 0x1E86: return 0x1E87; + case 0x1E88: return 0x1E89; + case 0x1E8A: return 0x1E8B; + case 0x1E8C: return 0x1E8D; + case 0x1E8E: return 0x1E8F; + case 0x1E90: return 0x1E91; + case 0x1E92: return 0x1E93; + case 0x1E94: return 0x1E95; + case 0x1E9E: return 0x00DF; + case 0x1EA0: return 0x1EA1; + case 0x1EA2: return 0x1EA3; + case 0x1EA4: return 0x1EA5; + case 0x1EA6: return 0x1EA7; + case 0x1EA8: return 0x1EA9; + case 0x1EAA: return 0x1EAB; + case 0x1EAC: return 0x1EAD; + case 0x1EAE: return 0x1EAF; + case 0x1EB0: return 0x1EB1; + case 0x1EB2: return 0x1EB3; + case 0x1EB4: return 0x1EB5; + case 0x1EB6: return 0x1EB7; + case 0x1EB8: return 0x1EB9; + case 0x1EBA: return 0x1EBB; + case 0x1EBC: return 0x1EBD; + case 0x1EBE: return 0x1EBF; + case 0x1EC0: return 0x1EC1; + case 0x1EC2: return 0x1EC3; + case 0x1EC4: return 0x1EC5; + case 0x1EC6: return 0x1EC7; + case 0x1EC8: return 0x1EC9; + case 0x1ECA: return 0x1ECB; + case 0x1ECC: return 0x1ECD; + case 0x1ECE: return 0x1ECF; + case 0x1ED0: return 0x1ED1; + case 0x1ED2: return 0x1ED3; + case 0x1ED4: return 0x1ED5; + case 0x1ED6: return 0x1ED7; + case 0x1ED8: return 0x1ED9; + case 0x1EDA: return 0x1EDB; + case 0x1EDC: return 0x1EDD; + case 0x1EDE: return 0x1EDF; + case 0x1EE0: return 0x1EE1; + case 0x1EE2: return 0x1EE3; + case 0x1EE4: return 0x1EE5; + case 0x1EE6: return 0x1EE7; + case 0x1EE8: return 0x1EE9; + case 0x1EEA: return 0x1EEB; + case 0x1EEC: return 0x1EED; + case 0x1EEE: return 0x1EEF; + case 0x1EF0: return 0x1EF1; + case 0x1EF2: return 0x1EF3; + case 0x1EF4: return 0x1EF5; + case 0x1EF6: return 0x1EF7; + case 0x1EF8: return 0x1EF9; + case 0x1EFA: return 0x1EFB; + case 0x1EFC: return 0x1EFD; + case 0x1EFE: return 0x1EFF; + case 0x1F08: return 0x1F00; + case 0x1F09: return 0x1F01; + case 0x1F0A: return 0x1F02; + case 0x1F0B: return 0x1F03; + case 0x1F0C: return 0x1F04; + case 0x1F0D: return 0x1F05; + case 0x1F0E: return 0x1F06; + case 0x1F0F: return 0x1F07; + case 0x1F18: return 0x1F10; + case 0x1F19: return 0x1F11; + case 0x1F1A: return 0x1F12; + case 0x1F1B: return 0x1F13; + case 0x1F1C: return 0x1F14; + case 0x1F1D: return 0x1F15; + case 0x1F28: return 0x1F20; + case 0x1F29: return 0x1F21; + case 0x1F2A: return 0x1F22; + case 0x1F2B: return 0x1F23; + case 0x1F2C: return 0x1F24; + case 0x1F2D: return 0x1F25; + case 0x1F2E: return 0x1F26; + case 0x1F2F: return 0x1F27; + case 0x1F38: return 0x1F30; + case 0x1F39: return 0x1F31; + case 0x1F3A: return 0x1F32; + case 0x1F3B: return 0x1F33; + case 0x1F3C: return 0x1F34; + case 0x1F3D: return 0x1F35; + case 0x1F3E: return 0x1F36; + case 0x1F3F: return 0x1F37; + case 0x1F48: return 0x1F40; + case 0x1F49: return 0x1F41; + case 0x1F4A: return 0x1F42; + case 0x1F4B: return 0x1F43; + case 0x1F4C: return 0x1F44; + case 0x1F4D: return 0x1F45; + case 0x1F59: return 0x1F51; + case 0x1F5B: return 0x1F53; + case 0x1F5D: return 0x1F55; + case 0x1F5F: return 0x1F57; + case 0x1F68: return 0x1F60; + case 0x1F69: return 0x1F61; + case 0x1F6A: return 0x1F62; + case 0x1F6B: return 0x1F63; + case 0x1F6C: return 0x1F64; + case 0x1F6D: return 0x1F65; + case 0x1F6E: return 0x1F66; + case 0x1F6F: return 0x1F67; + case 0x1F88: return 0x1F80; + case 0x1F89: return 0x1F81; + case 0x1F8A: return 0x1F82; + case 0x1F8B: return 0x1F83; + case 0x1F8C: return 0x1F84; + case 0x1F8D: return 0x1F85; + case 0x1F8E: return 0x1F86; + case 0x1F8F: return 0x1F87; + case 0x1F98: return 0x1F90; + case 0x1F99: return 0x1F91; + case 0x1F9A: return 0x1F92; + case 0x1F9B: return 0x1F93; + case 0x1F9C: return 0x1F94; + case 0x1F9D: return 0x1F95; + case 0x1F9E: return 0x1F96; + case 0x1F9F: return 0x1F97; + case 0x1FA8: return 0x1FA0; + case 0x1FA9: return 0x1FA1; + case 0x1FAA: return 0x1FA2; + case 0x1FAB: return 0x1FA3; + case 0x1FAC: return 0x1FA4; + case 0x1FAD: return 0x1FA5; + case 0x1FAE: return 0x1FA6; + case 0x1FAF: return 0x1FA7; + case 0x1FB8: return 0x1FB0; + case 0x1FB9: return 0x1FB1; + case 0x1FBA: return 0x1F70; + case 0x1FBB: return 0x1F71; + case 0x1FBC: return 0x1FB3; + case 0x1FC8: return 0x1F72; + case 0x1FC9: return 0x1F73; + case 0x1FCA: return 0x1F74; + case 0x1FCB: return 0x1F75; + case 0x1FCC: return 0x1FC3; + case 0x1FD8: return 0x1FD0; + case 0x1FD9: return 0x1FD1; + case 0x1FDA: return 0x1F76; + case 0x1FDB: return 0x1F77; + case 0x1FE8: return 0x1FE0; + case 0x1FE9: return 0x1FE1; + case 0x1FEA: return 0x1F7A; + case 0x1FEB: return 0x1F7B; + case 0x1FEC: return 0x1FE5; + case 0x1FF8: return 0x1F78; + case 0x1FF9: return 0x1F79; + case 0x1FFA: return 0x1F7C; + case 0x1FFB: return 0x1F7D; + case 0x1FFC: return 0x1FF3; + case 0x2126: return 0x03C9; + case 0x212A: return 0x006B; + case 0x212B: return 0x00E5; + case 0x2132: return 0x214E; + case 0x2160: return 0x2170; + case 0x2161: return 0x2171; + case 0x2162: return 0x2172; + case 0x2163: return 0x2173; + case 0x2164: return 0x2174; + case 0x2165: return 0x2175; + case 0x2166: return 0x2176; + case 0x2167: return 0x2177; + case 0x2168: return 0x2178; + case 0x2169: return 0x2179; + case 0x216A: return 0x217A; + case 0x216B: return 0x217B; + case 0x216C: return 0x217C; + case 0x216D: return 0x217D; + case 0x216E: return 0x217E; + case 0x216F: return 0x217F; + case 0x2183: return 0x2184; + case 0x24B6: return 0x24D0; + case 0x24B7: return 0x24D1; + case 0x24B8: return 0x24D2; + case 0x24B9: return 0x24D3; + case 0x24BA: return 0x24D4; + case 0x24BB: return 0x24D5; + case 0x24BC: return 0x24D6; + case 0x24BD: return 0x24D7; + case 0x24BE: return 0x24D8; + case 0x24BF: return 0x24D9; + case 0x24C0: return 0x24DA; + case 0x24C1: return 0x24DB; + case 0x24C2: return 0x24DC; + case 0x24C3: return 0x24DD; + case 0x24C4: return 0x24DE; + case 0x24C5: return 0x24DF; + case 0x24C6: return 0x24E0; + case 0x24C7: return 0x24E1; + case 0x24C8: return 0x24E2; + case 0x24C9: return 0x24E3; + case 0x24CA: return 0x24E4; + case 0x24CB: return 0x24E5; + case 0x24CC: return 0x24E6; + case 0x24CD: return 0x24E7; + case 0x24CE: return 0x24E8; + case 0x24CF: return 0x24E9; + case 0x2C00: return 0x2C30; + case 0x2C01: return 0x2C31; + case 0x2C02: return 0x2C32; + case 0x2C03: return 0x2C33; + case 0x2C04: return 0x2C34; + case 0x2C05: return 0x2C35; + case 0x2C06: return 0x2C36; + case 0x2C07: return 0x2C37; + case 0x2C08: return 0x2C38; + case 0x2C09: return 0x2C39; + case 0x2C0A: return 0x2C3A; + case 0x2C0B: return 0x2C3B; + case 0x2C0C: return 0x2C3C; + case 0x2C0D: return 0x2C3D; + case 0x2C0E: return 0x2C3E; + case 0x2C0F: return 0x2C3F; + case 0x2C10: return 0x2C40; + case 0x2C11: return 0x2C41; + case 0x2C12: return 0x2C42; + case 0x2C13: return 0x2C43; + case 0x2C14: return 0x2C44; + case 0x2C15: return 0x2C45; + case 0x2C16: return 0x2C46; + case 0x2C17: return 0x2C47; + case 0x2C18: return 0x2C48; + case 0x2C19: return 0x2C49; + case 0x2C1A: return 0x2C4A; + case 0x2C1B: return 0x2C4B; + case 0x2C1C: return 0x2C4C; + case 0x2C1D: return 0x2C4D; + case 0x2C1E: return 0x2C4E; + case 0x2C1F: return 0x2C4F; + case 0x2C20: return 0x2C50; + case 0x2C21: return 0x2C51; + case 0x2C22: return 0x2C52; + case 0x2C23: return 0x2C53; + case 0x2C24: return 0x2C54; + case 0x2C25: return 0x2C55; + case 0x2C26: return 0x2C56; + case 0x2C27: return 0x2C57; + case 0x2C28: return 0x2C58; + case 0x2C29: return 0x2C59; + case 0x2C2A: return 0x2C5A; + case 0x2C2B: return 0x2C5B; + case 0x2C2C: return 0x2C5C; + case 0x2C2D: return 0x2C5D; + case 0x2C2E: return 0x2C5E; + case 0x2C60: return 0x2C61; + case 0x2C62: return 0x026B; + case 0x2C63: return 0x1D7D; + case 0x2C64: return 0x027D; + case 0x2C67: return 0x2C68; + case 0x2C69: return 0x2C6A; + case 0x2C6B: return 0x2C6C; + case 0x2C6D: return 0x0251; + case 0x2C6E: return 0x0271; + case 0x2C6F: return 0x0250; + case 0x2C70: return 0x0252; + case 0x2C72: return 0x2C73; + case 0x2C75: return 0x2C76; + case 0x2C7E: return 0x023F; + case 0x2C7F: return 0x0240; + case 0x2C80: return 0x2C81; + case 0x2C82: return 0x2C83; + case 0x2C84: return 0x2C85; + case 0x2C86: return 0x2C87; + case 0x2C88: return 0x2C89; + case 0x2C8A: return 0x2C8B; + case 0x2C8C: return 0x2C8D; + case 0x2C8E: return 0x2C8F; + case 0x2C90: return 0x2C91; + case 0x2C92: return 0x2C93; + case 0x2C94: return 0x2C95; + case 0x2C96: return 0x2C97; + case 0x2C98: return 0x2C99; + case 0x2C9A: return 0x2C9B; + case 0x2C9C: return 0x2C9D; + case 0x2C9E: return 0x2C9F; + case 0x2CA0: return 0x2CA1; + case 0x2CA2: return 0x2CA3; + case 0x2CA4: return 0x2CA5; + case 0x2CA6: return 0x2CA7; + case 0x2CA8: return 0x2CA9; + case 0x2CAA: return 0x2CAB; + case 0x2CAC: return 0x2CAD; + case 0x2CAE: return 0x2CAF; + case 0x2CB0: return 0x2CB1; + case 0x2CB2: return 0x2CB3; + case 0x2CB4: return 0x2CB5; + case 0x2CB6: return 0x2CB7; + case 0x2CB8: return 0x2CB9; + case 0x2CBA: return 0x2CBB; + case 0x2CBC: return 0x2CBD; + case 0x2CBE: return 0x2CBF; + case 0x2CC0: return 0x2CC1; + case 0x2CC2: return 0x2CC3; + case 0x2CC4: return 0x2CC5; + case 0x2CC6: return 0x2CC7; + case 0x2CC8: return 0x2CC9; + case 0x2CCA: return 0x2CCB; + case 0x2CCC: return 0x2CCD; + case 0x2CCE: return 0x2CCF; + case 0x2CD0: return 0x2CD1; + case 0x2CD2: return 0x2CD3; + case 0x2CD4: return 0x2CD5; + case 0x2CD6: return 0x2CD7; + case 0x2CD8: return 0x2CD9; + case 0x2CDA: return 0x2CDB; + case 0x2CDC: return 0x2CDD; + case 0x2CDE: return 0x2CDF; + case 0x2CE0: return 0x2CE1; + case 0x2CE2: return 0x2CE3; + case 0x2CEB: return 0x2CEC; + case 0x2CED: return 0x2CEE; + case 0xA640: return 0xA641; + case 0xA642: return 0xA643; + case 0xA644: return 0xA645; + case 0xA646: return 0xA647; + case 0xA648: return 0xA649; + case 0xA64A: return 0xA64B; + case 0xA64C: return 0xA64D; + case 0xA64E: return 0xA64F; + case 0xA650: return 0xA651; + case 0xA652: return 0xA653; + case 0xA654: return 0xA655; + case 0xA656: return 0xA657; + case 0xA658: return 0xA659; + case 0xA65A: return 0xA65B; + case 0xA65C: return 0xA65D; + case 0xA65E: return 0xA65F; + case 0xA660: return 0xA661; + case 0xA662: return 0xA663; + case 0xA664: return 0xA665; + case 0xA666: return 0xA667; + case 0xA668: return 0xA669; + case 0xA66A: return 0xA66B; + case 0xA66C: return 0xA66D; + case 0xA680: return 0xA681; + case 0xA682: return 0xA683; + case 0xA684: return 0xA685; + case 0xA686: return 0xA687; + case 0xA688: return 0xA689; + case 0xA68A: return 0xA68B; + case 0xA68C: return 0xA68D; + case 0xA68E: return 0xA68F; + case 0xA690: return 0xA691; + case 0xA692: return 0xA693; + case 0xA694: return 0xA695; + case 0xA696: return 0xA697; + case 0xA722: return 0xA723; + case 0xA724: return 0xA725; + case 0xA726: return 0xA727; + case 0xA728: return 0xA729; + case 0xA72A: return 0xA72B; + case 0xA72C: return 0xA72D; + case 0xA72E: return 0xA72F; + case 0xA732: return 0xA733; + case 0xA734: return 0xA735; + case 0xA736: return 0xA737; + case 0xA738: return 0xA739; + case 0xA73A: return 0xA73B; + case 0xA73C: return 0xA73D; + case 0xA73E: return 0xA73F; + case 0xA740: return 0xA741; + case 0xA742: return 0xA743; + case 0xA744: return 0xA745; + case 0xA746: return 0xA747; + case 0xA748: return 0xA749; + case 0xA74A: return 0xA74B; + case 0xA74C: return 0xA74D; + case 0xA74E: return 0xA74F; + case 0xA750: return 0xA751; + case 0xA752: return 0xA753; + case 0xA754: return 0xA755; + case 0xA756: return 0xA757; + case 0xA758: return 0xA759; + case 0xA75A: return 0xA75B; + case 0xA75C: return 0xA75D; + case 0xA75E: return 0xA75F; + case 0xA760: return 0xA761; + case 0xA762: return 0xA763; + case 0xA764: return 0xA765; + case 0xA766: return 0xA767; + case 0xA768: return 0xA769; + case 0xA76A: return 0xA76B; + case 0xA76C: return 0xA76D; + case 0xA76E: return 0xA76F; + case 0xA779: return 0xA77A; + case 0xA77B: return 0xA77C; + case 0xA77D: return 0x1D79; + case 0xA77E: return 0xA77F; + case 0xA780: return 0xA781; + case 0xA782: return 0xA783; + case 0xA784: return 0xA785; + case 0xA786: return 0xA787; + case 0xA78B: return 0xA78C; + case 0xA78D: return 0x0265; + case 0xA790: return 0xA791; + case 0xA7A0: return 0xA7A1; + case 0xA7A2: return 0xA7A3; + case 0xA7A4: return 0xA7A5; + case 0xA7A6: return 0xA7A7; + case 0xA7A8: return 0xA7A9; + case 0xFF21: return 0xFF41; + case 0xFF22: return 0xFF42; + case 0xFF23: return 0xFF43; + case 0xFF24: return 0xFF44; + case 0xFF25: return 0xFF45; + case 0xFF26: return 0xFF46; + case 0xFF27: return 0xFF47; + case 0xFF28: return 0xFF48; + case 0xFF29: return 0xFF49; + case 0xFF2A: return 0xFF4A; + case 0xFF2B: return 0xFF4B; + case 0xFF2C: return 0xFF4C; + case 0xFF2D: return 0xFF4D; + case 0xFF2E: return 0xFF4E; + case 0xFF2F: return 0xFF4F; + case 0xFF30: return 0xFF50; + case 0xFF31: return 0xFF51; + case 0xFF32: return 0xFF52; + case 0xFF33: return 0xFF53; + case 0xFF34: return 0xFF54; + case 0xFF35: return 0xFF55; + case 0xFF36: return 0xFF56; + case 0xFF37: return 0xFF57; + case 0xFF38: return 0xFF58; + case 0xFF39: return 0xFF59; + case 0xFF3A: return 0xFF5A; + case 0x10400: return 0x10428; + case 0x10401: return 0x10429; + case 0x10402: return 0x1042A; + case 0x10403: return 0x1042B; + case 0x10404: return 0x1042C; + case 0x10405: return 0x1042D; + case 0x10406: return 0x1042E; + case 0x10407: return 0x1042F; + case 0x10408: return 0x10430; + case 0x10409: return 0x10431; + case 0x1040A: return 0x10432; + case 0x1040B: return 0x10433; + case 0x1040C: return 0x10434; + case 0x1040D: return 0x10435; + case 0x1040E: return 0x10436; + case 0x1040F: return 0x10437; + case 0x10410: return 0x10438; + case 0x10411: return 0x10439; + case 0x10412: return 0x1043A; + case 0x10413: return 0x1043B; + case 0x10414: return 0x1043C; + case 0x10415: return 0x1043D; + case 0x10416: return 0x1043E; + case 0x10417: return 0x1043F; + case 0x10418: return 0x10440; + case 0x10419: return 0x10441; + case 0x1041A: return 0x10442; + case 0x1041B: return 0x10443; + case 0x1041C: return 0x10444; + case 0x1041D: return 0x10445; + case 0x1041E: return 0x10446; + case 0x1041F: return 0x10447; + case 0x10420: return 0x10448; + case 0x10421: return 0x10449; + case 0x10422: return 0x1044A; + case 0x10423: return 0x1044B; + case 0x10424: return 0x1044C; + case 0x10425: return 0x1044D; + case 0x10426: return 0x1044E; + case 0x10427: return 0x1044F; + default: return ch; + } +} diff --git a/misc/source/darkplaces-src/utf8lib.h b/misc/source/darkplaces-src/utf8lib.h new file mode 100644 index 00000000..9124c4e7 --- /dev/null +++ b/misc/source/darkplaces-src/utf8lib.h @@ -0,0 +1,65 @@ +/* + * UTF-8 utility functions for DarkPlaces + */ +#ifndef UTF8LIB_H__ +#define UTF8LIB_H__ + +#include "qtypes.h" + +// types for unicode strings +// let them be 32 bit for now +// normally, whcar_t is 16 or 32 bit, 16 on linux I think, 32 on haiku and maybe windows +#ifdef _MSC_VER +typedef __int32 U_int32; +#else +#include +#include +typedef int32_t U_int32; +#endif + +// Uchar, a wide character +typedef U_int32 Uchar; + +// Initialize UTF8, this registers cvars which allows for UTF8 to be disabled +// completely. +// When UTF8 is disabled, every u8_ function will work exactly as you'd expect +// a non-utf8 version to work: u8_strlen() will wrap to strlen() +// u8_byteofs() and u8_charidx() will simply return whatever is passed as index parameter +// u8_getchar() will will just return the next byte, u8_fromchar will write one byte, ... +extern cvar_t utf8_enable; +void u8_Init(void); + +size_t u8_strlen(const char*); +size_t u8_strnlen(const char*, size_t); +int u8_byteofs(const char*, size_t, size_t*); +int u8_charidx(const char*, size_t, size_t*); +size_t u8_bytelen(const char*, size_t); +size_t u8_prevbyte(const char*, size_t); +Uchar u8_getchar_utf8_enabled(const char*, const char**); +Uchar u8_getnchar_utf8_enabled(const char*, const char**, size_t); +int u8_fromchar(Uchar, char*, size_t); +size_t u8_wcstombs(char*, const Uchar*, size_t); +size_t u8_COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); + +// returns a static buffer, use this for inlining +char *u8_encodech(Uchar ch, size_t*); + +size_t u8_strpad(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth); + +/* Careful: if we disable utf8 but not freetype, we wish to see freetype chars + * for normal letters. So use E000+x for special chars, but leave the freetype stuff for the + * rest: + */ +extern Uchar u8_quake2utf8map[256]; +// these defines get a bit tricky, as c and e may be aliased to the same variable +#define u8_getchar(c,e) (utf8_enable.integer ? u8_getchar_utf8_enabled(c,e) : (u8_quake2utf8map[(unsigned char)(*(e) = (c) + 1)[-1]])) +#define u8_getchar_noendptr(c) (utf8_enable.integer ? u8_getchar_utf8_enabled(c,NULL) : (u8_quake2utf8map[(unsigned char)*(c)])) +#define u8_getchar_check(c,e) ((e) ? u8_getchar((c),(e)) : u8_getchar_noendptr((c))) +#define u8_getnchar(c,e,n) (utf8_enable.integer ? u8_getnchar_utf8_enabled(c,e,n) : ((n) <= 0 ? ((*(e) = c), 0) : (u8_quake2utf8map[(unsigned char)(*(e) = (c) + 1)[-1]]))) +#define u8_getnchar_noendptr(c,n) (utf8_enable.integer ? u8_getnchar_utf8_enabled(c,NULL,n) : ((n) <= 0 ? 0 : (u8_quake2utf8map[(unsigned char)*(c)]))) +#define u8_getnchar_check(c,e,n) ((e) ? u8_getchar((c),(e),(n)) : u8_getchar_noendptr((c),(n))) + +Uchar u8_toupper(Uchar ch); +Uchar u8_tolower(Uchar ch); + +#endif // UTF8LIB_H__ diff --git a/misc/source/darkplaces-src/vid.h b/misc/source/darkplaces-src/vid.h new file mode 100644 index 00000000..4840ae4a --- /dev/null +++ b/misc/source/darkplaces-src/vid.h @@ -0,0 +1,292 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// vid.h -- video driver defs + +#ifndef VID_H +#define VID_H + +#define ENGINE_ICON ( (gamemode == GAME_NEXUIZ) ? nexuiz_xpm : darkplaces_xpm ) + +extern int cl_available; + +#define MAX_TEXTUREUNITS 16 + +typedef enum renderpath_e +{ + RENDERPATH_GL11, + RENDERPATH_GL13, + RENDERPATH_GL20, + RENDERPATH_D3D9, + RENDERPATH_D3D10, + RENDERPATH_D3D11, + RENDERPATH_SOFT, + RENDERPATH_GLES1, + RENDERPATH_GLES2 +} +renderpath_t; + +typedef struct viddef_support_s +{ + qboolean gl20shaders; + qboolean gl20shaders130; + qboolean amd_texture_texture4; + qboolean arb_depth_texture; + qboolean arb_draw_buffers; + qboolean arb_multitexture; + qboolean arb_occlusion_query; + qboolean arb_shadow; + qboolean arb_texture_compression; + qboolean arb_texture_cube_map; + qboolean arb_texture_env_combine; + qboolean arb_texture_gather; + qboolean arb_texture_non_power_of_two; + qboolean arb_vertex_buffer_object; + qboolean ati_separate_stencil; + qboolean ext_blend_minmax; + qboolean ext_blend_subtract; + qboolean ext_draw_range_elements; + qboolean ext_framebuffer_object; + qboolean ext_stencil_two_side; + qboolean ext_texture_3d; + qboolean ext_texture_compression_s3tc; + qboolean ext_texture_edge_clamp; + qboolean ext_texture_filter_anisotropic; + qboolean ext_texture_srgb; + qboolean arb_multisample; +} +viddef_support_t; + +typedef struct viddef_mode_s +{ + int width; + int height; + int bitsperpixel; + qboolean fullscreen; + float refreshrate; + qboolean userefreshrate; + qboolean stereobuffer; + int samples; +} +viddef_mode_t; + +typedef struct viddef_s +{ + // these are set by VID_Mode + viddef_mode_t mode; + // used in many locations in the renderer + int width; + int height; + int bitsperpixel; + qboolean fullscreen; + float refreshrate; + qboolean userefreshrate; + qboolean stereobuffer; + int samples; + qboolean stencil; + qboolean sRGB2D; // whether 2D rendering is sRGB corrected (based on sRGBcapable2D) + qboolean sRGB3D; // whether 3D rendering is sRGB corrected (based on sRGBcapable3D) + qboolean sRGBcapable2D; // whether 2D rendering can be sRGB corrected (renderpath, v_hwgamma) + qboolean sRGBcapable3D; // whether 3D rendering can be sRGB corrected (renderpath, v_hwgamma) + + renderpath_t renderpath; + qboolean forcevbo; // some renderpaths can not operate without it + qboolean useinterleavedarrays; // required by some renderpaths + qboolean allowalphatocoverage; // indicates the GL_AlphaToCoverage function works on this renderpath and framebuffer + + unsigned int texunits; + unsigned int teximageunits; + unsigned int texarrayunits; + unsigned int drawrangeelements_maxvertices; + unsigned int drawrangeelements_maxindices; + + unsigned int maxtexturesize_2d; + unsigned int maxtexturesize_3d; + unsigned int maxtexturesize_cubemap; + unsigned int max_anisotropy; + unsigned int maxdrawbuffers; + + viddef_support_t support; + + // in RENDERPATH_SOFT this is a 32bpp native-endian ARGB framebuffer + // (native-endian ARGB meaning that in little endian it is BGRA bytes, + // in big endian it is ARGB byte order, the format is converted during + // blit to the window) + unsigned int *softpixels; + unsigned int *softdepthpixels; + + int forcetextype; // always use GL_BGRA for D3D, always use GL_RGBA for GLES, etc +} viddef_t; + +// global video state +extern viddef_t vid; +extern void (*vid_menudrawfn)(void); +extern void (*vid_menukeyfn)(int key); + +#define MAXJOYAXIS 16 +// if this is changed, the corresponding code in vid_shared.c must be updated +#define MAXJOYBUTTON 36 +typedef struct vid_joystate_s +{ + float axis[MAXJOYAXIS]; // -1 to +1 + unsigned char button[MAXJOYBUTTON]; // 0 or 1 + qboolean is360; // indicates this joystick is a Microsoft Xbox 360 Controller For Windows +} +vid_joystate_t; + +extern vid_joystate_t vid_joystate; + +extern cvar_t joy_index; +extern cvar_t joy_enable; +extern cvar_t joy_detected; +extern cvar_t joy_active; + +float VID_JoyState_GetAxis(const vid_joystate_t *joystate, int axis, float sensitivity, float deadzone); +void VID_ApplyJoyState(vid_joystate_t *joystate); +void VID_BuildJoyState(vid_joystate_t *joystate); +void VID_Shared_BuildJoyState_Begin(vid_joystate_t *joystate); +void VID_Shared_BuildJoyState_Finish(vid_joystate_t *joystate); +int VID_Shared_SetJoystick(int index); +qboolean VID_JoyBlockEmulatedKeys(int keycode); +void VID_EnableJoystick(qboolean enable); + +extern qboolean vid_hidden; +extern qboolean vid_activewindow; +extern cvar_t vid_hardwaregammasupported; +extern qboolean vid_usinghwgamma; +extern qboolean vid_supportrefreshrate; + +extern cvar_t vid_soft; +extern cvar_t vid_soft_threads; +extern cvar_t vid_soft_interlace; + +extern cvar_t vid_fullscreen; +extern cvar_t vid_width; +extern cvar_t vid_height; +extern cvar_t vid_bitsperpixel; +extern cvar_t vid_samples; +extern cvar_t vid_refreshrate; +extern cvar_t vid_userefreshrate; +extern cvar_t vid_vsync; +extern cvar_t vid_mouse; +extern cvar_t vid_grabkeyboard; +extern cvar_t vid_touchscreen; +extern cvar_t vid_stick_mouse; +extern cvar_t vid_resizable; +extern cvar_t vid_minwidth; +extern cvar_t vid_minheight; + +extern cvar_t gl_finish; + +extern cvar_t v_gamma; +extern cvar_t v_contrast; +extern cvar_t v_brightness; +extern cvar_t v_color_enable; +extern cvar_t v_color_black_r; +extern cvar_t v_color_black_g; +extern cvar_t v_color_black_b; +extern cvar_t v_color_grey_r; +extern cvar_t v_color_grey_g; +extern cvar_t v_color_grey_b; +extern cvar_t v_color_white_r; +extern cvar_t v_color_white_g; +extern cvar_t v_color_white_b; +extern cvar_t v_hwgamma; + +// brand of graphics chip +extern const char *gl_vendor; +// graphics chip model and other information +extern const char *gl_renderer; +// begins with 1.0.0, 1.1.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, or 1.4.0 +extern const char *gl_version; +// extensions list, space separated +extern const char *gl_extensions; +// WGL, GLX, or AGL +extern const char *gl_platform; +// another extensions list, containing platform-specific extensions that are +// not in the main list +extern const char *gl_platformextensions; +// name of driver library (opengl32.dll, libGL.so.1, or whatever) +extern char gl_driver[256]; + +// compatibility hacks +extern qboolean isG200; +extern qboolean isRagePro; + +void *GL_GetProcAddress(const char *name); +qboolean GL_CheckExtension(const char *minglver_or_ext, const dllfunction_t *funcs, const char *disableparm, int silent); + +void VID_Shared_Init(void); + +void GL_Init (void); + +void VID_ClearExtensions(void); +void VID_CheckExtensions(void); + +void VID_Init (void); +// Called at startup + +void VID_Shutdown (void); +// Called at shutdown + +int VID_SetMode (int modenum); +// sets the mode; only used by the Quake engine for resetting to mode 0 (the +// base mode) on memory allocation failures + +qboolean VID_InitMode(viddef_mode_t *mode); +// allocates and opens an appropriate OpenGL context (and its window) + + +// sets hardware gamma correction, returns false if the device does not +// support gamma control +// (ONLY called by VID_UpdateGamma and VID_RestoreSystemGamma) +int VID_SetGamma(unsigned short *ramps, int rampsize); +// gets hardware gamma correction, returns false if the device does not +// support gamma control +// (ONLY called by VID_UpdateGamma and VID_RestoreSystemGamma) +int VID_GetGamma(unsigned short *ramps, int rampsize); +// makes sure ramp arrays are big enough and calls VID_GetGamma/VID_SetGamma +// (ONLY to be called from VID_Finish!) +void VID_UpdateGamma(qboolean force, int rampsize); +// turns off hardware gamma ramps immediately +// (called from various shutdown/deactivation functions) +void VID_RestoreSystemGamma(void); + +void VID_SetMouse (qboolean fullscreengrab, qboolean relative, qboolean hidecursor); +void VID_Finish (void); + +void VID_Restart_f(void); + +void VID_Start(void); + +extern unsigned int vid_gammatables_serial; // so other subsystems can poll if gamma parameters have changed; this starts with 0 and gets increased by 1 each time the gamma parameters get changed and VID_BuildGammaTables should be called again +extern qboolean vid_gammatables_trivial; // this is set to true if all color control values are at default setting, and it therefore would make no sense to use the gamma table +void VID_BuildGammaTables(unsigned short *ramps, int rampsize); // builds the current gamma tables into an array (needs 3*rampsize items) + +typedef struct +{ + int width, height, bpp, refreshrate; + int pixelheight_num, pixelheight_denom; +} +vid_mode_t; +size_t VID_ListModes(vid_mode_t *modes, size_t maxcount); +size_t VID_SortModes(vid_mode_t *modes, size_t count, qboolean usebpp, qboolean userefreshrate, qboolean useaspect); +void VID_Soft_SharedSetup(void); +#endif + diff --git a/misc/source/darkplaces-src/vid_agl.c b/misc/source/darkplaces-src/vid_agl.c new file mode 100644 index 00000000..d21ffc06 --- /dev/null +++ b/misc/source/darkplaces-src/vid_agl.c @@ -0,0 +1,1188 @@ +/* + vid_agl.c + + Mac OS X OpenGL and input module, using Carbon and AGL + + Copyright (C) 2005-2006 Mathieu Olivier + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "quakedef.h" +#include "vid_agl_mackeys.h" // this is SDL/src/video/maccommon/SDL_mackeys.h + +#ifndef kCGLCEMPEngine +#define kCGLCEMPEngine 313 +#endif + +// Tell startup code that we have a client +int cl_available = true; + +qboolean vid_supportrefreshrate = true; + +// AGL prototypes +AGLPixelFormat (*qaglChoosePixelFormat) (const AGLDevice *gdevs, GLint ndev, const GLint *attribList); +AGLContext (*qaglCreateContext) (AGLPixelFormat pix, AGLContext share); +GLboolean (*qaglDestroyContext) (AGLContext ctx); +void (*qaglDestroyPixelFormat) (AGLPixelFormat pix); +const GLubyte* (*qaglErrorString) (GLenum code); +GLenum (*qaglGetError) (void); +GLboolean (*qaglSetCurrentContext) (AGLContext ctx); +GLboolean (*qaglSetDrawable) (AGLContext ctx, AGLDrawable draw); +GLboolean (*qaglSetFullScreen) (AGLContext ctx, GLsizei width, GLsizei height, GLsizei freq, GLint device); +GLboolean (*qaglSetInteger) (AGLContext ctx, GLenum pname, const GLint *params); +void (*qaglSwapBuffers) (AGLContext ctx); +CGLError (*qCGLEnable) (CGLContextObj ctx, CGLContextEnable pname); +CGLError (*qCGLDisable) (CGLContextObj ctx, CGLContextEnable pname); +CGLContextObj (*qCGLGetCurrentContext) (void); + +static qboolean multithreadedgl; +static qboolean mouse_avail = true; +static qboolean vid_usingmouse = false; +static qboolean vid_usinghidecursor = false; +static qboolean vid_usingnoaccel = false; + +static qboolean vid_isfullscreen = false; +static qboolean vid_usingvsync = false; + +static qboolean sound_active = true; + +static cvar_t apple_multithreadedgl = {CVAR_SAVE, "apple_multithreadedgl", "1", "makes use of a second thread for the OpenGL driver (if possible) rather than using the engine thread (note: this is done automatically on most other operating systems)"}; +static cvar_t apple_mouse_noaccel = {CVAR_SAVE, "apple_mouse_noaccel", "1", "disables mouse acceleration while DarkPlaces is active"}; + +static AGLContext context; +static WindowRef window; + +static double originalMouseSpeed = -1.0; + +io_connect_t IN_GetIOHandle(void) +{ + io_connect_t iohandle = MACH_PORT_NULL; + kern_return_t status; + io_service_t iohidsystem = MACH_PORT_NULL; + mach_port_t masterport; + + status = IOMasterPort(MACH_PORT_NULL, &masterport); + if(status != KERN_SUCCESS) + return 0; + + iohidsystem = IORegistryEntryFromPath(masterport, kIOServicePlane ":/IOResources/IOHIDSystem"); + if(!iohidsystem) + return 0; + + status = IOServiceOpen(iohidsystem, mach_task_self(), kIOHIDParamConnectType, &iohandle); + IOObjectRelease(iohidsystem); + + return iohandle; +} + +void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor) +{ + if (!mouse_avail || !window) + relative = hidecursor = false; + + if (relative) + { + if(vid_usingmouse && (vid_usingnoaccel != !!apple_mouse_noaccel.integer)) + VID_SetMouse(false, false, false); // ungrab first! + if (!vid_usingmouse) + { + Rect winBounds; + CGPoint winCenter; + + SelectWindow(window); + + // Put the mouse cursor at the center of the window + GetWindowBounds (window, kWindowContentRgn, &winBounds); + winCenter.x = (winBounds.left + winBounds.right) / 2; + winCenter.y = (winBounds.top + winBounds.bottom) / 2; + CGWarpMouseCursorPosition(winCenter); + + // Lock the mouse pointer at its current position + CGAssociateMouseAndMouseCursorPosition(false); + + // Save the status of mouse acceleration + originalMouseSpeed = -1.0; // in case of error + if(apple_mouse_noaccel.integer) + { + io_connect_t mouseDev = IN_GetIOHandle(); + if(mouseDev != 0) + { + if(IOHIDGetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), &originalMouseSpeed) == kIOReturnSuccess) + { + Con_DPrintf("previous mouse acceleration: %f\n", originalMouseSpeed); + if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), -1.0) != kIOReturnSuccess) + { + Con_Print("Could not disable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + Cvar_SetValueQuick(&apple_mouse_noaccel, 0); + } + } + else + { + Con_Print("Could not disable mouse acceleration (failed at IOHIDGetAccelerationWithKey).\n"); + Cvar_SetValueQuick(&apple_mouse_noaccel, 0); + } + IOServiceClose(mouseDev); + } + else + { + Con_Print("Could not disable mouse acceleration (failed at IO_GetIOHandle).\n"); + Cvar_SetValueQuick(&apple_mouse_noaccel, 0); + } + } + + vid_usingmouse = true; + vid_usingnoaccel = !!apple_mouse_noaccel.integer; + } + } + else + { + if (vid_usingmouse) + { + if(originalMouseSpeed != -1.0) + { + io_connect_t mouseDev = IN_GetIOHandle(); + if(mouseDev != 0) + { + Con_DPrintf("restoring mouse acceleration to: %f\n", originalMouseSpeed); + if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), originalMouseSpeed) != kIOReturnSuccess) + Con_Print("Could not re-enable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + IOServiceClose(mouseDev); + } + else + Con_Print("Could not re-enable mouse acceleration (failed at IO_GetIOHandle).\n"); + } + + CGAssociateMouseAndMouseCursorPosition(true); + + vid_usingmouse = false; + } + } + + if (vid_usinghidecursor != hidecursor) + { + vid_usinghidecursor = hidecursor; + if (hidecursor) + CGDisplayHideCursor(CGMainDisplayID()); + else + CGDisplayShowCursor(CGMainDisplayID()); + } +} + +#define GAMMA_TABLE_SIZE 256 +void VID_Finish (void) +{ + qboolean vid_usevsync; + + // handle changes of the vsync option + vid_usevsync = (vid_vsync.integer && !cls.timedemo); + if (vid_usingvsync != vid_usevsync) + { + GLint sync = (vid_usevsync ? 1 : 0); + + if (qaglSetInteger(context, AGL_SWAP_INTERVAL, &sync) == GL_TRUE) + { + vid_usingvsync = vid_usevsync; + Con_DPrintf("Vsync %s\n", vid_usevsync ? "activated" : "deactivated"); + } + else + Con_Printf("ERROR: can't %s vsync\n", vid_usevsync ? "activate" : "deactivate"); + } + + if (!vid_hidden) + { + if (r_speeds.integer == 2 || gl_finish.integer) + GL_Finish(); + qaglSwapBuffers(context); + } + VID_UpdateGamma(false, GAMMA_TABLE_SIZE); + + if (apple_multithreadedgl.integer) + { + if (!multithreadedgl) + { + if(qCGLGetCurrentContext && qCGLEnable && qCGLDisable) + { + CGLContextObj ctx = qCGLGetCurrentContext(); + CGLError e = qCGLEnable(ctx, kCGLCEMPEngine); + if(e == kCGLNoError) + multithreadedgl = true; + else + { + Con_Printf("WARNING: can't enable multithreaded GL, error %d\n", (int) e); + Cvar_SetValueQuick(&apple_multithreadedgl, 0); + } + } + else + { + Con_Printf("WARNING: can't enable multithreaded GL, CGL functions not present\n"); + Cvar_SetValueQuick(&apple_multithreadedgl, 0); + } + } + } + else + { + if (multithreadedgl) + { + if(qCGLGetCurrentContext && qCGLEnable && qCGLDisable) + { + CGLContextObj ctx = qCGLGetCurrentContext(); + qCGLDisable(ctx, kCGLCEMPEngine); + multithreadedgl = false; + } + } + } +} + +int VID_SetGamma(unsigned short *ramps, int rampsize) +{ + CGGammaValue table_red [GAMMA_TABLE_SIZE]; + CGGammaValue table_green [GAMMA_TABLE_SIZE]; + CGGammaValue table_blue [GAMMA_TABLE_SIZE]; + int i; + + // Convert the unsigned short table into 3 float tables + for (i = 0; i < rampsize; i++) + table_red[i] = (float)ramps[i] / 65535.0f; + for (i = 0; i < rampsize; i++) + table_green[i] = (float)ramps[i + rampsize] / 65535.0f; + for (i = 0; i < rampsize; i++) + table_blue[i] = (float)ramps[i + 2 * rampsize] / 65535.0f; + + if (CGSetDisplayTransferByTable(CGMainDisplayID(), rampsize, table_red, table_green, table_blue) != CGDisplayNoErr) + { + Con_Print("VID_SetGamma: ERROR: CGSetDisplayTransferByTable failed!\n"); + return false; + } + + return true; +} + +int VID_GetGamma(unsigned short *ramps, int rampsize) +{ + CGGammaValue table_red [GAMMA_TABLE_SIZE]; + CGGammaValue table_green [GAMMA_TABLE_SIZE]; + CGGammaValue table_blue [GAMMA_TABLE_SIZE]; + CGTableCount actualsize = 0; + int i; + + // Get the gamma ramps from the system + if (CGGetDisplayTransferByTable(CGMainDisplayID(), rampsize, table_red, table_green, table_blue, &actualsize) != CGDisplayNoErr) + { + Con_Print("VID_GetGamma: ERROR: CGGetDisplayTransferByTable failed!\n"); + return false; + } + if (actualsize != (unsigned int)rampsize) + { + Con_Printf("VID_GetGamma: ERROR: invalid gamma table size (%u != %u)\n", actualsize, rampsize); + return false; + } + + // Convert the 3 float tables into 1 unsigned short table + for (i = 0; i < rampsize; i++) + ramps[i] = table_red[i] * 65535.0f; + for (i = 0; i < rampsize; i++) + ramps[i + rampsize] = table_green[i] * 65535.0f; + for (i = 0; i < rampsize; i++) + ramps[i + 2 * rampsize] = table_blue[i] * 65535.0f; + + return true; +} + +void signal_handler(int sig) +{ + printf("Received signal %d, exiting...\n", sig); + VID_RestoreSystemGamma(); + Sys_Quit(1); +} + +void InitSig(void) +{ + signal(SIGHUP, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGQUIT, signal_handler); + signal(SIGILL, signal_handler); + signal(SIGTRAP, signal_handler); + signal(SIGIOT, signal_handler); + signal(SIGBUS, signal_handler); + signal(SIGFPE, signal_handler); + signal(SIGSEGV, signal_handler); + signal(SIGTERM, signal_handler); +} + +void VID_Init(void) +{ + InitSig(); // trap evil signals + Cvar_RegisterVariable(&apple_multithreadedgl); + Cvar_RegisterVariable(&apple_mouse_noaccel); +// COMMANDLINEOPTION: Input: -nomouse disables mouse support (see also vid_mouse cvar) + if (COM_CheckParm ("-nomouse")) + mouse_avail = false; +} + +static void *prjobj = NULL; +static void *cglobj = NULL; + +static void GL_CloseLibrary(void) +{ + if (cglobj) + dlclose(cglobj); + cglobj = NULL; + if (prjobj) + dlclose(prjobj); + prjobj = NULL; + gl_driver[0] = 0; + gl_extensions = ""; + gl_platform = ""; + gl_platformextensions = ""; +} + +static int GL_OpenLibrary(void) +{ + const char *name = "/System/Library/Frameworks/AGL.framework/AGL"; + const char *name2 = "/System/Library/Frameworks/OpenGL.framework/OpenGL"; + + Con_Printf("Loading OpenGL driver %s\n", name); + GL_CloseLibrary(); + if (!(prjobj = dlopen(name, RTLD_LAZY))) + { + Con_Printf("Unable to open symbol list for %s\n", name); + return false; + } + strlcpy(gl_driver, name, sizeof(gl_driver)); + + Con_Printf("Loading OpenGL driver %s\n", name2); + if (!(cglobj = dlopen(name2, RTLD_LAZY))) + Con_Printf("Unable to open symbol list for %s; multithreaded GL disabled\n", name); + + return true; +} + +void *GL_GetProcAddress(const char *name) +{ + return dlsym(prjobj, name); +} + +static void *CGL_GetProcAddress(const char *name) +{ + if(!cglobj) + return NULL; + return dlsym(cglobj, name); +} + +void VID_Shutdown(void) +{ + if (context == NULL && window == NULL) + return; + + VID_EnableJoystick(false); + VID_SetMouse(false, false, false); + VID_RestoreSystemGamma(); + + if (context != NULL) + { + qaglDestroyContext(context); + context = NULL; + } + + if (vid_isfullscreen) + CGReleaseAllDisplays(); + + if (window != NULL) + { + DisposeWindow(window); + window = NULL; + } + + vid_hidden = true; + vid_isfullscreen = false; + + GL_CloseLibrary(); + Key_ClearStates (); +} + +// Since the event handler can be called at any time, we store the events for later processing +static qboolean AsyncEvent_Quitting = false; +static qboolean AsyncEvent_Collapsed = false; +static OSStatus MainWindowEventHandler (EventHandlerCallRef nextHandler, EventRef event, void *userData) +{ + OSStatus err = noErr; + + switch (GetEventKind (event)) + { + case kEventWindowClosed: + AsyncEvent_Quitting = true; + break; + + // Docked (start) + case kEventWindowCollapsing: + AsyncEvent_Collapsed = true; + break; + + // Undocked / restored (end) + case kEventWindowExpanded: + AsyncEvent_Collapsed = false; + break; + + default: + err = eventNotHandledErr; + break; + } + + return err; +} + +static void VID_AppFocusChanged(qboolean windowIsActive) +{ + if (vid_activewindow != windowIsActive) + { + vid_activewindow = windowIsActive; + if (!vid_activewindow) + VID_RestoreSystemGamma(); + } + + if (windowIsActive || !snd_mutewhenidle.integer) + { + if (!sound_active) + { + S_UnblockSound (); + sound_active = true; + } + } + else + { + if (sound_active) + { + S_BlockSound (); + sound_active = false; + } + } +} + +static void VID_ProcessPendingAsyncEvents (void) +{ + // Collapsed / expanded + if (AsyncEvent_Collapsed != vid_hidden) + { + vid_hidden = !vid_hidden; + VID_AppFocusChanged(!vid_hidden); + } + + // Closed + if (AsyncEvent_Quitting) + Sys_Quit(0); +} + +static void VID_BuildAGLAttrib(GLint *attrib, qboolean stencil, qboolean fullscreen, qboolean stereobuffer, int samples) +{ + *attrib++ = AGL_RGBA; + *attrib++ = AGL_RED_SIZE;*attrib++ = stencil ? 8 : 5; + *attrib++ = AGL_GREEN_SIZE;*attrib++ = stencil ? 8 : 5; + *attrib++ = AGL_BLUE_SIZE;*attrib++ = stencil ? 8 : 5; + *attrib++ = AGL_DOUBLEBUFFER; + *attrib++ = AGL_DEPTH_SIZE;*attrib++ = stencil ? 24 : 16; + + // if stencil is enabled, ask for alpha too + if (stencil) + { + *attrib++ = AGL_STENCIL_SIZE;*attrib++ = 8; + *attrib++ = AGL_ALPHA_SIZE;*attrib++ = 8; + } + if (fullscreen) + *attrib++ = AGL_FULLSCREEN; + if (stereobuffer) + *attrib++ = AGL_STEREO; +#ifdef AGL_SAMPLE_BUFFERS_ARB +#ifdef AGL_SAMPLES_ARB + if (samples > 1) + { + *attrib++ = AGL_SAMPLE_BUFFERS_ARB; + *attrib++ = 1; + *attrib++ = AGL_SAMPLES_ARB; + *attrib++ = samples; + } +#endif +#endif + + *attrib++ = AGL_NONE; +} + +qboolean VID_InitMode(viddef_mode_t *mode) +{ + const EventTypeSpec winEvents[] = + { + { kEventClassWindow, kEventWindowClosed }, + { kEventClassWindow, kEventWindowCollapsing }, + { kEventClassWindow, kEventWindowExpanded }, + }; + OSStatus carbonError; + Rect windowBounds; + CFStringRef windowTitle; + AGLPixelFormat pixelFormat; + GLint attributes [32]; + GLenum error; + + if (!GL_OpenLibrary()) + { + Con_Printf("Unable to load GL driver\n"); + return false; + } + + if ((qaglChoosePixelFormat = (AGLPixelFormat (*) (const AGLDevice *gdevs, GLint ndev, const GLint *attribList))GL_GetProcAddress("aglChoosePixelFormat")) == NULL + || (qaglCreateContext = (AGLContext (*) (AGLPixelFormat pix, AGLContext share))GL_GetProcAddress("aglCreateContext")) == NULL + || (qaglDestroyContext = (GLboolean (*) (AGLContext ctx))GL_GetProcAddress("aglDestroyContext")) == NULL + || (qaglDestroyPixelFormat = (void (*) (AGLPixelFormat pix))GL_GetProcAddress("aglDestroyPixelFormat")) == NULL + || (qaglErrorString = (const GLubyte* (*) (GLenum code))GL_GetProcAddress("aglErrorString")) == NULL + || (qaglGetError = (GLenum (*) (void))GL_GetProcAddress("aglGetError")) == NULL + || (qaglSetCurrentContext = (GLboolean (*) (AGLContext ctx))GL_GetProcAddress("aglSetCurrentContext")) == NULL + || (qaglSetDrawable = (GLboolean (*) (AGLContext ctx, AGLDrawable draw))GL_GetProcAddress("aglSetDrawable")) == NULL + || (qaglSetFullScreen = (GLboolean (*) (AGLContext ctx, GLsizei width, GLsizei height, GLsizei freq, GLint device))GL_GetProcAddress("aglSetFullScreen")) == NULL + || (qaglSetInteger = (GLboolean (*) (AGLContext ctx, GLenum pname, const GLint *params))GL_GetProcAddress("aglSetInteger")) == NULL + || (qaglSwapBuffers = (void (*) (AGLContext ctx))GL_GetProcAddress("aglSwapBuffers")) == NULL + ) + { + Con_Printf("AGL functions not found\n"); + ReleaseWindow(window); + return false; + } + + qCGLEnable = (CGLError (*) (CGLContextObj ctx, CGLContextEnable pname)) CGL_GetProcAddress("CGLEnable"); + qCGLDisable = (CGLError (*) (CGLContextObj ctx, CGLContextEnable pname)) CGL_GetProcAddress("CGLDisable"); + qCGLGetCurrentContext = (CGLContextObj (*) (void)) CGL_GetProcAddress("CGLGetCurrentContext"); + if(!qCGLEnable || !qCGLDisable || !qCGLGetCurrentContext) + Con_Printf("CGL functions not found; disabling multithreaded OpenGL\n"); + + // Ignore the events from the previous window + AsyncEvent_Quitting = false; + AsyncEvent_Collapsed = false; + + // Create the window, a bit towards the center of the screen + windowBounds.left = 100; + windowBounds.top = 100; + windowBounds.right = mode->width + 100; + windowBounds.bottom = mode->height + 100; + carbonError = CreateNewWindow(kDocumentWindowClass, kWindowStandardFloatingAttributes | kWindowStandardHandlerAttribute, &windowBounds, &window); + if (carbonError != noErr || window == NULL) + { + Con_Printf("Unable to create window (error %u)\n", (unsigned)carbonError); + return false; + } + + // Set the window title + windowTitle = CFSTR("DarkPlaces AGL"); + SetWindowTitleWithCFString(window, windowTitle); + + // Install the callback function for the window events we can't get + // through ReceiveNextEvent (i.e. close, collapse, and expand) + InstallWindowEventHandler (window, NewEventHandlerUPP (MainWindowEventHandler), + GetEventTypeCount(winEvents), winEvents, window, NULL); + + // Create the desired attribute list + VID_BuildAGLAttrib(attributes, mode->bitsperpixel == 32, mode->fullscreen, mode->stereobuffer, mode->samples); + + if (!mode->fullscreen) + { + // Output to Window + pixelFormat = qaglChoosePixelFormat(NULL, 0, attributes); + error = qaglGetError(); + if (error != AGL_NO_ERROR) + { + Con_Printf("qaglChoosePixelFormat FAILED: %s\n", + (char *)qaglErrorString(error)); + ReleaseWindow(window); + return false; + } + } + else // Output is fullScreen + { + CGDirectDisplayID mainDisplay; + CFDictionaryRef refDisplayMode; + GDHandle gdhDisplay; + + // Get the mainDisplay and set resolution to current + mainDisplay = CGMainDisplayID(); + CGDisplayCapture(mainDisplay); + + // TOCHECK: not sure whether or not it's necessary to change the resolution + // "by hand", or if aglSetFullscreen does the job anyway + refDisplayMode = CGDisplayBestModeForParametersAndRefreshRateWithProperty(mainDisplay, mode->bitsperpixel, mode->width, mode->height, mode->refreshrate, kCGDisplayModeIsSafeForHardware, NULL); + CGDisplaySwitchToMode(mainDisplay, refDisplayMode); + DMGetGDeviceByDisplayID((DisplayIDType)mainDisplay, &gdhDisplay, false); + + // Set pixel format with built attribs + // Note: specifying a device is *required* for AGL_FullScreen + pixelFormat = qaglChoosePixelFormat(&gdhDisplay, 1, attributes); + error = qaglGetError(); + if (error != AGL_NO_ERROR) + { + Con_Printf("qaglChoosePixelFormat FAILED: %s\n", + (char *)qaglErrorString(error)); + ReleaseWindow(window); + return false; + } + } + + // Create a context using the pform + context = qaglCreateContext(pixelFormat, NULL); + error = qaglGetError(); + if (error != AGL_NO_ERROR) + { + Con_Printf("qaglCreateContext FAILED: %s\n", + (char *)qaglErrorString(error)); + } + + // Make the context the current one ('enable' it) + qaglSetCurrentContext(context); + error = qaglGetError(); + if (error != AGL_NO_ERROR) + { + Con_Printf("qaglSetCurrentContext FAILED: %s\n", + (char *)qaglErrorString(error)); + ReleaseWindow(window); + return false; + } + + // Discard pform + qaglDestroyPixelFormat(pixelFormat); + + // Attempt fullscreen if requested + if (mode->fullscreen) + { + qaglSetFullScreen (context, mode->width, mode->height, mode->refreshrate, 0); + error = qaglGetError(); + if (error != AGL_NO_ERROR) + { + Con_Printf("qaglSetFullScreen FAILED: %s\n", + (char *)qaglErrorString(error)); + return false; + } + } + else + { + // Set Window as Drawable + qaglSetDrawable(context, GetWindowPort(window)); + error = qaglGetError(); + if (error != AGL_NO_ERROR) + { + Con_Printf("qaglSetDrawable FAILED: %s\n", + (char *)qaglErrorString(error)); + ReleaseWindow(window); + return false; + } + } + + if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL) + Sys_Error("glGetString not found in %s", gl_driver); + + gl_platformextensions = ""; + gl_platform = "AGL"; + + multithreadedgl = false; + vid_isfullscreen = mode->fullscreen; + vid_usingmouse = false; + vid_usinghidecursor = false; + vid_hidden = false; + vid_activewindow = true; + sound_active = true; + GL_Init(); + + SelectWindow(window); + ShowWindow(window); + + return true; +} + +static void Handle_KeyMod(UInt32 keymod) +{ + const struct keymod_to_event_s { UInt32 keybit; keynum_t event; } keymod_events [] = + { + { cmdKey, K_AUX1 }, + { shiftKey, K_SHIFT }, + { alphaLock, K_CAPSLOCK }, + { optionKey, K_ALT }, + { controlKey, K_CTRL }, + { kEventKeyModifierNumLockMask, K_NUMLOCK }, + { kEventKeyModifierFnMask, K_AUX2 } + }; + static UInt32 prev_keymod = 0; + unsigned int i; + UInt32 modChanges; + + modChanges = prev_keymod ^ keymod; + if (modChanges == 0) + return; + + for (i = 0; i < sizeof(keymod_events) / sizeof(keymod_events[0]); i++) + { + UInt32 keybit = keymod_events[i].keybit; + + if ((modChanges & keybit) != 0) + Key_Event(keymod_events[i].event, '\0', (keymod & keybit) != 0); + } + + prev_keymod = keymod; +} + +static void Handle_Key(unsigned char charcode, UInt32 mackeycode, qboolean keypressed) +{ + unsigned int keycode = 0; + char ascii = '\0'; + + switch (mackeycode) + { + case MK_ESCAPE: + keycode = K_ESCAPE; + break; + case MK_F1: + keycode = K_F1; + break; + case MK_F2: + keycode = K_F2; + break; + case MK_F3: + keycode = K_F3; + break; + case MK_F4: + keycode = K_F4; + break; + case MK_F5: + keycode = K_F5; + break; + case MK_F6: + keycode = K_F6; + break; + case MK_F7: + keycode = K_F7; + break; + case MK_F8: + keycode = K_F8; + break; + case MK_F9: + keycode = K_F9; + break; + case MK_F10: + keycode = K_F10; + break; + case MK_F11: + keycode = K_F11; + break; + case MK_F12: + keycode = K_F12; + break; + case MK_SCROLLOCK: + keycode = K_SCROLLOCK; + break; + case MK_PAUSE: + keycode = K_PAUSE; + break; + case MK_BACKSPACE: + keycode = K_BACKSPACE; + break; + case MK_INSERT: + keycode = K_INS; + break; + case MK_HOME: + keycode = K_HOME; + break; + case MK_PAGEUP: + keycode = K_PGUP; + break; + case MK_NUMLOCK: + keycode = K_NUMLOCK; + break; + case MK_KP_EQUALS: + keycode = K_KP_EQUALS; + break; + case MK_KP_DIVIDE: + keycode = K_KP_DIVIDE; + break; + case MK_KP_MULTIPLY: + keycode = K_KP_MULTIPLY; + break; + case MK_TAB: + keycode = K_TAB; + break; + case MK_DELETE: + keycode = K_DEL; + break; + case MK_END: + keycode = K_END; + break; + case MK_PAGEDOWN: + keycode = K_PGDN; + break; + case MK_KP7: + keycode = K_KP_7; + break; + case MK_KP8: + keycode = K_KP_8; + break; + case MK_KP9: + keycode = K_KP_9; + break; + case MK_KP_MINUS: + keycode = K_KP_MINUS; + break; + case MK_CAPSLOCK: + keycode = K_CAPSLOCK; + break; + case MK_RETURN: + keycode = K_ENTER; + break; + case MK_KP4: + keycode = K_KP_4; + break; + case MK_KP5: + keycode = K_KP_5; + break; + case MK_KP6: + keycode = K_KP_6; + break; + case MK_KP_PLUS: + keycode = K_KP_PLUS; + break; + case MK_KP1: + keycode = K_KP_1; + break; + case MK_KP2: + keycode = K_KP_2; + break; + case MK_KP3: + keycode = K_KP_3; + break; + case MK_KP_ENTER: + case MK_IBOOK_ENTER: + keycode = K_KP_ENTER; + break; + case MK_KP0: + keycode = K_KP_0; + break; + case MK_KP_PERIOD: + keycode = K_KP_PERIOD; + break; + default: + switch(charcode) + { + case kUpArrowCharCode: + keycode = K_UPARROW; + break; + case kLeftArrowCharCode: + keycode = K_LEFTARROW; + break; + case kDownArrowCharCode: + keycode = K_DOWNARROW; + break; + case kRightArrowCharCode: + keycode = K_RIGHTARROW; + break; + case 0: + case 191: + // characters 0 and 191 are sent by the mouse buttons (?!) + break; + default: + if ('A' <= charcode && charcode <= 'Z') + { + keycode = charcode + ('a' - 'A'); // lowercase it + ascii = charcode; + } + else if (charcode >= 32) + { + keycode = charcode; + ascii = charcode; + } + else + Con_DPrintf(">> UNKNOWN char/keycode: %d/%u <<\n", charcode, (unsigned) mackeycode); + } + } + + if (keycode != 0) + Key_Event(keycode, ascii, keypressed); +} + +void Sys_SendKeyEvents(void) +{ + EventRef theEvent; + EventTargetRef theTarget; + + // Start by processing the asynchronous events we received since the previous frame + VID_ProcessPendingAsyncEvents(); + + theTarget = GetEventDispatcherTarget(); + while (ReceiveNextEvent(0, NULL, kEventDurationNoWait, true, &theEvent) == noErr) + { + UInt32 eventClass = GetEventClass(theEvent); + UInt32 eventKind = GetEventKind(theEvent); + + switch (eventClass) + { + case kEventClassMouse: + { + EventMouseButton theButton; + int key; + + switch (eventKind) + { + case kEventMouseDown: + case kEventMouseUp: + GetEventParameter(theEvent, kEventParamMouseButton, typeMouseButton, NULL, sizeof(theButton), NULL, &theButton); + switch (theButton) + { + default: + case kEventMouseButtonPrimary: + key = K_MOUSE1; + break; + case kEventMouseButtonSecondary: + key = K_MOUSE2; + break; + case kEventMouseButtonTertiary: + key = K_MOUSE3; + break; + } + Key_Event(key, '\0', eventKind == kEventMouseDown); + break; + + // Note: These two events are mutual exclusives + // Treat MouseDragged in the same statement, so we don't block MouseMoved while a mousebutton is held + case kEventMouseMoved: + case kEventMouseDragged: + { + HIPoint deltaPos; + HIPoint windowPos; + + GetEventParameter(theEvent, kEventParamMouseDelta, typeHIPoint, NULL, sizeof(deltaPos), NULL, &deltaPos); + GetEventParameter(theEvent, kEventParamWindowMouseLocation, typeHIPoint, NULL, sizeof(windowPos), NULL, &windowPos); + + if (vid_usingmouse) + { + in_mouse_x += deltaPos.x; + in_mouse_y += deltaPos.y; + } + + in_windowmouse_x = windowPos.x; + in_windowmouse_y = windowPos.y; + break; + } + + case kEventMouseWheelMoved: + { + SInt32 delta; + unsigned int wheelEvent; + + GetEventParameter(theEvent, kEventParamMouseWheelDelta, typeSInt32, NULL, sizeof(delta), NULL, &delta); + + wheelEvent = (delta > 0) ? K_MWHEELUP : K_MWHEELDOWN; + Key_Event(wheelEvent, 0, true); + Key_Event(wheelEvent, 0, false); + break; + } + + default: + Con_Printf (">> kEventClassMouse (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); + break; + } + } + + case kEventClassKeyboard: + { + char charcode; + UInt32 keycode; + + switch (eventKind) + { + case kEventRawKeyDown: + GetEventParameter(theEvent, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(charcode), NULL, &charcode); + GetEventParameter(theEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof(keycode), NULL, &keycode); + Handle_Key(charcode, keycode, true); + break; + + case kEventRawKeyRepeat: + break; + + case kEventRawKeyUp: + GetEventParameter(theEvent, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(charcode), NULL, &charcode); + GetEventParameter(theEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof(keycode), NULL, &keycode); + Handle_Key(charcode, keycode, false); + break; + + case kEventRawKeyModifiersChanged: + { + UInt32 keymod = 0; + GetEventParameter(theEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(keymod), NULL, &keymod); + Handle_KeyMod(keymod); + break; + } + + case kEventHotKeyPressed: + break; + + case kEventHotKeyReleased: + break; + + case kEventMouseWheelMoved: + break; + + default: + Con_Printf (">> kEventClassKeyboard (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); + break; + } + break; + } + + case kEventClassTextInput: + Con_Printf(">> kEventClassTextInput (%d) <<\n", (int)eventKind); + break; + + case kEventClassApplication: + switch (eventKind) + { + case kEventAppActivated : + VID_AppFocusChanged(true); + break; + case kEventAppDeactivated: + VID_AppFocusChanged(false); + break; + case kEventAppQuit: + Sys_Quit(0); + break; + case kEventAppActiveWindowChanged: + break; + default: + Con_Printf(">> kEventClassApplication (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); + break; + } + break; + + case kEventClassAppleEvent: + switch (eventKind) + { + case kEventAppleEvent : + break; + default: + Con_Printf(">> kEventClassAppleEvent (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); + break; + } + break; + + case kEventClassWindow: + switch (eventKind) + { + case kEventWindowUpdate : + break; + default: + Con_Printf(">> kEventClassWindow (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); + break; + } + break; + + case kEventClassControl: + break; + + default: + /*Con_Printf(">> UNKNOWN eventClass: %c%c%c%c, eventKind: %d <<\n", + eventClass >> 24, (eventClass >> 16) & 0xFF, + (eventClass >> 8) & 0xFF, eventClass & 0xFF, + eventKind);*/ + break; + } + + SendEventToEventTarget (theEvent, theTarget); + ReleaseEvent(theEvent); + } +} + +void VID_BuildJoyState(vid_joystate_t *joystate) +{ + VID_Shared_BuildJoyState_Begin(joystate); + VID_Shared_BuildJoyState_Finish(joystate); +} + +void VID_EnableJoystick(qboolean enable) +{ + int index = joy_enable.integer > 0 ? joy_index.integer : -1; + qboolean success = false; + int sharedcount = 0; + sharedcount = VID_Shared_SetJoystick(index); + if (index >= 0 && index < sharedcount) + success = true; + + // update cvar containing count of XInput joysticks + if (joy_detected.integer != sharedcount) + Cvar_SetValueQuick(&joy_detected, sharedcount); + + Cvar_SetValueQuick(&joy_active, success ? 1 : 0); +} + +void IN_Move (void) +{ + vid_joystate_t joystate; + VID_EnableJoystick(true); + VID_BuildJoyState(&joystate); + VID_ApplyJoyState(&joystate); +} + +static bool GetDictionaryBoolean(CFDictionaryRef d, const void *key) +{ + CFBooleanRef ref = (CFBooleanRef) CFDictionaryGetValue(d, key); + if(ref) + return CFBooleanGetValue(ref); + return false; +} + +long GetDictionaryLong(CFDictionaryRef d, const void *key) +{ + long value = 0; + CFNumberRef ref = (CFNumberRef) CFDictionaryGetValue(d, key); + if(ref) + CFNumberGetValue(ref, kCFNumberLongType, &value); + return value; +} + +size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) +{ + CGDirectDisplayID mainDisplay = CGMainDisplayID(); + CFArrayRef vidmodes = CGDisplayAvailableModes(mainDisplay); + CFDictionaryRef thismode; + unsigned int n = CFArrayGetCount(vidmodes); + unsigned int i; + size_t k; + + k = 0; + for(i = 0; i < n; ++i) + { + thismode = (CFDictionaryRef) CFArrayGetValueAtIndex(vidmodes, i); + if(!GetDictionaryBoolean(thismode, kCGDisplayModeIsSafeForHardware)) + continue; + + if(k >= maxcount) + break; + modes[k].width = GetDictionaryLong(thismode, kCGDisplayWidth); + modes[k].height = GetDictionaryLong(thismode, kCGDisplayHeight); + modes[k].bpp = GetDictionaryLong(thismode, kCGDisplayBitsPerPixel); + modes[k].refreshrate = GetDictionaryLong(thismode, kCGDisplayRefreshRate); + modes[k].pixelheight_num = 1; + modes[k].pixelheight_denom = 1; // OS X doesn't expose this either + ++k; + } + return k; +} diff --git a/misc/source/darkplaces-src/vid_agl_mackeys.h b/misc/source/darkplaces-src/vid_agl_mackeys.h new file mode 100644 index 00000000..0f266e9b --- /dev/null +++ b/misc/source/darkplaces-src/vid_agl_mackeys.h @@ -0,0 +1,145 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997, 1998, 1999, 2000, 2001 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@devolution.com +*/ + +#ifdef SAVE_RCSID +static char rcsid = + "@(#) $Id$"; +#endif + +/* These are the Macintosh key scancode constants -- from Inside Macintosh */ + +#define MK_ESCAPE 0x35 +#define MK_F1 0x7A +#define MK_F2 0x78 +#define MK_F3 0x63 +#define MK_F4 0x76 +#define MK_F5 0x60 +#define MK_F6 0x61 +#define MK_F7 0x62 +#define MK_F8 0x64 +#define MK_F9 0x65 +#define MK_F10 0x6D +#define MK_F11 0x67 +#define MK_F12 0x6F +#define MK_PRINT 0x69 +#define MK_SCROLLOCK 0x6B +#define MK_PAUSE 0x71 +#define MK_POWER 0x7F +#define MK_BACKQUOTE 0x32 +#define MK_1 0x12 +#define MK_2 0x13 +#define MK_3 0x14 +#define MK_4 0x15 +#define MK_5 0x17 +#define MK_6 0x16 +#define MK_7 0x1A +#define MK_8 0x1C +#define MK_9 0x19 +#define MK_0 0x1D +#define MK_MINUS 0x1B +#define MK_EQUALS 0x18 +#define MK_BACKSPACE 0x33 +#define MK_INSERT 0x72 +#define MK_HOME 0x73 +#define MK_PAGEUP 0x74 +#define MK_NUMLOCK 0x47 +#define MK_KP_EQUALS 0x51 +#define MK_KP_DIVIDE 0x4B +#define MK_KP_MULTIPLY 0x43 +#define MK_TAB 0x30 +#define MK_q 0x0C +#define MK_w 0x0D +#define MK_e 0x0E +#define MK_r 0x0F +#define MK_t 0x11 +#define MK_y 0x10 +#define MK_u 0x20 +#define MK_i 0x22 +#define MK_o 0x1F +#define MK_p 0x23 +#define MK_LEFTBRACKET 0x21 +#define MK_RIGHTBRACKET 0x1E +#define MK_BACKSLASH 0x2A +#define MK_DELETE 0x75 +#define MK_END 0x77 +#define MK_PAGEDOWN 0x79 +#define MK_KP7 0x59 +#define MK_KP8 0x5B +#define MK_KP9 0x5C +#define MK_KP_MINUS 0x4E +#define MK_CAPSLOCK 0x39 +#define MK_a 0x00 +#define MK_s 0x01 +#define MK_d 0x02 +#define MK_f 0x03 +#define MK_g 0x05 +#define MK_h 0x04 +#define MK_j 0x26 +#define MK_k 0x28 +#define MK_l 0x25 +#define MK_SEMICOLON 0x29 +#define MK_QUOTE 0x27 +#define MK_RETURN 0x24 +#define MK_KP4 0x56 +#define MK_KP5 0x57 +#define MK_KP6 0x58 +#define MK_KP_PLUS 0x45 +#define MK_LSHIFT 0x38 +#define MK_z 0x06 +#define MK_x 0x07 +#define MK_c 0x08 +#define MK_v 0x09 +#define MK_b 0x0B +#define MK_n 0x2D +#define MK_m 0x2E +#define MK_COMMA 0x2B +#define MK_PERIOD 0x2F +#define MK_SLASH 0x2C +#if 0 /* These are the same as the left versions - use left by default */ +#define MK_RSHIFT 0x38 +#endif +#define MK_UP 0x7E +#define MK_KP1 0x53 +#define MK_KP2 0x54 +#define MK_KP3 0x55 +#define MK_KP_ENTER 0x4C +#define MK_LCTRL 0x3B +#define MK_LALT 0x3A +#define MK_LMETA 0x37 +#define MK_SPACE 0x31 +#if 0 /* These are the same as the left versions - use left by default */ +#define MK_RMETA 0x37 +#define MK_RALT 0x3A +#define MK_RCTRL 0x3B +#endif +#define MK_LEFT 0x7B +#define MK_DOWN 0x7D +#define MK_RIGHT 0x7C +#define MK_KP0 0x52 +#define MK_KP_PERIOD 0x41 + +/* Wierd, these keys are on my iBook under MacOS X */ +#define MK_IBOOK_ENTER 0x34 +#define MK_IBOOK_LEFT 0x3B +#define MK_IBOOK_RIGHT 0x3C +#define MK_IBOOK_DOWN 0x3D +#define MK_IBOOK_UP 0x3E diff --git a/misc/source/darkplaces-src/vid_glx.c b/misc/source/darkplaces-src/vid_glx.c new file mode 100644 index 00000000..9f142bb9 --- /dev/null +++ b/misc/source/darkplaces-src/vid_glx.c @@ -0,0 +1,1743 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include + +#include +#include +#include +#include // TODO possibly ifdef this out on non-supporting systems... Solaris (as always)? +#include + +#include "quakedef.h" +#include "dpsoftrast.h" + +#include +#include +#include + +#include +#if !defined(__APPLE__) && !defined(__MACH__) && !defined(SUNOS) +#include +#endif +#include + +#include +#include +#include + +// get the Uchar type +#include "utf8lib.h" +#include "image.h" + +#include "nexuiz.xpm" +#include "darkplaces.xpm" + +// Tell startup code that we have a client +int cl_available = true; + +// note: if we used the XRandR extension we could support refresh rates +qboolean vid_supportrefreshrate = false; + +//GLX prototypes +XVisualInfo *(GLAPIENTRY *qglXChooseVisual)(Display *dpy, int screen, int *attribList); +GLXContext (GLAPIENTRY *qglXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct); +void (GLAPIENTRY *qglXDestroyContext)(Display *dpy, GLXContext ctx); +Bool (GLAPIENTRY *qglXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx); +void (GLAPIENTRY *qglXSwapBuffers)(Display *dpy, GLXDrawable drawable); +const char *(GLAPIENTRY *qglXQueryExtensionsString)(Display *dpy, int screen); + +//GLX_ARB_get_proc_address +void *(GLAPIENTRY *qglXGetProcAddressARB)(const GLubyte *procName); + +static dllfunction_t getprocaddressfuncs[] = +{ + {"glXGetProcAddressARB", (void **) &qglXGetProcAddressARB}, + {NULL, NULL} +}; + +//GLX_SGI_swap_control +GLint (GLAPIENTRY *qglXSwapIntervalSGI)(GLint interval); + +static dllfunction_t swapcontrolfuncs[] = +{ + {"glXSwapIntervalSGI", (void **) &qglXSwapIntervalSGI}, + {NULL, NULL} +}; + +static Display *vidx11_display = NULL; +static int vidx11_screen; +static Window win, root; +static GLXContext ctx = NULL; +static GC vidx11_gc = NULL; +static XImage *vidx11_ximage[2] = { NULL, NULL }; +static int vidx11_ximage_pos = 0; +static XShmSegmentInfo vidx11_shminfo[2]; +static int vidx11_shmevent = -1; +static int vidx11_shmwait = 0; // number of frames outstanding + +Atom wm_delete_window_atom; +Atom net_wm_state_atom; +Atom net_wm_state_hidden_atom; +Atom net_wm_state_fullscreen_atom; +Atom net_wm_icon; +Atom cardinal; + +#define KEY_MASK (KeyPressMask | KeyReleaseMask) +#define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | \ + PointerMotionMask | ButtonMotionMask) +#define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask | \ + StructureNotifyMask | FocusChangeMask | EnterWindowMask | \ + LeaveWindowMask) + + +static qboolean mouse_avail = true; +static qboolean vid_usingmousegrab = false; +static qboolean vid_usingmouse = false; +static qboolean vid_usinghidecursor = false; +static qboolean vid_usingvsync = false; +static qboolean vid_usevsync = false; +static qboolean vid_x11_hardwaregammasupported = false; +static qboolean vid_x11_dgasupported = false; +static int vid_x11_gammarampsize = 0; + +#if !defined(__APPLE__) && !defined(SUNOS) +cvar_t vid_dgamouse = {CVAR_SAVE, "vid_dgamouse", "0", "make use of DGA mouse input"}; +static qboolean vid_usingdgamouse = false; +#endif +cvar_t vid_netwmfullscreen = {CVAR_SAVE, "vid_netwmfullscreen", "0", "make use _NET_WM_STATE_FULLSCREEN; turn this off if fullscreen does not work for you"}; + +qboolean vidmode_ext = false; + +static int win_x, win_y; + +static XF86VidModeModeInfo init_vidmode, game_vidmode; +static qboolean vid_isfullscreen = false; +static qboolean vid_isvidmodefullscreen = false; +static qboolean vid_isnetwmfullscreen = false; +static qboolean vid_isoverrideredirect = false; + +static Visual *vidx11_visual; +static Colormap vidx11_colormap; + +/*-----------------------------------------------------------------------*/ +// + +long keysym2ucs(KeySym keysym); +void DP_Xutf8LookupString(XKeyEvent * ev, + Uchar *uch, + KeySym * keysym_return, + Status * status_return) +{ + int rc; + KeySym keysym; + int codepoint; + char buffer[64]; + int nbytes = sizeof(buffer); + + rc = XLookupString(ev, buffer, nbytes, &keysym, NULL); + + if (rc > 0) { + codepoint = buffer[0] & 0xFF; + } else { + codepoint = keysym2ucs(keysym); + } + + if (codepoint < 0) { + if (keysym == None) { + *status_return = XLookupNone; + } else { + *status_return = XLookupKeySym; + *keysym_return = keysym; + } + *uch = 0; + return; + } + + *uch = codepoint; + + if (keysym != None) { + *keysym_return = keysym; + *status_return = XLookupBoth; + } else { + *status_return = XLookupChars; + } +} +static int XLateKey(XKeyEvent *ev, Uchar *ascii) +{ + int key = 0; + //char buf[64]; + KeySym keysym, shifted; + Status status; + + keysym = XLookupKeysym (ev, 0); + DP_Xutf8LookupString(ev, ascii, &shifted, &status); + + switch(keysym) + { + case XK_KP_Page_Up: key = K_KP_PGUP; break; + case XK_Page_Up: key = K_PGUP; break; + + case XK_KP_Page_Down: key = K_KP_PGDN; break; + case XK_Page_Down: key = K_PGDN; break; + + case XK_KP_Home: key = K_KP_HOME; break; + case XK_Home: key = K_HOME; break; + + case XK_KP_End: key = K_KP_END; break; + case XK_End: key = K_END; break; + + case XK_KP_Left: key = K_KP_LEFTARROW; break; + case XK_Left: key = K_LEFTARROW; break; + + case XK_KP_Right: key = K_KP_RIGHTARROW; break; + case XK_Right: key = K_RIGHTARROW; break; + + case XK_KP_Down: key = K_KP_DOWNARROW; break; + case XK_Down: key = K_DOWNARROW; break; + + case XK_KP_Up: key = K_KP_UPARROW; break; + case XK_Up: key = K_UPARROW; break; + + case XK_Escape: key = K_ESCAPE; break; + + case XK_KP_Enter: key = K_KP_ENTER; break; + case XK_Return: key = K_ENTER; break; + + case XK_Tab: key = K_TAB; break; + + case XK_F1: key = K_F1; break; + + case XK_F2: key = K_F2; break; + + case XK_F3: key = K_F3; break; + + case XK_F4: key = K_F4; break; + + case XK_F5: key = K_F5; break; + + case XK_F6: key = K_F6; break; + + case XK_F7: key = K_F7; break; + + case XK_F8: key = K_F8; break; + + case XK_F9: key = K_F9; break; + + case XK_F10: key = K_F10; break; + + case XK_F11: key = K_F11; break; + + case XK_F12: key = K_F12; break; + + case XK_BackSpace: key = K_BACKSPACE; break; + + case XK_KP_Delete: key = K_KP_DEL; break; + case XK_Delete: key = K_DEL; break; + + case XK_Pause: key = K_PAUSE; break; + + case XK_Shift_L: + case XK_Shift_R: key = K_SHIFT; break; + + case XK_Execute: + case XK_Control_L: + case XK_Control_R: key = K_CTRL; break; + + case XK_Alt_L: + case XK_Meta_L: + case XK_ISO_Level3_Shift: + case XK_Alt_R: + case XK_Meta_R: key = K_ALT; break; + + case XK_KP_Begin: key = K_KP_5; break; + + case XK_Insert:key = K_INS; break; + case XK_KP_Insert: key = K_KP_INS; break; + + case XK_KP_Multiply: key = K_KP_MULTIPLY; break; + case XK_KP_Add: key = K_KP_PLUS; break; + case XK_KP_Subtract: key = K_KP_MINUS; break; + case XK_KP_Divide: key = K_KP_SLASH; break; + + case XK_asciicircum: *ascii = key = '^'; break; // for some reason, XLookupString returns "" on this one for Grunt|2 + + case XK_section: *ascii = key = '~'; break; + + default: + if (keysym < 32) + break; + + if (keysym >= 'A' && keysym <= 'Z') + key = keysym - 'A' + 'a'; + else + key = keysym; + + break; + } + + return key; +} + +static Cursor CreateNullCursor(Display *display, Window root) +{ + Pixmap cursormask; + XGCValues xgc; + GC gc; + XColor dummycolour; + Cursor cursor; + + cursormask = XCreatePixmap(display, root, 1, 1, 1); + xgc.function = GXclear; + gc = XCreateGC(display, cursormask, GCFunction, &xgc); + XFillRectangle(display, cursormask, gc, 0, 0, 1, 1); + dummycolour.pixel = 0; + dummycolour.red = 0; + dummycolour.flags = 04; + cursor = XCreatePixmapCursor(display, cursormask, cursormask, &dummycolour,&dummycolour, 0,0); + XFreePixmap(display,cursormask); + XFreeGC(display,gc); + return cursor; +} + +void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor) +{ + static int originalmouseparms_num; + static int originalmouseparms_denom; + static int originalmouseparms_threshold; + static qboolean restore_spi; + +#if !defined(__APPLE__) && !defined(SUNOS) + qboolean usedgamouse; +#endif + + if (!vidx11_display || !win) + return; + + if (relative) + fullscreengrab = true; + + if (!mouse_avail) + fullscreengrab = relative = hidecursor = false; + +#if !defined(__APPLE__) && !defined(SUNOS) + usedgamouse = relative && vid_dgamouse.integer; + if (!vid_x11_dgasupported) + usedgamouse = false; + if (fullscreengrab && vid_usingmouse && (vid_usingdgamouse != usedgamouse)) + VID_SetMouse(false, false, false); // ungrab first! +#endif + + if (vid_usingmousegrab != fullscreengrab) + { + vid_usingmousegrab = fullscreengrab; + cl_ignoremousemoves = 2; + if (fullscreengrab) + { + XGrabPointer(vidx11_display, win, True, 0, GrabModeAsync, GrabModeAsync, win, None, CurrentTime); + if (vid_grabkeyboard.integer || vid_isoverrideredirect) + XGrabKeyboard(vidx11_display, win, False, GrabModeAsync, GrabModeAsync, CurrentTime); + } + else + { + XUngrabPointer(vidx11_display, CurrentTime); + XUngrabKeyboard(vidx11_display, CurrentTime); + } + } + + if (relative) + { + if (!vid_usingmouse) + { + XWindowAttributes attribs_1; + XSetWindowAttributes attribs_2; + + XGetWindowAttributes(vidx11_display, win, &attribs_1); + attribs_2.event_mask = attribs_1.your_event_mask | KEY_MASK | MOUSE_MASK; + XChangeWindowAttributes(vidx11_display, win, CWEventMask, &attribs_2); + +#if !defined(__APPLE__) && !defined(SUNOS) + vid_usingdgamouse = usedgamouse; + if (usedgamouse) + { + XF86DGADirectVideo(vidx11_display, DefaultScreen(vidx11_display), XF86DGADirectMouse); + XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, 0, 0); + } + else +#endif + XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, vid.width / 2, vid.height / 2); + +// COMMANDLINEOPTION: X11 Input: -noforcemparms disables setting of mouse parameters (not used with DGA, windows only) +#if !defined(__APPLE__) && !defined(SUNOS) + if (!COM_CheckParm ("-noforcemparms") && !usedgamouse) +#else + if (!COM_CheckParm ("-noforcemparms")) +#endif + { + XGetPointerControl(vidx11_display, &originalmouseparms_num, &originalmouseparms_denom, &originalmouseparms_threshold); + XChangePointerControl (vidx11_display, true, false, 1, 1, -1); // TODO maybe change threshold here, or remove this comment + restore_spi = true; + } + else + restore_spi = false; + + cl_ignoremousemoves = 2; + vid_usingmouse = true; + } + } + else + { + if (vid_usingmouse) + { +#if !defined(__APPLE__) && !defined(SUNOS) + if (vid_usingdgamouse) + XF86DGADirectVideo(vidx11_display, DefaultScreen(vidx11_display), 0); + vid_usingdgamouse = false; +#endif + cl_ignoremousemoves = 2; + + if (restore_spi) + XChangePointerControl (vidx11_display, true, true, originalmouseparms_num, originalmouseparms_denom, originalmouseparms_threshold); + restore_spi = false; + + vid_usingmouse = false; + } + } + + if (vid_usinghidecursor != hidecursor) + { + vid_usinghidecursor = hidecursor; + if (hidecursor) + XDefineCursor(vidx11_display, win, CreateNullCursor(vidx11_display, win)); + else + XUndefineCursor(vidx11_display, win); + } +} + +static keynum_t buttonremap[18] = +{ + K_MOUSE1, + K_MOUSE3, + K_MOUSE2, + K_MWHEELUP, + K_MWHEELDOWN, + K_MOUSE4, + K_MOUSE5, + K_MOUSE6, + K_MOUSE7, + K_MOUSE8, + K_MOUSE9, + K_MOUSE10, + K_MOUSE11, + K_MOUSE12, + K_MOUSE13, + K_MOUSE14, + K_MOUSE15, + K_MOUSE16, +}; + +static qboolean BuildXImages(int w, int h) +{ + int i; + if(DefaultDepth(vidx11_display, vidx11_screen) != 32 && DefaultDepth(vidx11_display, vidx11_screen) != 24) + { + Con_Printf("Sorry, we only support 24bpp and 32bpp modes\n"); + VID_Shutdown(); + return false; + } + // match to dpsoftrast's specs + if(vidx11_visual->red_mask != 0x00FF0000) + { + Con_Printf("Sorry, we only support BGR visuals\n"); + VID_Shutdown(); + return false; + } + if(vidx11_visual->green_mask != 0x0000FF00) + { + Con_Printf("Sorry, we only support BGR visuals\n"); + VID_Shutdown(); + return false; + } + if(vidx11_visual->blue_mask != 0x000000FF) + { + Con_Printf("Sorry, we only support BGR visuals\n"); + VID_Shutdown(); + return false; + } + if(vidx11_shmevent >= 0) + { + for(i = 0; i < 2; ++i) + { + vidx11_shminfo[i].shmid = -1; + vidx11_ximage[i] = XShmCreateImage(vidx11_display, vidx11_visual, DefaultDepth(vidx11_display, vidx11_screen), ZPixmap, NULL, &vidx11_shminfo[i], w, h); + if(!vidx11_ximage[i]) + { + Con_Printf("Failed to get an XImage segment\n"); + VID_Shutdown(); + return false; + } + if(vidx11_ximage[i]->bytes_per_line != w * 4) + { + Con_Printf("Sorry, we only support linear pixel layout\n"); + VID_Shutdown(); + return false; + } + vidx11_shminfo[i].shmid = shmget(IPC_PRIVATE, vidx11_ximage[i]->bytes_per_line * vidx11_ximage[i]->height, IPC_CREAT|0777); + if(vidx11_shminfo[i].shmid < 0) + { + Con_Printf("Failed to get a shm segment\n"); + VID_Shutdown(); + return false; + } + vidx11_shminfo[i].shmaddr = vidx11_ximage[i]->data = shmat(vidx11_shminfo[i].shmid, NULL, 0); + if(!vidx11_shminfo[i].shmaddr) + { + Con_Printf("Failed to get a shm segment addresst\n"); + VID_Shutdown(); + return false; + } + vidx11_shminfo[i].readOnly = True; + XShmAttach(vidx11_display, &vidx11_shminfo[i]); + } + } + else + { + for(i = 0; i < 1; ++i) // we only need one buffer if we don't use Xshm + { + char *p = calloc(4, w * h); + vidx11_shminfo[i].shmid = -1; + vidx11_ximage[i] = XCreateImage(vidx11_display, vidx11_visual, DefaultDepth(vidx11_display, vidx11_screen), ZPixmap, 0, (char*)p, w, h, 8, 0); + if(!vidx11_ximage[i]) + { + Con_Printf("Failed to get an XImage segment\n"); + VID_Shutdown(); + return false; + } + if(vidx11_ximage[i]->bytes_per_line != w * 4) + { + Con_Printf("Sorry, we only support linear pixel layout\n"); + VID_Shutdown(); + return false; + } + } + } + return true; +} +static void DestroyXImages(void) +{ + int i; + for(i = 0; i < 2; ++i) + { + if(vidx11_shminfo[i].shmid >= 0) + { + XShmDetach(vidx11_display, &vidx11_shminfo[i]); + XDestroyImage(vidx11_ximage[i]); + vidx11_ximage[i] = NULL; + shmdt(vidx11_shminfo[i].shmaddr); + shmctl(vidx11_shminfo[i].shmid, IPC_RMID, 0); + vidx11_shminfo[i].shmid = -1; + } + if(vidx11_ximage[i]) + XDestroyImage(vidx11_ximage[i]); + vidx11_ximage[i] = 0; + } +} + +static int in_mouse_x_save = 0, in_mouse_y_save = 0; +static void HandleEvents(void) +{ + XEvent event; + int key; + Uchar unicode; + qboolean dowarp = false; + + if (!vidx11_display) + return; + + in_mouse_x += in_mouse_x_save; + in_mouse_y += in_mouse_y_save; + in_mouse_x_save = 0; + in_mouse_y_save = 0; + + while (XPending(vidx11_display)) + { + XNextEvent(vidx11_display, &event); + + switch (event.type) + { + case KeyPress: + // key pressed + key = XLateKey (&event.xkey, &unicode); + Key_Event(key, unicode, true); + break; + + case KeyRelease: + // key released + key = XLateKey (&event.xkey, &unicode); + Key_Event(key, unicode, false); + break; + + case MotionNotify: + // mouse moved + if (vid_usingmouse) + { +#if !defined(__APPLE__) && !defined(SUNOS) + if (vid_usingdgamouse) + { + in_mouse_x += event.xmotion.x_root; + in_mouse_y += event.xmotion.y_root; + } + else +#endif + { + if (!event.xmotion.send_event) + { + in_mouse_x += event.xmotion.x - in_windowmouse_x; + in_mouse_y += event.xmotion.y - in_windowmouse_y; + //if (abs(vid.width/2 - event.xmotion.x) + abs(vid.height/2 - event.xmotion.y)) + if (vid_stick_mouse.integer || abs(vid.width/2 - event.xmotion.x) > vid.width / 4 || abs(vid.height/2 - event.xmotion.y) > vid.height / 4) + dowarp = true; + } + } + } + in_windowmouse_x = event.xmotion.x; + in_windowmouse_y = event.xmotion.y; + break; + + case ButtonPress: + // mouse button pressed + if (event.xbutton.button <= 18) + Key_Event(buttonremap[event.xbutton.button - 1], 0, true); + else + Con_Printf("HandleEvents: ButtonPress gave value %d, 1-18 expected\n", event.xbutton.button); + break; + + case ButtonRelease: + // mouse button released + if (event.xbutton.button <= 18) + Key_Event(buttonremap[event.xbutton.button - 1], 0, false); + else + Con_Printf("HandleEvents: ButtonRelease gave value %d, 1-18 expected\n", event.xbutton.button); + break; + + case CreateNotify: + // window created + win_x = event.xcreatewindow.x; + win_y = event.xcreatewindow.y; + break; + + case ConfigureNotify: + // window changed size/location + win_x = event.xconfigure.x; + win_y = event.xconfigure.y; + if((vid_resizable.integer < 2 || vid_isnetwmfullscreen) && (vid.width != event.xconfigure.width || vid.height != event.xconfigure.height)) + { + vid.width = event.xconfigure.width; + vid.height = event.xconfigure.height; + if(vid_isnetwmfullscreen) + Con_Printf("NetWM fullscreen: actually using resolution %dx%d\n", vid.width, vid.height); + else + Con_DPrintf("Updating to ConfigureNotify resolution %dx%d\n", vid.width, vid.height); + + DPSOFTRAST_Flush(); + + if(vid.softdepthpixels) + free(vid.softdepthpixels); + + DestroyXImages(); + XSync(vidx11_display, False); + if(!BuildXImages(vid.width, vid.height)) + return; + XSync(vidx11_display, False); + + vid.softpixels = (unsigned int *) vidx11_ximage[vidx11_ximage_pos]->data; + vid.softdepthpixels = (unsigned int *)calloc(4, vid.width * vid.height); + } + break; + case DestroyNotify: + // window has been destroyed + Sys_Quit(0); + break; + case ClientMessage: + // window manager messages + if ((event.xclient.format == 32) && ((unsigned int)event.xclient.data.l[0] == wm_delete_window_atom)) + Sys_Quit(0); + break; + case MapNotify: + if (vid_isoverrideredirect) + break; + // window restored + vid_hidden = false; + VID_RestoreSystemGamma(); + + if(vid_isvidmodefullscreen) + { + // set our video mode + XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &game_vidmode); + + // Move the viewport to top left + XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); + } + + if(vid_isnetwmfullscreen) + { + // make sure it's fullscreen + XEvent event; + event.type = ClientMessage; + event.xclient.serial = 0; + event.xclient.send_event = True; + event.xclient.message_type = net_wm_state_atom; + event.xclient.window = win; + event.xclient.format = 32; + event.xclient.data.l[0] = 1; + event.xclient.data.l[1] = net_wm_state_fullscreen_atom; + event.xclient.data.l[2] = 0; + event.xclient.data.l[3] = 1; + event.xclient.data.l[4] = 0; + XSendEvent(vidx11_display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event); + } + + dowarp = true; + + break; + case UnmapNotify: + if (vid_isoverrideredirect) + break; + // window iconified/rolledup/whatever + vid_hidden = true; + VID_RestoreSystemGamma(); + + if(vid_isvidmodefullscreen) + XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &init_vidmode); + + break; + case FocusIn: + if (vid_isoverrideredirect) + break; + // window is now the input focus + vid_activewindow = true; + break; + case FocusOut: + if (vid_isoverrideredirect) + break; + + if(vid_isnetwmfullscreen && event.xfocus.mode == NotifyNormal) + { + // iconify netwm fullscreen window when it loses focus + // when the user selects it in the taskbar, the window manager will map it again and send MapNotify + XEvent event; + event.type = ClientMessage; + event.xclient.serial = 0; + event.xclient.send_event = True; + event.xclient.message_type = net_wm_state_atom; + event.xclient.window = win; + event.xclient.format = 32; + event.xclient.data.l[0] = 1; + event.xclient.data.l[1] = net_wm_state_hidden_atom; + event.xclient.data.l[2] = 0; + event.xclient.data.l[3] = 1; + event.xclient.data.l[4] = 0; + XSendEvent(vidx11_display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event); + } + + // window is no longer the input focus + vid_activewindow = false; + VID_RestoreSystemGamma(); + + break; + case EnterNotify: + // mouse entered window + break; + case LeaveNotify: + // mouse left window + break; + default: + if(vidx11_shmevent >= 0 && event.type == vidx11_shmevent) + --vidx11_shmwait; + break; + } + } + + if (dowarp) + { + /* move the mouse to the window center again */ + // we'll catch the warp motion by its send_event flag, updating the + // stored mouse position without adding any delta motion + XEvent event; + event.type = MotionNotify; + event.xmotion.display = vidx11_display; + event.xmotion.window = win; + event.xmotion.x = vid.width / 2; + event.xmotion.y = vid.height / 2; + XSendEvent(vidx11_display, win, False, PointerMotionMask, &event); + XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, vid.width / 2, vid.height / 2); + } +} + +static void *prjobj = NULL; + +static void GL_CloseLibrary(void) +{ + if (prjobj) + dlclose(prjobj); + prjobj = NULL; + gl_driver[0] = 0; + qglXGetProcAddressARB = NULL; + gl_extensions = ""; + gl_platform = ""; + gl_platformextensions = ""; +} + +static int GL_OpenLibrary(const char *name) +{ + Con_Printf("Loading OpenGL driver %s\n", name); + GL_CloseLibrary(); + if (!(prjobj = dlopen(name, RTLD_LAZY | RTLD_GLOBAL))) + { + Con_Printf("Unable to open symbol list for %s\n", name); + return false; + } + strlcpy(gl_driver, name, sizeof(gl_driver)); + return true; +} + +void *GL_GetProcAddress(const char *name) +{ + void *p = NULL; + if (qglXGetProcAddressARB != NULL) + p = (void *) qglXGetProcAddressARB((GLubyte *)name); + if (p == NULL) + p = (void *) dlsym(prjobj, name); + return p; +} + +void VID_Shutdown(void) +{ + if (!vidx11_display) + return; + + VID_EnableJoystick(false); + VID_SetMouse(false, false, false); + VID_RestoreSystemGamma(); + + // FIXME: glXDestroyContext here? + if (vid_isvidmodefullscreen) + XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &init_vidmode); + + if(vidx11_gc) + XFreeGC(vidx11_display, vidx11_gc); + vidx11_gc = NULL; + + DestroyXImages(); + vidx11_shmevent = -1; + vid.softpixels = NULL; + + if (vid.softdepthpixels) + free(vid.softdepthpixels); + vid.softdepthpixels = NULL; + + if (win) + XDestroyWindow(vidx11_display, win); + XCloseDisplay(vidx11_display); + + vid_hidden = true; + vid_isfullscreen = false; + vid_isnetwmfullscreen = false; + vid_isvidmodefullscreen = false; + vid_isoverrideredirect = false; + vidx11_display = NULL; + win = 0; + ctx = NULL; + + GL_CloseLibrary(); + Key_ClearStates (); +} + +void signal_handler(int sig) +{ + Con_Printf("Received signal %d, exiting...\n", sig); + VID_RestoreSystemGamma(); + Sys_Quit(1); +} + +void InitSig(void) +{ + signal(SIGHUP, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGQUIT, signal_handler); + signal(SIGILL, signal_handler); + signal(SIGTRAP, signal_handler); + signal(SIGIOT, signal_handler); + signal(SIGBUS, signal_handler); + signal(SIGFPE, signal_handler); + signal(SIGSEGV, signal_handler); + signal(SIGTERM, signal_handler); +} + +void VID_Finish (void) +{ + vid_usevsync = vid_vsync.integer && !cls.timedemo && qglXSwapIntervalSGI; + switch(vid.renderpath) + { + case RENDERPATH_SOFT: + if(vidx11_shmevent >= 0) { + vidx11_ximage_pos = !vidx11_ximage_pos; + vid.softpixels = (unsigned int *) vidx11_ximage[vidx11_ximage_pos]->data; + DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); + + // save mouse motion so we can deal with it later + in_mouse_x = 0; + in_mouse_y = 0; + while(vidx11_shmwait) + HandleEvents(); + in_mouse_x_save += in_mouse_x; + in_mouse_y_save += in_mouse_y; + in_mouse_x = 0; + in_mouse_y = 0; + + ++vidx11_shmwait; + XShmPutImage(vidx11_display, win, vidx11_gc, vidx11_ximage[!vidx11_ximage_pos], 0, 0, 0, 0, vid.width, vid.height, True); + } else { + // no buffer switching here, we just flush the renderer + DPSOFTRAST_Finish(); + XPutImage(vidx11_display, win, vidx11_gc, vidx11_ximage[vidx11_ximage_pos], 0, 0, 0, 0, vid.width, vid.height); + } + break; + + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (vid_usingvsync != vid_usevsync) + { + vid_usingvsync = vid_usevsync; + if (qglXSwapIntervalSGI (vid_usevsync)) + Con_Print("glXSwapIntervalSGI didn't accept the vid_vsync change, it will take effect on next vid_restart (GLX_SGI_swap_control does not allow turning off vsync)\n"); + } + + if (!vid_hidden) + { + CHECKGLERROR + if (r_speeds.integer == 2 || gl_finish.integer) + GL_Finish(); + qglXSwapBuffers(vidx11_display, win);CHECKGLERROR + } + break; + + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + } + + if (vid_x11_hardwaregammasupported) + VID_UpdateGamma(false, vid_x11_gammarampsize); +} + +int VID_SetGamma(unsigned short *ramps, int rampsize) +{ + return XF86VidModeSetGammaRamp(vidx11_display, vidx11_screen, rampsize, ramps, ramps + rampsize, ramps + rampsize*2); +} + +int VID_GetGamma(unsigned short *ramps, int rampsize) +{ + return XF86VidModeGetGammaRamp(vidx11_display, vidx11_screen, rampsize, ramps, ramps + rampsize, ramps + rampsize*2); +} + +void VID_Init(void) +{ +#if !defined(__APPLE__) && !defined(SUNOS) + Cvar_RegisterVariable (&vid_dgamouse); +#endif + Cvar_RegisterVariable (&vid_netwmfullscreen); + InitSig(); // trap evil signals +// COMMANDLINEOPTION: Input: -nomouse disables mouse support (see also vid_mouse cvar) + if (COM_CheckParm ("-nomouse")) + mouse_avail = false; + vidx11_shminfo[0].shmid = -1; + vidx11_shminfo[1].shmid = -1; +} + +void VID_BuildGLXAttrib(int *attrib, qboolean stencil, qboolean stereobuffer, int samples) +{ + *attrib++ = GLX_RGBA; + *attrib++ = GLX_RED_SIZE;*attrib++ = stencil ? 8 : 5; + *attrib++ = GLX_GREEN_SIZE;*attrib++ = stencil ? 8 : 5; + *attrib++ = GLX_BLUE_SIZE;*attrib++ = stencil ? 8 : 5; + *attrib++ = GLX_DOUBLEBUFFER; + *attrib++ = GLX_DEPTH_SIZE;*attrib++ = stencil ? 24 : 16; + // if stencil is enabled, ask for alpha too + if (stencil) + { + *attrib++ = GLX_STENCIL_SIZE;*attrib++ = 8; + *attrib++ = GLX_ALPHA_SIZE;*attrib++ = 8; + } + if (stereobuffer) + *attrib++ = GLX_STEREO; + if (samples > 1) + { + *attrib++ = GLX_SAMPLE_BUFFERS_ARB; + *attrib++ = 1; + *attrib++ = GLX_SAMPLES_ARB; + *attrib++ = samples; + } + *attrib++ = None; +} + +qboolean VID_InitModeSoft(viddef_mode_t *mode) +{ + int i, j; + XSetWindowAttributes attr; + XClassHint *clshints; + XWMHints *wmhints; + XSizeHints *szhints; + unsigned long mask; + int MajorVersion, MinorVersion; + char *xpm; + char **idata; + unsigned char *data; + XGCValues gcval; + const char *dpyname; + + vid_isfullscreen = false; + vid_isnetwmfullscreen = false; + vid_isvidmodefullscreen = false; + vid_isoverrideredirect = false; + + if (!(vidx11_display = XOpenDisplay(NULL))) + { + Con_Print("Couldn't open the X display\n"); + return false; + } + dpyname = XDisplayName(NULL); + + // LordHavoc: making the close button on a window do the right thing + // seems to involve this mess, sigh... + wm_delete_window_atom = XInternAtom(vidx11_display, "WM_DELETE_WINDOW", false); + net_wm_state_atom = XInternAtom(vidx11_display, "_NET_WM_STATE", false); + net_wm_state_fullscreen_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_FULLSCREEN", false); + net_wm_state_hidden_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_HIDDEN", false); + net_wm_icon = XInternAtom(vidx11_display, "_NET_WM_ICON", false); + cardinal = XInternAtom(vidx11_display, "CARDINAL", false); + + // make autorepeat send keypress/keypress/.../keyrelease instead of intervening keyrelease + XkbSetDetectableAutoRepeat(vidx11_display, true, NULL); + + vidx11_screen = DefaultScreen(vidx11_display); + root = RootWindow(vidx11_display, vidx11_screen); + + // Get video mode list + MajorVersion = MinorVersion = 0; + if (!XF86VidModeQueryVersion(vidx11_display, &MajorVersion, &MinorVersion)) + vidmode_ext = false; + else + { + Con_DPrintf("Using XFree86-VidModeExtension Version %d.%d\n", MajorVersion, MinorVersion); + vidmode_ext = true; + } + + if (mode->fullscreen) + { + if(vid_netwmfullscreen.integer) + { + // TODO detect WM support + vid_isnetwmfullscreen = true; + vid_isfullscreen = true; + // width and height will be filled in later + Con_DPrintf("Using NetWM fullscreen mode\n"); + } + + if(!vid_isfullscreen && vidmode_ext) + { + int best_fit, best_dist, dist, x, y; + + // Are we going fullscreen? If so, let's change video mode + XF86VidModeModeLine *current_vidmode; + XF86VidModeModeInfo **vidmodes; + int num_vidmodes; + + // This nice hack comes from the SDL source code + current_vidmode = (XF86VidModeModeLine*)((char*)&init_vidmode + sizeof(init_vidmode.dotclock)); + XF86VidModeGetModeLine(vidx11_display, vidx11_screen, (int*)&init_vidmode.dotclock, current_vidmode); + + XF86VidModeGetAllModeLines(vidx11_display, vidx11_screen, &num_vidmodes, &vidmodes); + best_dist = 0; + best_fit = -1; + + for (i = 0; i < num_vidmodes; i++) + { + if (mode->width > vidmodes[i]->hdisplay || mode->height > vidmodes[i]->vdisplay) + continue; + + x = mode->width - vidmodes[i]->hdisplay; + y = mode->height - vidmodes[i]->vdisplay; + dist = (x * x) + (y * y); + if (best_fit == -1 || dist < best_dist) + { + best_dist = dist; + best_fit = i; + } + } + + if (best_fit != -1) + { + // LordHavoc: changed from ActualWidth/ActualHeight =, + // to width/height =, so the window will take the full area of + // the mode chosen + mode->width = vidmodes[best_fit]->hdisplay; + mode->height = vidmodes[best_fit]->vdisplay; + + // change to the mode + XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, vidmodes[best_fit]); + memcpy(&game_vidmode, vidmodes[best_fit], sizeof(game_vidmode)); + vid_isvidmodefullscreen = true; + vid_isfullscreen = true; + + // Move the viewport to top left + XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); + Con_DPrintf("Using XVidMode fullscreen mode at %dx%d\n", mode->width, mode->height); + } + + free(vidmodes); + } + + if(!vid_isfullscreen) + { + // sorry, no FS available + // use the full desktop resolution + vid_isfullscreen = true; + // width and height will be filled in later + mode->width = DisplayWidth(vidx11_display, vidx11_screen); + mode->height = DisplayHeight(vidx11_display, vidx11_screen); + Con_DPrintf("Using X11 fullscreen mode at %dx%d\n", mode->width, mode->height); + } + } + + // LordHavoc: save the visual for use in gamma ramp settings later + vidx11_visual = DefaultVisual(vidx11_display, vidx11_screen); + + /* window attributes */ + attr.background_pixel = 0; + attr.border_pixel = 0; + // LordHavoc: save the colormap for later, too + vidx11_colormap = attr.colormap = XCreateColormap(vidx11_display, root, vidx11_visual, AllocNone); + attr.event_mask = X_MASK; + + if (mode->fullscreen) + { + if(vid_isnetwmfullscreen) + { + mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask; + attr.backing_store = NotUseful; + attr.save_under = False; + } + else + { + mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask | CWOverrideRedirect; + attr.override_redirect = True; + attr.backing_store = NotUseful; + attr.save_under = False; + vid_isoverrideredirect = true; // so it knows to grab + } + } + else + { + mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; + } + + win = XCreateWindow(vidx11_display, root, 0, 0, mode->width, mode->height, 0, CopyFromParent, InputOutput, vidx11_visual, mask, &attr); + + data = loadimagepixelsbgra("darkplaces-icon", false, false, false, NULL); + if(data) + { + // use _NET_WM_ICON too + static long netwm_icon[MAX_NETWM_ICON]; + int pos = 0; + int i = 1; + + while(data) + { + if(pos + 2 * image_width * image_height < MAX_NETWM_ICON) + { + netwm_icon[pos++] = image_width; + netwm_icon[pos++] = image_height; + for(i = 0; i < image_height; ++i) + for(j = 0; j < image_width; ++j) + netwm_icon[pos++] = BuffLittleLong(&data[(i*image_width+j)*4]); + } + else + { + Con_Printf("Skipping NETWM icon #%d because there is no space left\n", i); + } + ++i; + Mem_Free(data); + data = loadimagepixelsbgra(va("darkplaces-icon%d", i), false, false, false, NULL); + } + XChangeProperty(vidx11_display, win, net_wm_icon, cardinal, 32, PropModeReplace, (const unsigned char *) netwm_icon, pos); + } + + // fallthrough for old window managers + xpm = (char *) FS_LoadFile("darkplaces-icon.xpm", tempmempool, false, NULL); + idata = NULL; + if(xpm) + idata = XPM_DecodeString(xpm); + if(!idata) + idata = ENGINE_ICON; + + wmhints = XAllocWMHints(); + if(XpmCreatePixmapFromData(vidx11_display, win, + idata, + &wmhints->icon_pixmap, &wmhints->icon_mask, NULL) == XpmSuccess) + wmhints->flags |= IconPixmapHint | IconMaskHint; + + if(xpm) + Mem_Free(xpm); + + clshints = XAllocClassHint(); + clshints->res_name = strdup(gamename); + clshints->res_class = strdup("DarkPlaces"); + + szhints = XAllocSizeHints(); + if(vid_resizable.integer == 0 && !vid_isnetwmfullscreen) + { + szhints->min_width = szhints->max_width = mode->width; + szhints->min_height = szhints->max_height = mode->height; + szhints->flags |= PMinSize | PMaxSize; + } + + XmbSetWMProperties(vidx11_display, win, gamename, gamename, (char **) com_argv, com_argc, szhints, wmhints, clshints); + // strdup() allocates using malloc(), should be freed with free() + free(clshints->res_name); + free(clshints->res_class); + XFree(clshints); + XFree(wmhints); + XFree(szhints); + + //XStoreName(vidx11_display, win, gamename); + XMapWindow(vidx11_display, win); + + XSetWMProtocols(vidx11_display, win, &wm_delete_window_atom, 1); + + if (vid_isoverrideredirect) + { + XMoveWindow(vidx11_display, win, 0, 0); + XRaiseWindow(vidx11_display, win); + XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, 0, 0); + XFlush(vidx11_display); + } + + if(vid_isvidmodefullscreen) + { + // Move the viewport to top left + XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); + } + + //XSync(vidx11_display, False); + + // COMMANDLINEOPTION: Unix GLX: -noshm disables XShm extensioon + if(dpyname && dpyname[0] == ':' && dpyname[1] && (dpyname[2] < '0' || dpyname[2] > '9') && !COM_CheckParm("-noshm") && XShmQueryExtension(vidx11_display)) + { + Con_Printf("Using XShm\n"); + vidx11_shmevent = XShmGetEventBase(vidx11_display) + ShmCompletion; + } + else + { + Con_Printf("Not using XShm\n"); + vidx11_shmevent = -1; + } + BuildXImages(mode->width, mode->height); + + vidx11_ximage_pos = 0; + vid.softpixels = (unsigned int *) vidx11_ximage[vidx11_ximage_pos]->data; + vidx11_shmwait = 0; + vid.softdepthpixels = (unsigned int *)calloc(1, mode->width * mode->height * 4); + + memset(&gcval, 0, sizeof(gcval)); + vidx11_gc = XCreateGC(vidx11_display, win, 0, &gcval); + + if (DPSOFTRAST_Init(mode->width, mode->height, vid_soft_threads.integer, vid_soft_interlace.integer, (unsigned int *)vid.softpixels, (unsigned int *)vid.softdepthpixels) < 0) + { + Con_Printf("Failed to initialize software rasterizer\n"); + VID_Shutdown(); + return false; + } + + XSync(vidx11_display, False); + + vid_usingmousegrab = false; + vid_usingmouse = false; + vid_usinghidecursor = false; + vid_usingvsync = false; + vid_hidden = false; + vid_activewindow = true; + vid_x11_hardwaregammasupported = XF86VidModeGetGammaRampSize(vidx11_display, vidx11_screen, &vid_x11_gammarampsize) != 0; +#if !defined(__APPLE__) && !defined(SUNOS) + vid_x11_dgasupported = XF86DGAQueryVersion(vidx11_display, &MajorVersion, &MinorVersion); + if (!vid_x11_dgasupported) + Con_Print( "Failed to detect XF86DGA Mouse extension\n" ); +#endif + + VID_Soft_SharedSetup(); + + return true; +} +qboolean VID_InitModeGL(viddef_mode_t *mode) +{ + int i, j; + int attrib[32]; + XSetWindowAttributes attr; + XClassHint *clshints; + XWMHints *wmhints; + XSizeHints *szhints; + unsigned long mask; + XVisualInfo *visinfo; + int MajorVersion, MinorVersion; + const char *drivername; + char *xpm; + char **idata; + unsigned char *data; + + vid_isfullscreen = false; + vid_isnetwmfullscreen = false; + vid_isvidmodefullscreen = false; + vid_isoverrideredirect = false; + +#if defined(__APPLE__) && defined(__MACH__) + drivername = "/usr/X11R6/lib/libGL.1.dylib"; +#else + drivername = "libGL.so.1"; +#endif +// COMMANDLINEOPTION: Linux GLX: -gl_driver selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it +// COMMANDLINEOPTION: BSD GLX: -gl_driver selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it +// LordHavoc: although this works on MacOSX, it's useless there (as there is only one system libGL) + i = COM_CheckParm("-gl_driver"); + if (i && i < com_argc - 1) + drivername = com_argv[i + 1]; + if (!GL_OpenLibrary(drivername)) + { + Con_Printf("Unable to load GL driver \"%s\"\n", drivername); + return false; + } + + if (!(vidx11_display = XOpenDisplay(NULL))) + { + Con_Print("Couldn't open the X display\n"); + return false; + } + + // LordHavoc: making the close button on a window do the right thing + // seems to involve this mess, sigh... + wm_delete_window_atom = XInternAtom(vidx11_display, "WM_DELETE_WINDOW", false); + net_wm_state_atom = XInternAtom(vidx11_display, "_NET_WM_STATE", false); + net_wm_state_fullscreen_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_FULLSCREEN", false); + net_wm_state_hidden_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_HIDDEN", false); + net_wm_icon = XInternAtom(vidx11_display, "_NET_WM_ICON", false); + cardinal = XInternAtom(vidx11_display, "CARDINAL", false); + + // make autorepeat send keypress/keypress/.../keyrelease instead of intervening keyrelease + XkbSetDetectableAutoRepeat(vidx11_display, true, NULL); + + vidx11_screen = DefaultScreen(vidx11_display); + root = RootWindow(vidx11_display, vidx11_screen); + + // Get video mode list + MajorVersion = MinorVersion = 0; + if (!XF86VidModeQueryVersion(vidx11_display, &MajorVersion, &MinorVersion)) + vidmode_ext = false; + else + { + Con_DPrintf("Using XFree86-VidModeExtension Version %d.%d\n", MajorVersion, MinorVersion); + vidmode_ext = true; + } + + if ((qglXChooseVisual = (XVisualInfo *(GLAPIENTRY *)(Display *dpy, int screen, int *attribList))GL_GetProcAddress("glXChooseVisual")) == NULL + || (qglXCreateContext = (GLXContext (GLAPIENTRY *)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct))GL_GetProcAddress("glXCreateContext")) == NULL + || (qglXDestroyContext = (void (GLAPIENTRY *)(Display *dpy, GLXContext ctx))GL_GetProcAddress("glXDestroyContext")) == NULL + || (qglXMakeCurrent = (Bool (GLAPIENTRY *)(Display *dpy, GLXDrawable drawable, GLXContext ctx))GL_GetProcAddress("glXMakeCurrent")) == NULL + || (qglXSwapBuffers = (void (GLAPIENTRY *)(Display *dpy, GLXDrawable drawable))GL_GetProcAddress("glXSwapBuffers")) == NULL + || (qglXQueryExtensionsString = (const char *(GLAPIENTRY *)(Display *dpy, int screen))GL_GetProcAddress("glXQueryExtensionsString")) == NULL) + { + Con_Printf("glX functions not found in %s\n", gl_driver); + return false; + } + + VID_BuildGLXAttrib(attrib, mode->bitsperpixel == 32, mode->stereobuffer, mode->samples); + visinfo = qglXChooseVisual(vidx11_display, vidx11_screen, attrib); + if (!visinfo) + { + Con_Print("Couldn't get an RGB, Double-buffered, Depth visual\n"); + return false; + } + + if (mode->fullscreen) + { + if(vid_netwmfullscreen.integer) + { + // TODO detect WM support + vid_isnetwmfullscreen = true; + vid_isfullscreen = true; + // width and height will be filled in later + Con_DPrintf("Using NetWM fullscreen mode\n"); + } + + if(!vid_isfullscreen && vidmode_ext) + { + int best_fit, best_dist, dist, x, y; + + // Are we going fullscreen? If so, let's change video mode + XF86VidModeModeLine *current_vidmode; + XF86VidModeModeInfo **vidmodes; + int num_vidmodes; + + // This nice hack comes from the SDL source code + current_vidmode = (XF86VidModeModeLine*)((char*)&init_vidmode + sizeof(init_vidmode.dotclock)); + XF86VidModeGetModeLine(vidx11_display, vidx11_screen, (int*)&init_vidmode.dotclock, current_vidmode); + + XF86VidModeGetAllModeLines(vidx11_display, vidx11_screen, &num_vidmodes, &vidmodes); + best_dist = 0; + best_fit = -1; + + for (i = 0; i < num_vidmodes; i++) + { + if (mode->width > vidmodes[i]->hdisplay || mode->height > vidmodes[i]->vdisplay) + continue; + + x = mode->width - vidmodes[i]->hdisplay; + y = mode->height - vidmodes[i]->vdisplay; + dist = (x * x) + (y * y); + if (best_fit == -1 || dist < best_dist) + { + best_dist = dist; + best_fit = i; + } + } + + if (best_fit != -1) + { + // LordHavoc: changed from ActualWidth/ActualHeight =, + // to width/height =, so the window will take the full area of + // the mode chosen + mode->width = vidmodes[best_fit]->hdisplay; + mode->height = vidmodes[best_fit]->vdisplay; + + // change to the mode + XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, vidmodes[best_fit]); + memcpy(&game_vidmode, vidmodes[best_fit], sizeof(game_vidmode)); + vid_isvidmodefullscreen = true; + vid_isfullscreen = true; + + // Move the viewport to top left + XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); + Con_DPrintf("Using XVidMode fullscreen mode at %dx%d\n", mode->width, mode->height); + } + + free(vidmodes); + } + + if(!vid_isfullscreen) + { + // sorry, no FS available + // use the full desktop resolution + vid_isfullscreen = true; + // width and height will be filled in later + mode->width = DisplayWidth(vidx11_display, vidx11_screen); + mode->height = DisplayHeight(vidx11_display, vidx11_screen); + Con_DPrintf("Using X11 fullscreen mode at %dx%d\n", mode->width, mode->height); + } + } + + // LordHavoc: save the visual for use in gamma ramp settings later + vidx11_visual = visinfo->visual; + + /* window attributes */ + attr.background_pixel = 0; + attr.border_pixel = 0; + // LordHavoc: save the colormap for later, too + vidx11_colormap = attr.colormap = XCreateColormap(vidx11_display, root, visinfo->visual, AllocNone); + attr.event_mask = X_MASK; + + if (mode->fullscreen) + { + if(vid_isnetwmfullscreen) + { + mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask; + attr.backing_store = NotUseful; + attr.save_under = False; + } + else + { + mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask | CWOverrideRedirect; + attr.override_redirect = True; + attr.backing_store = NotUseful; + attr.save_under = False; + vid_isoverrideredirect = true; // so it knows to grab + } + } + else + { + mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; + } + + win = XCreateWindow(vidx11_display, root, 0, 0, mode->width, mode->height, 0, visinfo->depth, InputOutput, visinfo->visual, mask, &attr); + + data = loadimagepixelsbgra("darkplaces-icon", false, false, false, NULL); + if(data) + { + // use _NET_WM_ICON too + static long netwm_icon[MAX_NETWM_ICON]; + int pos = 0; + int i = 1; + + while(data) + { + if(pos + 2 * image_width * image_height < MAX_NETWM_ICON) + { + netwm_icon[pos++] = image_width; + netwm_icon[pos++] = image_height; + for(i = 0; i < image_height; ++i) + for(j = 0; j < image_width; ++j) + netwm_icon[pos++] = BuffLittleLong(&data[(i*image_width+j)*4]); + } + else + { + Con_Printf("Skipping NETWM icon #%d because there is no space left\n", i); + } + ++i; + Mem_Free(data); + data = loadimagepixelsbgra(va("darkplaces-icon%d", i), false, false, false, NULL); + } + XChangeProperty(vidx11_display, win, net_wm_icon, cardinal, 32, PropModeReplace, (const unsigned char *) netwm_icon, pos); + } + + // fallthrough for old window managers + xpm = (char *) FS_LoadFile("darkplaces-icon.xpm", tempmempool, false, NULL); + idata = NULL; + if(xpm) + idata = XPM_DecodeString(xpm); + if(!idata) + idata = ENGINE_ICON; + + wmhints = XAllocWMHints(); + if(XpmCreatePixmapFromData(vidx11_display, win, + idata, + &wmhints->icon_pixmap, &wmhints->icon_mask, NULL) == XpmSuccess) + wmhints->flags |= IconPixmapHint | IconMaskHint; + + if(xpm) + Mem_Free(xpm); + + clshints = XAllocClassHint(); + clshints->res_name = strdup(gamename); + clshints->res_class = strdup("DarkPlaces"); + + szhints = XAllocSizeHints(); + if(vid_resizable.integer == 0 && !vid_isnetwmfullscreen) + { + szhints->min_width = szhints->max_width = mode->width; + szhints->min_height = szhints->max_height = mode->height; + szhints->flags |= PMinSize | PMaxSize; + } + + XmbSetWMProperties(vidx11_display, win, gamename, gamename, (char **) com_argv, com_argc, szhints, wmhints, clshints); + // strdup() allocates using malloc(), should be freed with free() + free(clshints->res_name); + free(clshints->res_class); + XFree(clshints); + XFree(wmhints); + XFree(szhints); + + //XStoreName(vidx11_display, win, gamename); + XMapWindow(vidx11_display, win); + + XSetWMProtocols(vidx11_display, win, &wm_delete_window_atom, 1); + + if (vid_isoverrideredirect) + { + XMoveWindow(vidx11_display, win, 0, 0); + XRaiseWindow(vidx11_display, win); + XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, 0, 0); + XFlush(vidx11_display); + } + + if(vid_isvidmodefullscreen) + { + // Move the viewport to top left + XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); + } + + //XSync(vidx11_display, False); + + ctx = qglXCreateContext(vidx11_display, visinfo, NULL, True); + XFree(visinfo); // glXChooseVisual man page says to use XFree to free visinfo + if (!ctx) + { + Con_Printf ("glXCreateContext failed\n"); + return false; + } + + if (!qglXMakeCurrent(vidx11_display, win, ctx)) + { + Con_Printf ("glXMakeCurrent failed\n"); + return false; + } + + XSync(vidx11_display, False); + + if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL) + { + Con_Printf ("glGetString not found in %s\n", gl_driver); + return false; + } + + gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); + gl_platform = "GLX"; + gl_platformextensions = qglXQueryExtensionsString(vidx11_display, vidx11_screen); + +// COMMANDLINEOPTION: Linux GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) +// COMMANDLINEOPTION: BSD GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) +// COMMANDLINEOPTION: MacOSX GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) + GL_CheckExtension("GLX_ARB_get_proc_address", getprocaddressfuncs, "-nogetprocaddress", false); +// COMMANDLINEOPTION: Linux GLX: -novideosync disables GLX_SGI_swap_control +// COMMANDLINEOPTION: BSD GLX: -novideosync disables GLX_SGI_swap_control +// COMMANDLINEOPTION: MacOSX GLX: -novideosync disables GLX_SGI_swap_control + GL_CheckExtension("GLX_SGI_swap_control", swapcontrolfuncs, "-novideosync", false); + + vid_usingmousegrab = false; + vid_usingmouse = false; + vid_usinghidecursor = false; + vid_usingvsync = false; + vid_hidden = false; + vid_activewindow = true; + vid_x11_hardwaregammasupported = XF86VidModeGetGammaRampSize(vidx11_display, vidx11_screen, &vid_x11_gammarampsize) != 0; +#if !defined(__APPLE__) && !defined(SUNOS) + vid_x11_dgasupported = XF86DGAQueryVersion(vidx11_display, &MajorVersion, &MinorVersion); + if (!vid_x11_dgasupported) + Con_Print( "Failed to detect XF86DGA Mouse extension\n" ); +#endif + + GL_Init(); + return true; +} + +qboolean VID_InitMode(viddef_mode_t *mode) +{ +#ifdef SSE_POSSIBLE + if (vid_soft.integer) + return VID_InitModeSoft(mode); + else +#endif + return VID_InitModeGL(mode); +} + +void Sys_SendKeyEvents(void) +{ + static qboolean sound_active = true; + + // enable/disable sound on focus gain/loss + if ((!vid_hidden && vid_activewindow) || !snd_mutewhenidle.integer) + { + if (!sound_active) + { + S_UnblockSound (); + sound_active = true; + } + } + else + { + if (sound_active) + { + S_BlockSound (); + sound_active = false; + } + } + + HandleEvents(); +} + +void VID_BuildJoyState(vid_joystate_t *joystate) +{ + VID_Shared_BuildJoyState_Begin(joystate); + VID_Shared_BuildJoyState_Finish(joystate); +} + +void VID_EnableJoystick(qboolean enable) +{ + int index = joy_enable.integer > 0 ? joy_index.integer : -1; + qboolean success = false; + int sharedcount = 0; + sharedcount = VID_Shared_SetJoystick(index); + if (index >= 0 && index < sharedcount) + success = true; + + // update cvar containing count of XInput joysticks + if (joy_detected.integer != sharedcount) + Cvar_SetValueQuick(&joy_detected, sharedcount); + + Cvar_SetValueQuick(&joy_active, success ? 1 : 0); +} + +void IN_Move (void) +{ + vid_joystate_t joystate; + VID_EnableJoystick(true); + VID_BuildJoyState(&joystate); + VID_ApplyJoyState(&joystate); +} + +size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) +{ + if(vidmode_ext) + { + int i, bpp; + size_t k; + XF86VidModeModeInfo **vidmodes; + int num_vidmodes; + + XF86VidModeGetAllModeLines(vidx11_display, vidx11_screen, &num_vidmodes, &vidmodes); + k = 0; + for (i = 0; i < num_vidmodes; i++) + { + if(k >= maxcount) + break; + // we don't get bpp info, so let's just assume all of 8, 15, 16, 24, 32 work + for(bpp = 8; bpp <= 32; bpp = ((bpp == 8) ? 15 : (bpp & 0xF8) + 8)) + { + if(k >= maxcount) + break; + modes[k].width = vidmodes[i]->hdisplay; + modes[k].height = vidmodes[i]->vdisplay; + modes[k].bpp = 8; + if(vidmodes[i]->dotclock && vidmodes[i]->htotal && vidmodes[i]->vtotal) + modes[k].refreshrate = vidmodes[i]->dotclock / vidmodes[i]->htotal / vidmodes[i]->vtotal; + else + modes[k].refreshrate = 60; + modes[k].pixelheight_num = 1; + modes[k].pixelheight_denom = 1; // xvidmode does not provide this + ++k; + } + } + // manpage of XF86VidModeGetAllModeLines says it should be freed by the caller + XFree(vidmodes); + return k; + } + return 0; // FIXME implement this +} diff --git a/misc/source/darkplaces-src/vid_null.c b/misc/source/darkplaces-src/vid_null.c new file mode 100644 index 00000000..4ac7ab11 --- /dev/null +++ b/misc/source/darkplaces-src/vid_null.c @@ -0,0 +1,102 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "quakedef.h" + +#include + +int cl_available = false; + +qboolean vid_supportrefreshrate = false; + +void VID_Shutdown(void) +{ +} + +void signal_handler(int sig) +{ + Con_Printf("Received signal %d, exiting...\n", sig); + Sys_Quit(1); +} + +void InitSig(void) +{ +#ifndef WIN32 + signal(SIGHUP, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGQUIT, signal_handler); + signal(SIGILL, signal_handler); + signal(SIGTRAP, signal_handler); + signal(SIGIOT, signal_handler); + signal(SIGBUS, signal_handler); + signal(SIGFPE, signal_handler); + signal(SIGSEGV, signal_handler); + signal(SIGTERM, signal_handler); +#endif +} + +void VID_SetMouse (qboolean fullscreengrab, qboolean relative, qboolean hidecursor) +{ +} + +void VID_Finish (void) +{ +} + +int VID_SetGamma(unsigned short *ramps, int rampsize) +{ + return FALSE; +} + +int VID_GetGamma(unsigned short *ramps, int rampsize) +{ + return FALSE; +} + +void VID_Init(void) +{ + InitSig(); // trap evil signals +} + +qboolean VID_InitMode(viddef_mode_t *mode) +{ + return false; +} + +void *GL_GetProcAddress(const char *name) +{ + return NULL; +} + +void Sys_SendKeyEvents(void) +{ +} + +void VID_BuildJoyState(vid_joystate_t *joystate) +{ +} + +void IN_Move(void) +{ +} + +size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) +{ + return 0; +} diff --git a/misc/source/darkplaces-src/vid_sdl.c b/misc/source/darkplaces-src/vid_sdl.c new file mode 100644 index 00000000..d356150d --- /dev/null +++ b/misc/source/darkplaces-src/vid_sdl.c @@ -0,0 +1,2415 @@ +/* +Copyright (C) 2003 T. Joseph Carter + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#undef WIN32_LEAN_AND_MEAN //hush a warning, SDL.h redefines this +#include +#include +#include + +#include "quakedef.h" +#include "image.h" +#include "dpsoftrast.h" + +#ifndef __IPHONEOS__ +#ifdef MACOSX +#include +#include +#include +#include +static cvar_t apple_mouse_noaccel = {CVAR_SAVE, "apple_mouse_noaccel", "1", "disables mouse acceleration while DarkPlaces is active"}; +static qboolean vid_usingnoaccel; +static double originalMouseSpeed = -1.0; +io_connect_t IN_GetIOHandle(void) +{ + io_connect_t iohandle = MACH_PORT_NULL; + kern_return_t status; + io_service_t iohidsystem = MACH_PORT_NULL; + mach_port_t masterport; + + status = IOMasterPort(MACH_PORT_NULL, &masterport); + if(status != KERN_SUCCESS) + return 0; + + iohidsystem = IORegistryEntryFromPath(masterport, kIOServicePlane ":/IOResources/IOHIDSystem"); + if(!iohidsystem) + return 0; + + status = IOServiceOpen(iohidsystem, mach_task_self(), kIOHIDParamConnectType, &iohandle); + IOObjectRelease(iohidsystem); + + return iohandle; +} +#endif +#endif + +#ifdef WIN32 +#define SDL_R_RESTART +#endif + +// Tell startup code that we have a client +int cl_available = true; + +qboolean vid_supportrefreshrate = false; + +#ifdef USE_GLES2 +# define SETVIDEOMODE 0 +#else +# if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2 +# define SETVIDEOMODE 1 +# else +// LordHavoc: SDL 1.3's SDL_CreateWindow API is not finished enough to use yet, but you can set this to 0 if you want to try it... +# ifndef SETVIDEOMODE +# define SETVIDEOMODE 1 +# endif +# endif +#endif + +static qboolean vid_usingmouse = false; +static qboolean vid_usinghidecursor = false; +static qboolean vid_hasfocus = false; +static qboolean vid_isfullscreen; +#if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2 +#else +static qboolean vid_usingvsync = false; +#endif +static SDL_Joystick *vid_sdljoystick = NULL; + +static int win_half_width = 50; +static int win_half_height = 50; +static int video_bpp; + +#if SETVIDEOMODE +static SDL_Surface *screen; +static int video_flags; +#else +static SDL_GLContext *context; +static SDL_Window *window; +static int window_flags; +#endif +static SDL_Surface *vid_softsurface; + +///////////////////////// +// Input handling +//// +//TODO: Add error checking + +#ifndef SDLK_PERCENT +#define SDLK_PERCENT '%' +#define SDLK_PRINTSCREEN SDLK_PRINT +#define SDLK_SCROLLLOCK SDLK_SCROLLOCK +#define SDLK_NUMLOCKCLEAR SDLK_NUMLOCK +#define SDLK_KP_1 SDLK_KP1 +#define SDLK_KP_2 SDLK_KP2 +#define SDLK_KP_3 SDLK_KP3 +#define SDLK_KP_4 SDLK_KP4 +#define SDLK_KP_5 SDLK_KP5 +#define SDLK_KP_6 SDLK_KP6 +#define SDLK_KP_7 SDLK_KP7 +#define SDLK_KP_8 SDLK_KP8 +#define SDLK_KP_9 SDLK_KP9 +#define SDLK_KP_0 SDLK_KP0 +#endif + +static int MapKey( unsigned int sdlkey ) +{ + switch(sdlkey) + { + default: return 0; +// case SDLK_UNKNOWN: return K_UNKNOWN; + case SDLK_RETURN: return K_ENTER; + case SDLK_ESCAPE: return K_ESCAPE; + case SDLK_BACKSPACE: return K_BACKSPACE; + case SDLK_TAB: return K_TAB; + case SDLK_SPACE: return K_SPACE; + case SDLK_EXCLAIM: return '!'; + case SDLK_QUOTEDBL: return '"'; + case SDLK_HASH: return '#'; + case SDLK_PERCENT: return '%'; + case SDLK_DOLLAR: return '$'; + case SDLK_AMPERSAND: return '&'; + case SDLK_QUOTE: return '\''; + case SDLK_LEFTPAREN: return '('; + case SDLK_RIGHTPAREN: return ')'; + case SDLK_ASTERISK: return '*'; + case SDLK_PLUS: return '+'; + case SDLK_COMMA: return ','; + case SDLK_MINUS: return '-'; + case SDLK_PERIOD: return '.'; + case SDLK_SLASH: return '/'; + case SDLK_0: return '0'; + case SDLK_1: return '1'; + case SDLK_2: return '2'; + case SDLK_3: return '3'; + case SDLK_4: return '4'; + case SDLK_5: return '5'; + case SDLK_6: return '6'; + case SDLK_7: return '7'; + case SDLK_8: return '8'; + case SDLK_9: return '9'; + case SDLK_COLON: return ':'; + case SDLK_SEMICOLON: return ';'; + case SDLK_LESS: return '<'; + case SDLK_EQUALS: return '='; + case SDLK_GREATER: return '>'; + case SDLK_QUESTION: return '?'; + case SDLK_AT: return '@'; + case SDLK_LEFTBRACKET: return '['; + case SDLK_BACKSLASH: return '\\'; + case SDLK_RIGHTBRACKET: return ']'; + case SDLK_CARET: return '^'; + case SDLK_UNDERSCORE: return '_'; + case SDLK_BACKQUOTE: return '`'; + case SDLK_a: return 'a'; + case SDLK_b: return 'b'; + case SDLK_c: return 'c'; + case SDLK_d: return 'd'; + case SDLK_e: return 'e'; + case SDLK_f: return 'f'; + case SDLK_g: return 'g'; + case SDLK_h: return 'h'; + case SDLK_i: return 'i'; + case SDLK_j: return 'j'; + case SDLK_k: return 'k'; + case SDLK_l: return 'l'; + case SDLK_m: return 'm'; + case SDLK_n: return 'n'; + case SDLK_o: return 'o'; + case SDLK_p: return 'p'; + case SDLK_q: return 'q'; + case SDLK_r: return 'r'; + case SDLK_s: return 's'; + case SDLK_t: return 't'; + case SDLK_u: return 'u'; + case SDLK_v: return 'v'; + case SDLK_w: return 'w'; + case SDLK_x: return 'x'; + case SDLK_y: return 'y'; + case SDLK_z: return 'z'; + case SDLK_CAPSLOCK: return K_CAPSLOCK; + case SDLK_F1: return K_F1; + case SDLK_F2: return K_F2; + case SDLK_F3: return K_F3; + case SDLK_F4: return K_F4; + case SDLK_F5: return K_F5; + case SDLK_F6: return K_F6; + case SDLK_F7: return K_F7; + case SDLK_F8: return K_F8; + case SDLK_F9: return K_F9; + case SDLK_F10: return K_F10; + case SDLK_F11: return K_F11; + case SDLK_F12: return K_F12; + case SDLK_PRINTSCREEN: return K_PRINTSCREEN; + case SDLK_SCROLLLOCK: return K_SCROLLOCK; + case SDLK_PAUSE: return K_PAUSE; + case SDLK_INSERT: return K_INS; + case SDLK_HOME: return K_HOME; + case SDLK_PAGEUP: return K_PGUP; +#ifdef __IPHONEOS__ + case SDLK_DELETE: return K_BACKSPACE; +#else + case SDLK_DELETE: return K_DEL; +#endif + case SDLK_END: return K_END; + case SDLK_PAGEDOWN: return K_PGDN; + case SDLK_RIGHT: return K_RIGHTARROW; + case SDLK_LEFT: return K_LEFTARROW; + case SDLK_DOWN: return K_DOWNARROW; + case SDLK_UP: return K_UPARROW; + case SDLK_NUMLOCKCLEAR: return K_NUMLOCK; + case SDLK_KP_DIVIDE: return K_KP_DIVIDE; + case SDLK_KP_MULTIPLY: return K_KP_MULTIPLY; + case SDLK_KP_MINUS: return K_KP_MINUS; + case SDLK_KP_PLUS: return K_KP_PLUS; + case SDLK_KP_ENTER: return K_KP_ENTER; + case SDLK_KP_1: return K_KP_1; + case SDLK_KP_2: return K_KP_2; + case SDLK_KP_3: return K_KP_3; + case SDLK_KP_4: return K_KP_4; + case SDLK_KP_5: return K_KP_5; + case SDLK_KP_6: return K_KP_6; + case SDLK_KP_7: return K_KP_7; + case SDLK_KP_8: return K_KP_8; + case SDLK_KP_9: return K_KP_9; + case SDLK_KP_0: return K_KP_0; + case SDLK_KP_PERIOD: return K_KP_PERIOD; +// case SDLK_APPLICATION: return K_APPLICATION; +// case SDLK_POWER: return K_POWER; + case SDLK_KP_EQUALS: return K_KP_EQUALS; +// case SDLK_F13: return K_F13; +// case SDLK_F14: return K_F14; +// case SDLK_F15: return K_F15; +// case SDLK_F16: return K_F16; +// case SDLK_F17: return K_F17; +// case SDLK_F18: return K_F18; +// case SDLK_F19: return K_F19; +// case SDLK_F20: return K_F20; +// case SDLK_F21: return K_F21; +// case SDLK_F22: return K_F22; +// case SDLK_F23: return K_F23; +// case SDLK_F24: return K_F24; +// case SDLK_EXECUTE: return K_EXECUTE; +// case SDLK_HELP: return K_HELP; +// case SDLK_MENU: return K_MENU; +// case SDLK_SELECT: return K_SELECT; +// case SDLK_STOP: return K_STOP; +// case SDLK_AGAIN: return K_AGAIN; +// case SDLK_UNDO: return K_UNDO; +// case SDLK_CUT: return K_CUT; +// case SDLK_COPY: return K_COPY; +// case SDLK_PASTE: return K_PASTE; +// case SDLK_FIND: return K_FIND; +// case SDLK_MUTE: return K_MUTE; +// case SDLK_VOLUMEUP: return K_VOLUMEUP; +// case SDLK_VOLUMEDOWN: return K_VOLUMEDOWN; +// case SDLK_KP_COMMA: return K_KP_COMMA; +// case SDLK_KP_EQUALSAS400: return K_KP_EQUALSAS400; +// case SDLK_ALTERASE: return K_ALTERASE; +// case SDLK_SYSREQ: return K_SYSREQ; +// case SDLK_CANCEL: return K_CANCEL; +// case SDLK_CLEAR: return K_CLEAR; +// case SDLK_PRIOR: return K_PRIOR; +// case SDLK_RETURN2: return K_RETURN2; +// case SDLK_SEPARATOR: return K_SEPARATOR; +// case SDLK_OUT: return K_OUT; +// case SDLK_OPER: return K_OPER; +// case SDLK_CLEARAGAIN: return K_CLEARAGAIN; +// case SDLK_CRSEL: return K_CRSEL; +// case SDLK_EXSEL: return K_EXSEL; +// case SDLK_KP_00: return K_KP_00; +// case SDLK_KP_000: return K_KP_000; +// case SDLK_THOUSANDSSEPARATOR: return K_THOUSANDSSEPARATOR; +// case SDLK_DECIMALSEPARATOR: return K_DECIMALSEPARATOR; +// case SDLK_CURRENCYUNIT: return K_CURRENCYUNIT; +// case SDLK_CURRENCYSUBUNIT: return K_CURRENCYSUBUNIT; +// case SDLK_KP_LEFTPAREN: return K_KP_LEFTPAREN; +// case SDLK_KP_RIGHTPAREN: return K_KP_RIGHTPAREN; +// case SDLK_KP_LEFTBRACE: return K_KP_LEFTBRACE; +// case SDLK_KP_RIGHTBRACE: return K_KP_RIGHTBRACE; +// case SDLK_KP_TAB: return K_KP_TAB; +// case SDLK_KP_BACKSPACE: return K_KP_BACKSPACE; +// case SDLK_KP_A: return K_KP_A; +// case SDLK_KP_B: return K_KP_B; +// case SDLK_KP_C: return K_KP_C; +// case SDLK_KP_D: return K_KP_D; +// case SDLK_KP_E: return K_KP_E; +// case SDLK_KP_F: return K_KP_F; +// case SDLK_KP_XOR: return K_KP_XOR; +// case SDLK_KP_POWER: return K_KP_POWER; +// case SDLK_KP_PERCENT: return K_KP_PERCENT; +// case SDLK_KP_LESS: return K_KP_LESS; +// case SDLK_KP_GREATER: return K_KP_GREATER; +// case SDLK_KP_AMPERSAND: return K_KP_AMPERSAND; +// case SDLK_KP_DBLAMPERSAND: return K_KP_DBLAMPERSAND; +// case SDLK_KP_VERTICALBAR: return K_KP_VERTICALBAR; +// case SDLK_KP_DBLVERTICALBAR: return K_KP_DBLVERTICALBAR; +// case SDLK_KP_COLON: return K_KP_COLON; +// case SDLK_KP_HASH: return K_KP_HASH; +// case SDLK_KP_SPACE: return K_KP_SPACE; +// case SDLK_KP_AT: return K_KP_AT; +// case SDLK_KP_EXCLAM: return K_KP_EXCLAM; +// case SDLK_KP_MEMSTORE: return K_KP_MEMSTORE; +// case SDLK_KP_MEMRECALL: return K_KP_MEMRECALL; +// case SDLK_KP_MEMCLEAR: return K_KP_MEMCLEAR; +// case SDLK_KP_MEMADD: return K_KP_MEMADD; +// case SDLK_KP_MEMSUBTRACT: return K_KP_MEMSUBTRACT; +// case SDLK_KP_MEMMULTIPLY: return K_KP_MEMMULTIPLY; +// case SDLK_KP_MEMDIVIDE: return K_KP_MEMDIVIDE; +// case SDLK_KP_PLUSMINUS: return K_KP_PLUSMINUS; +// case SDLK_KP_CLEAR: return K_KP_CLEAR; +// case SDLK_KP_CLEARENTRY: return K_KP_CLEARENTRY; +// case SDLK_KP_BINARY: return K_KP_BINARY; +// case SDLK_KP_OCTAL: return K_KP_OCTAL; +// case SDLK_KP_DECIMAL: return K_KP_DECIMAL; +// case SDLK_KP_HEXADECIMAL: return K_KP_HEXADECIMAL; + case SDLK_LCTRL: return K_CTRL; + case SDLK_LSHIFT: return K_SHIFT; + case SDLK_LALT: return K_ALT; +// case SDLK_LGUI: return K_LGUI; + case SDLK_RCTRL: return K_CTRL; + case SDLK_RSHIFT: return K_SHIFT; + case SDLK_RALT: return K_ALT; +// case SDLK_RGUI: return K_RGUI; +// case SDLK_MODE: return K_MODE; +// case SDLK_AUDIONEXT: return K_AUDIONEXT; +// case SDLK_AUDIOPREV: return K_AUDIOPREV; +// case SDLK_AUDIOSTOP: return K_AUDIOSTOP; +// case SDLK_AUDIOPLAY: return K_AUDIOPLAY; +// case SDLK_AUDIOMUTE: return K_AUDIOMUTE; +// case SDLK_MEDIASELECT: return K_MEDIASELECT; +// case SDLK_WWW: return K_WWW; +// case SDLK_MAIL: return K_MAIL; +// case SDLK_CALCULATOR: return K_CALCULATOR; +// case SDLK_COMPUTER: return K_COMPUTER; +// case SDLK_AC_SEARCH: return K_AC_SEARCH; +// case SDLK_AC_HOME: return K_AC_HOME; +// case SDLK_AC_BACK: return K_AC_BACK; +// case SDLK_AC_FORWARD: return K_AC_FORWARD; +// case SDLK_AC_STOP: return K_AC_STOP; +// case SDLK_AC_REFRESH: return K_AC_REFRESH; +// case SDLK_AC_BOOKMARKS: return K_AC_BOOKMARKS; +// case SDLK_BRIGHTNESSDOWN: return K_BRIGHTNESSDOWN; +// case SDLK_BRIGHTNESSUP: return K_BRIGHTNESSUP; +// case SDLK_DISPLAYSWITCH: return K_DISPLAYSWITCH; +// case SDLK_KBDILLUMTOGGLE: return K_KBDILLUMTOGGLE; +// case SDLK_KBDILLUMDOWN: return K_KBDILLUMDOWN; +// case SDLK_KBDILLUMUP: return K_KBDILLUMUP; +// case SDLK_EJECT: return K_EJECT; +// case SDLK_SLEEP: return K_SLEEP; + } +} + +#ifdef __IPHONEOS__ +int SDL_iPhoneKeyboardShow(SDL_Window * window); // reveals the onscreen keyboard. Returns 0 on success and -1 on error. +int SDL_iPhoneKeyboardHide(SDL_Window * window); // hides the onscreen keyboard. Returns 0 on success and -1 on error. +SDL_bool SDL_iPhoneKeyboardIsShown(SDL_Window * window); // returns whether or not the onscreen keyboard is currently visible. +int SDL_iPhoneKeyboardToggle(SDL_Window * window); // toggles the visibility of the onscreen keyboard. Returns 0 on success and -1 on error. +#endif + +void VID_ShowKeyboard(qboolean show) +{ +#ifdef __IPHONEOS__ + if (show) + { + if (!SDL_iPhoneKeyboardIsShown(window)) + SDL_iPhoneKeyboardShow(window); + } + else + { + if (SDL_iPhoneKeyboardIsShown(window)) + SDL_iPhoneKeyboardHide(window); + } +#endif +} + +#ifdef __IPHONEOS__ +qboolean VID_ShowingKeyboard(void) +{ + return SDL_iPhoneKeyboardIsShown(window); +} +#endif + +void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor) +{ +#ifndef __IPHONEOS__ +#ifdef MACOSX + if(relative) + if(vid_usingmouse && (vid_usingnoaccel != !!apple_mouse_noaccel.integer)) + VID_SetMouse(false, false, false); // ungrab first! +#endif + if (vid_usingmouse != relative) + { + vid_usingmouse = relative; + cl_ignoremousemoves = 2; +#if SETVIDEOMODE + SDL_WM_GrabInput( relative ? SDL_GRAB_ON : SDL_GRAB_OFF ); +#else + SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE); +#endif +#ifdef MACOSX + if(relative) + { + // Save the status of mouse acceleration + originalMouseSpeed = -1.0; // in case of error + if(apple_mouse_noaccel.integer) + { + io_connect_t mouseDev = IN_GetIOHandle(); + if(mouseDev != 0) + { + if(IOHIDGetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), &originalMouseSpeed) == kIOReturnSuccess) + { + Con_DPrintf("previous mouse acceleration: %f\n", originalMouseSpeed); + if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), -1.0) != kIOReturnSuccess) + { + Con_Print("Could not disable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + Cvar_SetValueQuick(&apple_mouse_noaccel, 0); + } + } + else + { + Con_Print("Could not disable mouse acceleration (failed at IOHIDGetAccelerationWithKey).\n"); + Cvar_SetValueQuick(&apple_mouse_noaccel, 0); + } + IOServiceClose(mouseDev); + } + else + { + Con_Print("Could not disable mouse acceleration (failed at IO_GetIOHandle).\n"); + Cvar_SetValueQuick(&apple_mouse_noaccel, 0); + } + } + + vid_usingnoaccel = !!apple_mouse_noaccel.integer; + } + else + { + if(originalMouseSpeed != -1.0) + { + io_connect_t mouseDev = IN_GetIOHandle(); + if(mouseDev != 0) + { + Con_DPrintf("restoring mouse acceleration to: %f\n", originalMouseSpeed); + if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), originalMouseSpeed) != kIOReturnSuccess) + Con_Print("Could not re-enable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + IOServiceClose(mouseDev); + } + else + Con_Print("Could not re-enable mouse acceleration (failed at IO_GetIOHandle).\n"); + } + } +#endif + } + if (vid_usinghidecursor != hidecursor) + { + vid_usinghidecursor = hidecursor; + SDL_ShowCursor( hidecursor ? SDL_DISABLE : SDL_ENABLE); + } +#endif +} + +// multitouch[10][] represents the mouse pointer +// X and Y coordinates are 0-32767 as per SDL spec +#define MAXFINGERS 11 +int multitouch[MAXFINGERS][3]; + +qboolean VID_TouchscreenArea(int corner, float px, float py, float pwidth, float pheight, const char *icon, float *resultmove, qboolean *resultbutton, keynum_t key) +{ + int finger; + float fx, fy, fwidth, fheight; + float rel[3]; + qboolean button = false; + VectorClear(rel); + if (pwidth > 0 && pheight > 0) +#ifdef __IPHONEOS__ + if (!VID_ShowingKeyboard()) +#endif + { + if (corner & 1) px += vid_conwidth.value; + if (corner & 2) py += vid_conheight.value; + if (corner & 4) px += vid_conwidth.value * 0.5f; + if (corner & 8) py += vid_conheight.value * 0.5f; + if (corner & 16) {px *= vid_conwidth.value * (1.0f / 640.0f);py *= vid_conheight.value * (1.0f / 480.0f);pwidth *= vid_conwidth.value * (1.0f / 640.0f);pheight *= vid_conheight.value * (1.0f / 480.0f);} + fx = px * 32768.0f / vid_conwidth.value; + fy = py * 32768.0f / vid_conheight.value; + fwidth = pwidth * 32768.0f / vid_conwidth.value; + fheight = pheight * 32768.0f / vid_conheight.value; + for (finger = 0;finger < MAXFINGERS;finger++) + { + if (multitouch[finger][0] && multitouch[finger][1] >= fx && multitouch[finger][2] >= fy && multitouch[finger][1] < fx + fwidth && multitouch[finger][2] < fy + fheight) + { + rel[0] = (multitouch[finger][1] - (fx + 0.5f * fwidth)) * (2.0f / fwidth); + rel[1] = (multitouch[finger][2] - (fy + 0.5f * fheight)) * (2.0f / fheight); + rel[2] = 0; + button = true; + break; + } + } + if (scr_numtouchscreenareas < 16) + { + scr_touchscreenareas[scr_numtouchscreenareas].pic = icon; + scr_touchscreenareas[scr_numtouchscreenareas].rect[0] = px; + scr_touchscreenareas[scr_numtouchscreenareas].rect[1] = py; + scr_touchscreenareas[scr_numtouchscreenareas].rect[2] = pwidth; + scr_touchscreenareas[scr_numtouchscreenareas].rect[3] = pheight; + scr_touchscreenareas[scr_numtouchscreenareas].active = button; + scr_numtouchscreenareas++; + } + } + if (resultmove) + { + if (button) + VectorCopy(rel, resultmove); + else + VectorClear(resultmove); + } + if (resultbutton) + { + if (*resultbutton != button && (int)key > 0) + Key_Event(key, 0, button); + *resultbutton = button; + } + return button; +} + +void VID_BuildJoyState(vid_joystate_t *joystate) +{ + VID_Shared_BuildJoyState_Begin(joystate); + + if (vid_sdljoystick) + { + SDL_Joystick *joy = vid_sdljoystick; + int j; + int numaxes; + int numbuttons; + numaxes = SDL_JoystickNumAxes(joy); + for (j = 0;j < numaxes;j++) + joystate->axis[j] = SDL_JoystickGetAxis(joy, j) * (1.0f / 32767.0f); + numbuttons = SDL_JoystickNumButtons(joy); + for (j = 0;j < numbuttons;j++) + joystate->button[j] = SDL_JoystickGetButton(joy, j); + } + + VID_Shared_BuildJoyState_Finish(joystate); +} + +///////////////////// +// Movement handling +//// + +void IN_Move( void ) +{ + static int old_x = 0, old_y = 0; + static int stuck = 0; + int x, y; + vid_joystate_t joystate; + + scr_numtouchscreenareas = 0; + if (vid_touchscreen.integer) + { + vec3_t move, aim, click; + static qboolean buttons[16]; + static keydest_t oldkeydest; + keydest_t keydest = (key_consoleactive & KEY_CONSOLEACTIVE_USER) ? key_console : key_dest; + multitouch[MAXFINGERS-1][0] = SDL_GetMouseState(&x, &y); + multitouch[MAXFINGERS-1][1] = x * 32768 / vid.width; + multitouch[MAXFINGERS-1][2] = y * 32768 / vid.height; + if (oldkeydest != keydest) + { + switch(keydest) + { + case key_game: VID_ShowKeyboard(false);break; + case key_console: VID_ShowKeyboard(true);break; + case key_message: VID_ShowKeyboard(true);break; + default: break; + } + } + oldkeydest = keydest; + // top of screen is toggleconsole and K_ESCAPE + switch(keydest) + { + case key_console: +#ifdef __IPHONEOS__ + VID_TouchscreenArea( 0, 0, 0, 64, 64, NULL , NULL, &buttons[13], (keynum_t)'`'); + VID_TouchscreenArea( 0, 64, 0, 64, 64, "gfx/touch_menu.tga" , NULL, &buttons[14], K_ESCAPE); + if (!VID_ShowingKeyboard()) + { + // user entered a command, close the console now + Con_ToggleConsole_f(); + } +#endif + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , NULL, &buttons[15], (keynum_t)0); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , move, &buttons[0], K_MOUSE4); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , aim, &buttons[1], K_MOUSE5); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , click,&buttons[2], K_MOUSE1); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , NULL, &buttons[3], K_SPACE); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , NULL, &buttons[4], K_MOUSE2); + break; + case key_game: +#ifdef __IPHONEOS__ + VID_TouchscreenArea( 0, 0, 0, 64, 64, NULL , NULL, &buttons[13], (keynum_t)'`'); + VID_TouchscreenArea( 0, 64, 0, 64, 64, "gfx/touch_menu.tga" , NULL, &buttons[14], K_ESCAPE); +#endif + VID_TouchscreenArea( 2, 0,-128, 128, 128, "gfx/touch_movebutton.tga" , move, &buttons[0], K_MOUSE4); + VID_TouchscreenArea( 3,-128,-128, 128, 128, "gfx/touch_aimbutton.tga" , aim, &buttons[1], K_MOUSE5); + VID_TouchscreenArea( 2, 0,-160, 64, 32, "gfx/touch_jumpbutton.tga" , NULL, &buttons[3], K_SPACE); + VID_TouchscreenArea( 3,-128,-160, 64, 32, "gfx/touch_attackbutton.tga" , NULL, &buttons[2], K_MOUSE1); + VID_TouchscreenArea( 3, -64,-160, 64, 32, "gfx/touch_attack2button.tga", NULL, &buttons[4], K_MOUSE2); + buttons[15] = false; + break; + default: +#ifdef __IPHONEOS__ + VID_TouchscreenArea( 0, 0, 0, 64, 64, NULL , NULL, &buttons[13], (keynum_t)'`'); + VID_TouchscreenArea( 0, 64, 0, 64, 64, "gfx/touch_menu.tga" , NULL, &buttons[14], K_ESCAPE); + // in menus, an icon in the corner activates keyboard + VID_TouchscreenArea( 2, 0, -32, 32, 32, "gfx/touch_keyboard.tga" , NULL, &buttons[15], (keynum_t)0); + if (buttons[15]) + VID_ShowKeyboard(true); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , move, &buttons[0], K_MOUSE4); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , aim, &buttons[1], K_MOUSE5); + VID_TouchscreenArea(16, -320,-480,640, 960, NULL , click,&buttons[2], K_MOUSE1); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , NULL, &buttons[3], K_SPACE); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , NULL, &buttons[4], K_MOUSE2); + if (buttons[2]) + { + in_windowmouse_x = x; + in_windowmouse_y = y; + } +#else + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , NULL, &buttons[15], (keynum_t)0); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , move, &buttons[0], K_MOUSE4); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , aim, &buttons[1], K_MOUSE5); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , click,&buttons[2], K_MOUSE1); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , NULL, &buttons[3], K_SPACE); + VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , NULL, &buttons[4], K_MOUSE2); +#endif + break; + } + cl.cmd.forwardmove -= move[1] * cl_forwardspeed.value; + cl.cmd.sidemove += move[0] * cl_sidespeed.value; + cl.viewangles[0] += aim[1] * cl_pitchspeed.value * cl.realframetime; + cl.viewangles[1] -= aim[0] * cl_yawspeed.value * cl.realframetime; + } + else + { + if (vid_usingmouse) + { + if (vid_stick_mouse.integer) + { + // have the mouse stuck in the middle, example use: prevent expose effect of beryl during the game when not using + // window grabbing. --blub + + // we need 2 frames to initialize the center position + if(!stuck) + { +#if SETVIDEOMODE + SDL_WarpMouse(win_half_width, win_half_height); +#else + SDL_WarpMouseInWindow(window, win_half_width, win_half_height); +#endif + SDL_GetMouseState(&x, &y); + SDL_GetRelativeMouseState(&x, &y); + ++stuck; + } else { + SDL_GetRelativeMouseState(&x, &y); + in_mouse_x = x + old_x; + in_mouse_y = y + old_y; + SDL_GetMouseState(&x, &y); + old_x = x - win_half_width; + old_y = y - win_half_height; +#if SETVIDEOMODE + SDL_WarpMouse(win_half_width, win_half_height); +#else + SDL_WarpMouseInWindow(window, win_half_width, win_half_height); +#endif + } + } else { + SDL_GetRelativeMouseState( &x, &y ); + in_mouse_x = x; + in_mouse_y = y; + } + } + + SDL_GetMouseState(&x, &y); + in_windowmouse_x = x; + in_windowmouse_y = y; + } + + VID_BuildJoyState(&joystate); + VID_ApplyJoyState(&joystate); +} + +///////////////////// +// Message Handling +//// + +#ifdef SDL_R_RESTART +static qboolean sdl_needs_restart; +static void sdl_start(void) +{ +} +static void sdl_shutdown(void) +{ + sdl_needs_restart = false; +} +static void sdl_newmap(void) +{ +} +#endif + +#ifndef __IPHONEOS__ +static keynum_t buttonremap[18] = +{ + K_MOUSE1, + K_MOUSE3, + K_MOUSE2, + K_MWHEELUP, + K_MWHEELDOWN, + K_MOUSE4, + K_MOUSE5, + K_MOUSE6, + K_MOUSE7, + K_MOUSE8, + K_MOUSE9, + K_MOUSE10, + K_MOUSE11, + K_MOUSE12, + K_MOUSE13, + K_MOUSE14, + K_MOUSE15, + K_MOUSE16, +}; +#endif + +#if SETVIDEOMODE +// SDL 1.2 +void Sys_SendKeyEvents( void ) +{ + static qboolean sound_active = true; + int keycode; + SDL_Event event; + + VID_EnableJoystick(true); + + while( SDL_PollEvent( &event ) ) + switch( event.type ) { + case SDL_QUIT: + Sys_Quit(0); + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + keycode = MapKey(event.key.keysym.sym); + if (!VID_JoyBlockEmulatedKeys(keycode)) + Key_Event(keycode, event.key.keysym.unicode, (event.key.state == SDL_PRESSED)); + break; + case SDL_ACTIVEEVENT: + if( event.active.state & SDL_APPACTIVE ) + { + if( event.active.gain ) + vid_hidden = false; + else + vid_hidden = true; + } + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (!vid_touchscreen.integer) + if (event.button.button <= 18) + Key_Event( buttonremap[event.button.button - 1], 0, event.button.state == SDL_PRESSED ); + break; + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + case SDL_JOYAXISMOTION: + case SDL_JOYBALLMOTION: + case SDL_JOYHATMOTION: + break; + case SDL_VIDEOEXPOSE: + break; + case SDL_VIDEORESIZE: + if(vid_resizable.integer < 2) + { + vid.width = event.resize.w; + vid.height = event.resize.h; + screen = SDL_SetVideoMode(vid.width, vid.height, video_bpp, video_flags); + if (vid_softsurface) + { + SDL_FreeSurface(vid_softsurface); + vid_softsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, vid.width, vid.height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + vid.softpixels = (unsigned int *)vid_softsurface->pixels; + SDL_SetAlpha(vid_softsurface, 0, 255); + if (vid.softdepthpixels) + free(vid.softdepthpixels); + vid.softdepthpixels = (unsigned int*)calloc(1, vid.width * vid.height * 4); + } +#ifdef SDL_R_RESTART + // better not call R_Modules_Restart from here directly, as this may wreak havoc... + // so, let's better queue it for next frame + if(!sdl_needs_restart) + { + Cbuf_AddText("\nr_restart\n"); + sdl_needs_restart = true; + } +#endif + } + break; +#if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2 +#else + case SDL_TEXTEDITING: + // unused when SETVIDEOMODE API is used + break; + case SDL_TEXTINPUT: + // this occurs with SETVIDEOMODE but we are not using it + break; +#endif + case SDL_MOUSEMOTION: + break; + default: + Con_DPrintf("Received unrecognized SDL_Event type 0x%x\n", event.type); + break; + } + + // enable/disable sound on focus gain/loss + if ((!vid_hidden && vid_activewindow) || !snd_mutewhenidle.integer) + { + if (!sound_active) + { + S_UnblockSound (); + sound_active = true; + } + } + else + { + if (sound_active) + { + S_BlockSound (); + sound_active = false; + } + } +} + +#else + +// SDL 1.3 +void Sys_SendKeyEvents( void ) +{ + static qboolean sound_active = true; + static qboolean missingunicodehack = true; + int keycode; + int i; + int j; + int unicode; + SDL_Event event; + + VID_EnableJoystick(true); + + while( SDL_PollEvent( &event ) ) + switch( event.type ) { + case SDL_QUIT: + Sys_Quit(0); + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + keycode = MapKey(event.key.keysym.sym); + if (!VID_JoyBlockEmulatedKeys(keycode)) + Key_Event(keycode, 0, (event.key.state == SDL_PRESSED)); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (!vid_touchscreen.integer) + if (event.button.button <= 18) + Key_Event( buttonremap[event.button.button - 1], 0, event.button.state == SDL_PRESSED ); + break; + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + case SDL_JOYAXISMOTION: + case SDL_JOYBALLMOTION: + case SDL_JOYHATMOTION: + break; + case SDL_VIDEOEXPOSE: + break; + case SDL_WINDOWEVENT: + //if (event.window.windowID == window) // how to compare? + { + switch(event.window.event) + { + case SDL_WINDOWEVENT_SHOWN: + vid_hidden = false; + break; + case SDL_WINDOWEVENT_HIDDEN: + vid_hidden = true; + break; + case SDL_WINDOWEVENT_EXPOSED: + break; + case SDL_WINDOWEVENT_MOVED: + break; + case SDL_WINDOWEVENT_RESIZED: + if(vid_resizable.integer < 2) + { + vid.width = event.window.data1; + vid.height = event.window.data2; + if (vid_softsurface) + { + SDL_FreeSurface(vid_softsurface); + vid_softsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, vid.width, vid.height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + vid.softpixels = (unsigned int *)vid_softsurface->pixels; + SDL_SetAlpha(vid_softsurface, 0, 255); + if (vid.softdepthpixels) + free(vid.softdepthpixels); + vid.softdepthpixels = (unsigned int*)calloc(1, vid.width * vid.height * 4); + } +#ifdef SDL_R_RESTART + // better not call R_Modules_Restart from here directly, as this may wreak havoc... + // so, let's better queue it for next frame + if(!sdl_needs_restart) + { + Cbuf_AddText("\nr_restart\n"); + sdl_needs_restart = true; + } +#endif + } + break; + case SDL_WINDOWEVENT_MINIMIZED: + break; + case SDL_WINDOWEVENT_MAXIMIZED: + break; + case SDL_WINDOWEVENT_RESTORED: + break; + case SDL_WINDOWEVENT_ENTER: + break; + case SDL_WINDOWEVENT_LEAVE: + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + vid_hasfocus = true; + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + vid_hasfocus = false; + break; + case SDL_WINDOWEVENT_CLOSE: + Sys_Quit(0); + break; + } + } + break; + case SDL_TEXTEDITING: + // FIXME! this is where composition gets supported + break; + case SDL_TEXTINPUT: + // we have some characters to parse + missingunicodehack = false; + { + unicode = 0; + for (i = 0;event.text.text[i];) + { + unicode = event.text.text[i++]; + if (unicode & 0x80) + { + // UTF-8 character + // strip high bits (we could count these to validate character length but we don't) + for (j = 0x80;unicode & j;j >>= 1) + unicode ^= j; + for (;(event.text.text[i] & 0xC0) == 0x80;i++) + unicode = (unicode << 6) | (event.text.text[i] & 0x3F); + // low characters are invalid and could be bad, so replace them + if (unicode < 0x80) + unicode = '?'; // we could use 0xFFFD instead, the unicode substitute character + } + //Con_DPrintf("SDL_TEXTINPUT: K_TEXT %i \n", unicode); + Key_Event(K_TEXT, unicode, true); + Key_Event(K_TEXT, unicode, false); + } + } + break; + case SDL_MOUSEMOTION: + break; + case SDL_FINGERDOWN: + Con_DPrintf("SDL_FINGERDOWN for finger %i\n", (int)event.tfinger.fingerId); + for (i = 0;i < MAXFINGERS-1;i++) + { + if (!multitouch[i][0]) + { + multitouch[i][0] = event.tfinger.fingerId; + multitouch[i][1] = event.tfinger.x; + multitouch[i][2] = event.tfinger.y; + // TODO: use event.tfinger.pressure? + break; + } + } + if (i == MAXFINGERS-1) + Con_DPrintf("Too many fingers at once!\n"); + break; + case SDL_FINGERUP: + Con_DPrintf("SDL_FINGERUP for finger %i\n", (int)event.tfinger.fingerId); + for (i = 0;i < MAXFINGERS-1;i++) + { + if (multitouch[i][0] == event.tfinger.fingerId) + { + multitouch[i][0] = 0; + break; + } + } + if (i == MAXFINGERS-1) + Con_DPrintf("No SDL_FINGERDOWN event matches this SDL_FINGERMOTION event\n"); + break; + case SDL_FINGERMOTION: + Con_DPrintf("SDL_FINGERMOTION for finger %i\n", (int)event.tfinger.fingerId); + for (i = 0;i < MAXFINGERS-1;i++) + { + if (multitouch[i][0] == event.tfinger.fingerId) + { + multitouch[i][1] = event.tfinger.x; + multitouch[i][2] = event.tfinger.y; + break; + } + } + if (i == MAXFINGERS-1) + Con_DPrintf("No SDL_FINGERDOWN event matches this SDL_FINGERMOTION event\n"); + break; + case SDL_TOUCHBUTTONDOWN: + // not sure what to do with this... + break; + case SDL_TOUCHBUTTONUP: + // not sure what to do with this... + break; + default: + Con_DPrintf("Received unrecognized SDL_Event type 0x%x\n", event.type); + break; + } + + // enable/disable sound on focus gain/loss + if ((!vid_hidden && vid_activewindow) || !snd_mutewhenidle.integer) + { + if (!sound_active) + { + S_UnblockSound (); + sound_active = true; + } + } + else + { + if (sound_active) + { + S_BlockSound (); + sound_active = false; + } + } +} +#endif + +///////////////// +// Video system +//// + +#ifdef USE_GLES2 +#ifdef __IPHONEOS__ +#include +#else +#include +#endif + +GLboolean wrapglIsBuffer(GLuint buffer) {return glIsBuffer(buffer);} +GLboolean wrapglIsEnabled(GLenum cap) {return glIsEnabled(cap);} +GLboolean wrapglIsFramebuffer(GLuint framebuffer) {return glIsFramebuffer(framebuffer);} +//GLboolean wrapglIsQuery(GLuint qid) {return glIsQuery(qid);} +GLboolean wrapglIsRenderbuffer(GLuint renderbuffer) {return glIsRenderbuffer(renderbuffer);} +//GLboolean wrapglUnmapBuffer(GLenum target) {return glUnmapBuffer(target);} +GLenum wrapglCheckFramebufferStatus(GLenum target) {return glCheckFramebufferStatus(target);} +GLenum wrapglGetError(void) {return glGetError();} +GLuint wrapglCreateProgram(void) {return glCreateProgram();} +GLuint wrapglCreateShader(GLenum shaderType) {return glCreateShader(shaderType);} +//GLuint wrapglGetHandle(GLenum pname) {return glGetHandle(pname);} +GLint wrapglGetAttribLocation(GLuint programObj, const GLchar *name) {return glGetAttribLocation(programObj, name);} +GLint wrapglGetUniformLocation(GLuint programObj, const GLchar *name) {return glGetUniformLocation(programObj, name);} +//GLvoid* wrapglMapBuffer(GLenum target, GLenum access) {return glMapBuffer(target, access);} +const GLubyte* wrapglGetString(GLenum name) {return glGetString(name);} +void wrapglActiveStencilFace(GLenum e) {Con_Printf("glActiveStencilFace(e)\n");} +void wrapglActiveTexture(GLenum e) {glActiveTexture(e);} +void wrapglAlphaFunc(GLenum func, GLclampf ref) {Con_Printf("glAlphaFunc(func, ref)\n");} +void wrapglArrayElement(GLint i) {Con_Printf("glArrayElement(i)\n");} +void wrapglAttachShader(GLuint containerObj, GLuint obj) {glAttachShader(containerObj, obj);} +//void wrapglBegin(GLenum mode) {Con_Printf("glBegin(mode)\n");} +//void wrapglBeginQuery(GLenum target, GLuint qid) {glBeginQuery(target, qid);} +void wrapglBindAttribLocation(GLuint programObj, GLuint index, const GLchar *name) {glBindAttribLocation(programObj, index, name);} +void wrapglBindFragDataLocation(GLuint programObj, GLuint index, const GLchar *name) {glBindFragDataLocation(programObj, index, name);} +void wrapglBindBuffer(GLenum target, GLuint buffer) {glBindBuffer(target, buffer);} +void wrapglBindFramebuffer(GLenum target, GLuint framebuffer) {glBindFramebuffer(target, framebuffer);} +void wrapglBindRenderbuffer(GLenum target, GLuint renderbuffer) {glBindRenderbuffer(target, renderbuffer);} +void wrapglBindTexture(GLenum target, GLuint texture) {glBindTexture(target, texture);} +void wrapglBlendEquation(GLenum e) {glBlendEquation(e);} +void wrapglBlendFunc(GLenum sfactor, GLenum dfactor) {glBlendFunc(sfactor, dfactor);} +void wrapglBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage) {glBufferData(target, size, data, usage);} +void wrapglBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data) {glBufferSubData(target, offset, size, data);} +void wrapglClear(GLbitfield mask) {glClear(mask);} +void wrapglClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) {glClearColor(red, green, blue, alpha);} +void wrapglClearDepth(GLclampd depth) {glClearDepthf((float)depth);} +void wrapglClearStencil(GLint s) {glClearStencil(s);} +void wrapglClientActiveTexture(GLenum target) {Con_Printf("glClientActiveTexture(target)\n");} +void wrapglColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {Con_Printf("glColor4f(red, green, blue, alpha)\n");} +void wrapglColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) {Con_Printf("glColor4ub(red, green, blue, alpha)\n");} +void wrapglColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {glColorMask(red, green, blue, alpha);} +void wrapglColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) {Con_Printf("glColorPointer(size, type, stride, ptr)\n");} +void wrapglCompileShader(GLuint shaderObj) {glCompileShader(shaderObj);} +void wrapglCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data) {glCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data);} +void wrapglCompressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data) {Con_Printf("glCompressedTexImage3D(target, level, internalformat, width, height, depth, border, imageSize, data)\n");} +void wrapglCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data) {glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data);} +void wrapglCompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data) {Con_Printf("glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data)\n");} +void wrapglCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) {glCopyTexImage2D(target, level, internalformat, x, y, width, height, border);} +void wrapglCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) {glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);} +void wrapglCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) {Con_Printf("glCopyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height)\n");} +void wrapglCullFace(GLenum mode) {glCullFace(mode);} +void wrapglDeleteBuffers(GLsizei n, const GLuint *buffers) {glDeleteBuffers(n, buffers);} +void wrapglDeleteFramebuffers(GLsizei n, const GLuint *framebuffers) {glDeleteFramebuffers(n, framebuffers);} +void wrapglDeleteShader(GLuint obj) {glDeleteShader(obj);} +void wrapglDeleteProgram(GLuint obj) {glDeleteProgram(obj);} +//void wrapglDeleteQueries(GLsizei n, const GLuint *ids) {glDeleteQueries(n, ids);} +void wrapglDeleteRenderbuffers(GLsizei n, const GLuint *renderbuffers) {glDeleteRenderbuffers(n, renderbuffers);} +void wrapglDeleteTextures(GLsizei n, const GLuint *textures) {glDeleteTextures(n, textures);} +void wrapglDepthFunc(GLenum func) {glDepthFunc(func);} +void wrapglDepthMask(GLboolean flag) {glDepthMask(flag);} +void wrapglDepthRange(GLclampd near_val, GLclampd far_val) {glDepthRangef((float)near_val, (float)far_val);} +void wrapglDetachShader(GLuint containerObj, GLuint attachedObj) {glDetachShader(containerObj, attachedObj);} +void wrapglDisable(GLenum cap) {glDisable(cap);} +void wrapglDisableClientState(GLenum cap) {Con_Printf("glDisableClientState(cap)\n");} +void wrapglDisableVertexAttribArray(GLuint index) {glDisableVertexAttribArray(index);} +void wrapglDrawArrays(GLenum mode, GLint first, GLsizei count) {glDrawArrays(mode, first, count);} +void wrapglDrawBuffer(GLenum mode) {Con_Printf("glDrawBuffer(mode)\n");} +void wrapglDrawBuffers(GLsizei n, const GLenum *bufs) {Con_Printf("glDrawBuffers(n, bufs)\n");} +void wrapglDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) {glDrawElements(mode, count, type, indices);} +//void wrapglDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices) {glDrawRangeElements(mode, start, end, count, type, indices);} +//void wrapglDrawRangeElementsEXT(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices) {glDrawRangeElements(mode, start, end, count, type, indices);} +void wrapglEnable(GLenum cap) {glEnable(cap);} +void wrapglEnableClientState(GLenum cap) {Con_Printf("glEnableClientState(cap)\n");} +void wrapglEnableVertexAttribArray(GLuint index) {glEnableVertexAttribArray(index);} +//void wrapglEnd(void) {Con_Printf("glEnd()\n");} +//void wrapglEndQuery(GLenum target) {glEndQuery(target);} +void wrapglFinish(void) {glFinish();} +void wrapglFlush(void) {glFlush();} +void wrapglFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) {glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer);} +void wrapglFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) {glFramebufferTexture2D(target, attachment, textarget, texture, level);} +void wrapglFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) {Con_Printf("glFramebufferTexture3D()\n");} +void wrapglGenBuffers(GLsizei n, GLuint *buffers) {glGenBuffers(n, buffers);} +void wrapglGenFramebuffers(GLsizei n, GLuint *framebuffers) {glGenFramebuffers(n, framebuffers);} +//void wrapglGenQueries(GLsizei n, GLuint *ids) {glGenQueries(n, ids);} +void wrapglGenRenderbuffers(GLsizei n, GLuint *renderbuffers) {glGenRenderbuffers(n, renderbuffers);} +void wrapglGenTextures(GLsizei n, GLuint *textures) {glGenTextures(n, textures);} +void wrapglGenerateMipmap(GLenum target) {glGenerateMipmap(target);} +void wrapglGetActiveAttrib(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name) {glGetActiveAttrib(programObj, index, maxLength, length, size, type, name);} +void wrapglGetActiveUniform(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name) {glGetActiveUniform(programObj, index, maxLength, length, size, type, name);} +void wrapglGetAttachedShaders(GLuint containerObj, GLsizei maxCount, GLsizei *count, GLuint *obj) {glGetAttachedShaders(containerObj, maxCount, count, obj);} +void wrapglGetBooleanv(GLenum pname, GLboolean *params) {glGetBooleanv(pname, params);} +void wrapglGetCompressedTexImage(GLenum target, GLint lod, void *img) {Con_Printf("glGetCompressedTexImage(target, lod, img)\n");} +void wrapglGetDoublev(GLenum pname, GLdouble *params) {Con_Printf("glGetDoublev(pname, params)\n");} +void wrapglGetFloatv(GLenum pname, GLfloat *params) {glGetFloatv(pname, params);} +void wrapglGetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint *params) {glGetFramebufferAttachmentParameteriv(target, attachment, pname, params);} +void wrapglGetShaderInfoLog(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog) {glGetShaderInfoLog(obj, maxLength, length, infoLog);} +void wrapglGetProgramInfoLog(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog) {glGetProgramInfoLog(obj, maxLength, length, infoLog);} +void wrapglGetIntegerv(GLenum pname, GLint *params) {glGetIntegerv(pname, params);} +void wrapglGetShaderiv(GLuint obj, GLenum pname, GLint *params) {glGetShaderiv(obj, pname, params);} +void wrapglGetProgramiv(GLuint obj, GLenum pname, GLint *params) {glGetProgramiv(obj, pname, params);} +//void wrapglGetQueryObjectiv(GLuint qid, GLenum pname, GLint *params) {glGetQueryObjectiv(qid, pname, params);} +//void wrapglGetQueryObjectuiv(GLuint qid, GLenum pname, GLuint *params) {glGetQueryObjectuiv(qid, pname, params);} +//void wrapglGetQueryiv(GLenum target, GLenum pname, GLint *params) {glGetQueryiv(target, pname, params);} +void wrapglGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint *params) {glGetRenderbufferParameteriv(target, pname, params);} +void wrapglGetShaderSource(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *source) {glGetShaderSource(obj, maxLength, length, source);} +void wrapglGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels) {Con_Printf("glGetTexImage(target, level, format, type, pixels)\n");} +void wrapglGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params) {Con_Printf("glGetTexLevelParameterfv(target, level, pname, params)\n");} +void wrapglGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) {Con_Printf("glGetTexLevelParameteriv(target, level, pname, params)\n");} +void wrapglGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params) {glGetTexParameterfv(target, pname, params);} +void wrapglGetTexParameteriv(GLenum target, GLenum pname, GLint *params) {glGetTexParameteriv(target, pname, params);} +void wrapglGetUniformfv(GLuint programObj, GLint location, GLfloat *params) {glGetUniformfv(programObj, location, params);} +void wrapglGetUniformiv(GLuint programObj, GLint location, GLint *params) {glGetUniformiv(programObj, location, params);} +void wrapglHint(GLenum target, GLenum mode) {glHint(target, mode);} +void wrapglLineWidth(GLfloat width) {glLineWidth(width);} +void wrapglLinkProgram(GLuint programObj) {glLinkProgram(programObj);} +void wrapglLoadIdentity(void) {Con_Printf("glLoadIdentity()\n");} +void wrapglLoadMatrixf(const GLfloat *m) {Con_Printf("glLoadMatrixf(m)\n");} +void wrapglMatrixMode(GLenum mode) {Con_Printf("glMatrixMode(mode)\n");} +void wrapglMultiTexCoord1f(GLenum target, GLfloat s) {Con_Printf("glMultiTexCoord1f(target, s)\n");} +void wrapglMultiTexCoord2f(GLenum target, GLfloat s, GLfloat t) {Con_Printf("glMultiTexCoord2f(target, s, t)\n");} +void wrapglMultiTexCoord3f(GLenum target, GLfloat s, GLfloat t, GLfloat r) {Con_Printf("glMultiTexCoord3f(target, s, t, r)\n");} +void wrapglMultiTexCoord4f(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q) {Con_Printf("glMultiTexCoord4f(target, s, t, r, q)\n");} +void wrapglNormalPointer(GLenum type, GLsizei stride, const GLvoid *ptr) {Con_Printf("glNormalPointer(type, stride, ptr)\n");} +void wrapglPixelStorei(GLenum pname, GLint param) {glPixelStorei(pname, param);} +void wrapglPointSize(GLfloat size) {Con_Printf("glPointSize(size)\n");} +//void wrapglPolygonMode(GLenum face, GLenum mode) {Con_Printf("glPolygonMode(face, mode)\n");} +void wrapglPolygonOffset(GLfloat factor, GLfloat units) {glPolygonOffset(factor, units);} +void wrapglPolygonStipple(const GLubyte *mask) {Con_Printf("glPolygonStipple(mask)\n");} +void wrapglReadBuffer(GLenum mode) {Con_Printf("glReadBuffer(mode)\n");} +void wrapglReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) {glReadPixels(x, y, width, height, format, type, pixels);} +void wrapglRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height) {glRenderbufferStorage(target, internalformat, width, height);} +void wrapglScissor(GLint x, GLint y, GLsizei width, GLsizei height) {glScissor(x, y, width, height);} +void wrapglShaderSource(GLuint shaderObj, GLsizei count, const GLchar **string, const GLint *length) {glShaderSource(shaderObj, count, string, length);} +void wrapglStencilFunc(GLenum func, GLint ref, GLuint mask) {glStencilFunc(func, ref, mask);} +void wrapglStencilFuncSeparate(GLenum func1, GLenum func2, GLint ref, GLuint mask) {Con_Printf("glStencilFuncSeparate(func1, func2, ref, mask)\n");} +void wrapglStencilMask(GLuint mask) {glStencilMask(mask);} +void wrapglStencilOp(GLenum fail, GLenum zfail, GLenum zpass) {glStencilOp(fail, zfail, zpass);} +void wrapglStencilOpSeparate(GLenum e1, GLenum e2, GLenum e3, GLenum e4) {Con_Printf("glStencilOpSeparate(e1, e2, e3, e4)\n");} +void wrapglTexCoord1f(GLfloat s) {Con_Printf("glTexCoord1f(s)\n");} +void wrapglTexCoord2f(GLfloat s, GLfloat t) {Con_Printf("glTexCoord2f(s, t)\n");} +void wrapglTexCoord3f(GLfloat s, GLfloat t, GLfloat r) {Con_Printf("glTexCoord3f(s, t, r)\n");} +void wrapglTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) {Con_Printf("glTexCoord4f(s, t, r, q)\n");} +void wrapglTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) {Con_Printf("glTexCoordPointer(size, type, stride, ptr)\n");} +void wrapglTexEnvf(GLenum target, GLenum pname, GLfloat param) {Con_Printf("glTexEnvf(target, pname, param)\n");} +void wrapglTexEnvfv(GLenum target, GLenum pname, const GLfloat *params) {Con_Printf("glTexEnvfv(target, pname, params)\n");} +void wrapglTexEnvi(GLenum target, GLenum pname, GLint param) {Con_Printf("glTexEnvi(target, pname, param)\n");} +void wrapglTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) {glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);} +void wrapglTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels) {Con_Printf("glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels)\n");} +void wrapglTexParameterf(GLenum target, GLenum pname, GLfloat param) {glTexParameterf(target, pname, param);} +void wrapglTexParameterfv(GLenum target, GLenum pname, GLfloat *params) {glTexParameterfv(target, pname, params);} +void wrapglTexParameteri(GLenum target, GLenum pname, GLint param) {glTexParameteri(target, pname, param);} +void wrapglTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) {glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);} +void wrapglTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels) {Con_Printf("glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels)\n");} +void wrapglUniform1f(GLint location, GLfloat v0) {glUniform1f(location, v0);} +void wrapglUniform1fv(GLint location, GLsizei count, const GLfloat *value) {glUniform1fv(location, count, value);} +void wrapglUniform1i(GLint location, GLint v0) {glUniform1i(location, v0);} +void wrapglUniform1iv(GLint location, GLsizei count, const GLint *value) {glUniform1iv(location, count, value);} +void wrapglUniform2f(GLint location, GLfloat v0, GLfloat v1) {glUniform2f(location, v0, v1);} +void wrapglUniform2fv(GLint location, GLsizei count, const GLfloat *value) {glUniform2fv(location, count, value);} +void wrapglUniform2i(GLint location, GLint v0, GLint v1) {glUniform2i(location, v0, v1);} +void wrapglUniform2iv(GLint location, GLsizei count, const GLint *value) {glUniform2iv(location, count, value);} +void wrapglUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) {glUniform3f(location, v0, v1, v2);} +void wrapglUniform3fv(GLint location, GLsizei count, const GLfloat *value) {glUniform3fv(location, count, value);} +void wrapglUniform3i(GLint location, GLint v0, GLint v1, GLint v2) {glUniform3i(location, v0, v1, v2);} +void wrapglUniform3iv(GLint location, GLsizei count, const GLint *value) {glUniform3iv(location, count, value);} +void wrapglUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {glUniform4f(location, v0, v1, v2, v3);} +void wrapglUniform4fv(GLint location, GLsizei count, const GLfloat *value) {glUniform4fv(location, count, value);} +void wrapglUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3) {glUniform4i(location, v0, v1, v2, v3);} +void wrapglUniform4iv(GLint location, GLsizei count, const GLint *value) {glUniform4iv(location, count, value);} +void wrapglUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) {glUniformMatrix2fv(location, count, transpose, value);} +void wrapglUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) {glUniformMatrix3fv(location, count, transpose, value);} +void wrapglUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) {glUniformMatrix4fv(location, count, transpose, value);} +void wrapglUseProgram(GLuint programObj) {glUseProgram(programObj);} +void wrapglValidateProgram(GLuint programObj) {glValidateProgram(programObj);} +void wrapglVertex2f(GLfloat x, GLfloat y) {Con_Printf("glVertex2f(x, y)\n");} +void wrapglVertex3f(GLfloat x, GLfloat y, GLfloat z) {Con_Printf("glVertex3f(x, y, z)\n");} +void wrapglVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) {Con_Printf("glVertex4f(x, y, z, w)\n");} +void wrapglVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer) {glVertexAttribPointer(index, size, type, normalized, stride, pointer);} +void wrapglVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) {Con_Printf("glVertexPointer(size, type, stride, ptr)\n");} +void wrapglViewport(GLint x, GLint y, GLsizei width, GLsizei height) {glViewport(x, y, width, height);} +void wrapglVertexAttrib1f(GLuint index, GLfloat v0) {glVertexAttrib1f(index, v0);} +//void wrapglVertexAttrib1s(GLuint index, GLshort v0) {glVertexAttrib1s(index, v0);} +//void wrapglVertexAttrib1d(GLuint index, GLdouble v0) {glVertexAttrib1d(index, v0);} +void wrapglVertexAttrib2f(GLuint index, GLfloat v0, GLfloat v1) {glVertexAttrib2f(index, v0, v1);} +//void wrapglVertexAttrib2s(GLuint index, GLshort v0, GLshort v1) {glVertexAttrib2s(index, v0, v1);} +//void wrapglVertexAttrib2d(GLuint index, GLdouble v0, GLdouble v1) {glVertexAttrib2d(index, v0, v1);} +void wrapglVertexAttrib3f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2) {glVertexAttrib3f(index, v0, v1, v2);} +//void wrapglVertexAttrib3s(GLuint index, GLshort v0, GLshort v1, GLshort v2) {glVertexAttrib3s(index, v0, v1, v2);} +//void wrapglVertexAttrib3d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2) {glVertexAttrib3d(index, v0, v1, v2);} +void wrapglVertexAttrib4f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {glVertexAttrib4f(index, v0, v1, v2, v3);} +//void wrapglVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3) {glVertexAttrib4s(index, v0, v1, v2, v3);} +//void wrapglVertexAttrib4d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) {glVertexAttrib4d(index, v0, v1, v2, v3);} +//void wrapglVertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w) {glVertexAttrib4Nub(index, x, y, z, w);} +void wrapglVertexAttrib1fv(GLuint index, const GLfloat *v) {glVertexAttrib1fv(index, v);} +//void wrapglVertexAttrib1sv(GLuint index, const GLshort *v) {glVertexAttrib1sv(index, v);} +//void wrapglVertexAttrib1dv(GLuint index, const GLdouble *v) {glVertexAttrib1dv(index, v);} +void wrapglVertexAttrib2fv(GLuint index, const GLfloat *v) {glVertexAttrib2fv(index, v);} +//void wrapglVertexAttrib2sv(GLuint index, const GLshort *v) {glVertexAttrib2sv(index, v);} +//void wrapglVertexAttrib2dv(GLuint index, const GLdouble *v) {glVertexAttrib2dv(index, v);} +void wrapglVertexAttrib3fv(GLuint index, const GLfloat *v) {glVertexAttrib3fv(index, v);} +//void wrapglVertexAttrib3sv(GLuint index, const GLshort *v) {glVertexAttrib3sv(index, v);} +//void wrapglVertexAttrib3dv(GLuint index, const GLdouble *v) {glVertexAttrib3dv(index, v);} +void wrapglVertexAttrib4fv(GLuint index, const GLfloat *v) {glVertexAttrib4fv(index, v);} +//void wrapglVertexAttrib4sv(GLuint index, const GLshort *v) {glVertexAttrib4sv(index, v);} +//void wrapglVertexAttrib4dv(GLuint index, const GLdouble *v) {glVertexAttrib4dv(index, v);} +//void wrapglVertexAttrib4iv(GLuint index, const GLint *v) {glVertexAttrib4iv(index, v);} +//void wrapglVertexAttrib4bv(GLuint index, const GLbyte *v) {glVertexAttrib4bv(index, v);} +//void wrapglVertexAttrib4ubv(GLuint index, const GLubyte *v) {glVertexAttrib4ubv(index, v);} +//void wrapglVertexAttrib4usv(GLuint index, const GLushort *v) {glVertexAttrib4usv(index, GLushort v);} +//void wrapglVertexAttrib4uiv(GLuint index, const GLuint *v) {glVertexAttrib4uiv(index, v);} +//void wrapglVertexAttrib4Nbv(GLuint index, const GLbyte *v) {glVertexAttrib4Nbv(index, v);} +//void wrapglVertexAttrib4Nsv(GLuint index, const GLshort *v) {glVertexAttrib4Nsv(index, v);} +//void wrapglVertexAttrib4Niv(GLuint index, const GLint *v) {glVertexAttrib4Niv(index, v);} +//void wrapglVertexAttrib4Nubv(GLuint index, const GLubyte *v) {glVertexAttrib4Nubv(index, v);} +//void wrapglVertexAttrib4Nusv(GLuint index, const GLushort *v) {glVertexAttrib4Nusv(index, GLushort v);} +//void wrapglVertexAttrib4Nuiv(GLuint index, const GLuint *v) {glVertexAttrib4Nuiv(index, v);} +//void wrapglGetVertexAttribdv(GLuint index, GLenum pname, GLdouble *params) {glGetVertexAttribdv(index, pname, params);} +void wrapglGetVertexAttribfv(GLuint index, GLenum pname, GLfloat *params) {glGetVertexAttribfv(index, pname, params);} +void wrapglGetVertexAttribiv(GLuint index, GLenum pname, GLint *params) {glGetVertexAttribiv(index, pname, params);} +void wrapglGetVertexAttribPointerv(GLuint index, GLenum pname, GLvoid **pointer) {glGetVertexAttribPointerv(index, pname, pointer);} + +void GLES_Init(void) +{ + qglIsBufferARB = wrapglIsBuffer; + qglIsEnabled = wrapglIsEnabled; + qglIsFramebufferEXT = wrapglIsFramebuffer; +// qglIsQueryARB = wrapglIsQuery; + qglIsRenderbufferEXT = wrapglIsRenderbuffer; +// qglUnmapBufferARB = wrapglUnmapBuffer; + qglCheckFramebufferStatusEXT = wrapglCheckFramebufferStatus; + qglGetError = wrapglGetError; + qglCreateProgram = wrapglCreateProgram; + qglCreateShader = wrapglCreateShader; +// qglGetHandleARB = wrapglGetHandle; + qglGetAttribLocation = wrapglGetAttribLocation; + qglGetUniformLocation = wrapglGetUniformLocation; +// qglMapBufferARB = wrapglMapBuffer; + qglGetString = wrapglGetString; +// qglActiveStencilFaceEXT = wrapglActiveStencilFace; + qglActiveTexture = wrapglActiveTexture; + qglAlphaFunc = wrapglAlphaFunc; + qglArrayElement = wrapglArrayElement; + qglAttachShader = wrapglAttachShader; +// qglBegin = wrapglBegin; +// qglBeginQueryARB = wrapglBeginQuery; + qglBindAttribLocation = wrapglBindAttribLocation; + qglBindFragDataLocation = wrapglBindFragDataLocation; + qglBindBufferARB = wrapglBindBuffer; + qglBindFramebufferEXT = wrapglBindFramebuffer; + qglBindRenderbufferEXT = wrapglBindRenderbuffer; + qglBindTexture = wrapglBindTexture; + qglBlendEquationEXT = wrapglBlendEquation; + qglBlendFunc = wrapglBlendFunc; + qglBufferDataARB = wrapglBufferData; + qglBufferSubDataARB = wrapglBufferSubData; + qglClear = wrapglClear; + qglClearColor = wrapglClearColor; + qglClearDepth = wrapglClearDepth; + qglClearStencil = wrapglClearStencil; + qglClientActiveTexture = wrapglClientActiveTexture; + qglColor4f = wrapglColor4f; + qglColor4ub = wrapglColor4ub; + qglColorMask = wrapglColorMask; + qglColorPointer = wrapglColorPointer; + qglCompileShader = wrapglCompileShader; + qglCompressedTexImage2DARB = wrapglCompressedTexImage2D; + qglCompressedTexImage3DARB = wrapglCompressedTexImage3D; + qglCompressedTexSubImage2DARB = wrapglCompressedTexSubImage2D; + qglCompressedTexSubImage3DARB = wrapglCompressedTexSubImage3D; + qglCopyTexImage2D = wrapglCopyTexImage2D; + qglCopyTexSubImage2D = wrapglCopyTexSubImage2D; + qglCopyTexSubImage3D = wrapglCopyTexSubImage3D; + qglCullFace = wrapglCullFace; + qglDeleteBuffersARB = wrapglDeleteBuffers; + qglDeleteFramebuffersEXT = wrapglDeleteFramebuffers; + qglDeleteProgram = wrapglDeleteProgram; + qglDeleteShader = wrapglDeleteShader; +// qglDeleteQueriesARB = wrapglDeleteQueries; + qglDeleteRenderbuffersEXT = wrapglDeleteRenderbuffers; + qglDeleteTextures = wrapglDeleteTextures; + qglDepthFunc = wrapglDepthFunc; + qglDepthMask = wrapglDepthMask; + qglDepthRange = wrapglDepthRange; + qglDetachShader = wrapglDetachShader; + qglDisable = wrapglDisable; + qglDisableClientState = wrapglDisableClientState; + qglDisableVertexAttribArray = wrapglDisableVertexAttribArray; + qglDrawArrays = wrapglDrawArrays; +// qglDrawBuffer = wrapglDrawBuffer; +// qglDrawBuffersARB = wrapglDrawBuffers; + qglDrawElements = wrapglDrawElements; +// qglDrawRangeElements = wrapglDrawRangeElements; + qglEnable = wrapglEnable; + qglEnableClientState = wrapglEnableClientState; + qglEnableVertexAttribArray = wrapglEnableVertexAttribArray; +// qglEnd = wrapglEnd; +// qglEndQueryARB = wrapglEndQuery; + qglFinish = wrapglFinish; + qglFlush = wrapglFlush; + qglFramebufferRenderbufferEXT = wrapglFramebufferRenderbuffer; + qglFramebufferTexture2DEXT = wrapglFramebufferTexture2D; + qglFramebufferTexture3DEXT = wrapglFramebufferTexture3D; + qglGenBuffersARB = wrapglGenBuffers; + qglGenFramebuffersEXT = wrapglGenFramebuffers; +// qglGenQueriesARB = wrapglGenQueries; + qglGenRenderbuffersEXT = wrapglGenRenderbuffers; + qglGenTextures = wrapglGenTextures; + qglGenerateMipmapEXT = wrapglGenerateMipmap; + qglGetActiveAttrib = wrapglGetActiveAttrib; + qglGetActiveUniform = wrapglGetActiveUniform; + qglGetAttachedShaders = wrapglGetAttachedShaders; + qglGetBooleanv = wrapglGetBooleanv; +// qglGetCompressedTexImageARB = wrapglGetCompressedTexImage; + qglGetDoublev = wrapglGetDoublev; + qglGetFloatv = wrapglGetFloatv; + qglGetFramebufferAttachmentParameterivEXT = wrapglGetFramebufferAttachmentParameteriv; + qglGetProgramInfoLog = wrapglGetProgramInfoLog; + qglGetShaderInfoLog = wrapglGetShaderInfoLog; + qglGetIntegerv = wrapglGetIntegerv; + qglGetShaderiv = wrapglGetShaderiv; + qglGetProgramiv = wrapglGetProgramiv; +// qglGetQueryObjectivARB = wrapglGetQueryObjectiv; +// qglGetQueryObjectuivARB = wrapglGetQueryObjectuiv; +// qglGetQueryivARB = wrapglGetQueryiv; + qglGetRenderbufferParameterivEXT = wrapglGetRenderbufferParameteriv; + qglGetShaderSource = wrapglGetShaderSource; + qglGetTexImage = wrapglGetTexImage; + qglGetTexLevelParameterfv = wrapglGetTexLevelParameterfv; + qglGetTexLevelParameteriv = wrapglGetTexLevelParameteriv; + qglGetTexParameterfv = wrapglGetTexParameterfv; + qglGetTexParameteriv = wrapglGetTexParameteriv; + qglGetUniformfv = wrapglGetUniformfv; + qglGetUniformiv = wrapglGetUniformiv; + qglHint = wrapglHint; + qglLineWidth = wrapglLineWidth; + qglLinkProgram = wrapglLinkProgram; + qglLoadIdentity = wrapglLoadIdentity; + qglLoadMatrixf = wrapglLoadMatrixf; + qglMatrixMode = wrapglMatrixMode; + qglMultiTexCoord1f = wrapglMultiTexCoord1f; + qglMultiTexCoord2f = wrapglMultiTexCoord2f; + qglMultiTexCoord3f = wrapglMultiTexCoord3f; + qglMultiTexCoord4f = wrapglMultiTexCoord4f; + qglNormalPointer = wrapglNormalPointer; + qglPixelStorei = wrapglPixelStorei; + qglPointSize = wrapglPointSize; +// qglPolygonMode = wrapglPolygonMode; + qglPolygonOffset = wrapglPolygonOffset; +// qglPolygonStipple = wrapglPolygonStipple; + qglReadBuffer = wrapglReadBuffer; + qglReadPixels = wrapglReadPixels; + qglRenderbufferStorageEXT = wrapglRenderbufferStorage; + qglScissor = wrapglScissor; + qglShaderSource = wrapglShaderSource; + qglStencilFunc = wrapglStencilFunc; + qglStencilFuncSeparate = wrapglStencilFuncSeparate; + qglStencilMask = wrapglStencilMask; + qglStencilOp = wrapglStencilOp; + qglStencilOpSeparate = wrapglStencilOpSeparate; + qglTexCoord1f = wrapglTexCoord1f; + qglTexCoord2f = wrapglTexCoord2f; + qglTexCoord3f = wrapglTexCoord3f; + qglTexCoord4f = wrapglTexCoord4f; + qglTexCoordPointer = wrapglTexCoordPointer; + qglTexEnvf = wrapglTexEnvf; + qglTexEnvfv = wrapglTexEnvfv; + qglTexEnvi = wrapglTexEnvi; + qglTexImage2D = wrapglTexImage2D; + qglTexImage3D = wrapglTexImage3D; + qglTexParameterf = wrapglTexParameterf; + qglTexParameterfv = wrapglTexParameterfv; + qglTexParameteri = wrapglTexParameteri; + qglTexSubImage2D = wrapglTexSubImage2D; + qglTexSubImage3D = wrapglTexSubImage3D; + qglUniform1f = wrapglUniform1f; + qglUniform1fv = wrapglUniform1fv; + qglUniform1i = wrapglUniform1i; + qglUniform1iv = wrapglUniform1iv; + qglUniform2f = wrapglUniform2f; + qglUniform2fv = wrapglUniform2fv; + qglUniform2i = wrapglUniform2i; + qglUniform2iv = wrapglUniform2iv; + qglUniform3f = wrapglUniform3f; + qglUniform3fv = wrapglUniform3fv; + qglUniform3i = wrapglUniform3i; + qglUniform3iv = wrapglUniform3iv; + qglUniform4f = wrapglUniform4f; + qglUniform4fv = wrapglUniform4fv; + qglUniform4i = wrapglUniform4i; + qglUniform4iv = wrapglUniform4iv; + qglUniformMatrix2fv = wrapglUniformMatrix2fv; + qglUniformMatrix3fv = wrapglUniformMatrix3fv; + qglUniformMatrix4fv = wrapglUniformMatrix4fv; + qglUseProgram = wrapglUseProgram; + qglValidateProgram = wrapglValidateProgram; + qglVertex2f = wrapglVertex2f; + qglVertex3f = wrapglVertex3f; + qglVertex4f = wrapglVertex4f; + qglVertexAttribPointer = wrapglVertexAttribPointer; + qglVertexPointer = wrapglVertexPointer; + qglViewport = wrapglViewport; + qglVertexAttrib1f = wrapglVertexAttrib1f; +// qglVertexAttrib1s = wrapglVertexAttrib1s; +// qglVertexAttrib1d = wrapglVertexAttrib1d; + qglVertexAttrib2f = wrapglVertexAttrib2f; +// qglVertexAttrib2s = wrapglVertexAttrib2s; +// qglVertexAttrib2d = wrapglVertexAttrib2d; + qglVertexAttrib3f = wrapglVertexAttrib3f; +// qglVertexAttrib3s = wrapglVertexAttrib3s; +// qglVertexAttrib3d = wrapglVertexAttrib3d; + qglVertexAttrib4f = wrapglVertexAttrib4f; +// qglVertexAttrib4s = wrapglVertexAttrib4s; +// qglVertexAttrib4d = wrapglVertexAttrib4d; +// qglVertexAttrib4Nub = wrapglVertexAttrib4Nub; + qglVertexAttrib1fv = wrapglVertexAttrib1fv; +// qglVertexAttrib1sv = wrapglVertexAttrib1sv; +// qglVertexAttrib1dv = wrapglVertexAttrib1dv; + qglVertexAttrib2fv = wrapglVertexAttrib2fv; +// qglVertexAttrib2sv = wrapglVertexAttrib2sv; +// qglVertexAttrib2dv = wrapglVertexAttrib2dv; + qglVertexAttrib3fv = wrapglVertexAttrib3fv; +// qglVertexAttrib3sv = wrapglVertexAttrib3sv; +// qglVertexAttrib3dv = wrapglVertexAttrib3dv; + qglVertexAttrib4fv = wrapglVertexAttrib4fv; +// qglVertexAttrib4sv = wrapglVertexAttrib4sv; +// qglVertexAttrib4dv = wrapglVertexAttrib4dv; +// qglVertexAttrib4iv = wrapglVertexAttrib4iv; +// qglVertexAttrib4bv = wrapglVertexAttrib4bv; +// qglVertexAttrib4ubv = wrapglVertexAttrib4ubv; +// qglVertexAttrib4usv = wrapglVertexAttrib4usv; +// qglVertexAttrib4uiv = wrapglVertexAttrib4uiv; +// qglVertexAttrib4Nbv = wrapglVertexAttrib4Nbv; +// qglVertexAttrib4Nsv = wrapglVertexAttrib4Nsv; +// qglVertexAttrib4Niv = wrapglVertexAttrib4Niv; +// qglVertexAttrib4Nubv = wrapglVertexAttrib4Nubv; +// qglVertexAttrib4Nusv = wrapglVertexAttrib4Nusv; +// qglVertexAttrib4Nuiv = wrapglVertexAttrib4Nuiv; +// qglGetVertexAttribdv = wrapglGetVertexAttribdv; + qglGetVertexAttribfv = wrapglGetVertexAttribfv; + qglGetVertexAttribiv = wrapglGetVertexAttribiv; + qglGetVertexAttribPointerv = wrapglGetVertexAttribPointerv; + + gl_renderer = (const char *)qglGetString(GL_RENDERER); + gl_vendor = (const char *)qglGetString(GL_VENDOR); + gl_version = (const char *)qglGetString(GL_VERSION); + gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); + + if (!gl_extensions) + gl_extensions = ""; + if (!gl_platformextensions) + gl_platformextensions = ""; + + Con_Printf("GL_VENDOR: %s\n", gl_vendor); + Con_Printf("GL_RENDERER: %s\n", gl_renderer); + Con_Printf("GL_VERSION: %s\n", gl_version); + Con_DPrintf("GL_EXTENSIONS: %s\n", gl_extensions); + Con_DPrintf("%s_EXTENSIONS: %s\n", gl_platform, gl_platformextensions); + + // LordHavoc: report supported extensions + Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); + + // GLES devices in general do not like GL_BGRA, so use GL_RGBA + vid.forcetextype = TEXTYPE_RGBA; + + vid.support.gl20shaders = true; + vid.support.amd_texture_texture4 = false; + vid.support.arb_depth_texture = false; + vid.support.arb_draw_buffers = false; + vid.support.arb_multitexture = false; + vid.support.arb_occlusion_query = false; + vid.support.arb_shadow = false; + vid.support.arb_texture_compression = false; // different (vendor-specific) formats than on desktop OpenGL... + vid.support.arb_texture_cube_map = true; + vid.support.arb_texture_env_combine = false; + vid.support.arb_texture_gather = false; + vid.support.arb_texture_non_power_of_two = strstr(gl_extensions, "GL_OES_texture_npot") != NULL; + vid.support.arb_vertex_buffer_object = true; + vid.support.ati_separate_stencil = false; + vid.support.ext_blend_minmax = false; + vid.support.ext_blend_subtract = true; + vid.support.ext_draw_range_elements = true; + vid.support.ext_framebuffer_object = false;//true; + vid.support.ext_stencil_two_side = false; + vid.support.ext_texture_3d = false;//SDL_GL_ExtensionSupported("GL_OES_texture_3D"); // iPhoneOS does not support 3D textures, odd... + vid.support.ext_texture_compression_s3tc = false; + vid.support.ext_texture_edge_clamp = true; + vid.support.ext_texture_filter_anisotropic = false; // probably don't want to use it... + vid.support.ext_texture_srgb = false; + + qglGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_2d); + if (vid.support.ext_texture_filter_anisotropic) + qglGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint*)&vid.max_anisotropy); + if (vid.support.arb_texture_cube_map) + qglGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, (GLint*)&vid.maxtexturesize_cubemap); + if (vid.support.ext_texture_3d) + qglGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_3d); + Con_Printf("GL_MAX_CUBE_MAP_TEXTURE_SIZE = %i\n", vid.maxtexturesize_cubemap); + Con_Printf("GL_MAX_3D_TEXTURE_SIZE = %i\n", vid.maxtexturesize_3d); + + // verify that cubemap textures are really supported + if (vid.support.arb_texture_cube_map && vid.maxtexturesize_cubemap < 256) + vid.support.arb_texture_cube_map = false; + + // verify that 3d textures are really supported + if (vid.support.ext_texture_3d && vid.maxtexturesize_3d < 32) + { + vid.support.ext_texture_3d = false; + Con_Printf("GL_OES_texture_3d reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n"); + } + + vid.texunits = 4; + vid.teximageunits = 8; + vid.texarrayunits = 5; + vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = bound(1, vid.teximageunits, MAX_TEXTUREUNITS); + vid.texarrayunits = bound(1, vid.texarrayunits, MAX_TEXTUREUNITS); + Con_DPrintf("Using GLES2.0 rendering path - %i texture matrix, %i texture images, %i texcoords%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.support.ext_framebuffer_object ? ", shadowmapping supported" : ""); + vid.renderpath = RENDERPATH_GLES2; + vid.useinterleavedarrays = false; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + + // VorteX: set other info (maybe place them in VID_InitMode?) + extern cvar_t gl_info_vendor; + extern cvar_t gl_info_renderer; + extern cvar_t gl_info_version; + extern cvar_t gl_info_platform; + extern cvar_t gl_info_driver; + Cvar_SetQuick(&gl_info_vendor, gl_vendor); + Cvar_SetQuick(&gl_info_renderer, gl_renderer); + Cvar_SetQuick(&gl_info_version, gl_version); + Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); + Cvar_SetQuick(&gl_info_driver, gl_driver); +} +#endif + +void *GL_GetProcAddress(const char *name) +{ + void *p = NULL; + p = SDL_GL_GetProcAddress(name); + return p; +} + +static qboolean vid_sdl_initjoysticksystem = false; + +void VID_Init (void) +{ +#ifndef __IPHONEOS__ +#ifdef MACOSX + Cvar_RegisterVariable(&apple_mouse_noaccel); +#endif +#endif +#ifdef __IPHONEOS__ + Cvar_SetValueQuick(&vid_touchscreen, 1); +#endif + +#ifdef SDL_R_RESTART + R_RegisterModule("SDL", sdl_start, sdl_shutdown, sdl_newmap, NULL, NULL); +#endif + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + Sys_Error ("Failed to init SDL video subsystem: %s", SDL_GetError()); + vid_sdl_initjoysticksystem = SDL_InitSubSystem(SDL_INIT_JOYSTICK) >= 0; + if (vid_sdl_initjoysticksystem) + Con_Printf("Failed to init SDL joystick subsystem: %s\n", SDL_GetError()); + vid_isfullscreen = false; +} + +static int vid_sdljoystickindex = -1; +void VID_EnableJoystick(qboolean enable) +{ + int index = joy_enable.integer > 0 ? joy_index.integer : -1; + int numsdljoysticks; + qboolean success = false; + int sharedcount = 0; + int sdlindex = -1; + sharedcount = VID_Shared_SetJoystick(index); + if (index >= 0 && index < sharedcount) + success = true; + sdlindex = index - sharedcount; + + numsdljoysticks = SDL_NumJoysticks(); + if (sdlindex < 0 || sdlindex >= numsdljoysticks) + sdlindex = -1; + + // update cvar containing count of XInput joysticks + SDL joysticks + if (joy_detected.integer != sharedcount + numsdljoysticks) + Cvar_SetValueQuick(&joy_detected, sharedcount + numsdljoysticks); + + if (vid_sdljoystickindex != sdlindex) + { + vid_sdljoystickindex = sdlindex; + // close SDL joystick if active + if (vid_sdljoystick) + SDL_JoystickClose(vid_sdljoystick); + vid_sdljoystick = NULL; + if (sdlindex >= 0) + { + vid_sdljoystick = SDL_JoystickOpen(sdlindex); + if (vid_sdljoystick) + Con_Printf("Joystick %i opened (SDL_Joystick %i is \"%s\" with %i axes, %i buttons, %i balls)\n", index, sdlindex, SDL_JoystickName(sdlindex), (int)SDL_JoystickNumAxes(vid_sdljoystick), (int)SDL_JoystickNumButtons(vid_sdljoystick), (int)SDL_JoystickNumBalls(vid_sdljoystick)); + else + { + Con_Printf("Joystick %i failed (SDL_JoystickOpen(%i) returned: %s)\n", index, sdlindex, SDL_GetError()); + sdlindex = -1; + } + } + } + + if (sdlindex >= 0) + success = true; + + if (joy_active.integer != (success ? 1 : 0)) + Cvar_SetValueQuick(&joy_active, success ? 1 : 0); +} + +#if SETVIDEOMODE +// set the icon (we dont use SDL here since it would be too much a PITA) +#ifdef WIN32 +#include "resource.h" +#include +static SDL_Surface *VID_WrapSDL_SetVideoMode(int screenwidth, int screenheight, int screenbpp, int screenflags) +{ + SDL_Surface *screen = NULL; + SDL_SysWMinfo info; + HICON icon; + SDL_WM_SetCaption( gamename, NULL ); + screen = SDL_SetVideoMode(screenwidth, screenheight, screenbpp, screenflags); + if (screen) + { + // get the HWND handle + SDL_VERSION( &info.version ); + if (SDL_GetWMInfo(&info)) + { + icon = LoadIcon( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDI_ICON1 ) ); +#ifndef _W64 //If Windows 64bit data types don't exist +#ifndef SetClassLongPtr +#define SetClassLongPtr SetClassLong +#endif +#ifndef GCLP_HICON +#define GCLP_HICON GCL_HICON +#endif +#ifndef LONG_PTR +#define LONG_PTR LONG +#endif +#endif + SetClassLongPtr( info.window, GCLP_HICON, (LONG_PTR)icon ); + } + } + return screen; +} +#elif defined(MACOSX) +static SDL_Surface *VID_WrapSDL_SetVideoMode(int screenwidth, int screenheight, int screenbpp, int screenflags) +{ + SDL_Surface *screen = NULL; + SDL_WM_SetCaption( gamename, NULL ); + screen = SDL_SetVideoMode(screenwidth, screenheight, screenbpp, screenflags); + // we don't use SDL_WM_SetIcon here because the icon in the .app should be used + return screen; +} +#else +// Adding the OS independent XPM version --blub +#include "darkplaces.xpm" +#include "nexuiz.xpm" +static SDL_Surface *icon = NULL; +static SDL_Surface *VID_WrapSDL_SetVideoMode(int screenwidth, int screenheight, int screenbpp, int screenflags) +{ + /* + * Somewhat restricted XPM reader. Only supports XPMs saved by GIMP 2.4 at + * default settings with less than 91 colors and transparency. + */ + + int width, height, colors, isize, i, j; + int thenone = -1; + static SDL_Color palette[256]; + unsigned short palenc[256]; // store color id by char + char *xpm; + char **idata, *data; + const SDL_version *version; + SDL_Surface *screen = NULL; + + if (icon) + SDL_FreeSurface(icon); + icon = NULL; + version = SDL_Linked_Version(); + // only use non-XPM icon support in SDL v1.3 and higher + // SDL v1.2 does not support "smooth" transparency, and thus is better + // off the xpm way + if(version->major >= 2 || (version->major == 1 && version->minor >= 3)) + { + data = (char *) loadimagepixelsbgra("darkplaces-icon", false, false, false, NULL); + if(data) + { + unsigned int red = 0x00FF0000; + unsigned int green = 0x0000FF00; + unsigned int blue = 0x000000FF; + unsigned int alpha = 0xFF000000; + width = image_width; + height = image_height; + + // reallocate with malloc, as this is in tempmempool (do not want) + xpm = data; + data = malloc(width * height * 4); + memcpy(data, xpm, width * height * 4); + Mem_Free(xpm); + xpm = NULL; + + icon = SDL_CreateRGBSurface(SDL_SRCALPHA, width, height, 32, LittleLong(red), LittleLong(green), LittleLong(blue), LittleLong(alpha)); + + if (icon) + icon->pixels = data; + else + { + Con_Printf( "Failed to create surface for the window Icon!\n" + "%s\n", SDL_GetError()); + free(data); + } + } + } + + // we only get here if non-XPM icon was missing, or SDL version is not + // sufficient for transparent non-XPM icons + if(!icon) + { + xpm = (char *) FS_LoadFile("darkplaces-icon.xpm", tempmempool, false, NULL); + idata = NULL; + if(xpm) + idata = XPM_DecodeString(xpm); + if(!idata) + idata = ENGINE_ICON; + if(xpm) + Mem_Free(xpm); + + data = idata[0]; + + if(sscanf(data, "%i %i %i %i", &width, &height, &colors, &isize) == 4) + { + if(isize == 1) + { + for(i = 0; i < colors; ++i) + { + unsigned int r, g, b; + char idx; + + if(sscanf(idata[i+1], "%c c #%02x%02x%02x", &idx, &r, &g, &b) != 4) + { + char foo[2]; + if(sscanf(idata[i+1], "%c c Non%1[e]", &idx, foo) != 2) // I take the DailyWTF credit for this. --div0 + break; + else + { + palette[i].r = 255; // color key + palette[i].g = 0; + palette[i].b = 255; + thenone = i; // weeeee + palenc[(unsigned char) idx] = i; + } + } + else + { + palette[i].r = r - (r == 255 && g == 0 && b == 255); // change 255/0/255 pink to 254/0/255 for color key + palette[i].g = g; + palette[i].b = b; + palenc[(unsigned char) idx] = i; + } + } + + if (i == colors) + { + // allocate the image data + data = (char*) malloc(width*height); + + for(j = 0; j < height; ++j) + { + for(i = 0; i < width; ++i) + { + // casting to the safest possible datatypes ^^ + data[j * width + i] = palenc[((unsigned char*)idata[colors+j+1])[i]]; + } + } + + if(icon != NULL) + { + // SDL_FreeSurface should free the data too + // but for completeness' sake... + if(icon->flags & SDL_PREALLOC) + { + free(icon->pixels); + icon->pixels = NULL; // safety + } + SDL_FreeSurface(icon); + } + + icon = SDL_CreateRGBSurface(SDL_SRCCOLORKEY, width, height, 8, 0,0,0,0);// rmask, gmask, bmask, amask); no mask needed + // 8 bit surfaces get an empty palette allocated according to the docs + // so it's a palette image for sure :) no endian check necessary for the mask + + if(icon) + { + icon->pixels = data; + SDL_SetPalette(icon, SDL_PHYSPAL|SDL_LOGPAL, palette, 0, colors); + SDL_SetColorKey(icon, SDL_SRCCOLORKEY, thenone); + } + else + { + Con_Printf( "Failed to create surface for the window Icon!\n" + "%s\n", SDL_GetError()); + free(data); + } + } + else + { + Con_Printf("This XPM's palette looks odd. Can't continue.\n"); + } + } + else + { + // NOTE: Only 1-char colornames are supported + Con_Printf("This XPM's palette is either huge or idiotically unoptimized. It's key size is %i\n", isize); + } + } + else + { + // NOTE: Only 1-char colornames are supported + Con_Printf("Sorry, but this does not even look similar to an XPM.\n"); + } + } + + if (icon) + SDL_WM_SetIcon(icon, NULL); + + SDL_WM_SetCaption( gamename, NULL ); + screen = SDL_SetVideoMode(screenwidth, screenheight, screenbpp, screenflags); + +#if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2 +// LordHavoc: info.info.x11.lock_func and accompanying code do not seem to compile with SDL 1.3 +#if SDL_VIDEO_DRIVER_X11 && !SDL_VIDEO_DRIVER_QUARTZ + + version = SDL_Linked_Version(); + // only use non-XPM icon support in SDL v1.3 and higher + // SDL v1.2 does not support "smooth" transparency, and thus is better + // off the xpm way + if(screen && (!(version->major >= 2 || (version->major == 1 && version->minor >= 3)))) + { + // in this case, we did not set the good icon yet + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + if(SDL_GetWMInfo(&info) == 1 && info.subsystem == SDL_SYSWM_X11) + { + data = (char *) loadimagepixelsbgra("darkplaces-icon", false, false, false, NULL); + if(data) + { + // use _NET_WM_ICON too + static long netwm_icon[MAX_NETWM_ICON]; + int pos = 0; + int i = 1; + + while(data) + { + if(pos + 2 * image_width * image_height < MAX_NETWM_ICON) + { + netwm_icon[pos++] = image_width; + netwm_icon[pos++] = image_height; + for(i = 0; i < image_height; ++i) + for(j = 0; j < image_width; ++j) + netwm_icon[pos++] = BuffLittleLong((unsigned char *) &data[(i*image_width+j)*4]); + } + else + { + Con_Printf("Skipping NETWM icon #%d because there is no space left\n", i); + } + ++i; + Mem_Free(data); + data = (char *) loadimagepixelsbgra(va("darkplaces-icon%d", i), false, false, false, NULL); + } + + info.info.x11.lock_func(); + { + Atom net_wm_icon = XInternAtom(info.info.x11.display, "_NET_WM_ICON", false); + XChangeProperty(info.info.x11.display, info.info.x11.wmwindow, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (const unsigned char *) netwm_icon, pos); + } + info.info.x11.unlock_func(); + } + } + } +#endif +#endif + return screen; +} + +#endif +#endif + +static void VID_OutputVersion(void) +{ + const SDL_version *version; + version = SDL_Linked_Version(); + Con_Printf( "Linked against SDL version %d.%d.%d\n" + "Using SDL library version %d.%d.%d\n", + SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL, + version->major, version->minor, version->patch ); +} + +qboolean VID_InitModeGL(viddef_mode_t *mode) +{ + int i; +#if SETVIDEOMODE + static int notfirstvideomode = false; + int flags = SDL_OPENGL; +#else + int windowflags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL; +#endif + const char *drivername; + + win_half_width = mode->width>>1; + win_half_height = mode->height>>1; + + if(vid_resizable.integer) +#if SETVIDEOMODE + flags |= SDL_RESIZABLE; +#else + windowflags |= SDL_WINDOW_RESIZABLE; +#endif + + VID_OutputVersion(); + +#if SETVIDEOMODE + /* + SDL 1.2 Hack + We cant switch from one OpenGL video mode to another. + Thus we first switch to some stupid 2D mode and then back to OpenGL. + */ + if (notfirstvideomode) + SDL_SetVideoMode( 0, 0, 0, 0 ); + notfirstvideomode = true; +#endif + + // SDL usually knows best + drivername = NULL; + +// COMMANDLINEOPTION: SDL GL: -gl_driver selects a GL driver library, default is whatever SDL recommends, useful only for 3dfxogl.dll/3dfxvgl.dll or fxmesa or similar, if you don't know what this is for, you don't need it + i = COM_CheckParm("-gl_driver"); + if (i && i < com_argc - 1) + drivername = com_argv[i + 1]; + if (SDL_GL_LoadLibrary(drivername) < 0) + { + Con_Printf("Unable to load GL driver \"%s\": %s\n", drivername, SDL_GetError()); + return false; + } + +#ifdef __IPHONEOS__ + // mobile platforms are always fullscreen, we'll get the resolution after opening the window + mode->fullscreen = true; + // hide the menu with SDL_WINDOW_BORDERLESS + windowflags |= SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS; +#endif +#ifndef USE_GLES2 + if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL) + { + VID_Shutdown(); + Con_Print("Required OpenGL function glGetString not found\n"); + return false; + } +#endif + + // Knghtbrd: should do platform-specific extension string function here + + vid_isfullscreen = false; + if (mode->fullscreen) { +#if SETVIDEOMODE + flags |= SDL_FULLSCREEN; +#else + windowflags |= SDL_WINDOW_FULLSCREEN; +#endif + vid_isfullscreen = true; + } + //flags |= SDL_HWSURFACE; + + SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1); + if (mode->bitsperpixel >= 32) + { + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_ALPHA_SIZE, 8); + SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute (SDL_GL_STENCIL_SIZE, 8); + } + else + { + SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5); + SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 16); + } + if (mode->stereobuffer) + SDL_GL_SetAttribute (SDL_GL_STEREO, 1); + if (mode->samples > 1) + { + SDL_GL_SetAttribute (SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute (SDL_GL_MULTISAMPLESAMPLES, mode->samples); + } + +#if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2 + if (vid_vsync.integer) + SDL_GL_SetAttribute (SDL_GL_SWAP_CONTROL, 1); + else + SDL_GL_SetAttribute (SDL_GL_SWAP_CONTROL, 0); +#else +#ifdef USE_GLES2 + SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute (SDL_GL_RETAINED_BACKING, 1); +#endif +#endif + + video_bpp = mode->bitsperpixel; +#if SETVIDEOMODE + video_flags = flags; + screen = VID_WrapSDL_SetVideoMode(mode->width, mode->height, mode->bitsperpixel, flags); + if (screen == NULL) + { + Con_Printf("Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError()); + VID_Shutdown(); + return false; + } + mode->width = screen->w; + mode->height = screen->h; +#else + window_flags = windowflags; + window = SDL_CreateWindow(gamename, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, mode->width, mode->height, windowflags); + if (window == NULL) + { + Con_Printf("Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError()); + VID_Shutdown(); + return false; + } + SDL_GetWindowSize(window, &mode->width, &mode->height); + context = SDL_GL_CreateContext(window); + if (context == NULL) + { + Con_Printf("Failed to initialize OpenGL context: %s\n", SDL_GetError()); + VID_Shutdown(); + return false; + } +#endif + + vid_softsurface = NULL; + vid.softpixels = NULL; + + // init keyboard + SDL_EnableUNICODE( SDL_ENABLE ); + // enable key repeat since everyone expects it + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); + +#if !(SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2) + SDL_GL_SetSwapInterval(vid_vsync.integer != 0); + vid_usingvsync = (vid_vsync.integer != 0); +#endif + + gl_platform = "SDL"; + gl_platformextensions = ""; + +#ifdef USE_GLES2 + GLES_Init(); +#else + GL_Init(); +#endif + + vid_hidden = false; + vid_activewindow = false; + vid_hasfocus = true; + vid_usingmouse = false; + vid_usinghidecursor = false; + +#if SETVIDEOMODE + SDL_WM_GrabInput(SDL_GRAB_OFF); +#endif + return true; +} + +extern cvar_t gl_info_extensions; +extern cvar_t gl_info_vendor; +extern cvar_t gl_info_renderer; +extern cvar_t gl_info_version; +extern cvar_t gl_info_platform; +extern cvar_t gl_info_driver; + +qboolean VID_InitModeSoft(viddef_mode_t *mode) +{ +#if SETVIDEOMODE + int flags = SDL_HWSURFACE; + if(!COM_CheckParm("-noasyncblit")) flags |= SDL_ASYNCBLIT; +#else + int windowflags = SDL_WINDOW_SHOWN; +#endif + + win_half_width = mode->width>>1; + win_half_height = mode->height>>1; + + if(vid_resizable.integer) +#if SETVIDEOMODE + flags |= SDL_RESIZABLE; +#else + windowflags |= SDL_WINDOW_RESIZABLE; +#endif + + VID_OutputVersion(); + + vid_isfullscreen = false; + if (mode->fullscreen) { +#if SETVIDEOMODE + flags |= SDL_FULLSCREEN; +#else + windowflags |= SDL_WINDOW_FULLSCREEN; +#endif + vid_isfullscreen = true; + } + + video_bpp = mode->bitsperpixel; +#if SETVIDEOMODE + video_flags = flags; + screen = VID_WrapSDL_SetVideoMode(mode->width, mode->height, mode->bitsperpixel, flags); + if (screen == NULL) + { + Con_Printf("Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError()); + VID_Shutdown(); + return false; + } + mode->width = screen->w; + mode->height = screen->h; +#else + window_flags = windowflags; + window = SDL_CreateWindow(gamename, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, mode->width, mode->height, windowflags); + if (window == NULL) + { + Con_Printf("Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError()); + VID_Shutdown(); + return false; + } + SDL_GetWindowSize(window, &mode->width, &mode->height); +#endif + + // create a framebuffer using our specific color format, we let the SDL blit function convert it in VID_Finish + vid_softsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, mode->width, mode->height, 32, 0x00FF0000, 0x0000FF00, 0x00000000FF, 0xFF000000); + if (vid_softsurface == NULL) + { + Con_Printf("Failed to setup software rasterizer framebuffer %ix%ix32bpp: %s\n", mode->width, mode->height, SDL_GetError()); + VID_Shutdown(); + return false; + } + SDL_SetAlpha(vid_softsurface, 0, 255); + + vid.softpixels = (unsigned int *)vid_softsurface->pixels; + vid.softdepthpixels = (unsigned int *)calloc(1, mode->width * mode->height * 4); + if (DPSOFTRAST_Init(mode->width, mode->height, vid_soft_threads.integer, vid_soft_interlace.integer, (unsigned int *)vid_softsurface->pixels, (unsigned int *)vid.softdepthpixels) < 0) + { + Con_Printf("Failed to initialize software rasterizer\n"); + VID_Shutdown(); + return false; + } + + // init keyboard + SDL_EnableUNICODE( SDL_ENABLE ); + // enable key repeat since everyone expects it + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); + + VID_Soft_SharedSetup(); + + vid_hidden = false; + vid_activewindow = false; + vid_hasfocus = true; + vid_usingmouse = false; + vid_usinghidecursor = false; + +#if SETVIDEOMODE + SDL_WM_GrabInput(SDL_GRAB_OFF); +#endif + return true; +} + +qboolean VID_InitMode(viddef_mode_t *mode) +{ + if (!SDL_WasInit(SDL_INIT_VIDEO) && SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) + Sys_Error ("Failed to init SDL video subsystem: %s", SDL_GetError()); +#ifdef SSE_POSSIBLE + if (vid_soft.integer) + return VID_InitModeSoft(mode); + else +#endif + return VID_InitModeGL(mode); +} + +void VID_Shutdown (void) +{ + VID_EnableJoystick(false); + VID_SetMouse(false, false, false); + VID_RestoreSystemGamma(); + +#if SETVIDEOMODE +#ifndef WIN32 +#ifndef MACOSX + if (icon) + SDL_FreeSurface(icon); + icon = NULL; +#endif +#endif +#endif + + if (vid_softsurface) + SDL_FreeSurface(vid_softsurface); + vid_softsurface = NULL; + vid.softpixels = NULL; + if (vid.softdepthpixels) + free(vid.softdepthpixels); + vid.softdepthpixels = NULL; + +#if SETVIDEOMODE +#else + SDL_DestroyWindow(window); + window = NULL; +#endif + + SDL_QuitSubSystem(SDL_INIT_VIDEO); + + gl_driver[0] = 0; + gl_extensions = ""; + gl_platform = ""; + gl_platformextensions = ""; +} + +int VID_SetGamma (unsigned short *ramps, int rampsize) +{ + return !SDL_SetGammaRamp (ramps, ramps + rampsize, ramps + rampsize*2); +} + +int VID_GetGamma (unsigned short *ramps, int rampsize) +{ + return !SDL_GetGammaRamp (ramps, ramps + rampsize, ramps + rampsize*2); +} + +void VID_Finish (void) +{ +#if SETVIDEOMODE + Uint8 appstate; + + //react on appstate changes + appstate = SDL_GetAppState(); + + vid_hidden = !(appstate & SDL_APPACTIVE); + vid_hasfocus = (appstate & SDL_APPINPUTFOCUS) != 0; +#endif + vid_activewindow = !vid_hidden && vid_hasfocus; + + VID_UpdateGamma(false, 256); + + if (!vid_hidden) + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (r_speeds.integer == 2 || gl_finish.integer) + GL_Finish(); +#if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2 +#else +{ + qboolean vid_usevsync; + vid_usevsync = (vid_vsync.integer && !cls.timedemo); + if (vid_usingvsync != vid_usevsync) + { + if (SDL_GL_SetSwapInterval(vid_usevsync != 0) >= 0) + Con_DPrintf("Vsync %s\n", vid_usevsync ? "activated" : "deactivated"); + else + Con_DPrintf("ERROR: can't %s vsync\n", vid_usevsync ? "activate" : "deactivate"); + } +} +#endif +#if SETVIDEOMODE + SDL_GL_SwapBuffers(); +#else + SDL_GL_SwapWindow(window); +#endif + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Finish(); +#if SETVIDEOMODE +// if (!r_test.integer) + { + SDL_BlitSurface(vid_softsurface, NULL, screen, NULL); + SDL_Flip(screen); + } +#else + { + SDL_Surface *screen = SDL_GetWindowSurface(window); + SDL_BlitSurface(vid_softsurface, NULL, screen, NULL); + SDL_UpdateWindowSurface(window); + } +#endif + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + if (r_speeds.integer == 2 || gl_finish.integer) + GL_Finish(); + break; + } + } +} + +size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) +{ + size_t k; + SDL_Rect **vidmodes; + int bpp = SDL_GetVideoInfo()->vfmt->BitsPerPixel; + + k = 0; + for(vidmodes = SDL_ListModes(NULL, SDL_FULLSCREEN|SDL_HWSURFACE); vidmodes && *vidmodes; ++vidmodes) + { + if(k >= maxcount) + break; + modes[k].width = (*vidmodes)->w; + modes[k].height = (*vidmodes)->h; + modes[k].bpp = bpp; + modes[k].refreshrate = 60; // no support for refresh rate in SDL + modes[k].pixelheight_num = 1; + modes[k].pixelheight_denom = 1; // SDL does not provide this + ++k; + } + return k; +} diff --git a/misc/source/darkplaces-src/vid_shared.c b/misc/source/darkplaces-src/vid_shared.c new file mode 100644 index 00000000..c34f1c82 --- /dev/null +++ b/misc/source/darkplaces-src/vid_shared.c @@ -0,0 +1,2013 @@ + +#include "quakedef.h" +#include "cdaudio.h" + +#ifdef SUPPORTD3D +#include +#ifdef _MSC_VER +#pragma comment(lib, "d3d9.lib") +#endif + +LPDIRECT3DDEVICE9 vid_d3d9dev; +#endif + +#ifdef WIN32 +//#include +#define XINPUT_GAMEPAD_DPAD_UP 0x0001 +#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002 +#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004 +#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008 +#define XINPUT_GAMEPAD_START 0x0010 +#define XINPUT_GAMEPAD_BACK 0x0020 +#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040 +#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080 +#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 +#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 +#define XINPUT_GAMEPAD_A 0x1000 +#define XINPUT_GAMEPAD_B 0x2000 +#define XINPUT_GAMEPAD_X 0x4000 +#define XINPUT_GAMEPAD_Y 0x8000 +#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 +#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 +#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30 +#define XUSER_INDEX_ANY 0x000000FF + +typedef struct xinput_gamepad_s +{ + WORD wButtons; + BYTE bLeftTrigger; + BYTE bRightTrigger; + SHORT sThumbLX; + SHORT sThumbLY; + SHORT sThumbRX; + SHORT sThumbRY; +} +xinput_gamepad_t; + +typedef struct xinput_state_s +{ + DWORD dwPacketNumber; + xinput_gamepad_t Gamepad; +} +xinput_state_t; + +typedef struct xinput_keystroke_s +{ + WORD VirtualKey; + WCHAR Unicode; + WORD Flags; + BYTE UserIndex; + BYTE HidCode; +} +xinput_keystroke_t; + +DWORD (WINAPI *qXInputGetState)(DWORD index, xinput_state_t *state); +DWORD (WINAPI *qXInputGetKeystroke)(DWORD index, DWORD reserved, xinput_keystroke_t *keystroke); + +qboolean vid_xinputinitialized = false; +int vid_xinputindex = -1; +#endif + +// global video state +viddef_t vid; + +// LordHavoc: these are only set in wgl +qboolean isG200 = false; // LordHavoc: the Matrox G200 can't do per pixel alpha, and it uses a D3D driver for GL... ugh... +qboolean isRagePro = false; // LordHavoc: the ATI Rage Pro has limitations with per pixel alpha (the color scaler does not apply to per pixel alpha images...), although not as bad as a G200. + +// AK FIXME -> input_dest +qboolean in_client_mouse = true; + +// AK where should it be placed ? +float in_mouse_x, in_mouse_y; +float in_windowmouse_x, in_windowmouse_y; + +// LordHavoc: if window is hidden, don't update screen +qboolean vid_hidden = true; +// LordHavoc: if window is not the active window, don't hog as much CPU time, +// let go of the mouse, turn off sound, and restore system gamma ramps... +qboolean vid_activewindow = true; + +vid_joystate_t vid_joystate; + +#ifdef WIN32 +cvar_t joy_xinputavailable = {CVAR_READONLY, "joy_xinputavailable", "0", "indicates which devices are being reported by the Windows XInput API (first controller = 1, second = 2, third = 4, fourth = 8, added together)"}; +#endif +cvar_t joy_active = {CVAR_READONLY, "joy_active", "0", "indicates that a joystick is active (detected and enabled)"}; +cvar_t joy_detected = {CVAR_READONLY, "joy_detected", "0", "number of joysticks detected by engine"}; +cvar_t joy_enable = {CVAR_SAVE, "joy_enable", "0", "enables joystick support"}; +cvar_t joy_index = {0, "joy_index", "0", "selects which joystick to use if you have multiple (0 uses the first controller, 1 uses the second, ...)"}; +cvar_t joy_axisforward = {0, "joy_axisforward", "1", "which joystick axis to query for forward/backward movement"}; +cvar_t joy_axisside = {0, "joy_axisside", "0", "which joystick axis to query for right/left movement"}; +cvar_t joy_axisup = {0, "joy_axisup", "-1", "which joystick axis to query for up/down movement"}; +cvar_t joy_axispitch = {0, "joy_axispitch", "3", "which joystick axis to query for looking up/down"}; +cvar_t joy_axisyaw = {0, "joy_axisyaw", "2", "which joystick axis to query for looking right/left"}; +cvar_t joy_axisroll = {0, "joy_axisroll", "-1", "which joystick axis to query for tilting head right/left"}; +cvar_t joy_deadzoneforward = {0, "joy_deadzoneforward", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzoneside = {0, "joy_deadzoneside", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzoneup = {0, "joy_deadzoneup", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzonepitch = {0, "joy_deadzonepitch", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzoneyaw = {0, "joy_deadzoneyaw", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzoneroll = {0, "joy_deadzoneroll", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_sensitivityforward = {0, "joy_sensitivityforward", "-1", "movement multiplier"}; +cvar_t joy_sensitivityside = {0, "joy_sensitivityside", "1", "movement multiplier"}; +cvar_t joy_sensitivityup = {0, "joy_sensitivityup", "1", "movement multiplier"}; +cvar_t joy_sensitivitypitch = {0, "joy_sensitivitypitch", "1", "movement multiplier"}; +cvar_t joy_sensitivityyaw = {0, "joy_sensitivityyaw", "-1", "movement multiplier"}; +cvar_t joy_sensitivityroll = {0, "joy_sensitivityroll", "1", "movement multiplier"}; +cvar_t joy_axiskeyevents = {CVAR_SAVE, "joy_axiskeyevents", "0", "generate uparrow/leftarrow etc. keyevents for joystick axes, use if your joystick driver is not generating them"}; +cvar_t joy_axiskeyevents_deadzone = {CVAR_SAVE, "joy_axiskeyevents_deadzone", "0.5", "deadzone value for axes"}; +cvar_t joy_x360_axisforward = {0, "joy_x360_axisforward", "1", "which joystick axis to query for forward/backward movement"}; +cvar_t joy_x360_axisside = {0, "joy_x360_axisside", "0", "which joystick axis to query for right/left movement"}; +cvar_t joy_x360_axisup = {0, "joy_x360_axisup", "-1", "which joystick axis to query for up/down movement"}; +cvar_t joy_x360_axispitch = {0, "joy_x360_axispitch", "3", "which joystick axis to query for looking up/down"}; +cvar_t joy_x360_axisyaw = {0, "joy_x360_axisyaw", "2", "which joystick axis to query for looking right/left"}; +cvar_t joy_x360_axisroll = {0, "joy_x360_axisroll", "-1", "which joystick axis to query for tilting head right/left"}; +cvar_t joy_x360_deadzoneforward = {0, "joy_x360_deadzoneforward", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzoneside = {0, "joy_x360_deadzoneside", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzoneup = {0, "joy_x360_deadzoneup", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzonepitch = {0, "joy_x360_deadzonepitch", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzoneyaw = {0, "joy_x360_deadzoneyaw", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzoneroll = {0, "joy_x360_deadzoneroll", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_sensitivityforward = {0, "joy_x360_sensitivityforward", "1", "movement multiplier"}; +cvar_t joy_x360_sensitivityside = {0, "joy_x360_sensitivityside", "1", "movement multiplier"}; +cvar_t joy_x360_sensitivityup = {0, "joy_x360_sensitivityup", "1", "movement multiplier"}; +cvar_t joy_x360_sensitivitypitch = {0, "joy_x360_sensitivitypitch", "-1", "movement multiplier"}; +cvar_t joy_x360_sensitivityyaw = {0, "joy_x360_sensitivityyaw", "-1", "movement multiplier"}; +cvar_t joy_x360_sensitivityroll = {0, "joy_x360_sensitivityroll", "1", "movement multiplier"}; + +// cvars for DPSOFTRAST +cvar_t vid_soft = {CVAR_SAVE, "vid_soft", "0", "enables use of the DarkPlaces Software Rasterizer rather than OpenGL or Direct3D"}; +cvar_t vid_soft_threads = {CVAR_SAVE, "vid_soft_threads", "2", "the number of threads the DarkPlaces Software Rasterizer should use"}; +cvar_t vid_soft_interlace = {CVAR_SAVE, "vid_soft_interlace", "1", "whether the DarkPlaces Software Rasterizer should interlace the screen bands occupied by each thread"}; + +// we don't know until we try it! +cvar_t vid_hardwaregammasupported = {CVAR_READONLY,"vid_hardwaregammasupported","1", "indicates whether hardware gamma is supported (updated by attempts to set hardware gamma ramps)"}; + +// VorteX: more info cvars, mostly set in VID_CheckExtensions +cvar_t gl_info_vendor = {CVAR_READONLY, "gl_info_vendor", "", "indicates brand of graphics chip"}; +cvar_t gl_info_renderer = {CVAR_READONLY, "gl_info_renderer", "", "indicates graphics chip model and other information"}; +cvar_t gl_info_version = {CVAR_READONLY, "gl_info_version", "", "indicates version of current renderer. begins with 1.0.0, 1.1.0, 1.2.0, 1.3.1 etc."}; +cvar_t gl_info_extensions = {CVAR_READONLY, "gl_info_extensions", "", "indicates extension list found by engine, space separated."}; +cvar_t gl_info_platform = {CVAR_READONLY, "gl_info_platform", "", "indicates GL platform: WGL, GLX, or AGL."}; +cvar_t gl_info_driver = {CVAR_READONLY, "gl_info_driver", "", "name of driver library (opengl32.dll, libGL.so.1, or whatever)."}; + +// whether hardware gamma ramps are currently in effect +qboolean vid_usinghwgamma = false; + +int vid_gammarampsize = 0; +unsigned short *vid_gammaramps = NULL; +unsigned short *vid_systemgammaramps = NULL; + +cvar_t vid_fullscreen = {CVAR_SAVE, "vid_fullscreen", "1", "use fullscreen (1) or windowed (0)"}; +cvar_t vid_width = {CVAR_SAVE, "vid_width", "640", "resolution"}; +cvar_t vid_height = {CVAR_SAVE, "vid_height", "480", "resolution"}; +cvar_t vid_bitsperpixel = {CVAR_SAVE, "vid_bitsperpixel", "32", "how many bits per pixel to render at (32 or 16, 32 is recommended)"}; +cvar_t vid_samples = {CVAR_SAVE, "vid_samples", "1", "how many anti-aliasing samples per pixel to request from the graphics driver (4 is recommended, 1 is faster)"}; +cvar_t vid_refreshrate = {CVAR_SAVE, "vid_refreshrate", "60", "refresh rate to use, in hz (higher values flicker less, if supported by your monitor)"}; +cvar_t vid_userefreshrate = {CVAR_SAVE, "vid_userefreshrate", "0", "set this to 1 to make vid_refreshrate used, or to 0 to let the engine choose a sane default"}; +cvar_t vid_stereobuffer = {CVAR_SAVE, "vid_stereobuffer", "0", "enables 'quad-buffered' stereo rendering for stereo shutterglasses, HMD (head mounted display) devices, or polarized stereo LCDs, if supported by your drivers"}; + +cvar_t vid_vsync = {CVAR_SAVE, "vid_vsync", "0", "sync to vertical blank, prevents 'tearing' (seeing part of one frame and part of another on the screen at the same time), automatically disabled when doing timedemo benchmarks"}; +cvar_t vid_mouse = {CVAR_SAVE, "vid_mouse", "1", "whether to use the mouse in windowed mode (fullscreen always does)"}; +cvar_t vid_grabkeyboard = {CVAR_SAVE, "vid_grabkeyboard", "0", "whether to grab the keyboard when mouse is active (prevents use of volume control keys, music player keys, etc on some keyboards)"}; +cvar_t vid_minwidth = {0, "vid_minwidth", "0", "minimum vid_width that is acceptable (to be set in default.cfg in mods)"}; +cvar_t vid_minheight = {0, "vid_minheight", "0", "minimum vid_height that is acceptable (to be set in default.cfg in mods)"}; +cvar_t vid_gl13 = {0, "vid_gl13", "1", "enables faster rendering using OpenGL 1.3 features (such as GL_ARB_texture_env_combine extension)"}; +cvar_t vid_gl20 = {0, "vid_gl20", "1", "enables faster rendering using OpenGL 2.0 features (such as GL_ARB_fragment_shader extension)"}; +cvar_t gl_finish = {0, "gl_finish", "0", "make the cpu wait for the graphics processor at the end of each rendered frame (can help with strange input or video lag problems on some machines)"}; +cvar_t vid_sRGB = {CVAR_SAVE, "vid_sRGB", "0", "if hardware is capable, modify rendering to be gamma corrected for the sRGB color standard (computer monitors, TVs), recommended"}; + +cvar_t vid_touchscreen = {0, "vid_touchscreen", "0", "Use touchscreen-style input (no mouse grab, track mouse motion only while button is down, screen areas for mimicing joystick axes and buttons"}; +cvar_t vid_stick_mouse = {CVAR_SAVE, "vid_stick_mouse", "0", "have the mouse stuck in the center of the screen" }; +cvar_t vid_resizable = {CVAR_SAVE, "vid_resizable", "0", "0: window not resizable, 1: resizable, 2: window can be resized but the framebuffer isn't adjusted" }; + +cvar_t v_gamma = {CVAR_SAVE, "v_gamma", "1", "inverse gamma correction value, a brightness effect that does not affect white or black, and tends to make the image grey and dull"}; +cvar_t v_contrast = {CVAR_SAVE, "v_contrast", "1", "brightness of white (values above 1 give a brighter image with increased color saturation, unlike v_gamma)"}; +cvar_t v_brightness = {CVAR_SAVE, "v_brightness", "0", "brightness of black, useful for monitors that are too dark"}; +cvar_t v_contrastboost = {CVAR_SAVE, "v_contrastboost", "1", "by how much to multiply the contrast in dark areas (1 is no change)"}; +cvar_t v_color_enable = {CVAR_SAVE, "v_color_enable", "0", "enables black-grey-white color correction curve controls"}; +cvar_t v_color_black_r = {CVAR_SAVE, "v_color_black_r", "0", "desired color of black"}; +cvar_t v_color_black_g = {CVAR_SAVE, "v_color_black_g", "0", "desired color of black"}; +cvar_t v_color_black_b = {CVAR_SAVE, "v_color_black_b", "0", "desired color of black"}; +cvar_t v_color_grey_r = {CVAR_SAVE, "v_color_grey_r", "0.5", "desired color of grey"}; +cvar_t v_color_grey_g = {CVAR_SAVE, "v_color_grey_g", "0.5", "desired color of grey"}; +cvar_t v_color_grey_b = {CVAR_SAVE, "v_color_grey_b", "0.5", "desired color of grey"}; +cvar_t v_color_white_r = {CVAR_SAVE, "v_color_white_r", "1", "desired color of white"}; +cvar_t v_color_white_g = {CVAR_SAVE, "v_color_white_g", "1", "desired color of white"}; +cvar_t v_color_white_b = {CVAR_SAVE, "v_color_white_b", "1", "desired color of white"}; +cvar_t v_hwgamma = {CVAR_SAVE, "v_hwgamma", "0", "enables use of hardware gamma correction ramps if available (note: does not work very well on Windows2000 and above), values are 0 = off, 1 = attempt to use hardware gamma, 2 = use hardware gamma whether it works or not"}; +cvar_t v_glslgamma = {CVAR_SAVE, "v_glslgamma", "1", "enables use of GLSL to apply gamma correction ramps if available (note: overrides v_hwgamma)"}; +cvar_t v_psycho = {0, "v_psycho", "0", "easter egg"}; + +// brand of graphics chip +const char *gl_vendor; +// graphics chip model and other information +const char *gl_renderer; +// begins with 1.0.0, 1.1.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, or 1.4.0 +const char *gl_version; +// extensions list, space separated +const char *gl_extensions; +// WGL, GLX, or AGL +const char *gl_platform; +// another extensions list, containing platform-specific extensions that are +// not in the main list +const char *gl_platformextensions; +// name of driver library (opengl32.dll, libGL.so.1, or whatever) +char gl_driver[256]; + +// GL_ARB_multitexture +void (GLAPIENTRY *qglMultiTexCoord1f) (GLenum, GLfloat); +void (GLAPIENTRY *qglMultiTexCoord2f) (GLenum, GLfloat, GLfloat); +void (GLAPIENTRY *qglMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); +void (GLAPIENTRY *qglMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +void (GLAPIENTRY *qglActiveTexture) (GLenum); +void (GLAPIENTRY *qglClientActiveTexture) (GLenum); + +// general GL functions + +void (GLAPIENTRY *qglClearColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + +void (GLAPIENTRY *qglClear)(GLbitfield mask); + +void (GLAPIENTRY *qglAlphaFunc)(GLenum func, GLclampf ref); +void (GLAPIENTRY *qglBlendFunc)(GLenum sfactor, GLenum dfactor); +void (GLAPIENTRY *qglCullFace)(GLenum mode); + +void (GLAPIENTRY *qglDrawBuffer)(GLenum mode); +void (GLAPIENTRY *qglReadBuffer)(GLenum mode); +void (GLAPIENTRY *qglEnable)(GLenum cap); +void (GLAPIENTRY *qglDisable)(GLenum cap); +GLboolean (GLAPIENTRY *qglIsEnabled)(GLenum cap); + +void (GLAPIENTRY *qglEnableClientState)(GLenum cap); +void (GLAPIENTRY *qglDisableClientState)(GLenum cap); + +void (GLAPIENTRY *qglGetBooleanv)(GLenum pname, GLboolean *params); +void (GLAPIENTRY *qglGetDoublev)(GLenum pname, GLdouble *params); +void (GLAPIENTRY *qglGetFloatv)(GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglGetIntegerv)(GLenum pname, GLint *params); + +GLenum (GLAPIENTRY *qglGetError)(void); +const GLubyte* (GLAPIENTRY *qglGetString)(GLenum name); +void (GLAPIENTRY *qglFinish)(void); +void (GLAPIENTRY *qglFlush)(void); + +void (GLAPIENTRY *qglClearDepth)(GLclampd depth); +void (GLAPIENTRY *qglDepthFunc)(GLenum func); +void (GLAPIENTRY *qglDepthMask)(GLboolean flag); +void (GLAPIENTRY *qglDepthRange)(GLclampd near_val, GLclampd far_val); +void (GLAPIENTRY *qglColorMask)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); + +void (GLAPIENTRY *qglDrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +void (GLAPIENTRY *qglDrawElements)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void (GLAPIENTRY *qglDrawArrays)(GLenum mode, GLint first, GLsizei count); +void (GLAPIENTRY *qglVertexPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void (GLAPIENTRY *qglNormalPointer)(GLenum type, GLsizei stride, const GLvoid *ptr); +void (GLAPIENTRY *qglColorPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void (GLAPIENTRY *qglTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void (GLAPIENTRY *qglArrayElement)(GLint i); + +void (GLAPIENTRY *qglColor4ub)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +void (GLAPIENTRY *qglColor4f)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void (GLAPIENTRY *qglTexCoord1f)(GLfloat s); +void (GLAPIENTRY *qglTexCoord2f)(GLfloat s, GLfloat t); +void (GLAPIENTRY *qglTexCoord3f)(GLfloat s, GLfloat t, GLfloat r); +void (GLAPIENTRY *qglTexCoord4f)(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +void (GLAPIENTRY *qglVertex2f)(GLfloat x, GLfloat y); +void (GLAPIENTRY *qglVertex3f)(GLfloat x, GLfloat y, GLfloat z); +void (GLAPIENTRY *qglVertex4f)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void (GLAPIENTRY *qglBegin)(GLenum mode); +void (GLAPIENTRY *qglEnd)(void); + +void (GLAPIENTRY *qglMatrixMode)(GLenum mode); +//void (GLAPIENTRY *qglOrtho)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); +//void (GLAPIENTRY *qglFrustum)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); +void (GLAPIENTRY *qglViewport)(GLint x, GLint y, GLsizei width, GLsizei height); +//void (GLAPIENTRY *qglPushMatrix)(void); +//void (GLAPIENTRY *qglPopMatrix)(void); +void (GLAPIENTRY *qglLoadIdentity)(void); +//void (GLAPIENTRY *qglLoadMatrixd)(const GLdouble *m); +void (GLAPIENTRY *qglLoadMatrixf)(const GLfloat *m); +//void (GLAPIENTRY *qglMultMatrixd)(const GLdouble *m); +//void (GLAPIENTRY *qglMultMatrixf)(const GLfloat *m); +//void (GLAPIENTRY *qglRotated)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +//void (GLAPIENTRY *qglRotatef)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +//void (GLAPIENTRY *qglScaled)(GLdouble x, GLdouble y, GLdouble z); +//void (GLAPIENTRY *qglScalef)(GLfloat x, GLfloat y, GLfloat z); +//void (GLAPIENTRY *qglTranslated)(GLdouble x, GLdouble y, GLdouble z); +//void (GLAPIENTRY *qglTranslatef)(GLfloat x, GLfloat y, GLfloat z); + +void (GLAPIENTRY *qglReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); + +void (GLAPIENTRY *qglStencilFunc)(GLenum func, GLint ref, GLuint mask); +void (GLAPIENTRY *qglStencilMask)(GLuint mask); +void (GLAPIENTRY *qglStencilOp)(GLenum fail, GLenum zfail, GLenum zpass); +void (GLAPIENTRY *qglClearStencil)(GLint s); + +void (GLAPIENTRY *qglTexEnvf)(GLenum target, GLenum pname, GLfloat param); +void (GLAPIENTRY *qglTexEnvfv)(GLenum target, GLenum pname, const GLfloat *params); +void (GLAPIENTRY *qglTexEnvi)(GLenum target, GLenum pname, GLint param); +void (GLAPIENTRY *qglTexParameterf)(GLenum target, GLenum pname, GLfloat param); +void (GLAPIENTRY *qglTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglTexParameteri)(GLenum target, GLenum pname, GLint param); +void (GLAPIENTRY *qglGetTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglGetTexParameteriv)(GLenum target, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetTexLevelParameterfv)(GLenum target, GLint level, GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglGetTexLevelParameteriv)(GLenum target, GLint level, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +void (GLAPIENTRY *qglHint)(GLenum target, GLenum mode); + +void (GLAPIENTRY *qglGenTextures)(GLsizei n, GLuint *textures); +void (GLAPIENTRY *qglDeleteTextures)(GLsizei n, const GLuint *textures); +void (GLAPIENTRY *qglBindTexture)(GLenum target, GLuint texture); +//void (GLAPIENTRY *qglPrioritizeTextures)(GLsizei n, const GLuint *textures, const GLclampf *priorities); +//GLboolean (GLAPIENTRY *qglAreTexturesResident)(GLsizei n, const GLuint *textures, GLboolean *residences); +//GLboolean (GLAPIENTRY *qglIsTexture)(GLuint texture); +//void (GLAPIENTRY *qglPixelStoref)(GLenum pname, GLfloat param); +void (GLAPIENTRY *qglPixelStorei)(GLenum pname, GLint param); + +//void (GLAPIENTRY *qglTexImage1D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void (GLAPIENTRY *qglTexImage2D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +//void (GLAPIENTRY *qglTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +void (GLAPIENTRY *qglTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +//void (GLAPIENTRY *qglCopyTexImage1D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +void (GLAPIENTRY *qglCopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +//void (GLAPIENTRY *qglCopyTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +void (GLAPIENTRY *qglCopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + + +void (GLAPIENTRY *qglDrawRangeElementsEXT)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); + +//void (GLAPIENTRY *qglColorTableEXT)(int, int, int, int, int, const void *); + +void (GLAPIENTRY *qglTexImage3D)(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void (GLAPIENTRY *qglTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +void (GLAPIENTRY *qglCopyTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); + +void (GLAPIENTRY *qglScissor)(GLint x, GLint y, GLsizei width, GLsizei height); + +void (GLAPIENTRY *qglPolygonOffset)(GLfloat factor, GLfloat units); +void (GLAPIENTRY *qglPolygonMode)(GLenum face, GLenum mode); +void (GLAPIENTRY *qglPolygonStipple)(const GLubyte *mask); + +//void (GLAPIENTRY *qglClipPlane)(GLenum plane, const GLdouble *equation); +//void (GLAPIENTRY *qglGetClipPlane)(GLenum plane, GLdouble *equation); + +//[515]: added on 29.07.2005 +void (GLAPIENTRY *qglLineWidth)(GLfloat width); +void (GLAPIENTRY *qglPointSize)(GLfloat size); + +void (GLAPIENTRY *qglBlendEquationEXT)(GLenum); + +void (GLAPIENTRY *qglStencilOpSeparate)(GLenum, GLenum, GLenum, GLenum); +void (GLAPIENTRY *qglStencilFuncSeparate)(GLenum, GLenum, GLint, GLuint); +void (GLAPIENTRY *qglActiveStencilFaceEXT)(GLenum); + +void (GLAPIENTRY *qglDeleteShader)(GLuint obj); +void (GLAPIENTRY *qglDeleteProgram)(GLuint obj); +//GLuint (GLAPIENTRY *qglGetHandle)(GLenum pname); +void (GLAPIENTRY *qglDetachShader)(GLuint containerObj, GLuint attachedObj); +GLuint (GLAPIENTRY *qglCreateShader)(GLenum shaderType); +void (GLAPIENTRY *qglShaderSource)(GLuint shaderObj, GLsizei count, const GLchar **string, const GLint *length); +void (GLAPIENTRY *qglCompileShader)(GLuint shaderObj); +GLuint (GLAPIENTRY *qglCreateProgram)(void); +void (GLAPIENTRY *qglAttachShader)(GLuint containerObj, GLuint obj); +void (GLAPIENTRY *qglLinkProgram)(GLuint programObj); +void (GLAPIENTRY *qglUseProgram)(GLuint programObj); +void (GLAPIENTRY *qglValidateProgram)(GLuint programObj); +void (GLAPIENTRY *qglUniform1f)(GLint location, GLfloat v0); +void (GLAPIENTRY *qglUniform2f)(GLint location, GLfloat v0, GLfloat v1); +void (GLAPIENTRY *qglUniform3f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +void (GLAPIENTRY *qglUniform4f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +void (GLAPIENTRY *qglUniform1i)(GLint location, GLint v0); +void (GLAPIENTRY *qglUniform2i)(GLint location, GLint v0, GLint v1); +void (GLAPIENTRY *qglUniform3i)(GLint location, GLint v0, GLint v1, GLint v2); +void (GLAPIENTRY *qglUniform4i)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +void (GLAPIENTRY *qglUniform1fv)(GLint location, GLsizei count, const GLfloat *value); +void (GLAPIENTRY *qglUniform2fv)(GLint location, GLsizei count, const GLfloat *value); +void (GLAPIENTRY *qglUniform3fv)(GLint location, GLsizei count, const GLfloat *value); +void (GLAPIENTRY *qglUniform4fv)(GLint location, GLsizei count, const GLfloat *value); +void (GLAPIENTRY *qglUniform1iv)(GLint location, GLsizei count, const GLint *value); +void (GLAPIENTRY *qglUniform2iv)(GLint location, GLsizei count, const GLint *value); +void (GLAPIENTRY *qglUniform3iv)(GLint location, GLsizei count, const GLint *value); +void (GLAPIENTRY *qglUniform4iv)(GLint location, GLsizei count, const GLint *value); +void (GLAPIENTRY *qglUniformMatrix2fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void (GLAPIENTRY *qglUniformMatrix3fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void (GLAPIENTRY *qglUniformMatrix4fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void (GLAPIENTRY *qglGetShaderiv)(GLuint obj, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetProgramiv)(GLuint obj, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetShaderInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); +void (GLAPIENTRY *qglGetProgramInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); +void (GLAPIENTRY *qglGetAttachedShaders)(GLuint containerObj, GLsizei maxCount, GLsizei *count, GLuint *obj); +GLint (GLAPIENTRY *qglGetUniformLocation)(GLuint programObj, const GLchar *name); +void (GLAPIENTRY *qglGetActiveUniform)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +void (GLAPIENTRY *qglGetUniformfv)(GLuint programObj, GLint location, GLfloat *params); +void (GLAPIENTRY *qglGetUniformiv)(GLuint programObj, GLint location, GLint *params); +void (GLAPIENTRY *qglGetShaderSource)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *source); + +void (GLAPIENTRY *qglVertexAttrib1f)(GLuint index, GLfloat v0); +void (GLAPIENTRY *qglVertexAttrib1s)(GLuint index, GLshort v0); +void (GLAPIENTRY *qglVertexAttrib1d)(GLuint index, GLdouble v0); +void (GLAPIENTRY *qglVertexAttrib2f)(GLuint index, GLfloat v0, GLfloat v1); +void (GLAPIENTRY *qglVertexAttrib2s)(GLuint index, GLshort v0, GLshort v1); +void (GLAPIENTRY *qglVertexAttrib2d)(GLuint index, GLdouble v0, GLdouble v1); +void (GLAPIENTRY *qglVertexAttrib3f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); +void (GLAPIENTRY *qglVertexAttrib3s)(GLuint index, GLshort v0, GLshort v1, GLshort v2); +void (GLAPIENTRY *qglVertexAttrib3d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2); +void (GLAPIENTRY *qglVertexAttrib4f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +void (GLAPIENTRY *qglVertexAttrib4s)(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3); +void (GLAPIENTRY *qglVertexAttrib4d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +void (GLAPIENTRY *qglVertexAttrib4Nub)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +void (GLAPIENTRY *qglVertexAttrib1fv)(GLuint index, const GLfloat *v); +void (GLAPIENTRY *qglVertexAttrib1sv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib1dv)(GLuint index, const GLdouble *v); +void (GLAPIENTRY *qglVertexAttrib2fv)(GLuint index, const GLfloat *v); +void (GLAPIENTRY *qglVertexAttrib2sv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib2dv)(GLuint index, const GLdouble *v); +void (GLAPIENTRY *qglVertexAttrib3fv)(GLuint index, const GLfloat *v); +void (GLAPIENTRY *qglVertexAttrib3sv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib3dv)(GLuint index, const GLdouble *v); +void (GLAPIENTRY *qglVertexAttrib4fv)(GLuint index, const GLfloat *v); +void (GLAPIENTRY *qglVertexAttrib4sv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib4dv)(GLuint index, const GLdouble *v); +void (GLAPIENTRY *qglVertexAttrib4iv)(GLuint index, const GLint *v); +void (GLAPIENTRY *qglVertexAttrib4bv)(GLuint index, const GLbyte *v); +void (GLAPIENTRY *qglVertexAttrib4ubv)(GLuint index, const GLubyte *v); +void (GLAPIENTRY *qglVertexAttrib4usv)(GLuint index, const GLushort *v); +void (GLAPIENTRY *qglVertexAttrib4uiv)(GLuint index, const GLuint *v); +void (GLAPIENTRY *qglVertexAttrib4Nbv)(GLuint index, const GLbyte *v); +void (GLAPIENTRY *qglVertexAttrib4Nsv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib4Niv)(GLuint index, const GLint *v); +void (GLAPIENTRY *qglVertexAttrib4Nubv)(GLuint index, const GLubyte *v); +void (GLAPIENTRY *qglVertexAttrib4Nusv)(GLuint index, const GLushort *v); +void (GLAPIENTRY *qglVertexAttrib4Nuiv)(GLuint index, const GLuint *v); +void (GLAPIENTRY *qglVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +void (GLAPIENTRY *qglEnableVertexAttribArray)(GLuint index); +void (GLAPIENTRY *qglDisableVertexAttribArray)(GLuint index); +void (GLAPIENTRY *qglBindAttribLocation)(GLuint programObj, GLuint index, const GLchar *name); +void (GLAPIENTRY *qglBindFragDataLocation)(GLuint programObj, GLuint index, const GLchar *name); +void (GLAPIENTRY *qglGetActiveAttrib)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLint (GLAPIENTRY *qglGetAttribLocation)(GLuint programObj, const GLchar *name); +void (GLAPIENTRY *qglGetVertexAttribdv)(GLuint index, GLenum pname, GLdouble *params); +void (GLAPIENTRY *qglGetVertexAttribfv)(GLuint index, GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglGetVertexAttribiv)(GLuint index, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetVertexAttribPointerv)(GLuint index, GLenum pname, GLvoid **pointer); + +//GL_ARB_vertex_buffer_object +void (GLAPIENTRY *qglBindBufferARB) (GLenum target, GLuint buffer); +void (GLAPIENTRY *qglDeleteBuffersARB) (GLsizei n, const GLuint *buffers); +void (GLAPIENTRY *qglGenBuffersARB) (GLsizei n, GLuint *buffers); +GLboolean (GLAPIENTRY *qglIsBufferARB) (GLuint buffer); +GLvoid* (GLAPIENTRY *qglMapBufferARB) (GLenum target, GLenum access); +GLboolean (GLAPIENTRY *qglUnmapBufferARB) (GLenum target); +void (GLAPIENTRY *qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +void (GLAPIENTRY *qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); + +//GL_EXT_framebuffer_object +GLboolean (GLAPIENTRY *qglIsRenderbufferEXT)(GLuint renderbuffer); +void (GLAPIENTRY *qglBindRenderbufferEXT)(GLenum target, GLuint renderbuffer); +void (GLAPIENTRY *qglDeleteRenderbuffersEXT)(GLsizei n, const GLuint *renderbuffers); +void (GLAPIENTRY *qglGenRenderbuffersEXT)(GLsizei n, GLuint *renderbuffers); +void (GLAPIENTRY *qglRenderbufferStorageEXT)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +void (GLAPIENTRY *qglGetRenderbufferParameterivEXT)(GLenum target, GLenum pname, GLint *params); +GLboolean (GLAPIENTRY *qglIsFramebufferEXT)(GLuint framebuffer); +void (GLAPIENTRY *qglBindFramebufferEXT)(GLenum target, GLuint framebuffer); +void (GLAPIENTRY *qglDeleteFramebuffersEXT)(GLsizei n, const GLuint *framebuffers); +void (GLAPIENTRY *qglGenFramebuffersEXT)(GLsizei n, GLuint *framebuffers); +GLenum (GLAPIENTRY *qglCheckFramebufferStatusEXT)(GLenum target); +//void (GLAPIENTRY *qglFramebufferTexture1DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +void (GLAPIENTRY *qglFramebufferTexture2DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +void (GLAPIENTRY *qglFramebufferTexture3DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +void (GLAPIENTRY *qglFramebufferRenderbufferEXT)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +void (GLAPIENTRY *qglGetFramebufferAttachmentParameterivEXT)(GLenum target, GLenum attachment, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGenerateMipmapEXT)(GLenum target); + +void (GLAPIENTRY *qglDrawBuffersARB)(GLsizei n, const GLenum *bufs); + +void (GLAPIENTRY *qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +void (GLAPIENTRY *qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +//void (GLAPIENTRY *qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +void (GLAPIENTRY *qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +void (GLAPIENTRY *qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +//void (GLAPIENTRY *qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +void (GLAPIENTRY *qglGetCompressedTexImageARB)(GLenum target, GLint lod, void *img); + +void (GLAPIENTRY *qglGenQueriesARB)(GLsizei n, GLuint *ids); +void (GLAPIENTRY *qglDeleteQueriesARB)(GLsizei n, const GLuint *ids); +GLboolean (GLAPIENTRY *qglIsQueryARB)(GLuint qid); +void (GLAPIENTRY *qglBeginQueryARB)(GLenum target, GLuint qid); +void (GLAPIENTRY *qglEndQueryARB)(GLenum target); +void (GLAPIENTRY *qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetQueryObjectivARB)(GLuint qid, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetQueryObjectuivARB)(GLuint qid, GLenum pname, GLuint *params); + +void (GLAPIENTRY *qglSampleCoverageARB)(GLclampf value, GLboolean invert); + +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + +qboolean GL_CheckExtension(const char *minglver_or_ext, const dllfunction_t *funcs, const char *disableparm, int silent) +{ + int failed = false; + const dllfunction_t *func; + struct { int major, minor; } min_version, curr_version; + char extstr[MAX_INPUTLINE]; + int ext; + + if(sscanf(minglver_or_ext, "%d.%d", &min_version.major, &min_version.minor) == 2) + ext = 0; // opengl version + else if(minglver_or_ext[0] != toupper(minglver_or_ext[0])) + ext = -1; // pseudo name + else + ext = 1; // extension name + + if (ext) + Con_DPrintf("checking for %s... ", minglver_or_ext); + else + Con_DPrintf("checking for OpenGL %s core features... ", minglver_or_ext); + + for (func = funcs;func && func->name;func++) + *func->funcvariable = NULL; + + if (disableparm && (COM_CheckParm(disableparm) || COM_CheckParm("-safe"))) + { + Con_DPrint("disabled by commandline\n"); + return false; + } + + if (ext == 1) // opengl extension + { + if (!strstr(gl_extensions ? gl_extensions : "", minglver_or_ext) && !strstr(gl_platformextensions ? gl_platformextensions : "", minglver_or_ext)) + { + Con_DPrint("not detected\n"); + return false; + } + } + + if(ext == 0) // opengl version + { + if (sscanf(gl_version, "%d.%d", &curr_version.major, &curr_version.minor) < 2) + curr_version.major = curr_version.minor = 1; + + if (curr_version.major < min_version.major || (curr_version.major == min_version.major && curr_version.minor < min_version.minor)) + { + Con_DPrintf("not detected (OpenGL %d.%d loaded)\n", curr_version.major, curr_version.minor); + return false; + } + } + + for (func = funcs;func && func->name != NULL;func++) + { + // Con_DPrintf("\n %s... ", func->name); + + // functions are cleared before all the extensions are evaluated + if (!(*func->funcvariable = (void *) GL_GetProcAddress(func->name))) + { + if (ext && !silent) + Con_DPrintf("%s is missing function \"%s\" - broken driver!\n", minglver_or_ext, func->name); + if (!ext) + Con_Printf("OpenGL %s core features are missing function \"%s\" - broken driver!\n", minglver_or_ext, func->name); + failed = true; + } + } + // delay the return so it prints all missing functions + if (failed) + return false; + // VorteX: add to found extension list + dpsnprintf(extstr, sizeof(extstr), "%s %s ", gl_info_extensions.string, minglver_or_ext); + Cvar_SetQuick(&gl_info_extensions, extstr); + + Con_DPrint("enabled\n"); + return true; +} + +static dllfunction_t opengl110funcs[] = +{ + {"glClearColor", (void **) &qglClearColor}, + {"glClear", (void **) &qglClear}, + {"glAlphaFunc", (void **) &qglAlphaFunc}, + {"glBlendFunc", (void **) &qglBlendFunc}, + {"glCullFace", (void **) &qglCullFace}, + {"glDrawBuffer", (void **) &qglDrawBuffer}, + {"glReadBuffer", (void **) &qglReadBuffer}, + {"glEnable", (void **) &qglEnable}, + {"glDisable", (void **) &qglDisable}, + {"glIsEnabled", (void **) &qglIsEnabled}, + {"glEnableClientState", (void **) &qglEnableClientState}, + {"glDisableClientState", (void **) &qglDisableClientState}, + {"glGetBooleanv", (void **) &qglGetBooleanv}, + {"glGetDoublev", (void **) &qglGetDoublev}, + {"glGetFloatv", (void **) &qglGetFloatv}, + {"glGetIntegerv", (void **) &qglGetIntegerv}, + {"glGetError", (void **) &qglGetError}, + {"glGetString", (void **) &qglGetString}, + {"glFinish", (void **) &qglFinish}, + {"glFlush", (void **) &qglFlush}, + {"glClearDepth", (void **) &qglClearDepth}, + {"glDepthFunc", (void **) &qglDepthFunc}, + {"glDepthMask", (void **) &qglDepthMask}, + {"glDepthRange", (void **) &qglDepthRange}, + {"glDrawElements", (void **) &qglDrawElements}, + {"glDrawArrays", (void **) &qglDrawArrays}, + {"glColorMask", (void **) &qglColorMask}, + {"glVertexPointer", (void **) &qglVertexPointer}, + {"glNormalPointer", (void **) &qglNormalPointer}, + {"glColorPointer", (void **) &qglColorPointer}, + {"glTexCoordPointer", (void **) &qglTexCoordPointer}, + {"glArrayElement", (void **) &qglArrayElement}, + {"glColor4ub", (void **) &qglColor4ub}, + {"glColor4f", (void **) &qglColor4f}, + {"glTexCoord1f", (void **) &qglTexCoord1f}, + {"glTexCoord2f", (void **) &qglTexCoord2f}, + {"glTexCoord3f", (void **) &qglTexCoord3f}, + {"glTexCoord4f", (void **) &qglTexCoord4f}, + {"glVertex2f", (void **) &qglVertex2f}, + {"glVertex3f", (void **) &qglVertex3f}, + {"glVertex4f", (void **) &qglVertex4f}, + {"glBegin", (void **) &qglBegin}, + {"glEnd", (void **) &qglEnd}, +//[515]: added on 29.07.2005 + {"glLineWidth", (void**) &qglLineWidth}, + {"glPointSize", (void**) &qglPointSize}, +// + {"glMatrixMode", (void **) &qglMatrixMode}, +// {"glOrtho", (void **) &qglOrtho}, +// {"glFrustum", (void **) &qglFrustum}, + {"glViewport", (void **) &qglViewport}, +// {"glPushMatrix", (void **) &qglPushMatrix}, +// {"glPopMatrix", (void **) &qglPopMatrix}, + {"glLoadIdentity", (void **) &qglLoadIdentity}, +// {"glLoadMatrixd", (void **) &qglLoadMatrixd}, + {"glLoadMatrixf", (void **) &qglLoadMatrixf}, +// {"glMultMatrixd", (void **) &qglMultMatrixd}, +// {"glMultMatrixf", (void **) &qglMultMatrixf}, +// {"glRotated", (void **) &qglRotated}, +// {"glRotatef", (void **) &qglRotatef}, +// {"glScaled", (void **) &qglScaled}, +// {"glScalef", (void **) &qglScalef}, +// {"glTranslated", (void **) &qglTranslated}, +// {"glTranslatef", (void **) &qglTranslatef}, + {"glReadPixels", (void **) &qglReadPixels}, + {"glStencilFunc", (void **) &qglStencilFunc}, + {"glStencilMask", (void **) &qglStencilMask}, + {"glStencilOp", (void **) &qglStencilOp}, + {"glClearStencil", (void **) &qglClearStencil}, + {"glTexEnvf", (void **) &qglTexEnvf}, + {"glTexEnvfv", (void **) &qglTexEnvfv}, + {"glTexEnvi", (void **) &qglTexEnvi}, + {"glTexParameterf", (void **) &qglTexParameterf}, + {"glTexParameterfv", (void **) &qglTexParameterfv}, + {"glTexParameteri", (void **) &qglTexParameteri}, + {"glGetTexImage", (void **) &qglGetTexImage}, + {"glGetTexParameterfv", (void **) &qglGetTexParameterfv}, + {"glGetTexParameteriv", (void **) &qglGetTexParameteriv}, + {"glGetTexLevelParameterfv", (void **) &qglGetTexLevelParameterfv}, + {"glGetTexLevelParameteriv", (void **) &qglGetTexLevelParameteriv}, + {"glHint", (void **) &qglHint}, +// {"glPixelStoref", (void **) &qglPixelStoref}, + {"glPixelStorei", (void **) &qglPixelStorei}, + {"glGenTextures", (void **) &qglGenTextures}, + {"glDeleteTextures", (void **) &qglDeleteTextures}, + {"glBindTexture", (void **) &qglBindTexture}, +// {"glPrioritizeTextures", (void **) &qglPrioritizeTextures}, +// {"glAreTexturesResident", (void **) &qglAreTexturesResident}, +// {"glIsTexture", (void **) &qglIsTexture}, +// {"glTexImage1D", (void **) &qglTexImage1D}, + {"glTexImage2D", (void **) &qglTexImage2D}, +// {"glTexSubImage1D", (void **) &qglTexSubImage1D}, + {"glTexSubImage2D", (void **) &qglTexSubImage2D}, +// {"glCopyTexImage1D", (void **) &qglCopyTexImage1D}, + {"glCopyTexImage2D", (void **) &qglCopyTexImage2D}, +// {"glCopyTexSubImage1D", (void **) &qglCopyTexSubImage1D}, + {"glCopyTexSubImage2D", (void **) &qglCopyTexSubImage2D}, + {"glScissor", (void **) &qglScissor}, + {"glPolygonOffset", (void **) &qglPolygonOffset}, + {"glPolygonMode", (void **) &qglPolygonMode}, + {"glPolygonStipple", (void **) &qglPolygonStipple}, +// {"glClipPlane", (void **) &qglClipPlane}, +// {"glGetClipPlane", (void **) &qglGetClipPlane}, + {NULL, NULL} +}; + +static dllfunction_t drawrangeelementsfuncs[] = +{ + {"glDrawRangeElements", (void **) &qglDrawRangeElements}, + {NULL, NULL} +}; + +static dllfunction_t drawrangeelementsextfuncs[] = +{ + {"glDrawRangeElementsEXT", (void **) &qglDrawRangeElementsEXT}, + {NULL, NULL} +}; + +static dllfunction_t multitexturefuncs[] = +{ + {"glMultiTexCoord1fARB", (void **) &qglMultiTexCoord1f}, + {"glMultiTexCoord2fARB", (void **) &qglMultiTexCoord2f}, + {"glMultiTexCoord3fARB", (void **) &qglMultiTexCoord3f}, + {"glMultiTexCoord4fARB", (void **) &qglMultiTexCoord4f}, + {"glActiveTextureARB", (void **) &qglActiveTexture}, + {"glClientActiveTextureARB", (void **) &qglClientActiveTexture}, + {NULL, NULL} +}; + +static dllfunction_t texture3dextfuncs[] = +{ + {"glTexImage3DEXT", (void **) &qglTexImage3D}, + {"glTexSubImage3DEXT", (void **) &qglTexSubImage3D}, + {"glCopyTexSubImage3DEXT", (void **) &qglCopyTexSubImage3D}, + {NULL, NULL} +}; + +static dllfunction_t atiseparatestencilfuncs[] = +{ + {"glStencilOpSeparateATI", (void **) &qglStencilOpSeparate}, + {"glStencilFuncSeparateATI", (void **) &qglStencilFuncSeparate}, + {NULL, NULL} +}; + +static dllfunction_t gl2separatestencilfuncs[] = +{ + {"glStencilOpSeparate", (void **) &qglStencilOpSeparate}, + {"glStencilFuncSeparate", (void **) &qglStencilFuncSeparate}, + {NULL, NULL} +}; + +static dllfunction_t stenciltwosidefuncs[] = +{ + {"glActiveStencilFaceEXT", (void **) &qglActiveStencilFaceEXT}, + {NULL, NULL} +}; + +static dllfunction_t blendequationfuncs[] = +{ + {"glBlendEquationEXT", (void **) &qglBlendEquationEXT}, + {NULL, NULL} +}; + +static dllfunction_t gl20shaderfuncs[] = +{ + {"glDeleteShader", (void **) &qglDeleteShader}, + {"glDeleteProgram", (void **) &qglDeleteProgram}, +// {"glGetHandle", (void **) &qglGetHandle}, + {"glDetachShader", (void **) &qglDetachShader}, + {"glCreateShader", (void **) &qglCreateShader}, + {"glShaderSource", (void **) &qglShaderSource}, + {"glCompileShader", (void **) &qglCompileShader}, + {"glCreateProgram", (void **) &qglCreateProgram}, + {"glAttachShader", (void **) &qglAttachShader}, + {"glLinkProgram", (void **) &qglLinkProgram}, + {"glUseProgram", (void **) &qglUseProgram}, + {"glValidateProgram", (void **) &qglValidateProgram}, + {"glUniform1f", (void **) &qglUniform1f}, + {"glUniform2f", (void **) &qglUniform2f}, + {"glUniform3f", (void **) &qglUniform3f}, + {"glUniform4f", (void **) &qglUniform4f}, + {"glUniform1i", (void **) &qglUniform1i}, + {"glUniform2i", (void **) &qglUniform2i}, + {"glUniform3i", (void **) &qglUniform3i}, + {"glUniform4i", (void **) &qglUniform4i}, + {"glUniform1fv", (void **) &qglUniform1fv}, + {"glUniform2fv", (void **) &qglUniform2fv}, + {"glUniform3fv", (void **) &qglUniform3fv}, + {"glUniform4fv", (void **) &qglUniform4fv}, + {"glUniform1iv", (void **) &qglUniform1iv}, + {"glUniform2iv", (void **) &qglUniform2iv}, + {"glUniform3iv", (void **) &qglUniform3iv}, + {"glUniform4iv", (void **) &qglUniform4iv}, + {"glUniformMatrix2fv", (void **) &qglUniformMatrix2fv}, + {"glUniformMatrix3fv", (void **) &qglUniformMatrix3fv}, + {"glUniformMatrix4fv", (void **) &qglUniformMatrix4fv}, + {"glGetShaderiv", (void **) &qglGetShaderiv}, + {"glGetProgramiv", (void **) &qglGetProgramiv}, + {"glGetShaderInfoLog", (void **) &qglGetShaderInfoLog}, + {"glGetProgramInfoLog", (void **) &qglGetProgramInfoLog}, + {"glGetAttachedShaders", (void **) &qglGetAttachedShaders}, + {"glGetUniformLocation", (void **) &qglGetUniformLocation}, + {"glGetActiveUniform", (void **) &qglGetActiveUniform}, + {"glGetUniformfv", (void **) &qglGetUniformfv}, + {"glGetUniformiv", (void **) &qglGetUniformiv}, + {"glGetShaderSource", (void **) &qglGetShaderSource}, + {"glVertexAttrib1f", (void **) &qglVertexAttrib1f}, + {"glVertexAttrib1s", (void **) &qglVertexAttrib1s}, + {"glVertexAttrib1d", (void **) &qglVertexAttrib1d}, + {"glVertexAttrib2f", (void **) &qglVertexAttrib2f}, + {"glVertexAttrib2s", (void **) &qglVertexAttrib2s}, + {"glVertexAttrib2d", (void **) &qglVertexAttrib2d}, + {"glVertexAttrib3f", (void **) &qglVertexAttrib3f}, + {"glVertexAttrib3s", (void **) &qglVertexAttrib3s}, + {"glVertexAttrib3d", (void **) &qglVertexAttrib3d}, + {"glVertexAttrib4f", (void **) &qglVertexAttrib4f}, + {"glVertexAttrib4s", (void **) &qglVertexAttrib4s}, + {"glVertexAttrib4d", (void **) &qglVertexAttrib4d}, + {"glVertexAttrib4Nub", (void **) &qglVertexAttrib4Nub}, + {"glVertexAttrib1fv", (void **) &qglVertexAttrib1fv}, + {"glVertexAttrib1sv", (void **) &qglVertexAttrib1sv}, + {"glVertexAttrib1dv", (void **) &qglVertexAttrib1dv}, + {"glVertexAttrib2fv", (void **) &qglVertexAttrib1fv}, + {"glVertexAttrib2sv", (void **) &qglVertexAttrib1sv}, + {"glVertexAttrib2dv", (void **) &qglVertexAttrib1dv}, + {"glVertexAttrib3fv", (void **) &qglVertexAttrib1fv}, + {"glVertexAttrib3sv", (void **) &qglVertexAttrib1sv}, + {"glVertexAttrib3dv", (void **) &qglVertexAttrib1dv}, + {"glVertexAttrib4fv", (void **) &qglVertexAttrib1fv}, + {"glVertexAttrib4sv", (void **) &qglVertexAttrib1sv}, + {"glVertexAttrib4dv", (void **) &qglVertexAttrib1dv}, +// {"glVertexAttrib4iv", (void **) &qglVertexAttrib1iv}, +// {"glVertexAttrib4bv", (void **) &qglVertexAttrib1bv}, +// {"glVertexAttrib4ubv", (void **) &qglVertexAttrib1ubv}, +// {"glVertexAttrib4usv", (void **) &qglVertexAttrib1usv}, +// {"glVertexAttrib4uiv", (void **) &qglVertexAttrib1uiv}, +// {"glVertexAttrib4Nbv", (void **) &qglVertexAttrib1Nbv}, +// {"glVertexAttrib4Nsv", (void **) &qglVertexAttrib1Nsv}, +// {"glVertexAttrib4Niv", (void **) &qglVertexAttrib1Niv}, +// {"glVertexAttrib4Nubv", (void **) &qglVertexAttrib1Nubv}, +// {"glVertexAttrib4Nusv", (void **) &qglVertexAttrib1Nusv}, +// {"glVertexAttrib4Nuiv", (void **) &qglVertexAttrib1Nuiv}, + {"glVertexAttribPointer", (void **) &qglVertexAttribPointer}, + {"glEnableVertexAttribArray", (void **) &qglEnableVertexAttribArray}, + {"glDisableVertexAttribArray", (void **) &qglDisableVertexAttribArray}, + {"glBindAttribLocation", (void **) &qglBindAttribLocation}, + {"glGetActiveAttrib", (void **) &qglGetActiveAttrib}, + {"glGetAttribLocation", (void **) &qglGetAttribLocation}, + {"glGetVertexAttribdv", (void **) &qglGetVertexAttribdv}, + {"glGetVertexAttribfv", (void **) &qglGetVertexAttribfv}, + {"glGetVertexAttribiv", (void **) &qglGetVertexAttribiv}, + {"glGetVertexAttribPointerv", (void **) &qglGetVertexAttribPointerv}, + {NULL, NULL} +}; + +static dllfunction_t glsl130funcs[] = +{ + {"glBindFragDataLocation", (void **) &qglBindFragDataLocation}, + {NULL, NULL} +}; + +static dllfunction_t vbofuncs[] = +{ + {"glBindBufferARB" , (void **) &qglBindBufferARB}, + {"glDeleteBuffersARB" , (void **) &qglDeleteBuffersARB}, + {"glGenBuffersARB" , (void **) &qglGenBuffersARB}, + {"glIsBufferARB" , (void **) &qglIsBufferARB}, + {"glMapBufferARB" , (void **) &qglMapBufferARB}, + {"glUnmapBufferARB" , (void **) &qglUnmapBufferARB}, + {"glBufferDataARB" , (void **) &qglBufferDataARB}, + {"glBufferSubDataARB" , (void **) &qglBufferSubDataARB}, + {NULL, NULL} +}; + +static dllfunction_t fbofuncs[] = +{ + {"glIsRenderbufferEXT" , (void **) &qglIsRenderbufferEXT}, + {"glBindRenderbufferEXT" , (void **) &qglBindRenderbufferEXT}, + {"glDeleteRenderbuffersEXT" , (void **) &qglDeleteRenderbuffersEXT}, + {"glGenRenderbuffersEXT" , (void **) &qglGenRenderbuffersEXT}, + {"glRenderbufferStorageEXT" , (void **) &qglRenderbufferStorageEXT}, + {"glGetRenderbufferParameterivEXT" , (void **) &qglGetRenderbufferParameterivEXT}, + {"glIsFramebufferEXT" , (void **) &qglIsFramebufferEXT}, + {"glBindFramebufferEXT" , (void **) &qglBindFramebufferEXT}, + {"glDeleteFramebuffersEXT" , (void **) &qglDeleteFramebuffersEXT}, + {"glGenFramebuffersEXT" , (void **) &qglGenFramebuffersEXT}, + {"glCheckFramebufferStatusEXT" , (void **) &qglCheckFramebufferStatusEXT}, +// {"glFramebufferTexture1DEXT" , (void **) &qglFramebufferTexture1DEXT}, + {"glFramebufferTexture2DEXT" , (void **) &qglFramebufferTexture2DEXT}, + {"glFramebufferTexture3DEXT" , (void **) &qglFramebufferTexture3DEXT}, + {"glFramebufferRenderbufferEXT" , (void **) &qglFramebufferRenderbufferEXT}, + {"glGetFramebufferAttachmentParameterivEXT" , (void **) &qglGetFramebufferAttachmentParameterivEXT}, + {"glGenerateMipmapEXT" , (void **) &qglGenerateMipmapEXT}, + {NULL, NULL} +}; + +static dllfunction_t texturecompressionfuncs[] = +{ + {"glCompressedTexImage3DARB", (void **) &qglCompressedTexImage3DARB}, + {"glCompressedTexImage2DARB", (void **) &qglCompressedTexImage2DARB}, +// {"glCompressedTexImage1DARB", (void **) &qglCompressedTexImage1DARB}, + {"glCompressedTexSubImage3DARB", (void **) &qglCompressedTexSubImage3DARB}, + {"glCompressedTexSubImage2DARB", (void **) &qglCompressedTexSubImage2DARB}, +// {"glCompressedTexSubImage1DARB", (void **) &qglCompressedTexSubImage1DARB}, + {"glGetCompressedTexImageARB", (void **) &qglGetCompressedTexImageARB}, + {NULL, NULL} +}; + +static dllfunction_t occlusionqueryfuncs[] = +{ + {"glGenQueriesARB", (void **) &qglGenQueriesARB}, + {"glDeleteQueriesARB", (void **) &qglDeleteQueriesARB}, + {"glIsQueryARB", (void **) &qglIsQueryARB}, + {"glBeginQueryARB", (void **) &qglBeginQueryARB}, + {"glEndQueryARB", (void **) &qglEndQueryARB}, + {"glGetQueryivARB", (void **) &qglGetQueryivARB}, + {"glGetQueryObjectivARB", (void **) &qglGetQueryObjectivARB}, + {"glGetQueryObjectuivARB", (void **) &qglGetQueryObjectuivARB}, + {NULL, NULL} +}; + +static dllfunction_t drawbuffersfuncs[] = +{ + {"glDrawBuffersARB", (void **) &qglDrawBuffersARB}, + {NULL, NULL} +}; + +static dllfunction_t multisamplefuncs[] = +{ + {"glSampleCoverageARB", (void **) &qglSampleCoverageARB}, + {NULL, NULL} +}; + +void VID_ClearExtensions(void) +{ + // VorteX: reset extensions info cvar, it got filled by GL_CheckExtension + Cvar_SetQuick(&gl_info_extensions, ""); + + // clear the extension flags + memset(&vid.support, 0, sizeof(vid.support)); + vid.renderpath = RENDERPATH_GL11; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + vid.useinterleavedarrays = false; + vid.forcevbo = false; + vid.maxtexturesize_2d = 0; + vid.maxtexturesize_3d = 0; + vid.maxtexturesize_cubemap = 0; + vid.texunits = 1; + vid.teximageunits = 1; + vid.texarrayunits = 1; + vid.max_anisotropy = 1; + vid.maxdrawbuffers = 1; + + // this is a complete list of all functions that are directly checked in the renderer + qglDrawRangeElements = NULL; + qglDrawBuffer = NULL; + qglPolygonStipple = NULL; + qglFlush = NULL; + qglActiveTexture = NULL; + qglGetCompressedTexImageARB = NULL; + qglFramebufferTexture2DEXT = NULL; + qglDrawBuffersARB = NULL; +} + +void VID_CheckExtensions(void) +{ + if (!GL_CheckExtension("glbase", opengl110funcs, NULL, false)) + Sys_Error("OpenGL 1.1.0 functions not found"); + vid.support.gl20shaders = GL_CheckExtension("2.0", gl20shaderfuncs, "-noshaders", true); + + CHECKGLERROR + + Con_DPrint("Checking OpenGL extensions...\n"); + + if (vid.support.gl20shaders) + { + // this one is purely optional, needed for GLSL 1.3 support (#version 130), so we don't even check the return value of GL_CheckExtension + vid.support.gl20shaders130 = GL_CheckExtension("glshaders130", glsl130funcs, "-noglsl130", true); + if(vid.support.gl20shaders130) + { + char *s = (char *) qglGetString(GL_SHADING_LANGUAGE_VERSION); + if(!s || atof(s) < 1.30 - 0.00001) + vid.support.gl20shaders130 = 0; + } + if(vid.support.gl20shaders130) + Con_DPrintf("Using GLSL 1.30\n"); + else + Con_DPrintf("Using GLSL 1.00\n"); + } + + // GL drivers generally prefer GL_BGRA + vid.forcetextype = GL_BGRA; + + vid.support.amd_texture_texture4 = GL_CheckExtension("GL_AMD_texture_texture4", NULL, "-notexture4", false); + vid.support.arb_depth_texture = GL_CheckExtension("GL_ARB_depth_texture", NULL, "-nodepthtexture", false); + vid.support.arb_draw_buffers = GL_CheckExtension("GL_ARB_draw_buffers", drawbuffersfuncs, "-nodrawbuffers", false); + vid.support.arb_multitexture = GL_CheckExtension("GL_ARB_multitexture", multitexturefuncs, "-nomtex", false); + vid.support.arb_occlusion_query = GL_CheckExtension("GL_ARB_occlusion_query", occlusionqueryfuncs, "-noocclusionquery", false); + vid.support.arb_shadow = GL_CheckExtension("GL_ARB_shadow", NULL, "-noshadow", false); + vid.support.arb_texture_compression = GL_CheckExtension("GL_ARB_texture_compression", texturecompressionfuncs, "-notexturecompression", false); + vid.support.arb_texture_cube_map = GL_CheckExtension("GL_ARB_texture_cube_map", NULL, "-nocubemap", false); + vid.support.arb_texture_env_combine = GL_CheckExtension("GL_ARB_texture_env_combine", NULL, "-nocombine", false) || GL_CheckExtension("GL_EXT_texture_env_combine", NULL, "-nocombine", false); + vid.support.arb_texture_gather = GL_CheckExtension("GL_ARB_texture_gather", NULL, "-notexturegather", false); +#ifndef __APPLE__ + // LordHavoc: too many bugs on OSX! + vid.support.arb_texture_non_power_of_two = GL_CheckExtension("GL_ARB_texture_non_power_of_two", NULL, "-notexturenonpoweroftwo", false); +#endif + vid.support.arb_vertex_buffer_object = GL_CheckExtension("GL_ARB_vertex_buffer_object", vbofuncs, "-novbo", false); + vid.support.ati_separate_stencil = GL_CheckExtension("separatestencil", gl2separatestencilfuncs, "-noseparatestencil", true) || GL_CheckExtension("GL_ATI_separate_stencil", atiseparatestencilfuncs, "-noseparatestencil", false); + vid.support.ext_blend_minmax = GL_CheckExtension("GL_EXT_blend_minmax", blendequationfuncs, "-noblendminmax", false); + vid.support.ext_blend_subtract = GL_CheckExtension("GL_EXT_blend_subtract", blendequationfuncs, "-noblendsubtract", false); + vid.support.ext_draw_range_elements = GL_CheckExtension("drawrangeelements", drawrangeelementsfuncs, "-nodrawrangeelements", true) || GL_CheckExtension("GL_EXT_draw_range_elements", drawrangeelementsextfuncs, "-nodrawrangeelements", false); + vid.support.ext_framebuffer_object = GL_CheckExtension("GL_EXT_framebuffer_object", fbofuncs, "-nofbo", false); + vid.support.ext_stencil_two_side = GL_CheckExtension("GL_EXT_stencil_two_side", stenciltwosidefuncs, "-nostenciltwoside", false); + vid.support.ext_texture_3d = GL_CheckExtension("GL_EXT_texture3D", texture3dextfuncs, "-notexture3d", false); + vid.support.ext_texture_compression_s3tc = GL_CheckExtension("GL_EXT_texture_compression_s3tc", NULL, "-nos3tc", false); + vid.support.ext_texture_edge_clamp = GL_CheckExtension("GL_EXT_texture_edge_clamp", NULL, "-noedgeclamp", false) || GL_CheckExtension("GL_SGIS_texture_edge_clamp", NULL, "-noedgeclamp", false); + vid.support.ext_texture_filter_anisotropic = GL_CheckExtension("GL_EXT_texture_filter_anisotropic", NULL, "-noanisotropy", false); + vid.support.ext_texture_srgb = GL_CheckExtension("GL_EXT_texture_sRGB", NULL, "-nosrgb", false); + vid.support.arb_multisample = GL_CheckExtension("GL_ARB_multisample", multisamplefuncs, "-nomultisample", false); + vid.allowalphatocoverage = false; + +// COMMANDLINEOPTION: GL: -noshaders disables use of OpenGL 2.0 shaders (which allow pixel shader effects, can improve per pixel lighting performance and capabilities) +// COMMANDLINEOPTION: GL: -noanisotropy disables GL_EXT_texture_filter_anisotropic (allows higher quality texturing) +// COMMANDLINEOPTION: GL: -noblendminmax disables GL_EXT_blend_minmax +// COMMANDLINEOPTION: GL: -noblendsubtract disables GL_EXT_blend_subtract +// COMMANDLINEOPTION: GL: -nocombine disables GL_ARB_texture_env_combine or GL_EXT_texture_env_combine (required for bumpmapping and faster map rendering) +// COMMANDLINEOPTION: GL: -nocubemap disables GL_ARB_texture_cube_map (required for bumpmapping) +// COMMANDLINEOPTION: GL: -nodepthtexture disables use of GL_ARB_depth_texture (required for shadowmapping) +// COMMANDLINEOPTION: GL: -nodrawbuffers disables use of GL_ARB_draw_buffers (required for r_shadow_deferredprepass) +// COMMANDLINEOPTION: GL: -nodrawrangeelements disables GL_EXT_draw_range_elements (renders faster) +// COMMANDLINEOPTION: GL: -noedgeclamp disables GL_EXT_texture_edge_clamp or GL_SGIS_texture_edge_clamp (recommended, some cards do not support the other texture clamp method) +// COMMANDLINEOPTION: GL: -nofbo disables GL_EXT_framebuffer_object (which accelerates rendering), only used if GL_ARB_fragment_shader is also available +// COMMANDLINEOPTION: GL: -nomtex disables GL_ARB_multitexture (required for faster map rendering) +// COMMANDLINEOPTION: GL: -noocclusionquery disables GL_ARB_occlusion_query (which allows coronas to fade according to visibility, and potentially used for rendering optimizations) +// COMMANDLINEOPTION: GL: -nos3tc disables GL_EXT_texture_compression_s3tc (which allows use of .dds texture caching) +// COMMANDLINEOPTION: GL: -noseparatestencil disables use of OpenGL2.0 glStencilOpSeparate and GL_ATI_separate_stencil extensions (which accelerate shadow rendering) +// COMMANDLINEOPTION: GL: -noshadow disables use of GL_ARB_shadow (required for hardware shadowmap filtering) +// COMMANDLINEOPTION: GL: -nostenciltwoside disables GL_EXT_stencil_two_side (which accelerate shadow rendering) +// COMMANDLINEOPTION: GL: -notexture3d disables GL_EXT_texture3D (required for spherical lights, otherwise they render as a column) +// COMMANDLINEOPTION: GL: -notexture4 disables GL_AMD_texture_texture4 (which provides fetch4 sampling) +// COMMANDLINEOPTION: GL: -notexturecompression disables GL_ARB_texture_compression (which saves video memory if it is supported, but can also degrade image quality, see gl_texturecompression cvar documentation for more information) +// COMMANDLINEOPTION: GL: -notexturegather disables GL_ARB_texture_gather (which provides fetch4 sampling) +// COMMANDLINEOPTION: GL: -notexturenonpoweroftwo disables GL_ARB_texture_non_power_of_two (which saves video memory if it is supported, but crashes on some buggy drivers) +// COMMANDLINEOPTION: GL: -novbo disables GL_ARB_vertex_buffer_object (which accelerates rendering) +// COMMANDLINEOPTION: GL: -nosrgb disables GL_EXT_texture_sRGB (which is used for higher quality non-linear texture gamma) +// COMMANDLINEOPTION: GL: -nomultisample disables GL_ARB_multisample + + if (vid.support.arb_draw_buffers) + qglGetIntegerv(GL_MAX_DRAW_BUFFERS_ARB, (GLint*)&vid.maxdrawbuffers); + + // disable non-power-of-two textures on Radeon X1600 and other cards that do not accelerate it with some filtering modes / repeat modes that we use + // we detect these cards by checking if the hardware supports vertex texture fetch (Geforce6 does, Radeon X1600 does not, all GL3-class hardware does) + if(vid.support.arb_texture_non_power_of_two && vid.support.gl20shaders) + { + int val = 0; + qglGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &val);CHECKGLERROR + if (val < 1) + vid.support.arb_texture_non_power_of_two = false; + } + + // we don't care if it's an extension or not, they are identical functions, so keep it simple in the rendering code + if (qglDrawRangeElements == NULL) + qglDrawRangeElements = qglDrawRangeElementsEXT; + + qglGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_2d); + if (vid.support.ext_texture_filter_anisotropic) + qglGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint*)&vid.max_anisotropy); + if (vid.support.arb_texture_cube_map) + qglGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, (GLint*)&vid.maxtexturesize_cubemap); + if (vid.support.ext_texture_3d) + qglGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_3d); + + // verify that 3d textures are really supported + if (vid.support.ext_texture_3d && vid.maxtexturesize_3d < 32) + { + vid.support.ext_texture_3d = false; + Con_Printf("GL_EXT_texture3D reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n"); + } + + vid.texunits = vid.teximageunits = vid.texarrayunits = 1; + if (vid.support.arb_multitexture) + qglGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, (GLint*)&vid.texunits); + if (vid_gl20.integer && vid.support.gl20shaders) + { + qglGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, (GLint*)&vid.texunits); + qglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, (int *)&vid.teximageunits);CHECKGLERROR + qglGetIntegerv(GL_MAX_TEXTURE_COORDS, (int *)&vid.texarrayunits);CHECKGLERROR + vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS); + vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS); + Con_DPrintf("Using GL2.0 rendering path - %i texture matrix, %i texture images, %i texcoords%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.support.ext_framebuffer_object ? ", shadowmapping supported" : ""); + vid.renderpath = RENDERPATH_GL20; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = true; + vid.useinterleavedarrays = false; + Con_Printf("vid.support.arb_multisample %i\n", vid.support.arb_multisample); + Con_Printf("vid.mode.samples %i\n", vid.mode.samples); + Con_Printf("vid.support.gl20shaders %i\n", vid.support.gl20shaders); + vid.allowalphatocoverage = true; // but see below, it may get turned to false again if GL_SAMPLES_ARB is <= 1 + } + else if (vid.support.arb_texture_env_combine && vid.texunits >= 2 && vid_gl13.integer) + { + qglGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, (GLint*)&vid.texunits); + vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = vid.texunits; + vid.texarrayunits = vid.texunits; + Con_DPrintf("Using GL1.3 rendering path - %i texture units, single pass rendering\n", vid.texunits); + vid.renderpath = RENDERPATH_GL13; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + vid.useinterleavedarrays = false; + } + else + { + vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = vid.texunits; + vid.texarrayunits = vid.texunits; + Con_DPrintf("Using GL1.1 rendering path - %i texture units, two pass rendering\n", vid.texunits); + vid.renderpath = RENDERPATH_GL11; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + vid.useinterleavedarrays = false; + } + + // enable multisample antialiasing if possible + if(vid.support.arb_multisample) + { + int samples = 0; + qglGetIntegerv(GL_SAMPLES_ARB, &samples); + if (samples > 1) + qglEnable(GL_MULTISAMPLE_ARB); + else + vid.allowalphatocoverage = false; + } + else + vid.allowalphatocoverage = false; + + // VorteX: set other info (maybe place them in VID_InitMode?) + Cvar_SetQuick(&gl_info_vendor, gl_vendor); + Cvar_SetQuick(&gl_info_renderer, gl_renderer); + Cvar_SetQuick(&gl_info_version, gl_version); + Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); + Cvar_SetQuick(&gl_info_driver, gl_driver); +} + +float VID_JoyState_GetAxis(const vid_joystate_t *joystate, int axis, float sensitivity, float deadzone) +{ + float value; + value = (axis >= 0 && axis < MAXJOYAXIS) ? joystate->axis[axis] : 0.0f; + value = value > deadzone ? (value - deadzone) : (value < -deadzone ? (value + deadzone) : 0.0f); + value *= deadzone > 0 ? (1.0f / (1.0f - deadzone)) : 1.0f; + value = bound(-1, value, 1); + return value * sensitivity; +} + +qboolean VID_JoyBlockEmulatedKeys(int keycode) +{ + int j; + vid_joystate_t joystate; + + if (!joy_axiskeyevents.integer) + return false; + if (vid_joystate.is360) + return false; + if (keycode != K_UPARROW && keycode != K_DOWNARROW && keycode != K_RIGHTARROW && keycode != K_LEFTARROW) + return false; + + // block system-generated key events for arrow keys if we're emulating the arrow keys ourselves + VID_BuildJoyState(&joystate); + for (j = 32;j < 36;j++) + if (vid_joystate.button[j] || joystate.button[j]) + return true; + + return false; +} + +void VID_Shared_BuildJoyState_Begin(vid_joystate_t *joystate) +{ +#ifdef WIN32 + xinput_state_t xinputstate; +#endif + memset(joystate, 0, sizeof(*joystate)); +#ifdef WIN32 + if (vid_xinputindex >= 0 && qXInputGetState && qXInputGetState(vid_xinputindex, &xinputstate) == S_OK) + { + joystate->is360 = true; + joystate->button[ 0] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0; + joystate->button[ 1] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0; + joystate->button[ 2] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0; + joystate->button[ 3] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0; + joystate->button[ 4] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_START) != 0; + joystate->button[ 5] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) != 0; + joystate->button[ 6] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0; + joystate->button[ 7] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0; + joystate->button[ 8] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0; + joystate->button[ 9] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0; + joystate->button[10] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0; + joystate->button[11] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_B) != 0; + joystate->button[12] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_X) != 0; + joystate->button[13] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y) != 0; + joystate->button[14] = xinputstate.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; + joystate->button[15] = xinputstate.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; + joystate->button[16] = xinputstate.Gamepad.sThumbLY < -16384; + joystate->button[17] = xinputstate.Gamepad.sThumbLY > 16384; + joystate->button[18] = xinputstate.Gamepad.sThumbLX < -16384; + joystate->button[19] = xinputstate.Gamepad.sThumbLX > 16384; + joystate->button[20] = xinputstate.Gamepad.sThumbRY < -16384; + joystate->button[21] = xinputstate.Gamepad.sThumbRY > 16384; + joystate->button[22] = xinputstate.Gamepad.sThumbRX < -16384; + joystate->button[23] = xinputstate.Gamepad.sThumbRX > 16384; + joystate->axis[ 4] = xinputstate.Gamepad.bLeftTrigger * (1.0f / 255.0f); + joystate->axis[ 5] = xinputstate.Gamepad.bRightTrigger * (1.0f / 255.0f); + joystate->axis[ 0] = xinputstate.Gamepad.sThumbLX * (1.0f / 32767.0f); + joystate->axis[ 1] = xinputstate.Gamepad.sThumbLY * (1.0f / 32767.0f); + joystate->axis[ 2] = xinputstate.Gamepad.sThumbRX * (1.0f / 32767.0f); + joystate->axis[ 3] = xinputstate.Gamepad.sThumbRY * (1.0f / 32767.0f); + } +#endif +} + +void VID_Shared_BuildJoyState_Finish(vid_joystate_t *joystate) +{ + float f, r; + if (joystate->is360) + return; + // emulate key events for thumbstick + f = VID_JoyState_GetAxis(joystate, joy_axisforward.integer, 1, joy_axiskeyevents_deadzone.value) * joy_sensitivityforward.value; + r = VID_JoyState_GetAxis(joystate, joy_axisside.integer , 1, joy_axiskeyevents_deadzone.value) * joy_sensitivityside.value; +#if MAXJOYBUTTON != 36 +#error this code must be updated if MAXJOYBUTTON changes! +#endif + joystate->button[32] = f > 0.0f; + joystate->button[33] = f < 0.0f; + joystate->button[34] = r > 0.0f; + joystate->button[35] = r < 0.0f; +} + +void VID_KeyEventForButton(qboolean oldbutton, qboolean newbutton, int key, double *timer) +{ + if (oldbutton) + { + if (newbutton) + { + if (realtime >= *timer) + { + Key_Event(key, 0, true); + *timer = realtime + 0.1; + } + } + else + { + Key_Event(key, 0, false); + *timer = 0; + } + } + else + { + if (newbutton) + { + Key_Event(key, 0, true); + *timer = realtime + 0.5; + } + } +} + +#if MAXJOYBUTTON != 36 +#error this code must be updated if MAXJOYBUTTON changes! +#endif +static int joybuttonkey[MAXJOYBUTTON][2] = +{ + {K_JOY1, K_ENTER}, {K_JOY2, K_ESCAPE}, {K_JOY3, 0}, {K_JOY4, 0}, {K_JOY5, 0}, {K_JOY6, 0}, {K_JOY7, 0}, {K_JOY8, 0}, {K_JOY9, 0}, {K_JOY10, 0}, {K_JOY11, 0}, {K_JOY12, 0}, {K_JOY13, 0}, {K_JOY14, 0}, {K_JOY15, 0}, {K_JOY16, 0}, + {K_AUX1, 0}, {K_AUX2, 0}, {K_AUX3, 0}, {K_AUX4, 0}, {K_AUX5, 0}, {K_AUX6, 0}, {K_AUX7, 0}, {K_AUX8, 0}, {K_AUX9, 0}, {K_AUX10, 0}, {K_AUX11, 0}, {K_AUX12, 0}, {K_AUX13, 0}, {K_AUX14, 0}, {K_AUX15, 0}, {K_AUX16, 0}, + {K_JOY_UP, K_UPARROW}, {K_JOY_DOWN, K_DOWNARROW}, {K_JOY_RIGHT, K_RIGHTARROW}, {K_JOY_LEFT, K_LEFTARROW}, +}; + +static int joybuttonkey360[][2] = +{ + {K_X360_DPAD_UP, K_UPARROW}, + {K_X360_DPAD_DOWN, K_DOWNARROW}, + {K_X360_DPAD_LEFT, K_LEFTARROW}, + {K_X360_DPAD_RIGHT, K_RIGHTARROW}, + {K_X360_START, K_ESCAPE}, + {K_X360_BACK, K_ESCAPE}, + {K_X360_LEFT_THUMB, 0}, + {K_X360_RIGHT_THUMB, 0}, + {K_X360_LEFT_SHOULDER, 0}, + {K_X360_RIGHT_SHOULDER, 0}, + {K_X360_A, K_ENTER}, + {K_X360_B, K_ESCAPE}, + {K_X360_X, 0}, + {K_X360_Y, 0}, + {K_X360_LEFT_TRIGGER, 0}, + {K_X360_RIGHT_TRIGGER, 0}, + {K_X360_LEFT_THUMB_DOWN, K_DOWNARROW}, + {K_X360_LEFT_THUMB_UP, K_UPARROW}, + {K_X360_LEFT_THUMB_LEFT, K_LEFTARROW}, + {K_X360_LEFT_THUMB_RIGHT, K_RIGHTARROW}, + {K_X360_RIGHT_THUMB_DOWN, 0}, + {K_X360_RIGHT_THUMB_UP, 0}, + {K_X360_RIGHT_THUMB_LEFT, 0}, + {K_X360_RIGHT_THUMB_RIGHT, 0}, +}; + +double vid_joybuttontimer[MAXJOYBUTTON]; +void VID_ApplyJoyState(vid_joystate_t *joystate) +{ + int j; + int c = joy_axiskeyevents.integer != 0; + if (joystate->is360) + { +#if 0 + // keystrokes (chatpad) + // DOES NOT WORK - no driver support in xinput1_3.dll :( + xinput_keystroke_t keystroke; + while (qXInputGetKeystroke && qXInputGetKeystroke(XUSER_INDEX_ANY, 0, &keystroke) == S_OK) + Con_Printf("XInput KeyStroke: VirtualKey %i, Unicode %i, Flags %x, UserIndex %i, HidCode %i\n", keystroke.VirtualKey, keystroke.Unicode, keystroke.Flags, keystroke.UserIndex, keystroke.HidCode); +#endif + + // emit key events for buttons + for (j = 0;j < (int)(sizeof(joybuttonkey360)/sizeof(joybuttonkey360[0]));j++) + VID_KeyEventForButton(vid_joystate.button[j] != 0, joystate->button[j] != 0, joybuttonkey360[j][c], &vid_joybuttontimer[j]); + + // axes + cl.cmd.forwardmove += VID_JoyState_GetAxis(joystate, joy_x360_axisforward.integer, joy_x360_sensitivityforward.value, joy_x360_deadzoneforward.value) * cl_forwardspeed.value; + cl.cmd.sidemove += VID_JoyState_GetAxis(joystate, joy_x360_axisside.integer, joy_x360_sensitivityside.value, joy_x360_deadzoneside.value) * cl_sidespeed.value; + cl.cmd.upmove += VID_JoyState_GetAxis(joystate, joy_x360_axisup.integer, joy_x360_sensitivityup.value, joy_x360_deadzoneup.value) * cl_upspeed.value; + cl.viewangles[0] += VID_JoyState_GetAxis(joystate, joy_x360_axispitch.integer, joy_x360_sensitivitypitch.value, joy_x360_deadzonepitch.value) * cl.realframetime * cl_pitchspeed.value; + cl.viewangles[1] += VID_JoyState_GetAxis(joystate, joy_x360_axisyaw.integer, joy_x360_sensitivityyaw.value, joy_x360_deadzoneyaw.value) * cl.realframetime * cl_yawspeed.value; + //cl.viewangles[2] += VID_JoyState_GetAxis(joystate, joy_x360_axisroll.integer, joy_x360_sensitivityroll.value, joy_x360_deadzoneroll.value) * cl.realframetime * cl_rollspeed.value; + } + else + { + // emit key events for buttons + for (j = 0;j < MAXJOYBUTTON;j++) + VID_KeyEventForButton(vid_joystate.button[j] != 0, joystate->button[j] != 0, joybuttonkey[j][c], &vid_joybuttontimer[j]); + + // axes + cl.cmd.forwardmove += VID_JoyState_GetAxis(joystate, joy_axisforward.integer, joy_sensitivityforward.value, joy_deadzoneforward.value) * cl_forwardspeed.value; + cl.cmd.sidemove += VID_JoyState_GetAxis(joystate, joy_axisside.integer, joy_sensitivityside.value, joy_deadzoneside.value) * cl_sidespeed.value; + cl.cmd.upmove += VID_JoyState_GetAxis(joystate, joy_axisup.integer, joy_sensitivityup.value, joy_deadzoneup.value) * cl_upspeed.value; + cl.viewangles[0] += VID_JoyState_GetAxis(joystate, joy_axispitch.integer, joy_sensitivitypitch.value, joy_deadzonepitch.value) * cl.realframetime * cl_pitchspeed.value; + cl.viewangles[1] += VID_JoyState_GetAxis(joystate, joy_axisyaw.integer, joy_sensitivityyaw.value, joy_deadzoneyaw.value) * cl.realframetime * cl_yawspeed.value; + //cl.viewangles[2] += VID_JoyState_GetAxis(joystate, joy_axisroll.integer, joy_sensitivityroll.value, joy_deadzoneroll.value) * cl.realframetime * cl_rollspeed.value; + } + + vid_joystate = *joystate; +} + +int VID_Shared_SetJoystick(int index) +{ +#ifdef WIN32 + int i; + int xinputcount = 0; + int xinputindex = -1; + int xinputavailable = 0; + xinput_state_t state; + // detect available XInput controllers + for (i = 0;i < 4;i++) + { + if (qXInputGetState && qXInputGetState(i, &state) == S_OK) + { + xinputavailable |= 1<= 0) + Con_Printf("Joystick %i opened (XInput Device %i)\n", index, xinputindex); + } + return xinputcount; +#else + return 0; +#endif +} + + +void Force_CenterView_f (void) +{ + cl.viewangles[PITCH] = 0; +} + +static int gamma_forcenextframe = false; +static float cachegamma, cachebrightness, cachecontrast, cacheblack[3], cachegrey[3], cachewhite[3], cachecontrastboost; +static int cachecolorenable, cachehwgamma; + +unsigned int vid_gammatables_serial = 0; // so other subsystems can poll if gamma parameters have changed +qboolean vid_gammatables_trivial = true; +void VID_BuildGammaTables(unsigned short *ramps, int rampsize) +{ + float srgbmul = (vid.sRGB2D || vid.sRGB3D) ? 2.2f : 1.0f; + if (cachecolorenable) + { + BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[0]) * srgbmul, cachewhite[0], cacheblack[0], cachecontrastboost, ramps, rampsize); + BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[1]) * srgbmul, cachewhite[1], cacheblack[1], cachecontrastboost, ramps + rampsize, rampsize); + BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[2]) * srgbmul, cachewhite[2], cacheblack[2], cachecontrastboost, ramps + rampsize*2, rampsize); + } + else + { + BuildGammaTable16(1.0f, cachegamma * srgbmul, cachecontrast, cachebrightness, cachecontrastboost, ramps, rampsize); + BuildGammaTable16(1.0f, cachegamma * srgbmul, cachecontrast, cachebrightness, cachecontrastboost, ramps + rampsize, rampsize); + BuildGammaTable16(1.0f, cachegamma * srgbmul, cachecontrast, cachebrightness, cachecontrastboost, ramps + rampsize*2, rampsize); + } + + // LordHavoc: this code came from Ben Winslow and Zinx Verituse, I have + // immensely butchered it to work with variable framerates and fit in with + // the rest of darkplaces. + if (v_psycho.integer) + { + int x, y; + float t; + static float n[3], nd[3], nt[3]; + static int init = true; + unsigned short *ramp; + gamma_forcenextframe = true; + if (init) + { + init = false; + for (x = 0;x < 3;x++) + { + n[x] = lhrandom(0, 1); + nd[x] = (rand()&1)?-0.25:0.25; + nt[x] = lhrandom(1, 8.2); + } + } + + for (x = 0;x < 3;x++) + { + nt[x] -= cl.realframetime; + if (nt[x] < 0) + { + nd[x] = -nd[x]; + nt[x] += lhrandom(1, 8.2); + } + n[x] += nd[x] * cl.realframetime; + n[x] -= floor(n[x]); + } + + for (x = 0, ramp = ramps;x < 3;x++) + for (y = 0, t = n[x] - 0.75f;y < rampsize;y++, t += 0.75f * (2.0f / rampsize)) + *ramp++ = (unsigned short)(cos(t*(M_PI*2.0)) * 32767.0f + 32767.0f); + } +} + +void VID_UpdateGamma(qboolean force, int rampsize) +{ + cvar_t *c; + float f; + int wantgamma; + qboolean gamma_changed = false; + + // LordHavoc: don't mess with gamma tables if running dedicated + if (cls.state == ca_dedicated) + return; + + wantgamma = v_hwgamma.integer; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + if (v_glslgamma.integer) + wantgamma = 0; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + } + if(!vid_activewindow) + wantgamma = 0; +#define BOUNDCVAR(cvar, m1, m2) c = &(cvar);f = bound(m1, c->value, m2);if (c->value != f) Cvar_SetValueQuick(c, f); + BOUNDCVAR(v_gamma, 0.1, 5); + BOUNDCVAR(v_contrast, 0.2, 5); + BOUNDCVAR(v_brightness, -v_contrast.value * 0.8, 0.8); + //BOUNDCVAR(v_contrastboost, 0.0625, 16); + BOUNDCVAR(v_color_black_r, 0, 0.8); + BOUNDCVAR(v_color_black_g, 0, 0.8); + BOUNDCVAR(v_color_black_b, 0, 0.8); + BOUNDCVAR(v_color_grey_r, 0, 0.95); + BOUNDCVAR(v_color_grey_g, 0, 0.95); + BOUNDCVAR(v_color_grey_b, 0, 0.95); + BOUNDCVAR(v_color_white_r, 1, 5); + BOUNDCVAR(v_color_white_g, 1, 5); + BOUNDCVAR(v_color_white_b, 1, 5); +#undef BOUNDCVAR + + // set vid_gammatables_trivial to true if the current settings would generate the identity gamma table + vid_gammatables_trivial = false; + if(v_psycho.integer == 0) + if(v_contrastboost.value == 1) + if(!vid.sRGB2D) + if(!vid.sRGB3D) + { + if(v_color_enable.integer) + { + if(v_color_black_r.value == 0) + if(v_color_black_g.value == 0) + if(v_color_black_b.value == 0) + if(fabs(v_color_grey_r.value - 0.5) < 1e-6) + if(fabs(v_color_grey_g.value - 0.5) < 1e-6) + if(fabs(v_color_grey_b.value - 0.5) < 1e-6) + if(v_color_white_r.value == 1) + if(v_color_white_g.value == 1) + if(v_color_white_b.value == 1) + vid_gammatables_trivial = true; + } + else + { + if(v_gamma.value == 1) + if(v_contrast.value == 1) + if(v_brightness.value == 0) + vid_gammatables_trivial = true; + } + } + +#define GAMMACHECK(cache, value) if (cache != (value)) gamma_changed = true;cache = (value) + if(v_psycho.integer) + gamma_changed = true; + GAMMACHECK(cachegamma , v_gamma.value); + GAMMACHECK(cachecontrast , v_contrast.value); + GAMMACHECK(cachebrightness , v_brightness.value); + GAMMACHECK(cachecontrastboost, v_contrastboost.value); + GAMMACHECK(cachecolorenable, v_color_enable.integer); + GAMMACHECK(cacheblack[0] , v_color_black_r.value); + GAMMACHECK(cacheblack[1] , v_color_black_g.value); + GAMMACHECK(cacheblack[2] , v_color_black_b.value); + GAMMACHECK(cachegrey[0] , v_color_grey_r.value); + GAMMACHECK(cachegrey[1] , v_color_grey_g.value); + GAMMACHECK(cachegrey[2] , v_color_grey_b.value); + GAMMACHECK(cachewhite[0] , v_color_white_r.value); + GAMMACHECK(cachewhite[1] , v_color_white_g.value); + GAMMACHECK(cachewhite[2] , v_color_white_b.value); + + if(gamma_changed) + ++vid_gammatables_serial; + + GAMMACHECK(cachehwgamma , wantgamma); +#undef GAMMACHECK + + if (!force && !gamma_forcenextframe && !gamma_changed) + return; + + gamma_forcenextframe = false; + + if (cachehwgamma) + { + if (!vid_usinghwgamma) + { + vid_usinghwgamma = true; + if (vid_gammarampsize != rampsize || !vid_gammaramps) + { + vid_gammarampsize = rampsize; + if (vid_gammaramps) + Z_Free(vid_gammaramps); + vid_gammaramps = (unsigned short *)Z_Malloc(6 * vid_gammarampsize * sizeof(unsigned short)); + vid_systemgammaramps = vid_gammaramps + 3 * vid_gammarampsize; + } + VID_GetGamma(vid_systemgammaramps, vid_gammarampsize); + } + + VID_BuildGammaTables(vid_gammaramps, vid_gammarampsize); + + // set vid_hardwaregammasupported to true if VID_SetGamma succeeds, OR if vid_hwgamma is >= 2 (forced gamma - ignores driver return value) + Cvar_SetValueQuick(&vid_hardwaregammasupported, VID_SetGamma(vid_gammaramps, vid_gammarampsize) || cachehwgamma >= 2); + // if custom gamma ramps failed (Windows stupidity), restore to system gamma + if(!vid_hardwaregammasupported.integer) + { + if (vid_usinghwgamma) + { + vid_usinghwgamma = false; + VID_SetGamma(vid_systemgammaramps, vid_gammarampsize); + } + } + } + else + { + if (vid_usinghwgamma) + { + vid_usinghwgamma = false; + VID_SetGamma(vid_systemgammaramps, vid_gammarampsize); + } + } +} + +void VID_RestoreSystemGamma(void) +{ + if (vid_usinghwgamma) + { + vid_usinghwgamma = false; + Cvar_SetValueQuick(&vid_hardwaregammasupported, VID_SetGamma(vid_systemgammaramps, vid_gammarampsize)); + // force gamma situation to be reexamined next frame + gamma_forcenextframe = true; + } +} + +#ifdef WIN32 +static dllfunction_t xinputdllfuncs[] = +{ + {"XInputGetState", (void **) &qXInputGetState}, + {"XInputGetKeystroke", (void **) &qXInputGetKeystroke}, + {NULL, NULL} +}; +static const char* xinputdllnames [] = +{ + "xinput1_3.dll", + "xinput1_2.dll", + "xinput1_1.dll", + NULL +}; +static dllhandle_t xinputdll_dll = NULL; +#endif + +void VID_Shared_Init(void) +{ +#ifdef SSE_POSSIBLE + if (Sys_HaveSSE2()) + { + Con_Printf("DPSOFTRAST available (SSE2 instructions detected)\n"); + Cvar_RegisterVariable(&vid_soft); + Cvar_RegisterVariable(&vid_soft_threads); + Cvar_RegisterVariable(&vid_soft_interlace); + } + else + Con_Printf("DPSOFTRAST not available (SSE2 disabled or not detected)\n"); +#else + Con_Printf("DPSOFTRAST not available (SSE2 not compiled in)\n"); +#endif + + Cvar_RegisterVariable(&vid_hardwaregammasupported); + Cvar_RegisterVariable(&gl_info_vendor); + Cvar_RegisterVariable(&gl_info_renderer); + Cvar_RegisterVariable(&gl_info_version); + Cvar_RegisterVariable(&gl_info_extensions); + Cvar_RegisterVariable(&gl_info_platform); + Cvar_RegisterVariable(&gl_info_driver); + Cvar_RegisterVariable(&v_gamma); + Cvar_RegisterVariable(&v_brightness); + Cvar_RegisterVariable(&v_contrastboost); + Cvar_RegisterVariable(&v_contrast); + + Cvar_RegisterVariable(&v_color_enable); + Cvar_RegisterVariable(&v_color_black_r); + Cvar_RegisterVariable(&v_color_black_g); + Cvar_RegisterVariable(&v_color_black_b); + Cvar_RegisterVariable(&v_color_grey_r); + Cvar_RegisterVariable(&v_color_grey_g); + Cvar_RegisterVariable(&v_color_grey_b); + Cvar_RegisterVariable(&v_color_white_r); + Cvar_RegisterVariable(&v_color_white_g); + Cvar_RegisterVariable(&v_color_white_b); + + Cvar_RegisterVariable(&v_hwgamma); + Cvar_RegisterVariable(&v_glslgamma); + + Cvar_RegisterVariable(&v_psycho); + + Cvar_RegisterVariable(&vid_fullscreen); + Cvar_RegisterVariable(&vid_width); + Cvar_RegisterVariable(&vid_height); + Cvar_RegisterVariable(&vid_bitsperpixel); + Cvar_RegisterVariable(&vid_samples); + Cvar_RegisterVariable(&vid_refreshrate); + Cvar_RegisterVariable(&vid_userefreshrate); + Cvar_RegisterVariable(&vid_stereobuffer); + Cvar_RegisterVariable(&vid_vsync); + Cvar_RegisterVariable(&vid_mouse); + Cvar_RegisterVariable(&vid_grabkeyboard); + Cvar_RegisterVariable(&vid_touchscreen); + Cvar_RegisterVariable(&vid_stick_mouse); + Cvar_RegisterVariable(&vid_resizable); + Cvar_RegisterVariable(&vid_minwidth); + Cvar_RegisterVariable(&vid_minheight); + Cvar_RegisterVariable(&vid_gl13); + Cvar_RegisterVariable(&vid_gl20); + Cvar_RegisterVariable(&gl_finish); + Cvar_RegisterVariable(&vid_sRGB); + + Cvar_RegisterVariable(&joy_active); +#ifdef WIN32 + Cvar_RegisterVariable(&joy_xinputavailable); +#endif + Cvar_RegisterVariable(&joy_detected); + Cvar_RegisterVariable(&joy_enable); + Cvar_RegisterVariable(&joy_index); + Cvar_RegisterVariable(&joy_axisforward); + Cvar_RegisterVariable(&joy_axisside); + Cvar_RegisterVariable(&joy_axisup); + Cvar_RegisterVariable(&joy_axispitch); + Cvar_RegisterVariable(&joy_axisyaw); + //Cvar_RegisterVariable(&joy_axisroll); + Cvar_RegisterVariable(&joy_deadzoneforward); + Cvar_RegisterVariable(&joy_deadzoneside); + Cvar_RegisterVariable(&joy_deadzoneup); + Cvar_RegisterVariable(&joy_deadzonepitch); + Cvar_RegisterVariable(&joy_deadzoneyaw); + //Cvar_RegisterVariable(&joy_deadzoneroll); + Cvar_RegisterVariable(&joy_sensitivityforward); + Cvar_RegisterVariable(&joy_sensitivityside); + Cvar_RegisterVariable(&joy_sensitivityup); + Cvar_RegisterVariable(&joy_sensitivitypitch); + Cvar_RegisterVariable(&joy_sensitivityyaw); + //Cvar_RegisterVariable(&joy_sensitivityroll); + Cvar_RegisterVariable(&joy_axiskeyevents); + Cvar_RegisterVariable(&joy_axiskeyevents_deadzone); + Cvar_RegisterVariable(&joy_x360_axisforward); + Cvar_RegisterVariable(&joy_x360_axisside); + Cvar_RegisterVariable(&joy_x360_axisup); + Cvar_RegisterVariable(&joy_x360_axispitch); + Cvar_RegisterVariable(&joy_x360_axisyaw); + //Cvar_RegisterVariable(&joy_x360_axisroll); + Cvar_RegisterVariable(&joy_x360_deadzoneforward); + Cvar_RegisterVariable(&joy_x360_deadzoneside); + Cvar_RegisterVariable(&joy_x360_deadzoneup); + Cvar_RegisterVariable(&joy_x360_deadzonepitch); + Cvar_RegisterVariable(&joy_x360_deadzoneyaw); + //Cvar_RegisterVariable(&joy_x360_deadzoneroll); + Cvar_RegisterVariable(&joy_x360_sensitivityforward); + Cvar_RegisterVariable(&joy_x360_sensitivityside); + Cvar_RegisterVariable(&joy_x360_sensitivityup); + Cvar_RegisterVariable(&joy_x360_sensitivitypitch); + Cvar_RegisterVariable(&joy_x360_sensitivityyaw); + //Cvar_RegisterVariable(&joy_x360_sensitivityroll); + +#ifdef WIN32 + Sys_LoadLibrary(xinputdllnames, &xinputdll_dll, xinputdllfuncs); +#endif + + Cmd_AddCommand("force_centerview", Force_CenterView_f, "recenters view (stops looking up/down)"); + Cmd_AddCommand("vid_restart", VID_Restart_f, "restarts video system (closes and reopens the window, restarts renderer)"); +} + +int VID_Mode(int fullscreen, int width, int height, int bpp, float refreshrate, int stereobuffer, int samples) +{ + viddef_mode_t mode; + + memset(&mode, 0, sizeof(mode)); + mode.fullscreen = fullscreen != 0; + mode.width = width; + mode.height = height; + mode.bitsperpixel = bpp; + mode.refreshrate = vid_userefreshrate.integer ? max(1, refreshrate) : 0; + mode.userefreshrate = vid_userefreshrate.integer != 0; + mode.stereobuffer = stereobuffer != 0; + mode.samples = samples; + cl_ignoremousemoves = 2; + VID_ClearExtensions(); + if (VID_InitMode(&mode)) + { + // accept the (possibly modified) mode + vid.mode = mode; + vid.fullscreen = vid.mode.fullscreen; + vid.width = vid.mode.width; + vid.height = vid.mode.height; + vid.bitsperpixel = vid.mode.bitsperpixel; + vid.refreshrate = vid.mode.refreshrate; + vid.userefreshrate = vid.mode.userefreshrate; + vid.stereobuffer = vid.mode.stereobuffer; + vid.samples = vid.mode.samples; + vid.stencil = vid.mode.bitsperpixel > 16; + vid.sRGB2D = vid_sRGB.integer >= 1 && vid.sRGBcapable2D; + vid.sRGB3D = vid_sRGB.integer >= 1 && vid.sRGBcapable3D; + + Con_Printf("Video Mode: %s %dx%dx%dx%.2fhz%s%s\n", mode.fullscreen ? "fullscreen" : "window", mode.width, mode.height, mode.bitsperpixel, mode.refreshrate, mode.stereobuffer ? " stereo" : "", mode.samples > 1 ? va(" (%ix AA)", mode.samples) : ""); + + Cvar_SetValueQuick(&vid_fullscreen, vid.mode.fullscreen); + Cvar_SetValueQuick(&vid_width, vid.mode.width); + Cvar_SetValueQuick(&vid_height, vid.mode.height); + Cvar_SetValueQuick(&vid_bitsperpixel, vid.mode.bitsperpixel); + Cvar_SetValueQuick(&vid_samples, vid.mode.samples); + if(vid_userefreshrate.integer) + Cvar_SetValueQuick(&vid_refreshrate, vid.mode.refreshrate); + Cvar_SetValueQuick(&vid_stereobuffer, vid.mode.stereobuffer); + + return true; + } + else + return false; +} + +static void VID_OpenSystems(void) +{ + R_Modules_Start(); + S_Startup(); +} + +static void VID_CloseSystems(void) +{ + S_Shutdown(); + R_Modules_Shutdown(); +} + +qboolean vid_commandlinecheck = true; +extern qboolean vid_opened; + +void VID_Restart_f(void) +{ + // don't crash if video hasn't started yet + if (vid_commandlinecheck) + return; + + if (!vid_opened) + { + SCR_BeginLoadingPlaque(); + return; + } + + Con_Printf("VID_Restart: changing from %s %dx%dx%dbpp%s%s, to %s %dx%dx%dbpp%s%s.\n", + vid.mode.fullscreen ? "fullscreen" : "window", vid.mode.width, vid.mode.height, vid.mode.bitsperpixel, vid.mode.fullscreen && vid.mode.userefreshrate ? va("x%.2fhz", vid.mode.refreshrate) : "", vid.mode.samples > 1 ? va(" (%ix AA)", vid.mode.samples) : "", + vid_fullscreen.integer ? "fullscreen" : "window", vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_fullscreen.integer && vid_userefreshrate.integer ? va("x%.2fhz", vid_refreshrate.value) : "", vid_samples.integer > 1 ? va(" (%ix AA)", vid_samples.integer) : ""); + VID_CloseSystems(); + VID_Shutdown(); + if (!VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer)) + { + Con_Print("Video mode change failed\n"); + if (!VID_Mode(vid.mode.fullscreen, vid.mode.width, vid.mode.height, vid.mode.bitsperpixel, vid.mode.refreshrate, vid.mode.stereobuffer, vid.mode.samples)) + Sys_Error("Unable to restore to last working video mode"); + } + VID_OpenSystems(); +} + +const char *vidfallbacks[][2] = +{ + {"vid_stereobuffer", "0"}, + {"vid_samples", "1"}, + {"vid_userefreshrate", "0"}, + {"vid_width", "640"}, + {"vid_height", "480"}, + {"vid_bitsperpixel", "16"}, + {NULL, NULL} +}; + +// this is only called once by Host_StartVideo and again on each FS_GameDir_f +void VID_Start(void) +{ + int i, width, height, success; + if (vid_commandlinecheck) + { + // interpret command-line parameters + vid_commandlinecheck = false; +// COMMANDLINEOPTION: Video: -window performs +vid_fullscreen 0 + if (COM_CheckParm("-window") || COM_CheckParm("-safe")) + Cvar_SetValueQuick(&vid_fullscreen, false); +// COMMANDLINEOPTION: Video: -fullscreen performs +vid_fullscreen 1 + if (COM_CheckParm("-fullscreen")) + Cvar_SetValueQuick(&vid_fullscreen, true); + width = 0; + height = 0; +// COMMANDLINEOPTION: Video: -width performs +vid_width and also +vid_height if only -width is specified (example: -width 1024 sets 1024x768 mode) + if ((i = COM_CheckParm("-width")) != 0) + width = atoi(com_argv[i+1]); +// COMMANDLINEOPTION: Video: -height performs +vid_height and also +vid_width if only -height is specified (example: -height 768 sets 1024x768 mode) + if ((i = COM_CheckParm("-height")) != 0) + height = atoi(com_argv[i+1]); + if (width == 0) + width = height * 4 / 3; + if (height == 0) + height = width * 3 / 4; + if (width) + Cvar_SetValueQuick(&vid_width, width); + if (height) + Cvar_SetValueQuick(&vid_height, height); +// COMMANDLINEOPTION: Video: -bpp performs +vid_bitsperpixel (example -bpp 32 or -bpp 16) + if ((i = COM_CheckParm("-bpp")) != 0) + Cvar_SetQuick(&vid_bitsperpixel, com_argv[i+1]); + } + + success = VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer); + if (!success) + { + Con_Print("Desired video mode fail, trying fallbacks...\n"); + for (i = 0;!success && vidfallbacks[i][0] != NULL;i++) + { + Cvar_Set(vidfallbacks[i][0], vidfallbacks[i][1]); + success = VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer); + } + if (!success) + Sys_Error("Video modes failed"); + } + VID_OpenSystems(); +} + +void VID_Stop(void) +{ + VID_CloseSystems(); + VID_Shutdown(); +} + +int VID_SortModes_Compare(const void *a_, const void *b_) +{ + vid_mode_t *a = (vid_mode_t *) a_; + vid_mode_t *b = (vid_mode_t *) b_; + if(a->width > b->width) + return +1; + if(a->width < b->width) + return -1; + if(a->height > b->height) + return +1; + if(a->height < b->height) + return -1; + if(a->refreshrate > b->refreshrate) + return +1; + if(a->refreshrate < b->refreshrate) + return -1; + if(a->bpp > b->bpp) + return +1; + if(a->bpp < b->bpp) + return -1; + if(a->pixelheight_num * b->pixelheight_denom > a->pixelheight_denom * b->pixelheight_num) + return +1; + if(a->pixelheight_num * b->pixelheight_denom < a->pixelheight_denom * b->pixelheight_num) + return -1; + return 0; +} +size_t VID_SortModes(vid_mode_t *modes, size_t count, qboolean usebpp, qboolean userefreshrate, qboolean useaspect) +{ + size_t i; + if(count == 0) + return 0; + // 1. sort them + qsort(modes, count, sizeof(*modes), VID_SortModes_Compare); + // 2. remove duplicates + for(i = 0; i < count; ++i) + { + if(modes[i].width && modes[i].height) + { + if(i == 0) + continue; + if(modes[i].width != modes[i-1].width) + continue; + if(modes[i].height != modes[i-1].height) + continue; + if(userefreshrate) + if(modes[i].refreshrate != modes[i-1].refreshrate) + continue; + if(usebpp) + if(modes[i].bpp != modes[i-1].bpp) + continue; + if(useaspect) + if(modes[i].pixelheight_num * modes[i-1].pixelheight_denom != modes[i].pixelheight_denom * modes[i-1].pixelheight_num) + continue; + } + // a dupe, or a bogus mode! + if(i < count-1) + memmove(&modes[i], &modes[i+1], sizeof(*modes) * (count-1 - i)); + --i; // check this index again, as mode i+1 is now here + --count; + } + return count; +} + +void VID_Soft_SharedSetup(void) +{ + gl_platform = "DPSOFTRAST"; + gl_platformextensions = ""; + + gl_renderer = "DarkPlaces-Soft"; + gl_vendor = "Forest Hale"; + gl_version = "0.0"; + gl_extensions = ""; + + // clear the extension flags + memset(&vid.support, 0, sizeof(vid.support)); + Cvar_SetQuick(&gl_info_extensions, ""); + + // DPSOFTRAST requires BGRA + vid.forcetextype = TEXTYPE_BGRA; + + vid.forcevbo = false; + vid.support.arb_depth_texture = true; + vid.support.arb_draw_buffers = true; + vid.support.arb_occlusion_query = true; + vid.support.arb_shadow = true; + //vid.support.arb_texture_compression = true; + vid.support.arb_texture_cube_map = true; + vid.support.arb_texture_non_power_of_two = false; + vid.support.arb_vertex_buffer_object = true; + vid.support.ext_blend_subtract = true; + vid.support.ext_draw_range_elements = true; + vid.support.ext_framebuffer_object = true; + vid.support.ext_texture_3d = true; + //vid.support.ext_texture_compression_s3tc = true; + vid.support.ext_texture_filter_anisotropic = true; + vid.support.ati_separate_stencil = true; + vid.support.ext_texture_srgb = false; + + vid.maxtexturesize_2d = 16384; + vid.maxtexturesize_3d = 512; + vid.maxtexturesize_cubemap = 16384; + vid.texunits = 4; + vid.teximageunits = 32; + vid.texarrayunits = 8; + vid.max_anisotropy = 1; + vid.maxdrawbuffers = 4; + + vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS); + vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS); + Con_DPrintf("Using DarkPlaces Software Rasterizer rendering path\n"); + vid.renderpath = RENDERPATH_SOFT; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + vid.useinterleavedarrays = false; + + Cvar_SetQuick(&gl_info_vendor, gl_vendor); + Cvar_SetQuick(&gl_info_renderer, gl_renderer); + Cvar_SetQuick(&gl_info_version, gl_version); + Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); + Cvar_SetQuick(&gl_info_driver, gl_driver); + + // LordHavoc: report supported extensions + Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); + + // clear to black (loading plaque will be seen over this) + GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128); +} diff --git a/misc/source/darkplaces-src/vid_wgl.c b/misc/source/darkplaces-src/vid_wgl.c new file mode 100644 index 00000000..d30c6a75 --- /dev/null +++ b/misc/source/darkplaces-src/vid_wgl.c @@ -0,0 +1,2310 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// vid_wgl.c -- NT GL vid component + +#ifdef _MSC_VER +#pragma comment(lib, "comctl32.lib") +#endif + +#ifdef SUPPORTDIRECTX +// Include DX libs +#ifdef _MSC_VER +#pragma comment(lib, "dinput8.lib") +#pragma comment(lib, "dxguid.lib") +#endif +#ifndef DIRECTINPUT_VERSION +# define DIRECTINPUT_VERSION 0x0500 /* Version 5.0 */ +#endif +#endif + +#include "quakedef.h" +#include +#include +#ifdef SUPPORTDIRECTX +#include +#endif +#include "resource.h" +#include +#ifdef SUPPORTDIRECTX +#include +#endif +#include "dpsoftrast.h" + +#ifdef SUPPORTD3D +#include + +cvar_t vid_dx9 = {CVAR_SAVE, "vid_dx9", "0", "use Microsoft Direct3D9(r) for rendering"}; +cvar_t vid_dx9_hal = {CVAR_SAVE, "vid_dx9_hal", "1", "enables hardware rendering (1), otherwise software reference rasterizer (0 - very slow), note that 0 is necessary when using NVPerfHUD (which renders in hardware but requires this option to enable it)"}; +cvar_t vid_dx9_softvertex = {CVAR_SAVE, "vid_dx9_softvertex", "0", "enables software vertex processing (for compatibility testing? or if you have a very fast CPU), usually you want this off"}; +cvar_t vid_dx9_triplebuffer = {CVAR_SAVE, "vid_dx9_triplebuffer", "0", "enables triple buffering when using vid_vsync in fullscreen, this options adds some latency and only helps when framerate is below 60 so you usually don't want it"}; +//cvar_t vid_dx10 = {CVAR_SAVE, "vid_dx10", "1", "use Microsoft Direct3D10(r) for rendering"}; +//cvar_t vid_dx11 = {CVAR_SAVE, "vid_dx11", "1", "use Microsoft Direct3D11(r) for rendering"}; + +D3DPRESENT_PARAMETERS vid_d3dpresentparameters; + +// we declare this in vid_shared.c because it is required by dedicated server and all clients when SUPPORTD3D is defined +extern LPDIRECT3DDEVICE9 vid_d3d9dev; + +LPDIRECT3D9 vid_d3d9; +D3DCAPS9 vid_d3d9caps; +qboolean vid_d3ddevicelost; +#endif + +extern HINSTANCE global_hInstance; + +static HINSTANCE gldll; + +#ifndef WM_MOUSEWHEEL +#define WM_MOUSEWHEEL 0x020A +#endif + +// Tell startup code that we have a client +int cl_available = true; + +qboolean vid_supportrefreshrate = true; + +static int (WINAPI *qwglChoosePixelFormat)(HDC, CONST PIXELFORMATDESCRIPTOR *); +static int (WINAPI *qwglDescribePixelFormat)(HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +//static int (WINAPI *qwglGetPixelFormat)(HDC); +static BOOL (WINAPI *qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR *); +static BOOL (WINAPI *qwglSwapBuffers)(HDC); +static HGLRC (WINAPI *qwglCreateContext)(HDC); +static BOOL (WINAPI *qwglDeleteContext)(HGLRC); +static HGLRC (WINAPI *qwglGetCurrentContext)(VOID); +static HDC (WINAPI *qwglGetCurrentDC)(VOID); +static PROC (WINAPI *qwglGetProcAddress)(LPCSTR); +static BOOL (WINAPI *qwglMakeCurrent)(HDC, HGLRC); +static BOOL (WINAPI *qwglSwapIntervalEXT)(int interval); +static const char *(WINAPI *qwglGetExtensionsStringARB)(HDC hdc); +static BOOL (WINAPI *qwglChoosePixelFormatARB)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +static BOOL (WINAPI *qwglGetPixelFormatAttribivARB)(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); + +static dllfunction_t wglfuncs[] = +{ + {"wglChoosePixelFormat", (void **) &qwglChoosePixelFormat}, + {"wglDescribePixelFormat", (void **) &qwglDescribePixelFormat}, +// {"wglGetPixelFormat", (void **) &qwglGetPixelFormat}, + {"wglSetPixelFormat", (void **) &qwglSetPixelFormat}, + {"wglSwapBuffers", (void **) &qwglSwapBuffers}, + {"wglCreateContext", (void **) &qwglCreateContext}, + {"wglDeleteContext", (void **) &qwglDeleteContext}, + {"wglGetProcAddress", (void **) &qwglGetProcAddress}, + {"wglMakeCurrent", (void **) &qwglMakeCurrent}, + {"wglGetCurrentContext", (void **) &qwglGetCurrentContext}, + {"wglGetCurrentDC", (void **) &qwglGetCurrentDC}, + {NULL, NULL} +}; + +static dllfunction_t wglswapintervalfuncs[] = +{ + {"wglSwapIntervalEXT", (void **) &qwglSwapIntervalEXT}, + {NULL, NULL} +}; + +static dllfunction_t wglpixelformatfuncs[] = +{ + {"wglChoosePixelFormatARB", (void **) &qwglChoosePixelFormatARB}, + {"wglGetPixelFormatAttribivARB", (void **) &qwglGetPixelFormatAttribivARB}, + {NULL, NULL} +}; + +static DEVMODE gdevmode, initialdevmode; +static qboolean vid_initialized = false; +static qboolean vid_wassuspended = false; +static qboolean vid_usingmouse = false; +static qboolean vid_usinghidecursor = false; +static qboolean vid_usingvsync = false; +static qboolean vid_usevsync = false; +static HICON hIcon; + +// used by cd_win.c and snd_win.c +HWND mainwindow; + +static HDC baseDC; +static HGLRC baseRC; + +static HDC vid_softhdc; +static HGDIOBJ vid_softhdc_backup; +static BITMAPINFO vid_softbmi; +static HBITMAP vid_softdibhandle; + +//HWND WINAPI InitializeWindow (HINSTANCE hInstance, int nCmdShow); + +static qboolean vid_isfullscreen; + +//void VID_MenuDraw (void); +//void VID_MenuKey (int key); + +//LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +//void AppActivate(BOOL fActive, BOOL minimize); +//void ClearAllStates (void); +//void VID_UpdateWindowStatus (void); + +//==================================== + +static int window_x, window_y; + +static qboolean mouseinitialized; + +#ifdef SUPPORTDIRECTX +static qboolean dinput; +#define DINPUT_BUFFERSIZE 16 +#define iDirectInputCreate(a,b,c,d) pDirectInputCreate(a,b,c,d) + +static HRESULT (WINAPI *pDirectInputCreate)(HINSTANCE hinst, DWORD dwVersion, LPDIRECTINPUT * lplpDirectInput, LPUNKNOWN punkOuter); +#endif + +// LordHavoc: thanks to backslash for this support for mouse buttons 4 and 5 +/* backslash :: imouse explorer buttons */ +/* These are #ifdefed out for non-Win2K in the February 2001 version of + MS's platform SDK, but we need them for compilation. . . */ +#ifndef WM_XBUTTONDOWN + #define WM_XBUTTONDOWN 0x020B + #define WM_XBUTTONUP 0x020C +#endif +#ifndef MK_XBUTTON1 + #define MK_XBUTTON1 0x0020 + #define MK_XBUTTON2 0x0040 +#endif +#ifndef MK_XBUTTON3 +// LordHavoc: lets hope this allows more buttons in the future... + #define MK_XBUTTON3 0x0080 + #define MK_XBUTTON4 0x0100 + #define MK_XBUTTON5 0x0200 + #define MK_XBUTTON6 0x0400 + #define MK_XBUTTON7 0x0800 +#endif +/* :: backslash */ + +// mouse variables +static int mouse_buttons; +static int mouse_oldbuttonstate; + +static unsigned int uiWheelMessage; +#ifdef SUPPORTDIRECTX +static qboolean dinput_acquired; + +static unsigned int mstate_di; +#endif + +static cvar_t vid_forcerefreshrate = {0, "vid_forcerefreshrate", "0", "try to set the given vid_refreshrate even if Windows doesn't list it as valid video mode"}; + +#ifdef SUPPORTDIRECTX +static LPDIRECTINPUT g_pdi; +static LPDIRECTINPUTDEVICE g_pMouse; +static HINSTANCE hInstDI; +#endif + +// forward-referenced functions +static void IN_StartupMouse (void); + + +//==================================== + +qboolean vid_reallyhidden = true; +#ifdef SUPPORTD3D +qboolean vid_begunscene = false; +#endif +void VID_Finish (void) +{ +#ifdef SUPPORTD3D + HRESULT hr; +#endif + vid_hidden = vid_reallyhidden; + + vid_usevsync = vid_vsync.integer && !cls.timedemo && qwglSwapIntervalEXT; + + if (!vid_hidden) + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (vid_usingvsync != vid_usevsync) + { + vid_usingvsync = vid_usevsync; + qwglSwapIntervalEXT (vid_usevsync); + } + if (r_speeds.integer == 2 || gl_finish.integer) + GL_Finish(); + SwapBuffers(baseDC); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (vid_begunscene) + { + IDirect3DDevice9_EndScene(vid_d3d9dev); + vid_begunscene = false; + } + if (!vid_reallyhidden) + { + if (!vid_d3ddevicelost) + { + vid_hidden = vid_reallyhidden; + hr = IDirect3DDevice9_Present(vid_d3d9dev, NULL, NULL, NULL, NULL); + if (hr == D3DERR_DEVICELOST) + { + vid_d3ddevicelost = true; + vid_hidden = true; + Sleep(100); + } + } + else + { + hr = IDirect3DDevice9_TestCooperativeLevel(vid_d3d9dev); + switch(hr) + { + case D3DERR_DEVICELOST: + vid_d3ddevicelost = true; + vid_hidden = true; + Sleep(100); + break; + case D3DERR_DEVICENOTRESET: + vid_d3ddevicelost = false; + vid_hidden = vid_reallyhidden; + R_Modules_DeviceLost(); + IDirect3DDevice9_Reset(vid_d3d9dev, &vid_d3dpresentparameters); + R_Modules_DeviceRestored(); + break; + case D3D_OK: + vid_hidden = vid_reallyhidden; + IDirect3DDevice9_Present(vid_d3d9dev, NULL, NULL, NULL, NULL); + break; + } + } + if (!vid_begunscene && !vid_hidden) + { + IDirect3DDevice9_BeginScene(vid_d3d9dev); + vid_begunscene = true; + } + } +#endif + break; + case RENDERPATH_D3D10: + break; + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Finish(); +// baseDC = GetDC(mainwindow); + BitBlt(baseDC, 0, 0, vid.width, vid.height, vid_softhdc, 0, 0, SRCCOPY); +// ReleaseDC(mainwindow, baseDC); +// baseDC = NULL; + break; + } + } + + // make sure a context switch can happen every frame - Logitech drivers + // input drivers sometimes eat cpu time every 3 seconds or lag badly + // without this help + Sleep(0); + + VID_UpdateGamma(false, 256); +} + +//========================================================================== + + + + +static unsigned char scantokey[128] = +{ +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0 ,27 ,'1' ,'2' ,'3' ,'4' ,'5' ,'6' ,'7' ,'8' ,'9' ,'0' ,'-' ,'=' ,K_BACKSPACE,9 ,//0 + 'q' ,'w' ,'e' ,'r' ,'t' ,'y' ,'u' ,'i' ,'o' ,'p' ,'[' ,']' ,13 ,K_CTRL ,'a' ,'s' ,//1 + 'd' ,'f' ,'g' ,'h' ,'j' ,'k' ,'l' ,';' ,'\'' ,'`' ,K_SHIFT ,'\\' ,'z' ,'x' ,'c' ,'v' ,//2 + 'b' ,'n' ,'m' ,',' ,'.' ,'/' ,K_SHIFT,'*' ,K_ALT ,' ' ,0 ,K_F1 ,K_F2 ,K_F3 ,K_F4 ,K_F5 ,//3 + K_F6 ,K_F7 ,K_F8 ,K_F9 ,K_F10,K_PAUSE,0 ,K_HOME,K_UPARROW,K_PGUP,K_KP_MINUS,K_LEFTARROW,K_KP_5,K_RIGHTARROW,K_KP_PLUS ,K_END,//4 + K_DOWNARROW,K_PGDN,K_INS,K_DEL,0 ,0 ,0 ,K_F11 ,K_F12 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,//5 + 0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,//6 + 0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 //7 +}; + + +/* +======= +MapKey + +Map from windows to quake keynums +======= +*/ +static int MapKey (int key, int virtualkey) +{ + int result; + int modified = (key >> 16) & 255; + qboolean is_extended = false; + + if (modified < 128 && scantokey[modified]) + result = scantokey[modified]; + else + { + result = 0; + Con_DPrintf("key 0x%02x (0x%8x, 0x%8x) has no translation\n", modified, key, virtualkey); + } + + if (key & (1 << 24)) + is_extended = true; + + if ( !is_extended ) + { + switch ( result ) + { + case K_HOME: + return K_KP_HOME; + case K_UPARROW: + return K_KP_UPARROW; + case K_PGUP: + return K_KP_PGUP; + case K_LEFTARROW: + return K_KP_LEFTARROW; + case K_RIGHTARROW: + return K_KP_RIGHTARROW; + case K_END: + return K_KP_END; + case K_DOWNARROW: + return K_KP_DOWNARROW; + case K_PGDN: + return K_KP_PGDN; + case K_INS: + return K_KP_INS; + case K_DEL: + return K_KP_DEL; + default: + return result; + } + } + else + { + switch ( result ) + { + case 0x0D: + return K_KP_ENTER; + case 0x2F: + return K_KP_SLASH; + case 0xAF: + return K_KP_PLUS; + } + return result; + } +} + +/* +=================================================================== + +MAIN WINDOW + +=================================================================== +*/ + +/* +================ +ClearAllStates +================ +*/ +static void ClearAllStates (void) +{ + Key_ClearStates (); + if (vid_usingmouse) + mouse_oldbuttonstate = 0; +} + +void AppActivate(BOOL fActive, BOOL minimize) +/**************************************************************************** +* +* Function: AppActivate +* Parameters: fActive - True if app is activating +* +* Description: If the application is activating, then swap the system +* into SYSPAL_NOSTATIC mode so that our palettes will display +* correctly. +* +****************************************************************************/ +{ + static qboolean sound_active = false; // initially blocked by Sys_InitConsole() + + vid_activewindow = fActive != FALSE; + vid_reallyhidden = minimize != FALSE; + + // enable/disable sound on focus gain/loss + if ((!vid_reallyhidden && vid_activewindow) || !snd_mutewhenidle.integer) + { + if (!sound_active) + { + S_UnblockSound (); + sound_active = true; + } + } + else + { + if (sound_active) + { + S_BlockSound (); + sound_active = false; + } + } + + if (fActive) + { + if (vid_isfullscreen) + { + if (vid_wassuspended) + { + vid_wassuspended = false; + if (gldll) + { + ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN); + ShowWindow(mainwindow, SW_SHOWNORMAL); + } + } + + // LordHavoc: from dabb, fix for alt-tab bug in NVidia drivers + if (gldll) + MoveWindow(mainwindow,0,0,gdevmode.dmPelsWidth,gdevmode.dmPelsHeight,false); + } + } + + if (!fActive) + { + VID_SetMouse(false, false, false); + if (vid_isfullscreen) + { + if (gldll) + ChangeDisplaySettings (NULL, 0); + vid_wassuspended = true; + } + VID_RestoreSystemGamma(); + } +} + +//TODO: move it around in vid_wgl.c since I dont think this is the right position +void Sys_SendKeyEvents (void) +{ + MSG msg; + + while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) + { + if (!GetMessage (&msg, NULL, 0, 0)) + Sys_Quit (1); + + TranslateMessage (&msg); + DispatchMessage (&msg); + } +} + +LONG CDAudio_MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +static keynum_t buttonremap[16] = +{ + K_MOUSE1, + K_MOUSE2, + K_MOUSE3, + K_MOUSE4, + K_MOUSE5, + K_MOUSE6, + K_MOUSE7, + K_MOUSE8, + K_MOUSE9, + K_MOUSE10, + K_MOUSE11, + K_MOUSE12, + K_MOUSE13, + K_MOUSE14, + K_MOUSE15, + K_MOUSE16, +}; + +/* main window procedure */ +LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + LONG lRet = 1; + int fActive, fMinimized, temp; + unsigned char state[256]; + unsigned char asciichar[4]; + int vkey; + int charlength; + qboolean down = false; + + if ( uMsg == uiWheelMessage ) + uMsg = WM_MOUSEWHEEL; + + switch (uMsg) + { + case WM_KILLFOCUS: + if (vid_isfullscreen) + ShowWindow(mainwindow, SW_SHOWMINNOACTIVE); + break; + + case WM_CREATE: + break; + + case WM_MOVE: + window_x = (int) LOWORD(lParam); + window_y = (int) HIWORD(lParam); + VID_SetMouse(false, false, false); + break; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + down = true; + case WM_KEYUP: + case WM_SYSKEYUP: + vkey = MapKey(lParam, wParam); + GetKeyboardState (state); + // alt/ctrl/shift tend to produce funky ToAscii values, + // and if it's not a single character we don't know care about it + charlength = ToAscii (wParam, lParam >> 16, state, (LPWORD)asciichar, 0); + if (vkey == K_ALT || vkey == K_CTRL || vkey == K_SHIFT || charlength == 0) + asciichar[0] = 0; + else if( charlength == 2 ) { + asciichar[0] = asciichar[1]; + } + if (!VID_JoyBlockEmulatedKeys(vkey)) + Key_Event (vkey, asciichar[0], down); + break; + + case WM_SYSCHAR: + // keep Alt-Space from happening + break; + + case WM_SYSCOMMAND: + // prevent screensaver from occuring while the active window + // note: password-locked screensavers on Vista still work + if (vid_activewindow && ((wParam & 0xFFF0) == SC_SCREENSAVE || (wParam & 0xFFF0) == SC_MONITORPOWER)) + lRet = 0; + else + lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); + break; + + // this is complicated because Win32 seems to pack multiple mouse events into + // one update sometimes, so we always check all states and look for events + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_XBUTTONDOWN: // backslash :: imouse explorer buttons + case WM_XBUTTONUP: // backslash :: imouse explorer buttons + case WM_MOUSEMOVE: + temp = 0; + + if (wParam & MK_LBUTTON) + temp |= 1; + + if (wParam & MK_RBUTTON) + temp |= 2; + + if (wParam & MK_MBUTTON) + temp |= 4; + + /* backslash :: imouse explorer buttons */ + if (wParam & MK_XBUTTON1) + temp |= 8; + + if (wParam & MK_XBUTTON2) + temp |= 16; + /* :: backslash */ + + // LordHavoc: lets hope this allows more buttons in the future... + if (wParam & MK_XBUTTON3) + temp |= 32; + if (wParam & MK_XBUTTON4) + temp |= 64; + if (wParam & MK_XBUTTON5) + temp |= 128; + if (wParam & MK_XBUTTON6) + temp |= 256; + if (wParam & MK_XBUTTON7) + temp |= 512; + +#ifdef SUPPORTDIRECTX + if (!dinput_acquired) +#endif + { + // perform button actions + int i; + for (i=0 ; i 0) { + Key_Event(K_MWHEELUP, 0, true); + Key_Event(K_MWHEELUP, 0, false); + } else { + Key_Event(K_MWHEELDOWN, 0, true); + Key_Event(K_MWHEELDOWN, 0, false); + } + break; + + case WM_SIZE: + break; + + case WM_CLOSE: + if (MessageBox (mainwindow, "Are you sure you want to quit?", "Confirm Exit", MB_YESNO | MB_SETFOREGROUND | MB_ICONQUESTION) == IDYES) + Sys_Quit (0); + + break; + + case WM_ACTIVATE: + fActive = LOWORD(wParam); + fMinimized = (BOOL) HIWORD(wParam); + AppActivate(!(fActive == WA_INACTIVE), fMinimized); + + // fix the leftover Alt from any Alt-Tab or the like that switched us away + ClearAllStates (); + + break; + + //case WM_DESTROY: + // PostQuitMessage (0); + // break; + + case MM_MCINOTIFY: + lRet = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam); + break; + + default: + /* pass all unhandled messages to DefWindowProc */ + lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); + break; + } + + /* return 1 if handled message, 0 if not */ + return lRet; +} + +int VID_SetGamma(unsigned short *ramps, int rampsize) +{ + if (qwglMakeCurrent) + { + HDC hdc = GetDC (NULL); + int i = SetDeviceGammaRamp(hdc, ramps); + ReleaseDC (NULL, hdc); + return i; // return success or failure + } + else + return 0; +} + +int VID_GetGamma(unsigned short *ramps, int rampsize) +{ + if (qwglMakeCurrent) + { + HDC hdc = GetDC (NULL); + int i = GetDeviceGammaRamp(hdc, ramps); + ReleaseDC (NULL, hdc); + return i; // return success or failure + } + else + return 0; +} + +static void GL_CloseLibrary(void) +{ + if (gldll) + { + FreeLibrary(gldll); + gldll = 0; + gl_driver[0] = 0; + qwglGetProcAddress = NULL; + gl_extensions = ""; + gl_platform = ""; + gl_platformextensions = ""; + } +} + +static int GL_OpenLibrary(const char *name) +{ + Con_Printf("Loading OpenGL driver %s\n", name); + GL_CloseLibrary(); + if (!(gldll = LoadLibrary(name))) + { + Con_Printf("Unable to LoadLibrary %s\n", name); + return false; + } + strlcpy(gl_driver, name, sizeof(gl_driver)); + return true; +} + +void *GL_GetProcAddress(const char *name) +{ + if (gldll) + { + void *p = NULL; + if (qwglGetProcAddress != NULL) + p = (void *) qwglGetProcAddress(name); + if (p == NULL) + p = (void *) GetProcAddress(gldll, name); + return p; + } + else + return NULL; +} + +#ifndef WGL_ARB_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +#endif + +#ifndef WGL_ARB_multisample +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 +#endif + + +static void IN_Init(void); +void VID_Init(void) +{ + WNDCLASS wc; + +#ifdef SUPPORTD3D + Cvar_RegisterVariable(&vid_dx9); + Cvar_RegisterVariable(&vid_dx9_hal); + Cvar_RegisterVariable(&vid_dx9_softvertex); + Cvar_RegisterVariable(&vid_dx9_triplebuffer); +// Cvar_RegisterVariable(&vid_dx10); +// Cvar_RegisterVariable(&vid_dx11); +#endif + + InitCommonControls(); + hIcon = LoadIcon (global_hInstance, MAKEINTRESOURCE (IDI_ICON1)); + + // Register the frame class + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)MainWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = global_hInstance; + wc.hIcon = hIcon; + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = NULL; + wc.lpszMenuName = 0; + wc.lpszClassName = "DarkPlacesWindowClass"; + + if (!RegisterClass (&wc)) + Con_Printf ("Couldn't register window class\n"); + + memset(&initialdevmode, 0, sizeof(initialdevmode)); + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &initialdevmode); + + IN_Init(); +} + +qboolean VID_InitModeGL(viddef_mode_t *mode) +{ + int i; + HDC hdc; + RECT rect; + MSG msg; + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd + 1, // version number + PFD_DRAW_TO_WINDOW // support window + | PFD_SUPPORT_OPENGL // support OpenGL + | PFD_DOUBLEBUFFER , // double buffered + PFD_TYPE_RGBA, // RGBA type + 24, // 24-bit color depth + 0, 0, 0, 0, 0, 0, // color bits ignored + 0, // no alpha buffer + 0, // shift bit ignored + 0, // no accumulation buffer + 0, 0, 0, 0, // accum bits ignored + 32, // 32-bit z-buffer + 0, // no stencil buffer + 0, // no auxiliary buffer + PFD_MAIN_PLANE, // main layer + 0, // reserved + 0, 0, 0 // layer masks ignored + }; + int windowpass; + int pixelformat, newpixelformat; + UINT numpixelformats; + DWORD WindowStyle, ExWindowStyle; + int CenterX, CenterY; + const char *gldrivername; + int depth; + DEVMODE thismode; + qboolean foundmode, foundgoodmode; + int *a; + float *af; + int attribs[128]; + float attribsf[16]; + int bpp = mode->bitsperpixel; + int width = mode->width; + int height = mode->height; + int refreshrate = (int)floor(mode->refreshrate+0.5); + int stereobuffer = mode->stereobuffer; + int samples = mode->samples; + int fullscreen = mode->fullscreen; + + if (vid_initialized) + Sys_Error("VID_InitMode called when video is already initialised"); + + // if stencil is enabled, ask for alpha too + if (bpp >= 32) + { + pfd.cRedBits = 8; + pfd.cGreenBits = 8; + pfd.cBlueBits = 8; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + } + else + { + pfd.cRedBits = 5; + pfd.cGreenBits = 5; + pfd.cBlueBits = 5; + pfd.cAlphaBits = 0; + pfd.cDepthBits = 16; + pfd.cStencilBits = 0; + } + + if (stereobuffer) + pfd.dwFlags |= PFD_STEREO; + + a = attribs; + af = attribsf; + *a++ = WGL_DRAW_TO_WINDOW_ARB; + *a++ = GL_TRUE; + *a++ = WGL_ACCELERATION_ARB; + *a++ = WGL_FULL_ACCELERATION_ARB; + *a++ = WGL_DOUBLE_BUFFER_ARB; + *a++ = true; + + if (bpp >= 32) + { + *a++ = WGL_RED_BITS_ARB; + *a++ = 8; + *a++ = WGL_GREEN_BITS_ARB; + *a++ = 8; + *a++ = WGL_BLUE_BITS_ARB; + *a++ = 8; + *a++ = WGL_ALPHA_BITS_ARB; + *a++ = 8; + *a++ = WGL_DEPTH_BITS_ARB; + *a++ = 24; + *a++ = WGL_STENCIL_BITS_ARB; + *a++ = 8; + } + else + { + *a++ = WGL_RED_BITS_ARB; + *a++ = 1; + *a++ = WGL_GREEN_BITS_ARB; + *a++ = 1; + *a++ = WGL_BLUE_BITS_ARB; + *a++ = 1; + *a++ = WGL_DEPTH_BITS_ARB; + *a++ = 16; + } + + if (stereobuffer) + { + *a++ = WGL_STEREO_ARB; + *a++ = GL_TRUE; + } + + if (samples > 1) + { + *a++ = WGL_SAMPLE_BUFFERS_ARB; + *a++ = 1; + *a++ = WGL_SAMPLES_ARB; + *a++ = samples; + } + + *a = 0; + *af = 0; + + gldrivername = "opengl32.dll"; +// COMMANDLINEOPTION: Windows WGL: -gl_driver selects a GL driver library, default is opengl32.dll, useful only for 3dfxogl.dll or 3dfxvgl.dll, if you don't know what this is for, you don't need it + i = COM_CheckParm("-gl_driver"); + if (i && i < com_argc - 1) + gldrivername = com_argv[i + 1]; + if (!GL_OpenLibrary(gldrivername)) + { + Con_Printf("Unable to load GL driver %s\n", gldrivername); + return false; + } + + memset(&gdevmode, 0, sizeof(gdevmode)); + + vid_isfullscreen = false; + if (fullscreen) + { + if(vid_forcerefreshrate.integer) + { + foundmode = true; + gdevmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + gdevmode.dmBitsPerPel = bpp; + gdevmode.dmPelsWidth = width; + gdevmode.dmPelsHeight = height; + gdevmode.dmSize = sizeof (gdevmode); + if(refreshrate) + { + gdevmode.dmFields |= DM_DISPLAYFREQUENCY; + gdevmode.dmDisplayFrequency = refreshrate; + } + } + else + { + if(refreshrate == 0) + refreshrate = initialdevmode.dmDisplayFrequency; // default vid_refreshrate to the rate of the desktop + + foundmode = false; + foundgoodmode = false; + + thismode.dmSize = sizeof(thismode); + thismode.dmDriverExtra = 0; + for(i = 0; EnumDisplaySettings(NULL, i, &thismode); ++i) + { + if(~thismode.dmFields & (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY)) + { + Con_DPrintf("enumerating modes yielded a bogus item... please debug this\n"); + continue; + } + if(developer_extra.integer) + Con_DPrintf("Found mode %dx%dx%dbpp %dHz... ", (int)thismode.dmPelsWidth, (int)thismode.dmPelsHeight, (int)thismode.dmBitsPerPel, (int)thismode.dmDisplayFrequency); + if(thismode.dmBitsPerPel != (DWORD)bpp) + { + if(developer_extra.integer) + Con_DPrintf("wrong bpp\n"); + continue; + } + if(thismode.dmPelsWidth != (DWORD)width) + { + if(developer_extra.integer) + Con_DPrintf("wrong width\n"); + continue; + } + if(thismode.dmPelsHeight != (DWORD)height) + { + if(developer_extra.integer) + Con_DPrintf("wrong height\n"); + continue; + } + + if(foundgoodmode) + { + // if we have a good mode, make sure this mode is better than the previous one, and allowed by the refreshrate + if(thismode.dmDisplayFrequency > (DWORD)refreshrate) + { + if(developer_extra.integer) + Con_DPrintf("too high refresh rate\n"); + continue; + } + else if(thismode.dmDisplayFrequency <= gdevmode.dmDisplayFrequency) + { + if(developer_extra.integer) + Con_DPrintf("doesn't beat previous best match (too low)\n"); + continue; + } + } + else if(foundmode) + { + // we do have one, but it isn't good... make sure it has a lower frequency than the previous one + if(thismode.dmDisplayFrequency >= gdevmode.dmDisplayFrequency) + { + if(developer_extra.integer) + Con_DPrintf("doesn't beat previous best match (too high)\n"); + continue; + } + } + // otherwise, take anything + + memcpy(&gdevmode, &thismode, sizeof(gdevmode)); + if(thismode.dmDisplayFrequency <= (DWORD)refreshrate) + foundgoodmode = true; + else + { + if(developer_extra.integer) + Con_DPrintf("(out of range)\n"); + } + foundmode = true; + if(developer_extra.integer) + Con_DPrintf("accepted\n"); + } + } + + if (!foundmode) + { + VID_Shutdown(); + Con_Printf("Unable to find the requested mode %dx%dx%dbpp\n", width, height, bpp); + return false; + } + else if(ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) + { + VID_Shutdown(); + Con_Printf("Unable to change to requested mode %dx%dx%dbpp\n", width, height, bpp); + return false; + } + + vid_isfullscreen = true; + WindowStyle = WS_POPUP; + ExWindowStyle = WS_EX_TOPMOST; + } + else + { + hdc = GetDC (NULL); + i = GetDeviceCaps(hdc, RASTERCAPS); + depth = GetDeviceCaps(hdc, PLANES) * GetDeviceCaps(hdc, BITSPIXEL); + ReleaseDC (NULL, hdc); + if (i & RC_PALETTE) + { + VID_Shutdown(); + Con_Print("Can't run in non-RGB mode\n"); + return false; + } + if (bpp > depth) + { + VID_Shutdown(); + Con_Print("A higher desktop depth is required to run this video mode\n"); + return false; + } + + WindowStyle = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + ExWindowStyle = 0; + } + + rect.top = 0; + rect.left = 0; + rect.right = width; + rect.bottom = height; + AdjustWindowRectEx(&rect, WindowStyle, false, 0); + + if (fullscreen) + { + CenterX = 0; + CenterY = 0; + } + else + { + CenterX = (GetSystemMetrics(SM_CXSCREEN) - (rect.right - rect.left)) / 2; + CenterY = (GetSystemMetrics(SM_CYSCREEN) - (rect.bottom - rect.top)) / 2; + } + CenterX = max(0, CenterX); + CenterY = max(0, CenterY); + + // x and y may be changed by WM_MOVE messages + window_x = CenterX; + window_y = CenterY; + rect.left += CenterX; + rect.right += CenterX; + rect.top += CenterY; + rect.bottom += CenterY; + + pixelformat = 0; + newpixelformat = 0; + // start out at the final windowpass if samples is 1 as it's the only feature we need extended pixel formats for + for (windowpass = samples == 1;windowpass < 2;windowpass++) + { + gl_extensions = ""; + gl_platformextensions = ""; + + mainwindow = CreateWindowEx (ExWindowStyle, "DarkPlacesWindowClass", gamename, WindowStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, global_hInstance, NULL); + if (!mainwindow) + { + Con_Printf("CreateWindowEx(%d, %s, %s, %d, %d, %d, %d, %d, %p, %p, %p, %p) failed\n", (int)ExWindowStyle, "DarkPlacesWindowClass", gamename, (int)WindowStyle, (int)(rect.left), (int)(rect.top), (int)(rect.right - rect.left), (int)(rect.bottom - rect.top), (void *)NULL, (void *)NULL, (void *)global_hInstance, (void *)NULL); + VID_Shutdown(); + return false; + } + + baseDC = GetDC(mainwindow); + + if (!newpixelformat) + newpixelformat = ChoosePixelFormat(baseDC, &pfd); + pixelformat = newpixelformat; + if (!pixelformat) + { + VID_Shutdown(); + Con_Printf("ChoosePixelFormat(%p, %p) failed\n", (void *)baseDC, (void *)&pfd); + return false; + } + + if (SetPixelFormat(baseDC, pixelformat, &pfd) == false) + { + VID_Shutdown(); + Con_Printf("SetPixelFormat(%p, %d, %p) failed\n", (void *)baseDC, pixelformat, (void *)&pfd); + return false; + } + + if (!GL_CheckExtension("wgl", wglfuncs, NULL, false)) + { + VID_Shutdown(); + Con_Print("wgl functions not found\n"); + return false; + } + + baseRC = qwglCreateContext(baseDC); + if (!baseRC) + { + VID_Shutdown(); + Con_Print("Could not initialize GL (wglCreateContext failed).\n\nMake sure you are in 65536 color mode, and try running -window.\n"); + return false; + } + if (!qwglMakeCurrent(baseDC, baseRC)) + { + VID_Shutdown(); + Con_Printf("wglMakeCurrent(%p, %p) failed\n", (void *)baseDC, (void *)baseRC); + return false; + } + + if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL) + { + VID_Shutdown(); + Con_Print("glGetString not found\n"); + return false; + } + if ((qwglGetExtensionsStringARB = (const char *(WINAPI *)(HDC hdc))GL_GetProcAddress("wglGetExtensionsStringARB")) == NULL) + Con_Print("wglGetExtensionsStringARB not found\n"); + + gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); + gl_platform = "WGL"; + gl_platformextensions = ""; + + if (qwglGetExtensionsStringARB) + gl_platformextensions = (const char *)qwglGetExtensionsStringARB(baseDC); + + if (!gl_extensions) + gl_extensions = ""; + if (!gl_platformextensions) + gl_platformextensions = ""; + + // now some nice Windows pain: + // we have created a window, we needed one to find out if there are + // any multisample pixel formats available, the problem is that to + // actually use one of those multisample formats we now have to + // recreate the window (yes Microsoft OpenGL really is that bad) + + if (windowpass == 0) + { + if (!GL_CheckExtension("WGL_ARB_pixel_format", wglpixelformatfuncs, "-noarbpixelformat", false) || !qwglChoosePixelFormatARB(baseDC, attribs, attribsf, 1, &newpixelformat, &numpixelformats) || !newpixelformat) + break; + // ok we got one - do it all over again with newpixelformat + qwglMakeCurrent(NULL, NULL); + qwglDeleteContext(baseRC);baseRC = 0; + ReleaseDC(mainwindow, baseDC);baseDC = 0; + // eat up any messages waiting for us + while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + } + } + + /* + if (!fullscreen) + SetWindowPos (mainwindow, NULL, CenterX, CenterY, 0, 0,SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME); + */ + + ShowWindow (mainwindow, SW_SHOWDEFAULT); + UpdateWindow (mainwindow); + + // now we try to make sure we get the focus on the mode switch, because + // sometimes in some systems we don't. We grab the foreground, then + // finish setting up, pump all our messages, and sleep for a little while + // to let messages finish bouncing around the system, then we put + // ourselves at the top of the z order, then grab the foreground again, + // Who knows if it helps, but it probably doesn't hurt + SetForegroundWindow (mainwindow); + + while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + + Sleep (100); + + SetWindowPos (mainwindow, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOCOPYBITS); + + SetForegroundWindow (mainwindow); + + // fix the leftover Alt from any Alt-Tab or the like that switched us away + ClearAllStates (); + +// COMMANDLINEOPTION: Windows WGL: -novideosync disables WGL_EXT_swap_control + GL_CheckExtension("WGL_EXT_swap_control", wglswapintervalfuncs, "-novideosync", false); + + GL_Init (); + + //vid_menudrawfn = VID_MenuDraw; + //vid_menukeyfn = VID_MenuKey; + vid_usingmouse = false; + vid_usinghidecursor = false; + vid_usingvsync = false; + vid_reallyhidden = vid_hidden = false; + vid_initialized = true; + + IN_StartupMouse (); + + if (qwglSwapIntervalEXT) + { + vid_usevsync = vid_vsync.integer != 0; + vid_usingvsync = vid_vsync.integer != 0; + qwglSwapIntervalEXT (vid_usevsync); + } + + return true; +} + +#ifdef SUPPORTD3D +static D3DADAPTER_IDENTIFIER9 d3d9adapteridentifier; + +extern cvar_t gl_info_extensions; +extern cvar_t gl_info_vendor; +extern cvar_t gl_info_renderer; +extern cvar_t gl_info_version; +extern cvar_t gl_info_platform; +extern cvar_t gl_info_driver; +qboolean VID_InitModeDX(viddef_mode_t *mode, int version) +{ + int deviceindex; + RECT rect; + MSG msg; + DWORD WindowStyle, ExWindowStyle; + int CenterX, CenterY; + int bpp = mode->bitsperpixel; + int width = mode->width; + int height = mode->height; + int refreshrate = (int)floor(mode->refreshrate+0.5); +// int stereobuffer = mode->stereobuffer; + int samples = mode->samples; + int fullscreen = mode->fullscreen; + int numdevices; + + if (vid_initialized) + Sys_Error("VID_InitMode called when video is already initialised"); + + vid_isfullscreen = fullscreen != 0; + if (fullscreen) + { + WindowStyle = WS_POPUP; + ExWindowStyle = WS_EX_TOPMOST; + } + else + { + WindowStyle = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + ExWindowStyle = 0; + } + + rect.top = 0; + rect.left = 0; + rect.right = width; + rect.bottom = height; + AdjustWindowRectEx(&rect, WindowStyle, false, 0); + + if (fullscreen) + { + CenterX = 0; + CenterY = 0; + } + else + { + CenterX = (GetSystemMetrics(SM_CXSCREEN) - (rect.right - rect.left)) / 2; + CenterY = (GetSystemMetrics(SM_CYSCREEN) - (rect.bottom - rect.top)) / 2; + } + CenterX = max(0, CenterX); + CenterY = max(0, CenterY); + + // x and y may be changed by WM_MOVE messages + window_x = CenterX; + window_y = CenterY; + rect.left += CenterX; + rect.right += CenterX; + rect.top += CenterY; + rect.bottom += CenterY; + + gl_extensions = ""; + gl_platformextensions = ""; + + mainwindow = CreateWindowEx (ExWindowStyle, "DarkPlacesWindowClass", gamename, WindowStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, global_hInstance, NULL); + if (!mainwindow) + { + Con_Printf("CreateWindowEx(%d, %s, %s, %d, %d, %d, %d, %d, %p, %p, %p, %p) failed\n", (int)ExWindowStyle, "DarkPlacesWindowClass", gamename, (int)WindowStyle, (int)(rect.left), (int)(rect.top), (int)(rect.right - rect.left), (int)(rect.bottom - rect.top), (void *)NULL, (void *)NULL, global_hInstance, (void *)NULL); + VID_Shutdown(); + return false; + } + + baseDC = GetDC(mainwindow); + + vid_d3d9 = Direct3DCreate9(D3D_SDK_VERSION); + if (!vid_d3d9) + Sys_Error("VID_InitMode: Direct3DCreate9 failed"); + + numdevices = IDirect3D9_GetAdapterCount(vid_d3d9); + vid_d3d9dev = NULL; + memset(&d3d9adapteridentifier, 0, sizeof(d3d9adapteridentifier)); + for (deviceindex = 0;deviceindex < numdevices && !vid_d3d9dev;deviceindex++) + { + memset(&vid_d3dpresentparameters, 0, sizeof(vid_d3dpresentparameters)); +// vid_d3dpresentparameters.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL; + vid_d3dpresentparameters.Flags = 0; + vid_d3dpresentparameters.SwapEffect = D3DSWAPEFFECT_DISCARD; + vid_d3dpresentparameters.hDeviceWindow = mainwindow; + vid_d3dpresentparameters.BackBufferWidth = width; + vid_d3dpresentparameters.BackBufferHeight = height; + vid_d3dpresentparameters.MultiSampleType = samples > 1 ? (D3DMULTISAMPLE_TYPE)samples : D3DMULTISAMPLE_NONE; + vid_d3dpresentparameters.BackBufferCount = fullscreen ? (vid_dx9_triplebuffer.integer ? 3 : 2) : 1; + vid_d3dpresentparameters.FullScreen_RefreshRateInHz = fullscreen ? refreshrate : 0; + vid_d3dpresentparameters.Windowed = !fullscreen; + vid_d3dpresentparameters.EnableAutoDepthStencil = true; + vid_d3dpresentparameters.AutoDepthStencilFormat = bpp > 16 ? D3DFMT_D24S8 : D3DFMT_D16; + vid_d3dpresentparameters.BackBufferFormat = fullscreen?D3DFMT_X8R8G8B8:D3DFMT_UNKNOWN; + vid_d3dpresentparameters.PresentationInterval = vid_vsync.integer ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE; + + memset(&d3d9adapteridentifier, 0, sizeof(d3d9adapteridentifier)); + IDirect3D9_GetAdapterIdentifier(vid_d3d9, deviceindex, 0, &d3d9adapteridentifier); + + IDirect3D9_CreateDevice(vid_d3d9, deviceindex, vid_dx9_hal.integer ? D3DDEVTYPE_HAL : D3DDEVTYPE_REF, mainwindow, vid_dx9_softvertex.integer ? D3DCREATE_SOFTWARE_VERTEXPROCESSING : D3DCREATE_HARDWARE_VERTEXPROCESSING, &vid_d3dpresentparameters, &vid_d3d9dev); + } + + if (!vid_d3d9dev) + { + VID_Shutdown(); + return false; + } + + IDirect3DDevice9_GetDeviceCaps(vid_d3d9dev, &vid_d3d9caps); + + Con_Printf("Using D3D9 device: %s\n", d3d9adapteridentifier.Description); + gl_extensions = ""; + gl_platform = "D3D9"; + gl_platformextensions = ""; + + ShowWindow (mainwindow, SW_SHOWDEFAULT); + UpdateWindow (mainwindow); + + // now we try to make sure we get the focus on the mode switch, because + // sometimes in some systems we don't. We grab the foreground, then + // finish setting up, pump all our messages, and sleep for a little while + // to let messages finish bouncing around the system, then we put + // ourselves at the top of the z order, then grab the foreground again, + // Who knows if it helps, but it probably doesn't hurt + SetForegroundWindow (mainwindow); + + while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + + Sleep (100); + + SetWindowPos (mainwindow, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOCOPYBITS); + + SetForegroundWindow (mainwindow); + + // fix the leftover Alt from any Alt-Tab or the like that switched us away + ClearAllStates (); + + gl_renderer = d3d9adapteridentifier.Description; + gl_vendor = d3d9adapteridentifier.Driver; + gl_version = ""; + gl_extensions = ""; + + Con_Printf("D3D9 adapter info:\n"); + Con_Printf("Description: %s\n", d3d9adapteridentifier.Description); + Con_Printf("DeviceId: %x\n", (unsigned int)d3d9adapteridentifier.DeviceId); + Con_Printf("DeviceName: %p\n", d3d9adapteridentifier.DeviceName); + Con_Printf("Driver: %s\n", d3d9adapteridentifier.Driver); + Con_Printf("DriverVersion: %08x%08x\n", (unsigned int)d3d9adapteridentifier.DriverVersion.u.HighPart, (unsigned int)d3d9adapteridentifier.DriverVersion.u.LowPart); + Con_DPrintf("GL_EXTENSIONS: %s\n", gl_extensions); + Con_DPrintf("%s_EXTENSIONS: %s\n", gl_platform, gl_platformextensions); + + // clear the extension flags + memset(&vid.support, 0, sizeof(vid.support)); + Cvar_SetQuick(&gl_info_extensions, ""); + + // D3D9 requires BGRA + vid.forcetextype = TEXTYPE_BGRA; + + vid.forcevbo = false; + vid.support.arb_depth_texture = true; + vid.support.arb_draw_buffers = vid_d3d9caps.NumSimultaneousRTs > 1; + vid.support.arb_occlusion_query = true; // can't find a cap for this + vid.support.arb_shadow = true; + vid.support.arb_texture_compression = true; + vid.support.arb_texture_cube_map = true; + vid.support.arb_texture_non_power_of_two = (vid_d3d9caps.TextureCaps & D3DPTEXTURECAPS_POW2) == 0; + vid.support.arb_vertex_buffer_object = true; + vid.support.ext_blend_subtract = true; + vid.support.ext_draw_range_elements = true; + vid.support.ext_framebuffer_object = true; + vid.support.ext_texture_3d = true; + vid.support.ext_texture_compression_s3tc = true; + vid.support.ext_texture_filter_anisotropic = true; + vid.support.ati_separate_stencil = (vid_d3d9caps.StencilCaps & D3DSTENCILCAPS_TWOSIDED) != 0; + vid.support.ext_texture_srgb = false; // FIXME use D3DSAMP_SRGBTEXTURE if CheckDeviceFormat agrees + + vid.maxtexturesize_2d = min(vid_d3d9caps.MaxTextureWidth, vid_d3d9caps.MaxTextureHeight); + vid.maxtexturesize_3d = vid_d3d9caps.MaxVolumeExtent; + vid.maxtexturesize_cubemap = vid.maxtexturesize_2d; + vid.texunits = 4; + vid.teximageunits = vid_d3d9caps.MaxSimultaneousTextures; + vid.texarrayunits = 8; // can't find a caps field for this? + vid.max_anisotropy = vid_d3d9caps.MaxAnisotropy; + vid.maxdrawbuffers = vid_d3d9caps.NumSimultaneousRTs; + + vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS); + vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS); + Con_DPrintf("Using D3D9.0 rendering path - %i texture matrix, %i texture images, %i texcoords, shadowmapping supported%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.maxdrawbuffers > 1 ? ", MRT detected (allows prepass deferred lighting)" : ""); + vid.renderpath = RENDERPATH_D3D9; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = true; + vid.useinterleavedarrays = true; + + Cvar_SetQuick(&gl_info_vendor, gl_vendor); + Cvar_SetQuick(&gl_info_renderer, gl_renderer); + Cvar_SetQuick(&gl_info_version, gl_version); + Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); + Cvar_SetQuick(&gl_info_driver, gl_driver); + + // LordHavoc: report supported extensions + Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); + + // clear to black (loading plaque will be seen over this) + IDirect3DDevice9_Clear(vid_d3d9dev, 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); + IDirect3DDevice9_BeginScene(vid_d3d9dev); + IDirect3DDevice9_EndScene(vid_d3d9dev); + IDirect3DDevice9_Present(vid_d3d9dev, NULL, NULL, NULL, NULL); + // because the only time we end/begin scene is in VID_Finish, we'd better start a scene now... + IDirect3DDevice9_BeginScene(vid_d3d9dev); + vid_begunscene = true; + + //vid_menudrawfn = VID_MenuDraw; + //vid_menukeyfn = VID_MenuKey; + vid_usingmouse = false; + vid_usinghidecursor = false; + vid_usingvsync = false; + vid_hidden = vid_reallyhidden = false; + vid_initialized = true; + + IN_StartupMouse (); + + return true; +} +#endif + +qboolean VID_InitModeSOFT(viddef_mode_t *mode) +{ + int i; + HDC hdc; + RECT rect; + MSG msg; + int pixelformat, newpixelformat; + DWORD WindowStyle, ExWindowStyle; + int CenterX, CenterY; + int depth; + DEVMODE thismode; + qboolean foundmode, foundgoodmode; + int bpp = mode->bitsperpixel; + int width = mode->width; + int height = mode->height; + int refreshrate = (int)floor(mode->refreshrate+0.5); + int fullscreen = mode->fullscreen; + + if (vid_initialized) + Sys_Error("VID_InitMode called when video is already initialised"); + + memset(&gdevmode, 0, sizeof(gdevmode)); + + vid_isfullscreen = false; + if (fullscreen) + { + if(vid_forcerefreshrate.integer) + { + foundmode = true; + gdevmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + gdevmode.dmBitsPerPel = bpp; + gdevmode.dmPelsWidth = width; + gdevmode.dmPelsHeight = height; + gdevmode.dmSize = sizeof (gdevmode); + if(refreshrate) + { + gdevmode.dmFields |= DM_DISPLAYFREQUENCY; + gdevmode.dmDisplayFrequency = refreshrate; + } + } + else + { + if(refreshrate == 0) + refreshrate = initialdevmode.dmDisplayFrequency; // default vid_refreshrate to the rate of the desktop + + foundmode = false; + foundgoodmode = false; + + thismode.dmSize = sizeof(thismode); + thismode.dmDriverExtra = 0; + for(i = 0; EnumDisplaySettings(NULL, i, &thismode); ++i) + { + if(~thismode.dmFields & (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY)) + { + Con_DPrintf("enumerating modes yielded a bogus item... please debug this\n"); + continue; + } + if(developer_extra.integer) + Con_DPrintf("Found mode %dx%dx%dbpp %dHz... ", (int)thismode.dmPelsWidth, (int)thismode.dmPelsHeight, (int)thismode.dmBitsPerPel, (int)thismode.dmDisplayFrequency); + if(thismode.dmBitsPerPel != (DWORD)bpp) + { + if(developer_extra.integer) + Con_DPrintf("wrong bpp\n"); + continue; + } + if(thismode.dmPelsWidth != (DWORD)width) + { + if(developer_extra.integer) + Con_DPrintf("wrong width\n"); + continue; + } + if(thismode.dmPelsHeight != (DWORD)height) + { + if(developer_extra.integer) + Con_DPrintf("wrong height\n"); + continue; + } + + if(foundgoodmode) + { + // if we have a good mode, make sure this mode is better than the previous one, and allowed by the refreshrate + if(thismode.dmDisplayFrequency > (DWORD)refreshrate) + { + if(developer_extra.integer) + Con_DPrintf("too high refresh rate\n"); + continue; + } + else if(thismode.dmDisplayFrequency <= gdevmode.dmDisplayFrequency) + { + if(developer_extra.integer) + Con_DPrintf("doesn't beat previous best match (too low)\n"); + continue; + } + } + else if(foundmode) + { + // we do have one, but it isn't good... make sure it has a lower frequency than the previous one + if(thismode.dmDisplayFrequency >= gdevmode.dmDisplayFrequency) + { + if(developer_extra.integer) + Con_DPrintf("doesn't beat previous best match (too high)\n"); + continue; + } + } + // otherwise, take anything + + memcpy(&gdevmode, &thismode, sizeof(gdevmode)); + if(thismode.dmDisplayFrequency <= (DWORD)refreshrate) + foundgoodmode = true; + else + { + if(developer_extra.integer) + Con_DPrintf("(out of range)\n"); + } + foundmode = true; + if(developer_extra.integer) + Con_DPrintf("accepted\n"); + } + } + + if (!foundmode) + { + VID_Shutdown(); + Con_Printf("Unable to find the requested mode %dx%dx%dbpp\n", width, height, bpp); + return false; + } + else if(ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) + { + VID_Shutdown(); + Con_Printf("Unable to change to requested mode %dx%dx%dbpp\n", width, height, bpp); + return false; + } + + vid_isfullscreen = true; + WindowStyle = WS_POPUP; + ExWindowStyle = WS_EX_TOPMOST; + } + else + { + hdc = GetDC (NULL); + i = GetDeviceCaps(hdc, RASTERCAPS); + depth = GetDeviceCaps(hdc, PLANES) * GetDeviceCaps(hdc, BITSPIXEL); + ReleaseDC (NULL, hdc); + if (i & RC_PALETTE) + { + VID_Shutdown(); + Con_Print("Can't run in non-RGB mode\n"); + return false; + } + if (bpp > depth) + { + VID_Shutdown(); + Con_Print("A higher desktop depth is required to run this video mode\n"); + return false; + } + + WindowStyle = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + ExWindowStyle = 0; + } + + rect.top = 0; + rect.left = 0; + rect.right = width; + rect.bottom = height; + AdjustWindowRectEx(&rect, WindowStyle, false, 0); + + if (fullscreen) + { + CenterX = 0; + CenterY = 0; + } + else + { + CenterX = (GetSystemMetrics(SM_CXSCREEN) - (rect.right - rect.left)) / 2; + CenterY = (GetSystemMetrics(SM_CYSCREEN) - (rect.bottom - rect.top)) / 2; + } + CenterX = max(0, CenterX); + CenterY = max(0, CenterY); + + // x and y may be changed by WM_MOVE messages + window_x = CenterX; + window_y = CenterY; + rect.left += CenterX; + rect.right += CenterX; + rect.top += CenterY; + rect.bottom += CenterY; + + pixelformat = 0; + newpixelformat = 0; + gl_extensions = ""; + gl_platformextensions = ""; + + mainwindow = CreateWindowEx (ExWindowStyle, "DarkPlacesWindowClass", gamename, WindowStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, global_hInstance, NULL); + if (!mainwindow) + { + Con_Printf("CreateWindowEx(%d, %s, %s, %d, %d, %d, %d, %d, %p, %p, %p, %p) failed\n", (int)ExWindowStyle, "DarkPlacesWindowClass", gamename, (int)WindowStyle, (int)(rect.left), (int)(rect.top), (int)(rect.right - rect.left), (int)(rect.bottom - rect.top), (void *)NULL, (void *)NULL, (void *)global_hInstance, (void *)NULL); + VID_Shutdown(); + return false; + } + + baseDC = GetDC(mainwindow); + vid.softpixels = NULL; + memset(&vid_softbmi, 0, sizeof(vid_softbmi)); + vid_softbmi.bmiHeader.biSize = sizeof(vid_softbmi.bmiHeader); + vid_softbmi.bmiHeader.biWidth = width; + vid_softbmi.bmiHeader.biHeight = -height; // negative to make a top-down bitmap + vid_softbmi.bmiHeader.biPlanes = 1; + vid_softbmi.bmiHeader.biBitCount = 32; + vid_softbmi.bmiHeader.biCompression = BI_RGB; + vid_softbmi.bmiHeader.biSizeImage = width*height*4; + vid_softbmi.bmiHeader.biClrUsed = 256; + vid_softbmi.bmiHeader.biClrImportant = 256; + vid_softdibhandle = CreateDIBSection(baseDC, &vid_softbmi, DIB_RGB_COLORS, (void **)&vid.softpixels, NULL, 0); + if (!vid_softdibhandle) + { + Con_Printf("CreateDIBSection failed\n"); + VID_Shutdown(); + return false; + } + + vid_softhdc = CreateCompatibleDC(baseDC); + vid_softhdc_backup = SelectObject(vid_softhdc, vid_softdibhandle); + if (!vid_softhdc_backup) + { + Con_Printf("SelectObject failed\n"); + VID_Shutdown(); + return false; + } +// ReleaseDC(mainwindow, baseDC); +// baseDC = NULL; + + vid.softdepthpixels = (unsigned int *)calloc(1, mode->width * mode->height * 4); + if (DPSOFTRAST_Init(mode->width, mode->height, vid_soft_threads.integer, vid_soft_interlace.integer, (unsigned int *)vid.softpixels, (unsigned int *)vid.softdepthpixels) < 0) + { + Con_Printf("Failed to initialize software rasterizer\n"); + VID_Shutdown(); + return false; + } + + VID_Soft_SharedSetup(); + + ShowWindow (mainwindow, SW_SHOWDEFAULT); + UpdateWindow (mainwindow); + + // now we try to make sure we get the focus on the mode switch, because + // sometimes in some systems we don't. We grab the foreground, then + // finish setting up, pump all our messages, and sleep for a little while + // to let messages finish bouncing around the system, then we put + // ourselves at the top of the z order, then grab the foreground again, + // Who knows if it helps, but it probably doesn't hurt + SetForegroundWindow (mainwindow); + + while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + + Sleep (100); + + SetWindowPos (mainwindow, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOCOPYBITS); + + SetForegroundWindow (mainwindow); + + // fix the leftover Alt from any Alt-Tab or the like that switched us away + ClearAllStates (); + + //vid_menudrawfn = VID_MenuDraw; + //vid_menukeyfn = VID_MenuKey; + vid_usingmouse = false; + vid_usinghidecursor = false; + vid_usingvsync = false; + vid_reallyhidden = vid_hidden = false; + vid_initialized = true; + + IN_StartupMouse (); + + return true; +} + +qboolean VID_InitMode(viddef_mode_t *mode) +{ +#ifdef SSE_POSSIBLE + if (vid_soft.integer) + return VID_InitModeSOFT(mode); +#endif +#ifdef SUPPORTD3D +// if (vid_dx11.integer) +// return VID_InitModeDX(mode, 11); +// if (vid_dx10.integer) +// return VID_InitModeDX(mode, 10); + if (vid_dx9.integer) + return VID_InitModeDX(mode, 9); +#endif + return VID_InitModeGL(mode); +} + + +static void IN_Shutdown(void); +void VID_Shutdown (void) +{ + qboolean isgl; + if(vid_initialized == false) + return; + + VID_EnableJoystick(false); + VID_SetMouse(false, false, false); + VID_RestoreSystemGamma(); + + vid_initialized = false; + isgl = gldll != NULL; + IN_Shutdown(); + gl_driver[0] = 0; + gl_extensions = ""; + gl_platform = ""; + gl_platformextensions = ""; + if (vid_softhdc) + { + SelectObject(vid_softhdc, vid_softhdc_backup); + ReleaseDC(mainwindow, vid_softhdc); + } + vid_softhdc = NULL; + vid_softhdc_backup = NULL; + if (vid_softdibhandle) + DeleteObject(vid_softdibhandle); + vid_softdibhandle = NULL; + vid.softpixels = NULL; + if (vid.softdepthpixels) + free(vid.softdepthpixels); + vid.softdepthpixels = NULL; +#ifdef SUPPORTD3D + if (vid_d3d9dev) + { + if (vid_begunscene) + IDirect3DDevice9_EndScene(vid_d3d9dev); + vid_begunscene = false; +// Cmd_ExecuteString("r_texturestats", src_command); +// Cmd_ExecuteString("memlist", src_command); + IDirect3DDevice9_Release(vid_d3d9dev); + } + vid_d3d9dev = NULL; + if (vid_d3d9) + IDirect3D9_Release(vid_d3d9); + vid_d3d9 = NULL; +#endif + if (qwglMakeCurrent) + qwglMakeCurrent(NULL, NULL); + qwglMakeCurrent = NULL; + if (baseRC && qwglDeleteContext) + qwglDeleteContext(baseRC); + qwglDeleteContext = NULL; + // close the library before we get rid of the window + GL_CloseLibrary(); + if (baseDC && mainwindow) + ReleaseDC(mainwindow, baseDC); + baseDC = NULL; + AppActivate(false, false); + if (mainwindow) + DestroyWindow(mainwindow); + mainwindow = 0; + if (vid_isfullscreen && isgl) + ChangeDisplaySettings (NULL, 0); + vid_isfullscreen = false; +} + +void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor) +{ + static qboolean restore_spi; + static int originalmouseparms[3]; + + if (!mouseinitialized) + return; + + if (relative) + { + if (!vid_usingmouse) + { + vid_usingmouse = true; + cl_ignoremousemoves = 2; +#ifdef SUPPORTDIRECTX + if (dinput && g_pMouse) + { + IDirectInputDevice_Acquire(g_pMouse); + dinput_acquired = true; + } + else +#endif + { + RECT window_rect; + window_rect.left = window_x; + window_rect.top = window_y; + window_rect.right = window_x + vid.width; + window_rect.bottom = window_y + vid.height; + + // change mouse settings to turn off acceleration +// COMMANDLINEOPTION: Windows GDI Input: -noforcemparms disables setting of mouse parameters (not used with -dinput, windows only) + if (!COM_CheckParm ("-noforcemparms") && SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0)) + { + int newmouseparms[3]; + newmouseparms[0] = 0; // threshold to double movement (only if accel level is >= 1) + newmouseparms[1] = 0; // threshold to quadruple movement (only if accel level is >= 2) + newmouseparms[2] = 0; // maximum level of acceleration (0 = off) + restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0) != FALSE; + } + else + restore_spi = false; + SetCursorPos ((window_x + vid.width / 2), (window_y + vid.height / 2)); + + SetCapture (mainwindow); + ClipCursor (&window_rect); + } + } + } + else + { + if (vid_usingmouse) + { + vid_usingmouse = false; + cl_ignoremousemoves = 2; +#ifdef SUPPORTDIRECTX + if (dinput_acquired) + { + IDirectInputDevice_Unacquire(g_pMouse); + dinput_acquired = false; + } + else +#endif + { + // restore system mouseparms if we changed them + if (restore_spi) + SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); + restore_spi = false; + ClipCursor (NULL); + ReleaseCapture (); + } + } + } + + if (vid_usinghidecursor != hidecursor) + { + vid_usinghidecursor = hidecursor; + ShowCursor (!hidecursor); + } +} + +void VID_BuildJoyState(vid_joystate_t *joystate) +{ + VID_Shared_BuildJoyState_Begin(joystate); + VID_Shared_BuildJoyState_Finish(joystate); +} + +void VID_EnableJoystick(qboolean enable) +{ + int index = joy_enable.integer > 0 ? joy_index.integer : -1; + qboolean success = false; + int sharedcount = 0; + sharedcount = VID_Shared_SetJoystick(index); + if (index >= 0 && index < sharedcount) + success = true; + + // update cvar containing count of XInput joysticks + if (joy_detected.integer != sharedcount) + Cvar_SetValueQuick(&joy_detected, sharedcount); + + if (joy_active.integer != (success ? 1 : 0)) + Cvar_SetValueQuick(&joy_active, success ? 1 : 0); +} + +#ifdef SUPPORTDIRECTX +/* +=========== +IN_InitDInput +=========== +*/ +static qboolean IN_InitDInput (void) +{ + HRESULT hr; + DIPROPDWORD dipdw = { + { + sizeof(DIPROPDWORD), // diph.dwSize + sizeof(DIPROPHEADER), // diph.dwHeaderSize + 0, // diph.dwObj + DIPH_DEVICE, // diph.dwHow + }, + DINPUT_BUFFERSIZE, // dwData + }; + + if (!hInstDI) + { + hInstDI = LoadLibrary("dinput.dll"); + + if (hInstDI == NULL) + { + Con_Print("Couldn't load dinput.dll\n"); + return false; + } + } + + if (!pDirectInputCreate) + { + pDirectInputCreate = (HRESULT (__stdcall *)(HINSTANCE,DWORD,LPDIRECTINPUT *,LPUNKNOWN))GetProcAddress(hInstDI,"DirectInputCreateA"); + + if (!pDirectInputCreate) + { + Con_Print("Couldn't get DI proc addr\n"); + return false; + } + } + +// register with DirectInput and get an IDirectInput to play with. + hr = iDirectInputCreate(global_hInstance, DIRECTINPUT_VERSION, &g_pdi, NULL); + + if (FAILED(hr)) + { + return false; + } + +// obtain an interface to the system mouse device. +#ifdef __cplusplus + hr = IDirectInput_CreateDevice(g_pdi, GUID_SysMouse, &g_pMouse, NULL); +#else + hr = IDirectInput_CreateDevice(g_pdi, &GUID_SysMouse, &g_pMouse, NULL); +#endif + + if (FAILED(hr)) + { + Con_Print("Couldn't open DI mouse device\n"); + return false; + } + +// set the data format to "mouse format". + hr = IDirectInputDevice_SetDataFormat(g_pMouse, &c_dfDIMouse); + + if (FAILED(hr)) + { + Con_Print("Couldn't set DI mouse format\n"); + return false; + } + +// set the cooperativity level. + hr = IDirectInputDevice_SetCooperativeLevel(g_pMouse, mainwindow, + DISCL_EXCLUSIVE | DISCL_FOREGROUND); + + if (FAILED(hr)) + { + Con_Print("Couldn't set DI coop level\n"); + return false; + } + + +// set the buffer size to DINPUT_BUFFERSIZE elements. +// the buffer size is a DWORD property associated with the device + hr = IDirectInputDevice_SetProperty(g_pMouse, DIPROP_BUFFERSIZE, &dipdw.diph); + + if (FAILED(hr)) + { + Con_Print("Couldn't set DI buffersize\n"); + return false; + } + + return true; +} +#endif + + +/* +=========== +IN_StartupMouse +=========== +*/ +static void IN_StartupMouse (void) +{ + if (COM_CheckParm ("-nomouse")) + return; + + mouseinitialized = true; + +#ifdef SUPPORTDIRECTX +// COMMANDLINEOPTION: Windows Input: -dinput enables DirectInput for mouse input + if (COM_CheckParm ("-dinput")) + dinput = IN_InitDInput (); + + if (dinput) + Con_Print("DirectInput initialized\n"); + else + Con_Print("DirectInput not initialized\n"); +#endif + + mouse_buttons = 10; +} + + +/* +=========== +IN_MouseMove +=========== +*/ +static void IN_MouseMove (void) +{ + POINT current_pos; + + GetCursorPos (¤t_pos); + in_windowmouse_x = current_pos.x - window_x; + in_windowmouse_y = current_pos.y - window_y; + + if (!vid_usingmouse) + return; + +#ifdef SUPPORTDIRECTX + if (dinput_acquired) + { + int i; + DIDEVICEOBJECTDATA od; + DWORD dwElements; + HRESULT hr; + + for (;;) + { + dwElements = 1; + + hr = IDirectInputDevice_GetDeviceData(g_pMouse, + sizeof(DIDEVICEOBJECTDATA), &od, &dwElements, 0); + + if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED)) + { + IDirectInputDevice_Acquire(g_pMouse); + break; + } + + /* Unable to read data or no data available */ + if (FAILED(hr) || dwElements == 0) + break; + + /* Look at the element to see what happened */ + + if ((int)od.dwOfs == DIMOFS_X) + in_mouse_x += (LONG) od.dwData; + if ((int)od.dwOfs == DIMOFS_Y) + in_mouse_y += (LONG) od.dwData; + if ((int)od.dwOfs == DIMOFS_Z) + { + if((LONG)od.dwData < 0) + { + Key_Event(K_MWHEELDOWN, 0, true); + Key_Event(K_MWHEELDOWN, 0, false); + } + else if((LONG)od.dwData > 0) + { + Key_Event(K_MWHEELUP, 0, true); + Key_Event(K_MWHEELUP, 0, false); + } + } + if ((int)od.dwOfs == DIMOFS_BUTTON0) + mstate_di = (mstate_di & ~1) | ((od.dwData & 0x80) >> 7); + if ((int)od.dwOfs == DIMOFS_BUTTON1) + mstate_di = (mstate_di & ~2) | ((od.dwData & 0x80) >> 6); + if ((int)od.dwOfs == DIMOFS_BUTTON2) + mstate_di = (mstate_di & ~4) | ((od.dwData & 0x80) >> 5); + if ((int)od.dwOfs == DIMOFS_BUTTON3) + mstate_di = (mstate_di & ~8) | ((od.dwData & 0x80) >> 4); + } + + // perform button actions + for (i=0 ; i= maxcount) + break; + modes[k].width = thismode.dmPelsWidth; + modes[k].height = thismode.dmPelsHeight; + modes[k].bpp = thismode.dmBitsPerPel; + modes[k].refreshrate = thismode.dmDisplayFrequency; + modes[k].pixelheight_num = 1; + modes[k].pixelheight_denom = 1; // Win32 apparently does not provide this (FIXME) + ++k; + } + return k; +} diff --git a/misc/source/darkplaces-src/view.c b/misc/source/darkplaces-src/view.c new file mode 100644 index 00000000..3e0ccc57 --- /dev/null +++ b/misc/source/darkplaces-src/view.c @@ -0,0 +1,1068 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// view.c -- player eye positioning + +#include "quakedef.h" +#include "cl_collision.h" +#include "image.h" + +void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin); + +/* + +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. + +*/ + +cvar_t cl_rollspeed = {0, "cl_rollspeed", "200", "how much strafing is necessary to tilt the view"}; +cvar_t cl_rollangle = {0, "cl_rollangle", "2.0", "how much to tilt the view when strafing"}; + +cvar_t cl_bob = {CVAR_SAVE, "cl_bob","0.02", "view bobbing amount"}; +cvar_t cl_bobcycle = {CVAR_SAVE, "cl_bobcycle","0.6", "view bobbing speed"}; +cvar_t cl_bobup = {CVAR_SAVE, "cl_bobup","0.5", "view bobbing adjustment that makes the up or down swing of the bob last longer"}; +cvar_t cl_bob2 = {CVAR_SAVE, "cl_bob2","0", "sideways view bobbing amount"}; +cvar_t cl_bob2cycle = {CVAR_SAVE, "cl_bob2cycle","0.6", "sideways view bobbing speed"}; +cvar_t cl_bob2smooth = {CVAR_SAVE, "cl_bob2smooth","0.05", "how fast the view goes back when you stop touching the ground"}; +cvar_t cl_bobfall = {CVAR_SAVE, "cl_bobfall","0", "how much the view swings down when falling (influenced by the speed you hit the ground with)"}; +cvar_t cl_bobfallcycle = {CVAR_SAVE, "cl_bobfallcycle","3", "speed of the bobfall swing"}; +cvar_t cl_bobfallminspeed = {CVAR_SAVE, "cl_bobfallminspeed","200", "necessary amount of speed for bob-falling to occur"}; +cvar_t cl_bobmodel = {CVAR_SAVE, "cl_bobmodel", "1", "enables gun bobbing"}; +cvar_t cl_bobmodel_side = {CVAR_SAVE, "cl_bobmodel_side", "0.15", "gun bobbing sideways sway amount"}; +cvar_t cl_bobmodel_up = {CVAR_SAVE, "cl_bobmodel_up", "0.06", "gun bobbing upward movement amount"}; +cvar_t cl_bobmodel_speed = {CVAR_SAVE, "cl_bobmodel_speed", "7", "gun bobbing speed"}; + +cvar_t cl_leanmodel = {CVAR_SAVE, "cl_leanmodel", "0", "enables gun leaning"}; +cvar_t cl_leanmodel_side_speed = {CVAR_SAVE, "cl_leanmodel_side_speed", "0.7", "gun leaning sideways speed"}; +cvar_t cl_leanmodel_side_limit = {CVAR_SAVE, "cl_leanmodel_side_limit", "35", "gun leaning sideways limit"}; +cvar_t cl_leanmodel_side_highpass1 = {CVAR_SAVE, "cl_leanmodel_side_highpass1", "30", "gun leaning sideways pre-highpass in 1/s"}; +cvar_t cl_leanmodel_side_highpass = {CVAR_SAVE, "cl_leanmodel_side_highpass", "3", "gun leaning sideways highpass in 1/s"}; +cvar_t cl_leanmodel_side_lowpass = {CVAR_SAVE, "cl_leanmodel_side_lowpass", "20", "gun leaning sideways lowpass in 1/s"}; +cvar_t cl_leanmodel_up_speed = {CVAR_SAVE, "cl_leanmodel_up_speed", "0.65", "gun leaning upward speed"}; +cvar_t cl_leanmodel_up_limit = {CVAR_SAVE, "cl_leanmodel_up_limit", "50", "gun leaning upward limit"}; +cvar_t cl_leanmodel_up_highpass1 = {CVAR_SAVE, "cl_leanmodel_up_highpass1", "5", "gun leaning upward pre-highpass in 1/s"}; +cvar_t cl_leanmodel_up_highpass = {CVAR_SAVE, "cl_leanmodel_up_highpass", "15", "gun leaning upward highpass in 1/s"}; +cvar_t cl_leanmodel_up_lowpass = {CVAR_SAVE, "cl_leanmodel_up_lowpass", "20", "gun leaning upward lowpass in 1/s"}; + +cvar_t cl_followmodel = {CVAR_SAVE, "cl_followmodel", "0", "enables gun following"}; +cvar_t cl_followmodel_side_speed = {CVAR_SAVE, "cl_followmodel_side_speed", "0.25", "gun following sideways speed"}; +cvar_t cl_followmodel_side_limit = {CVAR_SAVE, "cl_followmodel_side_limit", "6", "gun following sideways limit"}; +cvar_t cl_followmodel_side_highpass1 = {CVAR_SAVE, "cl_followmodel_side_highpass1", "30", "gun following sideways pre-highpass in 1/s"}; +cvar_t cl_followmodel_side_highpass = {CVAR_SAVE, "cl_followmodel_side_highpass", "5", "gun following sideways highpass in 1/s"}; +cvar_t cl_followmodel_side_lowpass = {CVAR_SAVE, "cl_followmodel_side_lowpass", "10", "gun following sideways lowpass in 1/s"}; +cvar_t cl_followmodel_up_speed = {CVAR_SAVE, "cl_followmodel_up_speed", "0.5", "gun following upward speed"}; +cvar_t cl_followmodel_up_limit = {CVAR_SAVE, "cl_followmodel_up_limit", "5", "gun following upward limit"}; +cvar_t cl_followmodel_up_highpass1 = {CVAR_SAVE, "cl_followmodel_up_highpass1", "60", "gun following upward pre-highpass in 1/s"}; +cvar_t cl_followmodel_up_highpass = {CVAR_SAVE, "cl_followmodel_up_highpass", "2", "gun following upward highpass in 1/s"}; +cvar_t cl_followmodel_up_lowpass = {CVAR_SAVE, "cl_followmodel_up_lowpass", "10", "gun following upward lowpass in 1/s"}; + +cvar_t cl_viewmodel_scale = {0, "cl_viewmodel_scale", "1", "changes size of gun model, lower values prevent poking into walls but cause strange artifacts on lighting and especially r_stereo/vid_stereobuffer options where the size of the gun becomes visible"}; + +cvar_t v_kicktime = {0, "v_kicktime", "0.5", "how long a view kick from damage lasts"}; +cvar_t v_kickroll = {0, "v_kickroll", "0.6", "how much a view kick from damage rolls your view"}; +cvar_t v_kickpitch = {0, "v_kickpitch", "0.6", "how much a view kick from damage pitches your view"}; + +cvar_t v_iyaw_cycle = {0, "v_iyaw_cycle", "2", "v_idlescale yaw speed"}; +cvar_t v_iroll_cycle = {0, "v_iroll_cycle", "0.5", "v_idlescale roll speed"}; +cvar_t v_ipitch_cycle = {0, "v_ipitch_cycle", "1", "v_idlescale pitch speed"}; +cvar_t v_iyaw_level = {0, "v_iyaw_level", "0.3", "v_idlescale yaw amount"}; +cvar_t v_iroll_level = {0, "v_iroll_level", "0.1", "v_idlescale roll amount"}; +cvar_t v_ipitch_level = {0, "v_ipitch_level", "0.3", "v_idlescale pitch amount"}; + +cvar_t v_idlescale = {0, "v_idlescale", "0", "how much of the quake 'drunken view' effect to use"}; + +cvar_t crosshair = {CVAR_SAVE, "crosshair", "0", "selects crosshair to use (0 is none)"}; + +cvar_t v_centermove = {0, "v_centermove", "0.15", "how long before the view begins to center itself (if freelook/+mlook/+jlook/+klook are off)"}; +cvar_t v_centerspeed = {0, "v_centerspeed","500", "how fast the view centers itself"}; + +cvar_t cl_stairsmoothspeed = {CVAR_SAVE, "cl_stairsmoothspeed", "160", "how fast your view moves upward/downward when running up/down stairs"}; + +cvar_t cl_smoothviewheight = {CVAR_SAVE, "cl_smoothviewheight", "0", "time of the averaging to the viewheight value so that it creates a smooth transition. higher values = longer transition, 0 for instant transition."}; + +cvar_t chase_back = {CVAR_SAVE, "chase_back", "48", "chase cam distance from the player"}; +cvar_t chase_up = {CVAR_SAVE, "chase_up", "24", "chase cam distance from the player"}; +cvar_t chase_active = {CVAR_SAVE, "chase_active", "0", "enables chase cam"}; +cvar_t chase_overhead = {CVAR_SAVE, "chase_overhead", "0", "chase cam looks straight down if this is not zero"}; +// GAME_GOODVSBAD2 +cvar_t chase_stevie = {0, "chase_stevie", "0", "(GOODVSBAD2 only) chase cam view from above"}; + +cvar_t v_deathtilt = {0, "v_deathtilt", "1", "whether to use sideways view when dead"}; +cvar_t v_deathtiltangle = {0, "v_deathtiltangle", "80", "what roll angle to use when tilting the view while dead"}; + +// Prophecy camera pitchangle by Alexander "motorsep" Zubov +cvar_t chase_pitchangle = {CVAR_SAVE, "chase_pitchangle", "55", "chase cam pitch angle"}; + +float v_dmg_time, v_dmg_roll, v_dmg_pitch; + + +/* +=============== +V_CalcRoll + +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity) +{ + vec3_t right; + float sign; + float side; + float value; + + AngleVectors (angles, NULL, right, NULL); + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = cl_rollangle.value; + + if (side < cl_rollspeed.value) + side = side * value / cl_rollspeed.value; + else + side = value; + + return side*sign; + +} + +void V_StartPitchDrift (void) +{ + if (cl.laststop == cl.time) + return; // something else is keeping it from drifting + + if (cl.nodrift || !cl.pitchvel) + { + cl.pitchvel = v_centerspeed.value; + cl.nodrift = false; + cl.driftmove = 0; + } +} + +void V_StopPitchDrift (void) +{ + cl.laststop = cl.time; + cl.nodrift = true; + cl.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards cl.idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. + +Drifting is enabled when the center view key is hit, mlook is released and +lookspring is non 0, or when +=============== +*/ +void V_DriftPitch (void) +{ + float delta, move; + + if (noclip_anglehack || !cl.onground || cls.demoplayback ) + { + cl.driftmove = 0; + cl.pitchvel = 0; + return; + } + +// don't count small mouse motion + if (cl.nodrift) + { + if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value) + cl.driftmove = 0; + else + cl.driftmove += cl.realframetime; + + if ( cl.driftmove > v_centermove.value) + { + V_StartPitchDrift (); + } + return; + } + + delta = cl.idealpitch - cl.viewangles[PITCH]; + + if (!delta) + { + cl.pitchvel = 0; + return; + } + + move = cl.realframetime * cl.pitchvel; + cl.pitchvel += cl.realframetime * v_centerspeed.value; + + if (delta > 0) + { + if (move > delta) + { + cl.pitchvel = 0; + move = delta; + } + cl.viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + cl.pitchvel = 0; + move = -delta; + } + cl.viewangles[PITCH] -= move; + } +} + + +/* +============================================================================== + + SCREEN FLASHES + +============================================================================== +*/ + + +/* +=============== +V_ParseDamage +=============== +*/ +void V_ParseDamage (void) +{ + int armor, blood; + vec3_t from; + //vec3_t forward, right; + vec3_t localfrom; + entity_t *ent; + //float side; + float count; + + armor = MSG_ReadByte (); + blood = MSG_ReadByte (); + MSG_ReadVector(from, cls.protocol); + + // Send the Dmg Globals to CSQC + CL_VM_UpdateDmgGlobals(blood, armor, from); + + count = blood*0.5 + armor*0.5; + if (count < 10) + count = 10; + + cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame + + cl.cshifts[CSHIFT_DAMAGE].percent += 3*count; + cl.cshifts[CSHIFT_DAMAGE].alphafade = 150; + if (cl.cshifts[CSHIFT_DAMAGE].percent < 0) + cl.cshifts[CSHIFT_DAMAGE].percent = 0; + if (cl.cshifts[CSHIFT_DAMAGE].percent > 150) + cl.cshifts[CSHIFT_DAMAGE].percent = 150; + + if (armor > blood) + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100; + } + else if (armor) + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50; + } + else + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0; + } + + // calculate view angle kicks + if (cl.entities[cl.viewentity].state_current.active) + { + ent = &cl.entities[cl.viewentity]; + Matrix4x4_Transform(&ent->render.inversematrix, from, localfrom); + VectorNormalize(localfrom); + v_dmg_pitch = count * localfrom[0] * v_kickpitch.value; + v_dmg_roll = count * localfrom[1] * v_kickroll.value; + v_dmg_time = v_kicktime.value; + } +} + +static cshift_t v_cshift; + +/* +================== +V_cshift_f +================== +*/ +static void V_cshift_f (void) +{ + v_cshift.destcolor[0] = atof(Cmd_Argv(1)); + v_cshift.destcolor[1] = atof(Cmd_Argv(2)); + v_cshift.destcolor[2] = atof(Cmd_Argv(3)); + v_cshift.percent = atof(Cmd_Argv(4)); +} + + +/* +================== +V_BonusFlash_f + +When you run over an item, the server sends this command +================== +*/ +static void V_BonusFlash_f (void) +{ + if(Cmd_Argc() == 1) + { + cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215; + cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186; + cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69; + cl.cshifts[CSHIFT_BONUS].percent = 50; + cl.cshifts[CSHIFT_BONUS].alphafade = 100; + } + else if(Cmd_Argc() >= 4 && Cmd_Argc() <= 6) + { + cl.cshifts[CSHIFT_BONUS].destcolor[0] = atof(Cmd_Argv(1)) * 255; + cl.cshifts[CSHIFT_BONUS].destcolor[1] = atof(Cmd_Argv(2)) * 255; + cl.cshifts[CSHIFT_BONUS].destcolor[2] = atof(Cmd_Argv(3)) * 255; + if(Cmd_Argc() >= 5) + cl.cshifts[CSHIFT_BONUS].percent = atof(Cmd_Argv(4)) * 255; // yes, these are HEXADECIMAL percent ;) + else + cl.cshifts[CSHIFT_BONUS].percent = 50; + if(Cmd_Argc() >= 6) + cl.cshifts[CSHIFT_BONUS].alphafade = atof(Cmd_Argv(5)) * 255; + else + cl.cshifts[CSHIFT_BONUS].alphafade = 100; + } + else + Con_Printf("usage:\nbf, or bf R G B [A [alphafade]]\n"); +} + +/* +============================================================================== + + VIEW RENDERING + +============================================================================== +*/ + +extern matrix4x4_t viewmodelmatrix_nobob; +extern matrix4x4_t viewmodelmatrix_withbob; + +#include "cl_collision.h" +#include "csprogs.h" + +/* +================== +V_CalcRefdef + +================== +*/ +#if 0 +static vec3_t eyeboxmins = {-16, -16, -24}; +static vec3_t eyeboxmaxs = { 16, 16, 32}; +#endif + +static vec_t lowpass(vec_t value, vec_t frac, vec_t *store) +{ + frac = bound(0, frac, 1); + return (*store = *store * (1 - frac) + value * frac); +} + +static vec_t lowpass_limited(vec_t value, vec_t frac, vec_t limit, vec_t *store) +{ + lowpass(value, frac, store); + return (*store = bound(value - limit, *store, value + limit)); +} + +static vec_t highpass(vec_t value, vec_t frac, vec_t *store) +{ + return value - lowpass(value, frac, store); +} + +static vec_t highpass_limited(vec_t value, vec_t frac, vec_t limit, vec_t *store) +{ + return value - lowpass_limited(value, frac, limit, store); +} + +static void lowpass3(vec3_t value, vec_t fracx, vec_t fracy, vec_t fracz, vec3_t store, vec3_t out) +{ + out[0] = lowpass(value[0], fracx, &store[0]); + out[1] = lowpass(value[1], fracy, &store[1]); + out[2] = lowpass(value[2], fracz, &store[2]); +} + +static void highpass3(vec3_t value, vec_t fracx, vec_t fracy, vec_t fracz, vec3_t store, vec3_t out) +{ + out[0] = highpass(value[0], fracx, &store[0]); + out[1] = highpass(value[1], fracy, &store[1]); + out[2] = highpass(value[2], fracz, &store[2]); +} + +static void highpass3_limited(vec3_t value, vec_t fracx, vec_t limitx, vec_t fracy, vec_t limity, vec_t fracz, vec_t limitz, vec3_t store, vec3_t out) +{ + out[0] = highpass_limited(value[0], fracx, limitx, &store[0]); + out[1] = highpass_limited(value[1], fracy, limity, &store[1]); + out[2] = highpass_limited(value[2], fracz, limitz, &store[2]); +} + +void V_CalcRefdef (void) +{ + entity_t *ent; + float vieworg[3], viewangles[3], smoothtime; + float gunorg[3], gunangles[3]; + matrix4x4_t tmpmatrix; + + static float viewheightavg; + float viewheight; +#if 0 +// begin of chase camera bounding box size for proper collisions by Alexander Zubov + vec3_t camboxmins = {-3, -3, -3}; + vec3_t camboxmaxs = {3, 3, 3}; +// end of chase camera bounding box size for proper collisions by Alexander Zubov +#endif + trace_t trace; + VectorClear(gunorg); + viewmodelmatrix_nobob = identitymatrix; + viewmodelmatrix_withbob = identitymatrix; + r_refdef.view.matrix = identitymatrix; + if (cls.state == ca_connected && cls.signon == SIGNONS) + { + // ent is the view entity (visible when out of body) + ent = &cl.entities[cl.viewentity]; + // player can look around, so take the origin from the entity, + // and the angles from the input system + Matrix4x4_OriginFromMatrix(&ent->render.matrix, vieworg); + VectorCopy(cl.viewangles, viewangles); + + // calculate how much time has passed since the last V_CalcRefdef + smoothtime = bound(0, cl.time - cl.stairsmoothtime, 0.1); + cl.stairsmoothtime = cl.time; + + // fade damage flash + if (v_dmg_time > 0) + v_dmg_time -= bound(0, smoothtime, 0.1); + + if (cl.intermission) + { + // entity is a fixed camera, just copy the matrix + if (cls.protocol == PROTOCOL_QUAKEWORLD) + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, cl.qw_intermission_origin[0], cl.qw_intermission_origin[1], cl.qw_intermission_origin[2], cl.qw_intermission_angles[0], cl.qw_intermission_angles[1], cl.qw_intermission_angles[2], 1); + else + { + r_refdef.view.matrix = ent->render.matrix; + Matrix4x4_AdjustOrigin(&r_refdef.view.matrix, 0, 0, cl.stats[STAT_VIEWHEIGHT]); + } + Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); + Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); + Matrix4x4_Copy(&viewmodelmatrix_withbob, &viewmodelmatrix_nobob); + } + else + { + // smooth stair stepping, but only if onground and enabled + if (!cl.onground || cl_stairsmoothspeed.value <= 0 || !ent->persistent.trail_allowed) // FIXME use a better way to detect teleport/warp + cl.stairsmoothz = vieworg[2]; + else + { + if (cl.stairsmoothz < vieworg[2]) + vieworg[2] = cl.stairsmoothz = bound(vieworg[2] - cl.movevars_stepheight, cl.stairsmoothz + smoothtime * cl_stairsmoothspeed.value, vieworg[2]); + else if (cl.stairsmoothz > vieworg[2]) + vieworg[2] = cl.stairsmoothz = bound(vieworg[2], cl.stairsmoothz - smoothtime * cl_stairsmoothspeed.value, vieworg[2] + cl.movevars_stepheight); + } + + // apply qw weapon recoil effect (this did not work in QW) + // TODO: add a cvar to disable this + viewangles[PITCH] += cl.qw_weaponkick; + + // apply the viewofs (even if chasecam is used) + // Samual: Lets add smoothing for this too so that things like crouching are done with a transition. + viewheight = bound(0, (cl.time - cl.oldtime) / max(0.0001, cl_smoothviewheight.value), 1); + viewheightavg = viewheightavg * (1 - viewheight) + cl.stats[STAT_VIEWHEIGHT] * viewheight; + vieworg[2] += viewheightavg; + + if (chase_active.value) + { + // observing entity from third person. Added "campitch" by Alexander "motorsep" Zubov + vec_t camback, camup, dist, campitch, forward[3], chase_dest[3]; + + camback = chase_back.value; + camup = chase_up.value; + campitch = chase_pitchangle.value; + + AngleVectors(viewangles, forward, NULL, NULL); + + if (chase_overhead.integer) + { +#if 1 + vec3_t offset; + vec3_t bestvieworg; +#endif + vec3_t up; + viewangles[PITCH] = 0; + AngleVectors(viewangles, forward, NULL, up); + // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range) + chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup; + chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup; + chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup; +#if 0 +#if 1 + //trace = CL_TraceLine(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); + trace = CL_TraceLine(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); +#else + //trace = CL_TraceBox(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); + trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); +#endif + VectorCopy(trace.endpos, vieworg); + vieworg[2] -= 8; +#else + // trace from first person view location to our chosen third person view location +#if 1 + trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); +#else + trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); +#endif + VectorCopy(trace.endpos, bestvieworg); + offset[2] = 0; + for (offset[0] = -16;offset[0] <= 16;offset[0] += 8) + { + for (offset[1] = -16;offset[1] <= 16;offset[1] += 8) + { + AngleVectors(viewangles, NULL, NULL, up); + chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup + offset[0]; + chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup + offset[1]; + chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup + offset[2]; +#if 1 + trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); +#else + trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); +#endif + if (bestvieworg[2] > trace.endpos[2]) + bestvieworg[2] = trace.endpos[2]; + } + } + bestvieworg[2] -= 8; + VectorCopy(bestvieworg, vieworg); +#endif + viewangles[PITCH] = campitch; + } + else + { + if (gamemode == GAME_GOODVSBAD2 && chase_stevie.integer) + { + // look straight down from high above + viewangles[PITCH] = 90; + camback = 2048; + VectorSet(forward, 0, 0, -1); + } + + // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range) + dist = -camback - 8; + chase_dest[0] = vieworg[0] + forward[0] * dist; + chase_dest[1] = vieworg[1] + forward[1] * dist; + chase_dest[2] = vieworg[2] + forward[2] * dist + camup; + trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); + VectorMAMAM(1, trace.endpos, 8, forward, 4, trace.plane.normal, vieworg); + } + } + else + { + // first person view from entity + // angles + if (cl.stats[STAT_HEALTH] <= 0 && v_deathtilt.integer) + viewangles[ROLL] = v_deathtiltangle.value; + VectorAdd(viewangles, cl.punchangle, viewangles); + viewangles[ROLL] += V_CalcRoll(cl.viewangles, cl.velocity); + if (v_dmg_time > 0) + { + viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll; + viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch; + } + // origin + VectorAdd(vieworg, cl.punchvector, vieworg); + if (cl.stats[STAT_HEALTH] > 0) + { + double xyspeed, bob, bobfall; + float cycle; + vec_t frametime; + + //frametime = cl.realframetime * cl.movevars_timescale; + frametime = (cl.time - cl.oldtime) * cl.movevars_timescale; + + // 1. if we teleported, clear the frametime... the lowpass will recover the previous value then + if(!ent->persistent.trail_allowed) // FIXME improve this check + { + // try to fix the first highpass; result is NOT + // perfect! TODO find a better fix + VectorCopy(viewangles, cl.gunangles_prev); + VectorCopy(vieworg, cl.gunorg_prev); + } + + // 2. for the gun origin, only keep the high frequency (non-DC) parts, which is "somewhat like velocity" + VectorAdd(cl.gunorg_highpass, cl.gunorg_prev, cl.gunorg_highpass); + highpass3_limited(vieworg, frametime*cl_followmodel_side_highpass1.value, cl_followmodel_side_limit.value, frametime*cl_followmodel_side_highpass1.value, cl_followmodel_side_limit.value, frametime*cl_followmodel_up_highpass1.value, cl_followmodel_up_limit.value, cl.gunorg_highpass, gunorg); + VectorCopy(vieworg, cl.gunorg_prev); + VectorSubtract(cl.gunorg_highpass, cl.gunorg_prev, cl.gunorg_highpass); + + // in the highpass, we _store_ the DIFFERENCE to the actual view angles... + VectorAdd(cl.gunangles_highpass, cl.gunangles_prev, cl.gunangles_highpass); + cl.gunangles_highpass[PITCH] += 360 * floor((viewangles[PITCH] - cl.gunangles_highpass[PITCH]) / 360 + 0.5); + cl.gunangles_highpass[YAW] += 360 * floor((viewangles[YAW] - cl.gunangles_highpass[YAW]) / 360 + 0.5); + cl.gunangles_highpass[ROLL] += 360 * floor((viewangles[ROLL] - cl.gunangles_highpass[ROLL]) / 360 + 0.5); + highpass3_limited(viewangles, frametime*cl_leanmodel_up_highpass1.value, cl_leanmodel_up_limit.value, frametime*cl_leanmodel_side_highpass1.value, cl_leanmodel_side_limit.value, 0, 0, cl.gunangles_highpass, gunangles); + VectorCopy(viewangles, cl.gunangles_prev); + VectorSubtract(cl.gunangles_highpass, cl.gunangles_prev, cl.gunangles_highpass); + + // 3. calculate the RAW adjustment vectors + gunorg[0] *= (cl_followmodel.value ? -cl_followmodel_side_speed.value : 0); + gunorg[1] *= (cl_followmodel.value ? -cl_followmodel_side_speed.value : 0); + gunorg[2] *= (cl_followmodel.value ? -cl_followmodel_up_speed.value : 0); + + gunangles[PITCH] *= (cl_leanmodel.value ? -cl_leanmodel_up_speed.value : 0); + gunangles[YAW] *= (cl_leanmodel.value ? -cl_leanmodel_side_speed.value : 0); + gunangles[ROLL] = 0; + + // 4. perform highpass/lowpass on the adjustment vectors (turning velocity into acceleration!) + // trick: we must do the lowpass LAST, so the lowpass vector IS the final vector! + highpass3(gunorg, frametime*cl_followmodel_side_highpass.value, frametime*cl_followmodel_side_highpass.value, frametime*cl_followmodel_up_highpass.value, cl.gunorg_adjustment_highpass, gunorg); + lowpass3(gunorg, frametime*cl_followmodel_side_lowpass.value, frametime*cl_followmodel_side_lowpass.value, frametime*cl_followmodel_up_lowpass.value, cl.gunorg_adjustment_lowpass, gunorg); + // we assume here: PITCH = 0, YAW = 1, ROLL = 2 + highpass3(gunangles, frametime*cl_leanmodel_up_highpass.value, frametime*cl_leanmodel_side_highpass.value, 0, cl.gunangles_adjustment_highpass, gunangles); + lowpass3(gunangles, frametime*cl_leanmodel_up_lowpass.value, frametime*cl_leanmodel_side_lowpass.value, 0, cl.gunangles_adjustment_lowpass, gunangles); + + // 5. use the adjusted vectors + VectorAdd(vieworg, gunorg, gunorg); + VectorAdd(viewangles, gunangles, gunangles); + + // bounded XY speed, used by several effects below + xyspeed = bound (0, sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]), 400); + + // vertical view bobbing code + if (cl_bob.value && cl_bobcycle.value) + { + // LordHavoc: this code is *weird*, but not replacable (I think it + // should be done in QC on the server, but oh well, quake is quake) + // LordHavoc: figured out bobup: the time at which the sin is at 180 + // degrees (which allows lengthening or squishing the peak or valley) + cycle = cl.time / cl_bobcycle.value; + cycle -= (int) cycle; + if (cycle < cl_bobup.value) + cycle = sin(M_PI * cycle / cl_bobup.value); + else + cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); + // bob is proportional to velocity in the xy plane + // (don't count Z, or jumping messes it up) + bob = xyspeed * bound(0, cl_bob.value, 0.05); + bob = bob*0.3 + bob*0.7*cycle; + vieworg[2] += bob; + // we also need to adjust gunorg, or this appears like pushing the gun! + // In the old code, this was applied to vieworg BEFORE copying to gunorg, + // but this is not viable with the new followmodel code as that would mean + // that followmodel would work on the munged-by-bob vieworg and do feedback + gunorg[2] += bob; + } + + // horizontal view bobbing code + if (cl_bob2.value && cl_bob2cycle.value) + { + vec3_t bob2vel; + vec3_t forward, right, up; + float side, front; + + cycle = cl.time / cl_bob2cycle.value; + cycle -= (int) cycle; + if (cycle < 0.5) + cycle = cos(M_PI * cycle / 0.5); // cos looks better here with the other view bobbing using sin + else + cycle = cos(M_PI + M_PI * (cycle-0.5)/0.5); + bob = bound(0, cl_bob2.value, 0.05) * cycle; + + // this value slowly decreases from 1 to 0 when we stop touching the ground. + // The cycle is later multiplied with it so the view smooths back to normal + if (cl.onground && !cl.cmd.jump) // also block the effect while the jump button is pressed, to avoid twitches when bunny-hopping + cl.bob2_smooth = 1; + else + { + if(cl.bob2_smooth > 0) + cl.bob2_smooth -= bound(0, cl_bob2smooth.value, 1); + else + cl.bob2_smooth = 0; + } + + // calculate the front and side of the player between the X and Y axes + AngleVectors(viewangles, forward, right, up); + // now get the speed based on those angles. The bounds should match the same value as xyspeed's + side = bound(-400, DotProduct (cl.velocity, right) * cl.bob2_smooth, 400); + front = bound(-400, DotProduct (cl.velocity, forward) * cl.bob2_smooth, 400); + VectorScale(forward, bob, forward); + VectorScale(right, bob, right); + // we use side with forward and front with right, so the bobbing goes + // to the side when we walk forward and to the front when we strafe + VectorMAMAM(side, forward, front, right, 0, up, bob2vel); + vieworg[0] += bob2vel[0]; + vieworg[1] += bob2vel[1]; + // we also need to adjust gunorg, or this appears like pushing the gun! + // In the old code, this was applied to vieworg BEFORE copying to gunorg, + // but this is not viable with the new followmodel code as that would mean + // that followmodel would work on the munged-by-bob vieworg and do feedback + gunorg[0] += bob2vel[0]; + gunorg[1] += bob2vel[1]; + } + + // fall bobbing code + // causes the view to swing down and back up when touching the ground + if (cl_bobfall.value && cl_bobfallcycle.value) + { + if (!cl.onground) + { + cl.bobfall_speed = bound(-400, cl.velocity[2], 0) * bound(0, cl_bobfall.value, 0.1); + if (cl.velocity[2] < -cl_bobfallminspeed.value) + cl.bobfall_swing = 1; + else + cl.bobfall_swing = 0; // TODO really? + } + else + { + cl.bobfall_swing = max(0, cl.bobfall_swing - cl_bobfallcycle.value * frametime); + + bobfall = sin(M_PI * cl.bobfall_swing) * cl.bobfall_speed; + vieworg[2] += bobfall; + gunorg[2] += bobfall; + } + } + + // gun model bobbing code + if (cl_bobmodel.value) + { + // calculate for swinging gun model + // the gun bobs when running on the ground, but doesn't bob when you're in the air. + // Sajt: I tried to smooth out the transitions between bob and no bob, which works + // for the most part, but for some reason when you go through a message trigger or + // pick up an item or anything like that it will momentarily jolt the gun. + vec3_t forward, right, up; + float bspeed; + float s; + float t; + + s = cl.time * cl_bobmodel_speed.value; + if (cl.onground) + { + if (cl.time - cl.hitgroundtime < 0.2) + { + // just hit the ground, speed the bob back up over the next 0.2 seconds + t = cl.time - cl.hitgroundtime; + t = bound(0, t, 0.2); + t *= 5; + } + else + t = 1; + } + else + { + // recently left the ground, slow the bob down over the next 0.2 seconds + t = cl.time - cl.lastongroundtime; + t = 0.2 - bound(0, t, 0.2); + t *= 5; + } + + bspeed = xyspeed * 0.01f; + AngleVectors (gunangles, forward, right, up); + bob = bspeed * cl_bobmodel_side.value * cl_viewmodel_scale.value * sin (s) * t; + VectorMA (gunorg, bob, right, gunorg); + bob = bspeed * cl_bobmodel_up.value * cl_viewmodel_scale.value * cos (s * 2) * t; + VectorMA (gunorg, bob, up, gunorg); + } + } + } + // calculate a view matrix for rendering the scene + if (v_idlescale.value) + { + viewangles[0] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value; + viewangles[1] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value; + viewangles[2] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value; + } + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0], viewangles[1], viewangles[2], 1); + + // calculate a viewmodel matrix for use in view-attached entities + Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); + Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); + + Matrix4x4_CreateFromQuakeEntity(&viewmodelmatrix_withbob, gunorg[0], gunorg[1], gunorg[2], gunangles[0], gunangles[1], gunangles[2], cl_viewmodel_scale.value); + VectorCopy(vieworg, cl.csqc_vieworiginfromengine); + VectorCopy(viewangles, cl.csqc_viewanglesfromengine); + + Matrix4x4_Invert_Simple(&tmpmatrix, &r_refdef.view.matrix); + Matrix4x4_Concat(&cl.csqc_viewmodelmatrixfromengine, &tmpmatrix, &viewmodelmatrix_withbob); + } + } +} + +void V_FadeViewFlashs(void) +{ + // don't flash if time steps backwards + if (cl.time <= cl.oldtime) + return; + // drop the damage value + cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*cl.cshifts[CSHIFT_DAMAGE].alphafade; + if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0) + cl.cshifts[CSHIFT_DAMAGE].percent = 0; + // drop the bonus value + cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*cl.cshifts[CSHIFT_BONUS].alphafade; + if (cl.cshifts[CSHIFT_BONUS].percent <= 0) + cl.cshifts[CSHIFT_BONUS].percent = 0; +} + +void V_CalcViewBlend(void) +{ + float a2; + int j; + r_refdef.viewblend[0] = 0; + r_refdef.viewblend[1] = 0; + r_refdef.viewblend[2] = 0; + r_refdef.viewblend[3] = 0; + r_refdef.frustumscale_x = 1; + r_refdef.frustumscale_y = 1; + if (cls.state == ca_connected && cls.signon == SIGNONS) + { + // set contents color + int supercontents; + vec3_t vieworigin; + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, vieworigin); + supercontents = CL_PointSuperContents(vieworigin); + if (supercontents & SUPERCONTENTS_LIQUIDSMASK) + { + r_refdef.frustumscale_x *= 1 - (((sin(cl.time * 4.7) + 1) * 0.015) * r_waterwarp.value); + r_refdef.frustumscale_y *= 1 - (((sin(cl.time * 3.0) + 1) * 0.015) * r_waterwarp.value); + if (supercontents & SUPERCONTENTS_LAVA) + { + cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255; + cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80; + cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0; + } + else if (supercontents & SUPERCONTENTS_SLIME) + { + cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0; + cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25; + cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5; + } + else + { + cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130; + cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80; + cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50; + } + cl.cshifts[CSHIFT_CONTENTS].percent = 150 * 0.5; + } + else + { + cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0; + cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 0; + cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0; + cl.cshifts[CSHIFT_CONTENTS].percent = 0; + } + + if (gamemode != GAME_TRANSFUSION) + { + if (cl.stats[STAT_ITEMS] & IT_QUAD) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255; + cl.cshifts[CSHIFT_POWERUP].percent = 30; + } + else if (cl.stats[STAT_ITEMS] & IT_SUIT) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; + cl.cshifts[CSHIFT_POWERUP].percent = 20; + } + else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100; + cl.cshifts[CSHIFT_POWERUP].percent = 100; + } + else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; + cl.cshifts[CSHIFT_POWERUP].percent = 30; + } + else + cl.cshifts[CSHIFT_POWERUP].percent = 0; + } + + cl.cshifts[CSHIFT_VCSHIFT].destcolor[0] = v_cshift.destcolor[0]; + cl.cshifts[CSHIFT_VCSHIFT].destcolor[1] = v_cshift.destcolor[1]; + cl.cshifts[CSHIFT_VCSHIFT].destcolor[2] = v_cshift.destcolor[2]; + cl.cshifts[CSHIFT_VCSHIFT].percent = v_cshift.percent; + + // LordHavoc: fixed V_CalcBlend + for (j = 0;j < NUM_CSHIFTS;j++) + { + a2 = bound(0.0f, cl.cshifts[j].percent * (1.0f / 255.0f), 1.0f); + if (a2 > 0) + { + VectorLerp(r_refdef.viewblend, a2, cl.cshifts[j].destcolor, r_refdef.viewblend); + r_refdef.viewblend[3] = (1 - (1 - r_refdef.viewblend[3]) * (1 - a2)); // correct alpha multiply... took a while to find it on the web + } + } + // saturate color (to avoid blending in black) + if (r_refdef.viewblend[3]) + { + a2 = 1 / r_refdef.viewblend[3]; + VectorScale(r_refdef.viewblend, a2, r_refdef.viewblend); + } + r_refdef.viewblend[0] = bound(0.0f, r_refdef.viewblend[0], 255.0f); + r_refdef.viewblend[1] = bound(0.0f, r_refdef.viewblend[1], 255.0f); + r_refdef.viewblend[2] = bound(0.0f, r_refdef.viewblend[2], 255.0f); + r_refdef.viewblend[3] = bound(0.0f, r_refdef.viewblend[3] * gl_polyblend.value, 1.0f); + if (vid.sRGB3D) + { + r_refdef.viewblend[0] = Image_LinearFloatFromsRGB(r_refdef.viewblend[0]); + r_refdef.viewblend[1] = Image_LinearFloatFromsRGB(r_refdef.viewblend[1]); + r_refdef.viewblend[2] = Image_LinearFloatFromsRGB(r_refdef.viewblend[2]); + } + else + { + r_refdef.viewblend[0] *= (1.0f/256.0f); + r_refdef.viewblend[1] *= (1.0f/256.0f); + r_refdef.viewblend[2] *= (1.0f/256.0f); + } + + // Samual: Ugly hack, I know. But it's the best we can do since + // there is no way to detect client states from the engine. + if (cl.stats[STAT_HEALTH] <= 0 && cl.stats[STAT_HEALTH] != -666 && + cl.stats[STAT_HEALTH] != -2342 && cl_deathfade.value > 0) + { + cl.deathfade += cl_deathfade.value * max(0.00001, cl.time - cl.oldtime); + cl.deathfade = bound(0.0f, cl.deathfade, 0.9f); + } + else + cl.deathfade = 0.0f; + + if(cl.deathfade > 0) + { + float a; + float deathfadevec[3] = {0.3f, 0.0f, 0.0f}; + a = r_refdef.viewblend[3] + cl.deathfade - r_refdef.viewblend[3]*cl.deathfade; + if(a > 0) + VectorMAM(r_refdef.viewblend[3] * (1 - cl.deathfade) / a, r_refdef.viewblend, cl.deathfade / a, deathfadevec, r_refdef.viewblend); + r_refdef.viewblend[3] = a; + } + } +} + +//============================================================================ + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + Cmd_AddCommand ("v_cshift", V_cshift_f, "sets tint color of view"); + Cmd_AddCommand ("bf", V_BonusFlash_f, "briefly flashes a bright color tint on view (used when items are picked up); optionally takes R G B [A [alphafade]] arguments to specify how the flash looks"); + Cmd_AddCommand ("centerview", V_StartPitchDrift, "gradually recenter view (stop looking up/down)"); + + Cvar_RegisterVariable (&v_centermove); + Cvar_RegisterVariable (&v_centerspeed); + + Cvar_RegisterVariable (&v_iyaw_cycle); + Cvar_RegisterVariable (&v_iroll_cycle); + Cvar_RegisterVariable (&v_ipitch_cycle); + Cvar_RegisterVariable (&v_iyaw_level); + Cvar_RegisterVariable (&v_iroll_level); + Cvar_RegisterVariable (&v_ipitch_level); + + Cvar_RegisterVariable (&v_idlescale); + Cvar_RegisterVariable (&crosshair); + + Cvar_RegisterVariable (&cl_rollspeed); + Cvar_RegisterVariable (&cl_rollangle); + Cvar_RegisterVariable (&cl_bob); + Cvar_RegisterVariable (&cl_bobcycle); + Cvar_RegisterVariable (&cl_bobup); + Cvar_RegisterVariable (&cl_bob2); + Cvar_RegisterVariable (&cl_bob2cycle); + Cvar_RegisterVariable (&cl_bob2smooth); + Cvar_RegisterVariable (&cl_bobfall); + Cvar_RegisterVariable (&cl_bobfallcycle); + Cvar_RegisterVariable (&cl_bobfallminspeed); + Cvar_RegisterVariable (&cl_bobmodel); + Cvar_RegisterVariable (&cl_bobmodel_side); + Cvar_RegisterVariable (&cl_bobmodel_up); + Cvar_RegisterVariable (&cl_bobmodel_speed); + + Cvar_RegisterVariable (&cl_leanmodel); + Cvar_RegisterVariable (&cl_leanmodel_side_speed); + Cvar_RegisterVariable (&cl_leanmodel_side_limit); + Cvar_RegisterVariable (&cl_leanmodel_side_highpass1); + Cvar_RegisterVariable (&cl_leanmodel_side_lowpass); + Cvar_RegisterVariable (&cl_leanmodel_side_highpass); + Cvar_RegisterVariable (&cl_leanmodel_up_speed); + Cvar_RegisterVariable (&cl_leanmodel_up_limit); + Cvar_RegisterVariable (&cl_leanmodel_up_highpass1); + Cvar_RegisterVariable (&cl_leanmodel_up_lowpass); + Cvar_RegisterVariable (&cl_leanmodel_up_highpass); + + Cvar_RegisterVariable (&cl_followmodel); + Cvar_RegisterVariable (&cl_followmodel_side_speed); + Cvar_RegisterVariable (&cl_followmodel_side_limit); + Cvar_RegisterVariable (&cl_followmodel_side_highpass1); + Cvar_RegisterVariable (&cl_followmodel_side_lowpass); + Cvar_RegisterVariable (&cl_followmodel_side_highpass); + Cvar_RegisterVariable (&cl_followmodel_up_speed); + Cvar_RegisterVariable (&cl_followmodel_up_limit); + Cvar_RegisterVariable (&cl_followmodel_up_highpass1); + Cvar_RegisterVariable (&cl_followmodel_up_lowpass); + Cvar_RegisterVariable (&cl_followmodel_up_highpass); + + Cvar_RegisterVariable (&cl_viewmodel_scale); + + Cvar_RegisterVariable (&v_kicktime); + Cvar_RegisterVariable (&v_kickroll); + Cvar_RegisterVariable (&v_kickpitch); + + Cvar_RegisterVariable (&cl_stairsmoothspeed); + + Cvar_RegisterVariable (&cl_smoothviewheight); + + Cvar_RegisterVariable (&chase_back); + Cvar_RegisterVariable (&chase_up); + Cvar_RegisterVariable (&chase_active); + Cvar_RegisterVariable (&chase_overhead); + Cvar_RegisterVariable (&chase_pitchangle); + Cvar_RegisterVariable (&chase_stevie); + + Cvar_RegisterVariable (&v_deathtilt); + Cvar_RegisterVariable (&v_deathtiltangle); +} + diff --git a/misc/source/darkplaces-src/wad.c b/misc/source/darkplaces-src/wad.c new file mode 100644 index 00000000..a07f87bb --- /dev/null +++ b/misc/source/darkplaces-src/wad.c @@ -0,0 +1,296 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +#include "quakedef.h" +#include "image.h" +#include "wad.h" + +typedef struct mwad_s +{ + qfile_t *file; + int numlumps; + lumpinfo_t *lumps; +} +mwad_t; + +typedef struct wadstate_s +{ + unsigned char *gfx_base; + mwad_t gfx; + memexpandablearray_t hlwads; +} +wadstate_t; + +static wadstate_t wad; + +/* +================== +W_CleanupName + +Lowercases name and pads with spaces and a terminating 0 to the length of +lumpinfo_t->name. +Used so lumpname lookups can proceed rapidly by comparing 4 chars at a time +Space padding is so names can be printed nicely in tables. +Can safely be performed in place. +================== +*/ +static void W_CleanupName (const char *in, char *out) +{ + int i; + int c; + + for (i=0 ; i<16 ; i++ ) + { + c = in[i]; + if (!c) + break; + + if (c >= 'A' && c <= 'Z') + c += ('a' - 'A'); + out[i] = c; + } + + for ( ; i< 16 ; i++ ) + out[i] = 0; +} + +static void W_SwapLumps(int numlumps, lumpinfo_t *lumps) +{ + int i; + for (i = 0;i < numlumps;i++) + { + lumps[i].filepos = LittleLong(lumps[i].filepos); + lumps[i].disksize = LittleLong(lumps[i].disksize); + lumps[i].size = LittleLong(lumps[i].size); + W_CleanupName(lumps[i].name, lumps[i].name); + } +} + +void W_UnloadAll(void) +{ + unsigned int i; + mwad_t *w; + // free gfx.wad if it is loaded + if (wad.gfx_base) + Mem_Free(wad.gfx_base); + wad.gfx_base = NULL; + // close all hlwad files and free their lumps data + for (i = 0;i < Mem_ExpandableArray_IndexRange(&wad.hlwads);i++) + { + w = (mwad_t *) Mem_ExpandableArray_RecordAtIndex(&wad.hlwads, i); + if (!w) + continue; + if (w->file) + FS_Close(w->file); + w->file = NULL; + if (w->lumps) + Mem_Free(w->lumps); + w->lumps = NULL; + } + // free the hlwads array + Mem_ExpandableArray_FreeArray(&wad.hlwads); + // clear all state + memset(&wad, 0, sizeof(wad)); +} + +unsigned char *W_GetLumpName(const char *name) +{ + int i; + fs_offset_t filesize; + lumpinfo_t *lump; + char clean[16]; + wadinfo_t *header; + int infotableofs; + + W_CleanupName (name, clean); + + if (!wad.gfx_base) + { + if ((wad.gfx_base = FS_LoadFile ("gfx.wad", cls.permanentmempool, false, &filesize))) + { + if (memcmp(wad.gfx_base, "WAD2", 4)) + { + Con_Print("gfx.wad doesn't have WAD2 id\n"); + Mem_Free(wad.gfx_base); + wad.gfx_base = NULL; + } + else + { + header = (wadinfo_t *)wad.gfx_base; + wad.gfx.numlumps = LittleLong(header->numlumps); + infotableofs = LittleLong(header->infotableofs); + wad.gfx.lumps = (lumpinfo_t *)(wad.gfx_base + infotableofs); + + // byteswap the gfx.wad lumps in place + W_SwapLumps(wad.gfx.numlumps, wad.gfx.lumps); + } + } + } + + for (lump = wad.gfx.lumps, i = 0;i < wad.gfx.numlumps;i++, lump++) + if (!strcmp(clean, lump->name)) + return (wad.gfx_base + lump->filepos); + return NULL; +} + +/* +==================== +W_LoadTextureWadFile +==================== +*/ +void W_LoadTextureWadFile (char *filename, int complain) +{ + wadinfo_t header; + int infotableofs; + qfile_t *file; + int numlumps; + mwad_t *w; + + file = FS_OpenVirtualFile(filename, false); + if (!file) + { + if (complain) + Con_Printf("W_LoadTextureWadFile: couldn't find %s\n", filename); + return; + } + + if (FS_Read(file, &header, sizeof(wadinfo_t)) != sizeof(wadinfo_t)) + {Con_Print("W_LoadTextureWadFile: unable to read wad header\n");FS_Close(file);file = NULL;return;} + + if(memcmp(header.identification, "WAD3", 4)) + {Con_Printf("W_LoadTextureWadFile: Wad file %s doesn't have WAD3 id\n",filename);FS_Close(file);file = NULL;return;} + + numlumps = LittleLong(header.numlumps); + if (numlumps < 1 || numlumps > 65536) + {Con_Printf("W_LoadTextureWadFile: invalid number of lumps (%i)\n", numlumps);FS_Close(file);file = NULL;return;} + infotableofs = LittleLong(header.infotableofs); + if (FS_Seek (file, infotableofs, SEEK_SET)) + {Con_Print("W_LoadTextureWadFile: unable to seek to lump table\n");FS_Close(file);file = NULL;return;} + + if (!wad.hlwads.mempool) + Mem_ExpandableArray_NewArray(&wad.hlwads, cls.permanentmempool, sizeof(mwad_t), 16); + w = (mwad_t *) Mem_ExpandableArray_AllocRecord(&wad.hlwads); + w->file = file; + w->numlumps = numlumps; + w->lumps = (lumpinfo_t *) Mem_Alloc(cls.permanentmempool, w->numlumps * sizeof(lumpinfo_t)); + + if (!w->lumps) + { + Con_Print("W_LoadTextureWadFile: unable to allocate temporary memory for lump table\n"); + FS_Close(w->file); + w->file = NULL; + w->numlumps = 0; + return; + } + + if (FS_Read(file, w->lumps, sizeof(lumpinfo_t) * w->numlumps) != (fs_offset_t)sizeof(lumpinfo_t) * numlumps) + { + Con_Print("W_LoadTextureWadFile: unable to read lump table\n"); + FS_Close(w->file); + w->file = NULL; + w->numlumps = 0; + Mem_Free(w->lumps); + w->lumps = NULL; + return; + } + + W_SwapLumps(w->numlumps, w->lumps); + + // leaves the file open +} + +unsigned char *W_ConvertWAD3TextureBGRA(miptex_t *tex) +{ + unsigned char *in, *data, *out, *pal; + int d, p; + + in = (unsigned char *)tex + tex->offsets[0]; + data = out = (unsigned char *)Mem_Alloc(tempmempool, tex->width * tex->height * 4); + if (!data) + return NULL; + image_width = tex->width; + image_height = tex->height; + pal = in + (((image_width * image_height) * 85) >> 6); + pal += 2; + for (d = 0;d < image_width * image_height;d++) + { + p = *in++; + if (tex->name[0] == '{' && p == 255) + out[0] = out[1] = out[2] = out[3] = 0; + else + { + p *= 3; + out[2] = pal[p]; + out[1] = pal[p+1]; + out[0] = pal[p+2]; + out[3] = 255; + } + out += 4; + } + return data; +} + +unsigned char *W_GetTextureBGRA(char *name) +{ + unsigned int i, j, k; + miptex_t *tex; + unsigned char *data; + mwad_t *w; + char texname[17]; + size_t range; + + texname[16] = 0; + W_CleanupName(name, texname); + if (!wad.hlwads.mempool) + Mem_ExpandableArray_NewArray(&wad.hlwads, cls.permanentmempool, sizeof(mwad_t), 16); + range = Mem_ExpandableArray_IndexRange(&wad.hlwads); + for (k = 0;k < range;k++) + { + w = (mwad_t *)Mem_ExpandableArray_RecordAtIndex(&wad.hlwads, k); + if (!w) + continue; + for (i = 0;i < (unsigned int)w->numlumps;i++) + { + if (!strcmp(texname, w->lumps[i].name)) // found it + { + if (FS_Seek(w->file, w->lumps[i].filepos, SEEK_SET)) + {Con_Print("W_GetTexture: corrupt WAD3 file\n");return NULL;} + + tex = (miptex_t *)Mem_Alloc(tempmempool, w->lumps[i].disksize); + if (!tex) + return NULL; + if (FS_Read(w->file, tex, w->lumps[i].size) < w->lumps[i].disksize) + {Con_Print("W_GetTexture: corrupt WAD3 file\n");return NULL;} + + tex->width = LittleLong(tex->width); + tex->height = LittleLong(tex->height); + for (j = 0;j < MIPLEVELS;j++) + tex->offsets[j] = LittleLong(tex->offsets[j]); + data = W_ConvertWAD3TextureBGRA(tex); + Mem_Free(tex); + return data; + } + } + } + image_width = image_height = 0; + return NULL; +} + diff --git a/misc/source/darkplaces-src/wad.h b/misc/source/darkplaces-src/wad.h new file mode 100644 index 00000000..b5ab8dba --- /dev/null +++ b/misc/source/darkplaces-src/wad.h @@ -0,0 +1,77 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// wad.h + +#ifndef WAD_H +#define WAD_H + +//=============== +// TYPES +//=============== + +#define CMP_NONE 0 +#define CMP_LZSS 1 + +#define TYP_NONE 0 +#define TYP_LABEL 1 + +#define TYP_LUMPY 64 // 64 + grab command number +#define TYP_PALETTE 64 +#define TYP_QTEX 65 +#define TYP_QPIC 66 +#define TYP_SOUND 67 +#define TYP_MIPTEX 68 + +typedef struct qpic_s +{ + int width, height; + unsigned char data[4]; // variably sized +} qpic_t; + + + +typedef struct wadinfo_s +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct lumpinfo_s +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +void W_UnloadAll(void); +unsigned char *W_GetLumpName(const char *name); + +// halflife texture wads +void W_LoadTextureWadFile(char *filename, int complain); +unsigned char *W_GetTextureBGRA(char *name); // returns tempmempool allocated image data, width and height are in image_width and image_height +unsigned char *W_ConvertWAD3TextureBGRA(miptex_t *tex); // returns tempmempool allocated image data, width and height are in image_width and image_height + +#endif + diff --git a/misc/source/darkplaces-src/world.c b/misc/source/darkplaces-src/world.c new file mode 100644 index 00000000..4830996c --- /dev/null +++ b/misc/source/darkplaces-src/world.c @@ -0,0 +1,2696 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// world.c -- world query functions + +#include "quakedef.h" +#include "clvm_cmds.h" +#include "cl_collision.h" + +/* + +entities never clip against themselves, or their owner + +line of sight checks trace->inopen and trace->inwater, but bullets don't + +*/ + +static void World_Physics_Init(void); +void World_Init(void) +{ + Collision_Init(); + World_Physics_Init(); +} + +static void World_Physics_Shutdown(void); +void World_Shutdown(void) +{ + World_Physics_Shutdown(); +} + +static void World_Physics_Start(world_t *world); +void World_Start(world_t *world) +{ + World_Physics_Start(world); +} + +static void World_Physics_End(world_t *world); +void World_End(world_t *world) +{ + World_Physics_End(world); +} + +//============================================================================ + +/// World_ClearLink is used for new headnodes +void World_ClearLink (link_t *l) +{ + l->entitynumber = 0; + l->prev = l->next = l; +} + +void World_RemoveLink (link_t *l) +{ + l->next->prev = l->prev; + l->prev->next = l->next; +} + +void World_InsertLinkBefore (link_t *l, link_t *before, int entitynumber) +{ + l->entitynumber = entitynumber; + l->next = before; + l->prev = before->prev; + l->prev->next = l; + l->next->prev = l; +} + +/* +=============================================================================== + +ENTITY AREA CHECKING + +=============================================================================== +*/ + +void World_PrintAreaStats(world_t *world, const char *worldname) +{ + Con_Printf("%s areagrid check stats: %d calls %d nodes (%f per call) %d entities (%f per call)\n", worldname, world->areagrid_stats_calls, world->areagrid_stats_nodechecks, (double) world->areagrid_stats_nodechecks / (double) world->areagrid_stats_calls, world->areagrid_stats_entitychecks, (double) world->areagrid_stats_entitychecks / (double) world->areagrid_stats_calls); + world->areagrid_stats_calls = 0; + world->areagrid_stats_nodechecks = 0; + world->areagrid_stats_entitychecks = 0; +} + +/* +=============== +World_SetSize + +=============== +*/ +void World_SetSize(world_t *world, const char *filename, const vec3_t mins, const vec3_t maxs) +{ + int i; + + strlcpy(world->filename, filename, sizeof(world->filename)); + VectorCopy(mins, world->mins); + VectorCopy(maxs, world->maxs); + + // the areagrid_marknumber is not allowed to be 0 + if (world->areagrid_marknumber < 1) + world->areagrid_marknumber = 1; + // choose either the world box size, or a larger box to ensure the grid isn't too fine + world->areagrid_size[0] = max(world->maxs[0] - world->mins[0], AREA_GRID * sv_areagrid_mingridsize.value); + world->areagrid_size[1] = max(world->maxs[1] - world->mins[1], AREA_GRID * sv_areagrid_mingridsize.value); + world->areagrid_size[2] = max(world->maxs[2] - world->mins[2], AREA_GRID * sv_areagrid_mingridsize.value); + // figure out the corners of such a box, centered at the center of the world box + world->areagrid_mins[0] = (world->mins[0] + world->maxs[0] - world->areagrid_size[0]) * 0.5f; + world->areagrid_mins[1] = (world->mins[1] + world->maxs[1] - world->areagrid_size[1]) * 0.5f; + world->areagrid_mins[2] = (world->mins[2] + world->maxs[2] - world->areagrid_size[2]) * 0.5f; + world->areagrid_maxs[0] = (world->mins[0] + world->maxs[0] + world->areagrid_size[0]) * 0.5f; + world->areagrid_maxs[1] = (world->mins[1] + world->maxs[1] + world->areagrid_size[1]) * 0.5f; + world->areagrid_maxs[2] = (world->mins[2] + world->maxs[2] + world->areagrid_size[2]) * 0.5f; + // now calculate the actual useful info from that + VectorNegate(world->areagrid_mins, world->areagrid_bias); + world->areagrid_scale[0] = AREA_GRID / world->areagrid_size[0]; + world->areagrid_scale[1] = AREA_GRID / world->areagrid_size[1]; + world->areagrid_scale[2] = AREA_GRID / world->areagrid_size[2]; + World_ClearLink(&world->areagrid_outside); + for (i = 0;i < AREA_GRIDNODES;i++) + World_ClearLink(&world->areagrid[i]); + if (developer_extra.integer) + Con_DPrintf("areagrid settings: divisions %ix%ix1 : box %f %f %f : %f %f %f size %f %f %f grid %f %f %f (mingrid %f)\n", AREA_GRID, AREA_GRID, world->areagrid_mins[0], world->areagrid_mins[1], world->areagrid_mins[2], world->areagrid_maxs[0], world->areagrid_maxs[1], world->areagrid_maxs[2], world->areagrid_size[0], world->areagrid_size[1], world->areagrid_size[2], 1.0f / world->areagrid_scale[0], 1.0f / world->areagrid_scale[1], 1.0f / world->areagrid_scale[2], sv_areagrid_mingridsize.value); +} + +/* +=============== +World_UnlinkAll + +=============== +*/ +void World_UnlinkAll(world_t *world) +{ + int i; + link_t *grid; + // unlink all entities one by one + grid = &world->areagrid_outside; + while (grid->next != grid) + World_UnlinkEdict(PRVM_EDICT_NUM(grid->next->entitynumber)); + for (i = 0, grid = world->areagrid;i < AREA_GRIDNODES;i++, grid++) + while (grid->next != grid) + World_UnlinkEdict(PRVM_EDICT_NUM(grid->next->entitynumber)); +} + +/* +=============== + +=============== +*/ +void World_UnlinkEdict(prvm_edict_t *ent) +{ + int i; + for (i = 0;i < ENTITYGRIDAREAS;i++) + { + if (ent->priv.server->areagrid[i].prev) + { + World_RemoveLink (&ent->priv.server->areagrid[i]); + ent->priv.server->areagrid[i].prev = ent->priv.server->areagrid[i].next = NULL; + } + } +} + +int World_EntitiesInBox(world_t *world, const vec3_t mins, const vec3_t maxs, int maxlist, prvm_edict_t **list) +{ + int numlist; + link_t *grid; + link_t *l; + prvm_edict_t *ent; + int igrid[3], igridmins[3], igridmaxs[3]; + + // FIXME: if areagrid_marknumber wraps, all entities need their + // ent->priv.server->areagridmarknumber reset + world->areagrid_stats_calls++; + world->areagrid_marknumber++; + igridmins[0] = (int) floor((mins[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]); + igridmins[1] = (int) floor((mins[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]); + //igridmins[2] = (int) ((mins[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]); + igridmaxs[0] = (int) floor((maxs[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]) + 1; + igridmaxs[1] = (int) floor((maxs[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]) + 1; + //igridmaxs[2] = (int) ((maxs[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]) + 1; + igridmins[0] = max(0, igridmins[0]); + igridmins[1] = max(0, igridmins[1]); + //igridmins[2] = max(0, igridmins[2]); + igridmaxs[0] = min(AREA_GRID, igridmaxs[0]); + igridmaxs[1] = min(AREA_GRID, igridmaxs[1]); + //igridmaxs[2] = min(AREA_GRID, igridmaxs[2]); + + numlist = 0; + // add entities not linked into areagrid because they are too big or + // outside the grid bounds + if (world->areagrid_outside.next != &world->areagrid_outside) + { + grid = &world->areagrid_outside; + for (l = grid->next;l != grid;l = l->next) + { + ent = PRVM_EDICT_NUM(l->entitynumber); + if (ent->priv.server->areagridmarknumber != world->areagrid_marknumber) + { + ent->priv.server->areagridmarknumber = world->areagrid_marknumber; + if (!ent->priv.server->free && BoxesOverlap(mins, maxs, ent->priv.server->areamins, ent->priv.server->areamaxs)) + { + if (numlist < maxlist) + list[numlist] = ent; + numlist++; + } + world->areagrid_stats_entitychecks++; + } + } + } + // add grid linked entities + for (igrid[1] = igridmins[1];igrid[1] < igridmaxs[1];igrid[1]++) + { + grid = world->areagrid + igrid[1] * AREA_GRID + igridmins[0]; + for (igrid[0] = igridmins[0];igrid[0] < igridmaxs[0];igrid[0]++, grid++) + { + if (grid->next != grid) + { + for (l = grid->next;l != grid;l = l->next) + { + ent = PRVM_EDICT_NUM(l->entitynumber); + if (ent->priv.server->areagridmarknumber != world->areagrid_marknumber) + { + ent->priv.server->areagridmarknumber = world->areagrid_marknumber; + if (!ent->priv.server->free && BoxesOverlap(mins, maxs, ent->priv.server->areamins, ent->priv.server->areamaxs)) + { + if (numlist < maxlist) + list[numlist] = ent; + numlist++; + } + //Con_Printf("%d %f %f %f %f %f %f : %d : %f %f %f %f %f %f\n", BoxesOverlap(mins, maxs, ent->priv.server->areamins, ent->priv.server->areamaxs), ent->priv.server->areamins[0], ent->priv.server->areamins[1], ent->priv.server->areamins[2], ent->priv.server->areamaxs[0], ent->priv.server->areamaxs[1], ent->priv.server->areamaxs[2], PRVM_NUM_FOR_EDICT(ent), mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); + } + world->areagrid_stats_entitychecks++; + } + } + } + } + return numlist; +} + +void World_LinkEdict_AreaGrid(world_t *world, prvm_edict_t *ent) +{ + link_t *grid; + int igrid[3], igridmins[3], igridmaxs[3], gridnum, entitynumber = PRVM_NUM_FOR_EDICT(ent); + + if (entitynumber <= 0 || entitynumber >= prog->max_edicts || PRVM_EDICT_NUM(entitynumber) != ent) + { + Con_Printf ("World_LinkEdict_AreaGrid: invalid edict %p (edicts is %p, edict compared to prog->edicts is %i)\n", (void *)ent, (void *)prog->edicts, entitynumber); + return; + } + + igridmins[0] = (int) floor((ent->priv.server->areamins[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]); + igridmins[1] = (int) floor((ent->priv.server->areamins[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]); + //igridmins[2] = (int) floor((ent->priv.server->areamins[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]); + igridmaxs[0] = (int) floor((ent->priv.server->areamaxs[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]) + 1; + igridmaxs[1] = (int) floor((ent->priv.server->areamaxs[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]) + 1; + //igridmaxs[2] = (int) floor((ent->priv.server->areamaxs[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]) + 1; + if (igridmins[0] < 0 || igridmaxs[0] > AREA_GRID || igridmins[1] < 0 || igridmaxs[1] > AREA_GRID || ((igridmaxs[0] - igridmins[0]) * (igridmaxs[1] - igridmins[1])) > ENTITYGRIDAREAS) + { + // wow, something outside the grid, store it as such + World_InsertLinkBefore (&ent->priv.server->areagrid[0], &world->areagrid_outside, entitynumber); + return; + } + + gridnum = 0; + for (igrid[1] = igridmins[1];igrid[1] < igridmaxs[1];igrid[1]++) + { + grid = world->areagrid + igrid[1] * AREA_GRID + igridmins[0]; + for (igrid[0] = igridmins[0];igrid[0] < igridmaxs[0];igrid[0]++, grid++, gridnum++) + World_InsertLinkBefore (&ent->priv.server->areagrid[gridnum], grid, entitynumber); + } +} + +/* +=============== +World_LinkEdict + +=============== +*/ +void World_LinkEdict(world_t *world, prvm_edict_t *ent, const vec3_t mins, const vec3_t maxs) +{ + // unlink from old position first + if (ent->priv.server->areagrid[0].prev) + World_UnlinkEdict(ent); + + // don't add the world + if (ent == prog->edicts) + return; + + // don't add free entities + if (ent->priv.server->free) + return; + + VectorCopy(mins, ent->priv.server->areamins); + VectorCopy(maxs, ent->priv.server->areamaxs); + World_LinkEdict_AreaGrid(world, ent); +} + + + + +//============================================================================ +// physics engine support +//============================================================================ + +#ifndef ODE_STATIC +# define ODE_DYNAMIC 1 +#endif + +#if defined(ODE_STATIC) || defined(ODE_DYNAMIC) +#define USEODE 1 +#endif + +// recent ODE trunk has dWorldStepFast1 removed +//#define ODE_USE_STEPFAST + +#ifdef USEODE +cvar_t physics_ode_quadtree_depth = {0, "physics_ode_quadtree_depth","5", "desired subdivision level of quadtree culling space"}; +cvar_t physics_ode_contactsurfacelayer = {0, "physics_ode_contactsurfacelayer","1", "allows objects to overlap this many units to reduce jitter"}; +cvar_t physics_ode_worldstep = {0, "physics_ode_worldstep","2", "step function to use, 0 - dWorldStep, 1 - dWorldStepFast1, 2 - dWorldQuickStep"}; +cvar_t physics_ode_worldstep_iterations = {0, "physics_ode_worldstep_iterations", "20", "parameter to dWorldQuickStep and dWorldStepFast1"}; +cvar_t physics_ode_contact_mu = {0, "physics_ode_contact_mu", "1", "contact solver mu parameter - friction pyramid approximation 1 (see ODE User Guide)"}; +cvar_t physics_ode_contact_erp = {0, "physics_ode_contact_erp", "0.96", "contact solver erp parameter - Error Restitution Percent (see ODE User Guide)"}; +cvar_t physics_ode_contact_cfm = {0, "physics_ode_contact_cfm", "0", "contact solver cfm parameter - Constraint Force Mixing (see ODE User Guide)"}; +cvar_t physics_ode_world_erp = {0, "physics_ode_world_erp", "-1", "world solver erp parameter - Error Restitution Percent (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_cfm = {0, "physics_ode_world_cfm", "-1", "world solver cfm parameter - Constraint Force Mixing (see ODE User Guide); not touched when -1"}; +cvar_t physics_ode_world_damping = {0, "physics_ode_world_damping", "1", "enabled damping scale (see ODE User Guide), this scales all damping values, be aware that behavior depends of step type"}; +cvar_t physics_ode_world_damping_linear = {0, "physics_ode_world_damping_linear", "0.005", "world linear damping scale (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_damping_linear_threshold = {0, "physics_ode_world_damping_linear_threshold", "0.01", "world linear damping threshold (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_damping_angular = {0, "physics_ode_world_damping_angular", "0.005", "world angular damping scale (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_damping_angular_threshold = {0, "physics_ode_world_damping_angular_threshold", "0.01", "world angular damping threshold (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_iterationsperframe = {0, "physics_ode_iterationsperframe", "1", "divisor for time step, runs multiple physics steps per frame"}; +cvar_t physics_ode_constantstep = {0, "physics_ode_constantstep", "1", "use constant step (sys_ticrate value) instead of variable step which tends to increase stability"}; +cvar_t physics_ode_autodisable = {0, "physics_ode_autodisable", "1", "automatic disabling of objects which dont move for long period of time, makes object stacking a lot faster"}; +cvar_t physics_ode_autodisable_steps = {0, "physics_ode_autodisable_steps", "10", "how many steps object should be dormant to be autodisabled"}; +cvar_t physics_ode_autodisable_time = {0, "physics_ode_autodisable_time", "0", "how many seconds object should be dormant to be autodisabled"}; +cvar_t physics_ode_autodisable_threshold_linear = {0, "physics_ode_autodisable_threshold_linear", "0.2", "body will be disabled if it's linear move below this value"}; +cvar_t physics_ode_autodisable_threshold_angular = {0, "physics_ode_autodisable_threshold_angular", "0.3", "body will be disabled if it's angular move below this value"}; +cvar_t physics_ode_autodisable_threshold_samples = {0, "physics_ode_autodisable_threshold_samples", "5", "average threshold with this number of samples"}; +cvar_t physics_ode_movelimit = {0, "physics_ode_movelimit", "0.5", "clamp velocity if a single move would exceed this percentage of object thickness, to prevent flying through walls, be aware that behavior depends of step type"}; +cvar_t physics_ode_spinlimit = {0, "physics_ode_spinlimit", "10000", "reset spin velocity if it gets too large"}; +cvar_t physics_ode_trick_fixnan = {0, "physics_ode_trick_fixnan", "1", "engine trick that checks and fixes NaN velocity/origin/angles on objects, a value of 2 makes console prints on each fix"}; +cvar_t physics_ode_printstats = {0, "physics_ode_printstats", "0", "print ODE stats each frame"}; +cvar_t physics_ode = {0, "physics_ode", "0", "run ODE physics (VERY experimental and potentially buggy)"}; + +// LordHavoc: this large chunk of definitions comes from the ODE library +// include files. + +#ifdef ODE_STATIC +#include "ode/ode.h" +#else +#ifdef WINAPI +// ODE does not use WINAPI +#define ODE_API +#else +#define ODE_API +#endif + +// note: dynamic builds of ODE tend to be double precision, this is not used +// for static builds +typedef double dReal; + +typedef dReal dVector3[4]; +typedef dReal dVector4[4]; +typedef dReal dMatrix3[4*3]; +typedef dReal dMatrix4[4*4]; +typedef dReal dMatrix6[8*6]; +typedef dReal dQuaternion[4]; + +struct dxWorld; /* dynamics world */ +struct dxSpace; /* collision space */ +struct dxBody; /* rigid body (dynamics object) */ +struct dxGeom; /* geometry (collision object) */ +struct dxJoint; +struct dxJointNode; +struct dxJointGroup; +struct dxTriMeshData; + +#define dInfinity 3.402823466e+38f + +typedef struct dxWorld *dWorldID; +typedef struct dxSpace *dSpaceID; +typedef struct dxBody *dBodyID; +typedef struct dxGeom *dGeomID; +typedef struct dxJoint *dJointID; +typedef struct dxJointGroup *dJointGroupID; +typedef struct dxTriMeshData *dTriMeshDataID; + +typedef struct dJointFeedback +{ + dVector3 f1; /* force applied to body 1 */ + dVector3 t1; /* torque applied to body 1 */ + dVector3 f2; /* force applied to body 2 */ + dVector3 t2; /* torque applied to body 2 */ +} +dJointFeedback; + +typedef enum dJointType +{ + dJointTypeNone = 0, + dJointTypeBall, + dJointTypeHinge, + dJointTypeSlider, + dJointTypeContact, + dJointTypeUniversal, + dJointTypeHinge2, + dJointTypeFixed, + dJointTypeNull, + dJointTypeAMotor, + dJointTypeLMotor, + dJointTypePlane2D, + dJointTypePR, + dJointTypePU, + dJointTypePiston +} +dJointType; + +#define D_ALL_PARAM_NAMES(start) \ + /* parameters for limits and motors */ \ + dParamLoStop = start, \ + dParamHiStop, \ + dParamVel, \ + dParamFMax, \ + dParamFudgeFactor, \ + dParamBounce, \ + dParamCFM, \ + dParamStopERP, \ + dParamStopCFM, \ + /* parameters for suspension */ \ + dParamSuspensionERP, \ + dParamSuspensionCFM, \ + dParamERP, \ + +#define D_ALL_PARAM_NAMES_X(start,x) \ + /* parameters for limits and motors */ \ + dParamLoStop ## x = start, \ + dParamHiStop ## x, \ + dParamVel ## x, \ + dParamFMax ## x, \ + dParamFudgeFactor ## x, \ + dParamBounce ## x, \ + dParamCFM ## x, \ + dParamStopERP ## x, \ + dParamStopCFM ## x, \ + /* parameters for suspension */ \ + dParamSuspensionERP ## x, \ + dParamSuspensionCFM ## x, \ + dParamERP ## x, + +enum { + D_ALL_PARAM_NAMES(0) + D_ALL_PARAM_NAMES_X(0x100,2) + D_ALL_PARAM_NAMES_X(0x200,3) + + /* add a multiple of this constant to the basic parameter numbers to get + * the parameters for the second, third etc axes. + */ + dParamGroup=0x100 +}; + +typedef struct dMass +{ + dReal mass; + dVector3 c; + dMatrix3 I; +} +dMass; + +enum +{ + dContactMu2 = 0x001, + dContactFDir1 = 0x002, + dContactBounce = 0x004, + dContactSoftERP = 0x008, + dContactSoftCFM = 0x010, + dContactMotion1 = 0x020, + dContactMotion2 = 0x040, + dContactMotionN = 0x080, + dContactSlip1 = 0x100, + dContactSlip2 = 0x200, + + dContactApprox0 = 0x0000, + dContactApprox1_1 = 0x1000, + dContactApprox1_2 = 0x2000, + dContactApprox1 = 0x3000 +}; + +typedef struct dSurfaceParameters +{ + /* must always be defined */ + int mode; + dReal mu; + + /* only defined if the corresponding flag is set in mode */ + dReal mu2; + dReal bounce; + dReal bounce_vel; + dReal soft_erp; + dReal soft_cfm; + dReal motion1,motion2,motionN; + dReal slip1,slip2; +} dSurfaceParameters; + +typedef struct dContactGeom +{ + dVector3 pos; ///< contact position + dVector3 normal; ///< normal vector + dReal depth; ///< penetration depth + dGeomID g1,g2; ///< the colliding geoms + int side1,side2; ///< (to be documented) +} +dContactGeom; + +typedef struct dContact +{ + dSurfaceParameters surface; + dContactGeom geom; + dVector3 fdir1; +} +dContact; + +typedef void dNearCallback (void *data, dGeomID o1, dGeomID o2); + +// SAP +// Order XZY or ZXY usually works best, if your Y is up. +#define dSAP_AXES_XYZ ((0)|(1<<2)|(2<<4)) +#define dSAP_AXES_XZY ((0)|(2<<2)|(1<<4)) +#define dSAP_AXES_YXZ ((1)|(0<<2)|(2<<4)) +#define dSAP_AXES_YZX ((1)|(2<<2)|(0<<4)) +#define dSAP_AXES_ZXY ((2)|(0<<2)|(1<<4)) +#define dSAP_AXES_ZYX ((2)|(1<<2)|(0<<4)) + +//const char* (ODE_API *dGetConfiguration)(void); +int (ODE_API *dCheckConfiguration)( const char* token ); +int (ODE_API *dInitODE)(void); +//int (ODE_API *dInitODE2)(unsigned int uiInitFlags); +//int (ODE_API *dAllocateODEDataForThread)(unsigned int uiAllocateFlags); +//void (ODE_API *dCleanupODEAllDataForThread)(void); +void (ODE_API *dCloseODE)(void); + +//int (ODE_API *dMassCheck)(const dMass *m); +//void (ODE_API *dMassSetZero)(dMass *); +//void (ODE_API *dMassSetParameters)(dMass *, dReal themass, dReal cgx, dReal cgy, dReal cgz, dReal I11, dReal I22, dReal I33, dReal I12, dReal I13, dReal I23); +//void (ODE_API *dMassSetSphere)(dMass *, dReal density, dReal radius); +void (ODE_API *dMassSetSphereTotal)(dMass *, dReal total_mass, dReal radius); +//void (ODE_API *dMassSetCapsule)(dMass *, dReal density, int direction, dReal radius, dReal length); +void (ODE_API *dMassSetCapsuleTotal)(dMass *, dReal total_mass, int direction, dReal radius, dReal length); +//void (ODE_API *dMassSetCylinder)(dMass *, dReal density, int direction, dReal radius, dReal length); +//void (ODE_API *dMassSetCylinderTotal)(dMass *, dReal total_mass, int direction, dReal radius, dReal length); +//void (ODE_API *dMassSetBox)(dMass *, dReal density, dReal lx, dReal ly, dReal lz); +void (ODE_API *dMassSetBoxTotal)(dMass *, dReal total_mass, dReal lx, dReal ly, dReal lz); +//void (ODE_API *dMassSetTrimesh)(dMass *, dReal density, dGeomID g); +//void (ODE_API *dMassSetTrimeshTotal)(dMass *m, dReal total_mass, dGeomID g); +//void (ODE_API *dMassAdjust)(dMass *, dReal newmass); +//void (ODE_API *dMassTranslate)(dMass *, dReal x, dReal y, dReal z); +//void (ODE_API *dMassRotate)(dMass *, const dMatrix3 R); +//void (ODE_API *dMassAdd)(dMass *a, const dMass *b); +// +dWorldID (ODE_API *dWorldCreate)(void); +void (ODE_API *dWorldDestroy)(dWorldID world); +void (ODE_API *dWorldSetGravity)(dWorldID, dReal x, dReal y, dReal z); +void (ODE_API *dWorldGetGravity)(dWorldID, dVector3 gravity); +void (ODE_API *dWorldSetERP)(dWorldID, dReal erp); +//dReal (ODE_API *dWorldGetERP)(dWorldID); +void (ODE_API *dWorldSetCFM)(dWorldID, dReal cfm); +//dReal (ODE_API *dWorldGetCFM)(dWorldID); +void (ODE_API *dWorldStep)(dWorldID, dReal stepsize); +//void (ODE_API *dWorldImpulseToForce)(dWorldID, dReal stepsize, dReal ix, dReal iy, dReal iz, dVector3 force); +void (ODE_API *dWorldQuickStep)(dWorldID w, dReal stepsize); +void (ODE_API *dWorldSetQuickStepNumIterations)(dWorldID, int num); +//int (ODE_API *dWorldGetQuickStepNumIterations)(dWorldID); +//void (ODE_API *dWorldSetQuickStepW)(dWorldID, dReal over_relaxation); +//dReal (ODE_API *dWorldGetQuickStepW)(dWorldID); +//void (ODE_API *dWorldSetContactMaxCorrectingVel)(dWorldID, dReal vel); +//dReal (ODE_API *dWorldGetContactMaxCorrectingVel)(dWorldID); +void (ODE_API *dWorldSetContactSurfaceLayer)(dWorldID, dReal depth); +//dReal (ODE_API *dWorldGetContactSurfaceLayer)(dWorldID); +#ifdef ODE_USE_STEPFAST +void (ODE_API *dWorldStepFast1)(dWorldID, dReal stepsize, int maxiterations); +#endif +//void (ODE_API *dWorldSetAutoEnableDepthSF1)(dWorldID, int autoEnableDepth); +//int (ODE_API *dWorldGetAutoEnableDepthSF1)(dWorldID); +//dReal (ODE_API *dWorldGetAutoDisableLinearThreshold)(dWorldID); +void (ODE_API *dWorldSetAutoDisableLinearThreshold)(dWorldID, dReal linear_threshold); +//dReal (ODE_API *dWorldGetAutoDisableAngularThreshold)(dWorldID); +void (ODE_API *dWorldSetAutoDisableAngularThreshold)(dWorldID, dReal angular_threshold); +//dReal (ODE_API *dWorldGetAutoDisableLinearAverageThreshold)(dWorldID); +//void (ODE_API *dWorldSetAutoDisableLinearAverageThreshold)(dWorldID, dReal linear_average_threshold); +//dReal (ODE_API *dWorldGetAutoDisableAngularAverageThreshold)(dWorldID); +//void (ODE_API *dWorldSetAutoDisableAngularAverageThreshold)(dWorldID, dReal angular_average_threshold); +//int (ODE_API *dWorldGetAutoDisableAverageSamplesCount)(dWorldID); +void (ODE_API *dWorldSetAutoDisableAverageSamplesCount)(dWorldID, unsigned int average_samples_count ); +//int (ODE_API *dWorldGetAutoDisableSteps)(dWorldID); +void (ODE_API *dWorldSetAutoDisableSteps)(dWorldID, int steps); +//dReal (ODE_API *dWorldGetAutoDisableTime)(dWorldID); +void (ODE_API *dWorldSetAutoDisableTime)(dWorldID, dReal time); +//int (ODE_API *dWorldGetAutoDisableFlag)(dWorldID); +void (ODE_API *dWorldSetAutoDisableFlag)(dWorldID, int do_auto_disable); +//dReal (ODE_API *dWorldGetLinearDampingThreshold)(dWorldID w); +void (ODE_API *dWorldSetLinearDampingThreshold)(dWorldID w, dReal threshold); +//dReal (ODE_API *dWorldGetAngularDampingThreshold)(dWorldID w); +void (ODE_API *dWorldSetAngularDampingThreshold)(dWorldID w, dReal threshold); +//dReal (ODE_API *dWorldGetLinearDamping)(dWorldID w); +void (ODE_API *dWorldSetLinearDamping)(dWorldID w, dReal scale); +//dReal (ODE_API *dWorldGetAngularDamping)(dWorldID w); +void (ODE_API *dWorldSetAngularDamping)(dWorldID w, dReal scale); +//void (ODE_API *dWorldSetDamping)(dWorldID w, dReal linear_scale, dReal angular_scale); +//dReal (ODE_API *dWorldGetMaxAngularSpeed)(dWorldID w); +//void (ODE_API *dWorldSetMaxAngularSpeed)(dWorldID w, dReal max_speed); +//dReal (ODE_API *dBodyGetAutoDisableLinearThreshold)(dBodyID); +//void (ODE_API *dBodySetAutoDisableLinearThreshold)(dBodyID, dReal linear_average_threshold); +//dReal (ODE_API *dBodyGetAutoDisableAngularThreshold)(dBodyID); +//void (ODE_API *dBodySetAutoDisableAngularThreshold)(dBodyID, dReal angular_average_threshold); +//int (ODE_API *dBodyGetAutoDisableAverageSamplesCount)(dBodyID); +//void (ODE_API *dBodySetAutoDisableAverageSamplesCount)(dBodyID, unsigned int average_samples_count); +//int (ODE_API *dBodyGetAutoDisableSteps)(dBodyID); +//void (ODE_API *dBodySetAutoDisableSteps)(dBodyID, int steps); +//dReal (ODE_API *dBodyGetAutoDisableTime)(dBodyID); +//void (ODE_API *dBodySetAutoDisableTime)(dBodyID, dReal time); +//int (ODE_API *dBodyGetAutoDisableFlag)(dBodyID); +//void (ODE_API *dBodySetAutoDisableFlag)(dBodyID, int do_auto_disable); +//void (ODE_API *dBodySetAutoDisableDefaults)(dBodyID); +//dWorldID (ODE_API *dBodyGetWorld)(dBodyID); +dBodyID (ODE_API *dBodyCreate)(dWorldID); +void (ODE_API *dBodyDestroy)(dBodyID); +void (ODE_API *dBodySetData)(dBodyID, void *data); +void * (ODE_API *dBodyGetData)(dBodyID); +void (ODE_API *dBodySetPosition)(dBodyID, dReal x, dReal y, dReal z); +void (ODE_API *dBodySetRotation)(dBodyID, const dMatrix3 R); +//void (ODE_API *dBodySetQuaternion)(dBodyID, const dQuaternion q); +void (ODE_API *dBodySetLinearVel)(dBodyID, dReal x, dReal y, dReal z); +void (ODE_API *dBodySetAngularVel)(dBodyID, dReal x, dReal y, dReal z); +const dReal * (ODE_API *dBodyGetPosition)(dBodyID); +//void (ODE_API *dBodyCopyPosition)(dBodyID body, dVector3 pos); +const dReal * (ODE_API *dBodyGetRotation)(dBodyID); +//void (ODE_API *dBodyCopyRotation)(dBodyID, dMatrix3 R); +//const dReal * (ODE_API *dBodyGetQuaternion)(dBodyID); +//void (ODE_API *dBodyCopyQuaternion)(dBodyID body, dQuaternion quat); +const dReal * (ODE_API *dBodyGetLinearVel)(dBodyID); +const dReal * (ODE_API *dBodyGetAngularVel)(dBodyID); +void (ODE_API *dBodySetMass)(dBodyID, const dMass *mass); +//void (ODE_API *dBodyGetMass)(dBodyID, dMass *mass); +//void (ODE_API *dBodyAddForce)(dBodyID, dReal fx, dReal fy, dReal fz); +//void (ODE_API *dBodyAddTorque)(dBodyID, dReal fx, dReal fy, dReal fz); +//void (ODE_API *dBodyAddRelForce)(dBodyID, dReal fx, dReal fy, dReal fz); +void (ODE_API *dBodyAddRelTorque)(dBodyID, dReal fx, dReal fy, dReal fz); +//void (ODE_API *dBodyAddForceAtPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); +void (ODE_API *dBodyAddForceAtRelPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); +//void (ODE_API *dBodyAddRelForceAtPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); +//void (ODE_API *dBodyAddRelForceAtRelPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); +//const dReal * (ODE_API *dBodyGetForce)(dBodyID); +//const dReal * (ODE_API *dBodyGetTorque)(dBodyID); +//void (ODE_API *dBodySetForce)(dBodyID b, dReal x, dReal y, dReal z); +//void (ODE_API *dBodySetTorque)(dBodyID b, dReal x, dReal y, dReal z); +//void (ODE_API *dBodyGetRelPointPos)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyGetRelPointVel)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyGetPointVel)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyGetPosRelPoint)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyVectorToWorld)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyVectorFromWorld)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodySetFiniteRotationMode)(dBodyID, int mode); +//void (ODE_API *dBodySetFiniteRotationAxis)(dBodyID, dReal x, dReal y, dReal z); +//int (ODE_API *dBodyGetFiniteRotationMode)(dBodyID); +//void (ODE_API *dBodyGetFiniteRotationAxis)(dBodyID, dVector3 result); +int (ODE_API *dBodyGetNumJoints)(dBodyID b); +dJointID (ODE_API *dBodyGetJoint)(dBodyID, int index); +//void (ODE_API *dBodySetDynamic)(dBodyID); +//void (ODE_API *dBodySetKinematic)(dBodyID); +//int (ODE_API *dBodyIsKinematic)(dBodyID); +void (ODE_API *dBodyEnable)(dBodyID); +void (ODE_API *dBodyDisable)(dBodyID); +int (ODE_API *dBodyIsEnabled)(dBodyID); +void (ODE_API *dBodySetGravityMode)(dBodyID b, int mode); +int (ODE_API *dBodyGetGravityMode)(dBodyID b); +//void (*dBodySetMovedCallback)(dBodyID b, void(ODE_API *callback)(dBodyID)); +//dGeomID (ODE_API *dBodyGetFirstGeom)(dBodyID b); +//dGeomID (ODE_API *dBodyGetNextGeom)(dGeomID g); +//void (ODE_API *dBodySetDampingDefaults)(dBodyID b); +//dReal (ODE_API *dBodyGetLinearDamping)(dBodyID b); +//void (ODE_API *dBodySetLinearDamping)(dBodyID b, dReal scale); +//dReal (ODE_API *dBodyGetAngularDamping)(dBodyID b); +//void (ODE_API *dBodySetAngularDamping)(dBodyID b, dReal scale); +//void (ODE_API *dBodySetDamping)(dBodyID b, dReal linear_scale, dReal angular_scale); +//dReal (ODE_API *dBodyGetLinearDampingThreshold)(dBodyID b); +//void (ODE_API *dBodySetLinearDampingThreshold)(dBodyID b, dReal threshold); +//dReal (ODE_API *dBodyGetAngularDampingThreshold)(dBodyID b); +//void (ODE_API *dBodySetAngularDampingThreshold)(dBodyID b, dReal threshold); +//dReal (ODE_API *dBodyGetMaxAngularSpeed)(dBodyID b); +//void (ODE_API *dBodySetMaxAngularSpeed)(dBodyID b, dReal max_speed); +//int (ODE_API *dBodyGetGyroscopicMode)(dBodyID b); +//void (ODE_API *dBodySetGyroscopicMode)(dBodyID b, int enabled); +dJointID (ODE_API *dJointCreateBall)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateHinge)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateSlider)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateContact)(dWorldID, dJointGroupID, const dContact *); +dJointID (ODE_API *dJointCreateHinge2)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateUniversal)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreatePR)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreatePU)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreatePiston)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateFixed)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreateNull)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreateAMotor)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreateLMotor)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreatePlane2D)(dWorldID, dJointGroupID); +void (ODE_API *dJointDestroy)(dJointID); +dJointGroupID (ODE_API *dJointGroupCreate)(int max_size); +void (ODE_API *dJointGroupDestroy)(dJointGroupID); +void (ODE_API *dJointGroupEmpty)(dJointGroupID); +//int (ODE_API *dJointGetNumBodies)(dJointID); +void (ODE_API *dJointAttach)(dJointID, dBodyID body1, dBodyID body2); +//void (ODE_API *dJointEnable)(dJointID); +//void (ODE_API *dJointDisable)(dJointID); +//int (ODE_API *dJointIsEnabled)(dJointID); +void (ODE_API *dJointSetData)(dJointID, void *data); +void * (ODE_API *dJointGetData)(dJointID); +//dJointType (ODE_API *dJointGetType)(dJointID); +dBodyID (ODE_API *dJointGetBody)(dJointID, int index); +//void (ODE_API *dJointSetFeedback)(dJointID, dJointFeedback *); +//dJointFeedback *(ODE_API *dJointGetFeedback)(dJointID); +void (ODE_API *dJointSetBallAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetBallAnchor2)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetBallParam)(dJointID, int parameter, dReal value); +void (ODE_API *dJointSetHingeAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetHingeAnchorDelta)(dJointID, dReal x, dReal y, dReal z, dReal ax, dReal ay, dReal az); +void (ODE_API *dJointSetHingeAxis)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetHingeAxisOffset)(dJointID j, dReal x, dReal y, dReal z, dReal angle); +void (ODE_API *dJointSetHingeParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddHingeTorque)(dJointID joint, dReal torque); +void (ODE_API *dJointSetSliderAxis)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetSliderAxisDelta)(dJointID, dReal x, dReal y, dReal z, dReal ax, dReal ay, dReal az); +void (ODE_API *dJointSetSliderParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddSliderForce)(dJointID joint, dReal force); +void (ODE_API *dJointSetHinge2Anchor)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetHinge2Axis1)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetHinge2Axis2)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetHinge2Param)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddHinge2Torques)(dJointID joint, dReal torque1, dReal torque2); +void (ODE_API *dJointSetUniversalAnchor)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetUniversalAxis1)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetUniversalAxis1Offset)(dJointID, dReal x, dReal y, dReal z, dReal offset1, dReal offset2); +void (ODE_API *dJointSetUniversalAxis2)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetUniversalAxis2Offset)(dJointID, dReal x, dReal y, dReal z, dReal offset1, dReal offset2); +void (ODE_API *dJointSetUniversalParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddUniversalTorques)(dJointID joint, dReal torque1, dReal torque2); +//void (ODE_API *dJointSetPRAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPRAxis1)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPRAxis2)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPRParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddPRTorque)(dJointID j, dReal torque); +//void (ODE_API *dJointSetPUAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUAnchorOffset)(dJointID, dReal x, dReal y, dReal z, dReal dx, dReal dy, dReal dz); +//void (ODE_API *dJointSetPUAxis1)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUAxis2)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUAxis3)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUAxisP)(dJointID id, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddPUTorque)(dJointID j, dReal torque); +//void (ODE_API *dJointSetPistonAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPistonAnchorOffset)(dJointID j, dReal x, dReal y, dReal z, dReal dx, dReal dy, dReal dz); +//void (ODE_API *dJointSetPistonParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddPistonForce)(dJointID joint, dReal force); +//void (ODE_API *dJointSetFixed)(dJointID); +//void (ODE_API *dJointSetFixedParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetAMotorNumAxes)(dJointID, int num); +//void (ODE_API *dJointSetAMotorAxis)(dJointID, int anum, int rel, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetAMotorAngle)(dJointID, int anum, dReal angle); +//void (ODE_API *dJointSetAMotorParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetAMotorMode)(dJointID, int mode); +//void (ODE_API *dJointAddAMotorTorques)(dJointID, dReal torque1, dReal torque2, dReal torque3); +//void (ODE_API *dJointSetLMotorNumAxes)(dJointID, int num); +//void (ODE_API *dJointSetLMotorAxis)(dJointID, int anum, int rel, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetLMotorParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetPlane2DXParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetPlane2DYParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetPlane2DAngleParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointGetBallAnchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetBallAnchor2)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetBallParam)(dJointID, int parameter); +//void (ODE_API *dJointGetHingeAnchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHingeAnchor2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHingeAxis)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetHingeParam)(dJointID, int parameter); +//dReal (ODE_API *dJointGetHingeAngle)(dJointID); +//dReal (ODE_API *dJointGetHingeAngleRate)(dJointID); +//dReal (ODE_API *dJointGetSliderPosition)(dJointID); +//dReal (ODE_API *dJointGetSliderPositionRate)(dJointID); +//void (ODE_API *dJointGetSliderAxis)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetSliderParam)(dJointID, int parameter); +//void (ODE_API *dJointGetHinge2Anchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHinge2Anchor2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHinge2Axis1)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHinge2Axis2)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetHinge2Param)(dJointID, int parameter); +//dReal (ODE_API *dJointGetHinge2Angle1)(dJointID); +//dReal (ODE_API *dJointGetHinge2Angle1Rate)(dJointID); +//dReal (ODE_API *dJointGetHinge2Angle2Rate)(dJointID); +//void (ODE_API *dJointGetUniversalAnchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetUniversalAnchor2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetUniversalAxis1)(dJointID, dVector3 result); +//void (ODE_API *dJointGetUniversalAxis2)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetUniversalParam)(dJointID, int parameter); +//void (ODE_API *dJointGetUniversalAngles)(dJointID, dReal *angle1, dReal *angle2); +//dReal (ODE_API *dJointGetUniversalAngle1)(dJointID); +//dReal (ODE_API *dJointGetUniversalAngle2)(dJointID); +//dReal (ODE_API *dJointGetUniversalAngle1Rate)(dJointID); +//dReal (ODE_API *dJointGetUniversalAngle2Rate)(dJointID); +//void (ODE_API *dJointGetPRAnchor)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetPRPosition)(dJointID); +//dReal (ODE_API *dJointGetPRPositionRate)(dJointID); +//dReal (ODE_API *dJointGetPRAngle)(dJointID); +//dReal (ODE_API *dJointGetPRAngleRate)(dJointID); +//void (ODE_API *dJointGetPRAxis1)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPRAxis2)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetPRParam)(dJointID, int parameter); +//void (ODE_API *dJointGetPUAnchor)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetPUPosition)(dJointID); +//dReal (ODE_API *dJointGetPUPositionRate)(dJointID); +//void (ODE_API *dJointGetPUAxis1)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPUAxis2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPUAxis3)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPUAxisP)(dJointID id, dVector3 result); +//void (ODE_API *dJointGetPUAngles)(dJointID, dReal *angle1, dReal *angle2); +//dReal (ODE_API *dJointGetPUAngle1)(dJointID); +//dReal (ODE_API *dJointGetPUAngle1Rate)(dJointID); +//dReal (ODE_API *dJointGetPUAngle2)(dJointID); +//dReal (ODE_API *dJointGetPUAngle2Rate)(dJointID); +//dReal (ODE_API *dJointGetPUParam)(dJointID, int parameter); +//dReal (ODE_API *dJointGetPistonPosition)(dJointID); +//dReal (ODE_API *dJointGetPistonPositionRate)(dJointID); +//dReal (ODE_API *dJointGetPistonAngle)(dJointID); +//dReal (ODE_API *dJointGetPistonAngleRate)(dJointID); +//void (ODE_API *dJointGetPistonAnchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPistonAnchor2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPistonAxis)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetPistonParam)(dJointID, int parameter); +//int (ODE_API *dJointGetAMotorNumAxes)(dJointID); +//void (ODE_API *dJointGetAMotorAxis)(dJointID, int anum, dVector3 result); +//int (ODE_API *dJointGetAMotorAxisRel)(dJointID, int anum); +//dReal (ODE_API *dJointGetAMotorAngle)(dJointID, int anum); +//dReal (ODE_API *dJointGetAMotorAngleRate)(dJointID, int anum); +//dReal (ODE_API *dJointGetAMotorParam)(dJointID, int parameter); +//int (ODE_API *dJointGetAMotorMode)(dJointID); +//int (ODE_API *dJointGetLMotorNumAxes)(dJointID); +//void (ODE_API *dJointGetLMotorAxis)(dJointID, int anum, dVector3 result); +//dReal (ODE_API *dJointGetLMotorParam)(dJointID, int parameter); +//dReal (ODE_API *dJointGetFixedParam)(dJointID, int parameter); +//dJointID (ODE_API *dConnectingJoint)(dBodyID, dBodyID); +//int (ODE_API *dConnectingJointList)(dBodyID, dBodyID, dJointID*); +int (ODE_API *dAreConnected)(dBodyID, dBodyID); +int (ODE_API *dAreConnectedExcluding)(dBodyID body1, dBodyID body2, int joint_type); +// +dSpaceID (ODE_API *dSimpleSpaceCreate)(dSpaceID space); +dSpaceID (ODE_API *dHashSpaceCreate)(dSpaceID space); +dSpaceID (ODE_API *dQuadTreeSpaceCreate)(dSpaceID space, const dVector3 Center, const dVector3 Extents, int Depth); +//dSpaceID (ODE_API *dSweepAndPruneSpaceCreate)( dSpaceID space, int axisorder ); +void (ODE_API *dSpaceDestroy)(dSpaceID); +//void (ODE_API *dHashSpaceSetLevels)(dSpaceID space, int minlevel, int maxlevel); +//void (ODE_API *dHashSpaceGetLevels)(dSpaceID space, int *minlevel, int *maxlevel); +//void (ODE_API *dSpaceSetCleanup)(dSpaceID space, int mode); +//int (ODE_API *dSpaceGetCleanup)(dSpaceID space); +//void (ODE_API *dSpaceSetSublevel)(dSpaceID space, int sublevel); +//int (ODE_API *dSpaceGetSublevel)(dSpaceID space); +//void (ODE_API *dSpaceSetManualCleanup)(dSpaceID space, int mode); +//int (ODE_API *dSpaceGetManualCleanup)(dSpaceID space); +//void (ODE_API *dSpaceAdd)(dSpaceID, dGeomID); +//void (ODE_API *dSpaceRemove)(dSpaceID, dGeomID); +//int (ODE_API *dSpaceQuery)(dSpaceID, dGeomID); +//void (ODE_API *dSpaceClean)(dSpaceID); +//int (ODE_API *dSpaceGetNumGeoms)(dSpaceID); +//dGeomID (ODE_API *dSpaceGetGeom)(dSpaceID, int i); +//int (ODE_API *dSpaceGetClass)(dSpaceID space); +// +void (ODE_API *dGeomDestroy)(dGeomID geom); +void (ODE_API *dGeomSetData)(dGeomID geom, void* data); +void * (ODE_API *dGeomGetData)(dGeomID geom); +void (ODE_API *dGeomSetBody)(dGeomID geom, dBodyID body); +dBodyID (ODE_API *dGeomGetBody)(dGeomID geom); +void (ODE_API *dGeomSetPosition)(dGeomID geom, dReal x, dReal y, dReal z); +void (ODE_API *dGeomSetRotation)(dGeomID geom, const dMatrix3 R); +//void (ODE_API *dGeomSetQuaternion)(dGeomID geom, const dQuaternion Q); +//const dReal * (ODE_API *dGeomGetPosition)(dGeomID geom); +//void (ODE_API *dGeomCopyPosition)(dGeomID geom, dVector3 pos); +//const dReal * (ODE_API *dGeomGetRotation)(dGeomID geom); +//void (ODE_API *dGeomCopyRotation)(dGeomID geom, dMatrix3 R); +//void (ODE_API *dGeomGetQuaternion)(dGeomID geom, dQuaternion result); +//void (ODE_API *dGeomGetAABB)(dGeomID geom, dReal aabb[6]); +int (ODE_API *dGeomIsSpace)(dGeomID geom); +//dSpaceID (ODE_API *dGeomGetSpace)(dGeomID); +//int (ODE_API *dGeomGetClass)(dGeomID geom); +//void (ODE_API *dGeomSetCategoryBits)(dGeomID geom, unsigned long bits); +//void (ODE_API *dGeomSetCollideBits)(dGeomID geom, unsigned long bits); +//unsigned long (ODE_API *dGeomGetCategoryBits)(dGeomID); +//unsigned long (ODE_API *dGeomGetCollideBits)(dGeomID); +//void (ODE_API *dGeomEnable)(dGeomID geom); +//void (ODE_API *dGeomDisable)(dGeomID geom); +//int (ODE_API *dGeomIsEnabled)(dGeomID geom); +//void (ODE_API *dGeomSetOffsetPosition)(dGeomID geom, dReal x, dReal y, dReal z); +//void (ODE_API *dGeomSetOffsetRotation)(dGeomID geom, const dMatrix3 R); +//void (ODE_API *dGeomSetOffsetQuaternion)(dGeomID geom, const dQuaternion Q); +//void (ODE_API *dGeomSetOffsetWorldPosition)(dGeomID geom, dReal x, dReal y, dReal z); +//void (ODE_API *dGeomSetOffsetWorldRotation)(dGeomID geom, const dMatrix3 R); +//void (ODE_API *dGeomSetOffsetWorldQuaternion)(dGeomID geom, const dQuaternion); +//void (ODE_API *dGeomClearOffset)(dGeomID geom); +//int (ODE_API *dGeomIsOffset)(dGeomID geom); +//const dReal * (ODE_API *dGeomGetOffsetPosition)(dGeomID geom); +//void (ODE_API *dGeomCopyOffsetPosition)(dGeomID geom, dVector3 pos); +//const dReal * (ODE_API *dGeomGetOffsetRotation)(dGeomID geom); +//void (ODE_API *dGeomCopyOffsetRotation)(dGeomID geom, dMatrix3 R); +//void (ODE_API *dGeomGetOffsetQuaternion)(dGeomID geom, dQuaternion result); +int (ODE_API *dCollide)(dGeomID o1, dGeomID o2, int flags, dContactGeom *contact, int skip); +// +void (ODE_API *dSpaceCollide)(dSpaceID space, void *data, dNearCallback *callback); +void (ODE_API *dSpaceCollide2)(dGeomID space1, dGeomID space2, void *data, dNearCallback *callback); +// +dGeomID (ODE_API *dCreateSphere)(dSpaceID space, dReal radius); +//void (ODE_API *dGeomSphereSetRadius)(dGeomID sphere, dReal radius); +//dReal (ODE_API *dGeomSphereGetRadius)(dGeomID sphere); +//dReal (ODE_API *dGeomSpherePointDepth)(dGeomID sphere, dReal x, dReal y, dReal z); +// +//dGeomID (ODE_API *dCreateConvex)(dSpaceID space, dReal *_planes, unsigned int _planecount, dReal *_points, unsigned int _pointcount,unsigned int *_polygons); +//void (ODE_API *dGeomSetConvex)(dGeomID g, dReal *_planes, unsigned int _count, dReal *_points, unsigned int _pointcount,unsigned int *_polygons); +// +dGeomID (ODE_API *dCreateBox)(dSpaceID space, dReal lx, dReal ly, dReal lz); +//void (ODE_API *dGeomBoxSetLengths)(dGeomID box, dReal lx, dReal ly, dReal lz); +//void (ODE_API *dGeomBoxGetLengths)(dGeomID box, dVector3 result); +//dReal (ODE_API *dGeomBoxPointDepth)(dGeomID box, dReal x, dReal y, dReal z); +//dReal (ODE_API *dGeomBoxPointDepth)(dGeomID box, dReal x, dReal y, dReal z); +// +//dGeomID (ODE_API *dCreatePlane)(dSpaceID space, dReal a, dReal b, dReal c, dReal d); +//void (ODE_API *dGeomPlaneSetParams)(dGeomID plane, dReal a, dReal b, dReal c, dReal d); +//void (ODE_API *dGeomPlaneGetParams)(dGeomID plane, dVector4 result); +//dReal (ODE_API *dGeomPlanePointDepth)(dGeomID plane, dReal x, dReal y, dReal z); +// +dGeomID (ODE_API *dCreateCapsule)(dSpaceID space, dReal radius, dReal length); +//void (ODE_API *dGeomCapsuleSetParams)(dGeomID ccylinder, dReal radius, dReal length); +//void (ODE_API *dGeomCapsuleGetParams)(dGeomID ccylinder, dReal *radius, dReal *length); +//dReal (ODE_API *dGeomCapsulePointDepth)(dGeomID ccylinder, dReal x, dReal y, dReal z); +// +//dGeomID (ODE_API *dCreateCylinder)(dSpaceID space, dReal radius, dReal length); +//void (ODE_API *dGeomCylinderSetParams)(dGeomID cylinder, dReal radius, dReal length); +//void (ODE_API *dGeomCylinderGetParams)(dGeomID cylinder, dReal *radius, dReal *length); +// +//dGeomID (ODE_API *dCreateRay)(dSpaceID space, dReal length); +//void (ODE_API *dGeomRaySetLength)(dGeomID ray, dReal length); +//dReal (ODE_API *dGeomRayGetLength)(dGeomID ray); +//void (ODE_API *dGeomRaySet)(dGeomID ray, dReal px, dReal py, dReal pz, dReal dx, dReal dy, dReal dz); +//void (ODE_API *dGeomRayGet)(dGeomID ray, dVector3 start, dVector3 dir); +// +dGeomID (ODE_API *dCreateGeomTransform)(dSpaceID space); +void (ODE_API *dGeomTransformSetGeom)(dGeomID g, dGeomID obj); +//dGeomID (ODE_API *dGeomTransformGetGeom)(dGeomID g); +void (ODE_API *dGeomTransformSetCleanup)(dGeomID g, int mode); +//int (ODE_API *dGeomTransformGetCleanup)(dGeomID g); +//void (ODE_API *dGeomTransformSetInfo)(dGeomID g, int mode); +//int (ODE_API *dGeomTransformGetInfo)(dGeomID g); + +enum { TRIMESH_FACE_NORMALS }; +typedef int dTriCallback(dGeomID TriMesh, dGeomID RefObject, int TriangleIndex); +typedef void dTriArrayCallback(dGeomID TriMesh, dGeomID RefObject, const int* TriIndices, int TriCount); +typedef int dTriRayCallback(dGeomID TriMesh, dGeomID Ray, int TriangleIndex, dReal u, dReal v); +typedef int dTriTriMergeCallback(dGeomID TriMesh, int FirstTriangleIndex, int SecondTriangleIndex); + +dTriMeshDataID (ODE_API *dGeomTriMeshDataCreate)(void); +void (ODE_API *dGeomTriMeshDataDestroy)(dTriMeshDataID g); +//void (ODE_API *dGeomTriMeshDataSet)(dTriMeshDataID g, int data_id, void* in_data); +//void* (ODE_API *dGeomTriMeshDataGet)(dTriMeshDataID g, int data_id); +//void (*dGeomTriMeshSetLastTransform)( (ODE_API *dGeomID g, dMatrix4 last_trans ); +//dReal* (*dGeomTriMeshGetLastTransform)( (ODE_API *dGeomID g ); +void (ODE_API *dGeomTriMeshDataBuildSingle)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride); +//void (ODE_API *dGeomTriMeshDataBuildSingle1)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride, const void* Normals); +//void (ODE_API *dGeomTriMeshDataBuildDouble)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride); +//void (ODE_API *dGeomTriMeshDataBuildDouble1)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride, const void* Normals); +//void (ODE_API *dGeomTriMeshDataBuildSimple)(dTriMeshDataID g, const dReal* Vertices, int VertexCount, const dTriIndex* Indices, int IndexCount); +//void (ODE_API *dGeomTriMeshDataBuildSimple1)(dTriMeshDataID g, const dReal* Vertices, int VertexCount, const dTriIndex* Indices, int IndexCount, const int* Normals); +//void (ODE_API *dGeomTriMeshDataPreprocess)(dTriMeshDataID g); +//void (ODE_API *dGeomTriMeshDataGetBuffer)(dTriMeshDataID g, unsigned char** buf, int* bufLen); +//void (ODE_API *dGeomTriMeshDataSetBuffer)(dTriMeshDataID g, unsigned char* buf); +//void (ODE_API *dGeomTriMeshSetCallback)(dGeomID g, dTriCallback* Callback); +//dTriCallback* (ODE_API *dGeomTriMeshGetCallback)(dGeomID g); +//void (ODE_API *dGeomTriMeshSetArrayCallback)(dGeomID g, dTriArrayCallback* ArrayCallback); +//dTriArrayCallback* (ODE_API *dGeomTriMeshGetArrayCallback)(dGeomID g); +//void (ODE_API *dGeomTriMeshSetRayCallback)(dGeomID g, dTriRayCallback* Callback); +//dTriRayCallback* (ODE_API *dGeomTriMeshGetRayCallback)(dGeomID g); +//void (ODE_API *dGeomTriMeshSetTriMergeCallback)(dGeomID g, dTriTriMergeCallback* Callback); +//dTriTriMergeCallback* (ODE_API *dGeomTriMeshGetTriMergeCallback)(dGeomID g); +dGeomID (ODE_API *dCreateTriMesh)(dSpaceID space, dTriMeshDataID Data, dTriCallback* Callback, dTriArrayCallback* ArrayCallback, dTriRayCallback* RayCallback); +//void (ODE_API *dGeomTriMeshSetData)(dGeomID g, dTriMeshDataID Data); +//dTriMeshDataID (ODE_API *dGeomTriMeshGetData)(dGeomID g); +//void (ODE_API *dGeomTriMeshEnableTC)(dGeomID g, int geomClass, int enable); +//int (ODE_API *dGeomTriMeshIsTCEnabled)(dGeomID g, int geomClass); +//void (ODE_API *dGeomTriMeshClearTCCache)(dGeomID g); +//dTriMeshDataID (ODE_API *dGeomTriMeshGetTriMeshDataID)(dGeomID g); +//void (ODE_API *dGeomTriMeshGetTriangle)(dGeomID g, int Index, dVector3* v0, dVector3* v1, dVector3* v2); +//void (ODE_API *dGeomTriMeshGetPoint)(dGeomID g, int Index, dReal u, dReal v, dVector3 Out); +//int (ODE_API *dGeomTriMeshGetTriangleCount )(dGeomID g); +//void (ODE_API *dGeomTriMeshDataUpdate)(dTriMeshDataID g); + +static dllfunction_t odefuncs[] = +{ +// {"dGetConfiguration", (void **) &dGetConfiguration}, + {"dCheckConfiguration", (void **) &dCheckConfiguration}, + {"dInitODE", (void **) &dInitODE}, +// {"dInitODE2", (void **) &dInitODE2}, +// {"dAllocateODEDataForThread", (void **) &dAllocateODEDataForThread}, +// {"dCleanupODEAllDataForThread", (void **) &dCleanupODEAllDataForThread}, + {"dCloseODE", (void **) &dCloseODE}, +// {"dMassCheck", (void **) &dMassCheck}, +// {"dMassSetZero", (void **) &dMassSetZero}, +// {"dMassSetParameters", (void **) &dMassSetParameters}, +// {"dMassSetSphere", (void **) &dMassSetSphere}, + {"dMassSetSphereTotal", (void **) &dMassSetSphereTotal}, +// {"dMassSetCapsule", (void **) &dMassSetCapsule}, + {"dMassSetCapsuleTotal", (void **) &dMassSetCapsuleTotal}, +// {"dMassSetCylinder", (void **) &dMassSetCylinder}, +// {"dMassSetCylinderTotal", (void **) &dMassSetCylinderTotal}, +// {"dMassSetBox", (void **) &dMassSetBox}, + {"dMassSetBoxTotal", (void **) &dMassSetBoxTotal}, +// {"dMassSetTrimesh", (void **) &dMassSetTrimesh}, +// {"dMassSetTrimeshTotal", (void **) &dMassSetTrimeshTotal}, +// {"dMassAdjust", (void **) &dMassAdjust}, +// {"dMassTranslate", (void **) &dMassTranslate}, +// {"dMassRotate", (void **) &dMassRotate}, +// {"dMassAdd", (void **) &dMassAdd}, + + {"dWorldCreate", (void **) &dWorldCreate}, + {"dWorldDestroy", (void **) &dWorldDestroy}, + {"dWorldSetGravity", (void **) &dWorldSetGravity}, + {"dWorldGetGravity", (void **) &dWorldGetGravity}, + {"dWorldSetERP", (void **) &dWorldSetERP}, +// {"dWorldGetERP", (void **) &dWorldGetERP}, + {"dWorldSetCFM", (void **) &dWorldSetCFM}, +// {"dWorldGetCFM", (void **) &dWorldGetCFM}, + {"dWorldStep", (void **) &dWorldStep}, +// {"dWorldImpulseToForce", (void **) &dWorldImpulseToForce}, + {"dWorldQuickStep", (void **) &dWorldQuickStep}, + {"dWorldSetQuickStepNumIterations", (void **) &dWorldSetQuickStepNumIterations}, +// {"dWorldGetQuickStepNumIterations", (void **) &dWorldGetQuickStepNumIterations}, +// {"dWorldSetQuickStepW", (void **) &dWorldSetQuickStepW}, +// {"dWorldGetQuickStepW", (void **) &dWorldGetQuickStepW}, +// {"dWorldSetContactMaxCorrectingVel", (void **) &dWorldSetContactMaxCorrectingVel}, +// {"dWorldGetContactMaxCorrectingVel", (void **) &dWorldGetContactMaxCorrectingVel}, + {"dWorldSetContactSurfaceLayer", (void **) &dWorldSetContactSurfaceLayer}, +// {"dWorldGetContactSurfaceLayer", (void **) &dWorldGetContactSurfaceLayer}, +#ifdef ODE_USE_STEPFAST + {"dWorldStepFast1", (void **) &dWorldStepFast1}, +#endif +// {"dWorldSetAutoEnableDepthSF1", (void **) &dWorldSetAutoEnableDepthSF1}, +// {"dWorldGetAutoEnableDepthSF1", (void **) &dWorldGetAutoEnableDepthSF1}, +// {"dWorldGetAutoDisableLinearThreshold", (void **) &dWorldGetAutoDisableLinearThreshold}, + {"dWorldSetAutoDisableLinearThreshold", (void **) &dWorldSetAutoDisableLinearThreshold}, +// {"dWorldGetAutoDisableAngularThreshold", (void **) &dWorldGetAutoDisableAngularThreshold}, + {"dWorldSetAutoDisableAngularThreshold", (void **) &dWorldSetAutoDisableAngularThreshold}, +// {"dWorldGetAutoDisableLinearAverageThreshold", (void **) &dWorldGetAutoDisableLinearAverageThreshold}, +// {"dWorldSetAutoDisableLinearAverageThreshold", (void **) &dWorldSetAutoDisableLinearAverageThreshold}, +// {"dWorldGetAutoDisableAngularAverageThreshold", (void **) &dWorldGetAutoDisableAngularAverageThreshold}, +// {"dWorldSetAutoDisableAngularAverageThreshold", (void **) &dWorldSetAutoDisableAngularAverageThreshold}, +// {"dWorldGetAutoDisableAverageSamplesCount", (void **) &dWorldGetAutoDisableAverageSamplesCount}, + {"dWorldSetAutoDisableAverageSamplesCount", (void **) &dWorldSetAutoDisableAverageSamplesCount}, +// {"dWorldGetAutoDisableSteps", (void **) &dWorldGetAutoDisableSteps}, + {"dWorldSetAutoDisableSteps", (void **) &dWorldSetAutoDisableSteps}, +// {"dWorldGetAutoDisableTime", (void **) &dWorldGetAutoDisableTime}, + {"dWorldSetAutoDisableTime", (void **) &dWorldSetAutoDisableTime}, +// {"dWorldGetAutoDisableFlag", (void **) &dWorldGetAutoDisableFlag}, + {"dWorldSetAutoDisableFlag", (void **) &dWorldSetAutoDisableFlag}, +// {"dWorldGetLinearDampingThreshold", (void **) &dWorldGetLinearDampingThreshold}, + {"dWorldSetLinearDampingThreshold", (void **) &dWorldSetLinearDampingThreshold}, +// {"dWorldGetAngularDampingThreshold", (void **) &dWorldGetAngularDampingThreshold}, + {"dWorldSetAngularDampingThreshold", (void **) &dWorldSetAngularDampingThreshold}, +// {"dWorldGetLinearDamping", (void **) &dWorldGetLinearDamping}, + {"dWorldSetLinearDamping", (void **) &dWorldSetLinearDamping}, +// {"dWorldGetAngularDamping", (void **) &dWorldGetAngularDamping}, + {"dWorldSetAngularDamping", (void **) &dWorldSetAngularDamping}, +// {"dWorldSetDamping", (void **) &dWorldSetDamping}, +// {"dWorldGetMaxAngularSpeed", (void **) &dWorldGetMaxAngularSpeed}, +// {"dWorldSetMaxAngularSpeed", (void **) &dWorldSetMaxAngularSpeed}, +// {"dBodyGetAutoDisableLinearThreshold", (void **) &dBodyGetAutoDisableLinearThreshold}, +// {"dBodySetAutoDisableLinearThreshold", (void **) &dBodySetAutoDisableLinearThreshold}, +// {"dBodyGetAutoDisableAngularThreshold", (void **) &dBodyGetAutoDisableAngularThreshold}, +// {"dBodySetAutoDisableAngularThreshold", (void **) &dBodySetAutoDisableAngularThreshold}, +// {"dBodyGetAutoDisableAverageSamplesCount", (void **) &dBodyGetAutoDisableAverageSamplesCount}, +// {"dBodySetAutoDisableAverageSamplesCount", (void **) &dBodySetAutoDisableAverageSamplesCount}, +// {"dBodyGetAutoDisableSteps", (void **) &dBodyGetAutoDisableSteps}, +// {"dBodySetAutoDisableSteps", (void **) &dBodySetAutoDisableSteps}, +// {"dBodyGetAutoDisableTime", (void **) &dBodyGetAutoDisableTime}, +// {"dBodySetAutoDisableTime", (void **) &dBodySetAutoDisableTime}, +// {"dBodyGetAutoDisableFlag", (void **) &dBodyGetAutoDisableFlag}, +// {"dBodySetAutoDisableFlag", (void **) &dBodySetAutoDisableFlag}, +// {"dBodySetAutoDisableDefaults", (void **) &dBodySetAutoDisableDefaults}, +// {"dBodyGetWorld", (void **) &dBodyGetWorld}, + {"dBodyCreate", (void **) &dBodyCreate}, + {"dBodyDestroy", (void **) &dBodyDestroy}, + {"dBodySetData", (void **) &dBodySetData}, + {"dBodyGetData", (void **) &dBodyGetData}, + {"dBodySetPosition", (void **) &dBodySetPosition}, + {"dBodySetRotation", (void **) &dBodySetRotation}, +// {"dBodySetQuaternion", (void **) &dBodySetQuaternion}, + {"dBodySetLinearVel", (void **) &dBodySetLinearVel}, + {"dBodySetAngularVel", (void **) &dBodySetAngularVel}, + {"dBodyGetPosition", (void **) &dBodyGetPosition}, +// {"dBodyCopyPosition", (void **) &dBodyCopyPosition}, + {"dBodyGetRotation", (void **) &dBodyGetRotation}, +// {"dBodyCopyRotation", (void **) &dBodyCopyRotation}, +// {"dBodyGetQuaternion", (void **) &dBodyGetQuaternion}, +// {"dBodyCopyQuaternion", (void **) &dBodyCopyQuaternion}, + {"dBodyGetLinearVel", (void **) &dBodyGetLinearVel}, + {"dBodyGetAngularVel", (void **) &dBodyGetAngularVel}, + {"dBodySetMass", (void **) &dBodySetMass}, +// {"dBodyGetMass", (void **) &dBodyGetMass}, +// {"dBodyAddForce", (void **) &dBodyAddForce}, +// {"dBodyAddTorque", (void **) &dBodyAddTorque}, +// {"dBodyAddRelForce", (void **) &dBodyAddRelForce}, + {"dBodyAddRelTorque", (void **) &dBodyAddRelTorque}, +// {"dBodyAddForceAtPos", (void **) &dBodyAddForceAtPos}, + {"dBodyAddForceAtRelPos", (void **) &dBodyAddForceAtRelPos}, +// {"dBodyAddRelForceAtPos", (void **) &dBodyAddRelForceAtPos}, +// {"dBodyAddRelForceAtRelPos", (void **) &dBodyAddRelForceAtRelPos}, +// {"dBodyGetForce", (void **) &dBodyGetForce}, +// {"dBodyGetTorque", (void **) &dBodyGetTorque}, +// {"dBodySetForce", (void **) &dBodySetForce}, +// {"dBodySetTorque", (void **) &dBodySetTorque}, +// {"dBodyGetRelPointPos", (void **) &dBodyGetRelPointPos}, +// {"dBodyGetRelPointVel", (void **) &dBodyGetRelPointVel}, +// {"dBodyGetPointVel", (void **) &dBodyGetPointVel}, +// {"dBodyGetPosRelPoint", (void **) &dBodyGetPosRelPoint}, +// {"dBodyVectorToWorld", (void **) &dBodyVectorToWorld}, +// {"dBodyVectorFromWorld", (void **) &dBodyVectorFromWorld}, +// {"dBodySetFiniteRotationMode", (void **) &dBodySetFiniteRotationMode}, +// {"dBodySetFiniteRotationAxis", (void **) &dBodySetFiniteRotationAxis}, +// {"dBodyGetFiniteRotationMode", (void **) &dBodyGetFiniteRotationMode}, +// {"dBodyGetFiniteRotationAxis", (void **) &dBodyGetFiniteRotationAxis}, + {"dBodyGetNumJoints", (void **) &dBodyGetNumJoints}, + {"dBodyGetJoint", (void **) &dBodyGetJoint}, +// {"dBodySetDynamic", (void **) &dBodySetDynamic}, +// {"dBodySetKinematic", (void **) &dBodySetKinematic}, +// {"dBodyIsKinematic", (void **) &dBodyIsKinematic}, + {"dBodyEnable", (void **) &dBodyEnable}, + {"dBodyDisable", (void **) &dBodyDisable}, + {"dBodyIsEnabled", (void **) &dBodyIsEnabled}, + {"dBodySetGravityMode", (void **) &dBodySetGravityMode}, + {"dBodyGetGravityMode", (void **) &dBodyGetGravityMode}, +// {"dBodySetMovedCallback", (void **) &dBodySetMovedCallback}, +// {"dBodyGetFirstGeom", (void **) &dBodyGetFirstGeom}, +// {"dBodyGetNextGeom", (void **) &dBodyGetNextGeom}, +// {"dBodySetDampingDefaults", (void **) &dBodySetDampingDefaults}, +// {"dBodyGetLinearDamping", (void **) &dBodyGetLinearDamping}, +// {"dBodySetLinearDamping", (void **) &dBodySetLinearDamping}, +// {"dBodyGetAngularDamping", (void **) &dBodyGetAngularDamping}, +// {"dBodySetAngularDamping", (void **) &dBodySetAngularDamping}, +// {"dBodySetDamping", (void **) &dBodySetDamping}, +// {"dBodyGetLinearDampingThreshold", (void **) &dBodyGetLinearDampingThreshold}, +// {"dBodySetLinearDampingThreshold", (void **) &dBodySetLinearDampingThreshold}, +// {"dBodyGetAngularDampingThreshold", (void **) &dBodyGetAngularDampingThreshold}, +// {"dBodySetAngularDampingThreshold", (void **) &dBodySetAngularDampingThreshold}, +// {"dBodyGetMaxAngularSpeed", (void **) &dBodyGetMaxAngularSpeed}, +// {"dBodySetMaxAngularSpeed", (void **) &dBodySetMaxAngularSpeed}, +// {"dBodyGetGyroscopicMode", (void **) &dBodyGetGyroscopicMode}, +// {"dBodySetGyroscopicMode", (void **) &dBodySetGyroscopicMode}, + {"dJointCreateBall", (void **) &dJointCreateBall}, + {"dJointCreateHinge", (void **) &dJointCreateHinge}, + {"dJointCreateSlider", (void **) &dJointCreateSlider}, + {"dJointCreateContact", (void **) &dJointCreateContact}, + {"dJointCreateHinge2", (void **) &dJointCreateHinge2}, + {"dJointCreateUniversal", (void **) &dJointCreateUniversal}, +// {"dJointCreatePR", (void **) &dJointCreatePR}, +// {"dJointCreatePU", (void **) &dJointCreatePU}, +// {"dJointCreatePiston", (void **) &dJointCreatePiston}, + {"dJointCreateFixed", (void **) &dJointCreateFixed}, +// {"dJointCreateNull", (void **) &dJointCreateNull}, +// {"dJointCreateAMotor", (void **) &dJointCreateAMotor}, +// {"dJointCreateLMotor", (void **) &dJointCreateLMotor}, +// {"dJointCreatePlane2D", (void **) &dJointCreatePlane2D}, + {"dJointDestroy", (void **) &dJointDestroy}, + {"dJointGroupCreate", (void **) &dJointGroupCreate}, + {"dJointGroupDestroy", (void **) &dJointGroupDestroy}, + {"dJointGroupEmpty", (void **) &dJointGroupEmpty}, +// {"dJointGetNumBodies", (void **) &dJointGetNumBodies}, + {"dJointAttach", (void **) &dJointAttach}, +// {"dJointEnable", (void **) &dJointEnable}, +// {"dJointDisable", (void **) &dJointDisable}, +// {"dJointIsEnabled", (void **) &dJointIsEnabled}, + {"dJointSetData", (void **) &dJointSetData}, + {"dJointGetData", (void **) &dJointGetData}, +// {"dJointGetType", (void **) &dJointGetType}, + {"dJointGetBody", (void **) &dJointGetBody}, +// {"dJointSetFeedback", (void **) &dJointSetFeedback}, +// {"dJointGetFeedback", (void **) &dJointGetFeedback}, + {"dJointSetBallAnchor", (void **) &dJointSetBallAnchor}, +// {"dJointSetBallAnchor2", (void **) &dJointSetBallAnchor2}, + {"dJointSetBallParam", (void **) &dJointSetBallParam}, + {"dJointSetHingeAnchor", (void **) &dJointSetHingeAnchor}, +// {"dJointSetHingeAnchorDelta", (void **) &dJointSetHingeAnchorDelta}, + {"dJointSetHingeAxis", (void **) &dJointSetHingeAxis}, +// {"dJointSetHingeAxisOffset", (void **) &dJointSetHingeAxisOffset}, + {"dJointSetHingeParam", (void **) &dJointSetHingeParam}, +// {"dJointAddHingeTorque", (void **) &dJointAddHingeTorque}, + {"dJointSetSliderAxis", (void **) &dJointSetSliderAxis}, +// {"dJointSetSliderAxisDelta", (void **) &dJointSetSliderAxisDelta}, + {"dJointSetSliderParam", (void **) &dJointSetSliderParam}, +// {"dJointAddSliderForce", (void **) &dJointAddSliderForce}, + {"dJointSetHinge2Anchor", (void **) &dJointSetHinge2Anchor}, + {"dJointSetHinge2Axis1", (void **) &dJointSetHinge2Axis1}, + {"dJointSetHinge2Axis2", (void **) &dJointSetHinge2Axis2}, + {"dJointSetHinge2Param", (void **) &dJointSetHinge2Param}, +// {"dJointAddHinge2Torques", (void **) &dJointAddHinge2Torques}, + {"dJointSetUniversalAnchor", (void **) &dJointSetUniversalAnchor}, + {"dJointSetUniversalAxis1", (void **) &dJointSetUniversalAxis1}, +// {"dJointSetUniversalAxis1Offset", (void **) &dJointSetUniversalAxis1Offset}, + {"dJointSetUniversalAxis2", (void **) &dJointSetUniversalAxis2}, +// {"dJointSetUniversalAxis2Offset", (void **) &dJointSetUniversalAxis2Offset}, + {"dJointSetUniversalParam", (void **) &dJointSetUniversalParam}, +// {"dJointAddUniversalTorques", (void **) &dJointAddUniversalTorques}, +// {"dJointSetPRAnchor", (void **) &dJointSetPRAnchor}, +// {"dJointSetPRAxis1", (void **) &dJointSetPRAxis1}, +// {"dJointSetPRAxis2", (void **) &dJointSetPRAxis2}, +// {"dJointSetPRParam", (void **) &dJointSetPRParam}, +// {"dJointAddPRTorque", (void **) &dJointAddPRTorque}, +// {"dJointSetPUAnchor", (void **) &dJointSetPUAnchor}, +// {"dJointSetPUAnchorOffset", (void **) &dJointSetPUAnchorOffset}, +// {"dJointSetPUAxis1", (void **) &dJointSetPUAxis1}, +// {"dJointSetPUAxis2", (void **) &dJointSetPUAxis2}, +// {"dJointSetPUAxis3", (void **) &dJointSetPUAxis3}, +// {"dJointSetPUAxisP", (void **) &dJointSetPUAxisP}, +// {"dJointSetPUParam", (void **) &dJointSetPUParam}, +// {"dJointAddPUTorque", (void **) &dJointAddPUTorque}, +// {"dJointSetPistonAnchor", (void **) &dJointSetPistonAnchor}, +// {"dJointSetPistonAnchorOffset", (void **) &dJointSetPistonAnchorOffset}, +// {"dJointSetPistonParam", (void **) &dJointSetPistonParam}, +// {"dJointAddPistonForce", (void **) &dJointAddPistonForce}, +// {"dJointSetFixed", (void **) &dJointSetFixed}, +// {"dJointSetFixedParam", (void **) &dJointSetFixedParam}, +// {"dJointSetAMotorNumAxes", (void **) &dJointSetAMotorNumAxes}, +// {"dJointSetAMotorAxis", (void **) &dJointSetAMotorAxis}, +// {"dJointSetAMotorAngle", (void **) &dJointSetAMotorAngle}, +// {"dJointSetAMotorParam", (void **) &dJointSetAMotorParam}, +// {"dJointSetAMotorMode", (void **) &dJointSetAMotorMode}, +// {"dJointAddAMotorTorques", (void **) &dJointAddAMotorTorques}, +// {"dJointSetLMotorNumAxes", (void **) &dJointSetLMotorNumAxes}, +// {"dJointSetLMotorAxis", (void **) &dJointSetLMotorAxis}, +// {"dJointSetLMotorParam", (void **) &dJointSetLMotorParam}, +// {"dJointSetPlane2DXParam", (void **) &dJointSetPlane2DXParam}, +// {"dJointSetPlane2DYParam", (void **) &dJointSetPlane2DYParam}, +// {"dJointSetPlane2DAngleParam", (void **) &dJointSetPlane2DAngleParam}, +// {"dJointGetBallAnchor", (void **) &dJointGetBallAnchor}, +// {"dJointGetBallAnchor2", (void **) &dJointGetBallAnchor2}, +// {"dJointGetBallParam", (void **) &dJointGetBallParam}, +// {"dJointGetHingeAnchor", (void **) &dJointGetHingeAnchor}, +// {"dJointGetHingeAnchor2", (void **) &dJointGetHingeAnchor2}, +// {"dJointGetHingeAxis", (void **) &dJointGetHingeAxis}, +// {"dJointGetHingeParam", (void **) &dJointGetHingeParam}, +// {"dJointGetHingeAngle", (void **) &dJointGetHingeAngle}, +// {"dJointGetHingeAngleRate", (void **) &dJointGetHingeAngleRate}, +// {"dJointGetSliderPosition", (void **) &dJointGetSliderPosition}, +// {"dJointGetSliderPositionRate", (void **) &dJointGetSliderPositionRate}, +// {"dJointGetSliderAxis", (void **) &dJointGetSliderAxis}, +// {"dJointGetSliderParam", (void **) &dJointGetSliderParam}, +// {"dJointGetHinge2Anchor", (void **) &dJointGetHinge2Anchor}, +// {"dJointGetHinge2Anchor2", (void **) &dJointGetHinge2Anchor2}, +// {"dJointGetHinge2Axis1", (void **) &dJointGetHinge2Axis1}, +// {"dJointGetHinge2Axis2", (void **) &dJointGetHinge2Axis2}, +// {"dJointGetHinge2Param", (void **) &dJointGetHinge2Param}, +// {"dJointGetHinge2Angle1", (void **) &dJointGetHinge2Angle1}, +// {"dJointGetHinge2Angle1Rate", (void **) &dJointGetHinge2Angle1Rate}, +// {"dJointGetHinge2Angle2Rate", (void **) &dJointGetHinge2Angle2Rate}, +// {"dJointGetUniversalAnchor", (void **) &dJointGetUniversalAnchor}, +// {"dJointGetUniversalAnchor2", (void **) &dJointGetUniversalAnchor2}, +// {"dJointGetUniversalAxis1", (void **) &dJointGetUniversalAxis1}, +// {"dJointGetUniversalAxis2", (void **) &dJointGetUniversalAxis2}, +// {"dJointGetUniversalParam", (void **) &dJointGetUniversalParam}, +// {"dJointGetUniversalAngles", (void **) &dJointGetUniversalAngles}, +// {"dJointGetUniversalAngle1", (void **) &dJointGetUniversalAngle1}, +// {"dJointGetUniversalAngle2", (void **) &dJointGetUniversalAngle2}, +// {"dJointGetUniversalAngle1Rate", (void **) &dJointGetUniversalAngle1Rate}, +// {"dJointGetUniversalAngle2Rate", (void **) &dJointGetUniversalAngle2Rate}, +// {"dJointGetPRAnchor", (void **) &dJointGetPRAnchor}, +// {"dJointGetPRPosition", (void **) &dJointGetPRPosition}, +// {"dJointGetPRPositionRate", (void **) &dJointGetPRPositionRate}, +// {"dJointGetPRAngle", (void **) &dJointGetPRAngle}, +// {"dJointGetPRAngleRate", (void **) &dJointGetPRAngleRate}, +// {"dJointGetPRAxis1", (void **) &dJointGetPRAxis1}, +// {"dJointGetPRAxis2", (void **) &dJointGetPRAxis2}, +// {"dJointGetPRParam", (void **) &dJointGetPRParam}, +// {"dJointGetPUAnchor", (void **) &dJointGetPUAnchor}, +// {"dJointGetPUPosition", (void **) &dJointGetPUPosition}, +// {"dJointGetPUPositionRate", (void **) &dJointGetPUPositionRate}, +// {"dJointGetPUAxis1", (void **) &dJointGetPUAxis1}, +// {"dJointGetPUAxis2", (void **) &dJointGetPUAxis2}, +// {"dJointGetPUAxis3", (void **) &dJointGetPUAxis3}, +// {"dJointGetPUAxisP", (void **) &dJointGetPUAxisP}, +// {"dJointGetPUAngles", (void **) &dJointGetPUAngles}, +// {"dJointGetPUAngle1", (void **) &dJointGetPUAngle1}, +// {"dJointGetPUAngle1Rate", (void **) &dJointGetPUAngle1Rate}, +// {"dJointGetPUAngle2", (void **) &dJointGetPUAngle2}, +// {"dJointGetPUAngle2Rate", (void **) &dJointGetPUAngle2Rate}, +// {"dJointGetPUParam", (void **) &dJointGetPUParam}, +// {"dJointGetPistonPosition", (void **) &dJointGetPistonPosition}, +// {"dJointGetPistonPositionRate", (void **) &dJointGetPistonPositionRate}, +// {"dJointGetPistonAngle", (void **) &dJointGetPistonAngle}, +// {"dJointGetPistonAngleRate", (void **) &dJointGetPistonAngleRate}, +// {"dJointGetPistonAnchor", (void **) &dJointGetPistonAnchor}, +// {"dJointGetPistonAnchor2", (void **) &dJointGetPistonAnchor2}, +// {"dJointGetPistonAxis", (void **) &dJointGetPistonAxis}, +// {"dJointGetPistonParam", (void **) &dJointGetPistonParam}, +// {"dJointGetAMotorNumAxes", (void **) &dJointGetAMotorNumAxes}, +// {"dJointGetAMotorAxis", (void **) &dJointGetAMotorAxis}, +// {"dJointGetAMotorAxisRel", (void **) &dJointGetAMotorAxisRel}, +// {"dJointGetAMotorAngle", (void **) &dJointGetAMotorAngle}, +// {"dJointGetAMotorAngleRate", (void **) &dJointGetAMotorAngleRate}, +// {"dJointGetAMotorParam", (void **) &dJointGetAMotorParam}, +// {"dJointGetAMotorMode", (void **) &dJointGetAMotorMode}, +// {"dJointGetLMotorNumAxes", (void **) &dJointGetLMotorNumAxes}, +// {"dJointGetLMotorAxis", (void **) &dJointGetLMotorAxis}, +// {"dJointGetLMotorParam", (void **) &dJointGetLMotorParam}, +// {"dJointGetFixedParam", (void **) &dJointGetFixedParam}, +// {"dConnectingJoint", (void **) &dConnectingJoint}, +// {"dConnectingJointList", (void **) &dConnectingJointList}, + {"dAreConnected", (void **) &dAreConnected}, + {"dAreConnectedExcluding", (void **) &dAreConnectedExcluding}, + {"dSimpleSpaceCreate", (void **) &dSimpleSpaceCreate}, + {"dHashSpaceCreate", (void **) &dHashSpaceCreate}, + {"dQuadTreeSpaceCreate", (void **) &dQuadTreeSpaceCreate}, +// {"dSweepAndPruneSpaceCreate", (void **) &dSweepAndPruneSpaceCreate}, + {"dSpaceDestroy", (void **) &dSpaceDestroy}, +// {"dHashSpaceSetLevels", (void **) &dHashSpaceSetLevels}, +// {"dHashSpaceGetLevels", (void **) &dHashSpaceGetLevels}, +// {"dSpaceSetCleanup", (void **) &dSpaceSetCleanup}, +// {"dSpaceGetCleanup", (void **) &dSpaceGetCleanup}, +// {"dSpaceSetSublevel", (void **) &dSpaceSetSublevel}, +// {"dSpaceGetSublevel", (void **) &dSpaceGetSublevel}, +// {"dSpaceSetManualCleanup", (void **) &dSpaceSetManualCleanup}, +// {"dSpaceGetManualCleanup", (void **) &dSpaceGetManualCleanup}, +// {"dSpaceAdd", (void **) &dSpaceAdd}, +// {"dSpaceRemove", (void **) &dSpaceRemove}, +// {"dSpaceQuery", (void **) &dSpaceQuery}, +// {"dSpaceClean", (void **) &dSpaceClean}, +// {"dSpaceGetNumGeoms", (void **) &dSpaceGetNumGeoms}, +// {"dSpaceGetGeom", (void **) &dSpaceGetGeom}, +// {"dSpaceGetClass", (void **) &dSpaceGetClass}, + {"dGeomDestroy", (void **) &dGeomDestroy}, + {"dGeomSetData", (void **) &dGeomSetData}, + {"dGeomGetData", (void **) &dGeomGetData}, + {"dGeomSetBody", (void **) &dGeomSetBody}, + {"dGeomGetBody", (void **) &dGeomGetBody}, + {"dGeomSetPosition", (void **) &dGeomSetPosition}, + {"dGeomSetRotation", (void **) &dGeomSetRotation}, +// {"dGeomSetQuaternion", (void **) &dGeomSetQuaternion}, +// {"dGeomGetPosition", (void **) &dGeomGetPosition}, +// {"dGeomCopyPosition", (void **) &dGeomCopyPosition}, +// {"dGeomGetRotation", (void **) &dGeomGetRotation}, +// {"dGeomCopyRotation", (void **) &dGeomCopyRotation}, +// {"dGeomGetQuaternion", (void **) &dGeomGetQuaternion}, +// {"dGeomGetAABB", (void **) &dGeomGetAABB}, + {"dGeomIsSpace", (void **) &dGeomIsSpace}, +// {"dGeomGetSpace", (void **) &dGeomGetSpace}, +// {"dGeomGetClass", (void **) &dGeomGetClass}, +// {"dGeomSetCategoryBits", (void **) &dGeomSetCategoryBits}, +// {"dGeomSetCollideBits", (void **) &dGeomSetCollideBits}, +// {"dGeomGetCategoryBits", (void **) &dGeomGetCategoryBits}, +// {"dGeomGetCollideBits", (void **) &dGeomGetCollideBits}, +// {"dGeomEnable", (void **) &dGeomEnable}, +// {"dGeomDisable", (void **) &dGeomDisable}, +// {"dGeomIsEnabled", (void **) &dGeomIsEnabled}, +// {"dGeomSetOffsetPosition", (void **) &dGeomSetOffsetPosition}, +// {"dGeomSetOffsetRotation", (void **) &dGeomSetOffsetRotation}, +// {"dGeomSetOffsetQuaternion", (void **) &dGeomSetOffsetQuaternion}, +// {"dGeomSetOffsetWorldPosition", (void **) &dGeomSetOffsetWorldPosition}, +// {"dGeomSetOffsetWorldRotation", (void **) &dGeomSetOffsetWorldRotation}, +// {"dGeomSetOffsetWorldQuaternion", (void **) &dGeomSetOffsetWorldQuaternion}, +// {"dGeomClearOffset", (void **) &dGeomClearOffset}, +// {"dGeomIsOffset", (void **) &dGeomIsOffset}, +// {"dGeomGetOffsetPosition", (void **) &dGeomGetOffsetPosition}, +// {"dGeomCopyOffsetPosition", (void **) &dGeomCopyOffsetPosition}, +// {"dGeomGetOffsetRotation", (void **) &dGeomGetOffsetRotation}, +// {"dGeomCopyOffsetRotation", (void **) &dGeomCopyOffsetRotation}, +// {"dGeomGetOffsetQuaternion", (void **) &dGeomGetOffsetQuaternion}, + {"dCollide", (void **) &dCollide}, + {"dSpaceCollide", (void **) &dSpaceCollide}, + {"dSpaceCollide2", (void **) &dSpaceCollide2}, + {"dCreateSphere", (void **) &dCreateSphere}, +// {"dGeomSphereSetRadius", (void **) &dGeomSphereSetRadius}, +// {"dGeomSphereGetRadius", (void **) &dGeomSphereGetRadius}, +// {"dGeomSpherePointDepth", (void **) &dGeomSpherePointDepth}, +// {"dCreateConvex", (void **) &dCreateConvex}, +// {"dGeomSetConvex", (void **) &dGeomSetConvex}, + {"dCreateBox", (void **) &dCreateBox}, +// {"dGeomBoxSetLengths", (void **) &dGeomBoxSetLengths}, +// {"dGeomBoxGetLengths", (void **) &dGeomBoxGetLengths}, +// {"dGeomBoxPointDepth", (void **) &dGeomBoxPointDepth}, +// {"dGeomBoxPointDepth", (void **) &dGeomBoxPointDepth}, +// {"dCreatePlane", (void **) &dCreatePlane}, +// {"dGeomPlaneSetParams", (void **) &dGeomPlaneSetParams}, +// {"dGeomPlaneGetParams", (void **) &dGeomPlaneGetParams}, +// {"dGeomPlanePointDepth", (void **) &dGeomPlanePointDepth}, + {"dCreateCapsule", (void **) &dCreateCapsule}, +// {"dGeomCapsuleSetParams", (void **) &dGeomCapsuleSetParams}, +// {"dGeomCapsuleGetParams", (void **) &dGeomCapsuleGetParams}, +// {"dGeomCapsulePointDepth", (void **) &dGeomCapsulePointDepth}, +// {"dCreateCylinder", (void **) &dCreateCylinder}, +// {"dGeomCylinderSetParams", (void **) &dGeomCylinderSetParams}, +// {"dGeomCylinderGetParams", (void **) &dGeomCylinderGetParams}, +// {"dCreateRay", (void **) &dCreateRay}, +// {"dGeomRaySetLength", (void **) &dGeomRaySetLength}, +// {"dGeomRayGetLength", (void **) &dGeomRayGetLength}, +// {"dGeomRaySet", (void **) &dGeomRaySet}, +// {"dGeomRayGet", (void **) &dGeomRayGet}, + {"dCreateGeomTransform", (void **) &dCreateGeomTransform}, + {"dGeomTransformSetGeom", (void **) &dGeomTransformSetGeom}, +// {"dGeomTransformGetGeom", (void **) &dGeomTransformGetGeom}, + {"dGeomTransformSetCleanup", (void **) &dGeomTransformSetCleanup}, +// {"dGeomTransformGetCleanup", (void **) &dGeomTransformGetCleanup}, +// {"dGeomTransformSetInfo", (void **) &dGeomTransformSetInfo}, +// {"dGeomTransformGetInfo", (void **) &dGeomTransformGetInfo}, + {"dGeomTriMeshDataCreate", (void **) &dGeomTriMeshDataCreate}, + {"dGeomTriMeshDataDestroy", (void **) &dGeomTriMeshDataDestroy}, +// {"dGeomTriMeshDataSet", (void **) &dGeomTriMeshDataSet}, +// {"dGeomTriMeshDataGet", (void **) &dGeomTriMeshDataGet}, +// {"dGeomTriMeshSetLastTransform", (void **) &dGeomTriMeshSetLastTransform}, +// {"dGeomTriMeshGetLastTransform", (void **) &dGeomTriMeshGetLastTransform}, + {"dGeomTriMeshDataBuildSingle", (void **) &dGeomTriMeshDataBuildSingle}, +// {"dGeomTriMeshDataBuildSingle1", (void **) &dGeomTriMeshDataBuildSingle1}, +// {"dGeomTriMeshDataBuildDouble", (void **) &dGeomTriMeshDataBuildDouble}, +// {"dGeomTriMeshDataBuildDouble1", (void **) &dGeomTriMeshDataBuildDouble1}, +// {"dGeomTriMeshDataBuildSimple", (void **) &dGeomTriMeshDataBuildSimple}, +// {"dGeomTriMeshDataBuildSimple1", (void **) &dGeomTriMeshDataBuildSimple1}, +// {"dGeomTriMeshDataPreprocess", (void **) &dGeomTriMeshDataPreprocess}, +// {"dGeomTriMeshDataGetBuffer", (void **) &dGeomTriMeshDataGetBuffer}, +// {"dGeomTriMeshDataSetBuffer", (void **) &dGeomTriMeshDataSetBuffer}, +// {"dGeomTriMeshSetCallback", (void **) &dGeomTriMeshSetCallback}, +// {"dGeomTriMeshGetCallback", (void **) &dGeomTriMeshGetCallback}, +// {"dGeomTriMeshSetArrayCallback", (void **) &dGeomTriMeshSetArrayCallback}, +// {"dGeomTriMeshGetArrayCallback", (void **) &dGeomTriMeshGetArrayCallback}, +// {"dGeomTriMeshSetRayCallback", (void **) &dGeomTriMeshSetRayCallback}, +// {"dGeomTriMeshGetRayCallback", (void **) &dGeomTriMeshGetRayCallback}, +// {"dGeomTriMeshSetTriMergeCallback", (void **) &dGeomTriMeshSetTriMergeCallback}, +// {"dGeomTriMeshGetTriMergeCallback", (void **) &dGeomTriMeshGetTriMergeCallback}, + {"dCreateTriMesh", (void **) &dCreateTriMesh}, +// {"dGeomTriMeshSetData", (void **) &dGeomTriMeshSetData}, +// {"dGeomTriMeshGetData", (void **) &dGeomTriMeshGetData}, +// {"dGeomTriMeshEnableTC", (void **) &dGeomTriMeshEnableTC}, +// {"dGeomTriMeshIsTCEnabled", (void **) &dGeomTriMeshIsTCEnabled}, +// {"dGeomTriMeshClearTCCache", (void **) &dGeomTriMeshClearTCCache}, +// {"dGeomTriMeshGetTriMeshDataID", (void **) &dGeomTriMeshGetTriMeshDataID}, +// {"dGeomTriMeshGetTriangle", (void **) &dGeomTriMeshGetTriangle}, +// {"dGeomTriMeshGetPoint", (void **) &dGeomTriMeshGetPoint}, +// {"dGeomTriMeshGetTriangleCount", (void **) &dGeomTriMeshGetTriangleCount}, +// {"dGeomTriMeshDataUpdate", (void **) &dGeomTriMeshDataUpdate}, + {NULL, NULL} +}; + +// Handle for ODE DLL +dllhandle_t ode_dll = NULL; +#endif +#endif + +static void World_Physics_Init(void) +{ +#ifdef USEODE +#ifdef ODE_DYNAMIC + const char* dllnames [] = + { +# if defined(WIN32) + "libode1.dll", +# elif defined(MACOSX) + "libode.1.dylib", +# else + "libode.so.1", +# endif + NULL + }; +#endif + + Cvar_RegisterVariable(&physics_ode_quadtree_depth); + Cvar_RegisterVariable(&physics_ode_contactsurfacelayer); + Cvar_RegisterVariable(&physics_ode_worldstep); + Cvar_RegisterVariable(&physics_ode_worldstep_iterations); + Cvar_RegisterVariable(&physics_ode_contact_mu); + Cvar_RegisterVariable(&physics_ode_contact_erp); + Cvar_RegisterVariable(&physics_ode_contact_cfm); + Cvar_RegisterVariable(&physics_ode_world_erp); + Cvar_RegisterVariable(&physics_ode_world_cfm); + Cvar_RegisterVariable(&physics_ode_world_damping); + Cvar_RegisterVariable(&physics_ode_world_damping_linear); + Cvar_RegisterVariable(&physics_ode_world_damping_linear_threshold); + Cvar_RegisterVariable(&physics_ode_world_damping_angular); + Cvar_RegisterVariable(&physics_ode_world_damping_angular_threshold); + Cvar_RegisterVariable(&physics_ode_iterationsperframe); + Cvar_RegisterVariable(&physics_ode_constantstep); + Cvar_RegisterVariable(&physics_ode_movelimit); + Cvar_RegisterVariable(&physics_ode_spinlimit); + Cvar_RegisterVariable(&physics_ode_trick_fixnan); + Cvar_RegisterVariable(&physics_ode_autodisable); + Cvar_RegisterVariable(&physics_ode_autodisable_steps); + Cvar_RegisterVariable(&physics_ode_autodisable_time); + Cvar_RegisterVariable(&physics_ode_autodisable_threshold_linear); + Cvar_RegisterVariable(&physics_ode_autodisable_threshold_angular); + Cvar_RegisterVariable(&physics_ode_autodisable_threshold_samples); + Cvar_RegisterVariable(&physics_ode_printstats); + Cvar_RegisterVariable(&physics_ode); + +#ifdef ODE_DYNAMIC + // Load the DLL + if (Sys_LoadLibrary (dllnames, &ode_dll, odefuncs)) +#endif + { + dInitODE(); +// dInitODE2(0); +#ifdef ODE_DYNAMIC +# ifdef dSINGLE + if (!dCheckConfiguration("ODE_single_precision")) +# else + if (!dCheckConfiguration("ODE_double_precision")) +# endif + { +# ifdef dSINGLE + Con_Printf("ODE library not compiled for single precision - incompatible! Not using ODE physics.\n"); +# else + Con_Printf("ODE library not compiled for double precision - incompatible! Not using ODE physics.\n"); +# endif + Sys_UnloadLibrary(&ode_dll); + ode_dll = NULL; + } + else + { +# ifdef dSINGLE + Con_Printf("ODE library loaded with single precision.\n"); +# else + Con_Printf("ODE library loaded with double precision.\n"); +# endif + } +#endif + } +#endif +} + +static void World_Physics_Shutdown(void) +{ +#ifdef USEODE +#ifdef ODE_DYNAMIC + if (ode_dll) +#endif + { + dCloseODE(); +#ifdef ODE_DYNAMIC + Sys_UnloadLibrary(&ode_dll); + ode_dll = NULL; +#endif + } +#endif +} + +#ifdef USEODE +static void World_Physics_UpdateODE(world_t *world) +{ + dWorldID odeworld; + + odeworld = (dWorldID)world->physics.ode_world; + + // ERP and CFM + if (physics_ode_world_erp.value >= 0) + dWorldSetERP(odeworld, physics_ode_world_erp.value); + if (physics_ode_world_cfm.value >= 0) + dWorldSetCFM(odeworld, physics_ode_world_cfm.value); + // Damping + if (physics_ode_world_damping.integer) + { + dWorldSetLinearDamping(odeworld, (physics_ode_world_damping_linear.value >= 0) ? (physics_ode_world_damping_linear.value * physics_ode_world_damping.value) : 0); + dWorldSetLinearDampingThreshold(odeworld, (physics_ode_world_damping_linear_threshold.value >= 0) ? (physics_ode_world_damping_linear_threshold.value * physics_ode_world_damping.value) : 0); + dWorldSetAngularDamping(odeworld, (physics_ode_world_damping_angular.value >= 0) ? (physics_ode_world_damping_angular.value * physics_ode_world_damping.value) : 0); + dWorldSetAngularDampingThreshold(odeworld, (physics_ode_world_damping_angular_threshold.value >= 0) ? (physics_ode_world_damping_angular_threshold.value * physics_ode_world_damping.value) : 0); + } + else + { + dWorldSetLinearDamping(odeworld, 0); + dWorldSetLinearDampingThreshold(odeworld, 0); + dWorldSetAngularDamping(odeworld, 0); + dWorldSetAngularDampingThreshold(odeworld, 0); + } + // Autodisable + dWorldSetAutoDisableFlag(odeworld, (physics_ode_autodisable.integer) ? 1 : 0); + if (physics_ode_autodisable.integer) + { + dWorldSetAutoDisableSteps(odeworld, bound(1, physics_ode_autodisable_steps.integer, 100)); + dWorldSetAutoDisableTime(odeworld, physics_ode_autodisable_time.value); + dWorldSetAutoDisableAverageSamplesCount(odeworld, bound(1, physics_ode_autodisable_threshold_samples.integer, 100)); + dWorldSetAutoDisableLinearThreshold(odeworld, physics_ode_autodisable_threshold_linear.value); + dWorldSetAutoDisableAngularThreshold(odeworld, physics_ode_autodisable_threshold_angular.value); + } +} + +static void World_Physics_EnableODE(world_t *world) +{ + dVector3 center, extents; + if (world->physics.ode) + return; +#ifdef ODE_DYNAMIC + if (!ode_dll) + return; +#endif + world->physics.ode = true; + VectorMAM(0.5f, world->mins, 0.5f, world->maxs, center); + VectorSubtract(world->maxs, center, extents); + world->physics.ode_world = dWorldCreate(); + world->physics.ode_space = dQuadTreeSpaceCreate(NULL, center, extents, bound(1, physics_ode_quadtree_depth.integer, 10)); + world->physics.ode_contactgroup = dJointGroupCreate(0); + + World_Physics_UpdateODE(world); +} +#endif + +static void World_Physics_Start(world_t *world) +{ +#ifdef USEODE + if (world->physics.ode) + return; + World_Physics_EnableODE(world); +#endif +} + +static void World_Physics_End(world_t *world) +{ +#ifdef USEODE + if (world->physics.ode) + { + dWorldDestroy((dWorldID)world->physics.ode_world); + dSpaceDestroy((dSpaceID)world->physics.ode_space); + dJointGroupDestroy((dJointGroupID)world->physics.ode_contactgroup); + world->physics.ode = false; + } +#endif +} + +void World_Physics_RemoveJointFromEntity(world_t *world, prvm_edict_t *ed) +{ + ed->priv.server->ode_joint_type = 0; +#ifdef USEODE + if(ed->priv.server->ode_joint) + dJointDestroy((dJointID)ed->priv.server->ode_joint); + ed->priv.server->ode_joint = NULL; +#endif +} + +void World_Physics_RemoveFromEntity(world_t *world, prvm_edict_t *ed) +{ + edict_odefunc_t *f, *nf; + + // entity is not physics controlled, free any physics data + ed->priv.server->ode_physics = false; +#ifdef USEODE + if (ed->priv.server->ode_geom) + dGeomDestroy((dGeomID)ed->priv.server->ode_geom); + ed->priv.server->ode_geom = NULL; + if (ed->priv.server->ode_body) + { + dJointID j; + dBodyID b1, b2; + prvm_edict_t *ed2; + while(dBodyGetNumJoints((dBodyID)ed->priv.server->ode_body)) + { + j = dBodyGetJoint((dBodyID)ed->priv.server->ode_body, 0); + ed2 = (prvm_edict_t *) dJointGetData(j); + b1 = dJointGetBody(j, 0); + b2 = dJointGetBody(j, 1); + if(b1 == (dBodyID)ed->priv.server->ode_body) + { + b1 = 0; + ed2->priv.server->ode_joint_enemy = 0; + } + if(b2 == (dBodyID)ed->priv.server->ode_body) + { + b2 = 0; + ed2->priv.server->ode_joint_aiment = 0; + } + dJointAttach(j, b1, b2); + } + dBodyDestroy((dBodyID)ed->priv.server->ode_body); + } + ed->priv.server->ode_body = NULL; +#endif + if (ed->priv.server->ode_vertex3f) + Mem_Free(ed->priv.server->ode_vertex3f); + ed->priv.server->ode_vertex3f = NULL; + ed->priv.server->ode_numvertices = 0; + if (ed->priv.server->ode_element3i) + Mem_Free(ed->priv.server->ode_element3i); + ed->priv.server->ode_element3i = NULL; + ed->priv.server->ode_numtriangles = 0; + if(ed->priv.server->ode_massbuf) + Mem_Free(ed->priv.server->ode_massbuf); + ed->priv.server->ode_massbuf = NULL; + // clear functions stack + for(f = ed->priv.server->ode_func; f; f = nf) + { + nf = f->next; + Mem_Free(f); + } + ed->priv.server->ode_func = NULL; +} + +void World_Physics_ApplyCmd(prvm_edict_t *ed, edict_odefunc_t *f) +{ + dBodyID body = (dBodyID)ed->priv.server->ode_body; + +#ifdef USEODE + switch(f->type) + { + case ODEFUNC_ENABLE: + dBodyEnable(body); + break; + case ODEFUNC_DISABLE: + dBodyDisable(body); + break; + case ODEFUNC_RELFORCEATPOS: + dBodyEnable(body); + dBodyAddForceAtRelPos(body, f->v1[0], f->v1[1], f->v1[2], f->v2[0], f->v2[1], f->v2[2]); + break; + case ODEFUNC_RELTORQUE: + dBodyEnable(body); + dBodyAddRelTorque(body, f->v1[0], f->v1[1], f->v1[2]); + break; + default: + break; + } +#endif +} + +#ifdef USEODE +static void World_Physics_Frame_BodyToEntity(world_t *world, prvm_edict_t *ed) +{ + const dReal *avel; + const dReal *o; + const dReal *r; // for some reason dBodyGetRotation returns a [3][4] matrix + const dReal *vel; + dBodyID body = (dBodyID)ed->priv.server->ode_body; + int movetype; + matrix4x4_t bodymatrix; + matrix4x4_t entitymatrix; + vec3_t angles; + vec3_t avelocity; + vec3_t forward, left, up; + vec3_t origin; + vec3_t spinvelocity; + vec3_t velocity; + int jointtype; + if (!body) + return; + movetype = (int)PRVM_gameedictfloat(ed, movetype); + if (movetype != MOVETYPE_PHYSICS) + { + jointtype = (int)PRVM_gameedictfloat(ed, jointtype); + switch(jointtype) + { + // TODO feed back data from physics + case JOINTTYPE_POINT: + break; + case JOINTTYPE_HINGE: + break; + case JOINTTYPE_SLIDER: + break; + case JOINTTYPE_UNIVERSAL: + break; + case JOINTTYPE_HINGE2: + break; + case JOINTTYPE_FIXED: + break; + } + return; + } + // store the physics engine data into the entity + o = dBodyGetPosition(body); + r = dBodyGetRotation(body); + vel = dBodyGetLinearVel(body); + avel = dBodyGetAngularVel(body); + VectorCopy(o, origin); + forward[0] = r[0]; + forward[1] = r[4]; + forward[2] = r[8]; + left[0] = r[1]; + left[1] = r[5]; + left[2] = r[9]; + up[0] = r[2]; + up[1] = r[6]; + up[2] = r[10]; + VectorCopy(vel, velocity); + VectorCopy(avel, spinvelocity); + Matrix4x4_FromVectors(&bodymatrix, forward, left, up, origin); + Matrix4x4_Concat(&entitymatrix, &bodymatrix, &ed->priv.server->ode_offsetimatrix); + Matrix4x4_ToVectors(&entitymatrix, forward, left, up, origin); + + AnglesFromVectors(angles, forward, up, false); + VectorSet(avelocity, RAD2DEG(spinvelocity[PITCH]), RAD2DEG(spinvelocity[ROLL]), RAD2DEG(spinvelocity[YAW])); + + { + float pitchsign = 1; + if(!strcmp(prog->name, "server")) // FIXME some better way? + { + pitchsign = SV_GetPitchSign(ed); + } + else if(!strcmp(prog->name, "client")) + { + pitchsign = CL_GetPitchSign(ed); + } + angles[PITCH] *= pitchsign; + avelocity[PITCH] *= pitchsign; + } + + VectorCopy(origin, PRVM_gameedictvector(ed, origin)); + VectorCopy(velocity, PRVM_gameedictvector(ed, velocity)); + //VectorCopy(forward, PRVM_gameedictvector(ed, axis_forward)); + //VectorCopy(left, PRVM_gameedictvector(ed, axis_left)); + //VectorCopy(up, PRVM_gameedictvector(ed, axis_up)); + //VectorCopy(spinvelocity, PRVM_gameedictvector(ed, spinvelocity)); + VectorCopy(angles, PRVM_gameedictvector(ed, angles)); + VectorCopy(avelocity, PRVM_gameedictvector(ed, avelocity)); + + // values for BodyFromEntity to check if the qc modified anything later + VectorCopy(origin, ed->priv.server->ode_origin); + VectorCopy(velocity, ed->priv.server->ode_velocity); + VectorCopy(angles, ed->priv.server->ode_angles); + VectorCopy(avelocity, ed->priv.server->ode_avelocity); + ed->priv.server->ode_gravity = dBodyGetGravityMode(body) != 0; + + if(!strcmp(prog->name, "server")) // FIXME some better way? + { + SV_LinkEdict(ed); + SV_LinkEdict_TouchAreaGrid(ed); + } +} + +static void World_Physics_Frame_JointFromEntity(world_t *world, prvm_edict_t *ed) +{ + dJointID j = 0; + dBodyID b1 = 0; + dBodyID b2 = 0; + int movetype = 0; + int jointtype = 0; + int enemy = 0, aiment = 0; + vec3_t origin, velocity, angles, forward, left, up, movedir; + vec_t CFM, ERP, FMax, Stop, Vel; + VectorClear(origin); + VectorClear(velocity); + VectorClear(angles); + VectorClear(movedir); + movetype = (int)PRVM_gameedictfloat(ed, movetype); + jointtype = (int)PRVM_gameedictfloat(ed, jointtype); + enemy = PRVM_gameedictedict(ed, enemy); + aiment = PRVM_gameedictedict(ed, aiment); + VectorCopy(PRVM_gameedictvector(ed, origin), origin); + VectorCopy(PRVM_gameedictvector(ed, velocity), velocity); + VectorCopy(PRVM_gameedictvector(ed, angles), angles); + VectorCopy(PRVM_gameedictvector(ed, movedir), movedir); + if(movetype == MOVETYPE_PHYSICS) + jointtype = 0; // can't have both + if(enemy <= 0 || enemy >= prog->num_edicts || prog->edicts[enemy].priv.required->free || prog->edicts[enemy].priv.server->ode_body == 0) + enemy = 0; + if(aiment <= 0 || aiment >= prog->num_edicts || prog->edicts[aiment].priv.required->free || prog->edicts[aiment].priv.server->ode_body == 0) + aiment = 0; + // see http://www.ode.org/old_list_archives/2006-January/017614.html + // we want to set ERP? make it fps independent and work like a spring constant + // note: if movedir[2] is 0, it becomes ERP = 1, CFM = 1.0 / (H * K) + if(movedir[0] > 0 && movedir[1] > 0) + { + float K = movedir[0]; + float D = movedir[1]; + float R = 2.0 * D * sqrt(K); // we assume D is premultiplied by sqrt(sprungMass) + CFM = 1.0 / (world->physics.ode_step * K + R); // always > 0 + ERP = world->physics.ode_step * K * CFM; + Vel = 0; + FMax = 0; + Stop = movedir[2]; + } + else if(movedir[1] < 0) + { + CFM = 0; + ERP = 0; + Vel = movedir[0]; + FMax = -movedir[1]; // TODO do we need to multiply with world.physics.ode_step? + Stop = movedir[2] > 0 ? movedir[2] : dInfinity; + } + else // movedir[0] > 0, movedir[1] == 0 or movedir[0] < 0, movedir[1] >= 0 + { + CFM = 0; + ERP = 0; + Vel = 0; + FMax = 0; + Stop = dInfinity; + } + if(jointtype == ed->priv.server->ode_joint_type && VectorCompare(origin, ed->priv.server->ode_joint_origin) && VectorCompare(velocity, ed->priv.server->ode_joint_velocity) && VectorCompare(angles, ed->priv.server->ode_joint_angles) && enemy == ed->priv.server->ode_joint_enemy && aiment == ed->priv.server->ode_joint_aiment && VectorCompare(movedir, ed->priv.server->ode_joint_movedir)) + return; // nothing to do + AngleVectorsFLU(angles, forward, left, up); + switch(jointtype) + { + case JOINTTYPE_POINT: + j = dJointCreateBall((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_HINGE: + j = dJointCreateHinge((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_SLIDER: + j = dJointCreateSlider((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_UNIVERSAL: + j = dJointCreateUniversal((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_HINGE2: + j = dJointCreateHinge2((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_FIXED: + j = dJointCreateFixed((dWorldID)world->physics.ode_world, 0); + break; + case 0: + default: + // no joint + j = 0; + break; + } + if(ed->priv.server->ode_joint) + { + //Con_Printf("deleted old joint %i\n", (int) (ed - prog->edicts)); + dJointAttach((dJointID)ed->priv.server->ode_joint, 0, 0); + dJointDestroy((dJointID)ed->priv.server->ode_joint); + } + ed->priv.server->ode_joint = (void *) j; + ed->priv.server->ode_joint_type = jointtype; + ed->priv.server->ode_joint_enemy = enemy; + ed->priv.server->ode_joint_aiment = aiment; + VectorCopy(origin, ed->priv.server->ode_joint_origin); + VectorCopy(velocity, ed->priv.server->ode_joint_velocity); + VectorCopy(angles, ed->priv.server->ode_joint_angles); + VectorCopy(movedir, ed->priv.server->ode_joint_movedir); + if(j) + { + //Con_Printf("made new joint %i\n", (int) (ed - prog->edicts)); + dJointSetData(j, (void *) ed); + if(enemy) + b1 = (dBodyID)prog->edicts[enemy].priv.server->ode_body; + if(aiment) + b2 = (dBodyID)prog->edicts[aiment].priv.server->ode_body; + dJointAttach(j, b1, b2); + + switch(jointtype) + { + case JOINTTYPE_POINT: + dJointSetBallAnchor(j, origin[0], origin[1], origin[2]); + break; + case JOINTTYPE_HINGE: + dJointSetHingeAnchor(j, origin[0], origin[1], origin[2]); + dJointSetHingeAxis(j, forward[0], forward[1], forward[2]); + dJointSetHingeParam(j, dParamFMax, FMax); + dJointSetHingeParam(j, dParamHiStop, Stop); + dJointSetHingeParam(j, dParamLoStop, -Stop); + dJointSetHingeParam(j, dParamStopCFM, CFM); + dJointSetHingeParam(j, dParamStopERP, ERP); + dJointSetHingeParam(j, dParamVel, Vel); + break; + case JOINTTYPE_SLIDER: + dJointSetSliderAxis(j, forward[0], forward[1], forward[2]); + dJointSetSliderParam(j, dParamFMax, FMax); + dJointSetSliderParam(j, dParamHiStop, Stop); + dJointSetSliderParam(j, dParamLoStop, -Stop); + dJointSetSliderParam(j, dParamStopCFM, CFM); + dJointSetSliderParam(j, dParamStopERP, ERP); + dJointSetSliderParam(j, dParamVel, Vel); + break; + case JOINTTYPE_UNIVERSAL: + dJointSetUniversalAnchor(j, origin[0], origin[1], origin[2]); + dJointSetUniversalAxis1(j, forward[0], forward[1], forward[2]); + dJointSetUniversalAxis2(j, up[0], up[1], up[2]); + dJointSetUniversalParam(j, dParamFMax, FMax); + dJointSetUniversalParam(j, dParamHiStop, Stop); + dJointSetUniversalParam(j, dParamLoStop, -Stop); + dJointSetUniversalParam(j, dParamStopCFM, CFM); + dJointSetUniversalParam(j, dParamStopERP, ERP); + dJointSetUniversalParam(j, dParamVel, Vel); + dJointSetUniversalParam(j, dParamFMax2, FMax); + dJointSetUniversalParam(j, dParamHiStop2, Stop); + dJointSetUniversalParam(j, dParamLoStop2, -Stop); + dJointSetUniversalParam(j, dParamStopCFM2, CFM); + dJointSetUniversalParam(j, dParamStopERP2, ERP); + dJointSetUniversalParam(j, dParamVel2, Vel); + break; + case JOINTTYPE_HINGE2: + dJointSetHinge2Anchor(j, origin[0], origin[1], origin[2]); + dJointSetHinge2Axis1(j, forward[0], forward[1], forward[2]); + dJointSetHinge2Axis2(j, velocity[0], velocity[1], velocity[2]); + dJointSetHinge2Param(j, dParamFMax, FMax); + dJointSetHinge2Param(j, dParamHiStop, Stop); + dJointSetHinge2Param(j, dParamLoStop, -Stop); + dJointSetHinge2Param(j, dParamStopCFM, CFM); + dJointSetHinge2Param(j, dParamStopERP, ERP); + dJointSetHinge2Param(j, dParamVel, Vel); + dJointSetHinge2Param(j, dParamFMax2, FMax); + dJointSetHinge2Param(j, dParamHiStop2, Stop); + dJointSetHinge2Param(j, dParamLoStop2, -Stop); + dJointSetHinge2Param(j, dParamStopCFM2, CFM); + dJointSetHinge2Param(j, dParamStopERP2, ERP); + dJointSetHinge2Param(j, dParamVel2, Vel); + break; + case JOINTTYPE_FIXED: + break; + case 0: + default: + Sys_Error("what? but above the joint was valid...\n"); + break; + } +#undef SETPARAMS + + } +} + +static void World_Physics_Frame_BodyFromEntity(world_t *world, prvm_edict_t *ed) +{ + const float *iv; + const int *ie; + dBodyID body = (dBodyID)ed->priv.server->ode_body; + dMass mass; + dReal test; + const dReal *ovelocity, *ospinvelocity; + void *dataID; + dVector3 capsulerot[3]; + dp_model_t *model; + float *ov; + int *oe; + int axisindex; + int modelindex = 0; + int movetype = MOVETYPE_NONE; + int numtriangles; + int numvertices; + int solid = SOLID_NOT; + int triangleindex; + int vertexindex; + mempool_t *mempool; + qboolean modified = false; + vec3_t angles; + vec3_t avelocity; + vec3_t entmaxs; + vec3_t entmins; + vec3_t forward; + vec3_t geomcenter; + vec3_t geomsize; + vec3_t left; + vec3_t origin; + vec3_t spinvelocity; + vec3_t up; + vec3_t velocity; + vec_t f; + vec_t length; + vec_t massval = 1.0f; + vec_t movelimit; + vec_t radius; + vec_t scale = 1.0f; + vec_t spinlimit; + qboolean gravity; + edict_odefunc_t *func, *nextf; + +#ifdef ODE_DYNAMIC + if (!ode_dll) + return; +#endif + VectorClear(entmins); + VectorClear(entmaxs); + solid = (int)PRVM_gameedictfloat(ed, solid); + movetype = (int)PRVM_gameedictfloat(ed, movetype); + scale = PRVM_gameedictfloat(ed, scale);if (!scale) scale = 1.0f; + modelindex = 0; + if (world == &sv.world) + mempool = sv_mempool; + else if (world == &cl.world) + mempool = cls.levelmempool; + else + mempool = NULL; + model = NULL; + switch(solid) + { + case SOLID_BSP: + modelindex = (int)PRVM_gameedictfloat(ed, modelindex); + if (world == &sv.world) + model = SV_GetModelByIndex(modelindex); + else if (world == &cl.world) + model = CL_GetModelByIndex(modelindex); + else + model = NULL; + if (model) + { + VectorScale(model->normalmins, scale, entmins); + VectorScale(model->normalmaxs, scale, entmaxs); + massval = PRVM_gameedictfloat(ed, mass); + } + else + { + modelindex = 0; + massval = 1.0f; + } + break; + case SOLID_BBOX: + //case SOLID_SLIDEBOX: + case SOLID_CORPSE: + case SOLID_PHYSICS_BOX: + case SOLID_PHYSICS_SPHERE: + case SOLID_PHYSICS_CAPSULE: + VectorCopy(PRVM_gameedictvector(ed, mins), entmins); + VectorCopy(PRVM_gameedictvector(ed, maxs), entmaxs); + massval = PRVM_gameedictfloat(ed, mass); + break; + default: + if (ed->priv.server->ode_physics) + World_Physics_RemoveFromEntity(world, ed); + return; + } + + VectorSubtract(entmaxs, entmins, geomsize); + if (VectorLength2(geomsize) == 0) + { + // we don't allow point-size physics objects... + if (ed->priv.server->ode_physics) + World_Physics_RemoveFromEntity(world, ed); + return; + } + + if (movetype != MOVETYPE_PHYSICS) + massval = 1.0f; + + // check if we need to create or replace the geom + if (!ed->priv.server->ode_physics + || !VectorCompare(ed->priv.server->ode_mins, entmins) + || !VectorCompare(ed->priv.server->ode_maxs, entmaxs) + || ed->priv.server->ode_mass != massval + || ed->priv.server->ode_modelindex != modelindex) + { + modified = true; + World_Physics_RemoveFromEntity(world, ed); + ed->priv.server->ode_physics = true; + VectorCopy(entmins, ed->priv.server->ode_mins); + VectorCopy(entmaxs, ed->priv.server->ode_maxs); + ed->priv.server->ode_mass = massval; + ed->priv.server->ode_modelindex = modelindex; + VectorMAM(0.5f, entmins, 0.5f, entmaxs, geomcenter); + ed->priv.server->ode_movelimit = min(geomsize[0], min(geomsize[1], geomsize[2])); + + if (massval * geomsize[0] * geomsize[1] * geomsize[2] == 0) + { + if (movetype == MOVETYPE_PHYSICS) + Con_Printf("entity %i (classname %s) .mass * .size_x * .size_y * .size_z == 0\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(PRVM_gameedictstring(ed, classname))); + massval = 1.0f; + VectorSet(geomsize, 1.0f, 1.0f, 1.0f); + } + + switch(solid) + { + case SOLID_BSP: + ed->priv.server->ode_offsetmatrix = identitymatrix; + if (!model) + { + Con_Printf("entity %i (classname %s) has no model\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(PRVM_gameedictstring(ed, classname))); + goto treatasbox; + } + // add an optimized mesh to the model containing only the SUPERCONTENTS_SOLID surfaces + if (!model->brush.collisionmesh) + Mod_CreateCollisionMesh(model); + if (!model->brush.collisionmesh || !model->brush.collisionmesh->numtriangles) + { + Con_Printf("entity %i (classname %s) has no geometry\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(PRVM_gameedictstring(ed, classname))); + goto treatasbox; + } + // ODE requires persistent mesh storage, so we need to copy out + // the data from the model because renderer restarts could free it + // during the game, additionally we need to flip the triangles... + // note: ODE does preprocessing of the mesh for culling, removing + // concave edges, etc., so this is not a lightweight operation + ed->priv.server->ode_numvertices = numvertices = model->brush.collisionmesh->numverts; + ed->priv.server->ode_vertex3f = (float *)Mem_Alloc(mempool, numvertices * sizeof(float[3])); + for (vertexindex = 0, ov = ed->priv.server->ode_vertex3f, iv = model->brush.collisionmesh->vertex3f;vertexindex < numvertices;vertexindex++, ov += 3, iv += 3) + { + ov[0] = iv[0] - geomcenter[0]; + ov[1] = iv[1] - geomcenter[1]; + ov[2] = iv[2] - geomcenter[2]; + } + ed->priv.server->ode_numtriangles = numtriangles = model->brush.collisionmesh->numtriangles; + ed->priv.server->ode_element3i = (int *)Mem_Alloc(mempool, numtriangles * sizeof(int[3])); + //memcpy(ed->priv.server->ode_element3i, model->brush.collisionmesh->element3i, ed->priv.server->ode_numtriangles * sizeof(int[3])); + for (triangleindex = 0, oe = ed->priv.server->ode_element3i, ie = model->brush.collisionmesh->element3i;triangleindex < numtriangles;triangleindex++, oe += 3, ie += 3) + { + oe[0] = ie[2]; + oe[1] = ie[1]; + oe[2] = ie[0]; + } + Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); + // now create the geom + dataID = dGeomTriMeshDataCreate(); + dGeomTriMeshDataBuildSingle((dTriMeshDataID)dataID, (void*)ed->priv.server->ode_vertex3f, sizeof(float[3]), ed->priv.server->ode_numvertices, ed->priv.server->ode_element3i, ed->priv.server->ode_numtriangles*3, sizeof(int[3])); + ed->priv.server->ode_geom = (void *)dCreateTriMesh((dSpaceID)world->physics.ode_space, (dTriMeshDataID)dataID, NULL, NULL, NULL); + dMassSetBoxTotal(&mass, massval, geomsize[0], geomsize[1], geomsize[2]); + break; + case SOLID_BBOX: + case SOLID_SLIDEBOX: + case SOLID_CORPSE: + case SOLID_PHYSICS_BOX: +treatasbox: + Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); + ed->priv.server->ode_geom = (void *)dCreateBox((dSpaceID)world->physics.ode_space, geomsize[0], geomsize[1], geomsize[2]); + dMassSetBoxTotal(&mass, massval, geomsize[0], geomsize[1], geomsize[2]); + break; + case SOLID_PHYSICS_SPHERE: + Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); + ed->priv.server->ode_geom = (void *)dCreateSphere((dSpaceID)world->physics.ode_space, geomsize[0] * 0.5f); + dMassSetSphereTotal(&mass, massval, geomsize[0] * 0.5f); + break; + case SOLID_PHYSICS_CAPSULE: + axisindex = 0; + if (geomsize[axisindex] < geomsize[1]) + axisindex = 1; + if (geomsize[axisindex] < geomsize[2]) + axisindex = 2; + // the qc gives us 3 axis radius, the longest axis is the capsule + // axis, since ODE doesn't like this idea we have to create a + // capsule which uses the standard orientation, and apply a + // transform to it + memset(capsulerot, 0, sizeof(capsulerot)); + if (axisindex == 0) + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); + else if (axisindex == 1) + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); + else + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); + radius = geomsize[!axisindex] * 0.5f; // any other axis is the radius + length = geomsize[axisindex] - radius*2; + // because we want to support more than one axisindex, we have to + // create a transform, and turn on its cleanup setting (which will + // cause the child to be destroyed when it is destroyed) + ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCapsuleTotal(&mass, massval, axisindex+1, radius, length); + break; + default: + Sys_Error("World_Physics_BodyFromEntity: unrecognized solid value %i was accepted by filter\n", solid); + // this goto only exists to prevent warnings from the compiler + // about uninitialized variables (mass), while allowing it to + // catch legitimate uninitialized variable warnings + goto treatasbox; + } + Matrix4x4_Invert_Simple(&ed->priv.server->ode_offsetimatrix, &ed->priv.server->ode_offsetmatrix); + ed->priv.server->ode_massbuf = Mem_Alloc(mempool, sizeof(mass)); + memcpy(ed->priv.server->ode_massbuf, &mass, sizeof(dMass)); + } + + if(ed->priv.server->ode_geom) + dGeomSetData((dGeomID)ed->priv.server->ode_geom, (void*)ed); + if (movetype == MOVETYPE_PHYSICS && ed->priv.server->ode_geom) + { + if (ed->priv.server->ode_body == NULL) + { + ed->priv.server->ode_body = (void *)(body = dBodyCreate((dWorldID)world->physics.ode_world)); + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); + dBodySetData(body, (void*)ed); + dBodySetMass(body, (dMass *) ed->priv.server->ode_massbuf); + modified = true; + } + } + else + { + if (ed->priv.server->ode_body != NULL) + { + if(ed->priv.server->ode_geom) + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); + dBodyDestroy((dBodyID) ed->priv.server->ode_body); + ed->priv.server->ode_body = NULL; + modified = true; + } + } + + // get current data from entity + VectorClear(origin); + VectorClear(velocity); + //VectorClear(forward); + //VectorClear(left); + //VectorClear(up); + //VectorClear(spinvelocity); + VectorClear(angles); + VectorClear(avelocity); + gravity = true; + VectorCopy(PRVM_gameedictvector(ed, origin), origin); + VectorCopy(PRVM_gameedictvector(ed, velocity), velocity); + //VectorCopy(PRVM_gameedictvector(ed, axis_forward), forward); + //VectorCopy(PRVM_gameedictvector(ed, axis_left), left); + //VectorCopy(PRVM_gameedictvector(ed, axis_up), up); + //VectorCopy(PRVM_gameedictvector(ed, spinvelocity), spinvelocity); + VectorCopy(PRVM_gameedictvector(ed, angles), angles); + VectorCopy(PRVM_gameedictvector(ed, avelocity), avelocity); + if (PRVM_gameedictfloat(ed, gravity) != 0.0f && PRVM_gameedictfloat(ed, gravity) < 0.5f) gravity = false; + if (ed == prog->edicts) + gravity = false; + + // compatibility for legacy entities + //if (!VectorLength2(forward) || solid == SOLID_BSP) + { + float pitchsign = 1; + vec3_t qangles, qavelocity; + VectorCopy(angles, qangles); + VectorCopy(avelocity, qavelocity); + + if(!strcmp(prog->name, "server")) // FIXME some better way? + { + pitchsign = SV_GetPitchSign(ed); + } + else if(!strcmp(prog->name, "client")) + { + pitchsign = CL_GetPitchSign(ed); + } + qangles[PITCH] *= pitchsign; + qavelocity[PITCH] *= pitchsign; + + AngleVectorsFLU(qangles, forward, left, up); + // convert single-axis rotations in avelocity to spinvelocity + // FIXME: untested math - check signs + VectorSet(spinvelocity, DEG2RAD(qavelocity[PITCH]), DEG2RAD(qavelocity[ROLL]), DEG2RAD(qavelocity[YAW])); + } + + // compatibility for legacy entities + switch (solid) + { + case SOLID_BBOX: + case SOLID_SLIDEBOX: + case SOLID_CORPSE: + VectorSet(forward, 1, 0, 0); + VectorSet(left, 0, 1, 0); + VectorSet(up, 0, 0, 1); + VectorSet(spinvelocity, 0, 0, 0); + break; + } + + + // we must prevent NANs... + if (physics_ode_trick_fixnan.integer) + { + test = VectorLength2(origin) + VectorLength2(forward) + VectorLength2(left) + VectorLength2(up) + VectorLength2(velocity) + VectorLength2(spinvelocity); + if (IS_NAN(test)) + { + modified = true; + //Con_Printf("Fixing NAN values on entity %i : .classname = \"%s\" .origin = '%f %f %f' .velocity = '%f %f %f' .axis_forward = '%f %f %f' .axis_left = '%f %f %f' .axis_up = %f %f %f' .spinvelocity = '%f %f %f'\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(PRVM_gameedictstring(ed, classname)), origin[0], origin[1], origin[2], velocity[0], velocity[1], velocity[2], forward[0], forward[1], forward[2], left[0], left[1], left[2], up[0], up[1], up[2], spinvelocity[0], spinvelocity[1], spinvelocity[2]); + if (physics_ode_trick_fixnan.integer >= 2) + Con_Printf("Fixing NAN values on entity %i : .classname = \"%s\" .origin = '%f %f %f' .velocity = '%f %f %f' .angles = '%f %f %f' .avelocity = '%f %f %f'\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(PRVM_gameedictstring(ed, classname)), origin[0], origin[1], origin[2], velocity[0], velocity[1], velocity[2], angles[0], angles[1], angles[2], avelocity[0], avelocity[1], avelocity[2]); + test = VectorLength2(origin); + if (IS_NAN(test)) + VectorClear(origin); + test = VectorLength2(forward) * VectorLength2(left) * VectorLength2(up); + if (IS_NAN(test)) + { + VectorSet(angles, 0, 0, 0); + VectorSet(forward, 1, 0, 0); + VectorSet(left, 0, 1, 0); + VectorSet(up, 0, 0, 1); + } + test = VectorLength2(velocity); + if (IS_NAN(test)) + VectorClear(velocity); + test = VectorLength2(spinvelocity); + if (IS_NAN(test)) + { + VectorClear(avelocity); + VectorClear(spinvelocity); + } + } + } + + // check if the qc edited any position data + if (!VectorCompare(origin, ed->priv.server->ode_origin) + || !VectorCompare(velocity, ed->priv.server->ode_velocity) + || !VectorCompare(angles, ed->priv.server->ode_angles) + || !VectorCompare(avelocity, ed->priv.server->ode_avelocity) + || gravity != ed->priv.server->ode_gravity) + modified = true; + + // store the qc values into the physics engine + body = (dBodyID)ed->priv.server->ode_body; + if (modified && ed->priv.server->ode_geom) + { + dVector3 r[3]; + matrix4x4_t entitymatrix; + matrix4x4_t bodymatrix; + +#if 0 + Con_Printf("entity %i got changed by QC\n", (int) (ed - prog->edicts)); + if(!VectorCompare(origin, ed->priv.server->ode_origin)) + Con_Printf(" origin: %f %f %f -> %f %f %f\n", ed->priv.server->ode_origin[0], ed->priv.server->ode_origin[1], ed->priv.server->ode_origin[2], origin[0], origin[1], origin[2]); + if(!VectorCompare(velocity, ed->priv.server->ode_velocity)) + Con_Printf(" velocity: %f %f %f -> %f %f %f\n", ed->priv.server->ode_velocity[0], ed->priv.server->ode_velocity[1], ed->priv.server->ode_velocity[2], velocity[0], velocity[1], velocity[2]); + if(!VectorCompare(angles, ed->priv.server->ode_angles)) + Con_Printf(" angles: %f %f %f -> %f %f %f\n", ed->priv.server->ode_angles[0], ed->priv.server->ode_angles[1], ed->priv.server->ode_angles[2], angles[0], angles[1], angles[2]); + if(!VectorCompare(avelocity, ed->priv.server->ode_avelocity)) + Con_Printf(" avelocity: %f %f %f -> %f %f %f\n", ed->priv.server->ode_avelocity[0], ed->priv.server->ode_avelocity[1], ed->priv.server->ode_avelocity[2], avelocity[0], avelocity[1], avelocity[2]); + if(gravity != ed->priv.server->ode_gravity) + Con_Printf(" gravity: %i -> %i\n", ed->priv.server->ode_gravity, gravity); +#endif + + // values for BodyFromEntity to check if the qc modified anything later + VectorCopy(origin, ed->priv.server->ode_origin); + VectorCopy(velocity, ed->priv.server->ode_velocity); + VectorCopy(angles, ed->priv.server->ode_angles); + VectorCopy(avelocity, ed->priv.server->ode_avelocity); + ed->priv.server->ode_gravity = gravity; + + Matrix4x4_FromVectors(&entitymatrix, forward, left, up, origin); + Matrix4x4_Concat(&bodymatrix, &entitymatrix, &ed->priv.server->ode_offsetmatrix); + Matrix4x4_ToVectors(&bodymatrix, forward, left, up, origin); + r[0][0] = forward[0]; + r[1][0] = forward[1]; + r[2][0] = forward[2]; + r[0][1] = left[0]; + r[1][1] = left[1]; + r[2][1] = left[2]; + r[0][2] = up[0]; + r[1][2] = up[1]; + r[2][2] = up[2]; + if(body) + { + if(movetype == MOVETYPE_PHYSICS) + { + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); + dBodySetPosition(body, origin[0], origin[1], origin[2]); + dBodySetRotation(body, r[0]); + dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); + dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); + dBodySetGravityMode(body, gravity); + } + else + { + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); + dBodySetPosition(body, origin[0], origin[1], origin[2]); + dBodySetRotation(body, r[0]); + dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); + dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); + dBodySetGravityMode(body, gravity); + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); + } + } + else + { + // no body... then let's adjust the parameters of the geom directly + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); // just in case we previously HAD a body (which should never happen) + dGeomSetPosition((dGeomID)ed->priv.server->ode_geom, origin[0], origin[1], origin[2]); + dGeomSetRotation((dGeomID)ed->priv.server->ode_geom, r[0]); + } + } + + if(body) + { + + // limit movement speed to prevent missed collisions at high speed + ovelocity = dBodyGetLinearVel(body); + ospinvelocity = dBodyGetAngularVel(body); + movelimit = ed->priv.server->ode_movelimit * world->physics.ode_movelimit; + test = VectorLength2(ovelocity); + if (test > movelimit*movelimit) + { + // scale down linear velocity to the movelimit + // scale down angular velocity the same amount for consistency + f = movelimit / sqrt(test); + VectorScale(ovelocity, f, velocity); + VectorScale(ospinvelocity, f, spinvelocity); + dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); + dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); + } + + // make sure the angular velocity is not exploding + spinlimit = physics_ode_spinlimit.value; + test = VectorLength2(ospinvelocity); + if (test > spinlimit) + { + dBodySetAngularVel(body, 0, 0, 0); + } + + // apply functions and clear stack + for(func = ed->priv.server->ode_func; func; func = nextf) + { + nextf = func->next; + World_Physics_ApplyCmd(ed, func); + Mem_Free(func); + } + ed->priv.server->ode_func = NULL; + } +} + +#define MAX_CONTACTS 16 +static void nearCallback (void *data, dGeomID o1, dGeomID o2) +{ + world_t *world = (world_t *)data; + dContact contact[MAX_CONTACTS]; // max contacts per collision pair + dBodyID b1; + dBodyID b2; + dJointID c; + int i; + int numcontacts; + float bouncefactor1 = 0.0f; + float bouncestop1 = 60.0f / 800.0f; + float bouncefactor2 = 0.0f; + float bouncestop2 = 60.0f / 800.0f; + dVector3 grav; + prvm_edict_t *ed1, *ed2; + + if (dGeomIsSpace(o1) || dGeomIsSpace(o2)) + { + // colliding a space with something + dSpaceCollide2(o1, o2, data, &nearCallback); + // Note we do not want to test intersections within a space, + // only between spaces. + //if (dGeomIsSpace(o1)) dSpaceCollide(o1, data, &nearCallback); + //if (dGeomIsSpace(o2)) dSpaceCollide(o2, data, &nearCallback); + return; + } + + b1 = dGeomGetBody(o1); + b2 = dGeomGetBody(o2); + + // at least one object has to be using MOVETYPE_PHYSICS or we just don't care + if (!b1 && !b2) + return; + + // exit without doing anything if the two bodies are connected by a joint + if (b1 && b2 && dAreConnectedExcluding(b1, b2, dJointTypeContact)) + return; + + ed1 = (prvm_edict_t *) dGeomGetData(o1); + if(ed1 && ed1->priv.server->free) + ed1 = NULL; + if(ed1) + { + bouncefactor1 = PRVM_gameedictfloat(ed1, bouncefactor); + bouncestop1 = PRVM_gameedictfloat(ed1, bouncestop); + if (!bouncestop1) + bouncestop1 = 60.0f / 800.0f; + } + + ed2 = (prvm_edict_t *) dGeomGetData(o2); + if(ed2 && ed2->priv.server->free) + ed2 = NULL; + if(ed2) + { + bouncefactor2 = PRVM_gameedictfloat(ed2, bouncefactor); + bouncestop2 = PRVM_gameedictfloat(ed2, bouncestop); + if (!bouncestop2) + bouncestop2 = 60.0f / 800.0f; + } + + if(!strcmp(prog->name, "server")) + { + if(ed1 && PRVM_serveredictfunction(ed1, touch)) + { + SV_LinkEdict_TouchAreaGrid_Call(ed1, ed2 ? ed2 : prog->edicts); + } + if(ed2 && PRVM_serveredictfunction(ed2, touch)) + { + SV_LinkEdict_TouchAreaGrid_Call(ed2, ed1 ? ed1 : prog->edicts); + } + } + + // merge bounce factors and bounce stop + if(bouncefactor2 > 0) + { + if(bouncefactor1 > 0) + { + // TODO possibly better logic to merge bounce factor data? + if(bouncestop2 < bouncestop1) + bouncestop1 = bouncestop2; + if(bouncefactor2 > bouncefactor1) + bouncefactor1 = bouncefactor2; + } + else + { + bouncestop1 = bouncestop2; + bouncefactor1 = bouncefactor2; + } + } + dWorldGetGravity((dWorldID)world->physics.ode_world, grav); + bouncestop1 *= fabs(grav[2]); + + // generate contact points between the two non-space geoms + numcontacts = dCollide(o1, o2, MAX_CONTACTS, &(contact[0].geom), sizeof(contact[0])); + // add these contact points to the simulation + for (i = 0;i < numcontacts;i++) + { + contact[i].surface.mode = (physics_ode_contact_mu.value != -1 ? dContactApprox1 : 0) | (physics_ode_contact_erp.value != -1 ? dContactSoftERP : 0) | (physics_ode_contact_cfm.value != -1 ? dContactSoftCFM : 0) | (bouncefactor1 > 0 ? dContactBounce : 0); + contact[i].surface.mu = physics_ode_contact_mu.value; + contact[i].surface.soft_erp = physics_ode_contact_erp.value; + contact[i].surface.soft_cfm = physics_ode_contact_cfm.value; + contact[i].surface.bounce = bouncefactor1; + contact[i].surface.bounce_vel = bouncestop1; + c = dJointCreateContact((dWorldID)world->physics.ode_world, (dJointGroupID)world->physics.ode_contactgroup, contact + i); + dJointAttach(c, b1, b2); + } +} +#endif + +void World_Physics_Frame(world_t *world, double frametime, double gravity) +{ + double tdelta, tdelta2, tdelta3, simulationtime, collisiontime; + + tdelta = Sys_DoubleTime(); +#ifdef USEODE + if (world->physics.ode && physics_ode.integer) + { + int i; + prvm_edict_t *ed; + + world->physics.ode_iterations = bound(1, physics_ode_iterationsperframe.integer, 1000); + if (physics_ode_constantstep.integer) + world->physics.ode_step = sys_ticrate.value / world->physics.ode_iterations; + else + world->physics.ode_step = frametime / world->physics.ode_iterations; + world->physics.ode_movelimit = physics_ode_movelimit.value / world->physics.ode_step; + World_Physics_UpdateODE(world); + + // copy physics properties from entities to physics engine + if (prog) + { + for (i = 0, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) + if (!prog->edicts[i].priv.required->free) + World_Physics_Frame_BodyFromEntity(world, ed); + // oh, and it must be called after all bodies were created + for (i = 0, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) + if (!prog->edicts[i].priv.required->free) + World_Physics_Frame_JointFromEntity(world, ed); + } + + tdelta2 = Sys_DoubleTime(); + collisiontime = 0; + for (i = 0;i < world->physics.ode_iterations;i++) + { + // set the gravity + dWorldSetGravity((dWorldID)world->physics.ode_world, 0, 0, -gravity); + // set the tolerance for closeness of objects + dWorldSetContactSurfaceLayer((dWorldID)world->physics.ode_world, max(0, physics_ode_contactsurfacelayer.value)); + + // run collisions for the current world state, creating JointGroup + tdelta3 = Sys_DoubleTime(); + dSpaceCollide((dSpaceID)world->physics.ode_space, (void *)world, nearCallback); + collisiontime += (Sys_DoubleTime() - tdelta3)*10000; + + // run physics (move objects, calculate new velocities) + if (physics_ode_worldstep.integer == 2) + { + dWorldSetQuickStepNumIterations((dWorldID)world->physics.ode_world, bound(1, physics_ode_worldstep_iterations.integer, 200)); + dWorldQuickStep((dWorldID)world->physics.ode_world, world->physics.ode_step); + } +#ifdef ODE_USE_STEPFAST + else if (physics_ode_worldstep.integer == 1) + dWorldStepFast1((dWorldID)world->physics.ode_world, world->physics.ode_step, bound(1, physics_ode_worldstep_iterations.integer, 200)); +#endif + else + dWorldStep((dWorldID)world->physics.ode_world, world->physics.ode_step); + + // clear the JointGroup now that we're done with it + dJointGroupEmpty((dJointGroupID)world->physics.ode_contactgroup); + } + simulationtime = (Sys_DoubleTime() - tdelta2)*10000; + + // copy physics properties from physics engine to entities and do some stats + if (prog) + { + for (i = 1, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) + if (!prog->edicts[i].priv.required->free) + World_Physics_Frame_BodyToEntity(world, ed); + + // print stats + if (physics_ode_printstats.integer) + { + dBodyID body; + + world->physics.ode_numobjects = 0; + world->physics.ode_activeovjects = 0; + for (i = 1, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) + { + if (prog->edicts[i].priv.required->free) + continue; + body = (dBodyID)prog->edicts[i].priv.server->ode_body; + if (!body) + continue; + world->physics.ode_numobjects++; + if (dBodyIsEnabled(body)) + world->physics.ode_activeovjects++; + } + Con_Printf("ODE Stats(%s): %3.01f (%3.01f collision) %3.01f total : %i objects %i active %i disabled\n", prog->name, simulationtime, collisiontime, (Sys_DoubleTime() - tdelta)*10000, world->physics.ode_numobjects, world->physics.ode_activeovjects, (world->physics.ode_numobjects - world->physics.ode_activeovjects)); + } + } + } +#endif +} diff --git a/misc/source/darkplaces-src/world.h b/misc/source/darkplaces-src/world.h new file mode 100644 index 00000000..334420fb --- /dev/null +++ b/misc/source/darkplaces-src/world.h @@ -0,0 +1,132 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// world.h + +#ifndef WORLD_H +#define WORLD_H + +#include "collision.h" + +#define MOVE_NORMAL 0 +#define MOVE_NOMONSTERS 1 +#define MOVE_MISSILE 2 +#define MOVE_WORLDONLY 3 +#define MOVE_HITMODEL 4 + +#define AREA_GRID 128 +#define AREA_GRIDNODES (AREA_GRID * AREA_GRID) + +typedef struct link_s +{ + int entitynumber; + struct link_s *prev, *next; +} link_t; + +typedef struct world_physics_s +{ + // for ODE physics engine + qboolean ode; // if true then ode is activated + void *ode_world; + void *ode_space; + void *ode_contactgroup; + // number of constraint solver iterations to use (for dWorldStepFast) + int ode_iterations; + // actual step (server frametime / ode_iterations) + vec_t ode_step; + // stats + int ode_numobjects; // total objects cound + int ode_activeovjects; // active objects count + // max velocity for a 1-unit radius object at current step to prevent + // missed collisions + vec_t ode_movelimit; +} +world_physics_t; + +typedef struct world_s +{ + // convenient fields + char filename[MAX_QPATH]; + vec3_t mins; + vec3_t maxs; + + int areagrid_stats_calls; + int areagrid_stats_nodechecks; + int areagrid_stats_entitychecks; + + link_t areagrid[AREA_GRIDNODES]; + link_t areagrid_outside; + vec3_t areagrid_bias; + vec3_t areagrid_scale; + vec3_t areagrid_mins; + vec3_t areagrid_maxs; + vec3_t areagrid_size; + int areagrid_marknumber; + + // if the QC uses a physics engine, the data for it is here + world_physics_t physics; +} +world_t; + +struct prvm_edict_s; + +// cyclic doubly-linked list functions +void World_ClearLink(link_t *l); +void World_RemoveLink(link_t *l); +void World_InsertLinkBefore(link_t *l, link_t *before, int entitynumber); + +void World_Init(void); +void World_Shutdown(void); + +/// called after the world model has been loaded, before linking any entities +void World_SetSize(world_t *world, const char *filename, const vec3_t mins, const vec3_t maxs); +/// unlinks all entities (used before reallocation of edicts) +void World_UnlinkAll(world_t *world); + +void World_PrintAreaStats(world_t *world, const char *worldname); + +/// call before removing an entity, and before trying to move one, +/// so it doesn't clip against itself +void World_UnlinkEdict(struct prvm_edict_s *ent); + +/// Needs to be called any time an entity changes origin, mins, maxs +void World_LinkEdict(world_t *world, struct prvm_edict_s *ent, const vec3_t mins, const vec3_t maxs); + +/// \returns list of entities touching a box +int World_EntitiesInBox(world_t *world, const vec3_t mins, const vec3_t maxs, int maxlist, struct prvm_edict_s **list); + +void World_Start(world_t *world); +void World_End(world_t *world); + +// update physics +// this is called by SV_Physics +void World_Physics_Frame(world_t *world, double frametime, double gravity); + +// change physics properties of entity +struct prvm_edict_s; +struct edict_odefunc_s; +//void World_Physics_ApplyCmd(prvm_edict_s *ed, edict_odefunc_s *f); + +// remove physics data from entity +// this is called by entity removal +void World_Physics_RemoveFromEntity(world_t *world, struct prvm_edict_s *ed); +void World_Physics_RemoveJointFromEntity(world_t *world, struct prvm_edict_s *ed); + +#endif + diff --git a/misc/source/darkplaces-src/zone.c b/misc/source/darkplaces-src/zone.c new file mode 100644 index 00000000..585c1039 --- /dev/null +++ b/misc/source/darkplaces-src/zone.c @@ -0,0 +1,949 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// Z_zone.c + +#include "quakedef.h" + +#ifdef WIN32 +#include +#include +#else +#include +#endif + +#ifdef _MSC_VER +#include +#else +#include +#endif +#define MEMHEADER_SENTINEL_FOR_ADDRESS(p) ((sentinel_seed ^ (unsigned int) (uintptr_t) (p)) + sentinel_seed) +unsigned int sentinel_seed; + +qboolean mem_bigendian = false; + +// LordHavoc: enables our own low-level allocator (instead of malloc) +#define MEMCLUMPING 0 +#define MEMCLUMPING_FREECLUMPS 0 + +#if MEMCLUMPING +// smallest unit we care about is this many bytes +#define MEMUNIT 128 +// try to do 32MB clumps, but overhead eats into this +#define MEMWANTCLUMPSIZE (1<<27) +// give malloc padding so we can't waste most of a page at the end +#define MEMCLUMPSIZE (MEMWANTCLUMPSIZE - MEMWANTCLUMPSIZE/MEMUNIT/32 - 128) +#define MEMBITS (MEMCLUMPSIZE / MEMUNIT) +#define MEMBITINTS (MEMBITS / 32) + +typedef struct memclump_s +{ + // contents of the clump + unsigned char block[MEMCLUMPSIZE]; + // should always be MEMCLUMP_SENTINEL + unsigned int sentinel1; + // if a bit is on, it means that the MEMUNIT bytes it represents are + // allocated, otherwise free + unsigned int bits[MEMBITINTS]; + // should always be MEMCLUMP_SENTINEL + unsigned int sentinel2; + // if this drops to 0, the clump is freed + size_t blocksinuse; + // largest block of memory available (this is reset to an optimistic + // number when anything is freed, and updated when alloc fails the clump) + size_t largestavailable; + // next clump in the chain + struct memclump_s *chain; +} +memclump_t; + +#if MEMCLUMPING == 2 +static memclump_t masterclump; +#endif +static memclump_t *clumpchain = NULL; +#endif + + +cvar_t developer_memory = {0, "developer_memory", "0", "prints debugging information about memory allocations"}; +cvar_t developer_memorydebug = {0, "developer_memorydebug", "0", "enables memory corruption checks (very slow)"}; +cvar_t sys_memsize_physical = {CVAR_READONLY, "sys_memsize_physical", "", "physical memory size in MB (or empty if unknown)"}; +cvar_t sys_memsize_virtual = {CVAR_READONLY, "sys_memsize_virtual", "", "virtual memory size in MB (or empty if unknown)"}; + +static mempool_t *poolchain = NULL; + +void Mem_PrintStats(void); +void Mem_PrintList(size_t minallocationsize); + +#if MEMCLUMPING != 2 +// some platforms have a malloc that returns NULL but succeeds later +// (Windows growing its swapfile for example) +static void *attempt_malloc(size_t size) +{ + void *base; + // try for half a second or so + unsigned int attempts = 500; + while (attempts--) + { + base = (void *)malloc(size); + if (base) + return base; + Sys_Sleep(1000); + } + return NULL; +} +#endif + +#if MEMCLUMPING +static memclump_t *Clump_NewClump(void) +{ + memclump_t **clumpchainpointer; + memclump_t *clump; +#if MEMCLUMPING == 2 + if (clumpchain) + return NULL; + clump = &masterclump; +#else + clump = (memclump_t*)attempt_malloc(sizeof(memclump_t)); + if (!clump) + return NULL; +#endif + + // initialize clump + if (developer_memorydebug.integer) + memset(clump, 0xEF, sizeof(*clump)); + clump->sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1); + memset(clump->bits, 0, sizeof(clump->bits)); + clump->sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2); + clump->blocksinuse = 0; + clump->largestavailable = 0; + clump->chain = NULL; + + // link clump into chain + for (clumpchainpointer = &clumpchain;*clumpchainpointer;clumpchainpointer = &(*clumpchainpointer)->chain) + ; + *clumpchainpointer = clump; + + return clump; +} +#endif + +// low level clumping functions, all other memory functions use these +static void *Clump_AllocBlock(size_t size) +{ + unsigned char *base; +#if MEMCLUMPING + if (size <= MEMCLUMPSIZE) + { + int index; + unsigned int bit; + unsigned int needbits; + unsigned int startbit; + unsigned int endbit; + unsigned int needints; + int startindex; + int endindex; + unsigned int value; + unsigned int mask; + unsigned int *array; + memclump_t **clumpchainpointer; + memclump_t *clump; + needbits = (size + MEMUNIT - 1) / MEMUNIT; + needints = (needbits+31)>>5; + for (clumpchainpointer = &clumpchain;;clumpchainpointer = &(*clumpchainpointer)->chain) + { + clump = *clumpchainpointer; + if (!clump) + { + clump = Clump_NewClump(); + if (!clump) + return NULL; + } + if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) + Sys_Error("Clump_AllocBlock: trashed sentinel1\n"); + if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) + Sys_Error("Clump_AllocBlock: trashed sentinel2\n"); + startbit = 0; + endbit = startbit + needbits; + array = clump->bits; + // do as fast a search as possible, even if it means crude alignment + if (needbits >= 32) + { + // large allocations are aligned to large boundaries + // furthermore, they are allocated downward from the top... + endindex = MEMBITINTS; + startindex = endindex - needints; + index = endindex; + while (--index >= startindex) + { + if (array[index]) + { + endindex = index; + startindex = endindex - needints; + if (startindex < 0) + goto nofreeblock; + } + } + startbit = startindex*32; + goto foundblock; + } + else + { + // search for a multi-bit gap in a single int + // (not dealing with the cases that cross two ints) + mask = (1<bits[bit>>5] & (1<<(bit & 31))) + Sys_Error("Clump_AllocBlock: internal error (%i needbits)\n", needbits); + for (bit = startbit;bit < endbit;bit++) + clump->bits[bit>>5] |= (1<<(bit & 31)); + clump->blocksinuse += needbits; + base = clump->block + startbit * MEMUNIT; + if (developer_memorydebug.integer) + memset(base, 0xBF, needbits * MEMUNIT); + return base; +nofreeblock: + ; + } + // never reached + return NULL; + } + // too big, allocate it directly +#endif +#if MEMCLUMPING == 2 + return NULL; +#else + base = (unsigned char *)attempt_malloc(size); + if (base && developer_memorydebug.integer) + memset(base, 0xAF, size); + return base; +#endif +} +static void Clump_FreeBlock(void *base, size_t size) +{ +#if MEMCLUMPING + unsigned int needbits; + unsigned int startbit; + unsigned int endbit; + unsigned int bit; + memclump_t **clumpchainpointer; + memclump_t *clump; + unsigned char *start = (unsigned char *)base; + for (clumpchainpointer = &clumpchain;(clump = *clumpchainpointer);clumpchainpointer = &(*clumpchainpointer)->chain) + { + if (start >= clump->block && start < clump->block + MEMCLUMPSIZE) + { + if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) + Sys_Error("Clump_FreeBlock: trashed sentinel1\n"); + if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) + Sys_Error("Clump_FreeBlock: trashed sentinel2\n"); + if (start + size > clump->block + MEMCLUMPSIZE) + Sys_Error("Clump_FreeBlock: block overrun\n"); + // the block belongs to this clump, clear the range + needbits = (size + MEMUNIT - 1) / MEMUNIT; + startbit = (start - clump->block) / MEMUNIT; + endbit = startbit + needbits; + // first verify all bits are set, otherwise this may be misaligned or a double free + for (bit = startbit;bit < endbit;bit++) + if ((clump->bits[bit>>5] & (1<<(bit & 31))) == 0) + Sys_Error("Clump_FreeBlock: double free\n"); + for (bit = startbit;bit < endbit;bit++) + clump->bits[bit>>5] &= ~(1<<(bit & 31)); + clump->blocksinuse -= needbits; + memset(base, 0xFF, needbits * MEMUNIT); + // if all has been freed, free the clump itself + if (clump->blocksinuse == 0) + { + *clumpchainpointer = clump->chain; + if (developer_memorydebug.integer) + memset(clump, 0xFF, sizeof(*clump)); +#if MEMCLUMPING != 2 + free(clump); +#endif + } + return; + } + } + // does not belong to any known chunk... assume it was a direct allocation +#endif +#if MEMCLUMPING != 2 + memset(base, 0xFF, size); + free(base); +#endif +} + +void *_Mem_Alloc(mempool_t *pool, void *olddata, size_t size, size_t alignment, const char *filename, int fileline) +{ + unsigned int sentinel1; + unsigned int sentinel2; + size_t realsize; + size_t sharedsize; + size_t remainsize; + memheader_t *mem; + memheader_t *oldmem; + unsigned char *base; + + if (size <= 0) + { + if (olddata) + _Mem_Free(olddata, filename, fileline); + return NULL; + } + if (pool == NULL) + Sys_Error("Mem_Alloc: pool == NULL (alloc at %s:%i)", filename, fileline); + if (developer_memory.integer) + Con_DPrintf("Mem_Alloc: pool %s, file %s:%i, size %i bytes\n", pool->name, filename, fileline, (int)size); + //if (developer.integer > 0 && developer_memorydebug.integer) + // _Mem_CheckSentinelsGlobal(filename, fileline); + pool->totalsize += size; + realsize = alignment + sizeof(memheader_t) + size + sizeof(sentinel2); + pool->realsize += realsize; + base = (unsigned char *)Clump_AllocBlock(realsize); + if (base== NULL) + { + Mem_PrintList(0); + Mem_PrintStats(); + Mem_PrintList(1<<30); + Mem_PrintStats(); + Sys_Error("Mem_Alloc: out of memory (alloc at %s:%i)", filename, fileline); + } + // calculate address that aligns the end of the memheader_t to the specified alignment + mem = (memheader_t*)((((size_t)base + sizeof(memheader_t) + (alignment-1)) & ~(alignment-1)) - sizeof(memheader_t)); + mem->baseaddress = (void*)base; + mem->filename = filename; + mem->fileline = fileline; + mem->size = size; + mem->pool = pool; + + // calculate sentinels (detects buffer overruns, in a way that is hard to exploit) + sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); + sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); + mem->sentinel = sentinel1; + memcpy((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2)); + + // append to head of list + mem->next = pool->chain; + mem->prev = NULL; + pool->chain = mem; + if (mem->next) + mem->next->prev = mem; + + // copy the shared portion in the case of a realloc, then memset the rest + sharedsize = 0; + remainsize = size; + if (olddata) + { + oldmem = (memheader_t*)olddata - 1; + sharedsize = min(oldmem->size, size); + memcpy((void *)((unsigned char *) mem + sizeof(memheader_t)), olddata, sharedsize); + remainsize -= sharedsize; + _Mem_Free(olddata, filename, fileline); + } + memset((void *)((unsigned char *) mem + sizeof(memheader_t) + sharedsize), 0, remainsize); + return (void *)((unsigned char *) mem + sizeof(memheader_t)); +} + +// only used by _Mem_Free and _Mem_FreePool +static void _Mem_FreeBlock(memheader_t *mem, const char *filename, int fileline) +{ + mempool_t *pool; + size_t size; + size_t realsize; + unsigned int sentinel1; + unsigned int sentinel2; + + // check sentinels (detects buffer overruns, in a way that is hard to exploit) + sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); + sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); + if (mem->sentinel != sentinel1) + Sys_Error("Mem_Free: trashed head sentinel (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline); + if (memcmp((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2))) + Sys_Error("Mem_Free: trashed tail sentinel (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline); + + pool = mem->pool; + if (developer_memory.integer) + Con_DPrintf("Mem_Free: pool %s, alloc %s:%i, free %s:%i, size %i bytes\n", pool->name, mem->filename, mem->fileline, filename, fileline, (int)(mem->size)); + // unlink memheader from doubly linked list + if ((mem->prev ? mem->prev->next != mem : pool->chain != mem) || (mem->next && mem->next->prev != mem)) + Sys_Error("Mem_Free: not allocated or double freed (free at %s:%i)", filename, fileline); + if (mem->prev) + mem->prev->next = mem->next; + else + pool->chain = mem->next; + if (mem->next) + mem->next->prev = mem->prev; + // memheader has been unlinked, do the actual free now + size = mem->size; + realsize = sizeof(memheader_t) + size + sizeof(sentinel2); + pool->totalsize -= size; + pool->realsize -= realsize; + Clump_FreeBlock(mem->baseaddress, realsize); +} + +void _Mem_Free(void *data, const char *filename, int fileline) +{ + if (data == NULL) + { + Con_DPrintf("Mem_Free: data == NULL (called at %s:%i)\n", filename, fileline); + return; + } + + if (developer_memorydebug.integer) + { + //_Mem_CheckSentinelsGlobal(filename, fileline); + if (!Mem_IsAllocated(NULL, data)) + Sys_Error("Mem_Free: data is not allocated (called at %s:%i)", filename, fileline); + } + + _Mem_FreeBlock((memheader_t *)((unsigned char *) data - sizeof(memheader_t)), filename, fileline); +} + +mempool_t *_Mem_AllocPool(const char *name, int flags, mempool_t *parent, const char *filename, int fileline) +{ + mempool_t *pool; + if (developer_memorydebug.integer) + _Mem_CheckSentinelsGlobal(filename, fileline); + pool = (mempool_t *)Clump_AllocBlock(sizeof(mempool_t)); + if (pool == NULL) + { + Mem_PrintList(0); + Mem_PrintStats(); + Mem_PrintList(1<<30); + Mem_PrintStats(); + Sys_Error("Mem_AllocPool: out of memory (allocpool at %s:%i)", filename, fileline); + } + memset(pool, 0, sizeof(mempool_t)); + pool->sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1); + pool->sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2); + pool->filename = filename; + pool->fileline = fileline; + pool->flags = flags; + pool->chain = NULL; + pool->totalsize = 0; + pool->realsize = sizeof(mempool_t); + strlcpy (pool->name, name, sizeof (pool->name)); + pool->parent = parent; + pool->next = poolchain; + poolchain = pool; + return pool; +} + +void _Mem_FreePool(mempool_t **poolpointer, const char *filename, int fileline) +{ + mempool_t *pool = *poolpointer; + mempool_t **chainaddress, *iter, *temp; + + if (developer_memorydebug.integer) + _Mem_CheckSentinelsGlobal(filename, fileline); + if (pool) + { + // unlink pool from chain + for (chainaddress = &poolchain;*chainaddress && *chainaddress != pool;chainaddress = &((*chainaddress)->next)); + if (*chainaddress != pool) + Sys_Error("Mem_FreePool: pool already free (freepool at %s:%i)", filename, fileline); + if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) + Sys_Error("Mem_FreePool: trashed pool sentinel 1 (allocpool at %s:%i, freepool at %s:%i)", pool->filename, pool->fileline, filename, fileline); + if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) + Sys_Error("Mem_FreePool: trashed pool sentinel 2 (allocpool at %s:%i, freepool at %s:%i)", pool->filename, pool->fileline, filename, fileline); + *chainaddress = pool->next; + + // free memory owned by the pool + while (pool->chain) + _Mem_FreeBlock(pool->chain, filename, fileline); + + // free child pools, too + for(iter = poolchain; iter; temp = iter = iter->next) + if(iter->parent == pool) + _Mem_FreePool(&temp, filename, fileline); + + // free the pool itself + Clump_FreeBlock(pool, sizeof(*pool)); + + *poolpointer = NULL; + } +} + +void _Mem_EmptyPool(mempool_t *pool, const char *filename, int fileline) +{ + mempool_t *chainaddress; + + if (developer_memorydebug.integer) + { + //_Mem_CheckSentinelsGlobal(filename, fileline); + // check if this pool is in the poolchain + for (chainaddress = poolchain;chainaddress;chainaddress = chainaddress->next) + if (chainaddress == pool) + break; + if (!chainaddress) + Sys_Error("Mem_EmptyPool: pool is already free (emptypool at %s:%i)", filename, fileline); + } + if (pool == NULL) + Sys_Error("Mem_EmptyPool: pool == NULL (emptypool at %s:%i)", filename, fileline); + if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) + Sys_Error("Mem_EmptyPool: trashed pool sentinel 1 (allocpool at %s:%i, emptypool at %s:%i)", pool->filename, pool->fileline, filename, fileline); + if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) + Sys_Error("Mem_EmptyPool: trashed pool sentinel 2 (allocpool at %s:%i, emptypool at %s:%i)", pool->filename, pool->fileline, filename, fileline); + + // free memory owned by the pool + while (pool->chain) + _Mem_FreeBlock(pool->chain, filename, fileline); + + // empty child pools, too + for(chainaddress = poolchain; chainaddress; chainaddress = chainaddress->next) + if(chainaddress->parent == pool) + _Mem_EmptyPool(chainaddress, filename, fileline); + +} + +void _Mem_CheckSentinels(void *data, const char *filename, int fileline) +{ + memheader_t *mem; + unsigned int sentinel1; + unsigned int sentinel2; + + if (data == NULL) + Sys_Error("Mem_CheckSentinels: data == NULL (sentinel check at %s:%i)", filename, fileline); + + mem = (memheader_t *)((unsigned char *) data - sizeof(memheader_t)); + sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); + sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); + if (mem->sentinel != sentinel1) + Sys_Error("Mem_Free: trashed head sentinel (alloc at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline); + if (memcmp((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2))) + Sys_Error("Mem_Free: trashed tail sentinel (alloc at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline); +} + +#if MEMCLUMPING +static void _Mem_CheckClumpSentinels(memclump_t *clump, const char *filename, int fileline) +{ + // this isn't really very useful + if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) + Sys_Error("Mem_CheckClumpSentinels: trashed sentinel 1 (sentinel check at %s:%i)", filename, fileline); + if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) + Sys_Error("Mem_CheckClumpSentinels: trashed sentinel 2 (sentinel check at %s:%i)", filename, fileline); +} +#endif + +void _Mem_CheckSentinelsGlobal(const char *filename, int fileline) +{ + memheader_t *mem; +#if MEMCLUMPING + memclump_t *clump; +#endif + mempool_t *pool; + for (pool = poolchain;pool;pool = pool->next) + { + if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) + Sys_Error("Mem_CheckSentinelsGlobal: trashed pool sentinel 1 (allocpool at %s:%i, sentinel check at %s:%i)", pool->filename, pool->fileline, filename, fileline); + if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) + Sys_Error("Mem_CheckSentinelsGlobal: trashed pool sentinel 2 (allocpool at %s:%i, sentinel check at %s:%i)", pool->filename, pool->fileline, filename, fileline); + } + for (pool = poolchain;pool;pool = pool->next) + for (mem = pool->chain;mem;mem = mem->next) + _Mem_CheckSentinels((void *)((unsigned char *) mem + sizeof(memheader_t)), filename, fileline); +#if MEMCLUMPING + for (pool = poolchain;pool;pool = pool->next) + for (clump = clumpchain;clump;clump = clump->chain) + _Mem_CheckClumpSentinels(clump, filename, fileline); +#endif +} + +qboolean Mem_IsAllocated(mempool_t *pool, void *data) +{ + memheader_t *header; + memheader_t *target; + + if (pool) + { + // search only one pool + target = (memheader_t *)((unsigned char *) data - sizeof(memheader_t)); + for( header = pool->chain ; header ; header = header->next ) + if( header == target ) + return true; + } + else + { + // search all pools + for (pool = poolchain;pool;pool = pool->next) + if (Mem_IsAllocated(pool, data)) + return true; + } + return false; +} + +void Mem_ExpandableArray_NewArray(memexpandablearray_t *l, mempool_t *mempool, size_t recordsize, int numrecordsperarray) +{ + memset(l, 0, sizeof(*l)); + l->mempool = mempool; + l->recordsize = recordsize; + l->numrecordsperarray = numrecordsperarray; +} + +void Mem_ExpandableArray_FreeArray(memexpandablearray_t *l) +{ + size_t i; + if (l->maxarrays) + { + for (i = 0;i != l->numarrays;i++) + Mem_Free(l->arrays[i].data); + Mem_Free(l->arrays); + } + memset(l, 0, sizeof(*l)); +} + +// VorteX: hacked Mem_ExpandableArray_AllocRecord, it does allocate record at certain index +void *Mem_ExpandableArray_AllocRecordAtIndex(memexpandablearray_t *l, size_t index) +{ + size_t j; + if (index == l->numarrays) + { + if (l->numarrays == l->maxarrays) + { + memexpandablearray_array_t *oldarrays = l->arrays; + l->maxarrays = max(l->maxarrays * 2, 128); + l->arrays = (memexpandablearray_array_t*) Mem_Alloc(l->mempool, l->maxarrays * sizeof(*l->arrays)); + if (oldarrays) + { + memcpy(l->arrays, oldarrays, l->numarrays * sizeof(*l->arrays)); + Mem_Free(oldarrays); + } + } + l->arrays[index].numflaggedrecords = 0; + l->arrays[index].data = (unsigned char *) Mem_Alloc(l->mempool, (l->recordsize + 1) * l->numrecordsperarray); + l->arrays[index].allocflags = l->arrays[index].data + l->recordsize * l->numrecordsperarray; + l->numarrays++; + } + if (l->arrays[index].numflaggedrecords < l->numrecordsperarray) + { + for (j = 0;j < l->numrecordsperarray;j++) + { + if (!l->arrays[index].allocflags[j]) + { + l->arrays[index].allocflags[j] = true; + l->arrays[index].numflaggedrecords++; + memset(l->arrays[index].data + l->recordsize * j, 0, l->recordsize); + return (void *)(l->arrays[index].data + l->recordsize * j); + } + } + } + return NULL; +} + +void *Mem_ExpandableArray_AllocRecord(memexpandablearray_t *l) +{ + size_t i, j; + for (i = 0;;i++) + { + if (i == l->numarrays) + { + if (l->numarrays == l->maxarrays) + { + memexpandablearray_array_t *oldarrays = l->arrays; + l->maxarrays = max(l->maxarrays * 2, 128); + l->arrays = (memexpandablearray_array_t*) Mem_Alloc(l->mempool, l->maxarrays * sizeof(*l->arrays)); + if (oldarrays) + { + memcpy(l->arrays, oldarrays, l->numarrays * sizeof(*l->arrays)); + Mem_Free(oldarrays); + } + } + l->arrays[i].numflaggedrecords = 0; + l->arrays[i].data = (unsigned char *) Mem_Alloc(l->mempool, (l->recordsize + 1) * l->numrecordsperarray); + l->arrays[i].allocflags = l->arrays[i].data + l->recordsize * l->numrecordsperarray; + l->numarrays++; + } + if (l->arrays[i].numflaggedrecords < l->numrecordsperarray) + { + for (j = 0;j < l->numrecordsperarray;j++) + { + if (!l->arrays[i].allocflags[j]) + { + l->arrays[i].allocflags[j] = true; + l->arrays[i].numflaggedrecords++; + memset(l->arrays[i].data + l->recordsize * j, 0, l->recordsize); + return (void *)(l->arrays[i].data + l->recordsize * j); + } + } + } + } +} + +/***************************************************************************** + * IF YOU EDIT THIS: + * If this function was to change the size of the "expandable" array, you have + * to update r_shadow.c + * Just do a search for "range =", R_ShadowClearWorldLights would be the first + * function to look at. (And also seems like the only one?) You might have to + * move the call to Mem_ExpandableArray_IndexRange back into for(...) loop's + * condition + */ +void Mem_ExpandableArray_FreeRecord(memexpandablearray_t *l, void *record) // const! +{ + size_t i, j; + unsigned char *p = (unsigned char *)record; + for (i = 0;i != l->numarrays;i++) + { + if (p >= l->arrays[i].data && p < (l->arrays[i].data + l->recordsize * l->numrecordsperarray)) + { + j = (p - l->arrays[i].data) / l->recordsize; + if (p != l->arrays[i].data + j * l->recordsize) + Sys_Error("Mem_ExpandableArray_FreeRecord: no such record %p\n", p); + if (!l->arrays[i].allocflags[j]) + Sys_Error("Mem_ExpandableArray_FreeRecord: record %p is already free!\n", p); + l->arrays[i].allocflags[j] = false; + l->arrays[i].numflaggedrecords--; + return; + } + } +} + +size_t Mem_ExpandableArray_IndexRange(const memexpandablearray_t *l) +{ + size_t i, j, k, end = 0; + for (i = 0;i < l->numarrays;i++) + { + for (j = 0, k = 0;k < l->arrays[i].numflaggedrecords;j++) + { + if (l->arrays[i].allocflags[j]) + { + end = l->numrecordsperarray * i + j + 1; + k++; + } + } + } + return end; +} + +void *Mem_ExpandableArray_RecordAtIndex(const memexpandablearray_t *l, size_t index) +{ + size_t i, j; + i = index / l->numrecordsperarray; + j = index % l->numrecordsperarray; + if (i >= l->numarrays || !l->arrays[i].allocflags[j]) + return NULL; + return (void *)(l->arrays[i].data + j * l->recordsize); +} + + +// used for temporary memory allocations around the engine, not for longterm +// storage, if anything in this pool stays allocated during gameplay, it is +// considered a leak +mempool_t *tempmempool; +// only for zone +mempool_t *zonemempool; + +void Mem_PrintStats(void) +{ + size_t count = 0, size = 0, realsize = 0; + mempool_t *pool; + memheader_t *mem; + Mem_CheckSentinelsGlobal(); + for (pool = poolchain;pool;pool = pool->next) + { + count++; + size += pool->totalsize; + realsize += pool->realsize; + } + Con_Printf("%lu memory pools, totalling %lu bytes (%.3fMB)\n", (unsigned long)count, (unsigned long)size, size / 1048576.0); + Con_Printf("total allocated size: %lu bytes (%.3fMB)\n", (unsigned long)realsize, realsize / 1048576.0); + for (pool = poolchain;pool;pool = pool->next) + { + if ((pool->flags & POOLFLAG_TEMP) && pool->chain) + { + Con_Printf("Memory pool %p has sprung a leak totalling %lu bytes (%.3fMB)! Listing contents...\n", (void *)pool, (unsigned long)pool->totalsize, pool->totalsize / 1048576.0); + for (mem = pool->chain;mem;mem = mem->next) + Con_Printf("%10lu bytes allocated at %s:%i\n", (unsigned long)mem->size, mem->filename, mem->fileline); + } + } +} + +void Mem_PrintList(size_t minallocationsize) +{ + mempool_t *pool; + memheader_t *mem; + Mem_CheckSentinelsGlobal(); + Con_Print("memory pool list:\n" + "size name\n"); + for (pool = poolchain;pool;pool = pool->next) + { + Con_Printf("%10luk (%10luk actual) %s (%+li byte change) %s\n", (unsigned long) ((pool->totalsize + 1023) / 1024), (unsigned long)((pool->realsize + 1023) / 1024), pool->name, (long)(pool->totalsize - pool->lastchecksize), (pool->flags & POOLFLAG_TEMP) ? "TEMP" : ""); + pool->lastchecksize = pool->totalsize; + for (mem = pool->chain;mem;mem = mem->next) + if (mem->size >= minallocationsize) + Con_Printf("%10lu bytes allocated at %s:%i\n", (unsigned long)mem->size, mem->filename, mem->fileline); + } +} + +void MemList_f(void) +{ + switch(Cmd_Argc()) + { + case 1: + Mem_PrintList(1<<30); + Mem_PrintStats(); + break; + case 2: + Mem_PrintList(atoi(Cmd_Argv(1)) * 1024); + Mem_PrintStats(); + break; + default: + Con_Print("MemList_f: unrecognized options\nusage: memlist [all]\n"); + break; + } +} + +extern void R_TextureStats_Print(qboolean printeach, qboolean printpool, qboolean printtotal); +void MemStats_f(void) +{ + Mem_CheckSentinelsGlobal(); + R_TextureStats_Print(false, false, true); + GL_Mesh_ListVBOs(false); + Mem_PrintStats(); +} + + +char* Mem_strdup (mempool_t *pool, const char* s) +{ + char* p; + size_t sz; + if (s == NULL) + return NULL; + sz = strlen (s) + 1; + p = (char*)Mem_Alloc (pool, sz); + strlcpy (p, s, sz); + return p; +} + +/* +======================== +Memory_Init +======================== +*/ +void Memory_Init (void) +{ + static union {unsigned short s;unsigned char b[2];} u; + u.s = 0x100; + mem_bigendian = u.b[0] != 0; + + sentinel_seed = rand(); + poolchain = NULL; + tempmempool = Mem_AllocPool("Temporary Memory", POOLFLAG_TEMP, NULL); + zonemempool = Mem_AllocPool("Zone", 0, NULL); +} + +void Memory_Shutdown (void) +{ +// Mem_FreePool (&zonemempool); +// Mem_FreePool (&tempmempool); +} + +void Memory_Init_Commands (void) +{ + Cmd_AddCommand ("memstats", MemStats_f, "prints memory system statistics"); + Cmd_AddCommand ("memlist", MemList_f, "prints memory pool information (or if used as memlist 5 lists individual allocations of 5K or larger, 0 lists all allocations)"); + Cvar_RegisterVariable (&developer_memory); + Cvar_RegisterVariable (&developer_memorydebug); + Cvar_RegisterVariable (&sys_memsize_physical); + Cvar_RegisterVariable (&sys_memsize_virtual); + +#if defined(WIN32) +#ifdef _WIN64 + { + MEMORYSTATUSEX status; + // first guess + Cvar_SetValueQuick(&sys_memsize_virtual, 8388608); + // then improve + status.dwLength = sizeof(status); + if(GlobalMemoryStatusEx(&status)) + { + Cvar_SetValueQuick(&sys_memsize_physical, status.ullTotalPhys / 1048576.0); + Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value, status.ullTotalVirtual / 1048576.0)); + } + } +#else + { + MEMORYSTATUS status; + // first guess + Cvar_SetValueQuick(&sys_memsize_virtual, 2048); + // then improve + status.dwLength = sizeof(status); + GlobalMemoryStatus(&status); + Cvar_SetValueQuick(&sys_memsize_physical, status.dwTotalPhys / 1048576.0); + Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value, status.dwTotalVirtual / 1048576.0)); + } +#endif +#else + { + // first guess + Cvar_SetValueQuick(&sys_memsize_virtual, (sizeof(void*) == 4) ? 2048 : 268435456); + // then improve + { + // Linux, and BSD with linprocfs mounted + FILE *f = fopen("/proc/meminfo", "r"); + if(f) + { + static char buf[1024]; + while(fgets(buf, sizeof(buf), f)) + { + const char *p = buf; + if(!COM_ParseToken_Console(&p)) + continue; + if(!strcmp(com_token, "MemTotal:")) + { + if(!COM_ParseToken_Console(&p)) + continue; + Cvar_SetValueQuick(&sys_memsize_physical, atof(com_token) / 1024.0); + } + if(!strcmp(com_token, "SwapTotal:")) + { + if(!COM_ParseToken_Console(&p)) + continue; + Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value , atof(com_token) / 1024.0 + sys_memsize_physical.value)); + } + } + fclose(f); + } + } + } +#endif +} + diff --git a/misc/source/darkplaces-src/zone.h b/misc/source/darkplaces-src/zone.h new file mode 100644 index 00000000..065f1c0c --- /dev/null +++ b/misc/source/darkplaces-src/zone.h @@ -0,0 +1,145 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef ZONE_H +#define ZONE_H + +extern qboolean mem_bigendian; + +// div0: heap overflow detection paranoia +#define MEMPARANOIA 0 + +#define POOLNAMESIZE 128 +// if set this pool will be printed in memlist reports +#define POOLFLAG_TEMP 1 + +typedef struct memheader_s +{ + // address returned by Chunk_Alloc (may be significantly before this header to satisify alignment) + void *baseaddress; + // next and previous memheaders in chain belonging to pool + struct memheader_s *next; + struct memheader_s *prev; + // pool this memheader belongs to + struct mempool_s *pool; + // size of the memory after the header (excluding header and sentinel2) + size_t size; + // file name and line where Mem_Alloc was called + const char *filename; + int fileline; + // should always be equal to MEMHEADER_SENTINEL_FOR_ADDRESS() + unsigned int sentinel; + // immediately followed by data, which is followed by another copy of mem_sentinel[] +} +memheader_t; + +typedef struct mempool_s +{ + // should always be MEMPOOL_SENTINEL + unsigned int sentinel1; + // chain of individual memory allocations + struct memheader_s *chain; + // POOLFLAG_* + int flags; + // total memory allocated in this pool (inside memheaders) + size_t totalsize; + // total memory allocated in this pool (actual malloc total) + size_t realsize; + // updated each time the pool is displayed by memlist, shows change from previous time (unless pool was freed) + size_t lastchecksize; + // linked into global mempool list + struct mempool_s *next; + // parent object (used for nested memory pools) + struct mempool_s *parent; + // file name and line where Mem_AllocPool was called + const char *filename; + int fileline; + // name of the pool + char name[POOLNAMESIZE]; + // should always be MEMPOOL_SENTINEL + unsigned int sentinel2; +} +mempool_t; + +#define Mem_Alloc(pool,size) _Mem_Alloc(pool, NULL, size, 16, __FILE__, __LINE__) +#define Mem_Memalign(pool,alignment,size) _Mem_Alloc(pool, NULL, size, alignment, __FILE__, __LINE__) +#define Mem_Realloc(pool,data,size) _Mem_Alloc(pool, data, size, 16, __FILE__, __LINE__) +#define Mem_Free(mem) _Mem_Free(mem, __FILE__, __LINE__) +#define Mem_CheckSentinels(data) _Mem_CheckSentinels(data, __FILE__, __LINE__) +#define Mem_CheckSentinelsGlobal() _Mem_CheckSentinelsGlobal(__FILE__, __LINE__) +#define Mem_AllocPool(name, flags, parent) _Mem_AllocPool(name, flags, parent, __FILE__, __LINE__) +#define Mem_FreePool(pool) _Mem_FreePool(pool, __FILE__, __LINE__) +#define Mem_EmptyPool(pool) _Mem_EmptyPool(pool, __FILE__, __LINE__) + +void *_Mem_Alloc(mempool_t *pool, void *data, size_t size, size_t alignment, const char *filename, int fileline); +void _Mem_Free(void *data, const char *filename, int fileline); +mempool_t *_Mem_AllocPool(const char *name, int flags, mempool_t *parent, const char *filename, int fileline); +void _Mem_FreePool(mempool_t **pool, const char *filename, int fileline); +void _Mem_EmptyPool(mempool_t *pool, const char *filename, int fileline); +void _Mem_CheckSentinels(void *data, const char *filename, int fileline); +void _Mem_CheckSentinelsGlobal(const char *filename, int fileline); +// if pool is NULL this searches ALL pools for the allocation +qboolean Mem_IsAllocated(mempool_t *pool, void *data); + +char* Mem_strdup (mempool_t *pool, const char* s); + +typedef struct memexpandablearray_array_s +{ + unsigned char *data; + unsigned char *allocflags; + size_t numflaggedrecords; +} +memexpandablearray_array_t; + +typedef struct memexpandablearray_s +{ + mempool_t *mempool; + size_t recordsize; + size_t numrecordsperarray; + size_t numarrays; + size_t maxarrays; + memexpandablearray_array_t *arrays; +} +memexpandablearray_t; + +void Mem_ExpandableArray_NewArray(memexpandablearray_t *l, mempool_t *mempool, size_t recordsize, int numrecordsperarray); +void Mem_ExpandableArray_FreeArray(memexpandablearray_t *l); +void *Mem_ExpandableArray_AllocRecord(memexpandablearray_t *l); +void *Mem_ExpandableArray_AllocRecordAtIndex(memexpandablearray_t *l, size_t index); +void Mem_ExpandableArray_FreeRecord(memexpandablearray_t *l, void *record); +size_t Mem_ExpandableArray_IndexRange(const memexpandablearray_t *l) DP_FUNC_PURE; +void *Mem_ExpandableArray_RecordAtIndex(const memexpandablearray_t *l, size_t index) DP_FUNC_PURE; + +// used for temporary allocations +extern mempool_t *tempmempool; + +void Memory_Init (void); +void Memory_Shutdown (void); +void Memory_Init_Commands (void); + +extern mempool_t *zonemempool; +#define Z_Malloc(size) Mem_Alloc(zonemempool,size) +#define Z_Free(data) Mem_Free(data) + +extern struct cvar_s developer_memory; +extern struct cvar_s developer_memorydebug; + +#endif + diff --git a/misc/source/netradiant-src/libs/filematch.c b/misc/source/netradiant-src/libs/filematch.c new file mode 100644 index 00000000..7989cbb2 --- /dev/null +++ b/misc/source/netradiant-src/libs/filematch.c @@ -0,0 +1,69 @@ +#include +#include "filematch.h" + +// LordHavoc: some portable directory listing code I wrote for lmp2pcx, now used in darkplaces to load id1/*.pak and such... + +int matchpattern(const char *in, const char *pattern, int caseinsensitive) +{ + return matchpattern_with_separator(in, pattern, caseinsensitive, "/\\:", 0); +} + +// wildcard_least_one: if true * matches 1 or more characters +// if false * matches 0 or more characters +int matchpattern_with_separator(const char *in, const char *pattern, int caseinsensitive, const char *separators, int wildcard_least_one) +{ + int c1, c2; + while (*pattern) + { + switch (*pattern) + { + case 0: + return 1; // end of pattern + case '?': // match any single character + if (*in == 0 || strchr(separators, *in)) + return 0; // no match + in++; + pattern++; + break; + case '*': // match anything until following string + if(wildcard_least_one) + { + if (*in == 0 || strchr(separators, *in)) + return 0; // no match + in++; + } + pattern++; + while (*in) + { + if (strchr(separators, *in)) + break; + // see if pattern matches at this offset + if (matchpattern_with_separator(in, pattern, caseinsensitive, separators, wildcard_least_one)) + return 1; + // nope, advance to next offset + in++; + } + break; + default: + if (*in != *pattern) + { + if (!caseinsensitive) + return 0; // no match + c1 = *in; + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + c2 = *pattern; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + if (c1 != c2) + return 0; // no match + } + in++; + pattern++; + break; + } + } + if (*in) + return 0; // reached end of pattern but not end of input + return 1; // success +} diff --git a/misc/source/netradiant-src/libs/filematch.h b/misc/source/netradiant-src/libs/filematch.h new file mode 100644 index 00000000..f075b75c --- /dev/null +++ b/misc/source/netradiant-src/libs/filematch.h @@ -0,0 +1,16 @@ +#if !defined(INCLUDED_FILEMATCH_H) +#define INCLUDED_FILEMATCH_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +int matchpattern(const char *in, const char *pattern, int caseinsensitive); +int matchpattern_with_separator(const char *in, const char *pattern, int caseinsensitive, const char *separators, int wildcard_least_one); + +#ifdef __cplusplus +} +#endif + +#endif